fix(cobranza): invariante engancheTotal=plan completo, pago 0 inicial y abonos al pago correcto

This commit is contained in:
2026-09-12 22:58:23 +00:00
parent 34229f1894
commit 31379806be

View File

@@ -174,10 +174,11 @@ function leer() {
planes.push({ planes.push({
proyecto: nombreHoja, proyecto: nombreHoja,
fechaVenta: col.fechaVenta != null ? aFecha(fila[col.fechaVenta]) : null,
plan: { plan: {
ventaRef, clienteNombre, clienteTelefono: null, unidadCodigo, ventaRef, clienteNombre, clienteTelefono: null, unidadCodigo,
proyecto: nombreHoja, fechaInicio, proyecto: nombreHoja, fechaInicio,
engancheTotal: apartado + enganche, engancheTotal: apartado + enganche, // se recalcula en escribir() con el plan completo
numPagos: meses, mensualidad, tasaMoraMensual: 0, status: 'ACTIVO', numPagos: meses, mensualidad, tasaMoraMensual: 0, status: 'ACTIVO',
}, },
abonosGrid, abonosGrid,
@@ -199,11 +200,9 @@ async function escribir(planesLeidos) {
const borrados = await prisma.planPago.deleteMany({}) // cascada: Pago, AplicacionPago, NotaPlan const borrados = await prisma.planPago.deleteMany({}) // cascada: Pago, AplicacionPago, NotaPlan
console.log(`Planes borrados: ${borrados.count}`) console.log(`Planes borrados: ${borrados.count}`)
let totalPagos = 0, totalAbonos = 0, totalStandalone = 0 let totalPagos = 0, totalAbonos = 0, totalStandalone = 0, totalInicial = 0
for (const { plan, abonosGrid } of planesLeidos) {
const creado = await prisma.planPago.create({ data: plan })
for (const { plan, fechaVenta, abonosGrid } of planesLeidos) {
// 1) calendario programado: mensualidades desde MES INICIO // 1) calendario programado: mensualidades desde MES INICIO
const pagosProg = [] const pagosProg = []
if (plan.numPagos > 0 && plan.mensualidad > 0) { if (plan.numPagos > 0 && plan.mensualidad > 0) {
@@ -217,11 +216,26 @@ async function escribir(planesLeidos) {
} }
} }
// Invariante del módulo: Σ pagos.monto == engancheTotal. El apartado+enganche
// (ya pagado según el maestro) entra como pago 0 y engancheTotal = plan completo.
const inicialMonto = (plan.engancheTotal ?? 0)
if (inicialMonto > 0) {
pagosProg.unshift({
numPago: 0,
fechaVencimiento: fechaVenta ?? plan.fechaInicio,
monto: inicialMonto,
montoPagado: inicialMonto,
})
totalInicial++
}
plan.engancheTotal = pagosProg.reduce((a, p) => a + p.monto, 0)
// 2) abonos del grid sobre la mensualidad del mismo mes; sobrantes → standalone // 2) abonos del grid sobre la mensualidad del mismo mes; sobrantes → standalone
const aplicaciones = [] // { idxPago, fechaPago, monto } const aplicaciones = [] // { idxPago, fechaPago, monto }
const standalone = [] const standalone = []
for (const ab of abonosGrid) { for (const ab of abonosGrid) {
const idx = pagosProg.findIndex((p) => claveMes(p.fechaVencimiento) === claveMes(ab.fecha)) // idx del arreglo == numPago (el inicial ocupa numPago 0 en el índice 0)
const idx = pagosProg.findIndex((p) => p.numPago >= 1 && claveMes(p.fechaVencimiento) === claveMes(ab.fecha))
if (idx !== -1) { if (idx !== -1) {
aplicaciones.push({ idx, fechaPago: ab.fecha, monto: ab.monto }) aplicaciones.push({ idx, fechaPago: ab.fecha, monto: ab.monto })
} else { } else {
@@ -235,8 +249,18 @@ async function escribir(planesLeidos) {
p.montoPagado = Math.min(p.monto, p.montoPagado + ap.monto) p.montoPagado = Math.min(p.monto, p.montoPagado + ap.monto)
} }
// Si el maestro registra pagos por encima del plan declarado (filas con
// MESES/MENSUALIDAD vacíos pero grid lleno), el total del plan es lo pagado.
const totalPagadoAcumulado = pagosProg.reduce((a, p) => a + p.montoPagado, 0) +
standalone.reduce((a, s) => a + s.monto, 0)
if (totalPagadoAcumulado > plan.engancheTotal) {
plan.engancheTotal = Math.round(totalPagadoAcumulado * 100) / 100
}
const creado = await prisma.planPago.create({ data: plan })
// crear pagos programados // crear pagos programados
let numSiguiente = pagosProg.length let numSiguiente = pagosProg.length - 1
for (const p of pagosProg) { for (const p of pagosProg) {
await prisma.pago.create({ await prisma.pago.create({
data: { planId: creado.id, numPago: p.numPago, fechaVencimiento: p.fechaVencimiento, monto: p.monto, montoPagado: p.montoPagado }, data: { planId: creado.id, numPago: p.numPago, fechaVencimiento: p.fechaVencimiento, monto: p.monto, montoPagado: p.montoPagado },
@@ -257,7 +281,7 @@ async function escribir(planesLeidos) {
// 3) aplicaciones de pago (abonos con fecha real) // 3) aplicaciones de pago (abonos con fecha real)
for (const ap of aplicaciones) { for (const ap of aplicaciones) {
const pagoId = porNum.get(ap.idx + 1) const pagoId = porNum.get(ap.idx)
if (!pagoId) continue if (!pagoId) continue
await prisma.aplicacionPago.create({ await prisma.aplicacionPago.create({
data: { pagoId, fechaPago: ap.fechaPago, monto: ap.monto, referencia: 'Importación maestro cobranza 06/09/26', capturadoPorId: null }, data: { pagoId, fechaPago: ap.fechaPago, monto: ap.monto, referencia: 'Importación maestro cobranza 06/09/26', capturadoPorId: null },
@@ -266,7 +290,7 @@ async function escribir(planesLeidos) {
} }
} }
console.log(`\nPlanes: ${planesLeidos.length} | Pagos programados: ${totalPagos} | Standalone: ${totalStandalone} | Abonos (AplicacionPago): ${totalAbonos}`) console.log(`\nPlanes: ${planesLeidos.length} | Pagos programados: ${totalPagos} (incl. ${totalInicial} pagos iniciales apartado+enganche) | Standalone: ${totalStandalone} | Abonos (AplicacionPago): ${totalAbonos}`)
} }
async function verificar() { async function verificar() {