feat(cotizador): apartado editable con mínimo $20,000 + cuota de mantenimiento por desarrollo

- Input de apartado con validación (mínimo 20k), clamp en el motor y bloqueo de PDF si inválido
- El cliente puede apartar con más; solo se recorre del enganche, el total no cambia
- cuotaMantenimiento tipada (number|null) en config por desarrollo; null → PENDIENTE
- Knowledge IA actualizada: apartado mínimo 20k (FAQ + proceso de venta) reindexada
- Tests 61/61 verdes (8 nuevos)
This commit is contained in:
2026-08-24 23:23:57 +00:00
parent 122c4d6102
commit 29c3947313
10 changed files with 122 additions and 19 deletions

View File

@@ -43,6 +43,8 @@ export default function CotizadorPage() {
setAcabados,
apartado,
setApartado,
apartadoEfectivo,
errorApartado,
setDescuento,
precioLista,
numMeses,
@@ -63,7 +65,7 @@ export default function CotizadorPage() {
})
const nombreCliente = watch('nombre') ?? ''
const puedeGenerarPdf = Boolean(unidad) && nombreCliente.trim().length >= 3
const puedeGenerarPdf = Boolean(unidad) && nombreCliente.trim().length >= 3 && !errorApartado
const handleGenerarPdf = () => {
if (!unidad) return
@@ -72,7 +74,7 @@ export default function CotizadorPage() {
email: watch('email') || undefined,
telefono: watch('telefono') || undefined,
}
generarPdf({ unidad, acabados, apartado, precioLista, propuestas, cliente, desarrollo: params })
generarPdf({ unidad, acabados, apartado: apartadoEfectivo, precioLista, propuestas, cliente, desarrollo: params })
}
return (
@@ -189,8 +191,13 @@ export default function CotizadorPage() {
const v = Number(e.target.value)
setApartado(Number.isFinite(v) && v >= 0 ? v : 0)
}}
className={campoInput}
className={errorApartado ? campoInputError : campoInput}
/>
{errorApartado ? (
<p className="mt-1 text-xs text-red-700">{errorApartado}</p>
) : (
<p className="mt-1 text-xs text-neutral-500">{formatoMoneda(apartado)}</p>
)}
</div>
<div>
@@ -272,7 +279,7 @@ export default function CotizadorPage() {
</div>
<ComparadorPropuestas
precioLista={precioLista}
apartado={apartado}
apartado={apartadoEfectivo}
propuestas={propuestas}
titulos={propuestas.map((p) => p.titulo)}
/>