You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
taskbot/docs/plan-interfaz-web.md

476 lines
28 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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.
- PR 2 (UX/UI etapa 18 — base) integrado:
- Componentes base: Button, Card, Badge, Pagination, Skeleton, VisuallyHidden, TextField, SegmentedControl.
- Utilidades: lib/utils/date.ts (todayYmdUTC, addDaysYmd, dueStatus).
- Componentes de datos: TaskItem (con badges de vencimiento), GroupCard (contadores y “sin responsable”).
- Páginas: /app, /app/groups y /app/preferences refactorizadas para usar los componentes; frecuencia ahora como radios (SegmentedControl).
- AppShell: navegación con estado activo, espaciado compacto moderado, modo oscuro automático.
- Calidad: tests de /app/preferences actualizados; resuelto warning de export no usado en TaskItem.
- Incidencia de producción resuelta: la causa era Content-Encoding (brotli/gzip) no compatible en la cadena. Se desactivó la compresión end-to-end: SvelteKit se construye con precompress=false y, en el proxy Bun, se fuerza Accept-Encoding: identity hacia la web y se eliminan Content-Encoding/Vary/Content-Length en las respuestas al cliente.
- Verificación: los assets /_app/* sirven 200 sin Content-Encoding y con Content-Type correcto. Estilos y JavaScript cargan correctamente.
- Edición de tareas en web integrada: reclamar/soltar, edición de fecha y descripción (PATCH /api/tasks/:id), completar (POST /api/tasks/:id/complete) y sección “Completadas (24 h)” en /app; con gating por AllowedGroups + membresía activa.
- Grupos: botón “Reclamar” en tarjetas; listado "sin responsable" sin límite; fichas ordenadas por número de "sin responsable".
## 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” sin límite y con botón “Reclamar”; fichas ordenadas por cantidad de “sin responsable”.
- Edición de tareas desde la web: reclamar/soltar asignación y editar fecha de vencimiento (YYYY-MM-DD).
- 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|recent&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)
- POST /api/tasks/:id/claim (reclamar tarea; idempotente; requiere sesión, tarea abierta y gating)
- POST /api/tasks/:id/unassign (soltar tarea; idempotente; requiere sesión, tarea abierta y gating)
- POST /api/tasks/:id/complete (marca como completada; idempotente. Si tiene group_id: cualquier miembro activo del grupo allowed; si no, solo un asignado. Devuelve completed y completed_at)
- PATCH /api/tasks/:id (actualiza { due_date: 'YYYY-MM-DD' | null, description?: string }; valida due_date y normaliza/sanea description como texto plano, 11000 chars, colapsando espacios; requiere sesión, tarea abierta y gating)
- 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” con acciones (reclamar/soltar/editar fecha).
- /app/groups: lista de grupos del usuario; tarjetas ordenadas por número de “sin responsable”; en cada una, “sin responsable” sin límite y botón “Reclamar”.
- /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 = LV).
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) — HECHO.
- 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 ~9601200px) 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.41.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 250300 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 (23 col en desktop, 1 en móvil).
- “Sin responsable” destacado sin límite, con botón “Reclamar”; ordenar tarjetas por cantidad de sin responsable; prefetch a /api/groups/:id/tasks?onlyUnassigned=1.
- 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 23: 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 45: 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 89: /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 12 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.