|  |  | # 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.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ## Estado actual (2025-10-13)
 | 
						
						
						
							|  |  | - PR 1 (fundaciones de UI) integrado: tokens.css, base.css, AppShell, layout de /app y gating coherente a /login.
 | 
						
						
						
							|  |  | - Problema abierto en producción: los estilos globales no aparecen y el cliente muestra un error al cargar módulos de /_app.
 | 
						
						
						
							|  |  |   - start.*.js responde 200, pero el fallo puede estar en chunks secundarios (/_app/immutable/chunks/*.js) o en caché desincronizada.
 | 
						
						
						
							|  |  | - Acciones inmediatas:
 | 
						
						
						
							|  |  |   1) Verificar desde fuera: GET /_app/version.json, /_app/immutable/entry/start*.js y, especialmente, los /_app/immutable/chunks/*.js referenciados por start.*.js (deben devolver 200 y Content-Type application/javascript, no HTML).
 | 
						
						
						
							|  |  |   2) Asegurar despliegue del build completo de SvelteKit (apps/web/build/index.js y apps/web/build/client) y que el proceso web arranca con cwd correcto.
 | 
						
						
						
							|  |  |   3) Proxy: enrutar y no interceptar /_app/*; evitar catch-all que devuelva HTML; respetar Content-Type; no cachear el HTML de la app (o purgar tras deploy).
 | 
						
						
						
							|  |  |   4) Mantener imports CSS globales en apps/web/src/routes/+layout.svelte (ya aplicado).
 | 
						
						
						
							|  |  | - Seguimiento: cuando /_app/* sirva correctamente los chunks, los estilos deberían aparecer sin cambios adicionales de código.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ## 1) Decisiones fijadas
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | - Arquitectura: dos procesos (apps/bot y apps/web) ejecutándose en la misma app de CapRover, con un proxy interno en Bun (puerto 3000) que enruta /webhook y /metrics al bot (3007) y el resto a la web (3008). 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 fecha de vencimiento ascendente (NULL al final).
 | 
						
						
						
							|  |  | - 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 por fecha de vencimiento asc), 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):
 | 
						
						
						
							|  |  |   - GET /login muestra una página intermedia sin auto-submit; requiere interacción mínima. Un script establece una cookie efímera login_intent y habilita el botón.
 | 
						
						
						
							|  |  |   - POST /login valida hash y caducidad y comprueba la cookie login_intent; si ok, invalida el token (marcar usado).
 | 
						
						
						
							|  |  |   - Crea sesión en DB (web_sessions) y emite cookie de sesión (solo cookie de sesión, sin persistencia en disco).
 | 
						
						
						
							|  |  |   - Redirige 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.
 | 
						
						
						
							|  |  |   - CSRF: checkOrigin desactivado en SvelteKit debido al proxy interno que reenvía las peticiones (mismo dominio).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ## 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=… (página intermedia con gate de JS)
 | 
						
						
						
							|  |  |   - POST /login (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:
 | 
						
						
						
							|  |  |   - Un solo contenedor con proxy interno en Bun:
 | 
						
						
						
							|  |  |     - /webhook y /metrics → bot (puerto interno 3007).
 | 
						
						
						
							|  |  |     - Resto de rutas → web (puerto interno 3008, SvelteKit adapter-node).
 | 
						
						
						
							|  |  |     - Passthrough explícito de /_app/* hacia la web (sin reescrituras ni catch-all que devuelva HTML).
 | 
						
						
						
							|  |  |     - Asegurar Content-Type correcto para /_app/**/*.js (application/javascript) y /_app/**/*.css (text/css); no añadir nosniff en assets externos al HTML.
 | 
						
						
						
							|  |  |     - El build de SvelteKit debe desplegarse completo (build/index.js + build/client) y mantenerse coherente con el HTML servido para evitar hashes huérfanos.
 | 
						
						
						
							|  |  |     - Evitar cachear el HTML de la app en el proxy/CDN (o purgar tras cada deploy); los assets /_app/immutable pueden cachearse largo con immutable.
 | 
						
						
						
							|  |  |     - CapRover debe exponer el puerto 3000 del contenedor (PORT).
 | 
						
						
						
							|  |  |     - WEBHOOK_URL debe apuntar a https://<dominio>/webhook (mismo dominio).
 | 
						
						
						
							|  |  | - 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. — HECHO
 | 
						
						
						
							|  |  | - Bot: emisión de token de 10 min (hash, rate limit) en /t web. — HECHO
 | 
						
						
						
							|  |  | - Web: endpoint /login (GET intermedio + POST canje), cookie de sesión, redirect limpio; hooks de sesión con idle timeout 2h; gate de JS; CSRF checkOrigin desactivado por proxy interno. — HECHO
 | 
						
						
						
							|  |  | - Páginas de error/expiración.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Etapa 2 — Lectura de datos (MVP) — COMPLETADA: GET /api/me/tasks (orden por due_date asc con NULL al final; búsqueda con ESCAPE; filtros soonDays/dueBefore; paginación page/limit), GET /api/me/groups (contadores open/unassigned) y GET /api/groups/:id/tasks (unassignedFirst, onlyUnassigned, limit). UI: /app con filtros/búsqueda/paginación y /app/groups con prefetch "sin responsable".
 | 
						
						
						
							|  |  | - APIs: /api/me/tasks, /api/me/groups, /api/groups/:id/tasks, /api/me/preferences (GET).
 | 
						
						
						
							|  |  | - UI: “Mis tareas” y “Grupos” (solo lectura).
 | 
						
						
						
							|  |  | - Orden por fecha de vencimiento asc (NULL al final), filtros básicos, búsqueda.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Etapa 3 — Preferencias — COMPLETADA
 | 
						
						
						
							|  |  | - APIs: GET/POST /api/me/preferences (validación y upsert; normalización HH:MM; conservación de hora al desactivar).
 | 
						
						
						
							|  |  | - UI: /app/preferences con formulario (frecuencia y hora) y “próximo recordatorio” calculado en servidor, alineado con la TZ y la semántica del bot (weekly = lunes, weekdays = L–V).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 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
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Implementado: suite web con bun:test y build programático (helpers en tests/web/helpers/server.ts). Los tests arrancan la web real (adapter-node) y ejercitan endpoints y páginas vía HTTP, usando una base SQLite temporal.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | - 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
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ## 18) Plan UX/UI (detallado, sin dependencias externas)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Objetivo
 | 
						
						
						
							|  |  | - Disponer de una guía exhaustiva para diseñar e implementar la interfaz web usando SvelteKit/Svelte, sin dependencias externas de UI, asegurando consistencia, accesibilidad y un flujo de trabajo por etapas (vertical slices).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.1) Principios de diseño y restricciones
 | 
						
						
						
							|  |  | - Sin dependencias externas: no Tailwind ni librerías de componentes. CSS moderno con variables y módulos Svelte.
 | 
						
						
						
							|  |  | - Aprovechar SvelteKit:
 | 
						
						
						
							|  |  |   - SSR por defecto, progressive enhancement en eventos/acciones.
 | 
						
						
						
							|  |  |   - +page.svelte / +page.server.ts para data loading; endpoints +server.ts ya existentes.
 | 
						
						
						
							|  |  |   - Stores de Svelte para estado global mínimo (toasts, sesión).
 | 
						
						
						
							|  |  | - Mobile-first, responsive fluido; desktop con anchos máximos (contenedor ~960–1200px) y layout en 2 columnas donde aplique.
 | 
						
						
						
							|  |  | - Accesibilidad AA: foco visible, roles ARIA en componentes custom, labels asociados, contraste >= 4.5:1.
 | 
						
						
						
							|  |  | - Rendimiento: CSS mínimo crítico inline, diferir lo no esencial, listas paginadas; sin icon fonts (usar SVG inline).
 | 
						
						
						
							|  |  | - Seguridad: estados de sesión claros; nunca exponer tokens; evitar “copiar URL” en texto plano (usar botón Copy).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.2) Lenguaje visual y Design Tokens
 | 
						
						
						
							|  |  | - Tipografía: usar fuentes del sistema (Inter/SF Pro/Segoe UI/Roboto/Noto/Sans-Serif fallback).
 | 
						
						
						
							|  |  | - Escala tipográfica: 12/14/16/20/24 px; line-height 1.4–1.6.
 | 
						
						
						
							|  |  | - Espaciado: 4/8/12/16/24/32 px; grid de 8 px.
 | 
						
						
						
							|  |  | - Radios: 6/8 px; sombra suave para elevaciones (header sticky, tarjetas).
 | 
						
						
						
							|  |  | - Paleta (light/dark con prefers-color-scheme):
 | 
						
						
						
							|  |  |   - Neutral: bg, surface, border, text, text-muted.
 | 
						
						
						
							|  |  |   - Acentos: primary (acciones), danger (rotar/revocar), warning (pronto), success (ok).
 | 
						
						
						
							|  |  | - Badges semánticos:
 | 
						
						
						
							|  |  |   - Overdue: rojo.
 | 
						
						
						
							|  |  |   - Due soon (≤3 días): ámbar.
 | 
						
						
						
							|  |  |   - Unassigned: gris/azul neutro.
 | 
						
						
						
							|  |  | - Tokens (variables CSS en :root):
 | 
						
						
						
							|  |  |   - color-bg, color-surface, color-text, color-text-muted, color-border, color-primary, color-danger, color-warning, color-success
 | 
						
						
						
							|  |  |   - radius-sm/md, shadow-sm/md, space-1..5
 | 
						
						
						
							|  |  | - Modo oscuro: ajustar variables sin duplicar estilos.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.3) Accesibilidad (checklist)
 | 
						
						
						
							|  |  | - Navegación por teclado completa; focus ring perceptible.
 | 
						
						
						
							|  |  | - Contraste verificado para texto y controles (>=4.5:1).
 | 
						
						
						
							|  |  | - Labels y aria-describedby en inputs; botones con aria-label si solo icono.
 | 
						
						
						
							|  |  | - Estados y errores anunciados (role="status"/"alert" donde aplique).
 | 
						
						
						
							|  |  | - Trampas de foco evitadas; orden lógico en DOM.
 | 
						
						
						
							|  |  | - Tamaño táctil mínimo 44x44 px.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.4) Inventario de componentes (Design System v0)
 | 
						
						
						
							|  |  | - Base
 | 
						
						
						
							|  |  |   - Button (variants: primary/secondary/ghost/danger; tamaños sm/md; con/ sin icono).
 | 
						
						
						
							|  |  |   - IconButton (solo icono, aria-label).
 | 
						
						
						
							|  |  |   - TextField (búsqueda), TimeField HH:MM (validación simple).
 | 
						
						
						
							|  |  |   - SegmentedControl (frecuencia: daily/weekly/weekdays/off).
 | 
						
						
						
							|  |  |   - Select básico (nativo estilizado).
 | 
						
						
						
							|  |  |   - Switch/Checkbox (para activar feed C).
 | 
						
						
						
							|  |  |   - Badge (overdue/soon/default).
 | 
						
						
						
							|  |  |   - Card (surface + padding + shadow).
 | 
						
						
						
							|  |  |   - Pagination (prev/next + indicador página).
 | 
						
						
						
							|  |  |   - Toast/Snackbar (store global; auto-dismiss; role="status").
 | 
						
						
						
							|  |  |   - ConfirmDialog (portal sencillo con focus trap básico).
 | 
						
						
						
							|  |  |   - Skeleton (rectángulos/filas).
 | 
						
						
						
							|  |  |   - EmptyState y ErrorBanner.
 | 
						
						
						
							|  |  | - Datos
 | 
						
						
						
							|  |  |   - TaskItem (fila) con: [id], descripción, fecha (badge), grupo, asignación (solo lectura).
 | 
						
						
						
							|  |  |   - GroupCard con nombre, contadores open/unassigned.
 | 
						
						
						
							|  |  |   - FeedCard con nombre, descripción, botón Copiar y Rotar, estado (revocado/no disponible).
 | 
						
						
						
							|  |  | - Utilidades
 | 
						
						
						
							|  |  |   - CopyToClipboard (navigator.clipboard con fallback).
 | 
						
						
						
							|  |  |   - RelativeDate / DueBadge (lógica de overdue/soon).
 | 
						
						
						
							|  |  |   - VisuallyHidden (accesibilidad).
 | 
						
						
						
							|  |  |   - AppShell (header con usuario/logout, contenedor principal).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.5) Patrones de interacción
 | 
						
						
						
							|  |  | - Búsqueda: submit explícito o debounce 250–300 ms con actualización de query params; mantener estado al navegar atrás.
 | 
						
						
						
							|  |  | - Filtros: chips/segmented con sync a URL (page reset a 1 cuando cambian).
 | 
						
						
						
							|  |  | - Paginación: enlaces con URL (page, limit); accesible.
 | 
						
						
						
							|  |  | - Formularios: usar fetch desde el cliente con progressive enhancement; validación en cliente (básica) + servidor (autoritativa).
 | 
						
						
						
							|  |  | - Copiar: icono “copiar” con feedback de toast y aria-live.
 | 
						
						
						
							|  |  | - Confirmaciones peligrosas: diálogo modal con foco dentro y acciones claras.
 | 
						
						
						
							|  |  | - Estados: loading (skeletons), vacío (mensaje y CTA contextual), error (retry).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.6) IA y flujos por pantalla
 | 
						
						
						
							|  |  | - /login
 | 
						
						
						
							|  |  |   - Objetivo: canjear token con gate de interacción mínima.
 | 
						
						
						
							|  |  |   - Contenido: mensaje, botón “Continuar”, estado token inválido/expirado con instrucciones /t web.
 | 
						
						
						
							|  |  |   - Accesibilidad: botón enfocable, mensajes claros.
 | 
						
						
						
							|  |  | - /app (Mis tareas)
 | 
						
						
						
							|  |  |   - Controles: búsqueda texto, chips “Abiertas”, “Pronto (≤3 días)”, selector “Vencen antes de…” (3/7/14 días).
 | 
						
						
						
							|  |  |   - Lista: TaskItem con fecha badge, grupo, asignación; paginación.
 | 
						
						
						
							|  |  |   - Estados: vacío, sin resultados, error de carga.
 | 
						
						
						
							|  |  |   - Mobile: lista de una columna; Desktop: contenido centrado con ancho máx; opcional 2 columnas si hay filtros persistentes.
 | 
						
						
						
							|  |  | - /app/groups
 | 
						
						
						
							|  |  |   - Grid de GroupCard (2–3 col en desktop, 1 en móvil).
 | 
						
						
						
							|  |  |   - Ver “sin responsable” destacado; posibilidad de prefetch a /api/groups/:id/tasks?onlyUnassigned=1 (aunque MVP sea lectura agregada).
 | 
						
						
						
							|  |  |   - Estados: sin grupos, error.
 | 
						
						
						
							|  |  | - /app/preferences
 | 
						
						
						
							|  |  |   - Frecuencia (Segmented), Hora (TimeField HH:MM).
 | 
						
						
						
							|  |  |   - “Próximo recordatorio” calculado por servidor (mostrar string amigable e ISO en tooltip).
 | 
						
						
						
							|  |  |   - Acciones: Guardar y Revertir; toasts en éxito/error.
 | 
						
						
						
							|  |  |   - Validación: normalizar hora en cliente (HH:MM) y servidor.
 | 
						
						
						
							|  |  | - /app/integrations
 | 
						
						
						
							|  |  |   - Autogeneración perezosa de feeds B en la carga (el backend garantiza creación si falta).
 | 
						
						
						
							|  |  |   - Tarjetas: Personal (mis tareas), Grupo (B) por cada grupo activo, Multigrupo (C) opcional con switch.
 | 
						
						
						
							|  |  |   - Acciones: Copiar (URL oculta, se copia con click), Rotar (confirmación).
 | 
						
						
						
							|  |  |   - Estados: sin grupos → mostrar solo Personal; feed revocado → indicador y opción recrear.
 | 
						
						
						
							|  |  |   - Microcopy: guía breve (Google/Apple/Outlook), aviso privacidad.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.7) Contratos de datos (UI)
 | 
						
						
						
							|  |  | - TaskItem
 | 
						
						
						
							|  |  |   - id: number
 | 
						
						
						
							|  |  |   - description: string
 | 
						
						
						
							|  |  |   - due_date: string | null (YYYY-MM-DD)
 | 
						
						
						
							|  |  |   - group: { id: string; name: string } | null
 | 
						
						
						
							|  |  |   - assignees: string[] (ids normalizados)
 | 
						
						
						
							|  |  |   - flags: { overdue: boolean; dueSoon: boolean }
 | 
						
						
						
							|  |  | - TasksList meta
 | 
						
						
						
							|  |  |   - page: number; limit: number; total: number
 | 
						
						
						
							|  |  | - Group
 | 
						
						
						
							|  |  |   - id: string; name: string
 | 
						
						
						
							|  |  |   - counts: { open: number; unassigned: number }
 | 
						
						
						
							|  |  | - Preferences
 | 
						
						
						
							|  |  |   - freq: 'daily'|'weekly'|'weekdays'|'off'
 | 
						
						
						
							|  |  |   - time: string | null (HH:MM)
 | 
						
						
						
							|  |  |   - nextReminder: { human: string; iso: string | null }
 | 
						
						
						
							|  |  | - Feed
 | 
						
						
						
							|  |  |   - type: 'personal'|'group'|'aggregate'
 | 
						
						
						
							|  |  |   - groupId?: string
 | 
						
						
						
							|  |  |   - url?: string (solo UI, nunca persistida)
 | 
						
						
						
							|  |  |   - created_at?: string; last_used_at?: string | null
 | 
						
						
						
							|  |  |   - status?: 'active'|'revoked'|'unavailable'
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.8) Arquitectura front (sin librerías externas)
 | 
						
						
						
							|  |  | - Estructura sugerida en apps/web/src
 | 
						
						
						
							|  |  |   - lib/ui/atoms: Button.svelte, IconButton.svelte, Badge.svelte, Skeleton.svelte, VisuallyHidden.svelte
 | 
						
						
						
							|  |  |   - lib/ui/inputs: TextField.svelte, TimeField.svelte, SegmentedControl.svelte, Switch.svelte, Select.svelte
 | 
						
						
						
							|  |  |   - lib/ui/feedback: Toast.svelte (+ store), ConfirmDialog.svelte, EmptyState.svelte, ErrorBanner.svelte
 | 
						
						
						
							|  |  |   - lib/ui/layout: AppShell.svelte, Card.svelte, Pagination.svelte
 | 
						
						
						
							|  |  |   - lib/ui/data: TaskItem.svelte, GroupCard.svelte, FeedCard.svelte
 | 
						
						
						
							|  |  |   - lib/stores: toasts.ts, session.ts (mínimo, p. ej. userId)
 | 
						
						
						
							|  |  |   - lib/styles: tokens.css (variables), base.css (reset + utilidades mínimas)
 | 
						
						
						
							|  |  |   - routes/app/*: páginas; usar load con SSR y fetch interno
 | 
						
						
						
							|  |  | - Theming y estilos
 | 
						
						
						
							|  |  |   - tokens.css con variables; base.css con reset ligero (normalize reducido) y utilidades puntuales (sr-only, container, grid).
 | 
						
						
						
							|  |  |   - Modo oscuro con prefers-color-scheme; clase .theme-dark opcional.
 | 
						
						
						
							|  |  | - Iconos
 | 
						
						
						
							|  |  |   - SVG inline en componentes; set mínimo (copy, rotate, search, warning, check, x).
 | 
						
						
						
							|  |  | - Utilidades
 | 
						
						
						
							|  |  |   - copyToClipboard util con fallback.
 | 
						
						
						
							|  |  |   - date helpers en lib/utils/date.ts (formatos UI, dueSoon/overdue).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.9) Roadmap por etapas (2 semanas sugerido)
 | 
						
						
						
							|  |  | - Día 1: Tokens y base.css; AppShell; definición final de contratos de datos por pantalla.
 | 
						
						
						
							|  |  |   - Entregable: estilos base, header, contenedor, tipografía; documento de contratos de datos firmado.
 | 
						
						
						
							|  |  | - Días 2–3: Componentes base (Button, TextField, Segmented, Badge, Card, Toast, Skeleton, EmptyState).
 | 
						
						
						
							|  |  |   - Entregable: catálogo mínimo interactivo (página de sandbox oculta /app/_sandbox).
 | 
						
						
						
							|  |  | - Días 4–5: Página /app (Mis tareas) end-to-end con APIs existentes (GET /api/me/tasks).
 | 
						
						
						
							|  |  |   - Entregable: búsqueda, filtros, paginación, estados; lighthouse > 90 en móvil.
 | 
						
						
						
							|  |  | - Día 6: /app/groups con GroupCard y prefetch de “sin responsable”.
 | 
						
						
						
							|  |  |   - Entregable: grid responsive con contadores.
 | 
						
						
						
							|  |  | - Día 7: /app/preferences (GET/POST) con vista de “próximo recordatorio”.
 | 
						
						
						
							|  |  |   - Entregable: validación y toasts.
 | 
						
						
						
							|  |  | - Días 8–9: /app/integrations UI completa (autogeneración perezosa, Copiar, Rotar con confirmación).
 | 
						
						
						
							|  |  |   - Backend ICS puede avanzar en paralelo; usar mocks si falta endpoint.
 | 
						
						
						
							|  |  | - Día 10: QA accesibilidad y responsive; pulido de microcopy; revisión con 1–2 usuarios internos.
 | 
						
						
						
							|  |  |   - Entregable: checklist AA, correcciones.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.10) Criterios de aceptación UX
 | 
						
						
						
							|  |  | - Navegación completa con teclado; foco visible.
 | 
						
						
						
							|  |  | - Estados loading/vacío/error presentes y claros en todas las pantallas.
 | 
						
						
						
							|  |  | - “Copiar enlace” funciona y anuncia feedback; Rotar pide confirmación y comunica impacto.
 | 
						
						
						
							|  |  | - Preferencias reflejan correctamente TZ y el “próximo recordatorio”.
 | 
						
						
						
							|  |  | - Rendimiento aceptable: TTI < 2s en 3G rápida para pantallas principales; CSS < 15KB inicial.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.11) Validación y métricas (sin dependencias)
 | 
						
						
						
							|  |  | - Instrumentación mínima:
 | 
						
						
						
							|  |  |   - web_ui_interaction_total{type='copy'|'rotate'|'save_prefs'|'search'} mediante el sistema de métricas existente (si expuesto al front vía endpoint).
 | 
						
						
						
							|  |  |   - Alternativa: logs discretos en servidor al invocar endpoints relevantes.
 | 
						
						
						
							|  |  | - Feedback usuario:
 | 
						
						
						
							|  |  |   - Recoger observaciones de claridad en /app/integrations y /login.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.12) Riesgos y mitigaciones
 | 
						
						
						
							|  |  | - Falta de librerías UI → más trabajo inicial: mitigado con un Design System v0 bien delimitado.
 | 
						
						
						
							|  |  | - Desalineación back/front → trabajar en vertical por pantalla con contratos de datos acordados.
 | 
						
						
						
							|  |  | - Accesibilidad ignorada al final → checklist desde el inicio y QA día 10.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 18.13) Notas de implementación (guía sin código)
 | 
						
						
						
							|  |  | - Mantener estilos de componentes scopeados por defecto de Svelte.
 | 
						
						
						
							|  |  | - Evitar CSS complejo; preferir componentes pequeños y composables.
 | 
						
						
						
							|  |  | - Sin icon fonts ni frameworks; SVG inline o componentes Svelte de icono.
 | 
						
						
						
							|  |  | - Usar acciones de Svelte (use:) para patrón copy-to-clipboard y focus-trap del modal.
 | 
						
						
						
							|  |  | - Sin degradación de SEO relevante (app privada); aún así, SSR establece base de contenido.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Con esto, el equipo puede trabajar por etapas, validar tempranamente con usuarios y mantener coherencia visual sin dependencias externas.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Fin del documento.
 |