feat: MVP Fase 0 — Process Cost Platform v0.1.0

Plataforma completa de análisis de costos operativos basada en BPMN 2.0.

Funcionalidades incluidas:
- Import de archivos BPMN 2.0 por drag & drop + 3 procesos de ejemplo
- Visualización read-only del diagrama (bpmn-js)
- Configuración de actividades (costo, tiempo, recursos asignados)
- CRUD de recursos (rol, persona, sistema, equipo, insumo)
- Configuración global (moneda, overhead %, nombre, cliente)
- Motor de simulación determinístico agregado con propagación de gateways XOR/AND
- Reporte visual con mapa de calor en dos modos (relativo/absoluto)
- Gráficos: KPIs, top actividades, composición, costo por recurso
- Export PDF (jsPDF + html2canvas) y CSV (papaparse)
- Persistencia en IndexedDB (Dexie)
- 268 tests unitarios/integración + 6 E2E con Playwright
- Deploy: https://process-cost-platform.pages.dev

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 01:58:40 -03:00
commit bed161c92a
97 changed files with 19638 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
/**
* Smoke test de producción — verifica el flujo completo en la URL live.
* Usa el proceso de ejemplo "Aprobación de crédito" (carga via fetch desde el CDN).
*
* Ejecutar: npx playwright test --config=playwright.prod.config.ts
*/
import { test, expect } from '@playwright/test'
import { readFileSync, mkdirSync } from 'fs'
import { resolve } from 'path'
import { fileURLToPath } from 'url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const OUTPUT_DIR = resolve(__dirname, '__output__')
test.beforeAll(() => {
mkdirSync(OUTPUT_DIR, { recursive: true })
})
test('prod-smoke — import → simulate → export PDF', async ({ page }) => {
// ── 1. Landing page carga correctamente ──────────────────────────────────
await page.goto('/')
await expect(page).toHaveTitle(/Process Cost Platform/)
// 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('Proceso lineal simple')).toBeVisible()
await expect(page.getByText('Control de calidad con revisión')).toBeVisible()
// ── 2. Cargar proceso de ejemplo "Proceso lineal simple" ──────────────────
await page.getByText('Proceso lineal simple').click()
// Esperar navegación al workspace
await page.waitForURL(/\/workspace\//, { timeout: 30_000 })
// ── 3. Workspace: diagrama BPMN + botón Simular ───────────────────────────
await page.waitForSelector('button:has-text("Simular")', { timeout: 20_000 })
await page.waitForTimeout(1_500)
// Nombre del proceso visible en la topbar
await expect(page.getByText('simple-linear')).toBeVisible()
const simularBtn = page.getByRole('button', { name: 'Simular' })
await expect(simularBtn).not.toBeDisabled({ timeout: 5_000 })
await simularBtn.click()
// ── 4. Reporte carga ──────────────────────────────────────────────────────
await page.waitForURL(/\/report\//, { timeout: 30_000 })
await page.waitForSelector('button:has-text("Exportar PDF")', { timeout: 30_000 })
await page.waitForSelector('.bpmn-container .djs-group', { timeout: 30_000 })
// KPIs visibles
await expect(page.getByText('COSTO TOTAL ESPERADO')).toBeVisible()
// ── 5. Export PDF ─────────────────────────────────────────────────────────
const pdfPath = resolve(OUTPUT_DIR, 'prod-smoke.pdf')
const pdfDownload = page.waitForEvent('download', { timeout: 90_000 })
await page.getByRole('button', { name: 'Exportar PDF' }).click()
const dl = await pdfDownload
await dl.saveAs(pdfPath)
const pdfBuffer = readFileSync(pdfPath)
const pdfKB = (pdfBuffer.length / 1024).toFixed(1)
console.log(`\n📄 prod-smoke.pdf: ${pdfBuffer.length} bytes (${pdfKB} KB)`)
expect(pdfBuffer.length, 'PDF debe ser > 40 KB').toBeGreaterThan(40_000)
expect(pdfBuffer.length, 'PDF debe ser < 2 MB').toBeLessThan(2_000_000)
// JPEG heatmap embebido
const jpegMarker = Buffer.from([0xff, 0xd8, 0xff])
const hasJpeg = pdfBuffer.includes(jpegMarker)
console.log(` Heatmap JPEG embebido: ${hasJpeg}`)
expect(hasJpeg, 'PDF de prod debe tener heatmap embebido').toBe(true)
// ── 6. Export CSV ─────────────────────────────────────────────────────────
const csvPath = resolve(OUTPUT_DIR, 'prod-smoke.csv')
const csvDownload = page.waitForEvent('download')
await page.getByRole('button', { name: 'Exportar CSV' }).click()
const dlCsv = await csvDownload
await dlCsv.saveAs(csvPath)
const csvText = readFileSync(csvPath, 'utf-8')
expect(csvText, 'CSV debe tener BOM UTF-8').toMatch(/^/)
expect(csvText, 'CSV debe contener encabezado').toContain('Costo total')
console.log(`\n✅ Prod smoke PASSED — https://process-cost-platform.pages.dev`)
console.log(` PDF: ${pdfKB} KB | CSV: ${csvText.length} chars`)
})