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

24
src/persistence/db.ts Normal file
View File

@@ -0,0 +1,24 @@
import Dexie, { type Table } from 'dexie'
import type { Process, Activity, GatewayConfig, Resource, Simulation } from '@/domain/types'
export class ProcessCostDb extends Dexie {
processes!: Table<Process>
activities!: Table<Activity>
gateways!: Table<GatewayConfig>
resources!: Table<Resource>
simulations!: Table<Simulation>
constructor() {
super('ProcessCostPlatform')
this.version(1).stores({
processes: 'id, name, updatedAt',
activities: 'id, processId, bpmnElementId',
gateways: 'id, processId, bpmnElementId',
resources: 'id, processId, name',
simulations: 'id, processId, executedAt',
})
}
}
export const db = new ProcessCostDb()

View File

@@ -0,0 +1,114 @@
import { db } from './db'
import type { Process, Activity, GatewayConfig, Resource, Simulation } from '@/domain/types'
// ─── Process ─────────────────────────────────────────────────────────────────
export const processRepo = {
async save(process: Process): Promise<void> {
await db.processes.put(process)
},
async getById(id: string): Promise<Process | undefined> {
return db.processes.get(id)
},
async getAll(): Promise<Process[]> {
return db.processes.orderBy('updatedAt').reverse().toArray()
},
async delete(id: string): Promise<void> {
await db.transaction('rw', [db.processes, db.activities, db.gateways, db.resources, db.simulations], async () => {
await db.processes.delete(id)
await db.activities.where('processId').equals(id).delete()
await db.gateways.where('processId').equals(id).delete()
await db.resources.where('processId').equals(id).delete()
await db.simulations.where('processId').equals(id).delete()
})
},
}
// ─── Activity ─────────────────────────────────────────────────────────────────
export const activityRepo = {
async save(activity: Activity): Promise<void> {
await db.activities.put(activity)
},
async saveMany(activities: Activity[]): Promise<void> {
await db.activities.bulkPut(activities)
},
async getByProcess(processId: string): Promise<Activity[]> {
return db.activities.where('processId').equals(processId).toArray()
},
async getByBpmnElementId(processId: string, bpmnElementId: string): Promise<Activity | undefined> {
return db.activities
.where('[processId+bpmnElementId]')
.equals([processId, bpmnElementId])
.first()
},
async deleteByProcess(processId: string): Promise<void> {
await db.activities.where('processId').equals(processId).delete()
},
}
// ─── GatewayConfig ────────────────────────────────────────────────────────────
export const gatewayRepo = {
async save(gateway: GatewayConfig): Promise<void> {
await db.gateways.put(gateway)
},
async saveMany(gateways: GatewayConfig[]): Promise<void> {
await db.gateways.bulkPut(gateways)
},
async getByProcess(processId: string): Promise<GatewayConfig[]> {
return db.gateways.where('processId').equals(processId).toArray()
},
async deleteByProcess(processId: string): Promise<void> {
await db.gateways.where('processId').equals(processId).delete()
},
}
// ─── Resource ─────────────────────────────────────────────────────────────────
export const resourceRepo = {
async save(resource: Resource): Promise<void> {
await db.resources.put(resource)
},
async getByProcess(processId: string): Promise<Resource[]> {
return db.resources.where('processId').equals(processId).toArray()
},
async delete(id: string): Promise<void> {
await db.resources.delete(id)
},
async deleteByProcess(processId: string): Promise<void> {
await db.resources.where('processId').equals(processId).delete()
},
}
// ─── Simulation ───────────────────────────────────────────────────────────────
export const simulationRepo = {
async save(simulation: Simulation): Promise<void> {
await db.simulations.put(simulation)
},
async getLatestByProcess(processId: string): Promise<Simulation | undefined> {
return db.simulations
.where('processId')
.equals(processId)
.last()
},
async getByProcess(processId: string): Promise<Simulation[]> {
return db.simulations.where('processId').equals(processId).toArray()
},
}