### 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)

---
