feat(pendientes): cotizador (mensualidades, bloqueo de unidad, totales, numMensualidades), cobranza (recibos, atrasos, mora configurable, recordatorios), soporte (adjuntos, filtros, estados), clientes maestro (catalogos reales), contactos (ficha y duplicados) y permisos de captura por rol

This commit is contained in:
2026-09-21 08:34:53 +00:00
parent b5a20aed4e
commit 2fc80e7f9e
66 changed files with 3792 additions and 436 deletions

View File

@@ -1,4 +1,4 @@
import { statusPago, statusPlan, type PlanPago } from './cobranza.service.js'
import { pagoAtrasado, statusPlan, type PlanPago } from './cobranza.service.js'
import type { UnidadCotizable } from './cotizacion.service.js'
import { ETAPAS_PIPELINE } from './etapas.js'
import { prisma } from './prisma.js'
@@ -266,7 +266,9 @@ export function construirResumen(datos: DatosResumen, ahora: Date, dias?: number
.map((etapa) => ({ etapa, total: conteoEtapas.get(etapa) ?? 0 })),
]
const planesPorStatus = { activos: 0, liquidados: 0, cancelados: 0 }
// `sinPlan` = plan sin calendario capturado (no es lo mismo que cancelado:
// 3 planes del maestro llegaron sin pagos y antes se contaban como cancelados)
const planesPorStatus = { activos: 0, liquidados: 0, cancelados: 0, sinPlan: 0 }
let planesAlCorriente = 0
let planesConVencidos = 0
let pagosVencidos = 0
@@ -275,9 +277,12 @@ export function construirResumen(datos: DatosResumen, ahora: Date, dias?: number
const status = statusPlan(plan)
if (status === 'ACTIVO') planesPorStatus.activos++
else if (status === 'LIQUIDADO') planesPorStatus.liquidados++
else if (status === 'SIN_PLAN') planesPorStatus.sinPlan++
else planesPorStatus.cancelados++
if (status !== 'ACTIVO') continue
const vencidos = plan.pagos.filter((p) => statusPago(p, ahora) === 'VENCIDO')
// Un pago PARCIAL vencido también es atraso (antes no se contaba y había
// planes "al corriente" con meses de retraso)
const vencidos = plan.pagos.filter((p) => pagoAtrasado(p, ahora))
if (vencidos.length === 0) planesAlCorriente++
else planesConVencidos++
pagosVencidos += vencidos.length
@@ -435,7 +440,7 @@ export async function cargarDatosLocalesPrisma(): Promise<DatosLocalesReportes>
// Contar actividades abiertas
const actividadesAbiertas = await prisma.actividadLocal.count({ where: { estado: 'ABIERTA' } })
return { leads, oportunidades, unidades: unidadesRows.map(unidadLocalADto), actividadesAbiertas }
return { leads, oportunidades, unidades: unidadesRows.map((u) => unidadLocalADto(u)), actividadesAbiertas }
}
export class ReportesService {