# Sprint 4 — Etapa 2: Schema PostgreSQL + migración de repositorios (procesos + grupos + settings)

## Contexto

InQ ROI es una herramienta web de análisis de procesos BPMN y ROI de automatización.
Stack: React 18 + Vite + TypeScript + TanStack Router + Zustand + shadcn/ui + Tailwind.
Etapa 1 (completada): auth con Google, rutas protegidas, tabla `users` en Supabase, `AuthService` abstraction.

Esta etapa migra la persistencia de **procesos, grupos de biblioteca y settings** de IndexedDB/Dexie a Supabase PostgreSQL.
Las actividades, gateways, recursos y simulaciones siguen en Dexie hasta Etapas 3-4.

---

## Prerequisitos — verificar antes de empezar

- `.env.local` tiene `VITE_SUPABASE_URL` y `VITE_SUPABASE_ANON_KEY`
- El script `supabase/migrations/001_create_users.sql` ya fue ejecutado en Supabase
- `src/auth/AuthContext.tsx` y `useAuth()` hook existen y funcionan

---

## Alcance estricto — qué entra en esta etapa

1. **Scripts SQL** para `processes`, `process_groups`, `app_settings` en `supabase/migrations/`
2. **Nuevos repositorios Supabase** para procesos, grupos y settings (reemplazando las implementaciones Dexie de esas entidades)
3. **Actualizar los stores** (`useProcessStore`, `useLibraryStore`) para pasar `userId` a los repos en operaciones de escritura
4. **Limpieza de estado híbrido**: el `delete` de proceso sigue limpiando Dexie (activities, gateways, resources, simulations) hasta que se migren en Etapa 3-4

## Qué NO entra en esta etapa

- Migración de actividades, gateways, resources, simulations → Etapa 3
- Tabla `user_groups` y `group_memberships` (acceso a usuarios) → Etapa 5
- Tabla `process_access` → Etapa 5
- UI de visibilidad privado/compartido → Etapa 5
- Avatar/nombre en AppHeader → Etapa 6
- Eliminar Dexie del proyecto → Etapa 4 (cuando todo esté migrado)
- Cambiar el tipo TypeScript `Process.clientName` — NO modificar `src/domain/types.ts`

---

## Distinción importante: dos tipos de grupos

**`process_groups`** (esta etapa): grupos para organizar la biblioteca de procesos — "Cliente X", "Sin clasificar". Corresponde a `ProcessGroup` en `src/domain/types.ts`. Son compartidos por todo el equipo, sin control de acceso propio.

**`user_groups`** (Etapa 5): grupos de usuarios para control de acceso — "Equipo de consultoría", "Cliente Acme". Concepto nuevo, no existe en Dexie. No crear en esta etapa.

---

## Scripts SQL — crear en `supabase/migrations/`

### `002_create_process_groups.sql`

```sql
CREATE TABLE IF NOT EXISTS public.process_groups (
  id          uuid         PRIMARY KEY DEFAULT gen_random_uuid(),
  name        text         NOT NULL,
  created_by  uuid         REFERENCES public.users(id),
  created_at  timestamptz  NOT NULL DEFAULT now(),
  updated_at  timestamptz  NOT NULL DEFAULT now()
);

ALTER TABLE public.process_groups ENABLE ROW LEVEL SECURITY;

-- Todos los autenticados pueden leer y escribir grupos (son recursos compartidos del equipo)
CREATE POLICY "all_process_groups" ON public.process_groups
  FOR ALL USING (auth.uid() IS NOT NULL)
  WITH CHECK (auth.uid() IS NOT NULL);
```

### `003_create_processes.sql`

```sql
CREATE TABLE IF NOT EXISTS public.processes (
  id                      uuid         PRIMARY KEY DEFAULT gen_random_uuid(),
  name                    text         NOT NULL,
  client                  text         NOT NULL DEFAULT '',
  bpmn_xml                text         NOT NULL DEFAULT '',
  currency                text         NOT NULL DEFAULT 'USD',
  overhead_percentage     numeric      NOT NULL DEFAULT 0,
  annual_frequency        numeric      NOT NULL DEFAULT 1000,
  analysis_horizon_years  numeric      NOT NULL DEFAULT 3,
  automation_investment   numeric      NOT NULL DEFAULT 0,
  group_id                uuid         REFERENCES public.process_groups(id) ON DELETE SET NULL,
  tags                    text[]       NOT NULL DEFAULT '{}',
  owner_id                uuid         NOT NULL REFERENCES public.users(id),
  created_by              uuid         NOT NULL REFERENCES public.users(id),
  updated_by              uuid         NOT NULL REFERENCES public.users(id),
  created_at              timestamptz  NOT NULL DEFAULT now(),
  updated_at              timestamptz  NOT NULL DEFAULT now()
);

ALTER TABLE public.processes ENABLE ROW LEVEL SECURITY;

-- En Sprint 4 todos los usuarios son platform_admin: ven todo.
-- Las políticas granulares (process_access) se agregan en Etapa 5.
-- Por ahora: cualquier usuario autenticado puede leer y escribir todos los procesos.
CREATE POLICY "authenticated_all_processes" ON public.processes
  FOR ALL USING (auth.uid() IS NOT NULL)
  WITH CHECK (auth.uid() IS NOT NULL);
```

