Files
FRASA-CAS/api-gateway/src/services/cotizacion-desarrollos.service.test.ts
Consult AS Technology afb454b54c feat(cotizador): multi-desarrollo — ATELIER, ESTANZA, SENDA, SOLANA + DIAGONAL
- Motor de cálculo parametrizado por desarrollo (enganches, descuentos, splits,
  fuente de N, quirk Infonavit-lista); DIAGONAL byte-idéntico
- 285 unidades (5 desarrollos) en seeds, API (?desarrollo=) y board Monday
- Selector de desarrollo en UI, propuestas por config, PDF multi-desarrollo
- Knowledge IA: catálogo por desarrollo + matriz de diferencias (10 docs)
- Tests: cotizador 53, api 99 — fixtures de los Excel validados al centavo
2026-08-22 04:31:21 +00:00

167 lines
5.6 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import {
APARTADO_DEFAULT,
calcularPropuesta,
calcularPropuestas,
totalPagado,
type UnidadCotizable,
} from './cotizacion.service.js'
/**
* Fixtures de los 4 desarrollos nuevos, validados al centavo contra los Excel
* de producción (reference/cotizadores/*.md). Espejo de
* apps/cotizador/src/lib/calculations-desarrollos.test.ts.
*/
function unidad(parcial: Partial<UnidadCotizable> & Pick<UnidadCotizable, 'codigo' | 'desarrollo'>): UnidadCotizable {
return {
modelo: 'X',
fechaEntrega: null,
supEstacionamiento: 0,
supConstruccion: 0,
supExcedente: 0,
precioSinAcabados: 0,
precioConAcabados: 0,
status: 'DISPONIBLE',
...parcial,
}
}
const DESC_CERO = { creditoBancario: 0, creditoInfonavit: 0, contado: 0, semiContado: 0 }
describe('ATELIER — depto 10 (L = 3,997,500, apartado 20,000, N fijo = 1)', () => {
const depto10 = unidad({
codigo: 'ATELIER-10',
desarrollo: 'ATELIER',
fechaEntrega: '2026-01-21',
precioSinAcabados: 3997500,
precioConAcabados: 3997500,
})
it('BANCARIO: enganche 5% sobre LISTA, mensualidad remanente en 1 pago', () => {
const [p] = calcularPropuestas({
unidad: depto10,
acabados: false,
apartado: APARTADO_DEFAULT,
descuentos: DESC_CERO,
})
expect(p?.enganche).toBeCloseTo(179875, 2)
expect(p?.mensualidadesTotal).toBeCloseTo(199875, 2)
expect(p?.pagoFinal).toBeCloseTo(3597750, 2)
})
it('COFINAVIT (vía tipo COFINAVIT del API): 2.5% / 95% sobre precio final', () => {
const p = calcularPropuesta(depto10, 'COFINAVIT', false, APARTADO_DEFAULT)
expect(p.titulo).toBe('Crédito Cofinavit')
expect(p.enganche).toBeCloseTo(79937.5, 2)
expect(p.mensualidadesTotal).toBeCloseTo(99937.5, 2)
expect(p.pagoFinal).toBeCloseTo(3797625, 2)
expect(totalPagado(p, APARTADO_DEFAULT)).toBeCloseTo(3997500, 2)
})
})
describe('ESTANZA — casa 52 (L = 2,437,250): pago final sobre LISTA (quirk)', () => {
const casa52 = unidad({
codigo: 'ESTANZA-52',
desarrollo: 'ESTANZA',
precioSinAcabados: 2437250,
precioConAcabados: 2437250,
})
it('las 4 propuestas al centavo', () => {
const [bancario, infonavit, contado, semi] = calcularPropuestas({
unidad: casa52,
acabados: false,
apartado: APARTADO_DEFAULT,
descuentos: DESC_CERO,
})
expect(bancario?.enganche).toBeCloseTo(40931.25, 2)
expect(bancario?.mensualidadesTotal).toBeCloseTo(182793.75, 2)
expect(bancario?.pagoFinal).toBeCloseTo(2193525, 2)
expect(infonavit?.enganche).toBeCloseTo(40931.25, 2)
expect(infonavit?.mensualidadesTotal).toBeCloseTo(60931.25, 2)
expect(infonavit?.pagoFinal).toBeCloseTo(2315387.5, 2)
expect(contado?.enganche).toBeCloseTo(2173525, 2)
expect(contado?.pagoFinal).toBeCloseTo(243725, 2)
expect(semi?.enganche).toBeCloseTo(1198625, 2)
expect(semi?.mensualidadesTotal).toBeCloseTo(974900, 2)
expect(semi?.pagoFinal).toBeCloseTo(243725, 2)
})
})
describe('SENDA — 411-1-7 (L = 2,451,750, apartado 0, N = 4)', () => {
const casa = unidad({
codigo: 'SENDA-411-1-7',
desarrollo: 'SENDA',
fechaEntrega: '2026-12-19', // la N real se fuerza con numMeses en el test
precioSinAcabados: 2451750,
precioConAcabados: 2451750,
})
it('semi-contado 50/45/5 con desc 1% default', () => {
const p = calcularPropuesta(casa, 'SEMI_CONTADO', false, 0)
// N real depende de hoy; validamos montos que no dependen de N
expect(p.precioFinal).toBeCloseTo(2427232.5, 2)
expect(p.enganche).toBeCloseTo(1213616.25, 2)
expect(p.mensualidadesTotal).toBeCloseTo(1092254.625, 3)
expect(p.pagoFinal).toBeCloseTo(121361.625, 3)
})
it('bancario 5/5/90 con N forzada = 4', () => {
const [p] = calcularPropuestas({
unidad: casa,
acabados: false,
apartado: 0,
descuentos: DESC_CERO,
numMeses: 4,
})
expect(p?.enganche).toBeCloseTo(122587.5, 2)
expect(p?.mensualidad).toBeCloseTo(30646.875, 3)
expect(p?.pagoFinal).toBeCloseTo(2206575, 2)
})
})
describe('SENDA — unidad sin fecha de entrega', () => {
it('N = null, mensualidades "—" y total pagado null', () => {
const sinFecha = unidad({
codigo: 'SENDA-411-25-A101',
desarrollo: 'SENDA',
fechaEntrega: null,
precioSinAcabados: 1948000,
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()
expect(p.enganche).toBeCloseTo(0.05 * 1948000 - APARTADO_DEFAULT, 2)
expect(p.pagoFinal).toBeCloseTo(0.9 * 1948000, 2)
})
})
describe('SOLANA — casa 6 (L = 2,560,000, mesesParaFirma = 1)', () => {
const casa6 = unidad({
codigo: 'SOLANA-6',
desarrollo: 'SOLANA',
precioSinAcabados: 2560000,
precioConAcabados: 2560000,
mesesParaFirma: 1,
})
it('INFONAVIT con enganche 0% (guarda: no resta el apartado)', () => {
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.pagoFinal).toBeCloseTo(2432000, 2)
})
it('BANCARIO 5% sobre final', () => {
const p = calcularPropuesta(casa6, 'BANCARIO', false, APARTADO_DEFAULT)
expect(p.enganche).toBeCloseTo(108000, 2)
expect(p.mensualidadesTotal).toBeCloseTo(128000, 2)
expect(p.pagoFinal).toBeCloseTo(2304000, 2)
})
})