Files
FRASA-CAS/apps/cobranza/src/lib/pdf/reciboAbono.test.ts

103 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
})
})