Files
HoruxDespachosNuevo/apps/api/src/controllers/asignaciones.controller.ts
Horux Dev f43cb165c6 feat: asignaciones obligaciones/tareas + fixes backend
- Migracion 046: tablas obligacion_asignaciones y tarea_asignaciones
- Servicio y controller de asignaciones (CRUD + listados)
- Fix: enviar correo welcome al invitar usuario nuevo
- Fix: quitar JOIN users de queries tenant (usar Prisma en BD central)
- Fix: req.params.obligacionId correcto en asignaciones controller
- Fix: orden rutas estaticas antes de dinamicas en cartera.routes
- Fix: owner/cfo ven todas las asignaciones en getAsignacionesPorSupervisor
- Fix: validar que entidad pertenezca a cartera padre en subcartera
- Nuevo endpoint GET /carteras/asignaciones/sin-asignar
- Nuevo endpoint GET /tareas/mis-tareas
2026-05-23 23:40:12 +00:00

138 lines
4.1 KiB
TypeScript

import type { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import * as asignacionesService from '../services/asignaciones.service.js';
import { getEntidadesVisibles } from '../utils/entidades-visibles.js';
import { AppError } from '../middlewares/error.middleware.js';
/**
* Valida que el auxiliar pertenezca al supervisor (o que el caller sea owner).
* Owner puede asignar a cualquier auxiliar del tenant.
*/
async function validarAuxiliarDelSupervisor(
pool: import('pg').Pool,
supervisorUserId: string,
auxiliarUserId: string,
callerRole: string,
): Promise<void> {
if (callerRole === 'owner') return;
const { rows } = await pool.query(
`SELECT 1 FROM auxiliar_supervisores
WHERE auxiliar_user_id = $1 AND supervisor_user_id = $2
LIMIT 1`,
[auxiliarUserId, supervisorUserId],
);
if (rows.length === 0) {
throw new AppError(403, 'El auxiliar no pertenece a tu equipo');
}
}
// ── Obligaciones ──
export async function asignarObligacion(req: Request, res: Response, next: NextFunction) {
try {
const obligacionId = String(req.params.obligacionId);
const schema = z.object({ auxiliarUserId: z.string().uuid() });
const { auxiliarUserId } = schema.parse(req.body);
await validarAuxiliarDelSupervisor(
req.tenantPool!,
req.user!.userId,
auxiliarUserId,
req.user!.role,
);
await asignacionesService.asignarObligacion(
req.tenantPool!,
obligacionId,
auxiliarUserId,
req.user!.userId,
);
res.json({ message: 'Obligación asignada' });
} catch (err: any) {
if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message));
next(err);
}
}
export async function desasignarObligacion(req: Request, res: Response, next: NextFunction) {
try {
const obligacionId = String(req.params.obligacionId);
await asignacionesService.desasignarObligacion(req.tenantPool!, obligacionId);
res.json({ message: 'Asignación de obligación eliminada' });
} catch (err) { next(err); }
}
// ── Tareas ──
export async function asignarTarea(req: Request, res: Response, next: NextFunction) {
try {
const tareaId = String(req.params.id);
const schema = z.object({ auxiliarUserId: z.string().uuid() });
const { auxiliarUserId } = schema.parse(req.body);
await validarAuxiliarDelSupervisor(
req.tenantPool!,
req.user!.userId,
auxiliarUserId,
req.user!.role,
);
await asignacionesService.asignarTarea(
req.tenantPool!,
tareaId,
auxiliarUserId,
req.user!.userId,
);
res.json({ message: 'Tarea asignada' });
} catch (err: any) {
if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message));
next(err);
}
}
export async function desasignarTarea(req: Request, res: Response, next: NextFunction) {
try {
const tareaId = String(req.params.id);
await asignacionesService.desasignarTarea(req.tenantPool!, tareaId);
res.json({ message: 'Asignación de tarea eliminada' });
} catch (err) { next(err); }
}
// ── Listados ──
export async function listPorSupervisor(req: Request, res: Response, next: NextFunction) {
try {
const data = await asignacionesService.getAsignacionesPorSupervisor(
req.tenantPool!,
req.user!.userId,
req.user!.role,
);
res.json(data);
} catch (err) { next(err); }
}
export async function listPorAuxiliar(req: Request, res: Response, next: NextFunction) {
try {
const data = await asignacionesService.getAsignacionesPorAuxiliar(
req.tenantPool!,
req.user!.userId,
);
res.json(data);
} catch (err) { next(err); }
}
export async function listSinAsignar(req: Request, res: Response, next: NextFunction) {
try {
const entidadIds = await getEntidadesVisibles(req.tenantPool!, req.user!.userId, req.user!.role);
const [obligaciones, tareas] = await Promise.all([
asignacionesService.getObligacionesSinAsignar(req.tenantPool!, entidadIds),
asignacionesService.getTareasSinAsignar(req.tenantPool!, entidadIds),
]);
res.json({ obligaciones, tareas });
} catch (err) { next(err); }
}