### Fase 2b — Sincronización en Tiempo Real y Modo Compra **Estado: ⏳ Pendiente** **Duración estimada: 2–3 semanas** **Objetivo: el diferencial del producto. Es la fase más compleja técnicamente.** Esta fase sigue TDD: **primero los tests, al final la verificación.** Ninguna tarea de implementación se da por hecha hasta que todos los tests pasan (`just test-all`). #### 2b.0 Tests primero — escribir antes de implementar **Vitest (`packages/test-utils/tests/`):** - [ ] `realtime-presence.test.ts` — dos clientes (Ana + Borja) entran en la misma lista; ambos aparecen en el canal Presence; salir del canal elimina al usuario - [ ] `realtime-postgres-changes.test.ts` — Ana hace INSERT/UPDATE/DELETE en `shopping_items`; Borja (suscrito al canal) recibe el evento en < 1s - [ ] `realtime-isolation.test.ts` — Ana y David (guest del mismo colectivo) están suscritos a la misma lista; Eva (fuera del colectivo) NO recibe nada aunque intente suscribirse (RLS en `supabase_realtime.broadcast`) - [ ] `sync-queue.test.ts` (unit, con mock de `idb`) — cada mutación escribe en `pending_ops` antes de llamar a Supabase; al recibir la confirmación se elimina de la cola; si falla se mantiene para reintento - [ ] `sync-flush.test.ts` (unit) — al disparar el evento `online` se flushea la cola respetando el orden original; colisiones resueltas con last-write-wins **Playwright (`apps/web/tests/e2e/`):** - [ ] `session.test.ts` — S-01: abrir Modo Compra en `/lists/[id]/session` muestra el layout full-screen sin sidebar - [ ] `session.test.ts` — S-02: marcar un ítem lo mueve a la sección CHECKED con animación (no se duplica) - [ ] `session.test.ts` — S-03: botón "Finish shopping" → confirmación modal → lista pasa a `completed`, redirige a `/lists` - [ ] `realtime.test.ts` — R-01 (dos contextos de navegador): Ana crea ítem, Borja lo ve sin refrescar la página - [ ] `realtime.test.ts` — R-02: Ana marca ítem, avatar de Borja aparece en el indicador de Presence - [ ] `offline.test.ts` — O-01: con `context.setOffline(true)`, añadir/marcar ítems sigue funcionando localmente y aparece el banner "sin conexión" - [ ] `offline.test.ts` — O-02: al volver online (`setOffline(false)`), los cambios pendientes se sincronizan y el banner desaparece **pgTAP (`supabase/tests/`):** - [ ] `004_realtime_rls.sql` — verificar que `realtime.messages` respeta las políticas RLS heredadas de `shopping_items` (un miembro sólo ve cambios de sus colectivos) #### 2b.1 Sincronización Realtime - [ ] Suscripción a `Postgres Changes` de Supabase Realtime sobre `shopping_items` filtrada por `list_id` - [ ] Suscripción a `Presence` de Supabase Realtime para indicador de miembros presentes en lista - [ ] Store Svelte `realtimeSync`: - Gestiona el ciclo de vida de las suscripciones (subscribe/unsubscribe al entrar/salir de la lista) - Aplica cambios remotos al estado local sin causar bucles - Detecta conflicto last-write-wins: si el `updated_at` remoto > local, aplica remoto y muestra aviso visual #### 2b.2 Offline-First - [ ] Instalación y configuración de `idb` (wrapper de IndexedDB) - [ ] Esquema local en IndexedDB: `pending_ops`, `lists_cache`, `items_cache` - [ ] Cola de operaciones pendientes: cada mutación se escribe en `pending_ops` antes de llamar a Supabase - [ ] Service Worker (`@vite-pwa/sveltekit` + Workbox): - **NOTA TÉCNICA:** SvelteKit intercepta `src/service-worker.ts` y bloquea imports externos (solo permite `$service-worker` y `$env/static/public`). Para usar Workbox con `injectManifest` hay que nombrar el fichero `src/sw.ts` (SvelteKit no lo reconoce como SW) y configurar `strategies: 'injectManifest'`, `filename: 'sw.ts'` en `vite.config.ts`. Actualmente el plugin usa `generateSW` (SW auto-generado, precaching básico). - Cache-first para assets estáticos - Network-first con fallback a caché para rutas de la app - Background sync para flush de `pending_ops` al recuperar conexión - Fallback manual `online` event (Safari no soporta Background Sync API) - [ ] Fallback manual de sync: al detectar `online` en el evento del navegador, flush de la cola (cubre Safari que no soporta Background Sync API) - [ ] Indicador de estado de sincronización en la UI: `synced | syncing | offline` #### 2b.3 Modo Compra - [ ] Ruta `/lists/[id]/session` — UI dedicada al Modo Compra: - Ítems en formato grande (mínimo 64px de altura), optimizado para pulsar con el pulgar - Reordenamiento automático al marcar: pendientes arriba, marcados abajo (animación fluida con `flip`) - Indicador de presencia: avatares de miembros que tienen la lista abierta simultáneamente - Banner `sin conexión` cuando no hay red (los cambios se guardan en local) - Botón prominente **"Finish shopping"** — confirmación modal → lista pasa a `completed` - Notificación push (si el usuario ha dado permiso) a otros miembros al finalizar - [ ] Rendimiento: 60fps en gama media, tap < 100ms (validar en Chrome DevTools con throttling) #### 2b.Z Verificación final — todos los tests verdes - [ ] `just test-all` → 0 failures, incluyendo los nuevos de 2b.0 - [ ] Prueba manual de sincronización: dos dispositivos, misma lista, cambios visibles en < 1 segundo - [ ] Prueba manual de offline: desconectar red, mutar, reconectar, verificar sync **Criterio de aceptación:** dos personas en el mismo supermercado, con cobertura inestable, pueden marcar ítems desde sus móviles y verse los cambios mutuamente en tiempo real. Si se pierde la conexión, los cambios locales se sincronizan al recuperarla. Toda la suite pasa con `just test-all`. ---