8.6 KiB
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
areasy columnaarea_idenprocessesya 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—ProcesstieneclientName: string,groupId: UUID | null,orgId,orgName. SinareaIdaún.src/persistence/supabase/process-repo.ts—fromRow()extraeclientNameygroupId;toRow()escribeclientygroup_id;getById()hace JOIN conorganizations!org_id(name).src/persistence/supabase/group-repo.ts— repositorio del agrupador viejoprocess_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:
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:
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:
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.
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:
// 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:
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:
area_id: process.areaId,
4C — updateEditable(): incluir area_id
Agregar al objeto del .update(), después de group_id:
area_id: process.areaId,
4D — getById(): extender el JOIN para incluir área
Cambiar el select actual:
// 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.clientNameentoRow()— permanecegroup_id: process.groupIdentoRow()— permanececlient: process.clientNameenupdateEditable()— permanecegroup_id: process.groupIdenupdateEditable()— permanecefromRow()sigue extrayendoclientNameygroupId— permanecegetAll()— no cambia (no hace JOIN con areas, solo devuelvearea_idcomo UUID crudo viaselect('*'))
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:
getByOrg()devuelve lista correcta de áreascreate()inserta y retorna el área creada
Criterios de validación
Areainterface existe entypes.tsProcess.areaIdyProcess.areaNameexisten entypes.ts(sin eliminarclientName/groupId)area-repo.tsexiste con las 4 operaciones (getByOrg, create, update, delete)process-repo.ts:fromRow()extraeareaIdyareaName,toRow()incluyearea_id,updateEditable()incluyearea_id,getById()hace JOIN conareasnpm run buildsin errores TypeScriptnpm run testsin regresiones + 2 tests nuevos dearea-repo.ts- Ningún archivo de UI modificado (LibraryPage, WorkspacePage, etc. no cambian)
Restricciones explícitas
- NO eliminar
clientName,groupIddetypes.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_idalselect('*')degetAll()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
- Contenido actual de
schemas.ts(antes de modificar) - Diff compacto de
types.ts - Diff compacto de
process-repo.ts - Contenido de
area-repo.tscreado - Resultado de
npm run buildynpm run test(con conteo de tests) - Confirmar que
clientNameygroupIdsiguen presentes entypes.ts