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.

375 lines
24 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 trabajo — Web (SvelteKit) orientado a móvil y acciones de tareas
Contexto y objetivos
- Reforzar la utilidad móvil, mostrar grupos y responsables correctamente, y simplificar acciones prioritarias (completar/undo) manteniendo seguridad (gating) alineada con el backend.
- Mantener el diseño sin truncar descripciones; ofrecer orden por fecha o por grupo; y mostrar TODAS las abiertas en /app/groups.
- Evitar regresiones, con cambios iterativos y fácilmente revertibles.
Decisiones globales
- No truncar descripciones en TaskItem; envolver en varias líneas. Acciones secundarias se desplazan a una segunda línea/menú contextual en móvil.
- “Completar” se promueve como acción principal (checkbox/botón destacado). Incluir “Deshacer completar” con ventana configurable (24h por defecto).
- “Mis tareas” tendrá dos secciones:
1) Asignadas a mí (todas abiertas).
2) Sin responsable de mis grupos (todas abiertas).
- “Grupos” mostrará secciones por grupo, con TODAS las tareas abiertas, expandibles/colapsables, y con toggle “Unassigned first”.
- Búsqueda por texto desaparece de la UI (se reserva a futuro si fuese necesaria).
- Orden conmutado por el usuario: Fecha (due asc, NULL al final) | Grupo (agrupación por grupo y dentro por fecha).
- Seguridad: endurecer PATCH de tareas sin grupo para exigir ser responsable (y opcionalmente creador si el esquema lo permite).
- Ventana de uncomplete (deshacer completar): configurable por variable de entorno UNCOMPLETE_WINDOW_MIN (1440 por defecto).
Medición de impacto y riesgos
- N+1 inicial en /app (al agregar “sin responsable” de todos los grupos) tolerable en MVP, mitigado luego con endpoint “overview”.
- Posible crecimiento de DOM en /app/groups: se mitiga con secciones colapsables.
- Cambios de gating en PATCH requieren revisar casos edge (tareas personales sin asignados previos).
Archivos ya disponibles para edición (confirmados)
- UI:
- apps/web/src/lib/ui/layout/AppShell.svelte
- apps/web/src/lib/ui/data/TaskItem.svelte
- apps/web/src/lib/ui/data/GroupCard.svelte
- apps/web/src/routes/app/+page.svelte
- apps/web/src/routes/app/+page.server.ts
- apps/web/src/routes/app/groups/+page.svelte
- apps/web/src/routes/app/groups/+page.server.ts
- API:
- apps/web/src/routes/api/me/tasks/+server.ts
- apps/web/src/routes/api/me/groups/+server.ts
- apps/web/src/routes/api/groups/[id]/tasks/+server.ts
- apps/web/src/routes/api/tasks/[id]/+server.ts (PATCH)
- apps/web/src/routes/api/tasks/[id]/claim/+server.ts
- apps/web/src/routes/api/tasks/[id]/unassign/+server.ts
- apps/web/src/routes/api/tasks/[id]/complete/+server.ts
- Infra web:
- apps/web/src/lib/server/env.ts
- apps/web/src/lib/server/db.ts
- Núcleo:
- src/db.ts
Archivos que solicitaremos añadir al chat cuando toque editar
- Documentación: docs/operations.md (para documentar UNCOMPLETE_WINDOW_MIN y notas UX).
- Tests: tests/web/* y tests/unit/* (para cubrir uncomplete y cambios de gating).
- (Opcional) Migraciones: src/db/migrations/* si llegamos a necesitar created_by en gating de PATCH.
Fase 1 — UX base en páginas y TaskItem (sin backend nuevo) — Estado: Completada
Objetivos
- /app: añadir sección “Sin responsable de mis grupos”, quitar búsqueda, añadir conmutador de orden (Fecha | Grupo).
- /app/groups: mostrar TODAS las tareas por grupo en secciones expandibles/colapsables; toggle “Unassigned first”.
- TaskItem: mostrar chip de grupo; mantener descripción sin truncar; reorganizar acciones para móvil.
Archivos a editar
- apps/web/src/routes/app/+page.server.ts
- Cargar /api/me/tasks (open, asignadas a mí).
- Cargar /api/me/groups; para cada grupo, solicitar /api/groups/:id/tasks?onlyUnassigned=true&limit=0 y reunir la lista “unassigned”.
- Construir map {groupId → groupName} para la UI.
- Gestionar query param order=due|group.
- apps/web/src/routes/app/+page.svelte
- Renderizar dos secciones (asignadas y sin responsable). Añadir conmutador de orden. Eliminar campo de búsqueda.
- apps/web/src/routes/app/groups/+page.server.ts
- En vez de previews, cargar /api/groups/:id/tasks?limit=0 para cada grupo (todas abiertas).
- Permitir query unassignedFirst=true (por grupo o global).
- apps/web/src/routes/app/groups/+page.svelte
- Reemplazar cuadrícula de GroupCard por secciones por grupo (cabecera con +/ para colapsar).
- Dentro de cada sección, lista de TaskItem reutilizando las mismas acciones.
- apps/web/src/lib/ui/data/TaskItem.svelte
- Mostrar chip con el nombre del grupo si group_id != null; “Personal” si no tiene grupo (usar map pasado desde el server o prop groupName).
- Promover “Completar” visualmente (sin cambiar aún la API).
- apps/web/src/lib/ui/data/GroupCard.svelte
- Mantener para posibles resúmenes; la página de grupos dejará de usarla de forma principal.
Decisiones
- Orden “por grupo” inicialmente puede agruparse en cliente para el agregado “sin responsable”; “asignadas” ya vienen por fecha. En fases posteriores, endpoint overview dará orden estable en servidor.
Fase 2 — Backend: Uncomplete (24h configurable) + seguridad de PATCH — Estado: Completada
Objetivos
- Añadir POST /api/tasks/:id/uncomplete (idempotente) con gating simétrico a /complete y ventana configurable.
- Endurecer PATCH /api/tasks/:id para tareas sin group_id: exigir que el usuario sea asignado (y, si luego confirmamos schema, permitir también si created_by = usuario).
Archivos a editar/crear
- apps/web/src/lib/server/env.ts
- Añadir export const UNCOMPLETE_WINDOW_MIN = ... (leer de env, default 1440). Exponer helper uncompleteWindowMs si conviene.
- apps/web/src/routes/api/tasks/[id]/uncomplete/+server.ts (nuevo)
- Validar sesión, id, existencia de tarea.
- Gating:
- Con group_id: miembro activo de grupo allowed.
- Sin group_id: debe estar asignado a la tarea.
- Validar ventana: completed=1 y completed_at >= now - UNCOMPLETE_WINDOW_MIN.
- UPDATE: completed=0, completed_at=NULL (y opcional completed_by=NULL).
- Respuesta: status ('updated'|'already'|'not_found'|'forbidden'), tarea resultante.
- apps/web/src/routes/api/tasks/[id]/+server.ts (PATCH)
- Añadir chequeo cuando group_id IS NULL: exigir que exista asignación del user sobre la tarea (alineado con complete/unassign).
- UI
- apps/web/src/lib/ui/data/TaskItem.svelte — añadir acción “Deshacer completar” (usando el nuevo endpoint) y toast con feedback.
- apps/web/src/routes/app/+page.svelte — en la sección “Completadas (24 h)” añadir botón “Deshacer completar”.
Documentación y configuración (solicitaremos permiso para editar)
- docs/operations.md — documentar UNCOMPLETE_WINDOW_MIN (minutos; default 1440), semántica e idempotencia.
Fase 3 — Navegación móvil (barra de pestañas) y jerarquía de acciones — Estado: Completada
Objetivos
- Evitar que el header rebose en móvil. Añadir barra inferior con iconos (tabs) para Tareas, Grupos, Integraciones (calendario), Preferencias (alarma). Reducir peso visual de “Integraciones” en móvil.
- Consolidar “Completar” como acción primaria (checkbox/botón destacado); otras acciones (Reclamar/Soltar/Editar/Fecha) en segunda línea o menú contextual.
Archivos a editar/crear
- apps/web/src/lib/ui/layout/AppShell.svelte
- Añadir barra inferior sticky (solo en viewport ≤768px), con safe-area, iconos (SVG inline o emojis inicialmente), aria-labels y foco accesible.
- Mantener header para desktop.
- (Opcional) apps/web/src/lib/ui/icons/* (nuevos)
- CalendarIcon.svelte, AlarmIcon.svelte, TasksIcon.svelte, GroupsIcon.svelte (si prefieres SVGs en vez de emojis).
- apps/web/src/lib/ui/data/TaskItem.svelte
- Ajustar layout mobile-first: acciones secundarias y espaciado compacto.
Resultado (implementado)
- En móvil (≤768px):
- Header de desktop oculto; tabbar inferior con 5 pestañas: Tareas (✅), Grupos (👥), Recordatorios (⏰), Calendarios (📅) y Salir (🚪, POST).
- Barra superior mínima con título dinámico (“Tareas”, “Grupos”, “Recordatorios”, “Calendarios”), altura 24px y safe-area superior.
- Iconografía con emojis; icono + texto hasta 768px y solo icono en ≤480px.
- Safe-area inferior respetado y offset de Toast ajustado para no solapar la tabbar.
- En desktop (>768px):
- Header visible con navegación renombrada/reordenada: Tareas / Grupos / Recordatorios / Calendarios.
- Accesibilidad:
- aria-labels en pestañas y logout, estado activo visible, orden de tabulación coherente.
- TaskItem:
- “Completar/Deshacer” promovido como acción primaria; acciones secundarias (Reclamar/Soltar, Editar, Fecha) en segunda línea compacta en móvil.
- Descripciones sin truncar, manteniendo legibilidad.
- Integración:
- Cambios aplicados en AppShell y Toast, evitando solapes y reservando espacio en main.
Fase 4 — Optimización: endpoint “overview” y orden en servidor — Estado: Completada
Objetivos
- Evitar N peticiones en /app para el bloque “sin responsable”.
- Servir orden “por grupo” ya resuelto en servidor.
Archivos a crear/editar
- apps/web/src/routes/api/me/tasks/overview/+server.ts (nuevo)
- Respuesta: { assigned: Task[], unassigned: Task[] } (open).
- Params: order=due|group_then_due (por defecto due).
- Cada Task incluye: id, description, due_date, group_id, group_name, display_code, assignees[].
- Gating: igual que /api/me/tasks.
- apps/web/src/routes/api/me/tasks/+server.ts
- (Opcional) Añadir order=group_then_due y group_name via JOIN; mantener compatibilidad con tests existentes.
- apps/web/src/routes/app/+page.server.ts
- Consumir overview para reducir llamadas y aplicar el orden de servidor.
Resultado (implementado)
- Endpoint GET /api/me/tasks/overview creado.
- Devuelve assigned y unassigned (abiertas) con order=due|group_then_due (mapeo desde la UI: group → group_then_due).
- En cada tarea: id, description, due_date, group_id, group_name (null en personales), display_code, assignees[] (vacío en unassigned).
- Gating aplicado: assigned según /api/me/tasks; unassigned solo de grupos allowed con membresía activa del usuario; exclusión de personales en unassigned.
- /app/+page.server.ts consume overview para “sin responsable” y elimina el N+1; se mantiene /api/me/tasks para “Mis tareas (abiertas)” con su paginación actual.
- Respuestas con cache-control: no-store.
Fase 5 — Responsables: conteo, marca “tú” y popover con wa.me — Estado: Completada
Objetivos
- Mostrar de forma compacta cuántas personas están asignadas; marcar si el usuario actual está entre ellas; listar números y permitir mensaje directo (wa.me) bajo demanda.
Archivos a editar/crear
- apps/web/src/lib/ui/data/TaskItem.svelte
- Badge “Responsables: n” + “tú” si corresponde; al pulsar, abrir popover/modal con lista.
- apps/web/src/lib/ui/feedback/Popover.svelte (nuevo) o reutilizar un modal ligero existente
- Accesibilidad: rol="dialog", focus trap, cierre con ESC.
- (Opcional) apps/web/src/lib/utils/phone.ts (nuevo)
- Helpers para abreviar números y construir URL segura wa.me.
Resultado (implementado)
- TaskItem: badge con conteo e indicador “tú”; en ausencia de responsables se muestra botón deshabilitado con icono 🙅.
- Popover accesible (rol="dialog", focus trap, cierre con ESC, restauración de foco) y compatible con SSR.
- Enlaces directos a WhatsApp usando wa.me/<dígitos>, con normalización de números.
- Unificación de UI en escritorio y móvil.
- No se requirieron cambios de backend; los endpoints ya devolvían assignees[].
Fase 6 — Pulido y peso visual de “Integraciones” — Estado: Completada
Objetivos (cumplidos)
- Mantener “Integraciones” (renombrada a “Calendarios”) accesible pero con menor jerarquía en móvil.
- Ajustar densidad, estados vacíos y recordar colapsado por grupo en localStorage.
Decisiones aplicadas y resultado
- Etiqueta y jerarquía:
- La pestaña se renombra a “Calendarios” en navegación de escritorio y tabbar móvil; títulos y aria-labels actualizados.
- Atenuación en móvil cuando inactiva; estado activo mantiene color primario y foco visible.
- Safe-areas y solapes:
- Offsets en main y Toast para no solapar con la tabbar; sticky de topbar móvil verificado.
- Estados vacíos:
- Opción B aplicada: mensajes con pista de acción (“Crea o reclama…” / “Crea una nueva o invita…”).
- Persistencia de colapsado por usuario en /app/groups:
- Clave localStorage: groupsCollapsed:v1:{userId}.
- Por defecto: abiertos los grupos con al menos una tarea abierta; colapsados los que no tienen tareas abiertas.
- Limpieza de IDs obsoletos; restauración en onMount, SSR-safe (sin parpadeos apreciables).
- Accesibilidad:
- Foco visible en tabs; uso de <details>/<summary> con estado coherente con aria-expanded implícito.
Archivos editados
- apps/web/src/lib/ui/layout/AppShell.svelte (renombrado a “Calendarios”, atenuación móvil, offsets).
- apps/web/src/routes/app/groups/+page.svelte (persistencia de colapsado por usuario + defaults basados en tareas).
- apps/web/src/routes/app/+page.svelte (textos de estados vacíos opción B).
- apps/web/src/lib/ui/feedback/Toast.svelte (offset móvil contra tabbar).
Criterios de aceptación (OK)
- /app muestra dos secciones con todas las tareas requeridas; sin truncar descripciones; orden conmutado.
- /app/groups muestra todas las tareas abiertas por grupo; secciones colapsables; “Unassigned first” operativo.
- Completar y Deshacer completar funcionan desde ambas páginas; ventana de 24h configurable; gating correcto.
- PATCH no permite editar tareas sin grupo si no eres responsable (o creador, si se habilita).
- Navegación móvil no rebosa; barra inferior accesible.
- Asignados: conteo visible, “tú” resaltado y lista en popover con wa.me.
Fase 7 — Densidad y acciones en una sola fila (TaskItem) — Estado: Completada
Objetivos
- Compactar la fila de acciones de TaskItem:
- Convertir el botón/indicador de responsables en “icono + número” sin texto (“personas asignadas” → solo icono + contador), manteniendo aria-label y tooltip accesibles.
- Ubicar en la misma fila: Responsables (icono+conteo), Reclamar/Soltar, Editar, Fecha.
- Reducir padding vertical excesivo para ganar densidad, manteniendo objetivos de accesibilidad (área táctil ≈44px y foco visible).
Plan de trabajo
1) TaskItem.svelte
- Sustituir texto “Responsables: n” por icono + n; aria-label dinámico (“n responsables; tú incluido/excluido”).
- Reorganizar contenedor de acciones para una sola fila en móvil y desktop; permitir wrap en pantallas muy pequeñas si es necesario.
- Ajustar tamaños (icon-size 1618px) y gaps a 68px.
2) Estilos globales y utilidades
- Revisar variables de espacio en tokens.css/base.css; reducir ligeramente los márgenes/paddings verticales de:
- Listas de tareas (ul.list > li o contenedor del TaskItem).
- Card.svelte (padding vertical).
- AppShell .main en móvil (si procede).
- Mantener contraste y focus-visible.
3) QA
- Verificar que en ≤480px no haya desbordes; que los tooltips/aria sean correctos; y targets táctiles respeten accesibilidad.
Archivos a editar
- apps/web/src/lib/ui/data/TaskItem.svelte (layout de acciones, icono+conteo).
- apps/web/src/lib/styles/base.css (ajustes finos de paddings/gaps).
- apps/web/src/lib/styles/tokens.css (si se decide ajustar variables globales de spacing).
- apps/web/src/lib/ui/layout/Card.svelte (si requiere reducir padding vertical interno).
Criterios de aceptación
- TaskItem muestra todas las acciones en una sola fila en móvil estándar (≥360px) sin saltos.
- El indicador de responsables conserva accesibilidad (aria-label/tooltip) y se entiende su semántica.
- La densidad aumenta perceptiblemente sin comprometer legibilidad ni foco.
Fase 8 — Orden por fecha o por grupo (corrección y alineación) — Estado: Completada
Objetivos
- Alinear el comportamiento de “Orden: Fecha | Grupo” con expectativas:
- Fecha: due_date asc; NULL al final; estable por id.
- Grupo: agrupar por grupo (Personal al final); dentro de cada grupo ordenar por due_date asc; NULL al final.
- Que el orden seleccionado afecte coherentemente a las secciones relevantes (asignadas y/o sin responsable), evitando inconsistencias entre cliente y servidor.
Plan de trabajo
1) Auditoría actual
- Revisar apps/web/src/routes/app/+page.server.ts y el consumo de /api/me/tasks/overview.
- Revisar apps/web/src/routes/app/+page.svelte (groupByGroup/sortByDue) para evitar doble orden contradictorio.
2) Backend
- apps/web/src/routes/api/me/tasks/overview/+server.ts: asegurar order=due|group_then_due y aplicar NULLS LAST consistente.
- Añadir tests que validen el orden en ambos modos.
3) UI
- apps/web/src/routes/app/+page.server.ts: pasar order al backend y confiar en su orden siempre que sea posible.
- apps/web/src/routes/app/+page.svelte: limitar orden en cliente a casos estrictamente necesarios; evitar reordenar lo ya ordenado por servidor.
4) QA y tests
- Casos con due_date iguales, NULLs, mezcla de grupos, y tareas personales.
Archivos a editar
- apps/web/src/routes/api/me/tasks/overview/+server.ts
- apps/web/src/routes/app/+page.server.ts
- apps/web/src/routes/app/+page.svelte
- tests/web/* (añadir/ajustar tests de orden)
Criterios de aceptación
- El cambio de orden se refleja de forma predecible y consistente en toda la página /app.
- Tests cubren due_date NULL, empates y orden de grupos (Personal al final).
Resultado (implementado)
- Backend autoritativo: /api/me/tasks y /api/me/tasks/overview aceptan order=due|group_then_due.
- Modo Fecha: due_date ASC con NULL al final; desempate estable por id.
- Modo Grupo: grupos A→Z con “Personal” al final; dentro de cada grupo due_date ASC con NULL al final; desempate por id.
- Gating consistente aplicado en ambos endpoints.
- UI /app:
- Pasa el parámetro de orden al backend para ambas secciones.
- Evita reordenar en cliente; solo agrupa visualmente “Sin responsable” cuando order=group.
- “Mis tareas (abiertas)” respeta el orden recibido (sin agrupar).
Pendiente (futuro opcional)
- Añadir pruebas automatizadas de orden para /api/me/tasks y /api/me/tasks/overview (casos con NULL y empates).
- Si se desea, agrupar visualmente por grupo en “Mis tareas (abiertas)” cuando order=group (solo encabezados; sin alterar el orden).
- Considerar índices adicionales si el dataset crece (p. ej., índices por due_date y group_id) para acelerar ORDER BY.
Fase 9 — Semilla de desarrollo enriquecida — Estado: Completada
Objetivos
- Disponer de una BD de desarrollo amplia para probar casos reales:
- Grupos con y sin tareas; tareas personales; varias tareas sin responsable; tareas con múltiples responsables; tareas completadas recientemente y antiguas; due_dates en pasado/presente/sin fecha.
- Varios usuarios para validar “tú” y múltiples assignees.
Plan de trabajo
1) Semilla
- apps/web/src/lib/server/dev-seed.ts: ampliar dataset con:
- 34 usuarios (incluido el por defecto).
- 45 grupos; al menos 1 sin tareas, 1 con muchas tareas, 1 mixto.
- 2540 tareas variadas (diferentes due, estados, grupos/personales).
- Relaciones de asignación múltiples en algunas tareas.
- Asegurar idempotencia y que no se sobreescriba si ya hay datos.
2) Helpers de test (si procede)
- tests/web/helpers/db.ts: exponer utilidades para crear fixtures específicas.
3) Documentación
- Añadir nota en docs/operations.md sobre cómo regenerar BD local y variables relacionadas.
Archivos a editar
- apps/web/src/lib/server/dev-seed.ts
- tests/web/helpers/db.ts (si se añaden utilidades)
- docs/operations.md (documentación de uso de seed)
Criterios de aceptación
- Entorno dev listo tras bootstrap: datos variados y suficientes para probar todas las vistas/secciones.
- Tests pueden apoyarse en fixtures reproducibles.
Resultado (implementado)
- Semilla enriquecida que crea ~3035 tareas, 5 grupos (allowed/pending), usuarios múltiples y membresías coherentes; incluye tareas personales, sin/uno/múltiples responsables y completadas recientes/antiguas con due variado.
- Idempotente: se ejecuta solo si la tabla tasks está vacía y con DEV_AUTOSEED_DB='true' en desarrollo; usa DEV_DEFAULT_USER numérico cuando está definido.
- Documentación actualizada en docs/operations.md con instrucciones para activar y regenerar la base de datos de desarrollo.
Fase 10 — Completar tarea sin responsable: auto-asignación al completador — Estado: Completada
Objetivos
- Resolver el edge case: al completar una tarea sin responsables, debe aparecer en “Completadas (24h)” del usuario y permitir “Deshacer”.
- Mantener gating y trazabilidad coherentes.
Plan de trabajo
1) Backend
- apps/web/src/routes/api/tasks/[id]/complete/+server.ts:
- Si la tarea no tiene responsables, añadir (de forma atómica) una asignación al usuario que completa antes de marcar completed=1.
- Registrar completed_by (si existe) o equivalente.
- apps/web/src/routes/api/tasks/[id]/uncomplete/+server.ts:
- Mantener ventana; permitir deshacer si el usuario es responsable (lo será por la auto-asignación) o fue quien completó.
- apps/web/src/routes/api/me/tasks/overview/+server.ts y/o consultas de “recent”:
- Asegurar que la consulta de recientes recoge estas tareas asignadas durante el complete.
2) UI
- apps/web/src/lib/ui/data/TaskItem.svelte:
- Mensaje de feedback claro al completar una tarea que no tenía responsables (p.ej., “Te has asignado y completado la tarea”).
3) Tests
- Flujo: tarea sin responsables → complete → aparece en completadas → uncomplete permitido dentro de ventana.
Archivos a editar
- apps/web/src/routes/api/tasks/[id]/complete/+server.ts
- apps/web/src/routes/api/tasks/[id]/uncomplete/+server.ts
- apps/web/src/routes/api/me/tasks/overview/+server.ts (si la consulta de recientes depende de esto)
- apps/web/src/lib/ui/data/TaskItem.svelte (feedback UI)
- tests/web/* (casos de integración)
Resultado (implementado)
- Auto-asignación atómica al completar tareas de grupo sin responsables; se registra completed_by.
- Listado “Completadas (24 h)” incluye estas tareas gracias a la auto-asignación.
- Uncomplete permitido dentro de la ventana configurada, manteniendo la asignación creada.
- Tests de integración añadidos: complete-autoassign-recent, uncomplete-window y carrera de complete (con dos usuarios).
Criterios de aceptación
- Completar una tarea sin responsables la vincula al usuario y aparece inmediatamente en “Completadas (24h)”.
- “Deshacer completar” funciona para ese caso dentro de la ventana configurada.
Notas de implementación y buenas prácticas
- Mantener cabeceras cache-control: no-store en endpoints de listas/acciones.
- Reutilizar el gating ya presente en claim/unassign/complete; factorizar si conviene (pero sin sobre-ingeniería).
- Idempotencia en endpoints de mutación (claim, unassign, complete, uncomplete).
- Evitar dependencias externas para UI; usar SVG inline o emojis como placeholder.
- Accesibilidad: aria-label en iconos, focus visible, roles correctos en popovers/diálogos.
Siguientes pasos
1) Implementar Fase 1 (UI base) con los archivos listados.
2) Implementar Fase 2 (uncomplete + PATCH gating).
3) Validar en móvil; luego abordar Fase 3 (tabs inferiores).
4) Optimizar con overview (Fase 4) y cerrar UX de responsables (Fase 5).
5) Pulido y documentación (Fase 6).
Anexo — Ajustes opcionales futuros (Fase 4)
- Parámetro include=assigned|unassigned|both en /api/me/tasks/overview (por defecto unassigned) para reducir coste cuando solo se necesite una parte.
- Paginación en overview (assigned y/o unassigned) con parámetros page/limit independientes.
- Índices de rendimiento sugeridos (si el dataset crece):
- CREATE INDEX IF NOT EXISTS idx_tasks_group ON tasks(group_id);
- CREATE INDEX IF NOT EXISTS idx_tasks_due_open ON tasks(due_date) WHERE COALESCE(completed, 0) = 0;
- Cacheabilidad opcional con ETag/If-None-Match si se añade una versión por usuario; mantener no-store por defecto.