Files
reformix-hackaton/docs/superpowers/specs/2026-05-31-guion-agente-voz-preferencias-design.md
Carlos Narro a15d8c77b4 Diseño: guion del agente de voz + capa de preferencias
Spec del guion híbrido (slots fijos + bloque abierto) y de la capa que
clasifica/abstrae el texto de gustos en inputs del presupuesto, con cuatro
palancas (material, extras, render, ajustes etiquetados) y clasificador
keyless. Enfoque A: pre+post alrededor de computeBudget, motor intacto.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 15:55:33 +02:00

8.5 KiB

Guion del agente de voz + capa de preferencias — Diseño

Fecha: 2026-05-31 Estado: aprobado (brainstorming), pendiente de plan de implementación Superficie: funnel B2C (mvp/b2c), bloque de llamada del agente (RF-C) + panel (RF-D)

Objetivo

Plantear el guion del agente de voz para que recoja datos adicionales sobre los gustos del cliente, y una capa que clasifique y abstraiga ese texto libre en inputs que el motor de presupuesto pueda usar — mejorando la precisión y el acercamiento estético del presupuesto sin romper la trazabilidad partida-a-partida del motor.

Decisiones de alcance (validadas)

  • Cuatro palancas por las que los gustos afectan al presupuesto:
    1. Selección de material + calidad por categoría (vía catálogo).
    2. Detección de elementos/extras → partidas nuevas.
    3. Descriptores de render.
    4. Modificadores de € etiquetados y trazables (nunca % opaco).
  • Estilo de guion: híbrido — slots fijos + bloque abierto de gustos.
  • Capa de clasificación: clasificador keyless funcional + esquema durable, con costura para enchufar GPT-4o en F2.
  • Arquitectura: Enfoque A — pre + post alrededor de computeBudget; el motor queda intacto.

Arquitectura

RawCallData
  → abstractPreferences(raw, catalog) → AbstractedPreferences
  → mergeIntoBudgetInputs(prefs, lead) → BudgetInputs        [pre]
  → computeBudget(inputs, config, catalog) → BudgetResult    [motor intacto]
  → applyPreferences(result, prefs) → BudgetResult final     [post: elementos + ajustes]

El motor de presupuesto (src/budget) no se modifica. La extracción y la aplicación de extras viven en un módulo nuevo src/lib/voice, con interfaces bien definidas.

Componentes

1. Contrato de datos — src/lib/voice/preferences.ts

import type { Calidad, CategoriaMaterial, TipoReforma } from '@/budget/types';

export interface RawCallData {
  tipoReforma: TipoReforma;
  m2Suelo: number | null;
  calidad: Calidad | null;
  estructural: boolean | null;
  urgencia: 'alta' | 'media' | 'baja' | null;
  presupuestoTarget: number | null; // céntimos
  tasteText: string;                 // bloque abierto del guion
}

export interface PreferenceExtra {   // palanca 2 → se convierte en partida
  key: string;                       // 'isla_cocina'
  label: string;                     // 'Isla de cocina'
  importe: number;                   // céntimos (base, antes de factor zona)
}

export interface PreferenceAjuste {  // palanca 4 → ajuste etiquetado y trazable
  label: string;
  tipo: 'factor' | 'fijo';
  valor: number;                     // factor (1.15) o céntimos
  motivo: string;                    // visible en el panel
}

export interface AbstractedPreferences {
  calidadGlobal: Calidad;
  materialSelections: Partial<Record<CategoriaMaterial, string>>; // catalogItemId
  estructural: boolean;
  urgencia: 'alta' | 'media' | 'baja' | null;
  presupuestoTarget: number | null;
  elementos: PreferenceExtra[];
  estiloRender: string[];
  ajustes: PreferenceAjuste[];
  confianza: 'baja' | 'media' | 'alta';
  resumen: string;                   // 1-2 frases para el panel
  camposFaltantes: string[];         // RF-C-15 completeness
}

Notas:

  • No hay "calidad por categoría" separada: es expresable vía materialSelections (cada CatalogItem lleva su calidad).
  • Los modificadores (palanca 4) son siempre PreferenceAjuste con label + motivo.
  • Los elementos se inyectan como partidas tras computeBudget, reusando que PartidaResult.key admite string libre.

2. Guion — src/lib/voice/script.ts