### `004_create_app_settings.sql`

```sql
-- Settings globales del equipo (singleton compartido)
CREATE TABLE IF NOT EXISTS public.app_settings (
  id           text         PRIMARY KEY DEFAULT 'singleton',
  group_label  text         NOT NULL DEFAULT 'Cliente',
  updated_at   timestamptz  NOT NULL DEFAULT now()
);

ALTER TABLE public.app_settings ENABLE ROW LEVEL SECURITY;

-- Todos los autenticados pueden leer y actualizar settings
CREATE POLICY "all_app_settings" ON public.app_settings
  FOR ALL USING (auth.uid() IS NOT NULL)
  WITH CHECK (auth.uid() IS NOT NULL);

-- Insertar el singleton con defaults
INSERT INTO public.app_settings (id, group_label)
VALUES ('singleton', 'Cliente')
ON CONFLICT (id) DO NOTHING;
```

Estos scripts se ejecutan **manualmente** en Supabase Dashboard → SQL Editor, en orden numérico.

---

## Mapeo de tipos TypeScript ↔ PostgreSQL

Los repositorios Supabase deben mapear entre los tipos del dominio (camelCase, timestamps como `number` ms Unix) y las columnas de PostgreSQL (snake_case, `timestamptz`).

**NO modificar `src/domain/types.ts`**. El mapeo es responsabilidad del repo.

### Process → processes

| TypeScript (`Process`) | PostgreSQL (`processes`) | Notas |
|---|---|---|
| `id` | `id` | igual |
| `name` | `name` | igual |
| `clientName` | `client` | rename solo en DB |
| `bpmnXml` | `bpmn_xml` | snake_case |
| `currency` | `currency` | igual |
| `overheadPercentage` | `overhead_percentage` | snake_case |
| `annualFrequency` | `annual_frequency` | snake_case |
| `analysisHorizonYears` | `analysis_horizon_years` | snake_case |
| `automationInvestment` | `automation_investment` | snake_case |
| `groupId` | `group_id` | snake_case |
| `tags` | `tags` | igual (array) |
| `createdAt` (number ms) | `created_at` (timestamptz) | `new Date(createdAt).toISOString()` |
| `updatedAt` (number ms) | `updated_at` (timestamptz) | igual conversión |
| — | `owner_id` | nuevo campo, inyectado por el store |
| — | `created_by` | nuevo campo, inyectado en primer save |
| — | `updated_by` | nuevo campo, inyectado en cada save |

Para `from_db` (PostgreSQL → TypeScript):
- `createdAt: new Date(row.created_at).getTime()`
- `updatedAt: new Date(row.updated_at).getTime()`
- `clientName: row.client`

### ProcessGroup → process_groups

| TypeScript (`ProcessGroup`) | PostgreSQL (`process_groups`) |
|---|---|
| `id` | `id` |
| `name` | `name` |
| `createdAt` (number ms) | `created_at` (timestamptz) |
| `updatedAt` (number ms) | `updated_at` (timestamptz) |

### AppSettings → app_settings

| TypeScript (`AppSettings`) | PostgreSQL (`app_settings`) |
|---|---|
| `id` ('singleton') | `id` |
| `groupLabel` | `group_label` |

---

## Nuevos repositorios — estructura de archivos

Crear `src/persistence/supabase/` con:

```
src/persistence/supabase/
├── process-repo.ts
├── group-repo.ts
└── settings-repo.ts
```

Cada archivo importa `supabase` de `src/lib/supabase.ts` y expone una interfaz idéntica al repo Dexie correspondiente, con la firma extendida donde sea necesario para `userId`.

### `src/persistence/supabase/process-repo.ts`

```typescript
// Contrato (misma interface que el repo Dexie, más userId donde aplica)
export const supabaseProcessRepo = {
  async save(process: Process, userId: string): Promise<void>
  async getById(id: string): Promise<Process | undefined>
  async getAll(): Promise<Process[]>
  async delete(id: string): Promise<void>  // ver nota sobre estado híbrido
}
```

**Nota sobre `delete` en estado híbrido:**
La tabla `processes` está en Supabase. Las tablas `activities`, `gateways`, `resources`, `simulations` todavía están en Dexie. El `delete` debe:
1. Eliminar el proceso de Supabase
2. Importar `db` de `src/persistence/db.ts` y limpiar las tablas Dexie relacionadas

Es feo pero temporal. Dejar un comentario `// TODO Sprint 4 Etapa 3-4: eliminar limpieza Dexie cuando actividades migren a Supabase`.

