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.
90 lines
2.4 KiB
TypeScript
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,
|
|
}
|
|
}
|