feat(cotizador): enganche adicional hasta 90% de anticipo total (UI, opciones de credito, PDF, API y persistencia)
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
MENSUALIDADES_MIN,
|
||||
PCT_ENGANCHE,
|
||||
PCT_ENGANCHE_FIRMA,
|
||||
PCT_ENGANCHE_MAXIMO,
|
||||
PCT_ESCRITURACION,
|
||||
type EsquemaFrasaResultado,
|
||||
} from '../lib/esquema-frasa'
|
||||
@@ -15,6 +16,11 @@ interface Props {
|
||||
fechaEntrega?: string | null
|
||||
/** Tope de mensualidades: los meses que faltan para la entrega (default 36 sin fecha) */
|
||||
maxMensualidades?: number
|
||||
/** Enganche adicional capturado (>= 0) */
|
||||
engancheAdicional: number
|
||||
onEngancheAdicional: (v: number) => void
|
||||
/** Máximo de enganche adicional permitido por el tope del 90% de anticipo */
|
||||
engancheAdicionalMaximo: number
|
||||
numMensualidades: number
|
||||
onNumMensualidades: (n: number) => void
|
||||
escrituracionDiferida: boolean
|
||||
@@ -38,6 +44,9 @@ export function EsquemaPanel({
|
||||
esquema,
|
||||
fechaEntrega,
|
||||
maxMensualidades = MENSUALIDADES_MAX,
|
||||
engancheAdicional,
|
||||
onEngancheAdicional,
|
||||
engancheAdicionalMaximo,
|
||||
numMensualidades,
|
||||
onNumMensualidades,
|
||||
escrituracionDiferida,
|
||||
@@ -132,9 +141,53 @@ export function EsquemaPanel({
|
||||
{formatoMoneda(esquema.totalPagado)}
|
||||
</dd>
|
||||
</div>
|
||||
{esquema.engancheAdicional > 0 && (
|
||||
<div className="flex flex-col gap-1 py-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<dt className="text-sm text-[#64748B] dark:text-[#94A3B8]">Enganche adicional</dt>
|
||||
<dd className="text-sm font-medium text-[#111827] dark:text-[#E2E8F0]">
|
||||
{formatoMoneda(esquema.engancheAdicional)}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-1 py-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<dt className="text-sm font-semibold text-[#111827] dark:text-[#E2E8F0]">
|
||||
Saldo a financiar
|
||||
</dt>
|
||||
<dd className="text-sm font-semibold text-[#111827] dark:text-[#E2E8F0]">
|
||||
{formatoMoneda(esquema.saldoAFinanciar)}
|
||||
<span className="ml-2 text-xs font-normal text-[#64748B] dark:text-[#94A3B8]">
|
||||
{formatoPorcentaje(esquema.saldoAFinanciar / esquema.precio)} del precio
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{/* Enganche adicional: anticipo extra hasta completar 90% del precio */}
|
||||
<section className="rounded-xl border border-[#E5E7EB] bg-white p-6 dark:border-[#1E293B] dark:bg-[#1E293B] shadow-sm">
|
||||
<label htmlFor="enganche-adicional" className="mb-2 block text-xs font-medium text-[#4B5563] dark:text-[#CBD5E1]">
|
||||
Enganche adicional (opcional)
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-[#64748B] dark:text-[#94A3B8]">$</span>
|
||||
<input
|
||||
id="enganche-adicional"
|
||||
type="number"
|
||||
min={0}
|
||||
max={engancheAdicionalMaximo}
|
||||
step={1000}
|
||||
value={engancheAdicional === 0 ? '' : engancheAdicional}
|
||||
placeholder="0"
|
||||
onChange={(e) => onEngancheAdicional(e.target.value === '' ? 0 : Number(e.target.value))}
|
||||
className="w-full rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm transition-colors focus:border-accent focus:ring-2 focus:ring-accent/20 focus:outline-none dark:bg-[#0F172A] dark:border-[#334155] dark:text-[#E2E8F0]"
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-[#64748B] dark:text-[#94A3B8]">
|
||||
Anticipo extra del cliente; máximo {formatoMoneda(engancheAdicionalMaximo)} para no pasar del{' '}
|
||||
{formatoPorcentaje(PCT_ENGANCHE_MAXIMO)} de enganche total (queda un mínimo del 10% por financiar)
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Controles del diferimiento */}
|
||||
<section className="flex flex-col gap-4 rounded-xl border border-[#E5E7EB] bg-white p-6 dark:border-[#1E293B] dark:bg-[#1E293B] shadow-sm sm:flex-row sm:items-end">
|
||||
<div className="flex-1">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -79,3 +79,37 @@ describe('calcularEsquemaFrasa — mensualidades', () => {
|
||||
expect(validarEsquemaInput({ precio: 1000000, apartado: 15000, numMensualidades: 14, escrituracionDiferida: false })).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('calcularEsquemaFrasa — enganche adicional (hasta 90% de anticipo)', () => {
|
||||
const base = { precio: 1000000, apartado: 15000, numMensualidades: 14, escrituracionDiferida: false }
|
||||
|
||||
it('sin adicional: queda en 0 y el saldo a financiar es el 84.5%', () => {
|
||||
const r = calcularEsquemaFrasa(base)
|
||||
expect(r.engancheAdicional).toBe(0)
|
||||
expect(r.saldoAFinanciar).toBeCloseTo(845000, 2)
|
||||
})
|
||||
|
||||
it('el adicional reduce el saldo a financiar peso a peso', () => {
|
||||
const r = calcularEsquemaFrasa({ ...base, engancheAdicional: 400000 })
|
||||
expect(r.engancheAdicional).toBe(400000)
|
||||
expect(r.totalPagado).toBeCloseTo(155000, 2)
|
||||
expect(r.saldoAFinanciar).toBeCloseTo(445000, 2)
|
||||
// El esquema base no cambia: el adicional es aparte
|
||||
expect(r.enganche).toBeCloseTo(75000, 2)
|
||||
expect(r.mensualidad).toBeCloseTo(75000 / 14, 2)
|
||||
})
|
||||
|
||||
it('tope exacto: 90% del precio menos el esquema (745,000) es válido', () => {
|
||||
const r = calcularEsquemaFrasa({ ...base, engancheAdicional: 745000 })
|
||||
expect(r.saldoAFinanciar).toBeCloseTo(100000, 2)
|
||||
})
|
||||
|
||||
it('rechaza pasar del 90% de enganche total', () => {
|
||||
expect(() => calcularEsquemaFrasa({ ...base, engancheAdicional: 745001 })).toThrow(/90%/)
|
||||
expect(() => calcularEsquemaFrasa({ ...base, engancheAdicional: 900000 })).toThrow(/90%/)
|
||||
})
|
||||
|
||||
it('rechaza adicional negativo', () => {
|
||||
expect(validarEsquemaInput({ ...base, engancheAdicional: -1 })).toMatch(/adicional/i)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
export const PCT_ENGANCHE_FIRMA = 0.025
|
||||
export const PCT_ENGANCHE = 0.075
|
||||
export const PCT_ESCRITURACION = 0.055
|
||||
/** Tope total de enganche (esquema + adicional): el cliente puede dar hasta
|
||||
* 90% de anticipo, quedando un mínimo del 10% del precio a financiar */
|
||||
export const PCT_ENGANCHE_MAXIMO = 0.9
|
||||
|
||||
export const MENSUALIDADES_DEFAULT = 14
|
||||
export const MENSUALIDADES_MIN = 1
|
||||
@@ -31,6 +34,9 @@ export interface EsquemaFrasaInput {
|
||||
numMensualidades: number
|
||||
/** true = la escrituración se difiere con el enganche; false = se paga a la firma */
|
||||
escrituracionDiferida: boolean
|
||||
/** Enganche adicional (anticipo extra, >= 0): opcional; el total de
|
||||
* anticipos (esquema + adicional) no puede superar el 90% del precio */
|
||||
engancheAdicional?: number
|
||||
}
|
||||
|
||||
export interface EsquemaFrasaResultado {
|
||||
@@ -54,6 +60,10 @@ export interface EsquemaFrasaResultado {
|
||||
mensualidad: number
|
||||
/** apartado + firma + diferible — debe cuadrar con el precio saldado */
|
||||
totalPagado: number
|
||||
/** Anticipo extra sobre el esquema (0 si no se captura) */
|
||||
engancheAdicional: number
|
||||
/** Lo que queda por financiar: precio − esquema − adicional (mínimo 10%) */
|
||||
saldoAFinanciar: number
|
||||
}
|
||||
|
||||
/** Error de validación de entrada; null si es válida */
|
||||
@@ -64,6 +74,8 @@ export function validarEsquemaInput(input: EsquemaFrasaInput): string | null {
|
||||
if (!Number.isInteger(input.numMensualidades)) return 'Las mensualidades deben ser un número entero'
|
||||
if (input.numMensualidades < MENSUALIDADES_MIN) return `Mínimo ${MENSUALIDADES_MIN} mensualidad`
|
||||
if (input.numMensualidades > MENSUALIDADES_MAX) return `Máximo ${MENSUALIDADES_MAX} mensualidades`
|
||||
const adicional = input.engancheAdicional ?? 0
|
||||
if (!Number.isFinite(adicional) || adicional < 0) return 'El enganche adicional no puede ser negativo'
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -72,6 +84,7 @@ export function calcularEsquemaFrasa(input: EsquemaFrasaInput): EsquemaFrasaResu
|
||||
if (error) throw new Error(error)
|
||||
|
||||
const { precio, apartado, numMensualidades, escrituracionDiferida } = input
|
||||
const engancheAdicional = input.engancheAdicional ?? 0
|
||||
const engancheFirmaTotal = precio * PCT_ENGANCHE_FIRMA
|
||||
const saldoFirma = Math.max(0, engancheFirmaTotal - apartado)
|
||||
const excedenteApartado = Math.max(0, apartado - engancheFirmaTotal)
|
||||
@@ -81,6 +94,14 @@ export function calcularEsquemaFrasa(input: EsquemaFrasaInput): EsquemaFrasaResu
|
||||
const baseDiferible = enganche + (escrituracionDiferida ? escrituracion : 0)
|
||||
const mensualidad = baseDiferible / numMensualidades
|
||||
const totalPagado = apartado + pagoUnicoFirma + baseDiferible
|
||||
// El anticipo total (esquema + adicional) no puede superar el 90% del precio
|
||||
if (totalPagado + engancheAdicional > precio * PCT_ENGANCHE_MAXIMO + 1e-9) {
|
||||
throw new Error(
|
||||
`El enganche total no puede superar el ${PCT_ENGANCHE_MAXIMO * 100}% del precio (` +
|
||||
`máx. ${(precio * PCT_ENGANCHE_MAXIMO - totalPagado).toFixed(2)} de enganche adicional)`,
|
||||
)
|
||||
}
|
||||
const saldoAFinanciar = precio - totalPagado - engancheAdicional
|
||||
|
||||
return {
|
||||
precio,
|
||||
@@ -96,5 +117,7 @@ export function calcularEsquemaFrasa(input: EsquemaFrasaInput): EsquemaFrasaResu
|
||||
numMensualidades,
|
||||
mensualidad,
|
||||
totalPagado,
|
||||
engancheAdicional,
|
||||
saldoAFinanciar,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,3 +182,21 @@ describe('calcularOpcionCredito — tasa cero', () => {
|
||||
expect(r.interesesEstimados).toBe(Number((mensualidad * 240 - 845000).toFixed(2)))
|
||||
})
|
||||
})
|
||||
|
||||
describe('calcularOpcionCredito — enganche adicional', () => {
|
||||
it('el saldo a financiar de cada opción toma el adicional (y se acota al 90%)', () => {
|
||||
const base = {
|
||||
precioLista: 1000000,
|
||||
apartado: 15000,
|
||||
numMensualidades: 14,
|
||||
escrituracionDiferida: false,
|
||||
ajustes: AJUSTES_DEFAULT.CREDITO_BANCARIO,
|
||||
}
|
||||
const r = calcularOpcionCredito({ ...base, tipo: 'CREDITO_BANCARIO', engancheAdicional: 400000 })
|
||||
expect(r.saldoAFinanciar).toBe(445000)
|
||||
expect(r.montoCredito).toBe(445000)
|
||||
// Contado: precio final 980,000 → saldo 828,100 − 400,000 = 428,100 de contado
|
||||
const contado = calcularOpcionCredito({ ...base, tipo: 'CONTADO', ajustes: AJUSTES_DEFAULT.CONTADO, engancheAdicional: 400000 })
|
||||
expect(contado.saldoAFinanciar).toBe(428100)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* Módulo puro (sin UI, sin API) para probarlo a fondo.
|
||||
*/
|
||||
|
||||
import { calcularEsquemaFrasa, type EsquemaFrasaResultado } from './esquema-frasa'
|
||||
import { PCT_ENGANCHE_MAXIMO, calcularEsquemaFrasa, type EsquemaFrasaResultado } from './esquema-frasa'
|
||||
|
||||
export type TipoCredito =
|
||||
| 'CREDITO_BANCARIO'
|
||||
@@ -68,6 +68,8 @@ export interface EntradaOpcion {
|
||||
apartado: number
|
||||
numMensualidades: number
|
||||
escrituracionDiferida: boolean
|
||||
/** Enganche adicional (monto absoluto, >= 0): se acota al 90% del anticipo */
|
||||
engancheAdicional?: number
|
||||
tipo: TipoCredito
|
||||
ajustes: AjustesCredito
|
||||
}
|
||||
@@ -135,14 +137,28 @@ export function calcularOpcionCredito(e: EntradaOpcion): ResultadoOpcionCredito
|
||||
const montoDescuento = round2(e.precioLista * descuento)
|
||||
const precioFinal = e.precioLista - montoDescuento
|
||||
|
||||
const esquema = calcularEsquemaFrasa({
|
||||
const esquemaBase = calcularEsquemaFrasa({
|
||||
precio: precioFinal,
|
||||
apartado: e.apartado,
|
||||
numMensualidades: e.numMensualidades,
|
||||
escrituracionDiferida: e.escrituracionDiferida,
|
||||
})
|
||||
// El adicional se acota igual que en el hook: el anticipo total no puede
|
||||
// superar el 90% del precio final de la opción
|
||||
const adicionalSolicitado = Math.max(0, e.engancheAdicional ?? 0)
|
||||
const adicionalEfectivo = Math.min(adicionalSolicitado, round2(precioFinal * PCT_ENGANCHE_MAXIMO - esquemaBase.totalPagado))
|
||||
const esquema =
|
||||
adicionalEfectivo > 0
|
||||
? calcularEsquemaFrasa({
|
||||
precio: precioFinal,
|
||||
apartado: e.apartado,
|
||||
numMensualidades: e.numMensualidades,
|
||||
escrituracionDiferida: e.escrituracionDiferida,
|
||||
engancheAdicional: adicionalEfectivo,
|
||||
})
|
||||
: esquemaBase
|
||||
|
||||
const saldoAFinanciar = round2(precioFinal - esquema.totalPagado)
|
||||
const saldoAFinanciar = round2(esquema.saldoAFinanciar)
|
||||
const esContado = e.tipo === 'CONTADO'
|
||||
const pctContado = e.tipo === 'SEMI_CONTADO' ? (e.ajustes.pctContado ?? 0) : 0
|
||||
const pagoContado = esContado ? 0 : round2(saldoAFinanciar * pctContado)
|
||||
|
||||
@@ -178,6 +178,10 @@ export function crearDocumentoPdf({ unidad, acabados, apartado, precioLista, esq
|
||||
formatoMoneda(esquema.baseDiferible),
|
||||
],
|
||||
['TOTAL DEL ESQUEMA', '', '', formatoMoneda(esquema.totalPagado)],
|
||||
...(esquema.engancheAdicional > 0
|
||||
? [['ENGANCHE ADICIONAL (ANTICIPO EXTRA)', '', '', formatoMoneda(esquema.engancheAdicional)]]
|
||||
: []),
|
||||
['SALDO A FINANCIAR', '', '', formatoMoneda(esquema.saldoAFinanciar)],
|
||||
],
|
||||
})
|
||||
y = finalY(doc) + 6
|
||||
|
||||
@@ -82,6 +82,9 @@ export default function CotizadorPage() {
|
||||
setNumMensualidades,
|
||||
escrituracionDiferida,
|
||||
setEscrituracionDiferida,
|
||||
engancheAdicional,
|
||||
setEngancheAdicional,
|
||||
engancheAdicionalMaximo,
|
||||
precioLista,
|
||||
resultado,
|
||||
numMeses,
|
||||
@@ -186,6 +189,7 @@ export default function CotizadorPage() {
|
||||
apartado: apartadoEfectivo,
|
||||
numMensualidades,
|
||||
escrituracionDiferida,
|
||||
engancheAdicional: resultado?.engancheAdicional ?? 0,
|
||||
tipo,
|
||||
ajustes: ajustes[tipo],
|
||||
}),
|
||||
@@ -201,7 +205,7 @@ export default function CotizadorPage() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}, [precioLista, apartadoEfectivo, numMensualidades, escrituracionDiferida, ajustesCredito])
|
||||
}, [precioLista, apartadoEfectivo, numMensualidades, escrituracionDiferida, ajustesCredito, resultado])
|
||||
|
||||
// Prefill desde la URL (botón "Cotizar" de Mi día): cliente, contacto,
|
||||
// desarrollo, unidad y el id de la oportunidad a vincular al guardar
|
||||
@@ -303,6 +307,7 @@ export default function CotizadorPage() {
|
||||
engancheDiferible: resultado.enganche,
|
||||
escrituracion: resultado.escrituracion,
|
||||
escrituracionDiferida,
|
||||
engancheAdicional: resultado.engancheAdicional,
|
||||
enganche: resultado.enganche,
|
||||
numMensualidades,
|
||||
mensualidad: resultado.mensualidad,
|
||||
@@ -331,6 +336,7 @@ export default function CotizadorPage() {
|
||||
apartado: apartadoEfectivo,
|
||||
numMensualidades,
|
||||
escrituracionDiferida,
|
||||
engancheAdicional: resultado.engancheAdicional,
|
||||
opcionesCredito: JSON.stringify({ elegida: opcionElegida, ajustes: ajustesCredito }),
|
||||
...(oportunidadId !== null ? { oportunidadId } : {}),
|
||||
})
|
||||
@@ -496,6 +502,7 @@ export default function CotizadorPage() {
|
||||
? {
|
||||
numMensualidades: c.numMensualidades > 0 ? c.numMensualidades : undefined,
|
||||
escrituracionDiferida: c.escrituracionDiferida,
|
||||
engancheAdicional: c.engancheAdicional ?? 0,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
@@ -785,6 +792,9 @@ export default function CotizadorPage() {
|
||||
esquema={resultado}
|
||||
fechaEntrega={unidad?.fechaEntrega ?? null}
|
||||
maxMensualidades={unidad ? mensualidadesParaUnidad(unidad) : MENSUALIDADES_MAX}
|
||||
engancheAdicional={engancheAdicional}
|
||||
onEngancheAdicional={setEngancheAdicional}
|
||||
engancheAdicionalMaximo={engancheAdicionalMaximo}
|
||||
numMensualidades={numMensualidades}
|
||||
onNumMensualidades={setNumMensualidades}
|
||||
escrituracionDiferida={escrituracionDiferida}
|
||||
|
||||
@@ -103,6 +103,8 @@ export interface CotizacionGuardada {
|
||||
escrituracion?: number | null
|
||||
/** true = escrituración diferida con el enganche (esquema nuevo) */
|
||||
escrituracionDiferida?: boolean
|
||||
/** Enganche adicional (anticipo extra; esquema nuevo, 0 por default) */
|
||||
engancheAdicional?: number | null
|
||||
enganche: number
|
||||
numMensualidades: number
|
||||
mensualidad: number | null
|
||||
@@ -124,6 +126,8 @@ export interface GuardarCotizacionPayload {
|
||||
numMensualidades?: number
|
||||
/** true = la escrituración (5.5%) se difiere con el enganche */
|
||||
escrituracionDiferida?: boolean
|
||||
/** Enganche adicional (anticipo extra >= 0); el total no puede pasar del 90% del precio */
|
||||
engancheAdicional?: number
|
||||
/** Item del tablero Oportunidades a vincular (botón "Cotizar" de Mi día) */
|
||||
oportunidadId?: string
|
||||
/** JSON serializado {elegida, ajustes} de las 4 opciones de crédito; el API lo guarda crudo */
|
||||
|
||||
Reference in New Issue
Block a user