Files
inq-roi-simulador-web/sprints/sprint-8/ETAPA_2_PROMPT.md
Marcos Benítez de00e3a902
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
etapa 3 terminada
2026-07-07 20:19:45 -03:00

263 lines
8.6 KiB
Markdown

# Sprint 8 — Etapa 2: Repositorios y tipos de dominio
> **Tipo:** Backend TypeScript — dominio + repos
> **Rama:** main
> **Estimación:** 1.5-2 horas
> **Dependencias:** Etapa 1 completada ✅ (tabla `areas` y columna `area_id` en `processes` ya existen en DB)
---
## Objetivo
Extender la capa de dominio y repositorios para soportar el nuevo campo `area_id`. Esto es **no destructivo**: se agregan los campos nuevos sin eliminar los viejos (`clientName`, `groupId`), que todavía son usados por la UI hasta que Etapas 3 y 4 los remplacen.
Al terminar esta etapa, el dominio conoce `areaId/areaName`, el repositorio lee y escribe `area_id` en Supabase, y el código de UI existente sigue funcionando sin cambios.
---
## Contexto del código actual
Leer estos archivos antes de modificar nada:
- `src/domain/types.ts``Process` tiene `clientName: string`, `groupId: UUID | null`, `orgId`, `orgName`. Sin `areaId` aún.
- `src/persistence/supabase/process-repo.ts``fromRow()` extrae `clientName` y `groupId`; `toRow()` escribe `client` y `group_id`; `getById()` hace JOIN con `organizations!org_id(name)`.
- `src/persistence/supabase/group-repo.ts` — repositorio del agrupador viejo `process_groups`. **No tocar** — sigue siendo usado por la Settings page.
- `src/domain/schemas.ts` — leer el contenido actual antes de modificar.
---
## Alcance
### 1. Actualizar `src/domain/types.ts`
**A. Agregar interface `Area`:**
```typescript
export interface Area {
id: string
orgId: string
name: string
description: string | null
createdAt: number // timestamp ms, igual que Process.createdAt
}
```
Agregar después de la interface `ProcessGroup` existente (o en una sección nueva de tipos de organización).
**B. Extender interface `Process`:**
Agregar `areaId` y `areaName` **después** de `orgName`:
```typescript
orgId: string | null // 🆕 Sprint 7
orgName?: string | null // 🆕 Sprint 7 — poblado via JOIN en getById
areaId: string | null // 🆕 Sprint 8 — área de la org (null = sin área)
areaName?: string | null // 🆕 Sprint 8 — poblado via JOIN en getById
```
**NO eliminar** `clientName: string` ni `groupId: UUID | null` — siguen siendo necesarios hasta Etapas 3-4.
### 2. Actualizar `src/domain/schemas.ts`
Leer el archivo primero y reportar su contenido.
Agregar `areaId` al schema de Process:
```typescript
areaId: z.string().uuid().nullable(),
```
**NO eliminar** `clientName` ni `groupId` del schema — todavía se usan.
Si `schemas.ts` no tiene un schema de Process (posiblemente solo tiene schemas para formularios), reportarlo antes de modificar.
### 3. Crear `src/persistence/supabase/area-repo.ts`
Nuevo repositorio para la tabla `areas`. Seguir el mismo patrón que `group-repo.ts`.
```typescript
import { supabase } from '@/lib/supabase'
import type { Area } from '@/domain/types'
function fromRow(row: Record<string, unknown>): Area {
return {
id: row.id as string,
orgId: row.org_id as string,
name: row.name as string,
description: (row.description as string | null) ?? null,
createdAt: new Date(row.created_at as string).getTime(),
}
}
export const supabaseAreaRepo = {
// Todas las áreas de una organización, ordenadas por nombre
async getByOrg(orgId: string): Promise<Area[]> {
const { data, error } = await supabase
.from('areas')
.select('*')
.eq('org_id', orgId)
.order('name', { ascending: true })
if (error) throw error
return (data ?? []).map(fromRow)
},
// Crear área nueva en una org
async create(data: { orgId: string; name: string; description?: string }): Promise<Area> {
const { data: row, error } = await supabase
.from('areas')
.insert({
org_id: data.orgId,
name: data.name,
description: data.description ?? null,
})
.select('*')
.single()
if (error) throw error
return fromRow(row as Record<string, unknown>)
},
// Actualizar nombre y/o descripción de un área
async update(id: string, data: { name?: string; description?: string }): Promise<Area> {
const { data: row, error } = await supabase
.from('areas')
.update({
...(data.name !== undefined && { name: data.name }),
...(data.description !== undefined && { description: data.description }),
updated_at: new Date().toISOString(),
})
.eq('id', id)
.select('*')
.single()
if (error) throw error
return fromRow(row as Record<string, unknown>)
},
// Eliminar área. Los procesos con esta área pasan a area_id = null (ON DELETE SET NULL)
async delete(id: string): Promise<void> {
const { error } = await supabase.from('areas').delete().eq('id', id)
if (error) throw error
},
}
```
### 4. Actualizar `src/persistence/supabase/process-repo.ts`
Cuatro cambios específicos en el archivo existente:
**4A — `fromRow()`: extraer `areaId` y `areaName`**
Agregar a la función existente, después de la extracción de `org`:
```typescript
// area puede estar presente si el query hizo JOIN con areas (getById lo incluye)
const area = row.area as { name: string } | null
```
Y en el objeto de retorno, después de `orgName`:
```typescript
areaId: (row.area_id as string | null) ?? null,
areaName: area?.name ?? null,
```
**4B — `toRow()`: incluir `area_id`**
Agregar al objeto de retorno de `toRow()`, después de `group_id`:
```typescript
area_id: process.areaId,
```
**4C — `updateEditable()`: incluir `area_id`**
Agregar al objeto del `.update()`, después de `group_id`:
```typescript
area_id: process.areaId,
```
**4D — `getById()`: extender el JOIN para incluir área**
Cambiar el select actual:
```typescript
// Antes:
.select('*, org:organizations!org_id(name)')
// Después:
.select('*, org:organizations!org_id(name), area:areas!area_id(name)')
```
**Lo que NO cambia en `process-repo.ts`:**
- `client: process.clientName` en `toRow()` — permanece
- `group_id: process.groupId` en `toRow()` — permanece
- `client: process.clientName` en `updateEditable()` — permanece
- `group_id: process.groupId` en `updateEditable()` — permanece
- `fromRow()` sigue extrayendo `clientName` y `groupId` — permanece
- `getAll()` — no cambia (no hace JOIN con areas, solo devuelve `area_id` como UUID crudo via `select('*')`)
---
## Tests
Agregar al menos 2 tests unitarios para `area-repo.ts`. Seguir el patrón de los tests existentes de repositorios (probablemente con `vi.mock('@/lib/supabase')`).
Tests mínimos:
1. `getByOrg()` devuelve lista correcta de áreas
2. `create()` inserta y retorna el área creada
---
## Criterios de validación
- [ ] `Area` interface existe en `types.ts`
- [ ] `Process.areaId` y `Process.areaName` existen en `types.ts` (sin eliminar `clientName`/`groupId`)
- [ ] `area-repo.ts` existe con las 4 operaciones (getByOrg, create, update, delete)
- [ ] `process-repo.ts`: `fromRow()` extrae `areaId` y `areaName`, `toRow()` incluye `area_id`, `updateEditable()` incluye `area_id`, `getById()` hace JOIN con `areas`
- [ ] `npm run build` sin errores TypeScript
- [ ] `npm run test` sin regresiones + 2 tests nuevos de `area-repo.ts`
- [ ] Ningún archivo de UI modificado (LibraryPage, WorkspacePage, etc. no cambian)
---
## Restricciones explícitas
- **NO eliminar** `clientName`, `groupId` de `types.ts` — aún usados por UI
- **NO modificar** `group-repo.ts` — sigue siendo usado por la Settings page
- **NO modificar** `library-store.ts` — los cambios de store van en Etapa 3
- **NO modificar** ningún componente React — solo capa de dominio y persistencia
- **NO agregar** `area_id` al `select('*')` de `getAll()` explícitamente — ya está incluido en el wildcard
---
## Archivos autorizados a modificar/crear
```
src/
├── domain/
│ ├── types.ts ← agregar Area interface + areaId/areaName a Process
│ └── schemas.ts ← agregar areaId al schema (leer primero)
└── persistence/
└── supabase/
├── area-repo.ts ← crear
└── process-repo.ts ← 4 cambios puntuales
```
Opcionalmente, un archivo de tests para `area-repo.ts` en `tests/`.
---
## Commit esperado
```
feat(area-repo): repositorio CRUD de áreas por organización
feat(process-repo): extender fromRow/toRow/getById con areaId y areaName
feat(domain): interface Area + campos areaId/areaName en Process
```
---
## Reporte esperado
1. Contenido actual de `schemas.ts` (antes de modificar)
2. Diff compacto de `types.ts`
3. Diff compacto de `process-repo.ts`
4. Contenido de `area-repo.ts` creado
5. Resultado de `npm run build` y `npm run test` (con conteo de tests)
6. Confirmar que `clientName` y `groupId` siguen presentes en `types.ts`