181 lines
6.5 KiB
TypeScript
181 lines
6.5 KiB
TypeScript
// @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)
|
|
})
|
|
})
|