From d6e722684221d8b417d260d8ca3d96d149d6339c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Ben=C3=ADtez?= Date: Tue, 7 Jul 2026 18:18:14 -0300 Subject: [PATCH] feat(process-repo): extender fromRow/toRow/getById con areaId y areaName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fromRow: extrae areaId y areaName (vía JOIN en getById) - toRow y updateEditable: incluyen area_id en escrituras a Supabase - getById: JOIN con areas!area_id(name) para obtener nombre en una query - Fixtures de tests actualizados con areaId: null (campo requerido) - 4 tests nuevos en tests/persistence/area-repo.test.ts Co-Authored-By: Claude Sonnet 4.6 --- src/features/import/ImportPage.tsx | 1 + src/persistence/supabase/process-repo.ts | 8 +- tests/features/report/components.test.tsx | 8 +- tests/features/report/roi-components.test.tsx | 2 +- tests/lib/export/csv-export-roi.test.ts | 2 +- tests/lib/export/csv-export.test.ts | 2 +- tests/lib/export/measure-sizes.test.ts | 2 +- tests/lib/export/pdf-export-roi.test.ts | 2 +- tests/lib/export/pdf-export-sprint2.test.ts | 2 +- tests/lib/export/pdf-export.test.ts | 2 +- tests/lib/export/pdf-orientation.test.ts | 2 +- tests/lib/export/pdf-roi-page1.test.ts | 2 +- tests/lib/export/pdf-roi-pages2-5.test.ts | 2 +- tests/lib/export/pdf-scenario-page1.test.ts | 2 +- tests/persistence/area-repo.test.ts | 94 +++++++++++++++++++ .../simulation-store-invalidation.test.ts | 2 +- 16 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 tests/persistence/area-repo.test.ts diff --git a/src/features/import/ImportPage.tsx b/src/features/import/ImportPage.tsx index 1aa62cd..0583274 100644 --- a/src/features/import/ImportPage.tsx +++ b/src/features/import/ImportPage.tsx @@ -92,6 +92,7 @@ export function ImportPage() { ownerId: user!.id, updatedBy: user!.id, orgId: null, + areaId: null, } const activityElements = extractActivityElements(graph) diff --git a/src/persistence/supabase/process-repo.ts b/src/persistence/supabase/process-repo.ts index 739aa3f..9a688f8 100644 --- a/src/persistence/supabase/process-repo.ts +++ b/src/persistence/supabase/process-repo.ts @@ -13,6 +13,7 @@ function toRow(process: Process, userId: string) { analysis_horizon_years: process.analysisHorizonYears, automation_investment: process.automationInvestment, group_id: process.groupId, + area_id: process.areaId, tags: process.tags, // owner_id se preserva del valor existente en el proceso; solo se inicializa con userId en creates owner_id: process.ownerId || userId, @@ -25,6 +26,8 @@ function toRow(process: Process, userId: string) { function fromRow(row: Record): Process { // org puede estar presente si el query hizo JOIN con organizations (getById lo incluye) const org = row.org as { name: string } | null + // area puede estar presente si el query hizo JOIN con areas (getById lo incluye) + const area = row.area as { name: string } | null return { id: row.id as string, name: row.name as string, @@ -43,6 +46,8 @@ function fromRow(row: Record): Process { updatedBy: (row.updated_by as string) ?? '', orgId: (row.org_id as string | null) ?? null, orgName: org?.name ?? null, + areaId: (row.area_id as string | null) ?? null, + areaName: area?.name ?? null, } } @@ -91,6 +96,7 @@ export const supabaseProcessRepo = { automation_investment: process.automationInvestment, tags: process.tags, group_id: process.groupId, + area_id: process.areaId, updated_by: userId, updated_at: new Date().toISOString(), }) @@ -102,7 +108,7 @@ export const supabaseProcessRepo = { // JOIN con organizations para obtener orgName en una sola query (evita fetch separado y race conditions) const { data, error } = await supabase .from('processes') - .select('*, org:organizations!org_id(name)') + .select('*, org:organizations!org_id(name), area:areas!area_id(name)') .eq('id', id) .single() if (error) return undefined diff --git a/tests/features/report/components.test.tsx b/tests/features/report/components.test.tsx index 56f85a9..14ad47a 100644 --- a/tests/features/report/components.test.tsx +++ b/tests/features/report/components.test.tsx @@ -229,7 +229,7 @@ describe('MethodologyFooter', () => { id: 'p1', name: 'Proceso Test', clientName: '', bpmnXml: '', currency: 'USD', overheadPercentage: 0.2, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0, - createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } expect(() => render()).not.toThrow() }) @@ -239,7 +239,7 @@ describe('MethodologyFooter', () => { id: 'p1', name: 'Proc', clientName: '', bpmnXml: '', currency: 'USD', overheadPercentage: 0.25, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0, - createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } render() const text = document.body.textContent ?? '' @@ -251,7 +251,7 @@ describe('MethodologyFooter', () => { id: 'p1', name: 'Proc', clientName: '', bpmnXml: '', currency: 'USD', overheadPercentage: 0.2, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0, - createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } render() const text = document.body.textContent ?? '' @@ -342,7 +342,7 @@ describe('ReportPage — con datos completos', () => { updatedAt: Date.now() - 1000 * 60 * 30, groupId: null, tags: [], - ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } const mockSimulation = { diff --git a/tests/features/report/roi-components.test.tsx b/tests/features/report/roi-components.test.tsx index ebeab38..67ae6ab 100644 --- a/tests/features/report/roi-components.test.tsx +++ b/tests/features/report/roi-components.test.tsx @@ -600,7 +600,7 @@ const mockProcessBase = { currency: 'USD', overheadPercentage: 0.2, annualFrequency: 1200, analysisHorizonYears: 3, automationInvestment: 50_000, createdAt: Date.now() - 3600_000, updatedAt: Date.now() - 3600_000, - groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } const mockSimulationBase = { diff --git a/tests/lib/export/csv-export-roi.test.ts b/tests/lib/export/csv-export-roi.test.ts index cb6c605..978e035 100644 --- a/tests/lib/export/csv-export-roi.test.ts +++ b/tests/lib/export/csv-export-roi.test.ts @@ -14,7 +14,7 @@ function makeProcess(extras: Partial = {}): Process { id: 'p1', name: 'Proceso de Crédito', clientName: 'Banco XYZ', bpmnXml: '', currency: 'USD', overheadPercentage: 0.2, annualFrequency: 1200, analysisHorizonYears: 3, automationInvestment: 50_000, - createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, ...extras, + createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, ...extras, } } diff --git a/tests/lib/export/csv-export.test.ts b/tests/lib/export/csv-export.test.ts index 4f67ea2..7c9f074 100644 --- a/tests/lib/export/csv-export.test.ts +++ b/tests/lib/export/csv-export.test.ts @@ -9,7 +9,7 @@ function makeProcess(currency: string, extras: Partial = {}): Process { id: 'p1', name: 'Proceso de Ventas', clientName: 'Empresa ABC', bpmnXml: '', currency, overheadPercentage: 0.2, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0, - createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, ...extras, + createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, ...extras, } } diff --git a/tests/lib/export/measure-sizes.test.ts b/tests/lib/export/measure-sizes.test.ts index cbb92c7..6dfeecf 100644 --- a/tests/lib/export/measure-sizes.test.ts +++ b/tests/lib/export/measure-sizes.test.ts @@ -66,7 +66,7 @@ describe('Medición de tamaños de CSV — 3 sample BPMNs', () => { bpmnXml: xml, currency: 'USD', overheadPercentage: 0.2, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0, createdAt: 0, updatedAt: 0, - groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } const simulation: Simulation = { diff --git a/tests/lib/export/pdf-export-roi.test.ts b/tests/lib/export/pdf-export-roi.test.ts index b70e205..6111e9a 100644 --- a/tests/lib/export/pdf-export-roi.test.ts +++ b/tests/lib/export/pdf-export-roi.test.ts @@ -51,7 +51,7 @@ const mockProcess: Process = { bpmnXml: '', currency: 'USD', overheadPercentage: 0.2, annualFrequency: 1200, analysisHorizonYears: 3, automationInvestment: 50_000, createdAt: 0, updatedAt: 0, - groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } const perActivityActual = [ diff --git a/tests/lib/export/pdf-export-sprint2.test.ts b/tests/lib/export/pdf-export-sprint2.test.ts index 8bef4a9..390e9cc 100644 --- a/tests/lib/export/pdf-export-sprint2.test.ts +++ b/tests/lib/export/pdf-export-sprint2.test.ts @@ -43,7 +43,7 @@ const mockProcess: Process = { bpmnXml: '', currency: 'USD', overheadPercentage: 0.2, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0, createdAt: 0, updatedAt: 0, - groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } // Simulación SIN recursos en ninguna actividad diff --git a/tests/lib/export/pdf-export.test.ts b/tests/lib/export/pdf-export.test.ts index 1894740..3d04fe4 100644 --- a/tests/lib/export/pdf-export.test.ts +++ b/tests/lib/export/pdf-export.test.ts @@ -54,7 +54,7 @@ const mockProcess: Process = { overheadPercentage: 0.2, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0, createdAt: 0, updatedAt: 0, - groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } const mockSimulation: Simulation = { diff --git a/tests/lib/export/pdf-orientation.test.ts b/tests/lib/export/pdf-orientation.test.ts index 6ea5cfb..d7e140a 100644 --- a/tests/lib/export/pdf-orientation.test.ts +++ b/tests/lib/export/pdf-orientation.test.ts @@ -59,7 +59,7 @@ const mockProcess: Process = { overheadPercentage: 0.2, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 50000, createdAt: 0, updatedAt: 0, - groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } const mockSimulation: Simulation = { diff --git a/tests/lib/export/pdf-roi-page1.test.ts b/tests/lib/export/pdf-roi-page1.test.ts index a6817c4..1fd9769 100644 --- a/tests/lib/export/pdf-roi-page1.test.ts +++ b/tests/lib/export/pdf-roi-page1.test.ts @@ -56,7 +56,7 @@ const mockProcess: Process = { overheadPercentage: 0.2, annualFrequency: 5000, analysisHorizonYears: 3, automationInvestment: 80000, createdAt: 0, updatedAt: 0, - groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } const mockSimulation: Simulation = { diff --git a/tests/lib/export/pdf-roi-pages2-5.test.ts b/tests/lib/export/pdf-roi-pages2-5.test.ts index ae6189f..8052007 100644 --- a/tests/lib/export/pdf-roi-pages2-5.test.ts +++ b/tests/lib/export/pdf-roi-pages2-5.test.ts @@ -52,7 +52,7 @@ const mockProcess: Process = { bpmnXml: '', currency: 'USD', overheadPercentage: 0.2, annualFrequency: 5000, analysisHorizonYears: 3, automationInvestment: 80000, createdAt: 0, updatedAt: 0, - groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } const mockRoi: RoiResult = { diff --git a/tests/lib/export/pdf-scenario-page1.test.ts b/tests/lib/export/pdf-scenario-page1.test.ts index b940ac0..2f55623 100644 --- a/tests/lib/export/pdf-scenario-page1.test.ts +++ b/tests/lib/export/pdf-scenario-page1.test.ts @@ -52,7 +52,7 @@ const mockProcess: Process = { bpmnXml: '', currency: 'USD', overheadPercentage: 0.2, annualFrequency: 5000, analysisHorizonYears: 3, automationInvestment: 80000, createdAt: 0, updatedAt: 0, - groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } const mockSimulation: Simulation = { diff --git a/tests/persistence/area-repo.test.ts b/tests/persistence/area-repo.test.ts new file mode 100644 index 0000000..fbca783 --- /dev/null +++ b/tests/persistence/area-repo.test.ts @@ -0,0 +1,94 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { supabaseAreaRepo } from '@/persistence/supabase/area-repo' + +const { mockSingle, mockData } = vi.hoisted(() => ({ + mockSingle: vi.fn(), + mockData: vi.fn(), +})) + +vi.mock('@/lib/supabase', () => { + const buildChain = () => { + const chain: Record = {} + chain.select = vi.fn().mockReturnValue(chain) + chain.insert = vi.fn().mockReturnValue(chain) + chain.update = vi.fn().mockReturnValue(chain) + chain.delete = vi.fn().mockReturnValue(chain) + chain.eq = vi.fn().mockReturnValue(chain) + chain.order = vi.fn().mockImplementation(() => mockData()) + chain.single = mockSingle + return chain + } + + return { + supabase: { + from: vi.fn().mockImplementation(() => buildChain()), + }, + } +}) + +const AREA_ROW = { + id: 'area-uuid-1', + org_id: 'org-uuid-1', + name: 'Finanzas', + description: 'Área de finanzas', + created_at: '2026-07-07T00:00:00.000Z', + updated_at: '2026-07-07T00:00:00.000Z', +} + +describe('supabaseAreaRepo', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('getByOrg', () => { + it('devuelve lista de áreas mapeadas correctamente', async () => { + mockData.mockResolvedValue({ data: [AREA_ROW], error: null }) + + const result = await supabaseAreaRepo.getByOrg('org-uuid-1') + + expect(result).toHaveLength(1) + expect(result[0]).toMatchObject({ + id: 'area-uuid-1', + orgId: 'org-uuid-1', + name: 'Finanzas', + description: 'Área de finanzas', + createdAt: new Date('2026-07-07T00:00:00.000Z').getTime(), + }) + }) + + it('devuelve array vacío si la org no tiene áreas', async () => { + mockData.mockResolvedValue({ data: [], error: null }) + + const result = await supabaseAreaRepo.getByOrg('org-sin-areas') + + expect(result).toEqual([]) + }) + }) + + describe('create', () => { + it('inserta el área y retorna el registro mapeado', async () => { + mockSingle.mockResolvedValue({ data: AREA_ROW, error: null }) + + const result = await supabaseAreaRepo.create({ + orgId: 'org-uuid-1', + name: 'Finanzas', + description: 'Área de finanzas', + }) + + expect(result).toMatchObject({ + id: 'area-uuid-1', + orgId: 'org-uuid-1', + name: 'Finanzas', + description: 'Área de finanzas', + }) + }) + + it('propaga el error de Supabase si el insert falla', async () => { + mockSingle.mockResolvedValue({ data: null, error: new Error('RLS violation') }) + + await expect( + supabaseAreaRepo.create({ orgId: 'org-1', name: 'Test' }) + ).rejects.toThrow('RLS violation') + }) + }) +}) diff --git a/tests/store/simulation-store-invalidation.test.ts b/tests/store/simulation-store-invalidation.test.ts index 1e240b0..f88ce2c 100644 --- a/tests/store/simulation-store-invalidation.test.ts +++ b/tests/store/simulation-store-invalidation.test.ts @@ -94,7 +94,7 @@ describe('simulation store — invalidación por cambio en process store', () => currency: 'USD', overheadPercentage: 0.2, annualFrequency: 500, analysisHorizonYears: 2, automationInvestment: 10_000, createdAt: Date.now(), updatedAt: Date.now(), - groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, areaId: null, } useProcessStore.setState({ currentProcess: proc })