### Fase 4 — Búsqueda y Pulido Final **Estado: 🟡 Búsqueda + RLS audit completos. PWA validation / push / Kong rate-limit / JWT rotation diferidos (requieren hardware real o config prod).** **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/`):** - [x] `search.test.ts` — 10 tests: S-01 ítems / tareas / notas por texto; S-02 aislamiento cross-collective (Eva 0 filas, collective_id desconocido 0 filas); S-03 papelera excluida (RN-08); S-04 filtros por tipo, creador y rango de fechas ✅ - [x] `rls-audit.test.ts` — 42 tests: 3 intrusos (Ana/Borja/David) × 14 operaciones (SELECT/INSERT/UPDATE/DELETE sobre collectives, shopping_lists, shopping_items, task_lists, tasks, notes) → 0 filas ni mutaciones a través de colectivos ✅ **pgTAP (`supabase/tests/`):** - [x] `007_search_tsvector.sql` — 9 tests: columnas `search` en shopping_items/tasks/notes, índices GIN creados, función `search_in_collective` existe, invariante funcional (vector generado refleja el texto insertado y matchea su `tsquery`) ✅ - [~] `008_rls_audit.sql` — sustituido por `rls-audit.test.ts` (Vitest) para evitar duplicar la matriz en SQL; la auditoría cross-collective se hace en TypeScript donde es trivial parametrizar por usuario+operación **Playwright (`apps/web/tests/e2e/`):** - [x] `search.test.ts` — SR-01 (buscar "milk" → grupo shopping_item con resultado), SR-02 (desactivar filtro "Listas" → grupo shopping_item desaparece) ✅ - [ ] `profile.test.ts` — diferido: `/profile` no implementado en este sprint (ver 4.2) - [ ] `pwa.test.ts` — diferido a validación manual con `lighthouse-ci` - [ ] `rate-limit.test.ts` — diferido a deploy de producción (Kong rate-limit plugin) **Manual / herramientas externas:** - [ ] Lighthouse PWA score ≥ 90 en mobile — pendiente (requiere build de producción) - [ ] Instalable en iOS + Android — pendiente (requiere dispositivos reales) - [ ] Notificaciones push iOS 16.4+ — diferido - [x] Offline completo — cubierto por `O-01`/`O-02` en Playwright desde Fase 2b #### 4.1 Búsqueda global - [x] Migración `010_search_vectors.sql`: - Columnas STORED GENERATED `search tsvector` en `shopping_items`, `tasks`, `notes` (config `simple` — bilingüe en/es sin estemizar) - Índices GIN en cada una (`shopping_items_search_idx`, `tasks_search_idx`, `notes_search_idx`) - Enum `search_result_type` + función `search_in_collective(p_collective_id, p_query, p_types, p_creator, p_from, p_to)` con `SECURITY INVOKER` (RLS aplica automáticamente) - Trash excluido vía `shopping_lists.deleted_at IS NULL` y `notes.deleted_at IS NULL` - `GRANT EXECUTE` a `anon` + `authenticated` - [x] Store `apps/web/src/lib/stores/search.ts` — wrapper RPC con tipado `SearchRow` - [x] Pantalla `/search`: - Input con debounce de 300ms + filtrado mínimo de 2 caracteres - Filtros tipo (chips multi-toggle: Lists, Tasks, Notes — si todos activos no se envían, optimiza el SQL) - Resultados agrupados por tipo con `data-testid="search-group-"` para Playwright - Deep-link según tipo (shopping_item → `/lists/[list_id]`, task → `/tasks/[list_id]`, note → `/notes/[id]`) - Filtros por creador y rango de fechas: soportados por la RPC; pendiente exponer en la UI cuando se necesite #### 4.2 Perfil de usuario - [ ] Pantalla `/profile` — **diferido**. La mayoría de la funcionalidad ya existe en `/settings` (nombre, avatar, idioma). Abandonar colectivo y eliminar cuenta no se han priorizado; los triggers SQL relevantes (promoción automática del admin más antiguo) ya están implementados y testeados desde Fase 1. #### 4.3 PWA — Validación final **Diferido a un sprint posterior** (requiere hardware y build de producción): - [ ] `manifest.webmanifest` con iconos (512, 192, 180), theme-color, display: standalone - [ ] Migrar Service Worker a `injectManifest` con `src/sw.ts` (ver gotcha #10 en CLAUDE.md) - [ ] Notificaciones push con suscripción en el perfil El PWA con `generateSW` de la Fase 2b cubre el caso MVP (precaching básico + offline para listas de compra). #### 4.4 Seguridad y hardening - [x] Auditoría de políticas RLS — `rls-audit.test.ts` (42 tests Vitest) prueba aislamiento cross-collective exhaustivo - [ ] Rate limiting en Kong — diferido a deploy de producción - [ ] Headers de seguridad en nginx — gestionado externamente en la config de nginx del VPS - [ ] Rotación de `JWT_SECRET` y `ANON_KEY` para producción — diferido a deploy - [ ] **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 - [x] `just test-all` → **211 verdes, 2 skipped** (34 pgTAP + 140 Vitest integration + 6 unit + 31 Playwright). Los 2 skipped siguen siendo los tests de presence bloqueados por bug upstream de `supabase/realtime`. - [ ] Lighthouse PWA score ≥ 90 — manual, diferido - [ ] Prueba de instalación PWA en iPhone + Android — manual, diferido - [x] Aislamiento RLS auditado automáticamente — `rls-audit.test.ts` reemplaza la prueba manual con curl **Criterio de aceptación:** búsqueda global por UI funcional, RLS auditado, `just test-all` verde. **Alcance de producción** (PWA install, push, rate-limit, JWT rotation) queda explícitamente marcado como diferido para un sprint de deploy.