# 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`
