86 lines
3.6 KiB
TypeScript
86 lines
3.6 KiB
TypeScript
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',
|
||
})
|
||
|
||
// Sprint 1: agrega campos de automatización a Activity y volumetría a Process.
|
||
// El upgrade popula datos existentes con defaults para garantizar invariante de tipo.
|
||
this.version(2)
|
||
.stores({
|
||
processes: 'id, name, updatedAt',
|
||
activities: 'id, processId, bpmnElementId',
|
||
gateways: 'id, processId, bpmnElementId',
|
||
resources: 'id, processId, name',
|
||
simulations: 'id, processId, executedAt',
|
||
})
|
||
.upgrade(async (tx) => {
|
||
await tx.table('activities').toCollection().modify((act) => {
|
||
if (act.automatable === undefined) act.automatable = false
|
||
if (act.automatedCostFixed === undefined) act.automatedCostFixed = 0
|
||
if (act.automatedTimeMinutes === undefined) act.automatedTimeMinutes = 0
|
||
})
|
||
await tx.table('processes').toCollection().modify((proc) => {
|
||
if (proc.annualFrequency === undefined) proc.annualFrequency = 1000
|
||
if (proc.analysisHorizonYears === undefined) proc.analysisHorizonYears = 3
|
||
if (proc.automationInvestment === undefined) proc.automationInvestment = 0
|
||
})
|
||
})
|
||
|
||
// Sprint 2: migra ActivityResourceAssignment.utilizationPercent → utilizationMinutes + units.
|
||
// Los stores no cambian — assignments están embebidos en Activity, no son tabla propia.
|
||
this.version(3)
|
||
.stores({
|
||
processes: 'id, name, updatedAt',
|
||
activities: 'id, processId, bpmnElementId',
|
||
gateways: 'id, processId, bpmnElementId',
|
||
resources: 'id, processId, name',
|
||
simulations: 'id, processId, executedAt',
|
||
})
|
||
.upgrade(async (tx) => {
|
||
await tx.table('activities').toCollection().modify((act) => {
|
||
if (!Array.isArray(act.assignedResources)) {
|
||
act.assignedResources = []
|
||
return
|
||
}
|
||
act.assignedResources = act.assignedResources.map((assignment: Record<string, unknown>) => {
|
||
if ('utilizationPercent' in assignment) {
|
||
// Heurística: minutos = round(executionTimeMinutes × utilizationPercent)
|
||
// Si executionTimeMinutes no está disponible o es 0, usar 0 (conservador)
|
||
const execMinutes = typeof act.executionTimeMinutes === 'number' ? act.executionTimeMinutes : 0
|
||
const utilizationMinutes = Math.round(execMinutes * (assignment.utilizationPercent as number))
|
||
return { resourceId: assignment.resourceId, utilizationMinutes, units: 1 }
|
||
}
|
||
if ('utilizationMinutes' in assignment) {
|
||
// Ya está en nuevo formato (idempotencia)
|
||
return {
|
||
resourceId: assignment.resourceId,
|
||
utilizationMinutes: assignment.utilizationMinutes ?? 0,
|
||
units: assignment.units ?? 1,
|
||
}
|
||
}
|
||
// Fallback defensivo
|
||
return { resourceId: assignment.resourceId, utilizationMinutes: 0, units: 1 }
|
||
})
|
||
})
|
||
})
|
||
}
|
||
}
|
||
|
||
export const db = new ProcessCostDb()
|