Files
FRASA-CAS/api-gateway/src/services/mora.service.ts
Consult AS Technology db18685872 feat(ux): 17 mejoras de experiencia de usuario
Backend (10 endpoints nuevos):
- Recordatorios personales (CRUD + tabla RecordatorioVendedor)
- Lead scoring 0-100 (etapa+recencia+datos)
- Dashboard mora (pagos vencidos, monto, días atraso)
- Timeline de pagos (pagado/vence/futuro)
- Perfil completo contacto (pagos+cotizaciones+docs+score)
- Exportar contactos a CSV

Frontend Mi día:
- Resumen matutino (saludo+KPIs)
- Historial visual lead (timeline)
- Score badge 0-100 (verde/amarillo/rojo)
- Vista kanban (drag & drop)

Frontend Cobranza:
- Dashboard mora (tabla+WhatsApp)
- Timeline pagos visual (nodos coloreados)

Frontend Alta Clientes:
- Checklist documentos visual
- Foto cliente (cámara)

Frontend Landing:
- Perfil contacto completo
- Exportar contactos CSV

Frontend Mi día (extras):
- Comparativa mensual
- KPIs dashboard (leads hoy, ventas, conversión)

806 tests, 5 apps construidas.
2026-09-08 07:42:54 +00:00

90 lines
2.4 KiB
TypeScript

import { prisma } from './prisma.js'
/**
* Dashboard de mora: clientes con pagos vencidos (fechaVencimiento < hoy
* y montoPagado < monto). Devuelve resumen + detalle por cliente.
*/
export interface ClienteMora {
clienteNombre: string
unidadCodigo: string
proyecto: string
pagoVencido: number
monto: number
diasAtraso: number
telefono: string | null
planId: string
}
export interface ResumenMora {
clientesConMora: number
montoTotalVencido: number
diasPromedioAtraso: number
clientes: ClienteMora[]
}
/**
* Calcula días naturales de diferencia entre hoy y la fecha de vencimiento.
*/
function diasDesde(fechaVencimiento: Date, hoy: Date): number {
const hv = new Date(hoy.getFullYear(), hoy.getMonth(), hoy.getDate())
const fv = new Date(
fechaVencimiento.getFullYear(),
fechaVencimiento.getMonth(),
fechaVencimiento.getDate(),
)
return Math.max(0, Math.round((hv.getTime() - fv.getTime()) / 86_400_000))
}
/**
* Resumen de mora: todos los pagos vencidos de planes activos.
*/
export async function resumenMora(): Promise<ResumenMora> {
const hoy = new Date()
const inicioHoy = new Date(hoy.getFullYear(), hoy.getMonth(), hoy.getDate())
const planes = await prisma.planPago.findMany({
where: { status: { not: 'CANCELADO' } },
include: { pagos: true },
})
const clientes: ClienteMora[] = []
for (const plan of planes) {
for (const pago of plan.pagos) {
const restante = pago.monto - pago.montoPagado
if (restante <= 0) continue // ya pagado
const fv = pago.fechaVencimiento
if (fv >= inicioHoy) continue // aún no vence
const dias = diasDesde(fv, hoy)
clientes.push({
clienteNombre: plan.clienteNombre,
unidadCodigo: plan.unidadCodigo,
proyecto: plan.proyecto,
pagoVencido: pago.numPago,
monto: Math.round(restante * 100) / 100,
diasAtraso: dias,
telefono: plan.clienteTelefono,
planId: plan.id,
})
}
}
// Ordenar por días de atraso (mayor primero)
clientes.sort((a, b) => b.diasAtraso - a.diasAtraso)
const montoTotalVencido = Math.round(clientes.reduce((acc, c) => acc + c.monto, 0) * 100) / 100
const diasPromedioAtraso =
clientes.length > 0
? Math.round(clientes.reduce((acc, c) => acc + c.diasAtraso, 0) / clientes.length)
: 0
return {
clientesConMora: clientes.length,
montoTotalVencido,
diasPromedioAtraso,
clientes,
}
}