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:
91
tests/e2e/prod-smoke.spec.ts
Normal file
91
tests/e2e/prod-smoke.spec.ts
Normal 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`)
|
||||
})
|
||||
Reference in New Issue
Block a user