This commit is contained in:
262
sprints/sprint-8/ETAPA_2_PROMPT.md
Normal file
262
sprints/sprint-8/ETAPA_2_PROMPT.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# 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`
|
||||
Reference in New Issue
Block a user