feat(pendientes): cotizador (mensualidades, bloqueo de unidad, totales, numMensualidades), cobranza (recibos, atrasos, mora configurable, recordatorios), soporte (adjuntos, filtros, estados), clientes maestro (catalogos reales), contactos (ficha y duplicados) y permisos de captura por rol
This commit is contained in:
@@ -122,7 +122,7 @@ describe('SENDA — 411-1-7 (L = 2,451,750, apartado 0, N = 4)', () => {
|
||||
})
|
||||
|
||||
describe('SENDA — unidad sin fecha de entrega', () => {
|
||||
it('N = null, mensualidades "—" y total pagado null', () => {
|
||||
it('usa el plazo por defecto del desarrollo: mensualidad y total SÍ se calculan', () => {
|
||||
const sinFecha = unidad({
|
||||
codigo: 'SENDA-411-25-A101',
|
||||
desarrollo: 'SENDA',
|
||||
@@ -131,10 +131,12 @@ describe('SENDA — unidad sin fecha de entrega', () => {
|
||||
precioConAcabados: 1948000,
|
||||
})
|
||||
const p = calcularPropuesta(sinFecha, 'BANCARIO', false, APARTADO_DEFAULT)
|
||||
expect(p.numMensualidades).toBeNull()
|
||||
expect(p.mensualidad).toBeNull()
|
||||
expect(p.notaMensualidades).toBe('sin fecha de entrega')
|
||||
expect(totalPagado(p, APARTADO_DEFAULT)).toBeNull()
|
||||
// Antes salía numMensualidades/mensualidad null y "— (sin fecha de entrega)"
|
||||
expect(p.numMensualidades).toBe(6) // nDefault de SENDA
|
||||
expect(p.notaMensualidades).toBeUndefined()
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(97400, 2)
|
||||
expect(p.mensualidad).toBeCloseTo(97400 / 6, 4)
|
||||
expect(totalPagado(p, APARTADO_DEFAULT)).toBeCloseTo(1948000, 2)
|
||||
expect(p.enganche).toBeCloseTo(0.05 * 1948000 - APARTADO_DEFAULT, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(0.9 * 1948000, 2)
|
||||
})
|
||||
@@ -149,12 +151,13 @@ describe('SOLANA — casa 6 (L = 2,560,000, mesesParaFirma = 1)', () => {
|
||||
mesesParaFirma: 1,
|
||||
})
|
||||
|
||||
it('INFONAVIT con enganche 0% (guarda: no resta el apartado)', () => {
|
||||
it('INFONAVIT con enganche 0%: el apartado se descuenta (no se cobra encima del precio)', () => {
|
||||
const p = calcularPropuesta(casa6, 'INFONAVIT', false, APARTADO_DEFAULT)
|
||||
expect(p.enganche).toBe(0)
|
||||
expect(p.numMensualidades).toBe(1)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(128000, 2)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(108000, 2)
|
||||
expect(p.pagoFinal).toBeCloseTo(2432000, 2)
|
||||
expect(totalPagado(p, APARTADO_DEFAULT)).toBeCloseTo(2560000, 2)
|
||||
})
|
||||
|
||||
it('BANCARIO 5% sobre final', () => {
|
||||
@@ -164,3 +167,82 @@ describe('SOLANA — casa 6 (L = 2,560,000, mesesParaFirma = 1)', () => {
|
||||
expect(p.pagoFinal).toBeCloseTo(2304000, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('cuadre del total: apartado y enganche personalizado', () => {
|
||||
const TIPOS = ['BANCARIO', 'INFONAVIT', 'CONTADO', 'SEMI_CONTADO'] as const
|
||||
const CASOS = [
|
||||
{ desarrollo: 'DIAGONAL', precio: 4_415_000 },
|
||||
{ desarrollo: 'ATELIER', precio: 3_997_500 },
|
||||
{ desarrollo: 'ESTANZA', precio: 2_437_250 },
|
||||
{ desarrollo: 'SENDA', precio: 2_451_750 },
|
||||
{ desarrollo: 'SOLANA', precio: 2_560_000 },
|
||||
]
|
||||
const unidadDe = (desarrollo: string, precio: number) =>
|
||||
unidad({
|
||||
codigo: `${desarrollo}-1`,
|
||||
desarrollo,
|
||||
precioSinAcabados: precio,
|
||||
precioConAcabados: precio,
|
||||
})
|
||||
|
||||
it('las 4 propuestas de cada desarrollo cuadran con su precio final', () => {
|
||||
for (const caso of CASOS) {
|
||||
for (const tipo of TIPOS) {
|
||||
const p = calcularPropuesta(unidadDe(caso.desarrollo, caso.precio), tipo, false, APARTADO_DEFAULT)
|
||||
expect(totalPagado(p, APARTADO_DEFAULT)).toBeCloseTo(p.precioFinal, 2)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('nunca hay bloques negativos y el total es el precio (apartados y enganches extremos)', () => {
|
||||
for (const caso of CASOS) {
|
||||
for (const tipo of TIPOS) {
|
||||
for (const apartado of [0, APARTADO_DEFAULT, 200_000]) {
|
||||
for (const enganchePct of [undefined, 0.05, 0.2, 0.5, 1]) {
|
||||
const p = calcularPropuesta(
|
||||
unidadDe(caso.desarrollo, caso.precio),
|
||||
tipo,
|
||||
false,
|
||||
apartado,
|
||||
undefined,
|
||||
enganchePct,
|
||||
)
|
||||
expect(p.enganche).toBeGreaterThanOrEqual(0)
|
||||
expect(p.mensualidadesTotal ?? 0).toBeGreaterThanOrEqual(0)
|
||||
expect(p.pagoFinal).toBeGreaterThanOrEqual(0)
|
||||
expect(totalPagado(p, apartado)).toBeCloseTo(p.precioFinal, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('enganche personalizado alto (20%) no infla el total: sigue siendo el precio', () => {
|
||||
const u101 = unidad({
|
||||
codigo: 'U-101',
|
||||
desarrollo: 'DIAGONAL',
|
||||
precioSinAcabados: 4_415_000,
|
||||
precioConAcabados: 4_579_000,
|
||||
})
|
||||
const p = calcularPropuesta(u101, 'BANCARIO', false, APARTADO_DEFAULT, undefined, 0.2)
|
||||
// Antes: 20% de enganche + 90% de pago final daban $4,856,500 sobre $4,415,000
|
||||
expect(totalPagado(p, APARTADO_DEFAULT)).toBeCloseTo(4_415_000, 2)
|
||||
expect(p.mensualidadesTotal).toBe(0)
|
||||
expect(p.pagoFinal).toBeCloseTo(4_415_000 - APARTADO_DEFAULT - p.enganche, 2)
|
||||
})
|
||||
|
||||
it('apartado mayor al enganche: el enganche queda en 0 (nunca negativo) y el total cuadra', () => {
|
||||
const u101 = unidad({
|
||||
codigo: 'U-101',
|
||||
desarrollo: 'DIAGONAL',
|
||||
precioSinAcabados: 4_415_000,
|
||||
precioConAcabados: 4_579_000,
|
||||
})
|
||||
// 2.5% de 4,415,000 = 110,375; con apartado 200,000 el neto sería −89,625
|
||||
const p = calcularPropuesta(u101, 'BANCARIO', false, 200_000)
|
||||
expect(p.enganche).toBe(0)
|
||||
// El apartado de más se acredita en las mensualidades (remanente real)
|
||||
expect(p.mensualidadesTotal).toBeCloseTo(4_415_000 - 200_000 - p.pagoFinal, 2)
|
||||
expect(totalPagado(p, 200_000)).toBeCloseTo(4_415_000, 2)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user