Sprint 4 — Etapa 8: reporte de entrega
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled

Tier 1 — Automatizado
Build:  limpio
Lint:  limpio en archivos nuevos de Sprint 4 (separé resource-types.ts de resource-display.tsx para sacar 2 warnings de Fast Refresh; quedan 0 errores/warnings nuevos). Quedó 1 error preexistente en ReportPage.tsx (no tocado desde Sprint 3, fuera de scope) y ~102 warnings no-explicit-any preexistentes en tests viejos.
Tests unitarios:  521/521 verdes
Tests E2E: ⚠️ 43-44/48 verdes (varía levemente entre corridas) — detalle abajo
Auditoría inicial vs. estado final
Antes: 39/39 specs (100%) fallaban — ROJO-AUTH puro, todos por #bpmn-file-input nunca apareciendo (esperaban el flujo pre-Sprint-4 directamente en /).

Decisión tomada (con tu input): Opción A — sesión real. Creaste el usuario martin.bernal@inquality.com.py; yo verifiqué que no tenía fila en public.users y se la inserté con platform_role='platform_admin'. Implementé tests/e2e/setup/auth-setup.ts como globalSetup de Playwright: hace signInWithPassword, inyecta la sesión real en localStorage con el formato exacto que usa supabase-js v2, y genera tests/e2e/setup/auth-state.json (gitignored) en cada corrida.

Fix adicional necesario: todos los specs navegaban a / esperando el input de archivo ahí — ahora vive en /import. Corregí los 18 page.goto('/') → /import, incluyendo reescribir el flujo obsoleto de prod-smoke.spec.ts (esperaba una landing pre-auth que ya no existe).

Resultado: 33→43-44 specs pasan. Quedan 5-6 ROJO-OTRO, con dos causas raíz identificadas (no relacionadas con auth, fuera del mandato de esta etapa que pide no reescribir lógica de tests):

Botón "Guardar" en ActivityPanel queda deshabilitado (3 specs de PDF) — encontré algo extraño: el texto del botón dice "Guardar cambios" (que solo se renderiza si isDirty=true) pero Playwright lo sigue viendo disabled. Esto contradice la lógica simple de disabled={!isDirty} en el componente — sospecho un bug real de re-render en producción, pero diagnosticarlo a fondo requiere debugging en vivo. Recomiendo abrir esto como bug dedicado en un sprint futuro.
2 specs usan IndexedDB/Dexie directamente (export-pdf.spec.ts, validate-dod-etapa4.spec.ts) — Dexie se eliminó completamente en Etapa 4. Estos tests necesitan reescritura para inyectar datos vía Supabase en vez de IndexedDB — eso sí es "reescribir lógica de tests", explícitamente fuera de este alcance.
Los resultados varían levemente entre corridas porque Playwright corre specs en paralelo bajo la misma cuenta de test compartida — puede haber interferencia de datos entre specs concurrentes. Sugiero como mejora futura (no implementada ahora): --workers=1 para diagnóstico determinístico.

Entregables nuevos
tests/e2e/setup/auth-setup.ts — global setup con sesión real
tests/e2e/sprint-4-smoke.spec.ts — 9 tests (4 sin auth: login, redirects de /, /workspace, /recursos; 5 con auth: biblioteca sin 500, sin 406 en simulations, catálogo carga, avatar visible, import navega a workspace)
docs/CHECKLIST_ENTREGA.md — las 4 tiers completas
.env.test (gitignored) + .env.example documentado
.gitignore actualizado (.env.test, auth-state.json)
Polish
Textos de OwnershipBadge y "Compartir con equipo" ya estaban en español — sin cambios.
Tooltip "Disponible próximamente" para "Compartir con equipo": no lo agregué, tal como pedía el propio brief, consultarte antes. ¿Lo querés?
This commit is contained in:
2026-06-19 11:22:50 -03:00
parent f77085c42d
commit 84daf82e65
42 changed files with 701 additions and 50 deletions

View File

@@ -2,3 +2,9 @@
# Obtener desde Supabase Dashboard → Project Settings → API
VITE_SUPABASE_URL=https://<project-ref>.supabase.co
VITE_SUPABASE_ANON_KEY=<anon-public-key>
# Usuario de test para E2E (email/password, NO Google OAuth) — crear en
# Supabase Studio → Authentication, y darle platform_role='platform_admin'
# en la tabla public.users. Va en .env.test (gitignored), no en .env.local.
TEST_USER_EMAIL=test@inquality.com.py
TEST_USER_PASSWORD=<password-del-usuario-de-test>

2
.gitignore vendored
View File

