feat(cotizador): enganche adicional hasta 90% de anticipo total (UI, opciones de credito, PDF, API y persistencia)
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
MENSUALIDADES_DEFAULT,
|
||||
MENSUALIDADES_MAX,
|
||||
MENSUALIDADES_MIN,
|
||||
PCT_ENGANCHE_MAXIMO,
|
||||
calcularEsquemaFrasa,
|
||||
type EsquemaFrasaResultado,
|
||||
} from '../lib/esquema-frasa'
|
||||
@@ -53,6 +54,12 @@ interface UseCotizacionResult {
|
||||
/** true = la escrituración (5.5%) se difiere con el enganche; false = se paga a la firma */
|
||||
escrituracionDiferida: boolean
|
||||
setEscrituracionDiferida: (v: boolean) => void
|
||||
/** Enganche adicional (anticipo extra, opcional): el total de anticipos no
|
||||
* puede superar el 90% del precio (mínimo 10% queda por financiar) */
|
||||
engancheAdicional: number
|
||||
setEngancheAdicional: (v: number) => void
|
||||
/** Máximo de enganche adicional permitido por el tope del 90% (0 sin unidad) */
|
||||
engancheAdicionalMaximo: number
|
||||
precioLista: number
|
||||
/** Esquema FRASA 2026 calculado; null mientras no hay unidad elegida */
|
||||
resultado: EsquemaFrasaResultado | null
|
||||
@@ -74,6 +81,8 @@ export interface EstadoRestaurar {
|
||||
numMensualidades?: number
|
||||
/** Escrituración diferida guardada (default false) */
|
||||
escrituracionDiferida?: boolean
|
||||
/** Enganche adicional guardado (default 0) */
|
||||
engancheAdicional?: number
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,6 +100,7 @@ export function useCotizacion(reglas: ReglasDesarrollo = SIN_REGLAS): UseCotizac
|
||||
const [apartado, setApartado] = useState<number>(APARTADO_ESQUEMA_DEFAULT)
|
||||
const [numMensualidades, setNumMensualidades] = useState<number>(MENSUALIDADES_DEFAULT)
|
||||
const [escrituracionDiferida, setEscrituracionDiferida] = useState(false)
|
||||
const [engancheAdicional, setEngancheAdicionalState] = useState(0)
|
||||
|
||||
const params = desarrolloEfectivo(reglas, desarrollo)
|
||||
|
||||
@@ -115,6 +125,12 @@ export function useCotizacion(reglas: ReglasDesarrollo = SIN_REGLAS): UseCotizac
|
||||
setNumMensualidades(Math.max(MENSUALIDADES_MIN, Math.min(n, topeMensualidades())))
|
||||
}
|
||||
|
||||
/** Enganche adicional: acotado a >= 0; el esquema valida que el anticipo
|
||||
* total no supere el 90% del precio (mínimo 10% por financiar) */
|
||||
const setEngancheAdicional = (v: number) => {
|
||||
setEngancheAdicionalState(Number.isFinite(v) ? Math.max(0, v) : 0)
|
||||
}
|
||||
|
||||
const restaurar = (estado: EstadoRestaurar) => {
|
||||
setDesarrolloState(estado.desarrollo)
|
||||
setUnidad(estado.unidad)
|
||||
@@ -126,6 +142,7 @@ export function useCotizacion(reglas: ReglasDesarrollo = SIN_REGLAS): UseCotizac
|
||||
Math.min(estado.numMensualidades ?? MENSUALIDADES_DEFAULT, mensualidadesParaUnidad(estado.unidad)),
|
||||
)
|
||||
setEscrituracionDiferida(estado.escrituracionDiferida ?? false)
|
||||
setEngancheAdicionalState(Math.max(0, estado.engancheAdicional ?? 0))
|
||||
}
|
||||
|
||||
const nueva = () => {
|
||||
@@ -134,6 +151,7 @@ export function useCotizacion(reglas: ReglasDesarrollo = SIN_REGLAS): UseCotizac
|
||||
setApartado(APARTADO_ESQUEMA_DEFAULT)
|
||||
setNumMensualidades(MENSUALIDADES_DEFAULT)
|
||||
setEscrituracionDiferida(false)
|
||||
setEngancheAdicionalState(0)
|
||||
}
|
||||
|
||||
const numMeses = useMemo(
|
||||
@@ -150,15 +168,36 @@ export function useCotizacion(reglas: ReglasDesarrollo = SIN_REGLAS): UseCotizac
|
||||
// incluso $0; solo se acota a >= 0 como guarda contra capturas raras
|
||||
const apartadoEfectivo = Math.max(0, apartado)
|
||||
|
||||
/**
|
||||
* Esquema FRASA 2026 calculado; null mientras no hay unidad elegida. El
|
||||
* enganche adicional se acota al máximo que permite el 90% de anticipo
|
||||
* (el esquema lanza error si el total lo rebasa; aquí se previene).
|
||||
*/
|
||||
const resultado = useMemo<EsquemaFrasaResultado | null>(() => {
|
||||
if (!unidad || precioLista <= 0) return null
|
||||
return calcularEsquemaFrasa({
|
||||
const base = calcularEsquemaFrasa({
|
||||
precio: precioLista,
|
||||
apartado: apartadoEfectivo,
|
||||
numMensualidades,
|
||||
escrituracionDiferida,
|
||||
})
|
||||
}, [unidad, precioLista, apartadoEfectivo, numMensualidades, escrituracionDiferida])
|
||||
const maxAdicional = Math.max(0, precioLista * PCT_ENGANCHE_MAXIMO - base.totalPagado)
|
||||
const adicionalEfectivo = Math.min(Math.max(0, engancheAdicional), maxAdicional)
|
||||
if (adicionalEfectivo === 0) return base
|
||||
return calcularEsquemaFrasa({
|
||||
precio: precioLista,
|
||||
apartado: apartadoEfectivo,
|
||||
numMensualidades,
|
||||
escrituracionDiferida,
|
||||
engancheAdicional: adicionalEfectivo,
|
||||
})
|
||||
}, [unidad, precioLista, apartadoEfectivo, numMensualidades, escrituracionDiferida, engancheAdicional])
|
||||
|
||||
/** Máximo de enganche adicional que permite el 90% de anticipo (0 sin unidad) */
|
||||
const engancheAdicionalMaximo = useMemo(() => {
|
||||
if (!resultado) return 0
|
||||
return Math.max(0, precioLista * PCT_ENGANCHE_MAXIMO - (resultado.totalPagado - resultado.engancheAdicional))
|
||||
}, [resultado, precioLista])
|
||||
|
||||
return {
|
||||
desarrollo,
|
||||
@@ -175,6 +214,9 @@ export function useCotizacion(reglas: ReglasDesarrollo = SIN_REGLAS): UseCotizac
|
||||
setNumMensualidades: ajustarMensualidades,
|
||||
escrituracionDiferida,
|
||||
setEscrituracionDiferida,
|
||||
engancheAdicional,
|
||||
setEngancheAdicional,
|
||||
engancheAdicionalMaximo,
|
||||
precioLista,
|
||||
resultado,
|
||||
numMeses,
|
||||
|
||||
Reference in New Issue
Block a user