Diseño validado del motor de presupuesto: modelo híbrido partidas←precios unitarios, medidas mínimas (m² suelo + supuestos), calidad B/M/P + catálogo importable por CSV, y progressive disclosure de personalización en el funnel. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7.3 KiB
Motor de presupuesto Reformix — Diseño
Fecha: 2026-05-30 · Estado: aprobado (brainstorming) · Owner dominio: Goyo · Coord: Carlos Alcance: F2 (en sprint actual). El configurador multi-tenant real sigue siendo F1.5.
Objetivo
Producir un presupuesto orientativo desglosado por partidas a partir de datos que escalan "de menos a más": con el mínimo (tipo de reforma + calidad) ya sale un número, y cada etapa del funnel (medidas, material exacto, llamada) lo afina. El reformista define los precios en el panel; el catálogo se puede sembrar y actualizar por CSV.
Requisitos cubiertos: RF-C-21 (desglose por partidas + factor zona), RF-C-22 (licencia
si hay cambios estructurales), RF-D-07 (tabla de precios editable), RF-B-07 (DIN-A4 como
input de medidas, fallback stubbeable), RF-B-09 (disclaimer orientativo),
RNF-MAINT-01 (≥70% cobertura en src/budget/*).
Decisiones de diseño (validadas con el usuario)
- Modelo híbrido partidas ← precios unitarios. El reformista configura precios unitarios (€/m² suelo por calidad, €/m² pared, €/m² pintura, €/ml mobiliario, mano de obra). El motor calcula cantidades desde las medidas y agrupa el resultado en las partidas de RF-C-21.
- Medidas mínimas = m² de suelo + supuestos. El resto se deriva: perímetro ≈ 4·√(m²), m² pared = perímetro × altura (2,5 m por defecto). Si no hay m², mediana por tipo. El refinamiento con dimensiones reales (largo×ancho×alto) o DIN-A4 es posterior/opcional.
- Calidad = columna de precio por material (B/M/P) más un catálogo de materiales con precio e identidad propia, importable por CSV. El cliente elige una calidad global por defecto; puede personalizar material exacto si quiere.
- Progressive disclosure. Se fomenta lo básico (solo calidad). La personalización (material exacto del catálogo) aparece sutil y opcional en el funnel, y el agente la afina en la llamada. El material elegido alimenta el prompt del render para que la imagen refleje exactamente lo presupuestado.
Arquitectura
PANEL (CRUD) pricing_config + catalog_items (por tenant)
reformista · precios unitarios B/M/P por material
+ CSV import · mano de obra, factor zona, partidas
│ (lee de DB)
▼
FUNNEL (cliente)
inputs por etapa ─► computeBudget(config, catalog, inputs) ─► BudgetResult
· m² suelo, calidad [ función PURA en src/budget/ ] · partidas[]
· (opc.) material exacto · subtotal, total
· (llamada) estructural · rango + confianza
· materiales→render
src/budget/(núcleo puro): tipos,computeBudget(), derivación de cantidades, agrupación en partidas, partida condicional de licencia, cálculo de rango+confianza. Sin imports de DB ni de red. Es el módulo con cobertura ≥70% (RNF-MAINT-01).- DB (Drizzle): extiende el schema existente (
src/db/schema.ts). - Panel: CRUD sobre config/catálogo + importador CSV. Tenant único "Reformas Ejemplo".
- Funnel: recoge inputs mínimos, llama al engine, persiste snapshot por etapa.
Flujo de cálculo computeBudget(config, catalog, inputs)
- Cantidades (con degradación):
m² sueloaportado; si no, mediana por tipo (cocina 10, baño 5, salón 20, integral 70).perímetro ≈ 4·√(m² suelo);m² pared = perímetro × alturaTecho(default 2,5 m).ml mobiliario ≈ perímetro × factor_tipo(solo cocina/baño).
- Precio unitario por material: ítem exacto elegido del catálogo si existe; si no,
ítem
esDefaultde la calidad global. - Partidas (RF-C-21): cada partida = Σ(cantidad × precio unitario material) + mano de obra. Categorías: demolición, alicatado, fontanería, electricidad, carpintería, mano de obra, extras.
- Factor zona: multiplicador por provincia (configurable) sobre el subtotal.
- Licencia (RF-C-22): si
inputs.estructural = true, partida "Licencia + Proyecto técnico" con rango 300–1.500 €. - Rango + confianza: total como
{ min, max, confianza }. Menos datos → banda más ancha (≈ ±25% solo con calidad; ≈ ±10% tras llamada con material exacto + estructural confirmado).
Modelo de datos (extensión Drizzle)
pricing_config (1 por tenant)
tenantId, alturaTechoDefault, factorZona (jsonb provincia→mult),
manoObra (jsonb partida→€), updatedAt
catalog_items (N por tenant)
id, tenantId, categoria (suelo|pared|pintura|mobiliario|...),
nombre, calidad (basica|media|premium), precioUnit (cents),
unidad (m2|ml|ud), descriptorRender (text), esDefault (bool), sku
leads (campos nuevos)
m2Suelo, alturaTecho, calidadGlobal, estructural,
materialSelections (jsonb categoria→catalogItemId),
desgloseSnapshot (jsonb: partidas+rango por pipeline_stage)
- Dinero en enteros (cents), consistente con el schema actual.
desgloseSnapshotguarda el resultado en cadapipeline_stage→ permite analizar cómo evoluciona la estimación lead a lead.descriptorRenderde cada material se inyecta en el prompt del render.
Panel del reformista (/panel/precios)
- Catálogo editable: tabla de
catalog_itemspor categoría, precio por calidad inline; crear/editar/borrar; marcaresDefaultpor calidad. - Importar CSV: subida → parse con zod → preview filas válidas/erróneas → confirmar.
Cabeceras:
categoria,nombre,calidad,precio,unidad,descriptor_render,sku. Upsert porsku. Si hay errores de validación, se escriben cero filas. - Config general: factor zona por provincia, mano de obra por partida, altura techo.
Funnel (cliente) — progressive disclosure
- Por defecto: tipo de reforma + calidad (B/M/P) y, opcional, m² de suelo → estimación
- render genérico de la calidad.
- Afordance sutil "Personalizar materiales" (colapsado): galería del catálogo para elegir ítems exactos. Quien no la toca, usa el default.
- La llamada enriquece inputs (material exacto, estructural, medidas finas) → recálculo
- render exacto.
Seed (demo 11-jun-2026)
Sembrar pricing_config + un catálogo demo (suelos, alicatados, pinturas, mobiliario
en B/M/P con descriptorRender) para que la demo funcione sin cargar CSV.
Fuera de alcance (ahora)
- Recálculo retroactivo de presupuestos ya guardados (los snapshots no se tocan).
- Calidad por elemento mezclada como input por defecto (refinamiento F1.5; el catálogo ya lo permite a nivel de selección manual).
- Extracción real de medidas por visión/DIN-A4: se deja stub (
has_din_a4flag + hook de medidas) y se usa la degradación por medianas mientras tanto. - Multi-tenant real (F1.5): todo opera sobre "Reformas Ejemplo".
- Dimensiones largo×ancho×alto como input mínimo (queda como refinamiento opcional).
Verificación
- Tests unitarios de
computeBudgetcon inputs conocidos: desglose ±1 € vs cálculo manual (RF-C-21), partida de licencia presente conestructural=true(RF-C-22), degradación sin m², estrechamiento del rango al añadir datos. Coberturasrc/budget/*≥70% (RNF-MAINT-01). - Test de parser CSV: filas válidas/erróneas, upsert por sku, cero escrituras si hay error.