Files
collective-lists/plan/fase-2b-realtime-modo-compra.md
Oier Bravo Urtasun 4c913cf495 feat(realtime): Fase 2b.0 infra + first R-series tests green
Enable postgres_changes broadcasts on shopping_items / shopping_lists and prove
the path end-to-end with Vitest Realtime tests and a pgTAP configuration check.

Infra changes that were silently blocking events
- Migration 007: shopping_items + shopping_lists added to supabase_realtime
  publication with REPLICA IDENTITY FULL (UPDATE/DELETE payloads need the full
  row so list_id-based filters work and RLS can evaluate DELETE against OLD)
- Realtime service: DB_USER=supabase_admin (superuser; supabase_replication_admin
  lacks CREATE on the `realtime` schema that the service auto-creates per tenant)
- Realtime service: SELF_HOST_TENANT_NAME=realtime so the seed tenant name
  matches the default supabase-js resolves for localhost URLs (was realtime-dev
  → TenantNotFound)

Tests
- pgTAP 004_realtime_publication.sql — P-01..P-04: publication membership and
  REPLICA IDENTITY FULL for both shopping tables
- Vitest realtime-postgres-changes.test.ts — R-01 INSERT broadcast,
  R-02 UPDATE carries full row, R-03 list_id filter isolates events
- test-utils realtime-helpers.ts — subscribePostgresChanges() returns a
  waitFor(predicate, timeout) helper that captures the SUBSCRIBED handshake
  before returning so mutations issued right after are not lost
- createClientAs now calls realtime.setAuth(token) so the server evaluates
  RLS against the test user's JWT
- Justfile test-db now globs supabase/tests/*.sql so new pgTAP files are picked
  up automatically

Totals: 54→57 Vitest, 12→16 pgTAP, 15 Playwright = 88 tests green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 02:33:40 +02:00

5.6 KiB
Raw Blame History

Fase 2b — Sincronización en Tiempo Real y Modo Compra

Estado: 🚧 En curso — tests 2b.0 verdes (Realtime infra lista) Duración estimada: 23 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

Prep infra (previa a los tests):

  • Migración 007_realtime_publication.sql — añade shopping_items y shopping_lists a la publicación supabase_realtime; pone REPLICA IDENTITY FULL en ambas
  • Docker Compose: DB_USER: supabase_admin (superuser) + SELF_HOST_TENANT_NAME: realtime para Realtime (ver memoria project_realtime_config)

Vitest (packages/test-utils/tests/):

  • realtime-postgres-changes.test.ts — R-01 INSERT broadcast, R-02 UPDATE con fila completa, R-03 filtro list_id aísla eventos de otras listas
  • 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-isolation.test.ts — Ana y David (guest del mismo colectivo) reciben eventos; Eva (fuera del colectivo) NO recibe nada (RLS)
  • 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 — dos contextos de navegador: Ana crea ítem, Borja lo ve sin refrescar; Ana marca ítem, el avatar de Borja aparece en Presence
  • offline.test.ts — O-01 con context.setOffline(true): mutaciones locales + banner "sin conexión"; O-02 al volver online: sync pendiente + banner desaparece

pgTAP (supabase/tests/):

  • 004_realtime_publication.sql — P-01..P-04: publication + REPLICA IDENTITY FULL asegurados

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.