Schema
CREATE TABLE correo_peticiones (
id INTEGER PRIMARY KEY AUTOINCREMENT,
owner TEXT NOT NULL,
texto TEXT NOT NULL,
estado TEXT NOT NULL DEFAULT 'pendiente',
respuesta TEXT,
compose_url TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
atendida_at TEXT
);
CREATE INDEX idx_correo_peticiones_owner
ON correo_peticiones(owner, estado, created_at DESC);
Descripción
Almacén persistente de la cola de peticiones (encargos) que los miembros encomiendan a la secretaria de correo (F2).
Columnas
| Nombre | Tipo | Nulable | Default | Descripción |
|---|---|---|---|---|
id | INTEGER | NO | AUTOINCREMENT | PK. Identificador de la petición. |
owner | TEXT | NO | — | Miembro que encola ('Edu'|'Dani'|'Txell'). |
texto | TEXT | NO | — | La petición en palabras del miembro (ej. “Respóndele a Hetzner que confirmen…”). |
estado | TEXT | NO | 'pendiente' | Ciclo de vida: 'pendiente'|'atendida'|'descartada'. |
respuesta | TEXT | YES | NULL | Qué hizo la secretaria (borrador de correo incluido), si aplica. |
compose_url | TEXT | YES | NULL | Deep-link a Zoho Compose con el borrador pre-rellenado, si se generó. |
created_at | TEXT | NO | datetime('now') | Timestamp de creación (ISO 8601). |
atendida_at | TEXT | YES | NULL | Timestamp cuando se marcó como 'atendida' (si aplica). |
Ciclo de Vida de una Petición
┌─────────────┐
│ pendiente │ ← creada por navegador (POST /api/correo/peticiones)
└──────┬──────┘
│
├──→ ┌──────────────┐
│ │ atendida │ ← pasada desatendida llamó PUT con estado + respuesta + compose_url
│ └──────────────┘
│
└──→ ┌───────────────┐
│ descartada │ ← miembro o pasada llamó PUT/DELETE para descartar
└───────────────┘
Transiciones
pendiente→atendida: PUT con{"estado":"atendida", "respuesta":"...", "compose_url":"..."}→ estableceatendida_at = now().pendiente→descartada: PUT con{"estado":"descartada"}o DELETE (elimina fila).atendida→descartada: PUT con{"estado":"descartada"}(miembro la archiva sin enviar).
Índice
CREATE INDEX idx_correo_peticiones_owner
ON correo_peticiones(owner, estado, created_at DESC);
Optimiza las queries típicas:
SELECT ... WHERE owner = ? AND estado = ?(listar peticiones pendientes del miembro).SELECT ... WHERE owner = ? ORDER BY created_at DESC(historial completo).
Datos Reales para Apuesta #9
La tabla alimenta métricas de adopción (WAGERS.md apuesta #9):
-- ¿Cuántas peticiones atendidas por miembro?
SELECT owner, COUNT(*)
FROM correo_peticiones
WHERE estado = 'atendida'
GROUP BY owner;
-- ¿Cuántos días ha habido actividad?
SELECT COUNT(DISTINCT DATE(created_at))
FROM correo_peticiones
WHERE estado = 'atendida';
Criterio: ≥2 peticiones atendida para cada miembro (con datos reales en D1, no de memoria).
Restricciones de Scoping
El endpoint /api/correo/peticiones (y sus sub-rutas) siempre verifica owner:
if (existing.owner !== member) {
return Response.json({ error: 'La petición es de otro miembro' }, { status: 403 });
}
No hay lectura cruzada ni “admin override” — cada miembro solo ve/modifica sus propias peticiones.
Conversión HTML→Texto en Borrador
Cuando la pasada desatendida genera el respuesta (borrador), es texto plano generado por claude-method. El compose_url es un deep-link a Zoho Compose que pre-rellena el cuerpo:
https://mail.zoho.com/...
#compose
&to=recipient@example.com
&subject=...
&body=...
El cuerpo va URL-encoded. Zoho abre la ventana de redacción con el draft listo para revisar y enviar.
Migración
Creada en 0051_correo_chat_peticiones.sql (12-08-2026, mismo commit que F1/F2).
-- Menú Correo F1+F2 (12-08-2026).
CREATE TABLE IF NOT EXISTS correo_peticiones (
id INTEGER PRIMARY KEY AUTOINCREMENT,
owner TEXT NOT NULL,
texto TEXT NOT NULL,
estado TEXT NOT NULL DEFAULT 'pendiente',
respuesta TEXT,
compose_url TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
atendida_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_correo_peticiones_owner
ON correo_peticiones(owner, estado, created_at DESC);
Véase también
- [[entity—correo—endpoint—peticiones]]
- [[feature—workspace—correo-menu-f0-f1-f2]]
- [[concept—saas—multi-tenancy]]