### `src/persistence/supabase/group-repo.ts`

```typescript
export const supabaseGroupRepo = {
  async save(group: ProcessGroup, userId: string): Promise<void>
  async getAll(): Promise<ProcessGroup[]>
  async getById(id: string): Promise<ProcessGroup | undefined>
  async delete(id: string): Promise<void>  // debe hacer SET NULL en processes.group_id (la FK ya tiene ON DELETE SET NULL)
}
```

### `src/persistence/supabase/settings-repo.ts`

```typescript
export const supabaseSettingsRepo = {
  async get(): Promise<AppSettings>
  async update(updates: Partial<Omit<AppSettings, 'id'>>): Promise<void>
}
```

---

## Actualización de stores

Los stores Zustand llaman a los repos. Con el cambio a Supabase, los repos de escritura necesitan el `userId`. Los stores obtienen el usuario actual del `AuthContext`.

### Patrón recomendado en los stores:

```typescript
// En el store, acceder al userId desde el contexto de auth
// Los stores son módulos, no React components — no pueden usar hooks directamente.
// Opciones:
// A) Pasar userId como parámetro a las acciones del store que lo necesitan
// B) Leer el usuario del AuthContext en el componente que llama al store y pasarlo
// C) Usar un getter del store de auth

// Opción recomendada: exponer un authStore simple con el userId,
// o pasar userId como parámetro a las acciones de escritura del store.
```

Claude Code debe elegir el patrón más limpio dado el código existente de los stores. La regla es: **ningún componente llama al repo directamente** — todo pasa por el store.

---

## Criterios de validación — esta etapa está completa cuando

- [ ] `npm run build` sin errores TypeScript
- [ ] `npm run test` — 561 tests siguen verdes (los tests de dominio no tocan los repos)
- [ ] En la biblioteca se pueden crear y listar procesos (datos vienen de Supabase, no de Dexie)
- [ ] En Supabase Dashboard → Table Editor → `processes`: aparecen los procesos creados
- [ ] En Supabase Dashboard → Table Editor → `processes`: las columnas `owner_id`, `created_by`, `updated_by` tienen el UUID del usuario que los creó
- [ ] Crear un grupo de biblioteca → aparece en Supabase tabla `process_groups`
- [ ] Setting `groupLabel` → aparece/actualiza en Supabase tabla `app_settings`
- [ ] Eliminar un proceso → desaparece de Supabase **y** sus actividades/gateways/resources/simulations desaparecen de Dexie
- [ ] No hay imports directos de la tabla Dexie `processes`/`groups`/`settings` fuera de `db.ts` (los repos Supabase no usan esas tablas)
- [ ] Validación visual del director: biblioteca cargando procesos desde Supabase

---

## Reporte esperado al completar

1. Lista de archivos creados/modificados
2. Resultado de `npm run test` (número de tests, pass/fail)
3. Resultado de `npm run build`
4. Captura de pantalla de la biblioteca con al menos un proceso cargado
5. Screenshot de Supabase Table Editor mostrando `processes` con `owner_id` populado
6. Descripción de cómo se resolvió el patrón `userId` en los stores
7. Cualquier decisión de iniciativa fuera del scope (clasificar nivel 1/2/3)

---

## Referencia de archivos existentes

| Archivo | Relevancia |
|---|---|
| `src/persistence/db.ts` | Schema Dexie actual — referencia para entender el modelo |
| `src/persistence/repositories.ts` | Repos Dexie actuales — base para los nuevos repos Supabase |
| `src/domain/types.ts` | Tipos TypeScript — NO modificar |
| `src/store/` | Stores Zustand a actualizar |
| `src/lib/supabase.ts` | Cliente Supabase singleton |
| `src/auth/AuthContext.tsx` | Fuente del userId actual |
| `supabase/migrations/001_create_users.sql` | Referencia de estilo para los nuevos scripts |

---

## Notas arquitectónicas

### Estado híbrido es intencional

Al terminar esta etapa, la app tiene:
- Procesos, grupos, settings → Supabase PostgreSQL
- Actividades, gateways, recursos, simulaciones → Dexie (hasta Etapa 3-4)

Esto es correcto. No anticipar la migración de las tablas restantes.

### Los tests de dominio no se rompen

Los tests en `src/domain/` son funciones puras sin persistencia. No dependen de Dexie ni de Supabase. Deben seguir verdes sin cambios.

Los tests de integración que usan los repos directamente (si los hay) pueden necesitar mocks de Supabase — evaluar caso por caso.

### Supabase genera `updated_at` automáticamente

Para que `updated_at` se actualice automáticamente en cada UPDATE, se puede agregar un trigger PostgreSQL, o simplemente incluir `updated_at: new Date().toISOString()` en cada `UPDATE` desde el repo. Usar el approach del repo (más simple, sin trigger en esta etapa).
