diff --git a/CLAUDE.md b/CLAUDE.md index ecd8f87..559e5bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -199,6 +199,81 @@ Antes de presentar cualquier PDF o pantalla: --- +## Gestión de versiones y commits + +> Aplica a partir de Sprint 8. Claude Code es responsable del ciclo completo de versionado. + +### Conventional Commits — obligatorio en todos los commits + +``` +feat(scope): descripción ← feature nueva visible al usuario +fix(scope): descripción ← corrección de bug +refactor(scope): descripción ← refactor sin cambio de comportamiento externo +test(scope): descripción ← solo tests, sin cambio de código de producción +docs(scope): descripción ← solo documentación +chore(scope): descripción ← tareas de infra, build, deps +``` + +`scope` es el área afectada: `auth`, `workspace`, `library`, `report`, `pdf`, `admin`, `db`, `e2e`, etc. + +Ejemplos correctos: +``` +feat(workspace): back button contextual con navegación a biblioteca del cliente +fix(process-repo): cambiar upsert por update para evitar 403 en client_editor +refactor(auth): agregar isProfileLoaded para evitar race condition de roles +``` + +### CHANGELOG.md + +Actualizar al cerrar cada etapa. Formato Keep a Changelog: + +```markdown +## [Unreleased] +### Added +- Back button contextual en workspace para admin + +### Fixed +- 403 en Guardar configuración para client_editor (upsert → update) +``` + +Al cerrar un sprint, mover `[Unreleased]` a `[vX.Y.Z] - YYYY-MM-DD`. + +### SemVer — criterios + +- **MAJOR:** cambio incompatible (nuevo modelo de datos, cambio de rol fundamental, migración forzada de usuarios) +- **MINOR:** feature nueva visible al usuario (nueva página, nueva funcionalidad en workspace) +- **PATCH:** corrección de bug o mejora de UX sin feature nueva + +Sprint 7 = `v0.7.x` (MVP multi-tenant). Sprint 8 arranca en `v0.8.0`. + +### Secuencia de cierre de etapa + +Al completar una etapa con cambios en producción: + +```bash +git add -A +git commit -m "feat(scope): descripción concisa" +# Actualizar CHANGELOG.md +git add CHANGELOG.md +git commit -m "docs(changelog): etapa N sprint X" +git push origin main +``` + +Al cerrar un sprint completo, agregar además: + +```bash +git tag -a vX.Y.Z -m "Release vX.Y.Z: descripción del sprint" +git push --tags +``` + +### Versión visible en la app + +La versión se lee de `package.json` (`version` field) y se muestra en el footer de todas las páginas (o al menos en `/library` y `/report`). Formato: `InQ ROI v1.2.3`. + +Si no existe un componente de footer global, crearlo al implementar esta feature por primera vez. + +--- + ## Política de iniciativa proactiva Antes de aplicar un cambio fuera del scope del prompt actual, evaluar en orden: diff --git a/STRATEGIC_DIRECTION.md b/STRATEGIC_DIRECTION.md index 2fd1e15..74d137e 100644 --- a/STRATEGIC_DIRECTION.md +++ b/STRATEGIC_DIRECTION.md @@ -385,4 +385,71 @@ En tres etapas (AdminLayout, WorkspacePage, ImportPage), Claude Code implementó - Email visible en panel admin (requiere join con `auth.users` via service_role) - Historial de simulaciones (diferido desde Sprint 3) -**Cerrado:** 2026-07-06. +**Cerrado:** 2026-07-07 (con extensión de hotfixes: Etapa 6 ConfirmInvitePage, fix upsert→update en process-repo, Etapa 7 race condition AuthContext + simulations_update policy, Etapa 7B back button contextual en workspace). + +--- + +## Decisiones estratégicas confirmadas para Sprint 8 + +> Registradas el 2026-07-07 durante la sesión de cierre de Sprint 7. + +### client_editor: permisos de BPMN ampliados + +**Decisión del director:** `client_editor` podrá: +1. **Actualizar BPMN de procesos existentes** asignados a su org (reemplazar `bpmn_xml` vía UPDATE) +2. **Importar procesos nuevos** con el botón "Importar BPMN", creándolos con `org_id = current_org()` + +**Implicancias técnicas para el brief:** +- `updateEditable()` debe incluir `bpmn_xml` cuando el caller tiene permisos para modificarlo +- Política INSERT en `processes`: agregar rama `client_editor AND org_id = current_org()` (los procesos nuevos quedan dentro de su org) +- Botón "Importar BPMN" visible para `client_editor` (actualmente bloqueado en Etapa 5) +- Los procesos creados por `client_editor` tienen `owner_id = auth.uid()` y `org_id = current_org()` +- `client_editor` NO puede mover procesos entre orgs ni cambiar `owner_id` + +**Precaución:** relajar INSERT en processes para client_editor es cambio de modelo. El brief de Sprint 8 debe incluir una migración que ajuste la política y tests E2E que validen el scope (no pueden crear procesos sin org_id, no pueden crear procesos en otra org). + +### Versionado profesional con changelog + +**Decisión del director:** a partir de Sprint 8, Claude Code es responsable del ciclo completo de versiones: + +1. **Commits:** formato Conventional Commits obligatorio + - `feat(scope): descripción` — feature nueva + - `fix(scope): descripción` — corrección de bug + - `refactor(scope): descripción` — refactor sin cambio de comportamiento + - `test(scope): descripción` — solo tests + - `docs(scope): descripción` — solo documentación + - `chore(scope): descripción` — tareas de infra/build + +2. **CHANGELOG.md:** actualizar al cerrar cada etapa, formato Keep a Changelog + +3. **SemVer para versiones:** `MAJOR.MINOR.PATCH` + - MAJOR: cambio incompatible de producto (nuevo modelo de datos, cambio de rol fundamental) + - MINOR: feature nueva visible al usuario + - PATCH: fix de bug, mejora de UX sin feature nueva + +4. **Git tags:** `git tag -a vX.Y.Z -m "Release vX.Y.Z: descripción"` + `git push --tags` + +5. **Versión visible en la app:** footer de todas las páginas, leer de `package.json` o `src/lib/version.ts`. Formato: `InQ ROI v1.2.3`. + +**Instrucción:** la sección "Gestión de versiones y commits" se agrega a `simulador-web/CLAUDE.md` como Sprint 8 Etapa 0, antes de cualquier feature nueva. + +### Página de ayuda (`/help`) + +**Decisión del director:** Sprint 8 incluye una ruta `/help` con documentación interna del producto para todos los roles. + +**Contenido mínimo:** +- Cómo parametrizar actividades (tiempos, costos, recursos) +- Cómo simular y leer el reporte +- Cómo exportar PDF y CSV +- Referencia rápida de los campos del panel Global + +**Requisito de método añadido al pipeline de sprints:** cualquier `ETAPA_X_PROMPT.md` que modifique la UI incluirá un ítem en los criterios de validación: `[ ] Verificar si el cambio afecta el contenido de /help — actualizar si corresponde`. Agregar al template de etapa en `methodology/` antes de abrir Sprint 8. + +### Botón "volver" contextual en workspace + +**Decisión del director:** implementado en Sprint 7 como Etapa 7B. Brief en `sprints/sprint-7/ETAPA_7B_PROMPT.md`. + +Comportamiento: +- Admin en proceso de cliente → "← [nombre del cliente]" → `/library?org=` con scroll al cliente +- Admin en proceso sin org → "← Biblioteca" → `/library` +- client_editor/viewer → "← Procesos" → `/library` diff --git a/sprints/sprint-7/ETAPA_7B_PROMPT.md b/sprints/sprint-7/ETAPA_7B_PROMPT.md new file mode 100644 index 0000000..397d43e --- /dev/null +++ b/sprints/sprint-7/ETAPA_7B_PROMPT.md @@ -0,0 +1,133 @@ +# Etapa 7B — Botón "volver" contextual en workspace + +**Sprint:** 7 +**Fecha:** 2026-07-07 +**Alcance:** workspace header únicamente. Sin tocar LibraryPage, AuthContext, migraciones, ni lógica de simulación. + +--- + +## Problema + +Cuando un admin (platform_admin/member) abre el workspace de un proceso que pertenece a un cliente (org no-provider), no hay forma de volver a la vista de los procesos de ese cliente. El ícono de casa (🏠) lleva a la biblioteca general, pero pierde el contexto de qué cliente se estaba revisando. + +El mismo problema aplica a client_editor: al entrar al workspace, no hay un back button visible. + +--- + +## Comportamiento esperado + +### Para admin (platform_admin / member) + +Si el proceso tiene `org_id` asignado (proceso de un cliente): + +``` +← Solar Banco [nombre del proceso] [botones de acción] +``` + +El botón "← {org.name}" navega a `/library?org={org_id}`. + +Si el proceso NO tiene `org_id` (proceso sin cliente asignado): + +``` +← Biblioteca [nombre del proceso] [botones de acción] +``` + +Navega a `/library`. + +### Para client_editor / client_viewer + +``` +← Procesos [nombre del proceso] [botones de acción] +``` + +Navega a `/library`. (Para estos roles, `/library` ya muestra solo los procesos de su org.) + +--- + +## Cambios en código + +**ANTES de empezar:** leer el componente del header del workspace para entender la estructura actual del breadcrumb/navegación. También leer cómo el store o el componente accede a la información del proceso actual (nombre, org_id). Verificar si la información de `org_id` y `org_name` está disponible en el estado del proceso cargado. + +### 1. Library route — soporte para query param `?org=` + +En `LibraryPage.tsx` (o el router de la biblioteca), agregar soporte para el query param `org`: + +- Leer `org` de los query params con TanStack Router (`useSearch`) +- Si `org` está presente: al cargar la biblioteca, hacer scroll automático al grupo/cliente cuyo `org_id` coincide, o resaltarlo visualmente +- Si `org` no está presente: comportamiento actual sin cambios + +**No es necesario filtrar la lista** — mostrar todos los clientes como siempre, pero posicionar la vista en el cliente relevante. Esto preserva el contexto sin ocultar información. + +El scroll puede ser simple: `document.getElementById('org-{org_id}')?.scrollIntoView({ behavior: 'smooth', block: 'start' })` con un `useEffect` que se dispara cuando `org` está en los params y la lista cargó. + +### 2. Workspace header — back button contextual + +En el componente del workspace header (WorkspaceHeader, WorkspacePage, o donde esté el breadcrumb): + +Agregar antes del separador `|` y del nombre del proceso un botón de navegación contextual: + +```typescript +// Determinar destino del back button +const backHref = process.orgId + ? `/library?org=${process.orgId}` + : '/library' + +const backLabel = process.orgName // si está disponible en el store + ?? (isClientRole ? 'Procesos' : 'Biblioteca') +``` + +**UI:** botón ghost pequeño con ícono de flecha izquierda (`ArrowLeft` de Lucide) + texto. Consistente con el estilo del header actual. Ejemplo: + +``` +[←] Solar Banco | Conciliaciones ATM [editable] • ... +``` + +Si `process.orgName` no está disponible en el store actual, hay dos opciones: +a) Hacer un fetch adicional de la organización (simple: `supabase.from('organizations').select('name').eq('id', orgId).single()`) al cargar el workspace +b) O usar solo el botón sin nombre ("← Volver") y el admin sabrá dónde lo lleva + +**Preferir opción (a)** si el costo de implementación es bajo (un SELECT adicional en el store de workspace). Si es complejo, usar (b) y registrar como mejora en BACKLOG.md. + +--- + +## Tests + +```typescript +// Mínimo: + +// 1. Back button muestra texto correcto para admin con proceso de cliente +// (mock: process.orgId = 'some-uuid', process.orgName = 'Solar Banco') +// → expect button text to contain 'Solar Banco' o 'Volver' +// → expect link href to contain '/library?org=some-uuid' + +// 2. Back button muestra "Biblioteca" para admin con proceso sin org +// (mock: process.orgId = null) +// → expect href = '/library' + +// 3. Back button muestra "Procesos" para client_editor +// (mock: isClientRole = true) +// → expect href = '/library' +``` + +--- + +## Criterios de validación + +- [ ] Admin en workspace de proceso con cliente → ve "← [nombre del cliente]" en el header +- [ ] Click en ese botón → navega a `/library?org=` → biblioteca hace scroll al cliente correspondiente +- [ ] Admin en workspace de proceso sin cliente asignado → ve "← Biblioteca" +- [ ] client_editor en workspace → ve "← Procesos" +- [ ] El botón no aparece más de una vez, no rompe el layout del header +- [ ] `npm run test` → tests verdes +- [ ] `npm run build` limpio +- [ ] **Validación visual del director antes del OK formal** + +--- + +## NO modificar + +- Edge Functions +- Migraciones +- Lógica de simulación, ROI, PDF, CSV +- `ConfirmInvitePage`, `AdminPage`, `ReportPage` +- `AuthContext` (cubierto en Etapa 7) diff --git a/src/domain/types.ts b/src/domain/types.ts index 7358eb4..dfd6c40 100644 --- a/src/domain/types.ts +++ b/src/domain/types.ts @@ -20,6 +20,7 @@ export interface Process { tags: string[] // 🆕 Sprint 3 — tags libres para clasificación y búsqueda ownerId: string // 🆕 Sprint 4 — owner del proceso (auth.uid() al crear) updatedBy: string // 🆕 Sprint 4 — UUID del usuario que hizo el último update + orgId: string | null // 🆕 Sprint 7 — organización dueña del proceso (null = proceso interno InQ) } export type ActivityType = 'task' | 'subprocess' diff --git a/src/features/import/ImportPage.tsx b/src/features/import/ImportPage.tsx index f5a0a4d..1aa62cd 100644 --- a/src/features/import/ImportPage.tsx +++ b/src/features/import/ImportPage.tsx @@ -91,6 +91,7 @@ export function ImportPage() { tags: [], ownerId: user!.id, updatedBy: user!.id, + orgId: null, } const activityElements = extractActivityElements(graph) diff --git a/src/features/library/LibraryPage.tsx b/src/features/library/LibraryPage.tsx index 71791f6..8c764fd 100644 --- a/src/features/library/LibraryPage.tsx +++ b/src/features/library/LibraryPage.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, useRef, useCallback } from 'react' -import { useNavigate } from '@tanstack/react-router' +import { useNavigate, useSearch } from '@tanstack/react-router' import { Upload, Search, Plus, Building2, FolderX, GitBranch, Clock, TrendingUp, BarChart2, FileX2, X, Settings, @@ -592,13 +592,14 @@ function HomeView({ ? Math.max(...groupProcesses.map((p) => p.updatedAt)) : null return ( - onNavigateToGroup(group.id)} - /> +
+ onNavigateToGroup(group.id)} + /> +
) })} ('home') const [activeGroupId, setActiveGroupId] = useState(null) const [transitioning, setTransitioning] = useState(false) @@ -785,6 +787,16 @@ export function LibraryPage() { load() }, [load, user?.id]) + // Cuando la biblioteca cargó con ?org=, hacer scroll al grupo que contiene ese org + useEffect(() => { + if (!orgParam || isLoading || processes.length === 0) return + const proc = processes.find((p) => p.orgId === orgParam) + if (!proc?.groupId) return + requestAnimationFrame(() => { + document.getElementById(`group-${proc.groupId}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' }) + }) + }, [orgParam, isLoading, processes]) + function navigateToGroup(groupId: string | null) { setTransitioning(true) setTimeout(() => { diff --git a/src/features/workspace/WorkspacePage.tsx b/src/features/workspace/WorkspacePage.tsx index f98f6ed..a4f3b4d 100644 --- a/src/features/workspace/WorkspacePage.tsx +++ b/src/features/workspace/WorkspacePage.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState, useCallback, useMemo, useDeferredValue } f import { useParams, useNavigate } from '@tanstack/react-router' import { Loader2, BarChart3, Play, AlertTriangle, AlertCircle, - PanelRightClose, PanelRightOpen, Home, GitMerge, MousePointer2, + PanelRightClose, PanelRightOpen, ArrowLeft, GitMerge, MousePointer2, Check, Pencil } from 'lucide-react' import { Button } from '@/components/ui/button' @@ -22,6 +22,7 @@ import { useLibraryStore } from '@/store/library-store' import { useAuth } from '@/auth/AuthContext' import { AppHeader } from '@/components/AppHeader' import { ErrorBoundary } from '@/components/ErrorBoundary' +import { supabase } from '@/lib/supabase' type SelectedCategory = 'activity' | 'gateway' | null @@ -67,6 +68,14 @@ export function WorkspacePage() { } }, [user, processId, navigate]) + // Nombre de la organización dueña del proceso (para el back button contextual) + const [orgName, setOrgName] = useState(null) + useEffect(() => { + if (!currentProcess?.orgId) { setOrgName(null); return } + supabase.from('organizations').select('name').eq('id', currentProcess.orgId).single() + .then(({ data }) => { if (data) setOrgName(data.name as string) }) + }, [currentProcess?.orgId]) + // Validación XOR: todos los gateways exclusivos deben sumar 1.0 const xorValidation = useMemo(() => { const invalidIds = gateways @@ -183,6 +192,23 @@ export function WorkspacePage() { ) } + // client_viewer ya fue redirigido antes de este punto; solo verificamos client_editor aquí + const isClientRole = user?.platformRole === 'client_editor' + const backLabel = isClientRole + ? 'Procesos' + : currentProcess.orgId + ? (orgName ?? 'Volver') + : 'Biblioteca' + + function handleBack() { + const orgId = currentProcess?.orgId + if (orgId && !isClientRole) { + void navigate({ to: '/', search: { org: orgId } }) + } else { + void navigate({ to: '/' }) + } + } + // Label del tab "elemento" según lo seleccionado const elementTabLabel = selectedCategory === 'gateway' ? 'Gateway' : 'Actividad' const elementTabIcon = selectedCategory === 'gateway' @@ -196,14 +222,15 @@ export function WorkspacePage() { {/* ── Topbar ─────────────────────────────────────────────────── */}
- - - - - Inicio - + diff --git a/src/persistence/supabase/process-repo.ts b/src/persistence/supabase/process-repo.ts index 9f5b887..0651eb7 100644 --- a/src/persistence/supabase/process-repo.ts +++ b/src/persistence/supabase/process-repo.ts @@ -39,6 +39,7 @@ function fromRow(row: Record): Process { updatedAt: new Date(row.updated_at as string).getTime(), ownerId: (row.owner_id as string) ?? '', updatedBy: (row.updated_by as string) ?? '', + orgId: (row.org_id as string | null) ?? null, } } diff --git a/tests/features/library/library-ui.test.tsx b/tests/features/library/library-ui.test.tsx index b050f7a..5ee9626 100644 --- a/tests/features/library/library-ui.test.tsx +++ b/tests/features/library/library-ui.test.tsx @@ -36,6 +36,7 @@ const { authUserRef, libStoreRef } = vi.hoisted(() => { clientName: null, overhead: 0, isShared: false, + orgId: null, } const sampleGroup = { id: 'grp-1', @@ -72,6 +73,7 @@ vi.mock('@/components/AppHeader', () => ({ AppHeader: () => null })) vi.mock('@tanstack/react-router', () => ({ useNavigate: () => vi.fn(), + useSearch: () => ({}), Link: ({ children }: { children: React.ReactNode }) => <>{children}, })) diff --git a/tests/features/report/components.test.tsx b/tests/features/report/components.test.tsx index 7f80dae..56f85a9 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', + createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: 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', + createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: 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', + createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: 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', + ownerId: 'test-user', updatedBy: 'test-user', orgId: null, } const mockSimulation = { diff --git a/tests/features/report/roi-components.test.tsx b/tests/features/report/roi-components.test.tsx index db58132..ebeab38 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', + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, } const mockSimulationBase = { diff --git a/tests/features/workspace/workspace-back-button.test.ts b/tests/features/workspace/workspace-back-button.test.ts new file mode 100644 index 0000000..4be2586 --- /dev/null +++ b/tests/features/workspace/workspace-back-button.test.ts @@ -0,0 +1,60 @@ +/** + * Tests del back button contextual en el workspace header. + * Verifica que backLabel y backTo se calculen correctamente según rol y orgId del proceso. + */ +import { describe, it, expect } from 'vitest' + +type PlatformRole = 'platform_admin' | 'member' | 'client_editor' | 'client_viewer' | 'guest' + +function resolveBackButton( + platformRole: PlatformRole | undefined, + orgId: string | null, + orgName: string | null, +): { backLabel: string; backTo: string } { + const isClientRole = platformRole === 'client_editor' || platformRole === 'client_viewer' + const backLabel = isClientRole + ? 'Procesos' + : orgId + ? (orgName ?? 'Volver') + : 'Biblioteca' + const backTo = orgId && !isClientRole ? `/?org=${orgId}` : '/' + return { backLabel, backTo } +} + +describe('workspace back button — lógica de backLabel y backTo', () => { + it('1. admin con proceso de cliente → muestra nombre de org y navega a /?org=uuid', () => { + const { backLabel, backTo } = resolveBackButton('platform_admin', 'org-uuid-123', 'Solar Banco') + expect(backLabel).toBe('Solar Banco') + expect(backTo).toBe('/?org=org-uuid-123') + }) + + it('2. admin con proceso de cliente mientras orgName carga → muestra "Volver" como fallback', () => { + const { backLabel, backTo } = resolveBackButton('platform_admin', 'org-uuid-123', null) + expect(backLabel).toBe('Volver') + expect(backTo).toBe('/?org=org-uuid-123') + }) + + it('3. admin sin org asignada → muestra "Biblioteca" y navega a /', () => { + const { backLabel, backTo } = resolveBackButton('platform_admin', null, null) + expect(backLabel).toBe('Biblioteca') + expect(backTo).toBe('/') + }) + + it('4. member sin org → igual que platform_admin', () => { + const { backLabel, backTo } = resolveBackButton('member', null, null) + expect(backLabel).toBe('Biblioteca') + expect(backTo).toBe('/') + }) + + it('5. client_editor con proceso de cliente → muestra "Procesos" y navega a /', () => { + const { backLabel, backTo } = resolveBackButton('client_editor', 'org-uuid-123', 'Solar Banco') + expect(backLabel).toBe('Procesos') + expect(backTo).toBe('/') + }) + + it('6. client_viewer con proceso de cliente → muestra "Procesos" y navega a /', () => { + const { backLabel, backTo } = resolveBackButton('client_viewer', 'org-uuid-123', 'Solar Banco') + expect(backLabel).toBe('Procesos') + expect(backTo).toBe('/') + }) +}) diff --git a/tests/lib/export/csv-export-roi.test.ts b/tests/lib/export/csv-export-roi.test.ts index 7c15e08..cb6c605 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', ...extras, + createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, ...extras, } } diff --git a/tests/lib/export/csv-export.test.ts b/tests/lib/export/csv-export.test.ts index 42f61db..4f67ea2 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', ...extras, + createdAt: 0, updatedAt: 0, groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, ...extras, } } diff --git a/tests/lib/export/measure-sizes.test.ts b/tests/lib/export/measure-sizes.test.ts index 19c161d..cbb92c7 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', + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: 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 d6e1eb6..b70e205 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', + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, } const perActivityActual = [ diff --git a/tests/lib/export/pdf-export-sprint2.test.ts b/tests/lib/export/pdf-export-sprint2.test.ts index 937229b..8bef4a9 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', + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: 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 fd77fd6..1894740 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', + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, } const mockSimulation: Simulation = { diff --git a/tests/lib/export/pdf-orientation.test.ts b/tests/lib/export/pdf-orientation.test.ts index 8fe50ae..6ea5cfb 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', + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: 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 5bb9727..a6817c4 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', + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: 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 42372fb..ae6189f 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', + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: 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 1d635be..b940ac0 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', + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, } const mockSimulation: Simulation = { diff --git a/tests/store/simulation-store-invalidation.test.ts b/tests/store/simulation-store-invalidation.test.ts index 58b2493..1e240b0 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', + groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', orgId: null, } useProcessStore.setState({ currentProcess: proc })