Files
reformix-hackaton/docs/superpowers/specs/2026-05-30-motor-presupuesto-design.md
Carlos Narro bd07586b03 Add motor de presupuesto design spec
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>
2026-05-30 08:27:06 +02:00

7.3 KiB
Raw Permalink Blame History

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)

  1. 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.
  2. 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.
  3. 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.
  4. 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)

  1. Cantidades (con degradación):
    • m² suelo aportado; 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).
  2. Precio unitario por material: ítem exacto elegido del catálogo si existe; si no, ítem esDefault de la calidad global.
  3. 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.
  4. Factor zona: multiplicador por provincia (configurable) sobre el subtotal.
  5. Licencia (RF-C-22): si inputs.estructural = true, partida "Licencia + Proyecto técnico" con rango 3001.500 €.
  6. 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.
  • desgloseSnapshot guarda el resultado en cada pipeline_stage → permite analizar cómo evoluciona la estimación lead a lead.
  • descriptorRender de cada material se inyecta en el prompt del render.

Panel del reformista (/panel/precios)

  • Catálogo editable: tabla de catalog_items por categoría, precio por calidad inline; crear/editar/borrar; marcar esDefault por calidad.
  • Importar CSV: subida → parse con zod → preview filas válidas/erróneas → confirmar. Cabeceras: categoria,nombre,calidad,precio,unidad,descriptor_render,sku. Upsert por sku. 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_a4 flag + 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 computeBudget con inputs conocidos: desglose ±1 € vs cálculo manual (RF-C-21), partida de licencia presente con estructural=true (RF-C-22), degradación sin m², estrechamiento del rango al añadir datos. Cobertura src/budget/* ≥70% (RNF-MAINT-01).
  • Test de parser CSV: filas válidas/erróneas, upsert por sku, cero escrituras si hay error.