Files
inq-roi-simulador-web/src/features/report/KpiBar.tsx
Marcos Benítez bed161c92a 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>
2026-05-14 01:58:40 -03:00

73 lines
2.6 KiB
TypeScript

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>
)
}