|
|
|
@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
# Plan de implementación: Interfaz Web (SvelteKit) + Bot/Webhook (separado)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Este documento define el plan para añadir una interfaz web al sistema, manteniendo el bot/webhook existente como proceso independiente y compartiendo la misma base de datos SQLite (WAL). El objetivo es proporcionar al usuario un acceso seguro, rápido y cómodo a sus tareas, configuración y feeds de calendario, con especial atención a la seguridad y a la mínima fricción.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 1) Decisiones fijadas
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Arquitectura: dos procesos (apps/bot y apps/web). SvelteKit para la web (SSR, rutas de API, cookies).
|
|
|
|
|
|
|
|
- Acceso: enlace mágico por DM con token de 10 minutos, de un solo uso. Sin “recordarme”.
|
|
|
|
|
|
|
|
- Sesión: cookie de sesión (HttpOnly, SameSite=Lax, Secure en prod) + expiración por inactividad de servidor de 2 horas.
|
|
|
|
|
|
|
|
- Orden por defecto: tareas por creación descendente (más recientes primero).
|
|
|
|
|
|
|
|
- ICS:
|
|
|
|
|
|
|
|
- Horizonte temporal: 12 meses.
|
|
|
|
|
|
|
|
- Excluir tareas sin fecha.
|
|
|
|
|
|
|
|
- Feeds:
|
|
|
|
|
|
|
|
- B (por usuario+grupo, solo tareas sin responsable) como opción por defecto, con autogeneración.
|
|
|
|
|
|
|
|
- C (personal multigrupo, solo sin responsable) opcional para power users.
|
|
|
|
|
|
|
|
- Monorepo: estructura apps/bot y apps/web. Posible “shared” en el futuro para reutilizar utilidades.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 2) Alcance funcional (MVP)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Mis tareas: lista (orden creación desc), filtros (abiertas, vencen pronto), búsqueda por texto simple.
|
|
|
|
|
|
|
|
- Tareas de mis grupos: solo grupos permitidos y en los que el usuario está activo; sección destacada de “sin responsable”.
|
|
|
|
|
|
|
|
- Preferencias de recordatorios: ver y modificar frecuencia (daily/weekly/weekdays/off) y hora. Visualización de próximo recordatorio según TZ.
|
|
|
|
|
|
|
|
- Autenticación: comando /t web que devuelve URL con token. Canje en /login y cookie de sesión.
|
|
|
|
|
|
|
|
- Integraciones:
|
|
|
|
|
|
|
|
- ICS personal (solo “mis tareas” con due_date).
|
|
|
|
|
|
|
|
- ICS por usuario+grupo (solo sin responsable), autogenerados (sin clic de creación).
|
|
|
|
|
|
|
|
- ICS personal multigrupo opcional (solo sin responsable).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 3) Arquitectura técnica
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- apps/web (SvelteKit):
|
|
|
|
|
|
|
|
- SSR + endpoints (rutas +server.ts) para login, APIs, ICS.
|
|
|
|
|
|
|
|
- Gestión de cookies/sesión en hooks.server.ts.
|
|
|
|
|
|
|
|
- UI con Svelte (sin framework adicional salvo CSS utilitario si se desea).
|
|
|
|
|
|
|
|
- apps/bot:
|
|
|
|
|
|
|
|
- Se mantiene el webhook/servicios actuales.
|
|
|
|
|
|
|
|
- Emisión de tokens de login: puede implementarse desde el bot (insertando en DB) o delegarse al web (si el bot solo notifica al usuario la URL base + token emitido por la web). Para MVP: el bot crea el token directamente en DB y envía la URL.
|
|
|
|
|
|
|
|
- Concurrencia DB:
|
|
|
|
|
|
|
|
- SQLite en modo WAL con PRAGMA busy_timeout para ambos procesos. Reutilizar convenciones actuales de PRAGMA.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 4) Autenticación y sesiones
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Emisión de token (bot):
|
|
|
|
|
|
|
|
- En /t web por DM: crear token aleatorio, guardar hash (no el token en claro), TTL 10 min, uso único, rate-limit por usuario.
|
|
|
|
|
|
|
|
- Devolver URL del tipo: https://app.example.com/login?token=XYZ
|
|
|
|
|
|
|
|
- Canje (web):
|
|
|
|
|
|
|
|
- Validar hash y caducidad; si ok, invalidar token (marcar usado).
|
|
|
|
|
|
|
|
- Crear sesión en DB (web_sessions) y emitir cookie de sesión (solo cookie de sesión, sin persistencia en disco).
|
|
|
|
|
|
|
|
- Redirigir a /app (sin token en la URL).
|
|
|
|
|
|
|
|
- Expiración:
|
|
|
|
|
|
|
|
- Idle timeout: 2 horas de inactividad. Si excede, pedir un nuevo token /t web.
|
|
|
|
|
|
|
|
- Seguridad:
|
|
|
|
|
|
|
|
- Cookies: HttpOnly, SameSite=Lax, Secure (en prod), path acotado.
|
|
|
|
|
|
|
|
- Rate limit en /login para evitar bruteforce de tokens.
|
|
|
|
|
|
|
|
- Redirección inmediata tras canje para evitar fugas por Referer.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 5) Calendario ICS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Contenido:
|
|
|
|
|
|
|
|
- Solo tareas con due_date, dentro de los próximos 12 meses.
|
|
|
|
|
|
|
|
- Título con id y descripción, notas con URL a la tarea (opcional).
|
|
|
|
|
|
|
|
- Feeds:
|
|
|
|
|
|
|
|
- Personal (usuario): “mis tareas” con due_date.
|
|
|
|
|
|
|
|
- Por usuario+grupo (B, por defecto): “sin responsable” del grupo.
|
|
|
|
|
|
|
|
- Autogeneración: “perezosa” (on-demand) al solicitar listado de feeds en la UI o al primer acceso a la URL; o proactiva (sembrado) mediante tarea que cree un token por cada par usuario+grupo activo. Recomendado: perezosa, con garantía de existencia al cargar la página “Integraciones”.
|
|
|
|
|
|
|
|
- Un feed activo por usuario+grupo (regla de unicidad). Rotación manual por el usuario (revocar y crear nuevo).
|
|
|
|
|
|
|
|
- Personal multigrupo (C, opcional): “sin responsable” agregadas de todos los grupos en los que el usuario esté activo. Un único token; revocable.
|
|
|
|
|
|
|
|
- Seguridad y control:
|
|
|
|
|
|
|
|
- Tokens largos, no caducan por tiempo (estilo ICS), siempre revocables.
|
|
|
|
|
|
|
|
- Para B/C: revocación automática al detectar que el usuario dejó de ser miembro activo del grupo (B) o de todos los grupos (C), en conciliaciones de miembros.
|
|
|
|
|
|
|
|
- Rate limit de peticiones por token/IP (ligero) y soporte de ETag/Last-Modified para minimizar carga.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 6) Estructura del repo (monorepo)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- apps/bot: código actual del webhook, servicios, schedulers, migraciones.
|
|
|
|
|
|
|
|
- apps/web: SvelteKit (adapter-node).
|
|
|
|
|
|
|
|
- data/: carpeta con la base de datos SQLite compartida (ya existente).
|
|
|
|
|
|
|
|
- docs/: documentación (este archivo).
|
|
|
|
|
|
|
|
- Opcional futuro: packages/shared para extraer utilidades (normalizeWhatsAppId, métricas, tipos, etc.).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 7) Migraciones de base de datos (nuevas tablas)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- web_tokens
|
|
|
|
|
|
|
|
- id, user_id, token_hash, created_at, expires_at, used_at (nullable), metadata (JSON opcional).
|
|
|
|
|
|
|
|
- Índices: (user_id), (expires_at), (token_hash único).
|
|
|
|
|
|
|
|
- web_sessions
|
|
|
|
|
|
|
|
- id (session_id), user_id, session_hash, created_at, last_seen_at, expires_at (idle cutoff), user_agent, ip (opcional).
|
|
|
|
|
|
|
|
- Índices: (user_id), (expires_at), (session_hash único).
|
|
|
|
|
|
|
|
- calendar_tokens
|
|
|
|
|
|
|
|
- id, type (‘personal’, ‘group’, ‘aggregate’), user_id, group_id (nullable), token_hash, created_at, revoked_at (nullable), last_used_at.
|
|
|
|
|
|
|
|
- Unicidad: (type, user_id, group_id) activa (si revoked_at IS NULL).
|
|
|
|
|
|
|
|
- Índices: (user_id), (group_id), (token_hash único).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Notas:
|
|
|
|
|
|
|
|
- Guardar siempre hashes de tokens (no tokens en claro).
|
|
|
|
|
|
|
|
- Para autogeneración perezosa: crear on-demand si no existe registro activo para (user_id, group_id).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 8) Endpoints (apps/web)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Autenticación:
|
|
|
|
|
|
|
|
- GET /login?token=… (canjea token, crea sesión, redirige a /app)
|
|
|
|
|
|
|
|
- POST /api/logout (revoca sesión actual)
|
|
|
|
|
|
|
|
- APIs (todas requieren sesión válida):
|
|
|
|
|
|
|
|
- GET /api/me/tasks?status=open&search=...&page=...&limit=...
|
|
|
|
|
|
|
|
- GET /api/me/groups (grupos en los que está activo; solo allowed)
|
|
|
|
|
|
|
|
- GET /api/groups/:id/tasks?unassignedFirst=true (respeta gating y membresía)
|
|
|
|
|
|
|
|
- GET /api/me/preferences
|
|
|
|
|
|
|
|
- POST /api/me/preferences (actualiza frecuencia/hora)
|
|
|
|
|
|
|
|
- GET /api/integrations/feeds
|
|
|
|
|
|
|
|
- Genera automáticamente (si faltan) tokens B por cada grupo activo del usuario.
|
|
|
|
|
|
|
|
- Devuelve URLs para: ICS personal (mis tareas), ICS por grupo (B), y opcional ICS multigrupo (C).
|
|
|
|
|
|
|
|
- POST /api/integrations/feeds/rotate { type, groupId? } (revoca y recrea token)
|
|
|
|
|
|
|
|
- ICS (no requieren sesión; usan token en la URL):
|
|
|
|
|
|
|
|
- GET /ics/personal/:token.ics
|
|
|
|
|
|
|
|
- GET /ics/group/:token.ics
|
|
|
|
|
|
|
|
- GET /ics/aggregate/:token.ics
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 9) UI (apps/web)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Páginas:
|
|
|
|
|
|
|
|
- /app (dashboard): “Mis tareas” por defecto.
|
|
|
|
|
|
|
|
- /app/groups: lista de grupos del usuario; en cada uno, “sin responsable” prominente.
|
|
|
|
|
|
|
|
- /app/preferences: frecuencia y hora de recordatorios; vista “próximo recordatorio”.
|
|
|
|
|
|
|
|
- /app/integrations: enlaces ICS
|
|
|
|
|
|
|
|
- Autogenerados: mostrar directamente botones “Copiar” y breve guía (Google/Apple/Outlook).
|
|
|
|
|
|
|
|
- Rotar/revocar: botones por feed. Avisar que rotar invalida suscripción previa.
|
|
|
|
|
|
|
|
- Interacciones:
|
|
|
|
|
|
|
|
- Filtros rápidos, búsqueda, paginación liviana.
|
|
|
|
|
|
|
|
- Estado de sesión (2h de inactividad): al expirar, mostrar mensaje con instrucción de enviar /t web.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 10) Seguridad
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Tokens:
|
|
|
|
|
|
|
|
- Aleatorios criptográficos; hash en DB; TTL 10 min (web_tokens); uso único.
|
|
|
|
|
|
|
|
- calendar_tokens sin TTL (estilo ICS), siempre revocables.
|
|
|
|
|
|
|
|
- Cookies: HttpOnly, SameSite=Lax, Secure en prod, path acotado. No almacenar PII en cookies.
|
|
|
|
|
|
|
|
- CSRF: bajo riesgo con SameSite y API same-origin; añadir token anti-CSRF a mutaciones como defensa en profundidad.
|
|
|
|
|
|
|
|
- Cabeceras:
|
|
|
|
|
|
|
|
- X-Frame-Options: DENY, Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff, Content-Security-Policy básica.
|
|
|
|
|
|
|
|
- Robots: noindex, nofollow.
|
|
|
|
|
|
|
|
- Gating:
|
|
|
|
|
|
|
|
- Todas las consultas filtran por user_id y validan AllowedGroups + membresía activa (group_members).
|
|
|
|
|
|
|
|
- Logs: nunca registrar tokens en claro; solo hashes o IDs.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 11) Observabilidad y límites
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Métricas (via Metrics):
|
|
|
|
|
|
|
|
- web_tokens_issued_total, web_tokens_redeemed_total, web_login_success_total, web_login_failed_total
|
|
|
|
|
|
|
|
- web_sessions_active, web_api_requests_total{route=…}, ics_requests_total{type=…}
|
|
|
|
|
|
|
|
- ics_tokens_revoked_total, ics_tokens_created_total
|
|
|
|
|
|
|
|
- Rate limiting:
|
|
|
|
|
|
|
|
- Emisión de token /t web (en el bot) y /login (web).
|
|
|
|
|
|
|
|
- ICS por token/IP (p. ej., 4 req/min).
|
|
|
|
|
|
|
|
- Caching ICS:
|
|
|
|
|
|
|
|
- ETag/Last-Modified y Cache-Control: public, max-age=300 (suave), para que los clientes no abusen.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 12) DevOps y despliegue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Entornos:
|
|
|
|
|
|
|
|
- WEB_BASE_URL, COOKIE_SECRET, SESSION_IDLE_TTL_MIN=120, ICS_HORIZON_MONTHS=12, ICS_RATE_LIMIT, etc.
|
|
|
|
|
|
|
|
- Reutilizar EVOLUTION_API_* donde aplique (si se consulta API desde web).
|
|
|
|
|
|
|
|
- Build:
|
|
|
|
|
|
|
|
- SvelteKit con adapter-node; ejecución con Bun o Node en producción.
|
|
|
|
|
|
|
|
- Reverse proxy:
|
|
|
|
|
|
|
|
- apps/web servido en app.example.com (o /app).
|
|
|
|
|
|
|
|
- apps/bot mantiene su endpoint de webhook. Compartir .env según necesidad.
|
|
|
|
|
|
|
|
- Schedulers:
|
|
|
|
|
|
|
|
- Permanecen en el proceso del bot. apps/web no arranca ningún scheduler.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 13) Plan de trabajo por etapas
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Etapa 0 — Preparación
|
|
|
|
|
|
|
|
- Crear estructura apps/web (SvelteKit con adapter-node).
|
|
|
|
|
|
|
|
- Configurar ESLint/Prettier y CI mínimos (lint, build).
|
|
|
|
|
|
|
|
- Asegurar que la web abre la misma DB (PRAGMAs coherentes).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Etapa 1 — Autenticación
|
|
|
|
|
|
|
|
- Migraciones: web_tokens, web_sessions.
|
|
|
|
|
|
|
|
- Bot: emisión de token de 10 min (hash, rate limit) en /t web.
|
|
|
|
|
|
|
|
- Web: endpoint /login, cookie de sesión, redirect limpio; hooks de sesión con idle timeout 2h.
|
|
|
|
|
|
|
|
- Páginas de error/expiración.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Etapa 2 — Lectura de datos (MVP)
|
|
|
|
|
|
|
|
- APIs: /api/me/tasks, /api/me/groups, /api/groups/:id/tasks, /api/me/preferences (GET).
|
|
|
|
|
|
|
|
- UI: “Mis tareas” y “Grupos” (solo lectura).
|
|
|
|
|
|
|
|
- Orden de creación desc, filtros básicos, búsqueda.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Etapa 3 — Preferencias
|
|
|
|
|
|
|
|
- APIs: GET/POST /api/me/preferences.
|
|
|
|
|
|
|
|
- UI: edición de frecuencia/hora y vista del próximo recordatorio.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Etapa 4 — ICS
|
|
|
|
|
|
|
|
- Migraciones: calendar_tokens.
|
|
|
|
|
|
|
|
- APIs/UI Integraciones: autogeneración perezosa de feeds B (por usuario+grupo) y C (multigrupo opcional).
|
|
|
|
|
|
|
|
- Endpoints ICS: personal, group (B), aggregate (C), con horizonte 12 meses y solo con due_date.
|
|
|
|
|
|
|
|
- Revocación/rotación manual. Revocación automática al perder membresía (cron en bot o check dinámico).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Etapa 5 — Pulido y observabilidad
|
|
|
|
|
|
|
|
- Métricas, rate limits, ETag/Last-Modified en ICS.
|
|
|
|
|
|
|
|
- CSP y cabeceras de seguridad.
|
|
|
|
|
|
|
|
- UX: copiar enlace, avisos claros, vacíos de estado.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Etapa 6 — Evolutivos (posteriores)
|
|
|
|
|
|
|
|
- Edición de tareas (claim/unassign, fechas).
|
|
|
|
|
|
|
|
- Búsqueda avanzada y atajos.
|
|
|
|
|
|
|
|
- Notificaciones (SSE/polling).
|
|
|
|
|
|
|
|
- Panel admin (opcional).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 14) Pruebas
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Unit:
|
|
|
|
|
|
|
|
- Emisión/canje de token, expiración, cookie y expiración por inactividad.
|
|
|
|
|
|
|
|
- Autorización de endpoints (gating, membresía).
|
|
|
|
|
|
|
|
- Generación ICS y filtros (due_date, horizonte).
|
|
|
|
|
|
|
|
- Integración:
|
|
|
|
|
|
|
|
- Flujo end-to-end: /t web → /login → /app.
|
|
|
|
|
|
|
|
- Listado de feeds y autogeneración B.
|
|
|
|
|
|
|
|
- Regresión:
|
|
|
|
|
|
|
|
- Aislar que schedulers solo corran en el bot.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 15) Riesgos y mitigaciones
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Fuga de enlace de ICS:
|
|
|
|
|
|
|
|
- Tokens largos, revocación sencilla, métricas y rate-limit. Evitar ICS compartido de grupo. B y C permiten revocar por usuario.
|
|
|
|
|
|
|
|
- Doble arranque de tareas en web:
|
|
|
|
|
|
|
|
- Flag para no iniciar schedulers en apps/web.
|
|
|
|
|
|
|
|
- Concurrencia SQLite:
|
|
|
|
|
|
|
|
- WAL + busy_timeout ya configurados; operaciones ICE (lectura) mayoritarias en web.
|
|
|
|
|
|
|
|
- Fricción de login:
|
|
|
|
|
|
|
|
- /t web es rápido; expiración 10 min adecuada; mensajes claros si expira.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 16) Variables de entorno (propuestas, apps/web)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- WEB_BASE_URL
|
|
|
|
|
|
|
|
- COOKIE_SECRET
|
|
|
|
|
|
|
|
- SESSION_IDLE_TTL_MIN=120
|
|
|
|
|
|
|
|
- ICS_HORIZON_MONTHS=12
|
|
|
|
|
|
|
|
- ICS_RATE_LIMIT_PER_MIN=4
|
|
|
|
|
|
|
|
- NODE_ENV / BUN_ENV
|
|
|
|
|
|
|
|
- (Opcional) METRICS_ENABLED
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 17) Métricas (nombres sugeridos)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- web_tokens_issued_total
|
|
|
|
|
|
|
|
- web_tokens_redeemed_total
|
|
|
|
|
|
|
|
- web_login_success_total
|
|
|
|
|
|
|
|
- web_login_failed_total
|
|
|
|
|
|
|
|
- web_sessions_active
|
|
|
|
|
|
|
|
- web_api_requests_total{route}
|
|
|
|
|
|
|
|
- ics_tokens_created_total{type}
|
|
|
|
|
|
|
|
- ics_tokens_revoked_total{type}
|
|
|
|
|
|
|
|
- ics_requests_total{type}
|
|
|
|
|
|
|
|
- ics_rate_limit_hits_total
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Fin del documento.
|