Qué guarda cada pantalla, tabla por tabla
de: moisés (diseño)para: jaime (Supabase + 5 funciones)tarea: J123 jul 2026
Es el plano para tu esquema en Supabase: qué datos guarda cada pantalla, con su tipo, si es obligatorio y cómo se valida. No es SQL — el CREATE TABLE lo decides tú (tipos Postgres, PK/FK, índices, RLS). Sale directo de las pantallas del producto v2, del PRD §6 y de la tabla de inputs de a11y.md §2.4. Cada campo dice de dónde viene para que lo puedas contrastar con la maqueta.
ANTES DE NADA
Los principios que rigen todo el esquema
- Mínimo + progresivo. Al alta solo pedimos nombre + teléfono. Todo lo demás se rellena después, así que casi todo es nullable.
- El teléfono del usuario es obligatorio y único: así Alma y Mia lo reconocen en WhatsApp. Guardar en E.164 (
+34612480315), aunque en pantalla se vea con espacios. - El email viene de Google Auth, no lo pedimos nosotros (login OAuth de Francisco).
- Datos del usuario ≠ datos del destino. Los del usuario se reutilizan (SC 3.3.7); los del tercero (restaurante, médico…) viven en cada llamada, no en el perfil.
- Las grabaciones se almacenan siempre, con aviso (legal, de Juan José).
- Fase 1 por navegador; teléfono real (Zadarma/LiveKit) en fase 3. El esquema no cambia entre fases.
TABLA 1 · FUNCIÓN guardar_perfil
Quién eres usuarios
Origen: pantalla 00 (Alta) y pantalla 10 (Perfil).
| Campo | Tipo | Obl. | Validación | Origen / notas |
|---|---|---|---|---|
| id | uuid | 🔒 | — | El de Supabase Auth (Google). Es la clave. |
| nombre | texto | ✔ | 1–80 caracteres | Alta. Prellenado desde Google, editable. |
| telefono | texto (E.164) | ✔ | formato E.164, único | Alta. Obligatorio; validación suave (acepta espacios y +34, se normaliza). |
| texto | 🔒 | email válido | Viene de Google Auth. No editable. | |
| plan | enum plan | 🔒 | ver §7 | Al registrarse: prueba. Lo gobierna Stripe. |
| creado_en | timestamp | 🔒 | — | Alta. |
TABLA 2 · FUNCIÓN guardar_perfil
Cómo quieres que trabajemos preferencias
Origen: pantalla 10 (Perfil) → Preferencias. Progresivo: todo nullable, con valores por defecto. Puede ser tabla 1-a-1 o columnas en usuarios, a tu criterio.
| Campo | Tipo | Obl. | Validación | Origen / notas |
|---|---|---|---|---|
| usuario_id | uuid (FK) | ✔ | → usuarios.id | — |
| resumen_whatsapp | booleano | ○ | — | Toggle. Por defecto: activado. |
| avisar_antes | booleano | ○ | — | Toggle. Por defecto: desactivado. |
| horario_desde | hora | ○ | — | Por defecto 09:00. |
| horario_hasta | hora | ○ | hasta > desde | Por defecto 21:00. |
TABLA 3 · FUNCIÓN agenda
La agenda contactos
En esta fase el perfil no muestra la agenda todavía (perfil mínimo), pero conviene tener tabla y función listas: las plantillas ya guardan «a quién se llama con qué notas».
| Campo | Tipo | Obl. | Validación | Origen / notas |
|---|---|---|---|---|
| id | uuid | 🔒 | — | — |
| usuario_id | uuid (FK) | ✔ | → usuarios.id | Cada usuario ve solo los suyos (RLS). |
| nombre | texto | ✔ | 1–80 | «Mamá», «Restaurante La Traviesa». |
| telefono | texto (E.164) | ○ | formato E.164 | Es un tercero: autocomplete=off. |
| notas | texto | ○ | — | «mesa tranquila», «mejor por la tarde». |
| cumpleanos | fecha | ○ | — | Solo para la felicitación. |
TABLA 4 · FUNCIONES crear_llamada · estado_llamada · obtener_resultado
El corazón llamadas
Origen: pantallas 02–07 (crear → estado → resultado) y todo el panel admin.
| Campo | Tipo | Obl. | Validación | Origen / notas |
|---|---|---|---|---|
| id | uuid | 🔒 | — | El ID visible LLX-2026-0142 puede ser aparte o derivado. |
| usuario_id | uuid (FK) | ✔ | → usuarios.id | — |
| plantilla | enum plantilla | ✔ | ver §7 | Qué llamada es. La elige la home. |
| destino_nombre | texto | ✔ | 1–120 | Paso 1. «Restaurante La Traviesa». |
| destino_telefono | texto (E.164) | ✔ | formato E.164 | Paso 1. Tercero: autocomplete=off. |
| preguntar_por | texto | ○ | — | Paso 1, opcional. «Carmen». |
| variables | JSON | ✔ | schema por plantilla (§5) | Los datos de la plantilla; se inyectan al agente. |
| cuando | enum cuando | ✔ | ahora | programada | Paso 1. |
| programada_para | timestamp | ○ | futuro; requerido si programada | Paso 1 («a una hora concreta»). |
| estado | enum estado_llamada | 🔒 | ver §7 | Lo escribe el worker. Empieza en pendiente. |
| creada_en | timestamp | 🔒 | — | — |
| resumen | texto | 🔒 | — | Resultado. Lo genera la IA al colgar. |
| resultado_datos | JSON | 🔒 | — | Datos clave (día, hora, código de reserva…). |
| transcripcion | JSON | 🔒 | — | Turnos (hablante, hora, texto). |
| grabacion_id | uuid (FK) | ○ | → grabaciones.id | Ver §6. |
Los límites del paso 2 llamada_limites
Origen: pantalla 03 (paso 2 «Los límites»). Es lo que convierte el paso 2 en instrucciones para el agente. Recomiendo tabla propia porque el panel los pinta uno a uno.
| Campo | Tipo | Obl. | Validación | Origen / notas |
|---|---|---|---|---|
| id | uuid | 🔒 | — | — |
| llamada_id | uuid (FK) | ✔ | → llamadas.id | — |
| tipo | enum | ✔ | acepta | pregunta | bloqueado | Verde / ámbar / candado. |
| texto | texto | ✔ | — | «Otra hora entre 21:00 y 22:00». |
| es_sistema | booleano | 🔒 | — | Pagos/señal/datos bancarios: true y no se pueden quitar. |
DENTRO DE llamadas.variables
Las variables por plantilla (JSON)
El campo variables depende de la plantilla. Schema de cada una según el PRD §6 — son las variables que se inyectan al agente de voz. Recomiendo validarlo en la API con un schema por plantilla, no en la base. (El margen y las condiciones aceptables van en llamada_limites, no aquí.)
P1 · Reservar mesa / pedir cita reservar_mesa · pedir_cita
tipo_reserva · dia_preferido (fecha) · hora_preferida (hora) · personas (entero, si aplica) · nombre_reserva (texto) · mensaje_adicional (texto, opcional).
P2 · Cambiar una cita cambiar_cita
cita_actual (texto) · accion (enum: confirmar / cambiar / cancelar / avisar_retraso / reprogramar) · nueva_fecha (fecha, opcional) · mensaje (texto).
P3 · Preguntar precio / info preguntar_precio
pregunta_principal (texto) · preguntas_secundarias (lista) · datos_a_recoger (texto) · no_confirmar (texto) · canal_resumen (enum: whatsapp / app).
P4 · Felicitar cumpleaños felicitar_cumple
relacion (texto) · edad (entero, opcional) · datos_personalizar (texto) · tono (enum tono, §7) · bromas_permitidas · cosas_prohibidas · mensaje_final · decir_quien_encarga (booleano).
P6 · Mensaje divertido mensaje_divertido
personaje (texto) · mensaje (texto) · nivel_picante (entero 1–3) · cosas_prohibidas (texto). Nota: las divertidas pasan por el Revisor del panel antes de salir.
TABLA 5 · FUNCIÓN obtener_resultado
El audio, siempre con aviso grabaciones
Origen: requisito legal + pantalla de Resultado / detalle admin.
| Campo | Tipo | Obl. | Validación | Origen / notas |
|---|---|---|---|---|
| id | uuid | 🔒 | — | — |
| llamada_id | uuid (FK) | ✔ | → llamadas.id | — |
| url | texto | 🔒 | — | Supabase Storage con acceso restringido. |
| duracion_s | entero | 🔒 | ≥ 0 | Segundos. |
| creada_en | timestamp | 🔒 | — | — |
LISTAS CERRADAS
Enums
Los valores fijos. Fija los que uses en el panel para que color/estado coincidan con las maquetas.
estado_llamada · los 9 estados del feed, en orden natural
plan
Mini 4,99/3 llam · Plus 9,99/12 · Max 19,99/25 · 1ª gratis. Los pagos los gobierna Stripe, no esta tabla.
plantilla
Las 6 de la home. reservar_mesa y pedir_cita comparten el schema P1.
cuando
tono · para felicitaciones (PRD P4)
TU TAREA J2
Cómo las 5 funciones tocan estas tablas
Del anexo técnico de producto-v2.
| Función | Lee / escribe |
|---|---|
| guardar_perfil | usuarios + preferencias |
| crear_llamada | inserta en llamadas (+ llamada_limites, + variables); la mete en la cola |
| estado_llamada | actualiza llamadas.estado; lo lee el móvil en vivo (WebSocket) |
| agenda | contactos (leer y escribir) |
| obtener_resultado | escribe resumen, resultado_datos, transcripcion; enlaza grabaciones |
PARA QUE NO MONTES DE MÁS
Lo que NO entra en esta fase
- Petición libre («llama a mi hermana y dile…») con agente al momento → fase siguiente.
- Citas a Google Calendar desde la app → la columna
citaspuede esperar; solo Plus/Max lo promete. - Llamadas en lote → fase 2.
- Datos de facturación → Stripe, no aquí.