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:
72
src/features/report/KpiBar.tsx
Normal file
72
src/features/report/KpiBar.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { DollarSign, TrendingUp, Clock, LayoutList } from 'lucide-react'
|
||||
import { formatCurrency, formatPercent, formatMinutes } from '@/lib/format'
|
||||
import type { SimulationResult } from '@/domain/types'
|
||||
|
||||
interface KpiBarProps {
|
||||
result: SimulationResult
|
||||
currency: string
|
||||
}
|
||||
|
||||
interface KpiCardProps {
|
||||
label: string
|
||||
value: string
|
||||
sub?: string
|
||||
icon: React.ReactNode
|
||||
iconColor: string
|
||||
}
|
||||
|
||||
function KpiCard({ label, value, sub, icon, iconColor }: KpiCardProps) {
|
||||
return (
|
||||
<div data-testid="kpi-card" className="bg-white rounded-xl border border-slate-200 shadow-sm p-6 flex items-center gap-4">
|
||||
<div className={`shrink-0 p-3 rounded-lg ${iconColor}`}>
|
||||
{icon}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-medium text-slate-500 uppercase tracking-wide mb-1">{label}</p>
|
||||
<p className="text-2xl font-bold text-slate-900 font-mono leading-tight truncate">{value}</p>
|
||||
{sub && <p className="text-xs text-slate-400 mt-0.5">{sub}</p>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function KpiBar({ result, currency }: KpiBarProps) {
|
||||
const directPct = result.totalCost > 0
|
||||
? formatPercent(result.totalDirectCost / result.totalCost * 100)
|
||||
: '—'
|
||||
const indirectPct = result.totalCost > 0
|
||||
? formatPercent(result.totalIndirectCost / result.totalCost * 100)
|
||||
: '—'
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<KpiCard
|
||||
label="Costo total esperado"
|
||||
value={formatCurrency(result.totalCost, currency)}
|
||||
icon={<DollarSign className="h-5 w-5 text-primary" />}
|
||||
iconColor="bg-primary/10"
|
||||
/>
|
||||
<KpiCard
|
||||
label="Directo / Indirecto"
|
||||
value={formatCurrency(result.totalDirectCost, currency)}
|
||||
sub={`${directPct} directo · ${formatCurrency(result.totalIndirectCost, currency)} (${indirectPct}) overhead`}
|
||||
icon={<TrendingUp className="h-5 w-5 text-emerald-600" />}
|
||||
iconColor="bg-emerald-50"
|
||||
/>
|
||||
<KpiCard
|
||||
label="Tiempo total esperado"
|
||||
value={formatMinutes(result.totalTimeMinutes)}
|
||||
sub={`${result.totalTimeMinutes.toFixed(0)} minutos ponderados por probabilidad`}
|
||||
icon={<Clock className="h-5 w-5 text-amber-600" />}
|
||||
iconColor="bg-amber-50"
|
||||
/>
|
||||
<KpiCard
|
||||
label="Actividades costeadas"
|
||||
value={String(result.perActivity.length)}
|
||||
sub={result.perActivity.length === 1 ? 'actividad en el proceso' : 'actividades en el proceso'}
|
||||
icon={<LayoutList className="h-5 w-5 text-slate-500" />}
|
||||
iconColor="bg-slate-100"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user