feat: mis cobranzas, recordatorios de pago y agente IA con acciones
- PlanPago.vendedorId + filtro ?mios=1; toggle Mis planes/Todos en /pay - Motor de recordatorios: próximos a vencer (3d) y vencidos (re-cada 7d), canal Monday Actividad + stub WhatsApp listo para WA CRM, log persistente, cron diario 9:30, run manual con dry-run, historial en /api/cobranza/recordatorios - Agente IA: memoria por usuario (conversaciones aisladas) y tool calling: cotizar_unidad (motor real por desarrollo) y buscar_unidad (inventario Monday) - Tests: api 261, cobranza 75, agente-ia 13
This commit is contained in:
@@ -58,6 +58,7 @@ export function aDominio(plan: PlanCompleto): PlanPagoDominio {
|
||||
tasaMoraMensual: plan.tasaMoraMensual,
|
||||
status: plan.status === 'CANCELADO' ? 'CANCELADO' : 'ACTIVO',
|
||||
mondayItemId: plan.mondayItemId,
|
||||
vendedorId: plan.vendedorId,
|
||||
createdAt: plan.createdAt.toISOString(),
|
||||
pagos: plan.pagos
|
||||
.slice()
|
||||
@@ -142,6 +143,8 @@ export async function crearPlan(req: Request, res: Response): Promise<void> {
|
||||
mensualidad: calendario.mensualidad,
|
||||
tasaMoraMensual: body.tasaMoraMensual ?? 0,
|
||||
mondayItemId: body.mondayItemId ?? null,
|
||||
// Dueño del plan: sub del JWT (null si AUTH_ENABLED=false o token sin sub)
|
||||
vendedorId: req.usuarioToken?.sub ?? null,
|
||||
pagos: {
|
||||
create: calendario.pagos.map((p) => ({
|
||||
numPago: p.numPago,
|
||||
@@ -155,9 +158,29 @@ export async function crearPlan(req: Request, res: Response): Promise<void> {
|
||||
res.status(201).json(conCalculados(aDominio(plan)))
|
||||
}
|
||||
|
||||
/** GET /api/planes — con saldo/status calculados */
|
||||
export async function listarPlanes(_req: Request, res: Response): Promise<void> {
|
||||
// Roles con vista global de cobranza: el filtro ?mios=1 no les aplica
|
||||
const ROLES_VEN_TODO: readonly string[] = ['admin', 'coordinador']
|
||||
|
||||
/**
|
||||
* Filtro "mis cobranzas": con ?mios=1 un vendedor solo ve sus planes
|
||||
* (vendedorId = sub del token). Admin/coordinador ven todo aunque pidan mios;
|
||||
* sin token (AUTH_ENABLED=false) el filtro no aplica.
|
||||
*/
|
||||
export function resolverFiltroMios(
|
||||
mios: unknown,
|
||||
token: { sub: string; rol: string } | undefined,
|
||||
): { vendedorId: string } | undefined {
|
||||
if (mios !== '1') return undefined
|
||||
if (!token) return undefined
|
||||
if (ROLES_VEN_TODO.includes(token.rol)) return undefined
|
||||
return { vendedorId: token.sub }
|
||||
}
|
||||
|
||||
/** GET /api/planes — con saldo/status calculados; ?mios=1 filtra los del vendedor */
|
||||
export async function listarPlanes(req: Request, res: Response): Promise<void> {
|
||||
const where = resolverFiltroMios(req.query['mios'], req.usuarioToken)
|
||||
const planes = await prisma.planPago.findMany({
|
||||
...(where ? { where } : {}),
|
||||
include: includeCompleto,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user