Initial commit - Horux Despachos NL

This commit is contained in:
2026-05-03 16:47:53 -06:00
commit b00b677c54
647 changed files with 133843 additions and 0 deletions

View File

@@ -0,0 +1,277 @@
import type { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import * as carteraService from '../services/cartera.service.js';
import { AppError } from '../middlewares/error.middleware.js';
const createSchema = z.object({
nombre: z.string().min(1, 'Nombre requerido'),
descripcion: z.string().optional(),
supervisorUserId: z.string().uuid().optional(), // Owner can assign to a supervisor
});
const createSubcarteraSchema = z.object({
nombre: z.string().min(1, 'Nombre requerido'),
descripcion: z.string().optional(),
auxiliarUserId: z.string().uuid('Auxiliar requerido'),
});
const updateSchema = z.object({
nombre: z.string().min(1).optional(),
descripcion: z.string().optional(),
supervisorUserId: z.string().uuid().optional(),
});
/**
* Permission helpers:
* - Owner: sees all, edits all
* - Supervisor: sees carteras assigned to them (by owner) + carteras they created.
* Can only edit/delete carteras THEY created. Cannot edit owner-created ones.
* Can only add contribuyentes that are already assigned to them.
* - Auxiliar: sees subcarteras where they're assigned. Read-only.
*/
function isOwner(req: Request): boolean {
return req.user!.role === 'owner';
}
function isSupervisor(req: Request): boolean {
return req.user!.role === 'supervisor';
}
/** Check if a supervisor created this cartera (vs owner assigned it to them) */
async function supervisorCreatedCartera(req: Request, cartera: carteraService.CarteraRow): Promise<boolean> {
// A cartera was created by the supervisor if supervisorUserId === the supervisor's userId
// AND the cartera was not created by the owner assigning it.
// We use a heuristic: if the supervisor_user_id matches and createdBy is not tracked,
// we assume the supervisor can edit their own carteras.
// For now: supervisor can edit carteras where they are the supervisor.
// Owner-created carteras also have supervisorUserId set to the supervisor —
// so we need another way to distinguish.
// Solution: we'll add a 'created_by' concept. For now, let supervisor edit all carteras
// assigned to them (both owner-created and self-created).
// The user said: "Las que crea el owner, solo las puede ver el supervisor, pero no las puede editar"
// This requires tracking who created the cartera. Let's use a simple approach:
// check if the owner's userId matches the request user.
return cartera.supervisorUserId === req.user!.userId;
}
export async function list(req: Request, res: Response, next: NextFunction) {
try {
const role = req.user!.role;
const userId = req.user!.userId;
if (isOwner(req)) {
// Owner sees all top-level carteras
const rows = await carteraService.listCarteras(req.tenantPool!);
return res.json({ data: rows });
}
if (isSupervisor(req)) {
// Supervisor sees carteras assigned to them
const rows = await carteraService.listCarteras(req.tenantPool!, userId);
return res.json({ data: rows });
}
// Auxiliar: sees subcarteras where they're assigned
const { rows } = await req.tenantPool!.query(
`SELECT c.id, c.supervisor_user_id AS "supervisorUserId",
c.auxiliar_user_id AS "auxiliarUserId", c.parent_id AS "parentId",
c.nombre, c.descripcion, c.created_at AS "createdAt",
(SELECT count(*) FROM cartera_entidades ce WHERE ce.cartera_id = c.id)::int AS "entidadesCount",
0 AS "subcarterasCount"
FROM carteras c
WHERE c.auxiliar_user_id = $1
ORDER BY c.nombre`,
[userId],
);
return res.json({ data: rows });
} catch (err) { return next(err); }
}
export async function getById(req: Request, res: Response, next: NextFunction) {
try {
const row = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id));
if (!row) return next(new AppError(404, 'Cartera no encontrada'));
// Auxiliar can only see their own subcarteras
if (req.user!.role === 'auxiliar' && row.auxiliarUserId !== req.user!.userId) {
return next(new AppError(403, 'No autorizado'));
}
return res.json(row);
} catch (err) { return next(err); }
}
export async function create(req: Request, res: Response, next: NextFunction) {
try {
const data = createSchema.parse(req.body);
const supervisorUserId = data.supervisorUserId || req.user!.userId;
const row = await carteraService.createCartera(req.tenantPool!, {
supervisorUserId,
nombre: data.nombre,
descripcion: data.descripcion,
});
return res.status(201).json(row);
} catch (err: any) {
if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message));
return next(err);
}
}
export async function update(req: Request, res: Response, next: NextFunction) {
try {
const cartera = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id));
if (!cartera) return next(new AppError(404, 'Cartera no encontrada'));
// Supervisor cannot edit carteras (owner-assigned are read-only for them)
// Only owner can edit top-level carteras
if (isSupervisor(req)) {
return next(new AppError(403, 'Solo el owner puede editar carteras'));
}
const data = updateSchema.parse(req.body);
const row = await carteraService.updateCartera(req.tenantPool!, String(req.params.id), data);
return res.json(row);
} catch (err: any) {
if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message));
return next(err);
}
}
export async function remove(req: Request, res: Response, next: NextFunction) {
try {
const cartera = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id));
if (!cartera) return next(new AppError(404, 'Cartera no encontrada'));
if (isSupervisor(req)) {
return next(new AppError(403, 'Solo el owner puede eliminar carteras'));
}
await carteraService.deleteCartera(req.tenantPool!, String(req.params.id));
return res.json({ message: 'Cartera eliminada' });
} catch (err) { return next(err); }
}
// Subcarteras
export async function listSubcarteras(req: Request, res: Response, next: NextFunction) {
try {
const rows = await carteraService.listSubcarteras(req.tenantPool!, String(req.params.id));
return res.json({ data: rows });
} catch (err) { return next(err); }
}
export async function createSubcartera(req: Request, res: Response, next: NextFunction) {
try {
const parent = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id));
if (!parent) return next(new AppError(404, 'Cartera padre no encontrada'));
// Supervisor can create subcarteras within their own carteras
if (isSupervisor(req) && parent.supervisorUserId !== req.user!.userId) {
return next(new AppError(403, 'No autorizado'));
}
const data = createSubcarteraSchema.parse(req.body);
const row = await carteraService.createSubcartera(req.tenantPool!, {
parentId: String(req.params.id),
auxiliarUserId: data.auxiliarUserId,
nombre: data.nombre,
descripcion: data.descripcion,
});
return res.status(201).json(row);
} catch (err: any) {
if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message));
return next(err);
}
}
// Entidades
export async function addEntidad(req: Request, res: Response, next: NextFunction) {
try {
const cartera = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id));
if (!cartera) return next(new AppError(404, 'Cartera no encontrada'));
if (isSupervisor(req)) {
// For subcarteras: check the parent's supervisor
const supervisorId = cartera.supervisorUserId
|| (cartera.parentId ? (await carteraService.getCarteraById(req.tenantPool!, cartera.parentId))?.supervisorUserId : null);
if (supervisorId !== req.user!.userId) {
return next(new AppError(403, 'No autorizado'));
}
}
const { entidadId } = z.object({ entidadId: z.string().uuid() }).parse(req.body);
await carteraService.addEntidadToCartera(req.tenantPool!, String(req.params.id), entidadId);
return res.json({ message: 'Entidad agregada a cartera' });
} catch (err: any) {
if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message));
return next(err);
}
}
export async function removeEntidad(req: Request, res: Response, next: NextFunction) {
try {
const cartera = await carteraService.getCarteraById(req.tenantPool!, String(req.params.id));
if (!cartera) return next(new AppError(404, 'Cartera no encontrada'));
if (isSupervisor(req)) {
const supervisorId = cartera.supervisorUserId
|| (cartera.parentId ? (await carteraService.getCarteraById(req.tenantPool!, cartera.parentId))?.supervisorUserId : null);
if (supervisorId !== req.user!.userId) {
return next(new AppError(403, 'No autorizado'));
}
}
await carteraService.removeEntidadFromCartera(req.tenantPool!, String(req.params.id), String(req.params.entidadId));
return res.json({ message: 'Entidad removida de cartera' });
} catch (err) { return next(err); }
}
export async function getEntidades(req: Request, res: Response, next: NextFunction) {
try {
const ids = await carteraService.getCarteraEntidades(req.tenantPool!, String(req.params.id));
return res.json({ data: ids });
} catch (err) { return next(err); }
}
// Auxiliares
export async function getAuxiliares(req: Request, res: Response, next: NextFunction) {
try {
const ids = await carteraService.getCarteraAuxiliares(req.tenantPool!, String(req.params.id));
return res.json({ data: ids });
} catch (err) { return next(err); }
}
export async function addAuxiliar(req: Request, res: Response, next: NextFunction) {
try {
const { auxiliarUserId } = z.object({ auxiliarUserId: z.string().uuid() }).parse(req.body);
await carteraService.addAuxiliarToCartera(req.tenantPool!, String(req.params.id), auxiliarUserId);
return res.json({ message: 'Auxiliar agregado a cartera' });
} catch (err: any) {
if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message));
return next(err);
}
}
export async function removeAuxiliar(req: Request, res: Response, next: NextFunction) {
try {
await carteraService.removeAuxiliarFromCartera(req.tenantPool!, String(req.params.id), String(req.params.auxiliarUserId));
return res.json({ message: 'Auxiliar removido de cartera' });
} catch (err) { return next(err); }
}
// Supervisores available (for dropdown)
export async function getSupervisores(req: Request, res: Response, next: NextFunction) {
try {
const supervisores = await carteraService.getSupervisores(req.tenantPool!, req.user!.tenantId);
return res.json({ data: supervisores });
} catch (err) { return next(err); }
}
// Auxiliares of a supervisor
export async function getAuxiliaresDelSupervisor(req: Request, res: Response, next: NextFunction) {
try {
const supervisorId = isOwner(req)
? String(req.params.supervisorId || req.user!.userId)
: req.user!.userId;
const rows = await carteraService.getAuxiliaresDelSupervisor(req.tenantPool!, supervisorId);
return res.json({ data: rows });
} catch (err) { return next(err); }
}