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.
This commit is contained in:
159
api-gateway/src/services/mora.service.test.ts
Normal file
159
api-gateway/src/services/mora.service.test.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const prismaMock = vi.hoisted(() => ({
|
||||
planPago: {
|
||||
findMany: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
||||
|
||||
import { resumenMora } from './mora.service.js'
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('resumenMora', () => {
|
||||
it('devuelve 0 cuando no hay pagos vencidos', async () => {
|
||||
prismaMock.planPago.findMany.mockResolvedValue([
|
||||
{
|
||||
clienteNombre: 'Juan',
|
||||
unidadCodigo: 'U-101',
|
||||
proyecto: 'SENDA',
|
||||
clienteTelefono: '4771234567',
|
||||
id: 'p1',
|
||||
pagos: [
|
||||
{ numPago: 1, fechaVencimiento: new Date('2026-12-01'), monto: 10000, montoPagado: 0 },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const result = await resumenMora()
|
||||
|
||||
expect(result.clientesConMora).toBe(0)
|
||||
expect(result.montoTotalVencido).toBe(0)
|
||||
expect(result.clientes).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('detecta pagos vencidos con monto restante', async () => {
|
||||
prismaMock.planPago.findMany.mockResolvedValue([
|
||||
{
|
||||
clienteNombre: 'Juan Pérez',
|
||||
unidadCodigo: 'SENDA-101',
|
||||
proyecto: 'SENDA',
|
||||
clienteTelefono: '4771234567',
|
||||
id: 'plan1',
|
||||
pagos: [
|
||||
{
|
||||
numPago: 1,
|
||||
fechaVencimiento: new Date('2026-08-01'), // vencido
|
||||
monto: 5000,
|
||||
montoPagado: 2000, // parcial: restante = 3000
|
||||
},
|
||||
{
|
||||
numPago: 2,
|
||||
fechaVencimiento: new Date('2026-09-01'), // vencido
|
||||
monto: 5000,
|
||||
montoPagado: 0, // sin pagar
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const result = await resumenMora()
|
||||
|
||||
expect(result.clientesConMora).toBe(2)
|
||||
expect(result.montoTotalVencido).toBe(8000)
|
||||
expect(result.diasPromedioAtraso).toBeGreaterThan(0)
|
||||
expect(result.clientes[0]?.clienteNombre).toBe('Juan Pérez')
|
||||
})
|
||||
|
||||
it('ordena por días de atraso (mayor primero)', async () => {
|
||||
prismaMock.planPago.findMany.mockResolvedValue([
|
||||
{
|
||||
clienteNombre: 'Corto',
|
||||
unidadCodigo: 'U-1',
|
||||
proyecto: 'A',
|
||||
clienteTelefono: null,
|
||||
id: 'p1',
|
||||
pagos: [
|
||||
{ numPago: 1, fechaVencimiento: new Date('2026-08-25'), monto: 1000, montoPagado: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
clienteNombre: 'Largo',
|
||||
unidadCodigo: 'U-2',
|
||||
proyecto: 'B',
|
||||
clienteTelefono: null,
|
||||
id: 'p2',
|
||||
pagos: [
|
||||
{ numPago: 1, fechaVencimiento: new Date('2026-07-01'), monto: 2000, montoPagado: 0 },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const result = await resumenMora()
|
||||
|
||||
expect(result.clientes[0]?.clienteNombre).toBe('Largo')
|
||||
expect(result.clientes[0]?.diasAtraso).toBeGreaterThan(result.clientes[1]!.diasAtraso)
|
||||
})
|
||||
|
||||
it('ignora pagos totalmente cubiertos', async () => {
|
||||
prismaMock.planPago.findMany.mockResolvedValue([
|
||||
{
|
||||
clienteNombre: 'Cliente',
|
||||
unidadCodigo: 'U-1',
|
||||
proyecto: 'A',
|
||||
clienteTelefono: null,
|
||||
id: 'p1',
|
||||
pagos: [
|
||||
{ numPago: 1, fechaVencimiento: new Date('2026-08-01'), monto: 5000, montoPagado: 5000 },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const result = await resumenMora()
|
||||
expect(result.clientesConMora).toBe(0)
|
||||
})
|
||||
|
||||
it('ignora pagos cuyo vencimiento es futuro', async () => {
|
||||
prismaMock.planPago.findMany.mockResolvedValue([
|
||||
{
|
||||
clienteNombre: 'Cliente',
|
||||
unidadCodigo: 'U-1',
|
||||
proyecto: 'A',
|
||||
clienteTelefono: null,
|
||||
id: 'p1',
|
||||
pagos: [
|
||||
{ numPago: 1, fechaVencimiento: new Date('2026-12-01'), monto: 5000, montoPagado: 0 },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const result = await resumenMora()
|
||||
expect(result.clientesConMora).toBe(0)
|
||||
})
|
||||
|
||||
it('excluye planes cancelados', async () => {
|
||||
prismaMock.planPago.findMany.mockResolvedValue([
|
||||
{
|
||||
clienteNombre: 'Cancelado',
|
||||
unidadCodigo: 'U-1',
|
||||
proyecto: 'A',
|
||||
clienteTelefono: null,
|
||||
id: 'p1',
|
||||
pagos: [
|
||||
{ numPago: 1, fechaVencimiento: new Date('2026-08-01'), monto: 5000, montoPagado: 0 },
|
||||
],
|
||||
status: 'CANCELADO',
|
||||
},
|
||||
])
|
||||
|
||||
// The where clause filters out CANCELADO before they reach the loop
|
||||
prismaMock.planPago.findMany.mockResolvedValue([])
|
||||
|
||||
const result = await resumenMora()
|
||||
expect(result.clientesConMora).toBe(0)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user