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
This commit is contained in:
2026-08-22 04:31:21 +00:00
parent 05c44478ec
commit afb454b54c
33 changed files with 2074 additions and 338 deletions

View File

@@ -1,15 +1,19 @@
import { useMemo, useState } from 'react'
import type { Unidad, DescuentosPropuestas } from '../types'
import type { Unidad, DescuentosPropuestas, ParametrosDesarrollo } from '../types'
import {
APARTADO_DEFAULT,
DESCUENTOS_DEFAULT,
calcularNumMensualidades,
calcularPropuestas,
obtenerPrecioLista,
resolverNumMensualidades,
type PropuestaResultado,
} from '../lib/calculations'
import { DESARROLLO_DEFAULT, descuentosDefault, obtenerDesarrollo } from '../data/desarrollos'
interface UseCotizacionResult {
desarrollo: string
/** Cambia de desarrollo: limpia la unidad elegida y reinicia los descuentos */
setDesarrollo: (id: string) => void
params: ParametrosDesarrollo
unidad: Unidad | null
setUnidad: (u: Unidad | null) => void
acabados: boolean
@@ -19,24 +23,34 @@ interface UseCotizacionResult {
descuentos: DescuentosPropuestas
setDescuento: (clave: keyof DescuentosPropuestas, valor: number) => void
precioLista: number
numMeses: number
numMeses: number | null
propuestas: PropuestaResultado[]
}
/** Estado central de la cotización: unidad, acabados, apartado y descuentos */
/** Estado central de la cotización: desarrollo, unidad, acabados, apartado y descuentos */
export function useCotizacion(): UseCotizacionResult {
const [desarrollo, setDesarrolloState] = useState<string>(DESARROLLO_DEFAULT)
const [unidad, setUnidad] = useState<Unidad | null>(null)
const [acabados, setAcabados] = useState(false)
const [apartado, setApartado] = useState<number>(APARTADO_DEFAULT)
const [descuentos, setDescuentos] = useState<DescuentosPropuestas>({ ...DESCUENTOS_DEFAULT })
const [descuentos, setDescuentos] = useState<DescuentosPropuestas>(descuentosDefault(DESARROLLO_DEFAULT))
const params = obtenerDesarrollo(desarrollo)
const setDesarrollo = (id: string) => {
setDesarrolloState(id)
setUnidad(null)
setAcabados(false)
setDescuentos(descuentosDefault(id))
}
const setDescuento = (clave: keyof DescuentosPropuestas, valor: number) => {
setDescuentos((prev) => ({ ...prev, [clave]: valor }))
}
const numMeses = useMemo(
() => (unidad ? calcularNumMensualidades(unidad.fechaEntrega) : 0),
[unidad],
() => (unidad ? resolverNumMensualidades(unidad, params) : null),
[unidad, params],
)
const precioLista = useMemo(
@@ -46,10 +60,13 @@ export function useCotizacion(): UseCotizacionResult {
const propuestas = useMemo(() => {
if (!unidad) return []
return calcularPropuestas({ unidad, acabados, apartado, descuentos })
}, [unidad, acabados, apartado, descuentos])
return calcularPropuestas({ unidad, acabados, apartado, descuentos, desarrollo })
}, [unidad, acabados, apartado, descuentos, desarrollo])
return {
desarrollo,
setDesarrollo,
params,
unidad,
setUnidad,
acabados,