|  |  | # 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 16–18px) y gaps a 6–8px.
 | 
						
						
						
							|  |  | 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
 | 
						
						
						
							|  |  | 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:
 | 
						
						
						
							|  |  |      - 3–4 usuarios (incluido el por defecto).
 | 
						
						
						
							|  |  |      - 4–5 grupos; al menos 1 sin tareas, 1 con muchas tareas, 1 mixto.
 | 
						
						
						
							|  |  |      - 25–40 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.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Fase 10 — Completar tarea sin responsable: auto-asignación al completador
 | 
						
						
						
							|  |  | 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)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 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.
 |