// @vitest-environment jsdom import { afterEach, describe, expect, it } from 'vitest' import { act, cleanup, renderHook } from '@testing-library/react' import { APARTADO_ESQUEMA_DEFAULT, mensualidadesParaUnidad, useCotizacion } from './useCotizacion' import { SIN_REGLAS } from '../lib/reglas' import { MENSUALIDADES_DEFAULT, calcularEsquemaFrasa } from '../lib/esquema-frasa' import { INVENTARIO_SEED } from '../data/unidades' /** * Estado central con el esquema único FRASA 2026: apartado libre (default * $15,000), mensualidades del diferimiento (default 14) y escrituración a la * firma o diferida. Ya no hay propuestas: el resultado sale de * calcularEsquemaFrasa (mismo motor que el API). */ const unidadDiagonal = INVENTARIO_SEED.find((u) => u.desarrollo === 'DIAGONAL')! function montar() { const hook = renderHook(() => useCotizacion(SIN_REGLAS)) act(() => { hook.result.current.setUnidad(unidadDiagonal) }) return hook } afterEach(cleanup) describe('useCotizacion — esquema único FRASA 2026', () => { it('arranca con apartado $15,000, 14 mensualidades y escrituración a la firma', () => { const { result } = renderHook(() => useCotizacion(SIN_REGLAS)) expect(result.current.apartado).toBe(APARTADO_ESQUEMA_DEFAULT) expect(result.current.apartadoEfectivo).toBe(15000) expect(result.current.numMensualidades).toBe(MENSUALIDADES_DEFAULT) expect(result.current.escrituracionDiferida).toBe(false) }) it('elegir unidad con fecha de entrega: las mensualidades = meses que faltan para entregarla', () => { const { result } = renderHook(() => useCotizacion(SIN_REGLAS)) act(() => { result.current.setUnidad(unidadDiagonal) }) expect(result.current.numMensualidades).toBe(mensualidadesParaUnidad(unidadDiagonal)) expect(result.current.numMensualidades).not.toBe(MENSUALIDADES_DEFAULT) }) it('elegir unidad SIN fecha de entrega: cae al default del esquema (14)', () => { const { result } = renderHook(() => useCotizacion(SIN_REGLAS)) act(() => { result.current.setUnidad({ ...unidadDiagonal, fechaEntrega: null }) }) expect(result.current.numMensualidades).toBe(MENSUALIDADES_DEFAULT) }) it('mensualidadesParaUnidad: acota a 1..36 y cae al default sin fecha', () => { const hoy = new Date('2026-09-29T12:00:00') const base = { ...unidadDiagonal } expect(mensualidadesParaUnidad({ ...base, fechaEntrega: '2026-10-15' }, hoy)).toBe(1) expect(mensualidadesParaUnidad({ ...base, fechaEntrega: '2035-12-31' }, hoy)).toBe(36) expect(mensualidadesParaUnidad({ ...base, fechaEntrega: '2027-09-29' }, hoy)).toBe(12) expect(mensualidadesParaUnidad(null, hoy)).toBe(MENSUALIDADES_DEFAULT) }) it('el resultado cuadra con calcularEsquemaFrasa sobre el precio de lista', () => { const { result } = montar() const { unidad, acabados } = result.current const esperado = calcularEsquemaFrasa({ precio: result.current.precioLista, apartado: 15000, numMensualidades: mensualidadesParaUnidad(unidadDiagonal), escrituracionDiferida: false, }) expect(result.current.resultado).toEqual(esperado) // El esquema siempre liquida el 15.5% del precio (2.5 + 7.5 + 5.5) expect(result.current.resultado?.totalPagado).toBeCloseTo(result.current.precioLista * 0.155, 2) expect(unidad).toBe(unidadDiagonal) expect(acabados).toBe(false) }) it('sin unidad no hay resultado (panel vacío)', () => { const { result } = montar() act(() => { result.current.setUnidad(null) }) expect(result.current.resultado).toBeNull() expect(result.current.precioLista).toBe(0) }) it('el apartado libre alimenta el saldo a firma; el excedente acredita al enganche', () => { const { result } = montar() const excedente = Math.round(result.current.precioLista * 0.025) + 5000 act(() => { result.current.setApartado(excedente) }) const r = result.current.resultado! expect(r.excedenteApartado).toBeGreaterThan(0) expect(r.saldoFirma).toBe(0) // El total no cambia: el excedente se mueve de concepto, no se cobra dos veces expect(r.totalPagado).toBeCloseTo(result.current.precioLista * 0.155, 2) }) it('las mensualidades elegidas recalculan la mensualidad sobre el diferible', () => { const { result } = montar() act(() => { result.current.setNumMensualidades(12) }) const r = result.current.resultado! expect(r.numMensualidades).toBe(12) expect(r.mensualidad).toBeCloseTo(r.baseDiferible / 12, 6) }) it('escrituración diferida: sube el diferible y baja el pago a la firma', () => { const { result } = montar() act(() => { result.current.setEscrituracionDiferida(true) }) const r = result.current.resultado! expect(r.escrituracionDiferida).toBe(true) expect(r.baseDiferible).toBeCloseTo(r.enganche + r.escrituracion, 6) expect(r.pagoUnicoFirma).toBeCloseTo(r.saldoFirma, 6) }) it('restaurar carga una cotización guardada del esquema (N y escrituración)', () => { const { result } = montar() act(() => { result.current.restaurar({ desarrollo: 'DIAGONAL', unidad: unidadDiagonal, acabados: true, apartado: 20000, numMensualidades: 24, escrituracionDiferida: true, }) }) expect(result.current.acabados).toBe(true) expect(result.current.apartadoEfectivo).toBe(20000) expect(result.current.numMensualidades).toBe(24) expect(result.current.escrituracionDiferida).toBe(true) expect(result.current.resultado?.numMensualidades).toBe(24) }) it('restaurar una cotización legacy (sin N ni escrituración) cae a los defaults', () => { const { result } = montar() act(() => { result.current.restaurar({ desarrollo: 'DIAGONAL', unidad: unidadDiagonal, acabados: false, apartado: 20000, }) }) expect(result.current.numMensualidades).toBe(MENSUALIDADES_DEFAULT) expect(result.current.escrituracionDiferida).toBe(false) }) it('nueva reinicia apartado, mensualidades y escrituración, y limpia la unidad', () => { const { result } = montar() act(() => { result.current.setApartado(40000) result.current.setNumMensualidades(6) result.current.setEscrituracionDiferida(true) }) act(() => { result.current.nueva() }) expect(result.current.unidad).toBeNull() expect(result.current.apartado).toBe(APARTADO_ESQUEMA_DEFAULT) expect(result.current.numMensualidades).toBe(MENSUALIDADES_DEFAULT) expect(result.current.escrituracionDiferida).toBe(false) }) })