### 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 - [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 - [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`) - [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 Registra automáticamente la frecuencia de uso de cada ítem del colectivo y la usa como sistema de sugerencias al añadir nuevos ítems. Sin IA — solo frecuencia de uso basada en registros de la base de datos. **Tabla:** ```sql CREATE TABLE item_frequency ( collective_id UUID NOT NULL REFERENCES collectives(id) ON DELETE CASCADE, name TEXT NOT NULL, -- normalized: lower(trim(name)) use_count INTEGER NOT NULL DEFAULT 1, last_used_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (collective_id, name) ); CREATE INDEX idx_item_frequency_collective_count ON item_frequency(collective_id, use_count DESC); ``` **Trigger** — se dispara en cada `INSERT` en `shopping_items`, normaliza el nombre y hace upsert: ```sql CREATE OR REPLACE FUNCTION fn_record_item_frequency() RETURNS TRIGGER AS $$ DECLARE v_collective_id UUID; BEGIN SELECT collective_id INTO v_collective_id FROM shopping_lists WHERE id = NEW.list_id; INSERT INTO item_frequency (collective_id, name, use_count, last_used_at) VALUES (v_collective_id, lower(trim(NEW.name)), 1, now()) ON CONFLICT (collective_id, name) DO UPDATE SET use_count = item_frequency.use_count + 1, last_used_at = now(); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_item_frequency AFTER INSERT ON shopping_items FOR EACH ROW EXECUTE FUNCTION fn_record_item_frequency(); ``` **Consulta de sugerencias** — llamada desde el cliente al escribir en el input de añadir ítem: ```sql -- Input vacío: top 5 más usados del colectivo (chips de acceso rápido) SELECT name, use_count FROM item_frequency WHERE collective_id = $1 ORDER BY use_count DESC LIMIT 5; -- Con prefijo: filtro por lo que el usuario va escribiendo SELECT name, use_count FROM item_frequency WHERE collective_id = $1 AND name LIKE lower(trim($2)) || '%' ORDER BY use_count DESC LIMIT 10; ``` **RLS:** los miembros del colectivo solo pueden leer las frecuencias de su colectivo. Escritura solo vía trigger (el rol de la app no tiene `INSERT`/`UPDATE` directo sobre esta tabla). **Comportamiento en UI:** - Input vacío → muestra los 5 ítems más frecuentes como chips de acceso rápido bajo el input - Usuario escribe → sustituye los chips por resultados filtrados por prefijo (máx. 10) - Tap en chip/sugerencia → rellena el input con ese nombre (el usuario puede ajustar cantidad/unidad antes de añadir) - Al confirmar el ítem, el trigger registra la frecuencia automáticamente **Tareas:** - [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) ---