103 lines
3.7 KiB
TypeScript
103 lines
3.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import type { PlanPago } from '../../types'
|
||
import { generarCalendario, registrarAbono, saldoPlan } from '../cobranza'
|
||
import { crearDocumento, folioRecibo, saldosDelRecibo } from './reciboAbono'
|
||
|
||
function planConAbono(): PlanPago {
|
||
const { mensualidad, pagos } = generarCalendario('2026-01-15', 100000, 3)
|
||
const plan: PlanPago = {
|
||
id: 'plan-fixture',
|
||
ventaRef: 'VTA-2026-001',
|
||
clienteNombre: 'Cliente Fixture',
|
||
clienteTelefono: '55 0000 0000',
|
||
unidadCodigo: 'U-101',
|
||
proyecto: 'SENDA',
|
||
fechaInicio: '2026-01-15',
|
||
engancheTotal: 100000,
|
||
numPagos: 3,
|
||
mensualidad,
|
||
status: 'ACTIVO',
|
||
pagos,
|
||
notas: [],
|
||
createdAt: '2026-01-01T00:00:00.000Z',
|
||
}
|
||
return registrarAbono(plan, {
|
||
pagoId: plan.pagos[0].id,
|
||
fechaPago: '2026-01-15',
|
||
monto: 5000,
|
||
referencia: 'SPEI-001',
|
||
})
|
||
}
|
||
|
||
describe('reciboAbono PDF', () => {
|
||
it('el folio son los primeros 8 chars del id de la aplicación en mayúsculas', () => {
|
||
const plan = planConAbono()
|
||
const aplicacion = plan.pagos[0].aplicaciones[0]
|
||
expect(folioRecibo(aplicacion)).toBe(aplicacion.id.slice(0, 8).toUpperCase())
|
||
expect(folioRecibo({ ...aplicacion, id: 'abcdef12-3456-7890' })).toBe('ABCDEF12')
|
||
})
|
||
|
||
it('genera el documento con nombre de archivo correcto', () => {
|
||
const plan = planConAbono()
|
||
const pago = plan.pagos[0]
|
||
const aplicacion = pago.aplicaciones[0]
|
||
const { doc, nombreArchivo } = crearDocumento(plan, pago, aplicacion, new Date('2026-02-20T12:00:00'))
|
||
expect(nombreArchivo).toBe(`recibo_U-101_${folioRecibo(aplicacion)}.pdf`)
|
||
expect(doc.getNumberOfPages()).toBeGreaterThanOrEqual(1)
|
||
// smoke: el contenido interno es un PDF válido
|
||
expect(doc.output('datauristring')).toContain('application/pdf')
|
||
})
|
||
|
||
it('saldosDelRecibo usa los totales del plan, no solo los abonos registrados', () => {
|
||
const plan = planConAbono()
|
||
const pago = plan.pagos[0]
|
||
const { restantePago, saldoPlan: saldo } = saldosDelRecibo(plan, pago)
|
||
expect(restantePago).toBe(28333.33)
|
||
expect(saldo).toBe(saldoPlan(plan))
|
||
expect(saldo).toBe(95000)
|
||
})
|
||
|
||
it('el saldo incluye el apartado/enganche histórico sin fila de AplicacionPago', () => {
|
||
// Caso real del maestro importado: el pago inicial (numPago 0) trae
|
||
// montoPagado == monto pero sin aplicaciones. Antes el recibo restaba solo
|
||
// aplicaciones y mostraba un saldo inflado (o negativo).
|
||
const base = planConAbono()
|
||
const pagoInicial = {
|
||
id: 'pago-0',
|
||
numPago: 0,
|
||
fechaVencimiento: '2026-01-01',
|
||
monto: 20000,
|
||
montoPagado: 20000,
|
||
aplicaciones: [],
|
||
}
|
||
const plan: PlanPago = {
|
||
...base,
|
||
pagos: [pagoInicial, ...base.pagos],
|
||
engancheTotal: base.engancheTotal,
|
||
}
|
||
const pago = plan.pagos[1]
|
||
const aplicacion = pago.aplicaciones[0]
|
||
const { saldoPlan: saldo, restantePago } = saldosDelRecibo(plan, pago)
|
||
// saldo real = Σ monto − Σ montoPagado = 120000 − 25000
|
||
expect(saldo).toBe(95000)
|
||
expect(restantePago).toBe(28333.33)
|
||
expect(crearDocumento(plan, pago, aplicacion).doc.getNumberOfPages()).toBeGreaterThanOrEqual(1)
|
||
})
|
||
|
||
it('un abono mayor al monto de su pago no produce saldo negativo (dato importado)', () => {
|
||
// El importador viejo guardaba el valor crudo en AplicacionPago.monto
|
||
// aunque montoPagado se topara con Math.min: el recibo restaba de más.
|
||
const base = planConAbono()
|
||
const pago = base.pagos[0]
|
||
const inflado: PlanPago = {
|
||
...base,
|
||
pagos: base.pagos.map((p) =>
|
||
p.id === pago.id
|
||
? { ...p, aplicaciones: [{ ...p.aplicaciones[0], monto: 500000 }] }
|
||
: p,
|
||
),
|
||
}
|
||
expect(saldosDelRecibo(inflado, inflado.pagos[0]).saldoPlan).toBe(95000)
|
||
})
|
||
})
|