feat(process-repo): extender fromRow/toRow/getById con areaId y areaName
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -229,7 +229,7 @@ describe('MethodologyFooter', () => {
|
||||
id: 'p1', name: 'Proceso Test', clientName: '',
|
||||
bpmnXml: '<def/>', 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(<MethodologyFooter process={proc} />)).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(<MethodologyFooter process={proc} />)
|
||||
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(<MethodologyFooter process={proc} />)
|
||||
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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -14,7 +14,7 @@ function makeProcess(extras: Partial<Process> = {}): 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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ function makeProcess(currency: string, extras: Partial<Process> = {}): 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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
94
tests/persistence/area-repo.test.ts
Normal file
94
tests/persistence/area-repo.test.ts
Normal file
@@ -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<string, unknown> = {}
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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 })
|
||||
|
||||
|
||||
Reference in New Issue
Block a user