Corrección 1 — Delete de proceso restaurado + Corrección 2 — Patrón userId en repos
This commit is contained in:
314
sprints/sprint-4/ETAPA_2_PROMPT.md
Normal file
314
sprints/sprint-4/ETAPA_2_PROMPT.md
Normal 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).
|
||||
Reference in New Issue
Block a user