@@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
.env.test
# Editor directories and files
.vscode/*
@@ -26,6 +27,7 @@ dist-ssr
# Playwright outputs
tests/e2e/__output__/
test-results/
tests/e2e/setup/auth-state.json
# Wrangler
.wrangler/

View File

@@ -18,7 +18,13 @@
## Hallazgos activos
*(vacío)*
### [DEFERRED-BACKLOG] Compartir proceso — implementación real diferida hasta primer guest
**Etapa:** 5 (UI placeholder), decisión confirmada post-Etapa 7B
**Estado actual:** `setShared()` en `process-repo.ts` es no-op con comentario explicativo. El botón "Compartir con equipo" en LibraryPage muestra toast de confirmación pero no escribe en `process_access`. Todos los usuarios actuales son `platform_admin` y ven todo por bypass de RLS — el sharing no tiene efecto observable hoy.
**Schema listo:** tabla `process_access (process_id, grantee_type, grantee_id, permission)` + `user_groups` + `group_memberships` + RLS policies que ya usan `process_access` para usuarios no-admin.
**Trigger para implementar:** cuando se agregue el primer usuario `guest` (cliente externo o consultor externo) a la plataforma.
**Pendiente al implementar:** (1) UX de selección — ¿compartir con usuario específico, grupo, o "todo el equipo"? (2) implementación real de `setShared()` con escritura en `process_access`, (3) UI de gestión de grupos.
---

125
docs/CHECKLIST_ENTREGA.md Normal file
View File

@@ -0,0 +1,125 @@
# Checklist de entrega — InQ ROI
> Claude Code ejecuta este checklist ANTES de reportar una etapa como completada.
> El director ejecuta la sección "Validación visual" para el OK formal.
---
## Tier 1 — Automatizado (bloquea entrega si falla)
Correr en este orden. Si cualquier comando falla, no entregar — corregir primero.
```bash
npm run build # 0 errores TypeScript, bundle generado
npm run lint # 0 errores ESLint
npm run test # todos los tests Vitest verdes (reportar N/N)
npx playwright test # todos los E2E verdes (reportar N/N)
```
Reportar en el mensaje de entrega:
- Build: ✅ limpio / ❌ N errores
- Lint: ✅ limpio / ❌ N warnings/errors
- Tests unitarios: ✅ N/N verdes
- Tests E2E: ✅ N/N verdes
**Nota sobre E2E con auth:** los specs que requieren sesión usan `test.use({ storageState: 'tests/e2e/setup/auth-state.json' })`. Ese archivo lo genera el `globalSetup` (`tests/e2e/setup/auth-setup.ts`) automáticamente al correr `npx playwright test`, usando el usuario de test (`TEST_USER_EMAIL`/`TEST_USER_PASSWORD` en `.env.test`, gitignored). Si `.env.test` no existe, el setup avisa por consola y los specs con auth fallan — no es necesario ningún paso manual previo más allá de tener `.env.test` configurado una vez.
---
## Tier 2 — Deploy check (post-deploy en inq-roi.inqualityhq.com)
Abrir DevTools → Network tab. Realizar estas acciones y verificar:
| Acción | Verificar |
|--------|-----------|
| Cargar biblioteca (`/`) | Sin 500, sin 406 en `simulations` |
| Abrir cualquier proceso en workspace | Sin 500, `activities` y `gateways` con 200 |
| Abrir catálogo de recursos (`/recursos`) | Sin 500, tabla carga con datos |
| Exportar PDF (cualquier proceso simulado) | Sin errores en consola, archivo descargado |
---
## Tier 3 — Validación visual (director)
> Esta sección la completa el director. Claude Code NO puede aprobarla.
### Siempre verificar
- [ ] Header muestra avatar del usuario logueado
- [ ] Ningún texto dice "Process Cost Platform" (nombre viejo)
- [ ] Marca "InQ" escrita correctamente (no INQ, no Inq)
- [ ] Botones primarios en naranja `#F59845`
### Por área (verificar si la etapa tocó esa área)
**Biblioteca**
- [ ] Procesos cargan sin spinner infinito
- [ ] "Actualizado por [nombre]" visible en tarjetas
- [ ] Toggle Todos/Míos funciona
**Workspace**
- [ ] BPMN canvas renderiza el diagrama
- [ ] ActivityPanel abre al hacer click en una actividad
- [ ] ResourcesPanel muestra recursos usados en el proceso
**Catálogo de recursos**
- [ ] Tabla muestra recursos con badges de tipo coloreados
- [ ] Búsqueda por nombre funciona
- [ ] Filtro por tipo funciona
- [ ] Crear/editar recurso guarda en Supabase y aparece en la lista
- [ ] Recurso con costo `0.037` muestra `$0.037` (no `$0.04`)
**Auth**
- [ ] Login muestra selector de cuentas de Google
- [ ] Logout redirige a `/login`
- [ ] Sin sesión, cualquier ruta protegida redirige a `/login`
**PDF** (si la etapa tocó PDF)
- [ ] Footer dice "Powered by InQuality"
- [ ] Sin caracteres rotos ni encodings incorrectos
---
## Tier 4 — Integridad de datos (si la etapa tocó schema o repos)
Correr en Supabase Studio SQL Editor:
```sql
-- Sin procesos huérfanos (sin owner)
SELECT COUNT(*) FROM processes WHERE owner_id IS NULL; -- debe ser 0
-- Sin actividades huérfanas
SELECT COUNT(*) FROM activities a
LEFT JOIN processes p ON p.id = a.process_id
WHERE p.id IS NULL; -- debe ser 0
-- Sin recursos con tipo inválido
SELECT DISTINCT type FROM resources;
-- debe mostrar solo: person, role, system, equipment, supply
```
---
## Nota sobre regresiones de PDF
Los tests E2E de PDFs (`etapa-4-pdfs.spec.ts` etc.) son la red de contención para regresiones de PDF.
Si se tocó cualquier cosa de exportación o cálculos, correr específicamente:
```bash
npx playwright test tests/e2e/export-pdf.spec.ts tests/e2e/export-roi.spec.ts
```
---
## Setup del usuario de test E2E (referencia, una sola vez por entorno)
1. Supabase Studio → Authentication → Add user (email/password, NO Google OAuth)
2. SQL Editor:
```sql
insert into public.users (id, name, platform_role)
values ('<uuid del usuario creado>', 'Nombre Test E2E', 'platform_admin');
```
3. Completar `.env.test` (gitignored, ver `.env.example`):
```
TEST_USER_EMAIL=...
TEST_USER_PASSWORD=...
```
4. `npx playwright test` genera `tests/e2e/setup/auth-state.json` automáticamente en cada corrida (también gitignored).

View File

@@ -2,6 +2,8 @@ import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
testIgnore: '**/setup/**',
globalSetup: './tests/e2e/setup/auth-setup.ts',
timeout: 180_000,
use: {
baseURL: 'http://localhost:4173',

View File

@@ -0,0 +1,318 @@
# Sprint 4 — Etapa 8: Tests de integración + batería de entrega + polish
## Contexto
InQ ROI — React 18 + Vite + TypeScript + TanStack Router + Zustand + shadcn/ui + Tailwind.
Etapas 17B completadas. Sprint 4 introdujo auth (Google OAuth + Supabase), PostgreSQL, RLS, catálogo de recursos, y múltiples páginas nuevas.
**Problema crítico detectado:** todos los tests E2E existentes (15 specs en `tests/e2e/`) corren contra `localhost:4173` sin autenticación. Con Sprint 4, todas las rutas protegidas redirigen a `/login`. Es muy probable que la mayoría de los specs existentes estén fallando o saltando pasos silenciosamente.
Esta etapa tiene tres entregables:
1. **Auditoría y corrección** de los tests E2E existentes
2. **Nuevos tests E2E** para los flujos críticos de Sprint 4
3. **`docs/CHECKLIST_ENTREGA.md`** — protocolo exhaustivo que Claude Code ejecuta antes de entregar cualquier etapa, en cualquier sprint futuro
---
## Parte 1 — Auditoría de tests existentes
### 1a. Correr todos los specs actuales y reportar estado
```bash
npx playwright test --reporter=list 2>&1 | tail -50
```
Clasificar cada spec como:
- **VERDE** — pasa sin cambios
- **ROJO-AUTH** — falla porque la app redirige a `/login` antes de que el test llegue al contenido
- **ROJO-OTRO** — falla por otra razón
### 1b. Fix para specs ROJO-AUTH
Los specs que prueban flujos con datos (PDFs, simulaciones, BPMN) necesitan un mecanismo de bypass de auth para tests.
**Estrategia: mock de sesión vía localStorage en global setup**
Crear `tests/e2e/setup/auth-setup.ts`:
```typescript
import { chromium, FullConfig } from '@playwright/test'
async function globalSetup(config: FullConfig) {
// Usar supabase-js directamente con service role para crear sesión de test
// O: inyectar una sesión pre-generada en localStorage antes de que el test navegue
// Opción A (recomendada si hay usuario email/password en Supabase):
// const { data } = await supabase.auth.signInWithPassword({
// email: process.env.TEST_USER_EMAIL,
// password: process.env.TEST_USER_PASSWORD,
// })
// guardar storageState con la sesión
// Opción B (mock mínimo para tests que no necesitan queries reales):
// Inyectar localStorage key de sesión fake para bypasear el guard de AuthContext
}
```
**Prerequisito para Opción A:** crear un usuario de test en Supabase con email/password (NO Google OAuth) con `platform_role = 'platform_admin'`. Las credenciales van en `.env.test` (ignorado por git) y documentadas en `.env.example`:
```
TEST_USER_EMAIL=test@inquality.com.py
TEST_USER_PASSWORD=...
```
**Si Opción A no es viable ahora:** los specs que prueban PDF/screenshots pueden mockear el store de Zustand directamente (los PDFs no necesitan Supabase para generarse — solo necesitan datos en el store). Revisar si se puede inicializar el store con fixtures sin pasar por la DB.
Documentar la decisión tomada en un comentario al inicio del setup.
### 1c. Specs que prueban solo PDFs/screenshots (sin data de Supabase)
Los specs `etapa-4-pdfs.spec.ts` hasta `etapa-11-polish.spec.ts` y `export-pdf.spec.ts` probablemente cargan BPMNs locales y generan PDFs sin tocar Supabase. Para estos:
- Si el único problema es el redirect a `/login`, agregar el mock de sesión del setup
- Si necesitan datos en el store, cargarlos via fixture antes de navegar
No reescribir la lógica de los tests — solo agregar el mecanismo de auth bypass.
---
## Parte 2 — Nuevos tests E2E para Sprint 4
Crear `tests/e2e/sprint-4-smoke.spec.ts`:
### Tests sin auth (deben pasar sin sesión)
```typescript
test('login page renders — Google button visible', async ({ page }) => {
await page.goto('/login')
await expect(page.getByRole('button', { name: /iniciar sesión con google/i })).toBeVisible()
})
test('protected route redirects to login', async ({ page }) => {
await page.goto('/')
await expect(page).toHaveURL(/\/login/)
})
test('protected route /workspace redirects to login', async ({ page }) => {
await page.goto('/workspace/fake-id')
await expect(page).toHaveURL(/\/login/)
})
test('protected route /recursos redirects to login', async ({ page }) => {
await page.goto('/recursos')
await expect(page).toHaveURL(/\/login/)
})
```
### Tests con auth (requieren sesión de test)
Usar el fixture de sesión del global setup:
```typescript
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
test('library loads after auth — no 500 errors', async ({ page }) => {
const errors: string[] = []
page.on('response', r => { if (r.status() >= 500) errors.push(r.url()) })
await page.goto('/')
await page.waitForLoadState('networkidle')
expect(errors).toHaveLength(0)
})
test('library — no 406 errors in simulations queries', async ({ page }) => {
const errors406: string[] = []
page.on('response', r => {
if (r.status() === 406 && r.url().includes('simulations')) errors406.push(r.url())
})
await page.goto('/')
await page.waitForLoadState('networkidle')
expect(errors406).toHaveLength(0)
})
test('catalog page loads at /recursos', async ({ page }) => {
await page.goto('/recursos')
await expect(page.getByRole('heading', { name: /catálogo de recursos/i })).toBeVisible()
})
test('app header shows user avatar', async ({ page }) => {
await page.goto('/')
// El avatar tiene las iniciales del usuario o una imagen
await expect(page.locator('[aria-label*="usuario"]').or(
page.locator('img[alt*="avatar"]')
)).toBeVisible()
})
test('import BPMN — navigates to workspace', async ({ page }) => {
await page.goto('/import')
// Hacer click en "Usar ejemplo" si existe, o subir simple-linear.bpmn
// Verificar que navega al workspace
await expect(page).toHaveURL(/\/workspace\//)
})
```
Agregar solo los tests que sean estables y no flaky. Si un flujo es difícil de automatizar (ej: el import con file upload real), documentarlo como "manual" en el checklist.
---
## Parte 3 — `docs/CHECKLIST_ENTREGA.md`
Crear este archivo. Es el protocolo que Claude Code ejecuta **antes de reportar cualquier etapa como completada**, en este sprint y en todos los futuros.
```markdown
# Checklist de entrega — InQ ROI
> Claude Code ejecuta este checklist ANTES de reportar una etapa como completada.
> El director ejecuta la sección "Validación visual" para el OK formal.
---
## Tier 1 — Automatizado (bloquea entrega si falla)
Correr en este orden. Si cualquier comando falla, no entregar — corregir primero.
\`\`\`bash
npm run build # 0 errores TypeScript, bundle generado
npm run lint # 0 errores ESLint
npm run test # todos los tests Vitest verdes (reportar N/N)
npx playwright test # todos los E2E verdes (reportar N/N)
\`\`\`
Reportar en el mensaje de entrega:
- Build: ✅ limpio / ❌ N errores
- Lint: ✅ limpio / ❌ N warnings/errors
- Tests unitarios: ✅ N/N verdes
- Tests E2E: ✅ N/N verdes
---
## Tier 2 — Deploy check (post-deploy en inq-roi.inqualityhq.com)
Abrir DevTools → Network tab. Realizar estas acciones y verificar:
| Acción | Verificar |
|--------|-----------|
| Cargar biblioteca (`/`) | Sin 500, sin 406 en `simulations` |
| Abrir cualquier proceso en workspace | Sin 500, `activities` y `gateways` con 200 |
| Abrir catálogo de recursos (`/recursos`) | Sin 500, tabla carga con datos |
| Exportar PDF (cualquier proceso simulado) | Sin errores en consola, archivo descargado |
---
## Tier 3 — Validación visual (director)
> Esta sección la completa el director. Claude Code NO puede aprobarla.
### Siempre verificar
- [ ] Header muestra avatar del usuario logueado
- [ ] Ningún texto dice "Process Cost Platform" (nombre viejo)
- [ ] Marca "InQ" escrita correctamente (no INQ, no Inq)
- [ ] Botones primarios en naranja `#F59845`
### Por área (verificar si la etapa tocó esa área)
**Biblioteca**
- [ ] Procesos cargan sin spinner infinito
- [ ] "Actualizado por [nombre]" visible en tarjetas
- [ ] Toggle Todos/Míos funciona
**Workspace**
- [ ] BPMN canvas renderiza el diagrama
- [ ] ActivityPanel abre al hacer click en una actividad
- [ ] ResourcesPanel muestra recursos usados en el proceso
**Catálogo de recursos**
- [ ] Tabla muestra recursos con badges de tipo coloreados
- [ ] Búsqueda por nombre funciona
- [ ] Filtro por tipo funciona
- [ ] Crear/editar recurso guarda en Supabase y aparece en la lista
- [ ] VPS con costo `0.037` muestra `$0.037` (no `$0.04`)
**Auth**
- [ ] Login muestra selector de cuentas de Google
- [ ] Logout redirige a `/login`
- [ ] Sin sesión, cualquier ruta protegida redirige a `/login`
**PDF** (si la etapa tocó PDF)
- [ ] Footer dice "Powered by InQuality"
- [ ] Sin caracteres rotos ni encodings incorrectos
---
## Tier 4 — Integridad de datos (si la etapa tocó schema o repos)
Correr en Supabase Studio SQL Editor:
\`\`\`sql
-- Sin procesos huérfanos (sin owner)
SELECT COUNT(*) FROM processes WHERE owner_id IS NULL; -- debe ser 0
-- Sin actividades huérfanas
SELECT COUNT(*) FROM activities a
LEFT JOIN processes p ON p.id = a.process_id
WHERE p.id IS NULL; -- debe ser 0
-- Sin recursos con tipo inválido
SELECT DISTINCT type FROM resources;
-- debe mostrar solo: person, role, system, equipment, supply
\`\`\`
---
## Nota sobre regresiones de PDF
Los tests E2E de PDFs (`etapa-4-pdfs.spec.ts` etc.) son la red de contención para regresiones de PDF.
Si se tocó cualquier cosa de exportación o cálculos, correr específicamente:
\`\`\`bash
npx playwright test tests/e2e/export-pdf.spec.ts tests/e2e/export-roi.spec.ts
\`\`\`
```
---
## Parte 4 — Polish
Revisar y corregir cualquiera de estos si existe:
- En la biblioteca, si el `OwnershipBadge` todavía tiene texto "Solo vos" / "De otro consultor" en inglés o hardcodeado — corregir a español
- Verificar que el texto de "Compartir con equipo" en el menú ⋮ sigue siendo visible aunque sea no-op (con un tooltip o texto secundario que diga "Disponible próximamente" si el director lo prefiere — **consultar antes de implementar**)
- Revisar que `npm run lint` pasa sin warnings en los archivos nuevos de Sprint 4
---
## Qué NO entra en esta etapa
- Implementación real de `setShared` (diferida — ver OBSERVATIONS.md)
- Keycloak / migración de auth
- Features de producto nuevas
- Cambios en PDF o simulación
---
## Orden de ejecución
1. Correr todos los E2E existentes y clasificar estado (verde/rojo-auth/rojo-otro)
2. Implementar estrategia de auth bypass para tests (Opción A o B según viabilidad)
3. Corregir specs ROJO-AUTH con el nuevo setup
4. Crear `tests/e2e/sprint-4-smoke.spec.ts`
5. Crear `docs/CHECKLIST_ENTREGA.md`
6. Polish (lint warnings, textos)
7. `npm run test` — mantener 521+ tests Vitest verdes
8. `npx playwright test` — reportar estado completo (verde/rojo con razón)
9. Commit: `test(sprint-4/etapa-8): batería E2E Sprint 4 + CHECKLIST_ENTREGA + polish`
---
## Criterios de validación
- [ ] Todos los specs E2E clasificados como verde, rojo-auth, o rojo-otro
- [ ] Specs ROJO-AUTH corregidos con estrategia de auth bypass documentada
- [ ] `tests/e2e/sprint-4-smoke.spec.ts` creado con al menos los 4 tests sin-auth
- [ ] `docs/CHECKLIST_ENTREGA.md` creado con las 4 secciones (Tier 14)
- [ ] `npm run lint` sin warnings en archivos de Sprint 4
- [ ] 521+ tests Vitest verdes
- [ ] E2E: reportar N verdes / M rojos con razón para cada rojo
- [ ] **Validación visual del director** antes del OK formal

View File

@@ -12,7 +12,8 @@ import { useAuth } from '@/auth/AuthContext'
import { supabaseResourceRepo, type ResourceWithCreator } from '@/persistence/supabase/resource-repo'
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
import { formatCostPerHour } from '@/lib/format'
import { RESOURCE_TYPE_LABELS, RESOURCE_TYPE_STYLES, ResourceTypeAvatar } from './resource-display'
import { RESOURCE_TYPE_LABELS, RESOURCE_TYPE_STYLES } from './resource-types'
import { ResourceTypeAvatar } from './resource-display'
import { ResourceFormSheet } from './ResourceFormSheet'
import type { Resource, ResourceType } from '@/domain/types'

View File

@@ -6,7 +6,7 @@ import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { RESOURCE_TYPE_LABELS } from './resource-display'
import { RESOURCE_TYPE_LABELS } from './resource-types'
import type { Resource, ResourceType } from '@/domain/types'
interface ResourceFormState {

View File

@@ -1,28 +1,6 @@
import { User, Briefcase, Server, Wrench, Package, type LucideIcon } from 'lucide-react'
import { RESOURCE_TYPE_STYLES } from './resource-types'
import type { ResourceType } from '@/domain/types'
export const RESOURCE_TYPE_LABELS: Record<ResourceType, string> = {
person: 'Persona',
role: 'Rol',
system: 'Sistema',
equipment: 'Equipo',
supply: 'Insumo',
}
interface ResourceTypeStyle {
icon: LucideIcon
bg: string
fg: string
}
export const RESOURCE_TYPE_STYLES: Record<ResourceType, ResourceTypeStyle> = {
person: { icon: User, bg: '#FEF3E7', fg: '#F59845' },
role: { icon: Briefcase, bg: '#FEF3E7', fg: '#D97706' },
system: { icon: Server, bg: '#EEF2FF', fg: '#6366F1' },
equipment: { icon: Wrench, bg: '#F0FDF4', fg: '#16A34A' },
supply: { icon: Package, bg: '#F1F5F9', fg: '#64748B' },
}
export function ResourceTypeAvatar({ type, size = 32 }: { type: ResourceType; size?: number }) {
const { icon: Icon, bg, fg } = RESOURCE_TYPE_STYLES[type]
return (

View File

@@ -0,0 +1,24 @@
import { User, Briefcase, Server, Wrench, Package, type LucideIcon } from 'lucide-react'
import type { ResourceType } from '@/domain/types'
export const RESOURCE_TYPE_LABELS: Record<ResourceType, string> = {
person: 'Persona',
role: 'Rol',
system: 'Sistema',
equipment: 'Equipo',
supply: 'Insumo',
}
export interface ResourceTypeStyle {
icon: LucideIcon
bg: string
fg: string
}
export const RESOURCE_TYPE_STYLES: Record<ResourceType, ResourceTypeStyle> = {
person: { icon: User, bg: '#FEF3E7', fg: '#F59845' },
role: { icon: Briefcase, bg: '#FEF3E7', fg: '#D97706' },
system: { icon: Server, bg: '#EEF2FF', fg: '#6366F1' },
equipment: { icon: Wrench, bg: '#F0FDF4', fg: '#16A34A' },
supply: { icon: Package, bg: '#F1F5F9', fg: '#64748B' },
}

View File

@@ -6,7 +6,8 @@ import { ScrollArea } from '@/components/ui/scroll-area'
import { useProcessStore } from '@/store/process-store'
import { useAuth } from '@/auth/AuthContext'
import { formatCostPerHour } from '@/lib/format'
import { RESOURCE_TYPE_LABELS, ResourceTypeAvatar } from '@/features/resources/resource-display'
import { RESOURCE_TYPE_LABELS } from '@/features/resources/resource-types'
import { ResourceTypeAvatar } from '@/features/resources/resource-display'
import { ResourceFormSheet } from '@/features/resources/ResourceFormSheet'
import type { Resource } from '@/domain/types'

View File

@@ -102,6 +102,7 @@ export const supabaseProcessRepo = {
// No-op en Sprint 4: la implementación real (insertar en process_access) llega cuando existan guests.
// El método existe para que la UI de "Compartir con equipo" funcione sin escritura a DB.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async setShared(_processId: string, _shared: boolean): Promise<void> {
return
},

View File

@@ -9,6 +9,8 @@ import { fileURLToPath } from 'url'
import { PDFParse } from 'pdf-parse'
import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUT_DIR = resolve(__dirname, './__output__')
@@ -35,7 +37,7 @@ async function getOrientations(buffer: Buffer): Promise<string[]> {
}
test('etapa-11-roi.pdf — polish final página 1 ROI', async ({ page }) => {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'medium-with-gateways.bpmn'))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -13,6 +13,8 @@ import { mkdirSync, readFileSync } from 'fs'
import { fileURLToPath } from 'url'
import { PDFParse } from 'pdf-parse'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUT_DIR = resolve(__dirname, './__output__')
@@ -22,7 +24,7 @@ test.beforeAll(() => { mkdirSync(OUT_DIR, { recursive: true }) })
// ─── Helpers ──────────────────────────────────────────────────────────────────
async function importBpmn(page: import('@playwright/test').Page, filename: string) {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, filename))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -16,6 +16,8 @@ import { fileURLToPath } from 'url'
import { PDFParse } from 'pdf-parse'
import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUT_DIR = resolve(__dirname, './__output__')
@@ -44,7 +46,7 @@ async function getPageOrientations(buffer: Buffer): Promise<Array<'portrait' | '
}
async function importBpmn(page: import('@playwright/test').Page, filename: string) {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, filename))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -18,6 +18,8 @@ import { fileURLToPath } from 'url'
import { PDFParse } from 'pdf-parse'
import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUT_DIR = resolve(__dirname, './__output__')
@@ -46,7 +48,7 @@ async function getPageOrientations(buffer: Buffer): Promise<Array<'portrait' | '
}
async function importBpmn(page: import('@playwright/test').Page, filename: string) {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, filename))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -18,6 +18,8 @@ import { fileURLToPath } from 'url'
import { PDFParse } from 'pdf-parse'
import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUT_DIR = resolve(__dirname, './__output__')
@@ -46,7 +48,7 @@ async function getPageOrientations(buffer: Buffer): Promise<Array<'portrait' | '
}
async function setupAndSimulate(page: import('@playwright/test').Page) {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'medium-with-gateways.bpmn'))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -19,6 +19,8 @@ import { fileURLToPath } from 'url'
import { PDFParse } from 'pdf-parse'
import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUT_DIR = resolve(__dirname, './__output__')
@@ -45,7 +47,7 @@ async function getPageOrientations(buffer: Buffer): Promise<Array<'portrait' | '
}
async function setupAndSimulate(page: import('@playwright/test').Page) {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'medium-with-gateways.bpmn'))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -13,6 +13,8 @@ import { resolve } from 'path'
import { mkdirSync } from 'fs'
import { fileURLToPath } from 'url'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const SCREENSHOTS_DIR = resolve(__dirname, '../screenshots/etapa-9')
@@ -20,7 +22,7 @@ const SCREENSHOTS_DIR = resolve(__dirname, '../screenshots/etapa-9')
test.beforeAll(() => { mkdirSync(SCREENSHOTS_DIR, { recursive: true }) })
async function loadProcess(page: import('@playwright/test').Page, filename: string) {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, filename))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -14,6 +14,8 @@ import { PDFParse } from 'pdf-parse'
import sharp from 'sharp'
import { PDFDocument, PDFName, PDFRawStream } from 'pdf-lib'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUTPUT_DIR = resolve(__dirname, '__output__')
@@ -34,7 +36,7 @@ async function parsePdf(buffer: Buffer): Promise<{ numpages: number; text: strin
// ─── Helper: importa un BPMN, simula y llega al reporte ──────────────────────
async function importAndSimulate(page: import('@playwright/test').Page, bpmnFile: string) {
await page.goto('/')
await page.goto('/import')
// Subir el BPMN vía el input oculto (equivalente a drag-and-drop programático)
const fileInput = page.locator('#bpmn-file-input')
@@ -308,7 +310,7 @@ test('Heatmap con gradiente real — medium-with-gateways.bpmn', async ({ page }
}
// ── 1. Importar BPMN y navegar al workspace ─────────────────────────────────
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'medium-with-gateways.bpmn'))
await page.waitForURL(/\/workspace\//)

View File

@@ -21,6 +21,8 @@ import { resolve } from 'path'
import { fileURLToPath } from 'url'
import { PDFParse } from 'pdf-parse'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUTPUT_DIR = resolve(__dirname, '__output__')
@@ -39,7 +41,7 @@ async function parsePdf(buffer: Buffer): Promise<{ numpages: number; text: strin
// ─── Helper: importar BPMN y llegar al workspace ──────────────────────────────
async function importBpmn(page: import('@playwright/test').Page, bpmnFile: string) {
await page.goto('/')
await page.goto('/import')
const fileInput = page.locator('#bpmn-file-input')
await fileInput.setInputFiles(resolve(BPMN_DIR, bpmnFile))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })

View File

@@ -17,6 +17,8 @@ import { resolve } from 'path'
import { mkdirSync } from 'fs'
import { fileURLToPath } from 'url'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUT_DIR = resolve(__dirname, './__output__')
@@ -26,7 +28,7 @@ test.beforeAll(() => { mkdirSync(OUT_DIR, { recursive: true }) })
// ─── Helpers ──────────────────────────────────────────────────────────────────
async function importBpmn(page: import('@playwright/test').Page, filename: string) {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, filename))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -9,6 +9,8 @@ import { readFileSync, mkdirSync } from 'fs'
import { resolve } from 'path'
import { fileURLToPath } from 'url'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const OUTPUT_DIR = resolve(__dirname, '__output__')
@@ -17,15 +19,12 @@ test.beforeAll(() => {
})
test('prod-smoke — import → simulate → export PDF', async ({ page }) => {
// ── 1. Landing page carga correctamente ──────────────────────────────────
await page.goto('/')
// ── 1. Página de import carga correctamente (Sprint 4: requiere sesión) ───
await page.goto('/import')
await expect(page).toHaveTitle(/InQ ROI/)
// Verificar heading principal
await expect(page.getByText('Cuantificá el costo operativo')).toBeVisible({ timeout: 10_000 })
// Verificar que los 3 procesos de ejemplo están presentes
await expect(page.getByText('Aprobación de crédito')).toBeVisible()
await expect(page.getByText('Aprobación de crédito')).toBeVisible({ timeout: 10_000 })
await expect(page.getByText('Proceso lineal simple')).toBeVisible()
await expect(page.getByText('Control de calidad con revisión')).toBeVisible()

View File

@@ -14,6 +14,8 @@ import { resolve } from 'path'
import { mkdirSync } from 'fs'
import { fileURLToPath } from 'url'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUT_DIR = resolve(__dirname, '../screenshots/etapa-1')
@@ -21,7 +23,7 @@ const OUT_DIR = resolve(__dirname, '../screenshots/etapa-1')
test.beforeAll(() => { mkdirSync(OUT_DIR, { recursive: true }) })
test('screenshot workspace — CTAs naranjas visibles', async ({ page }) => {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'simple-linear.bpmn'))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })
@@ -31,7 +33,7 @@ test('screenshot workspace — CTAs naranjas visibles', async ({ page }) => {
})
test('screenshot switch ON — switch de automatización naranja', async ({ page }) => {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'simple-linear.bpmn'))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })
@@ -50,7 +52,7 @@ test('screenshot switch ON — switch de automatización naranja', async ({ page
})
test('screenshot report-tabs — tab activo naranja', async ({ page }) => {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'medium-with-gateways.bpmn'))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -16,6 +16,8 @@ import { resolve } from 'path'
import { mkdirSync } from 'fs'
import { fileURLToPath } from 'url'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUT_DIR = resolve(__dirname, '../screenshots/etapa-3')
@@ -25,7 +27,7 @@ test.beforeAll(() => { mkdirSync(OUT_DIR, { recursive: true }) })
// ─── Helpers ──────────────────────────────────────────────────────────────────
async function importBpmn(page: import('@playwright/test').Page, filename: string) {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, filename))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -0,0 +1,83 @@
// Global setup de Playwright — Sprint 4 Etapa 8.
//
// Decisión tomada: Opción A (sesión real contra Supabase con usuario de test
// email/password, platform_role='platform_admin' en public.users). Permite que
// los specs E2E ejerciten el flujo real de auth + RLS + queries, no un mock.
//
// Usuario de test creado manualmente en Supabase Studio (no vía signup automático).
// Credenciales en .env.test (gitignored) — ver .env.example para el formato.
//
// Genera tests/e2e/setup/auth-state.json: storageState de Playwright con la
// sesión inyectada en localStorage bajo la clave sb-{project_ref}-auth-token,
// el mismo formato que usa supabase-js v2 y que AuthContext.getUserFromStorage()
// espera leer. Los specs que necesitan auth usan:
// test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
import { chromium } from '@playwright/test'
import { createClient } from '@supabase/supabase-js'
import { readFileSync, existsSync } from 'fs'
import { resolve } from 'path'
import { fileURLToPath } from 'url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const ROOT = resolve(__dirname, '../../..')
const STATE_PATH = resolve(__dirname, 'auth-state.json')
function loadEnvFile(filename: string): Record<string, string> {
const path = resolve(ROOT, filename)
if (!existsSync(path)) return {}
const out: Record<string, string> = {}
for (const line of readFileSync(path, 'utf-8').split('\n')) {
const trimmed = line.trim()
if (!trimmed || trimmed.startsWith('#')) continue
const idx = trimmed.indexOf('=')
if (idx === -1) continue
out[trimmed.slice(0, idx)] = trimmed.slice(idx + 1)
}
return out
}
async function globalSetup() {
const local = loadEnvFile('.env.local')
const test = loadEnvFile('.env.test')
const supabaseUrl = local.VITE_SUPABASE_URL
const supabaseAnonKey = local.VITE_SUPABASE_ANON_KEY
const email = test.TEST_USER_EMAIL
const password = test.TEST_USER_PASSWORD
if (!supabaseUrl || !supabaseAnonKey) {
console.warn('[auth-setup] VITE_SUPABASE_URL/ANON_KEY no encontradas en .env.local — specs con auth se saltarán storageState')
return
}
if (!email || !password) {
console.warn('[auth-setup] TEST_USER_EMAIL/PASSWORD no encontradas en .env.test — specs con auth se saltarán storageState')
return
}
const supabase = createClient(supabaseUrl, supabaseAnonKey)
const { data, error } = await supabase.auth.signInWithPassword({ email, password })
if (error || !data.session) {
console.error('[auth-setup] Login de usuario de test falló:', error?.message)
return
}
const projectRef = new URL(supabaseUrl).hostname.split('.')[0]
const storageKey = `sb-${projectRef}-auth-token`
const browser = await chromium.launch()
const page = await browser.newPage()
await page.goto('http://localhost:4173/login')
await page.evaluate(
([key, session]) => {
window.localStorage.setItem(key, JSON.stringify(session))
},
[storageKey, data.session] as const
)
await page.context().storageState({ path: STATE_PATH })
await browser.close()
console.log(`[auth-setup] Sesión de test guardada en ${STATE_PATH}`)
}
export default globalSetup

View File

@@ -10,6 +10,8 @@ import { fileURLToPath } from 'url'
import { PDFParse } from 'pdf-parse'
import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUT_DIR = resolve(__dirname, './__output__/sprint-1-5-final')
@@ -36,7 +38,7 @@ async function getOrientations(buffer: Buffer): Promise<string[]> {
}
async function setupFullScenario(page: import('@playwright/test').Page) {
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'medium-with-gateways.bpmn'))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 })

View File

@@ -0,0 +1,77 @@
/**
* Sprint 4 — Etapa 8: smoke tests de los flujos críticos introducidos por el sprint
* (auth, biblioteca con Supabase, catálogo de recursos).
*
* Los tests "sin auth" verifican el guard de rutas protegidas — corren sin storageState.
* Los tests "con auth" usan la sesión real generada por tests/e2e/setup/auth-setup.ts.
*/
import { test, expect } from '@playwright/test'
// ─── Sin auth — deben pasar sin sesión ────────────────────────────────────────
test.describe('Sin sesión', () => {
test('login page renders — botón de Google visible', async ({ page }) => {
await page.goto('/login')
await expect(page.getByRole('button', { name: /iniciar sesión con google/i })).toBeVisible()
})
test('protected route / redirects to login', async ({ page }) => {
await page.goto('/')
await expect(page).toHaveURL(/\/login/)
})
test('protected route /workspace redirects to login', async ({ page }) => {
await page.goto('/workspace/fake-id')
await expect(page).toHaveURL(/\/login/)
})
test('protected route /recursos redirects to login', async ({ page }) => {
await page.goto('/recursos')
await expect(page).toHaveURL(/\/login/)
})
})
// ─── Con auth — requieren sesión de test ──────────────────────────────────────
test.describe('Con sesión', () => {
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
test('library loads after auth — sin errores 500', async ({ page }) => {
const errors: string[] = []
page.on('response', (r) => { if (r.status() >= 500) errors.push(r.url()) })
await page.goto('/')
await page.waitForLoadState('networkidle')
expect(errors).toHaveLength(0)
})
test('library — sin errores 406 en queries de simulations', async ({ page }) => {
const errors406: string[] = []
page.on('response', (r) => {
if (r.status() === 406 && r.url().includes('simulations')) errors406.push(r.url())
})
await page.goto('/')
await page.waitForLoadState('networkidle')
expect(errors406).toHaveLength(0)
})
test('catalog page loads at /recursos', async ({ page }) => {
await page.goto('/recursos')
await expect(page.getByRole('heading', { name: /catálogo de recursos/i })).toBeVisible()
})
test('app header shows user avatar', async ({ page }) => {
await page.goto('/')
// El trigger del dropdown de usuario tiene title={user.name} (ver AppHeader.tsx)
await expect(page.locator('header button[title]').first()).toBeVisible()
})
test('import BPMN — navega al workspace', async ({ page }) => {
await page.goto('/import')
await page.getByText('Proceso lineal simple').click()
await expect(page).toHaveURL(/\/workspace\//, { timeout: 15_000 })
})
})

View File

@@ -22,6 +22,8 @@ import { PDFParse } from 'pdf-parse'
import sharp from 'sharp'
import { PDFDocument, PDFName, PDFRawStream } from 'pdf-lib'
test.use({ storageState: 'tests/e2e/setup/auth-state.json' })
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const BPMN_DIR = resolve(__dirname, '../../public/sample-processes')
const OUTPUT_DIR = resolve(__dirname, '__output__')
@@ -109,7 +111,7 @@ const AUTO_TIME = 5
test('DoD Etapa 4 — genera y valida los 6 archivos de export para medium-with-gateways', async ({ page }) => {
// ── Fase 1: importar BPMN e inyectar configuración en IndexedDB ─────────────
await page.goto('/')
await page.goto('/import')
await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'medium-with-gateways.bpmn'))
await page.waitForURL(/\/workspace\//, { timeout: 15_000 })
const processId = page.url().split('/workspace/')[1]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 110 KiB