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>
This commit is contained in:
@@ -0,0 +1,149 @@
|
|||||||
|
# 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`
|
||||||
|
|
||||||
|
```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`
|
||||||
|
|
||||||
|
```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_LEXICON` → `estiloRender[]`, que se concatena con `materialesRender` del motor.
|
||||||
|
7. **Ajustes etiquetados:** reglas explícitas (*encimera de mármol/cuarzo* → `PreferenceAjuste` 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 → `abstractPreferences` → `mergeIntoBudgetInputs` → `computeBudget` → `applyPreferences`. 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.
|
||||||
Reference in New Issue
Block a user