Flujo aprobado por el cliente (28 sep 2026), porcentajes globales para todos los desarrollos: - Apartado: cantidad libre (sin minimo), se acredita dentro del enganche de firma; el excedente se descuenta del enganche - Enganche de firma: 2.5% del precio (saldo a firma = max(0, 2.5% - apartado)) - Enganche: 7.5%, diferible en N mensualidades (N a eleccion del vendedor, 1-36) - Gastos de escrituracion: 5.5%, pago unico a la firma o diferido con el enganche (toggle) - lib/esquema-frasa.ts (frontend) + cotizacion-esquema.service.ts (backend): modulos puros espejo, caso dorado M/apartado 5k/N=14 en tests de ambos - Migracion 20260928120000: 7 columnas nuevas en Cotizacion (esquema, saldoFirma, engancheDiferible, escrituracion, etc.); legacy se rellena para compatibilidad de reportes (tipoPropuesta=FRASA_2026) - UI: panel de linea de tiempo (Hoy -> Firma -> Mensualidades) sustituye a propuestas/comparadores; PDF y WhatsApp con el esquema unico - API recalcula server-side; smoke end-to-end verificado contra inventario - 743 tests API + 128 cotizador en verde
144 lines
4.9 KiB
TypeScript
144 lines
4.9 KiB
TypeScript
import { useMemo, useState } from 'react'
|
|
import type { Unidad, ParametrosDesarrollo } from '../types'
|
|
import { obtenerPrecioLista, resolverNumMensualidades } from '../lib/calculations'
|
|
import { DESARROLLO_DEFAULT } from '../data/desarrollos'
|
|
import {
|
|
MENSUALIDADES_DEFAULT,
|
|
calcularEsquemaFrasa,
|
|
type EsquemaFrasaResultado,
|
|
} from '../lib/esquema-frasa'
|
|
import { SIN_REGLAS, desarrolloEfectivo, type ReglasDesarrollo } from '../lib/reglas'
|
|
|
|
/** Apartado con el que arranca (y se reinicia) el cotizador: cantidad libre */
|
|
export const APARTADO_ESQUEMA_DEFAULT = 15000
|
|
|
|
interface UseCotizacionResult {
|
|
desarrollo: string
|
|
/** Cambia de desarrollo: limpia la unidad elegida */
|
|
setDesarrollo: (id: string) => void
|
|
params: ParametrosDesarrollo
|
|
unidad: Unidad | null
|
|
setUnidad: (u: Unidad | null) => void
|
|
acabados: boolean
|
|
setAcabados: (v: boolean) => void
|
|
apartado: number
|
|
setApartado: (v: number) => void
|
|
/** Apartado aplicado a los cálculos (acotado a >= 0; el vendedor puede apartar con cualquier monto, incluso $0) */
|
|
apartadoEfectivo: number
|
|
/** Número de mensualidades del diferimiento (elección del vendedor, 1..36) */
|
|
numMensualidades: number
|
|
setNumMensualidades: (n: number) => void
|
|
/** true = la escrituración (5.5%) se difiere con el enganche; false = se paga a la firma */
|
|
escrituracionDiferida: boolean
|
|
setEscrituracionDiferida: (v: boolean) => void
|
|
precioLista: number
|
|
/** Esquema FRASA 2026 calculado; null mientras no hay unidad elegida */
|
|
resultado: EsquemaFrasaResultado | null
|
|
/** Meses restantes para la entrega de la unidad (ficha técnica); null si no se pueden resolver */
|
|
numMeses: number | null
|
|
/** Restaura el estado completo desde una cotización guardada (historial) */
|
|
restaurar: (estado: EstadoRestaurar) => void
|
|
/** Reinicia la cotización (botón "Nueva") */
|
|
nueva: () => void
|
|
}
|
|
|
|
export interface EstadoRestaurar {
|
|
desarrollo: string
|
|
/** Unidad ya resuelta del inventario cargado */
|
|
unidad: Unidad
|
|
acabados: boolean
|
|
apartado: number
|
|
/** Mensualidades del diferimiento guardadas (default 14) */
|
|
numMensualidades?: number
|
|
/** Escrituración diferida guardada (default false) */
|
|
escrituracionDiferida?: boolean
|
|
}
|
|
|
|
/**
|
|
* Estado central de la cotización con el esquema único FRASA 2026:
|
|
* desarrollo, unidad, acabados, apartado libre, mensualidades del
|
|
* diferimiento (1..36, default 14) y si la escrituración se difiere o se paga
|
|
* a la firma. El resultado sale de calcularEsquemaFrasa (módulo puro, mismo
|
|
* motor que el API). Ya no hay propuestas: descuentos, enganche en % e índice
|
|
* de propuesta desaparecieron con las 4 propuestas.
|
|
*/
|
|
export function useCotizacion(reglas: ReglasDesarrollo = SIN_REGLAS): 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_ESQUEMA_DEFAULT)
|
|
const [numMensualidades, setNumMensualidades] = useState<number>(MENSUALIDADES_DEFAULT)
|
|
const [escrituracionDiferida, setEscrituracionDiferida] = useState(false)
|
|
|
|
const params = desarrolloEfectivo(reglas, desarrollo)
|
|
|
|
const setDesarrollo = (id: string) => {
|
|
setDesarrolloState(id)
|
|
setUnidad(null)
|
|
setAcabados(false)
|
|
}
|
|
|
|
const restaurar = (estado: EstadoRestaurar) => {
|
|
setDesarrolloState(estado.desarrollo)
|
|
setUnidad(estado.unidad)
|
|
setAcabados(estado.acabados)
|
|
setApartado(estado.apartado)
|
|
setNumMensualidades(estado.numMensualidades ?? MENSUALIDADES_DEFAULT)
|
|
setEscrituracionDiferida(estado.escrituracionDiferida ?? false)
|
|
}
|
|
|
|
const nueva = () => {
|
|
setUnidad(null)
|
|
setAcabados(false)
|
|
setApartado(APARTADO_ESQUEMA_DEFAULT)
|
|
setNumMensualidades(MENSUALIDADES_DEFAULT)
|
|
setEscrituracionDiferida(false)
|
|
}
|
|
|
|
const numMeses = useMemo(
|
|
() => (unidad ? resolverNumMensualidades(unidad, params) : null),
|
|
[unidad, params],
|
|
)
|
|
|
|
const precioLista = useMemo(
|
|
() => (unidad ? obtenerPrecioLista(unidad, acabados) : 0),
|
|
[unidad, acabados],
|
|
)
|
|
|
|
// Sin mínimo de apartado: el vendedor puede apartar con cualquier monto,
|
|
// incluso $0; solo se acota a >= 0 como guarda contra capturas raras
|
|
const apartadoEfectivo = Math.max(0, apartado)
|
|
|
|
const resultado = useMemo<EsquemaFrasaResultado | null>(() => {
|
|
if (!unidad || precioLista <= 0) return null
|
|
return calcularEsquemaFrasa({
|
|
precio: precioLista,
|
|
apartado: apartadoEfectivo,
|
|
numMensualidades,
|
|
escrituracionDiferida,
|
|
})
|
|
}, [unidad, precioLista, apartadoEfectivo, numMensualidades, escrituracionDiferida])
|
|
|
|
return {
|
|
desarrollo,
|
|
setDesarrollo,
|
|
params,
|
|
unidad,
|
|
setUnidad,
|
|
acabados,
|
|
setAcabados,
|
|
apartado,
|
|
setApartado,
|
|
apartadoEfectivo,
|
|
numMensualidades,
|
|
setNumMensualidades,
|
|
escrituracionDiferida,
|
|
setEscrituracionDiferida,
|
|
precioLista,
|
|
resultado,
|
|
numMeses,
|
|
restaurar,
|
|
nueva,
|
|
}
|
|
}
|