test: full test suite (Vitest + pgTAP + Playwright) + TDD plan restructure
Add a 4-layer test stack covering RLS, triggers, and UI flows for Fases 0–2a, then restructure every plan file so future fases start with tests and end with a verification gate. Test suite - packages/test-utils: Vitest integration tests signing HS256 JWTs via jose so each test acts as a specific seed user (createClientAs + createAdminClient) - supabase/tests: pgTAP for accept_invitation(), item_frequency trigger, and promote-on-admin-leave; each file self-installs pgtap extension - apps/web/tests: Playwright E2E with live Keycloak login per test (storageState caching doesn't rehydrate Supabase's session state reliably) - just test-all chains the three suites; test-db forwards POSTGRES_PASSWORD as PGPASSWORD with ON_ERROR_STOP=1 so failures abort the chain Supabase auth gotcha - PostgREST queries inside onAuthStateChange deadlock on GoTrue's navigator.locks auth lock (getAccessToken → getSession → initializePromise waits on the same lock that's held during event dispatch). Fix is two defenses: a pass-through lock in $lib/supabase, and a setTimeout(0) defer in root +layout.svelte to push loadUserCollectives out of the callback's microtask chain. Either alone is insufficient; both together unblock the Playwright suite. Env key rotation - apps/web/.env.development had a stale demo anon key signed with a different secret than root .env; Vite inlined that into the browser bundle so Kong (which uses the root .env value) rejected every request with 401. Aligned the two files and added a memory entry to flag this for the next rotation. Plan restructure (TDD) - Every fase now opens with X.0 Tests primero and closes with X.Z Verificación final. Completed fases (0, 1, 2a) show the pattern retroactively with the tests that currently cover them; pending fases (2b, 3, 4) list the tests to write before implementation. Docs - CLAUDE.md status line reports 54 + 12 + 15 = 81 green tests, adds gotchas #11 (auth-lock deadlock) and #12 (no storageState caching) - README.md adds a TDD methodology section and the test-all command - .gitignore excludes Playwright's generated reports and auth state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
### Fase 0 — Infraestructura y Entornos
|
||||
### Fase 0 — Infraestructura y Entornos ✅
|
||||
**Estado: Completa**
|
||||
**Duración estimada: 1–2 semanas**
|
||||
**Objetivo: tener los dos entornos funcionando antes de escribir producto.**
|
||||
|
||||
> **Nota sobre el patrón TDD:** desde la Fase 1 en adelante, cada fase abre con una sección `X.0 Tests primero` y cierra con `X.Z Verificación final`. La Fase 0 no sigue ese patrón porque es puro bootstrapping de infraestructura; su "test" es que `just dev` arranque la stack y `just test-all` (introducido en una fase posterior) pueda ejecutarse sin errores de conexión.
|
||||
|
||||
#### 0.1 Variables de entorno
|
||||
|
||||
Todas las variables viven en `.env.dev` (dev) y `.env.production` (prod, fuera del repo). El fichero `.env.example` documenta cada variable con su propósito.
|
||||
@@ -79,7 +82,7 @@ B2_APPLICATION_KEY=...
|
||||
|
||||
#### 0.2 Entorno de desarrollo local
|
||||
|
||||
- [ ] `docker-compose.dev.yml` con todos los servicios:
|
||||
- [x] `docker-compose.dev.yml` con todos los servicios:
|
||||
- **PostgreSQL 15** — puerto `5432`
|
||||
- **Supabase Auth (GoTrue)** — configurado con Keycloak como OIDC externo
|
||||
- **Supabase Realtime** — puerto `4000`
|
||||
@@ -87,14 +90,14 @@ B2_APPLICATION_KEY=...
|
||||
- **Kong API Gateway** — puerto `54321` (entrada principal de Supabase)
|
||||
- **Supabase Studio** — puerto `54323`
|
||||
- **Keycloak 24** — puerto `8080`, arranca con `--import-realm keycloak/realm-export.json`
|
||||
- [ ] `keycloak/realm-export.json` — realm `colectivo` preconfigurado con:
|
||||
- [x] `keycloak/realm-export.json` — realm `colectivo` preconfigurado con:
|
||||
- Client `colectivo-web` (public, PKCE habilitado, redirect URIs para dev)
|
||||
- **5 usuarios de prueba** creados y listos (ver tabla abajo)
|
||||
- Roles de realm: `app-user` (asignado a todos por defecto)
|
||||
- [ ] SvelteKit dev server en puerto `5173`, apuntando a variables de `.env.dev`
|
||||
- [ ] `supabase/seed.sql` — crea los usuarios de prueba en `auth.users` con los mismos UUIDs que Keycloak, un colectivo de ejemplo, listas e ítems
|
||||
- [ ] Generación automática de tipos TypeScript: `supabase gen types typescript`
|
||||
- [ ] `Justfile` con `just dev`, `just db-reset`, `just db-types`, `just kc-export`, `just kc-open`
|
||||
- [x] SvelteKit dev server en puerto `5173`, apuntando a variables de `.env.dev`
|
||||
- [x] `supabase/seed.sql` — crea los usuarios de prueba en `auth.users` con los mismos UUIDs que Keycloak, un colectivo de ejemplo, listas e ítems
|
||||
- [x] Generación automática de tipos TypeScript: `supabase gen types typescript`
|
||||
- [x] `Justfile` con `just dev`, `just db-reset`, `just db-types`, `just kc-export`, `just kc-open`
|
||||
|
||||
**Usuarios de prueba en Keycloak (dev):**
|
||||
|
||||
@@ -112,16 +115,16 @@ B2_APPLICATION_KEY=...
|
||||
|
||||
> **El entorno de producción ya está montado y dockerizado.** Keycloak, nginx y su PostgreSQL propio ya están operativos. Esta sección cubre únicamente lo que hay que añadir.
|
||||
|
||||
- [ ] Incorporar los servicios de Supabase al `docker-compose.prod.yml` existente:
|
||||
- [x] Incorporar los servicios de Supabase al `docker-compose.prod.yml` existente:
|
||||
- PostgreSQL dedicado para Supabase — contenedor independiente, volumen propio, **no compartir con Keycloak**
|
||||
- GoTrue (Auth), Realtime, Storage, Kong en la red interna Docker existente
|
||||
- Kong expuesto en un puerto interno (ej: `127.0.0.1:54321`) — nginx hace proxy hacia él, no expuesto directamente
|
||||
- [ ] Añadir el contenedor SvelteKit al compose, escuchando en un puerto interno (ej: `127.0.0.1:3000`) — nginx hace proxy hacia él
|
||||
- [ ] Configurar nginx externamente (fuera del repo) para los nuevos servicios — ver bloques de ejemplo abajo
|
||||
- [ ] Exportar el realm `colectivo` desde dev (`just kc-export`) e importarlo en el Keycloak de producción via Admin Console — sin `--import-realm` en prod
|
||||
- [ ] `.env.production` en el VPS con todas las variables (ver sección 0.1), fuera del repo
|
||||
- [ ] Scripts de backup — ver sección **0.6 Sistema de Backups** para el detalle completo
|
||||
- [ ] Script de deploy: `ssh vps "cd /app && git pull && docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d --no-deps web"`
|
||||
- [x] Añadir el contenedor SvelteKit al compose, escuchando en un puerto interno (ej: `127.0.0.1:3000`) — nginx hace proxy hacia él
|
||||
- [x] Configurar nginx externamente (fuera del repo) para los nuevos servicios — ver bloques de ejemplo abajo
|
||||
- [x] Exportar el realm `colectivo` desde dev (`just kc-export`) e importarlo en el Keycloak de producción via Admin Console — sin `--import-realm` en prod
|
||||
- [x] `.env.production` en el VPS con todas las variables (ver sección 0.1), fuera del repo
|
||||
- [x] Scripts de backup — ver sección **0.6 Sistema de Backups** para el detalle completo
|
||||
- [x] Script de deploy: `ssh vps "cd /app && git pull && docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d --no-deps web"`
|
||||
- `--no-deps web` para actualizar solo el contenedor SvelteKit sin reiniciar Supabase ni Keycloak
|
||||
|
||||
##### Configuración nginx de referencia
|
||||
@@ -265,15 +268,15 @@ server {
|
||||
|
||||
#### 0.4 CI/CD
|
||||
|
||||
- [ ] `ci.yml`: lint + typecheck + tests unitarios en cada PR
|
||||
- [ ] `deploy.yml`: build de imagen SvelteKit → GitHub Container Registry → deploy vía SSH en merge a `main`
|
||||
- [ ] Secrets de GitHub: `SSH_KEY`, `VPS_HOST`, `GHCR_TOKEN`
|
||||
- [x] `ci.yml`: lint + typecheck + tests unitarios en cada PR
|
||||
- [x] `deploy.yml`: build de imagen SvelteKit → GitHub Container Registry → deploy vía SSH en merge a `main`
|
||||
- [x] Secrets de GitHub: `SSH_KEY`, `VPS_HOST`, `GHCR_TOKEN`
|
||||
|
||||
#### 0.5 Monorepo
|
||||
|
||||
- [ ] Turborepo configurado con pipeline `build → test → deploy`
|
||||
- [ ] `packages/types` como paquete interno
|
||||
- [ ] `apps/web` consumiendo `packages/types`
|
||||
- [x] Turborepo configurado con pipeline `build → test → deploy`
|
||||
- [x] `packages/types` como paquete interno
|
||||
- [x] `apps/web` consumiendo `packages/types`
|
||||
|
||||
#### 0.6 Sistema de Backups
|
||||
|
||||
@@ -359,13 +362,13 @@ Un solo entry en cron. El script decide internamente si es daily, weekly o month
|
||||
|
||||
##### Tareas de la Fase 0
|
||||
|
||||
- [ ] Crear volumen Docker (o directorio montado) en `BACKUP_LOCAL_DIR` con permisos correctos
|
||||
- [ ] Implementar `backup.sh` con soporte para supabase y keycloak, compresión y subida opcional a B2
|
||||
- [ ] Implementar `backup-cron.sh` con lógica de rotación y purga por tipo
|
||||
- [ ] Implementar `restore.sh` con confirmación, parada de servicios y resumen post-restauración
|
||||
- [ ] Configurar crontab en el host del VPS
|
||||
- [ ] Verificar backup completo: ejecutar `backup.sh supabase`, comprobar fichero generado, ejecutar `restore.sh` en un entorno de prueba
|
||||
- [ ] Verificar purga: crear backups de prueba con fechas antiguas, ejecutar `backup-cron.sh`, confirmar que se eliminan los que exceden la retención
|
||||
- [x] Crear volumen Docker (o directorio montado) en `BACKUP_LOCAL_DIR` con permisos correctos
|
||||
- [x] Implementar `backup.sh` con soporte para supabase y keycloak, compresión y subida opcional a B2
|
||||
- [x] Implementar `backup-cron.sh` con lógica de rotación y purga por tipo
|
||||
- [x] Implementar `restore.sh` con confirmación, parada de servicios y resumen post-restauración
|
||||
- [x] Configurar crontab en el host del VPS
|
||||
- [x] Verificar backup completo: ejecutar `backup.sh supabase`, comprobar fichero generado, ejecutar `restore.sh` en un entorno de prueba
|
||||
- [x] Verificar purga: crear backups de prueba con fechas antiguas, ejecutar `backup-cron.sh`, confirmar que se eliminan los que exceden la retención
|
||||
|
||||
**Criterio de aceptación:** `just dev` levanta todo el stack en < 3 minutos. Los 5 usuarios de prueba pueden hacer login con Keycloak y llegar a la app. Deploy a producción funciona con un `git push`. Un backup manual de supabase y keycloak genera ficheros válidos que se restauran correctamente.
|
||||
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
### Fase 1 — Autenticación y Colectivo
|
||||
### Fase 1 — Autenticación y Colectivo ✅
|
||||
**Estado: Completa** (pendiente: detección automática de idioma desde `Accept-Language`)
|
||||
**Duración estimada: 2–3 semanas**
|
||||
**Objetivo: la base del dominio. Sin esto no existe nada más.**
|
||||
|
||||
#### 1.0 Tests — escritos y verdes ✅
|
||||
|
||||
A-series (auth), B-series (colectivo), F-series (aislamiento) están cubiertas por:
|
||||
|
||||
- [x] Vitest integration (`packages/test-utils/tests/rls-{collective,isolation}.test.ts`) — lectura por rol, invitación, `accept_invitation()`, aislamiento no-miembro
|
||||
- [x] pgTAP (`supabase/tests/001_accept_invitation.sql`, `003_promote_on_admin_leave.sql`) — happy path y casos de error de `accept_invitation`, promoción automática al último admin
|
||||
- [x] Playwright E2E (`apps/web/tests/e2e/auth.test.ts`) — redirect a Keycloak, login full OAuth, ver colectivo en sidebar, sign-out, login como guest
|
||||
|
||||
#### 1.1 Base de datos
|
||||
|
||||
- [x] Migración: tabla `users` — espejo de `auth.users` de Supabase, poblada por trigger al primer login OIDC:
|
||||
@@ -84,6 +93,10 @@ Librería: **Paraglide JS** (`@inlang/paraglide-sveltekit`). Compile-time, tree-
|
||||
|
||||
> **Auto-registro:** Self-registration está habilitado en Keycloak. Un usuario sin cuenta que recibe un link de invitación puede registrarse en la pantalla de Keycloak y volver al callback de invitación automáticamente.
|
||||
|
||||
#### 1.Z Verificación final ✅
|
||||
|
||||
- [x] `just test-all` — 0 failures (los 5 usuarios hacen login, A/B/F-series verdes)
|
||||
|
||||
**Criterio de aceptación:** los 5 usuarios de prueba pueden autenticarse contra Keycloak, llegar a la app con sesión activa, y `auth.uid()` en Supabase resuelve correctamente para las políticas RLS.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
### Fase 2a — Lista de la Compra (CRUD)
|
||||
### Fase 2a — Lista de la Compra (CRUD) ✅
|
||||
**Estado: Completa**
|
||||
**Duración estimada: 2 semanas**
|
||||
**Objetivo: funcionalidad completa de listas sin sincronización en tiempo real.**
|
||||
|
||||
#### 2a.0 Tests — escritos y verdes ✅
|
||||
|
||||
C-series (listas) y D-series (ítems) están cubiertas por:
|
||||
|
||||
- [x] Vitest integration (`packages/test-utils/tests/rls-{lists,items,frequency}.test.ts`) — RLS por rol, aislamiento cross-collective, trigger de frecuencia
|
||||
- [x] pgTAP (`supabase/tests/002_item_frequency_trigger.sql`) — trigger de frecuencia a nivel SQL
|
||||
- [x] Playwright E2E (`apps/web/tests/e2e/{lists,items}.test.ts`) — flujo UI completo: crear lista + reload persiste, papelera, archive, añadir ítem + reload persiste, check, borrado desktop, chips de sugerencias, lectura-solo para invitados
|
||||
|
||||
#### 2a.1 Base de datos
|
||||
|
||||
- [ ] Migración: tabla `shopping_lists` con `status enum(active | completed | archived)`
|
||||
- [ ] Migración: tabla `shopping_items` con `name`, `quantity`, `unit`, `is_checked`, `checked_by`, `sort_order`
|
||||
- [ ] RLS: solo miembros del colectivo pueden leer/escribir listas e ítems
|
||||
- [ ] Índice en `shopping_items.list_id` y `shopping_items.sort_order`
|
||||
- [ ] Job `pg_cron`: cada noche, mueve a `archived` las listas completadas hace > 7 días en estado borrado
|
||||
- [ ] Migración: tabla `item_frequency` y trigger de recomendaciones (ver sección 2a.3)
|
||||
- [x] Migración: tabla `shopping_lists` con `status enum(active | completed | archived)`
|
||||
- [x] Migración: tabla `shopping_items` con `name`, `quantity`, `unit`, `is_checked`, `checked_by`, `sort_order`
|
||||
- [x] RLS: solo miembros del colectivo pueden leer/escribir listas e ítems
|
||||
- [x] Índice en `shopping_items.list_id` y `shopping_items.sort_order`
|
||||
- [x] Job `pg_cron`: cada noche, mueve a `archived` las listas completadas hace > 7 días en estado borrado
|
||||
- [x] Migración: tabla `item_frequency` y trigger de recomendaciones (ver sección 2a.3)
|
||||
|
||||
#### 2a.2 Frontend
|
||||
|
||||
- [ ] Pantalla `/lists` — listado de listas activas del colectivo
|
||||
- [ ] Pantalla `/lists/[id]` — vista normal de lista:
|
||||
- [x] Pantalla `/lists` — listado de listas activas del colectivo
|
||||
- [x] Pantalla `/lists/[id]` — vista normal de lista:
|
||||
- Añadir ítem (input rápido: name + quantity + unit) con recomendaciones de ítems frecuentes
|
||||
- Editar ítem inline
|
||||
- Eliminar ítem con swipe (confirmación)
|
||||
- Reordenar con drag & drop (`svelte-dnd-action`)
|
||||
- [ ] Papelera de listas: retención 7 días, restauración, borrado definitivo
|
||||
- [ ] Store Svelte `lists` — listas del colectivo activo, optimistic updates
|
||||
- [x] Papelera de listas: retención 7 días, restauración, borrado definitivo
|
||||
- [x] Store Svelte `lists` — listas del colectivo activo, optimistic updates
|
||||
|
||||
#### 2a.3 Recomendaciones de ítems frecuentes
|
||||
|
||||
@@ -95,12 +104,16 @@ LIMIT 10;
|
||||
- Al confirmar el ítem, el trigger registra la frecuencia automáticamente
|
||||
|
||||
**Tareas:**
|
||||
- [ ] Migración: tabla `item_frequency` con índice
|
||||
- [ ] Función `fn_record_item_frequency` + trigger `trg_item_frequency`
|
||||
- [ ] RLS en `item_frequency`
|
||||
- [ ] Componente `ItemSuggestions.svelte` — chips + lista filtrada
|
||||
- [ ] Integrar `ItemSuggestions` en el input de añadir ítem de `/lists/[id]`
|
||||
- [x] Migración: tabla `item_frequency` con índice
|
||||
- [x] Función `fn_record_item_frequency` + trigger `trg_item_frequency`
|
||||
- [x] RLS en `item_frequency`
|
||||
- [x] Componente `ItemSuggestions.svelte` — chips + lista filtrada
|
||||
- [x] Integrar `ItemSuggestions` en el input de añadir ítem de `/lists/[id]`
|
||||
|
||||
**Criterio de aceptación:** crear, editar, reordenar y eliminar ítems funciona fluidamente. Al añadir un ítem, aparecen sugerencias basadas en el historial del colectivo; añadir un ítem actualiza su frecuencia automáticamente.
|
||||
|
||||
#### 2a.Z Verificación final ✅
|
||||
|
||||
- [x] `just test-all` — 0 failures (81 tests verdes en total, de los cuales la cobertura de 2a se detalla en 2a.0)
|
||||
|
||||
---
|
||||
|
||||
@@ -1,10 +1,37 @@
|
||||
### 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 `items_compra` filtrada por `lista_id`
|
||||
- [ ] 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)
|
||||
@@ -14,7 +41,7 @@
|
||||
#### 2b.2 Offline-First
|
||||
|
||||
- [ ] Instalación y configuración de `idb` (wrapper de IndexedDB)
|
||||
- [ ] Esquema local en IndexedDB: `pending_ops`, `listas_cache`, `items_cache`
|
||||
- [ ] 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).
|
||||
@@ -23,20 +50,25 @@
|
||||
- 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: `sincronizado | sincronizando | sin conexión`
|
||||
- [ ] Indicador de estado de sincronización en la UI: `synced | syncing | offline`
|
||||
|
||||
#### 2b.3 Modo Compra
|
||||
|
||||
- [ ] Ruta `/listas/[id]/compra` — UI dedicada al 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 **"Finalizar compra"** — confirmación modal → lista pasa a `completada`
|
||||
- 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)
|
||||
- [ ] Prueba de sincronización: dos dispositivos, misma lista, cambios visibles en < 1 segundo
|
||||
|
||||
**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.
|
||||
#### 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`.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,14 +1,43 @@
|
||||
### Fase 3 — Tareas y Notas
|
||||
**Estado: ⏳ Pendiente**
|
||||
**Duración estimada: 1–2 semanas**
|
||||
**Objetivo: completar los módulos secundarios.**
|
||||
|
||||
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`).
|
||||
|
||||
#### 3.0 Tests primero — escribir antes de implementar
|
||||
|
||||
**Vitest (`packages/test-utils/tests/`):**
|
||||
|
||||
- [ ] `rls-tasks.test.ts` — T-series:
|
||||
- T-01/T-02: miembro y admin pueden CRUD en `tasks`; guest solo lee; Eva (no miembro) ve 0 filas
|
||||
- T-03: `completed_by` y `completed_at` se fijan al marcar; se limpian al desmarcar
|
||||
- T-04: aislamiento cross-collective (Ana no ve tareas del colectivo de Eva)
|
||||
- [ ] `rls-notes.test.ts` — N-series:
|
||||
- N-01/N-02: CRUD por rol en `notes`
|
||||
- N-03: notas archivadas siguen siendo legibles; las `deleted_at` pasan a papelera
|
||||
- N-04: aislamiento cross-collective
|
||||
|
||||
**pgTAP (`supabase/tests/`):**
|
||||
|
||||
- [ ] `005_tasks_rls.sql` — invariantes SQL de `tasks` (`completed_at NOT NULL ⇔ completed_by NOT NULL`)
|
||||
- [ ] `006_notes_trash_purge.sql` — simular `pg_cron` job: insertar nota con `deleted_at = now() - interval '8 days'` → ejecutar el SQL del job → la nota ya no existe
|
||||
|
||||
**Playwright (`apps/web/tests/e2e/`):**
|
||||
|
||||
- [ ] `tasks.test.ts` — T-01 UI: crear tarea, marcar completada (muestra tooltip "Completada por …"), editar inline, eliminar, reordenar (drag & drop)
|
||||
- [ ] `tasks.test.ts` — T-02 UI: guest (David) ve tareas pero no puede crear
|
||||
- [ ] `notes.test.ts` — N-01 UI: crear nota, cambiar color, pin/unpin, archivar, enviar a papelera, restaurar
|
||||
- [ ] `notes.test.ts` — N-02 UI: editor Markdown básico (bold/italic), autosave tras 500ms
|
||||
- [ ] `notes.test.ts` — N-03 UI: guest ve notas pero no puede editar
|
||||
|
||||
#### 3.1 Tareas
|
||||
|
||||
- [ ] Migración: tablas `listas_tareas` y `tareas` con RLS
|
||||
- [ ] Pantalla `/tareas` — listado de listas de tareas
|
||||
- [ ] Migración: tablas `task_lists` y `tasks` con RLS
|
||||
- [ ] Pantalla `/tasks` — listado de listas de tareas
|
||||
- [ ] Vista de lista de tareas:
|
||||
- Añadir tarea (input rápido)
|
||||
- Checkbox de completado (registra `completada_por` + `completada_en`)
|
||||
- Checkbox de completado (registra `completed_by` + `completed_at`)
|
||||
- Tooltip "Completada por [Nombre] el [fecha]"
|
||||
- Edición inline del título
|
||||
- Swipe para eliminar
|
||||
@@ -18,17 +47,23 @@
|
||||
|
||||
#### 3.2 Notas
|
||||
|
||||
- [ ] Migración: tabla `notas` con `color`, `fijada`, `archivada`, `eliminada`, `eliminada_en` + RLS
|
||||
- [ ] Job `pg_cron`: borrado definitivo de notas en papelera con `eliminada_en` > 7 días
|
||||
- [ ] Pantalla `/notas` — tablero tipo masonry:
|
||||
- [ ] Migración: tabla `notes` con `color`, `is_pinned`, `is_archived`, `deleted_at` + RLS
|
||||
- [ ] Job `pg_cron`: borrado definitivo de notas en papelera con `deleted_at` > 7 días
|
||||
- [ ] Pantalla `/notes` — tablero tipo masonry:
|
||||
- Notas fijadas en bloque superior
|
||||
- Notas activas en rejilla de colores
|
||||
- [ ] Editor de nota (`/notas/[id]`):
|
||||
- [ ] Editor de nota (`/notes/[id]`):
|
||||
- Editor de texto con Markdown básico (negrita, cursiva, listas, cabeceras)
|
||||
- Guardado automático con debounce de 500ms
|
||||
- Selector de color (8 opciones predefinidas)
|
||||
- Opciones: fijar, archivar, mover a papelera, duplicar
|
||||
- [ ] Archivo y papelera accesibles desde `/notas/archivo` y `/notas/papelera`
|
||||
- [ ] Archivo y papelera accesibles desde `/notes/archive` y `/notes/trash`
|
||||
- [ ] Sincronización vía polling a intervalos de 30 segundos
|
||||
|
||||
#### 3.Z Verificación final — todos los tests verdes
|
||||
|
||||
- [ ] `just test-all` → 0 failures, incluyendo los nuevos de 3.0
|
||||
|
||||
**Criterio de aceptación:** CRUD completo de tareas y notas por UI con cobertura E2E en Playwright y RLS en Vitest/pgTAP. Papelera y archive operativos. Toda la suite pasa con `just test-all`.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,13 +1,45 @@
|
||||
### Fase 4 — Búsqueda y Pulido Final
|
||||
**Estado: ⏳ Pendiente**
|
||||
**Duración estimada: 1 semana**
|
||||
**Objetivo: completar el MVP con calidad de producción.**
|
||||
|
||||
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`).
|
||||
|
||||
#### 4.0 Tests primero — escribir antes de implementar
|
||||
|
||||
**Vitest (`packages/test-utils/tests/`):**
|
||||
|
||||
- [ ] `search.test.ts` — S-series:
|
||||
- S-01: `search_in_collective(col, 'mil')` devuelve ítems con "milk" del colectivo
|
||||
- S-02: respeta aislamiento cross-collective (Eva no ve nada del colectivo de Ana)
|
||||
- S-03: excluye papelera (RN-08) — crear ítem, enviarlo a trash (o el contenedor correspondiente), verificar que no aparece en búsqueda
|
||||
- S-04: filtros por tipo (lista/tarea/nota), por creador, por rango de fechas
|
||||
|
||||
**pgTAP (`supabase/tests/`):**
|
||||
|
||||
- [ ] `007_search_tsvector.sql` — trigger de mantenimiento de `tsvector`: INSERT/UPDATE dispara la actualización de la columna; índice GIN se usa (verificar con `EXPLAIN`)
|
||||
- [ ] `008_rls_audit.sql` — tests exhaustivos por cada tabla y rol (admin/member/guest/non-member) × (SELECT/INSERT/UPDATE/DELETE) para todas las tablas de dominio
|
||||
|
||||
**Playwright (`apps/web/tests/e2e/`):**
|
||||
|
||||
- [ ] `search.test.ts` — UI: input con debounce 300ms; resultados agrupados por tipo; filtros
|
||||
- [ ] `profile.test.ts` — abandonar colectivo, eliminar cuenta (con confirmación)
|
||||
- [ ] `pwa.test.ts` — manifest.webmanifest accesible, service worker registrado tras login
|
||||
- [ ] `rate-limit.test.ts` — disparar más peticiones de auth que el rate limit configurado en Kong; verificar 429
|
||||
|
||||
**Manual / herramientas externas:**
|
||||
|
||||
- [ ] Lighthouse PWA score ≥ 90 en mobile (ejecutar en un build de producción)
|
||||
- [ ] Instalable en iOS (Safari → Añadir a pantalla de inicio) y Android (Chrome → Instalar app)
|
||||
- [ ] Notificaciones push configuradas para iOS 16.4+ (requiere PWA instalada)
|
||||
- [ ] Offline completo verificado: desconectar red, operar en todos los módulos, reconectar y verificar sync
|
||||
|
||||
#### 4.1 Búsqueda global
|
||||
|
||||
- [ ] Función PostgreSQL `buscar_en_colectivo(colectivo_id, query, filtros)` usando `tsvector` + `tsquery`
|
||||
- [ ] Columnas `tsvector` en `items_compra`, `tareas`, `notas` con índice GIN
|
||||
- [ ] Función PostgreSQL `search_in_collective(collective_id, query, filters)` usando `tsvector` + `tsquery`
|
||||
- [ ] Columnas `tsvector` en `shopping_items`, `tasks`, `notes` con índice GIN
|
||||
- [ ] Trigger para mantener los vectores actualizados en cada INSERT/UPDATE
|
||||
- [ ] Pantalla `/buscar`:
|
||||
- [ ] Pantalla `/search`:
|
||||
- Input de búsqueda con debounce 300ms
|
||||
- Resultados agrupados por tipo (listas, tareas, notas)
|
||||
- Filtros: tipo de contenido, miembro creador, rango de fechas, estado
|
||||
@@ -15,7 +47,7 @@
|
||||
|
||||
#### 4.2 Perfil de usuario
|
||||
|
||||
- [ ] Pantalla `/perfil`:
|
||||
- [ ] Pantalla `/profile`:
|
||||
- Editar nombre y avatar (Supabase Storage)
|
||||
- Lista de colectivos a los que pertenece con rol
|
||||
- Opción de abandonar colectivo
|
||||
@@ -23,14 +55,25 @@
|
||||
|
||||
#### 4.3 PWA — Validación final
|
||||
|
||||
- [ ] Lighthouse PWA score ≥ 90 en mobile
|
||||
- [ ] Instalable en iOS (Safari → Añadir a pantalla de inicio) y Android (Chrome → Instalar app)
|
||||
- [ ] Notificaciones push configuradas para iOS 16.4+ (requiere PWA instalada)
|
||||
- [ ] Offline completo verificado: desconectar red, operar en todos los módulos, reconectar y verificar sync
|
||||
Implementación que necesitan los tests manuales de la sección 4.0:
|
||||
|
||||
- [ ] Configurar `manifest.webmanifest` con iconos (512, 192, 180), theme-color, display: standalone
|
||||
- [ ] Cambiar estrategia del Service Worker a `injectManifest` con fichero renombrado a `src/sw.ts` (ver gotcha #10 en CLAUDE.md)
|
||||
- [ ] Implementar notificaciones push con suscripción en el perfil
|
||||
|
||||
#### 4.4 Seguridad y hardening
|
||||
|
||||
- [ ] Auditoría de políticas RLS: verificar aislamiento total entre colectivos con tests de integración
|
||||
- [ ] Auditoría de políticas RLS: verificar aislamiento total entre colectivos con tests de integración (test `008_rls_audit.sql` de 4.0)
|
||||
- [ ] Rate limiting en Kong para endpoints de auth y invitaciones
|
||||
- [ ] Headers de seguridad en nginx: CSP, HSTS, X-Frame-Options — gestionados externamente en la config de nginx del VPS
|
||||
- [ ] Rotación de `JWT_SECRET` y `ANON_KEY` para producción (no usar los valores por defecto de Supabase)
|
||||
- [ ] **Recordatorio:** al rotar `JWT_SECRET` hay que actualizar `root/.env` Y `apps/web/.env.development` a la vez, luego `docker compose up -d --force-recreate` + reiniciar Vite (ver `project_env_keys_trap` en la memoria)
|
||||
|
||||
#### 4.Z Verificación final — todos los tests verdes
|
||||
|
||||
- [ ] `just test-all` → 0 failures con los nuevos de 4.0
|
||||
- [ ] Lighthouse PWA score ≥ 90 (manual)
|
||||
- [ ] Prueba de instalación PWA en un iPhone y un Android reales
|
||||
- [ ] Prueba de aislamiento RLS manual: login como Eva, intentar leer datos de otros colectivos vía curl con token — 0 filas
|
||||
|
||||
**Criterio de aceptación:** búsqueda global por UI en < 300ms, PWA instalable y funcional offline, aislamiento RLS auditado, `just test-all` verde.
|
||||
|
||||
Reference in New Issue
Block a user