buildScript(reformistaConfig, lead) → estructura de bloques/turnos (RF-C-14, generado desde la config del reformista).

  • Bloque 0 — Preámbulo legal (literal, obligatorio): identificación IA + aviso de grabación (RF-C-12, RNF-LEG-05). Si responde "no" → cierre educado + consent_revoked (RF-C-13).
  • Bloque 1 — Confirmación: tipo y m² del form.
  • Bloque 2 — Slots fijos (uno a uno, opciones acotadas): calidad (básica/media/premium), estructural (sí/no), urgencia (alta/media/baja), presupuesto target (opcional).
  • Bloque 3 — Bloque abierto de gustos: pregunta ancla "Cuéntame cómo te lo imaginas: estilo, colores, materiales… y si hay algún capricho que no quieras que falte." + hasta 2 repreguntas guiadas (materiales, elemento estrella). Todo alimenta tasteText.
  • Bloque 4 — Cierre + completeness (RF-C-15/16): repregunta dirigida si faltan campos críticos; tope 8 min → info parcial; despedida.

Hoy el orquestador usa la estructura para construir una transcripción realista; en F2 se traduce al prompt de Retell sin redeploy.

3. Clasificador — src/lib/voice/extractor.ts + src/lib/voice/lexicon.ts

export interface PreferenceExtractor {
  extract(raw: RawCallData, catalog: CatalogItem[]): AbstractedPreferences;
}
// hoy: DeterministicExtractor   |   F2: GPT4oExtractor (misma interfaz)

DeterministicExtractor (keyless):

  1. Normaliza tasteText (minúsculas, sin tildes) y lo trocea en señales.
  2. Calidad: CALIDAD_LEXICON (premium/básica/media). Gana el slot explícito si existe.
  3. Materiales por categoría: matchea keywords contra nombre + descriptorRender de los CatalogItem del tenant; elige item de la calidad detectada; devuelve catalogItemId.
  4. Elementos/extras: ELEMENTOS_LEXICON por tipo de reforma → PreferenceExtra con importe base versionado en la tabla (no inventado en runtime).
  5. Estructural: ESTRUCTURAL_LEXICON (tirar muro, mover el baño, abrir la cocina) → refuerza el slot.
  6. Estilo render: ESTILO_LEXICONestiloRender[], que se concatena con materialesRender del motor.
  7. Ajustes etiquetados: reglas explícitas (encimera de mármol/cuarzoPreferenceAjuste con motivo).
  8. Confianza + camposFaltantes: alta si slots críticos + señales claras; baja si texto pobre. Lista campos RF-C-15 ausentes.

Léxicos en español, en lexicon.ts, ampliables y unit-testeables.

4. Aplicación — src/lib/voice/apply.ts

  • mergeIntoBudgetInputs(prefs, lead): BudgetInputs — vuelca calidad, materialSelections, estructural, provincia/m² del lead.
  • applyPreferences(result, prefs): BudgetResult — añade prefs.elementos como partidas, aplica prefs.ajustes (factor o fijo) recalculando subtotal/total/rango, y concatena prefs.estiloRender a materialesRender. Cada extra/ajuste queda como partida o aviso con label visible.

Integración en el funnel

  • FotosUploader.tsx: + select urgencia, + checkbox estructural, + input target, + textarea "Cuéntanos cómo lo imaginas" (stand-in keyless del bloque 3; cierra hueco RF-C-15).
  • actions.ts (guardarDetallesYFotos): persiste los campos nuevos en el lead.
  • orchestrator.ts: construye RawCallData desde el lead → abstractPreferencesmergeIntoBudgetInputscomputeBudgetapplyPreferences. La transcripción simulada pasa a generarse desde buildScript + respuestas reales.
  • schema.ts: campos nuevos en leads (urgencia, presupuestoTarget, tasteText) + snapshot de AbstractedPreferences.
  • Panel detalle (panel/[id]/page.tsx): sección "Preferencias detectadas" con resumen, elementos, ajustes (con motivo) y estiloRender. El reformista edita extras vía el ConceptosEditor ya existente.

Manejo de errores

  • tasteText vacío → AbstractedPreferences con confianza: 'baja', sin elementos/ajustes, materialSelections por defecto; el presupuesto sigue calculándose con los slots.
  • Sin match de material en catálogo → se omite esa categoría (cae al default del motor), no se inventa item.
  • Ajuste sin importe resoluble → se descarta y se anota en camposFaltantes.

Testing (TDD)

  • extractor.test.ts: textos de gusto conocidos → AbstractedPreferences esperadas (calidad, materiales, elementos, estilo, estructural, confianza).
  • apply.test.ts: BudgetResult + prefs → partidas extra correctas y total con ajustes (±1 €).
  • script.test.ts: buildScript incluye el preámbulo legal literal y todos los bloques.

Compliance

RF-C-12 (aviso de grabación), RNF-LEG-05 (identificación IA), RF-C-13 (revoca consentimiento), RF-C-16 (tope 8 min), RF-C-15 (7 campos obligatorios).

Fuera de alcance

  • Llamada real (Retell), telefonía (Zadarma), transcripción/Vision real (GPT-4o): F2 con claves. El GPT4oExtractor se deja como costura, no se implementa.
  • 3 versiones B/M/P del presupuesto y refinamiento por lenguaje natural post-envío: F1.5.