Corrección 1 — Delete de proceso restaurado + Corrección 2 — Patrón userId en repos

This commit is contained in:
2026-05-28 01:13:28 -03:00
parent 1bc80e190f
commit 0a3c60cd09
26 changed files with 1650 additions and 25844 deletions

View File

@@ -0,0 +1,314 @@
# 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).

View File

@@ -0,0 +1,392 @@
# Sprint 4 — Etapa 3: Migración de actividades, gateways y recursos globales
## Contexto
InQ ROI — React 18 + Vite + TypeScript + TanStack Router + Zustand + shadcn/ui + Tailwind.
Etapas 1 y 2 completadas: auth con Google, rutas protegidas, procesos/grupos/settings en Supabase.
Esta etapa migra actividades, gateways y recursos de Dexie a Supabase.
El cambio más significativo: **recursos pasan de ser por-proceso a un catálogo global del equipo**.
Al terminar esta etapa, Dexie solo retiene **simulaciones** (Etapa 4).
---
## Prerequisitos — verificar antes de empezar
- Scripts 001-004 ejecutados en Supabase (de Etapas 1 y 2)
- `src/auth/AuthContext.tsx` con `useAuth()` funcionando
- `src/persistence/supabase/` con process-repo, group-repo, settings-repo
---
## Alcance estricto — qué entra en esta etapa
1. **Scripts SQL** para `resources`, `activities`, `activity_resource_assignments`, `gateways`
2. **Nuevos repos Supabase** para las cuatro entidades
3. **Actualizar `process-store.ts`** para usar repos Supabase
4. **Actualizar `useReportData.ts`** para cargar recursos desde Supabase
5. **Actualizar `ResourcesPanel.tsx`** mínimamente: crear recursos sin `processId`
6. **Actualizar `process-repo.ts` delete**: eliminar limpieza Dexie de activities/gateways/resources (pasan a CASCADE en Supabase); conservar limpieza Dexie solo para simulaciones
7. **Modificar tipo `Resource`** en `src/domain/types.ts`: hacer `processId` opcional (`processId?: UUID`)
## Qué NO entra en esta etapa
- Migración de simulaciones → Etapa 4
- Eliminar Dexie del proyecto → Etapa 4
- UX de catálogo global de recursos (selector, panel dedicado) → Etapa 7
- Tabla `user_groups`, `process_access` → Etapa 5
- Avatar / "Actualizado por" en UI → Etapa 6
- Cambiar ningún otro tipo en `src/domain/types.ts`
---
## Scripts SQL — crear en `supabase/migrations/`
### `005_create_resources.sql`
```sql
-- Catálogo global de recursos — sin process_id
CREATE TABLE IF NOT EXISTS public.resources (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
type text NOT NULL CHECK (type IN ('role','person','system','equipment','supply')),
cost_per_hour numeric NOT NULL DEFAULT 0,
created_by uuid REFERENCES public.users(id),
updated_by uuid REFERENCES public.users(id),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
ALTER TABLE public.resources ENABLE ROW LEVEL SECURITY;
-- Todos los autenticados pueden leer y escribir recursos (catálogo compartido)
CREATE POLICY "all_resources" ON public.resources
FOR ALL USING (auth.uid() IS NOT NULL)
WITH CHECK (auth.uid() IS NOT NULL);
```
### `006_create_activities.sql`
```sql
CREATE TABLE IF NOT EXISTS public.activities (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
process_id uuid NOT NULL REFERENCES public.processes(id) ON DELETE CASCADE,
bpmn_element_id text NOT NULL,
name text NOT NULL DEFAULT '',
type text NOT NULL DEFAULT 'task' CHECK (type IN ('task','subprocess')),
direct_cost_fixed numeric NOT NULL DEFAULT 0,
execution_time_minutes numeric NOT NULL DEFAULT 0,
automatable boolean NOT NULL DEFAULT false,
automated_cost_fixed numeric NOT NULL DEFAULT 0,
automated_time_minutes numeric NOT NULL DEFAULT 0,
created_by uuid REFERENCES public.users(id),
updated_by uuid REFERENCES public.users(id),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX IF NOT EXISTS activities_process_bpmn_idx
ON public.activities(process_id, bpmn_element_id);
ALTER TABLE public.activities ENABLE ROW LEVEL SECURITY;
CREATE POLICY "all_activities" ON public.activities
FOR ALL USING (auth.uid() IS NOT NULL)
WITH CHECK (auth.uid() IS NOT NULL);
```
### `007_create_activity_resource_assignments.sql`
```sql
-- Reemplaza los arrays embebidos assignedResources / automatedAssignedResources
CREATE TABLE IF NOT EXISTS public.activity_resource_assignments (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
activity_id uuid NOT NULL REFERENCES public.activities(id) ON DELETE CASCADE,
resource_id uuid NOT NULL REFERENCES public.resources(id) ON DELETE CASCADE,
utilization_minutes numeric NOT NULL DEFAULT 0,
units numeric NOT NULL DEFAULT 1,
scenario text NOT NULL DEFAULT 'actual' CHECK (scenario IN ('actual','automated'))
);
ALTER TABLE public.activity_resource_assignments ENABLE ROW LEVEL SECURITY;
CREATE POLICY "all_assignments" ON public.activity_resource_assignments
FOR ALL USING (auth.uid() IS NOT NULL)
WITH CHECK (auth.uid() IS NOT NULL);
```
### `008_create_gateways.sql`
```sql
CREATE TABLE IF NOT EXISTS public.gateways (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
process_id uuid NOT NULL REFERENCES public.processes(id) ON DELETE CASCADE,
bpmn_element_id text NOT NULL,
gateway_type text NOT NULL CHECK (gateway_type IN ('exclusive','parallel','inclusive','event-based')),
branches jsonb NOT NULL DEFAULT '[]',
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX IF NOT EXISTS gateways_process_bpmn_idx
ON public.gateways(process_id, bpmn_element_id);
ALTER TABLE public.gateways ENABLE ROW LEVEL SECURITY;
CREATE POLICY "all_gateways" ON public.gateways
FOR ALL USING (auth.uid() IS NOT NULL)
WITH CHECK (auth.uid() IS NOT NULL);
```
**Ejecutar en orden (005 → 006 → 007 → 008) en Supabase SQL Editor antes de continuar. Parar si alguno falla.**
---
## Modificación de tipo TypeScript — `src/domain/types.ts`
Hacer `processId` opcional en la interfaz `Resource`:
```typescript
// ANTES:
export interface Resource {
id: UUID
processId: UUID // ← campo requerido
name: string
type: ResourceType
costPerHour: number
}
// DESPUÉS:
export interface Resource {
id: UUID
processId?: UUID // ← opcional (backward compat con tests Dexie existentes)
name: string
type: ResourceType
costPerHour: number
}
```
**Solo este cambio en `types.ts`**. No modificar ninguna otra interfaz.
---
## Nuevos repositorios — `src/persistence/supabase/`
### `resource-repo.ts`
Catálogo global — no filtra por proceso.
```typescript
export const supabaseResourceRepo = {
async save(resource: Resource, userId: string): Promise<void>
// upsert: { id, name, type, cost_per_hour, updated_by: userId, updated_at: now() }
// en insert: también created_by: userId
// NO escribir processId en la DB
async getAll(): Promise<Resource[]>
// SELECT * FROM resources ORDER BY name
// from_db: { id, name, type: row.type, costPerHour: row.cost_per_hour }
// processId NO se popula en el objeto retornado (queda undefined)
async delete(resourceId: string): Promise<void>
// DELETE FROM resources WHERE id = resourceId
}
```
### `activity-repo.ts`
Las actividades tienen asignaciones embebidas en el tipo TypeScript pero normalizadas en la DB.
```typescript
export const supabaseActivityRepo = {
async save(activity: Activity, userId: string): Promise<void>
// 1. Upsert en activities (sin los arrays de recursos)
// 2. DELETE FROM activity_resource_assignments WHERE activity_id = activity.id
// 3. INSERT assignments de activity.assignedResources (scenario='actual')
// 4. INSERT assignments de activity.automatedAssignedResources (scenario='automated')
async saveMany(activities: Activity[], userId: string): Promise<void>
// Loop de save() — performance no crítica para import
async getByProcess(processId: string): Promise<Activity[]>
// SELECT activities.*, activity_resource_assignments(*) WHERE process_id = processId
// Reconstruir Activity con:
// assignedResources: assignments.filter(a => a.scenario === 'actual').map(toAssignment)
// automatedAssignedResources: assignments.filter(a => a.scenario === 'automated').map(toAssignment)
async getByBpmnElementId(processId: string, bpmnElementId: string): Promise<Activity | undefined>
// SELECT con WHERE process_id = ? AND bpmn_element_id = ? + join assignments
async deleteByProcess(processId: string): Promise<void>
// DELETE WHERE process_id = processId (assignments se eliminan por CASCADE)
}
```
**Mapeo Activity TypeScript ↔ PostgreSQL:**
| TypeScript | PostgreSQL |
|---|---|
| `id` | `id` |
| `processId` | `process_id` |
| `bpmnElementId` | `bpmn_element_id` |
| `name` | `name` |
| `type` | `type` |
| `directCostFixed` | `direct_cost_fixed` |
| `executionTimeMinutes` | `execution_time_minutes` |
| `automatable` | `automatable` |
| `automatedCostFixed` | `automated_cost_fixed` |
| `automatedTimeMinutes` | `automated_time_minutes` |
| `assignedResources` | via `activity_resource_assignments` WHERE scenario='actual' |
| `automatedAssignedResources` | via `activity_resource_assignments` WHERE scenario='automated' |
### `gateway-repo.ts`
```typescript
export const supabaseGatewayRepo = {
async save(gateway: GatewayConfig): Promise<void>
// upsert: { id, process_id, bpmn_element_id, gateway_type, branches: JSON.stringify(gateway.branches) }
async saveMany(gateways: GatewayConfig[]): Promise<void>
// Loop de save()
async getByProcess(processId: string): Promise<GatewayConfig[]>
// SELECT WHERE process_id = processId
// from_db: branches: JSON.parse(row.branches) as GatewayBranch[]
async deleteByProcess(processId: string): Promise<void>
// DELETE WHERE process_id (CASCADE ya lo maneja, pero mantener para explicitez)
}
```
**Mapeo GatewayConfig:**
| TypeScript | PostgreSQL |
|---|---|
| `id` | `id` |
| `processId` | `process_id` |
| `bpmnElementId` | `bpmn_element_id` |
| `gatewayType` | `gateway_type` |
| `branches` (array) | `branches` (jsonb) — `JSON.stringify` / `JSON.parse` |
---
## Actualización de stores y componentes
### `src/store/process-store.ts`
Reemplazar todas las llamadas a repos Dexie con los nuevos repos Supabase.
**`loadProcess`**: cambiar `resourceRepo.getByProcess(processId)``supabaseResourceRepo.getAll()`
Los recursos se cargan globalmente. El store los tiene todos disponibles para el workspace.
**`setCurrentProcess`**: igual — cargar recursos con `getAll()`
**`updateActivity`**: `activityRepo.save(activity)``supabaseActivityRepo.save(activity, userId)`
**`updateGateway`**: `gatewayRepo.save(gateway)``supabaseGatewayRepo.save(gateway)`
(gateways no tienen userId — no hay created_by/updated_by en el schema de gateways)
**`addResource` / `updateResource`**: `resourceRepo.save(resource)``supabaseResourceRepo.save(resource, userId)`
**Al crear un recurso, NO incluir `processId`** en el objeto que se pasa al repo.
**`deleteResource`**: `resourceRepo.delete(resourceId)``supabaseResourceRepo.delete(resourceId)`
### `src/persistence/supabase/process-repo.ts` — actualizar `delete`
Después de Etapa 3, activities/gateways/resources tienen CASCADE en Supabase.
Actualizar el método `delete` para que limpie Dexie solo de simulaciones:
```typescript
async delete(id: string): Promise<void> {
// Supabase CASCADE elimina automáticamente: activities, gateways, activity_resource_assignments
await supabase.from('processes').delete().eq('id', id)
// TODO Sprint 4 Etapa 4: eliminar limpieza Dexie cuando simulaciones migren a Supabase
await db.transaction('rw', [db.simulations], async () => {
await db.simulations.where('processId').equals(id).delete()
})
// Nota: activities, gateways, resources ya NO están en Dexie — no limpiar
}
```
### `src/features/report/useReportData.ts`
Cambiar `resourceRepo.getByProcess(processId)``supabaseResourceRepo.getAll()`
### `src/features/workspace/ResourcesPanel.tsx`
Al crear un nuevo recurso, **no incluir `processId`** en el objeto:
```typescript
// ANTES:
const newResource: Resource = {
id: crypto.randomUUID(),
processId: currentProcess.id, // ← eliminar esta línea
name: ...,
type: ...,
costPerHour: ...,
}
// DESPUÉS:
const newResource: Resource = {
id: crypto.randomUUID(),
name: ...,
type: ...,
costPerHour: ...,
}
```
No hacer otros cambios de UX en ResourcesPanel — el rediseño completo (selección desde catálogo) es Etapa 7.
### `src/features/import/ImportPage.tsx`
Si usa `activityRepo.saveMany()` o `gatewayRepo.saveMany()`, actualizar a los repos Supabase.
Asegurarse de que el `userId` se pasa correctamente (el componente tiene acceso via `useAuth()`).
---
## Criterios de validación
- [ ] `npm run build` sin errores TypeScript
- [ ] `npm run test` — 561 tests siguen verdes
- [ ] Importar un BPMN → el proceso se carga correctamente en el workspace
- [ ] Editar el costo de una actividad → persiste después de recargar la página
- [ ] Editar la probabilidad de un gateway → persiste después de recargar
- [ ] Crear un recurso → aparece en Supabase tabla `resources` sin `process_id`
- [ ] El mismo recurso es visible en todos los procesos (catálogo global)
- [ ] Eliminar un proceso → sus actividades y gateways desaparecen de Supabase (CASCADE)
- [ ] El recurso creado NO desaparece al eliminar el proceso (es global)
- [ ] En Supabase tabla `activities`: aparecen las actividades con `process_id` correcto
- [ ] En Supabase tabla `activity_resource_assignments`: aparecen las asignaciones con `scenario`
- [ ] Validación visual del director: workspace funcional (canvas + panel de actividad)
---
## 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. Screenshot del workspace con actividades cargadas
5. Screenshot de Supabase tabla `resources` (sin columna `process_id`)
6. Screenshot de Supabase tabla `activity_resource_assignments` con al menos una fila
7. Cualquier decisión de iniciativa fuera del scope (clasificar nivel 1/2/3)
---
## Notas arquitectónicas
### Los tests de Dexie siguen verdes
`tests/integration/indexeddb-persistence.test.ts` usa el `resourceRepo` de Dexie con `processId`. Esos tests deben seguir pasando sin cambios — el `resourceRepo` Dexie todavía existe en `repositories.ts`, simplemente ya no es usado por la producción. No borrar ni modificar ese archivo en esta etapa.
### Patrón userId establecido
El patrón de Etapa 2 aplica acá: repos reciben `userId` como parámetro, stores lo reciben de los componentes via `useAuth()`. Los tests de componentes que llaman acciones de escritura deben mockear `useAuth`.
Gateways no tienen `created_by`/`updated_by` en el schema — no necesitan `userId` en el repo.
### Catálogo global — impacto en UX
Al cargar el workspace, ahora se cargan TODOS los recursos del equipo (no solo los del proceso). El `ResourcesPanel` los mostrará todos. Esto es el comportamiento correcto del catálogo global — el rediseño visual de ese panel es Etapa 7.