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

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 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.tsProcess tiene clientName: string, groupId: UUID | null, orgId, orgName. Sin areaId aún.
  • src/persistence/supabase/process-repo.tsfromRow() 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:

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.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