import { describe, it, expect } from 'vitest' import { calcularPropuestas, obtenerPrecioLista, resolverNumMensualidades, totalPagado, APARTADO_DEFAULT, } from './calculations' import { DESARROLLOS, descuentosDefault, obtenerDesarrollo } from '../data/desarrollos' import { UNIDADES_ATELIER } from '../data/unidades-atelier' import { UNIDADES_ESTANZA } from '../data/unidades-estanza' import { UNIDADES_SENDA } from '../data/unidades-senda' import { UNIDADES_SOLANA } from '../data/unidades-solana' import type { Unidad } from '../types' /** * Fixtures validados al centavo contra los Excel de producción * (ver reference/cotizadores/{ATELIER,ESTANZA,SENDA,SOLANA}.md). * Orden de propuestas en la config: [bancario, infonavit/cofinavit, contado, semi]. */ function porCodigo(unidades: Unidad[], codigo: string): Unidad { const u = unidades.find((x) => x.codigo === codigo) if (!u) throw new Error(`Fixture ${codigo} no existe en el seed`) return u } describe('ATELIER — depto 10 (L = 3,997,500, apartado 20,000, N fijo = 1)', () => { const unidad = porCodigo(UNIDADES_ATELIER, 'ATELIER-10') const calcular = () => calcularPropuestas({ unidad, acabados: false, apartado: APARTADO_DEFAULT, descuentos: descuentosDefault('ATELIER'), }) it('precio único: no depende de acabados', () => { expect(obtenerPrecioLista(unidad, true)).toBe(3997500) expect(obtenerPrecioLista(unidad, false)).toBe(3997500) }) it('resuelve N = 1 fijo (entrega inmediata)', () => { expect(resolverNumMensualidades(unidad, DESARROLLOS['ATELIER'])).toBe(1) }) it('BANCARIO: enganche 5% sobre LISTA; mensualidad = remanente en 1 pago', () => { const [p] = calcular() expect(p.enganche).toBeCloseTo(179875, 2) expect(p.mensualidadesTotal).toBeCloseTo(199875, 2) expect(p.mensualidad).toBeCloseTo(199875, 2) expect(p.numMensualidades).toBe(1) expect(p.pagoFinal).toBeCloseTo(3597750, 2) }) it('COFINAVIT: estructura Infonavit sin quirk (todo sobre precio final)', () => { const [, p] = calcular() expect(p.titulo).toBe('Crédito Cofinavit') expect(p.enganche).toBeCloseTo(79937.5, 2) expect(p.mensualidadesTotal).toBeCloseTo(99937.5, 2) expect(p.mensualidad).toBeCloseTo(99937.5, 2) expect(p.pagoFinal).toBeCloseTo(3797625, 2) expect(totalPagado(p, APARTADO_DEFAULT)).toBeCloseTo(3997500, 2) }) it('CONTADO', () => { const [, , p] = calcular() expect(p.enganche).toBeCloseTo(3577750, 2) expect(p.pagoFinal).toBeCloseTo(399750, 2) expect(p.mensualidad).toBeNull() }) it('SEMI CONTADO', () => { const [, , , p] = calcular() expect(p.enganche).toBeCloseTo(1978750, 2) expect(p.mensualidadesTotal).toBeCloseTo(1599000, 2) expect(p.pagoFinal).toBeCloseTo(399750, 2) }) it('bancario cuadra: apartado + enganche + mensualidades + pago final = precio final', () => { const [p] = calcular() expect(totalPagado(p, APARTADO_DEFAULT)).toBeCloseTo(3997500, 2) }) }) describe('ESTANZA — casa 52 (L = 2,437,250, apartado 20,000, N fijo = 1, desc 0)', () => { const unidad = porCodigo(UNIDADES_ESTANZA, 'ESTANZA-52') const calcular = () => calcularPropuestas({ unidad, acabados: false, apartado: APARTADO_DEFAULT, descuentos: descuentosDefault('ESTANZA'), }) it('BANCARIO: enganche 2.5% sobre final; pago final 90% sobre LISTA (quirk)', () => { const [p] = calcular() expect(p.enganche).toBeCloseTo(40931.25, 2) expect(p.mensualidadesTotal).toBeCloseTo(182793.75, 2) expect(p.mensualidad).toBeCloseTo(182793.75, 2) expect(p.pagoFinal).toBeCloseTo(2193525, 2) }) it('INFONAVIT: pago final 95% sobre LISTA (quirk)', () => { const [, p] = calcular() expect(p.enganche).toBeCloseTo(40931.25, 2) expect(p.mensualidadesTotal).toBeCloseTo(60931.25, 2) expect(p.pagoFinal).toBeCloseTo(2315387.5, 2) }) it('CONTADO', () => { const [, , p] = calcular() expect(p.enganche).toBeCloseTo(2173525, 2) expect(p.pagoFinal).toBeCloseTo(243725, 2) }) it('SEMI CONTADO', () => { const [, , , p] = calcular() expect(p.enganche).toBeCloseTo(1198625, 2) expect(p.mensualidadesTotal).toBeCloseTo(974900, 2) expect(p.pagoFinal).toBeCloseTo(243725, 2) }) }) describe('SENDA — 411-1-7 (L = 2,451,750, apartado 0 en el Excel, N = 4)', () => { const unidad = porCodigo(UNIDADES_SENDA, 'SENDA-411-1-7') const calcular = () => calcularPropuestas({ unidad, acabados: false, apartado: 0, // el Excel de SENDA traía MONTO APARTADO (C15) en 0 descuentos: descuentosDefault('SENDA'), numMeses: 4, }) it('BANCARIO: enganche 5%, mensualidades 5% ÷ N, pago final 90%', () => { const [p] = calcular() expect(p.enganche).toBeCloseTo(122587.5, 2) expect(p.mensualidadesTotal).toBeCloseTo(122587.5, 2) expect(p.mensualidad).toBeCloseTo(30646.875, 3) expect(p.pagoFinal).toBeCloseTo(2206575, 2) }) it('INFONAVIT: sin quirk de lista — mensualidades sobre precio final', () => { const [, p] = calcular() expect(p.enganche).toBeCloseTo(61293.75, 2) expect(p.mensualidadesTotal).toBeCloseTo(61293.75, 2) expect(p.mensualidad).toBeCloseTo(15323.4375, 4) expect(p.pagoFinal).toBeCloseTo(2329162.5, 2) }) it('CONTADO (desc default 2%)', () => { const [, , p] = calcular() expect(p.precioFinal).toBeCloseTo(2402715, 2) expect(p.enganche).toBeCloseTo(2162443.5, 2) expect(p.pagoFinal).toBeCloseTo(240271.5, 2) }) it('SEMI CONTADO (desc default 1%, split 50/45/5)', () => { const [, , , p] = calcular() expect(p.precioFinal).toBeCloseTo(2427232.5, 2) expect(p.enganche).toBeCloseTo(1213616.25, 2) expect(p.mensualidadesTotal).toBeCloseTo(1092254.625, 3) expect(p.mensualidad).toBeCloseTo(273063.65625, 5) expect(p.pagoFinal).toBeCloseTo(121361.625, 3) }) }) describe('SENDA — unidad sin fecha de entrega (411-25-A101)', () => { const unidad = porCodigo(UNIDADES_SENDA, 'SENDA-411-25-A101') it('N = null y mensualidades "—" con nota; enganche y pago final sí se calculan', () => { expect(unidad.fechaEntrega).toBeNull() const [bancario] = calcularPropuestas({ unidad, acabados: false, apartado: APARTADO_DEFAULT, descuentos: descuentosDefault('SENDA'), }) expect(bancario.numMensualidades).toBeNull() expect(bancario.mensualidad).toBeNull() expect(bancario.mensualidadesTotal).toBeNull() expect(bancario.notaMensualidades).toBe('sin fecha de entrega') expect(bancario.enganche).toBeCloseTo(0.05 * 1948000 - APARTADO_DEFAULT, 2) expect(bancario.pagoFinal).toBeCloseTo(0.9 * 1948000, 2) expect(totalPagado(bancario, APARTADO_DEFAULT)).toBeNull() }) }) describe('SOLANA — casa 6 (L = 2,560,000, apartado 20,000, mesesParaFirma = 1)', () => { const unidad = porCodigo(UNIDADES_SOLANA, 'SOLANA-6') const calcular = () => calcularPropuestas({ unidad, acabados: false, apartado: APARTADO_DEFAULT, descuentos: descuentosDefault('SOLANA'), }) it('N viene de mesesParaFirma de la unidad', () => { expect(unidad.mesesParaFirma).toBe(1) expect(resolverNumMensualidades(unidad, DESARROLLOS['SOLANA'])).toBe(1) }) it('BANCARIO: enganche 5% sobre final', () => { const [p] = calcular() expect(p.enganche).toBeCloseTo(108000, 2) expect(p.mensualidadesTotal).toBeCloseTo(128000, 2) expect(p.pagoFinal).toBeCloseTo(2304000, 2) }) it('INFONAVIT: enganche 0% con guarda (no resta el apartado)', () => { const [, p] = calcular() expect(p.enganche).toBe(0) expect(p.mensualidadesTotal).toBeCloseTo(128000, 2) expect(p.pagoFinal).toBeCloseTo(2432000, 2) }) it('CONTADO', () => { const [, , p] = calcular() expect(p.enganche).toBeCloseTo(2284000, 2) expect(p.pagoFinal).toBeCloseTo(256000, 2) }) it('SEMI CONTADO', () => { const [, , , p] = calcular() expect(p.enganche).toBeCloseTo(1260000, 2) expect(p.mensualidadesTotal).toBeCloseTo(1024000, 2) expect(p.pagoFinal).toBeCloseTo(256000, 2) }) }) describe('SOLANA — casa 9 (L = 2,605,000, mesesParaFirma = 1)', () => { const unidad = porCodigo(UNIDADES_SOLANA, 'SOLANA-9') const calcular = () => calcularPropuestas({ unidad, acabados: false, apartado: APARTADO_DEFAULT, descuentos: descuentosDefault('SOLANA'), }) it('las 4 propuestas al centavo', () => { const [bancario, infonavit, contado, semi] = calcular() expect(bancario.enganche).toBeCloseTo(110250, 2) expect(bancario.mensualidadesTotal).toBeCloseTo(130250, 2) expect(bancario.pagoFinal).toBeCloseTo(2344500, 2) expect(infonavit.enganche).toBe(0) expect(infonavit.mensualidadesTotal).toBeCloseTo(130250, 2) expect(infonavit.pagoFinal).toBeCloseTo(2474750, 2) expect(contado.enganche).toBeCloseTo(2324500, 2) expect(contado.pagoFinal).toBeCloseTo(260500, 2) expect(semi.enganche).toBeCloseTo(1282500, 2) expect(semi.mensualidadesTotal).toBeCloseTo(1042000, 2) expect(semi.pagoFinal).toBeCloseTo(260500, 2) }) }) describe('configuración de desarrollos', () => { it('los 5 desarrollos tienen 4 propuestas', () => { for (const id of ['DIAGONAL', 'ATELIER', 'ESTANZA', 'SENDA', 'SOLANA']) { expect(DESARROLLOS[id].propuestas).toHaveLength(4) } }) it('id desconocido cae a DIAGONAL', () => { expect(obtenerDesarrollo('NO_EXISTE').id).toBe('DIAGONAL') expect(obtenerDesarrollo(undefined).id).toBe('DIAGONAL') }) it('descuentos default por desarrollo', () => { expect(descuentosDefault('DIAGONAL')).toEqual({ creditoBancario: 0, creditoInfonavit: 0, contado: 0.02, semiContado: 0.01, }) expect(descuentosDefault('SENDA')).toEqual({ creditoBancario: 0, creditoInfonavit: 0, contado: 0.02, semiContado: 0.01, }) expect(descuentosDefault('ATELIER')).toEqual({ creditoBancario: 0, creditoInfonavit: 0, contado: 0, semiContado: 0, }) }) })