/* global React */
// Reportes y analítica — tablero de gestión general de la Contraloría.
// KPIs y gráficas dinámicas (filtros que recalculan en vivo) + exportar + historial.
const { useState: useStateRp, useMemo: useMemoRp } = React;
// ---------- Gauge circular para KPIs ----------
function RpArc({ value, tone }) {
const r = 22, c = 2 * Math.PI * r;
const offset = c * (1 - (value || 0) / 100);
const color =
tone === 'ok' ? 'var(--sem-success)' :
tone === 'warn' ? 'var(--sem-warning-bg)' :
tone === 'danger' ? 'var(--sem-danger)' :
'var(--cgr-primary)';
return (
);
}
function RpKpi({ icon, label, num, sub, tone, gauge }) {
const I = window.Icons;
return (
{React.createElement(I[icon], { size: 16 })}
{label}
{num}
{gauge != null &&
}
{sub &&
{sub}
}
);
}
// ---------- Donut de semáforo (estado de cumplimiento) ----------
function RpDonut({ ok, partial, bad }) {
const total = (ok + partial + bad) || 1;
const r = 52, cx = 70, cy = 70, sw = 18, c = 2 * Math.PI * r;
const segs = [
{ v: ok, color: 'var(--sem-success)' },
{ v: partial, color: 'var(--sem-warning-bg)' },
{ v: bad, color: 'var(--sem-danger)' },
];
let acc = 0;
const cumpl = Math.round((ok / total) * 100);
return (
{segs.map((s, i) => {
const frac = s.v / total;
const dash = frac * c;
const el = (
);
acc += frac;
return el;
})}
{cumpl}%
cumplidos
Cumplidos {ok}
Parciales {partial}
No cumplidos {bad}
);
}
// ---------- Línea de evolución ----------
function RpSpark({ data, w = 360, h = 96 }) {
if (!data || !data.length) return null;
const pad = 6;
const min = Math.min(...data.map(d => d.v)) - 4;
const max = Math.max(...data.map(d => d.v)) + 4;
const step = (w - pad * 2) / (data.length - 1);
const pts = data.map((d, i) => {
const x = pad + i * step;
const y = h - pad - ((d.v - min) / (max - min)) * (h - pad * 2);
return [x, y];
});
const pathD = pts.map((p, i) => (i === 0 ? 'M' : 'L') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');
const areaD = pathD + ` L${pts[pts.length - 1][0].toFixed(1)} ${h - pad} L${pts[0][0].toFixed(1)} ${h - pad} Z`;
const last = pts[pts.length - 1];
return (
);
}
// ---------- Barras apiladas por mecanismo ----------
function RpMechBars({ items }) {
return (
{items.map(m => {
const okPct = (m.ok / m.total) * 100;
const prPct = (m.partial / m.total) * 100;
const bdPct = (m.bad / m.total) * 100;
return (
{m.label}
{m.total}
{m.ok}
{m.partial}
{m.bad}
);
})}
);
}
// ---------- Ranking de instituciones (toggle carga / cumplimiento) ----------
function RpInstBars({ items, metric }) {
const max = Math.max(...items.map(i => i.total));
const sorted = metric === 'cumplimiento'
? [...items].sort((a, b) => b.cumpl - a.cumpl)
: [...items].sort((a, b) => b.total - a.total);
return (
{sorted.slice(0, 7).map(it => {
const w = metric === 'cumplimiento' ? it.cumpl : (it.total / max) * 100;
const tone = it.cumpl >= 75 ? 'is-ok' : it.cumpl >= 55 ? 'is-warn' : 'is-danger';
return (
{it.inst}
{metric === 'cumplimiento' ? it.cumpl + '%' : it.total}
{metric === 'cumplimiento' ? it.total + ' comp.' : it.cumpl + '%'}
);
})}
);
}
function RpSeg({ value, options, onChange }) {
return (
{options.map(o => (
onChange(o.v)}>{o.l}
))}
);
}
function ReportesScreen({ roleId, user, onOpenDetalle }) {
const I = window.Icons;
const MECH = window.MECANISMO_DIST || [];
const INST = window.INST_RANKING || [];
const EVO = window.EVO_SERIES || [];
const COMP = window.COMPROMISOS || [];
const recientes = window.REPORTES_RECIENTES || [];
const org = (user && user.org) || 'Contraloría General de la República';
const esInst = roleId === 'institucion';
const [mech, setMech] = useStateRp('Todos');
const [periodo, setPeriodo] = useStateRp('2024 — 2026');
const [rango, setRango] = useStateRp('todo');
const [instMetric, setInstMetric] = useStateRp('carga');
// Recalcular distribución según el mecanismo seleccionado
const dist = useMemoRp(() => {
if (mech !== 'Todos') {
const m = MECH.find(x => x.label === mech);
if (m) return { total: m.total, ok: m.ok, partial: m.partial, bad: m.bad };
}
return MECH.reduce((a, m) => ({ total: a.total + m.total, ok: a.ok + m.ok, partial: a.partial + m.partial, bad: a.bad + m.bad }), { total: 0, ok: 0, partial: 0, bad: 0 });
}, [mech]);
const pct = (n) => dist.total ? Math.round((n / dist.total) * 100) : 0;
const cumplGlobal = mech === 'Todos'
? (EVO.length ? EVO[EVO.length - 1].v : 0)
: (dist.total ? Math.round(((dist.ok + dist.partial * 0.5) / dist.total) * 100) : 0);
const evoData = rango === '12m' ? EVO.slice(-12) : EVO;
const evoNow = evoData.length ? evoData[evoData.length - 1].v : 0;
const evoDelta = evoData.length ? evoNow - evoData[0].v : 0;
// Plazos / riesgo (sobre los compromisos en seguimiento)
const plazos = useMemoRp(() => ({
venc: COMP.filter(c => c.d < 0).length,
sem: COMP.filter(c => c.d >= 0 && c.d <= 7).length,
mes: COMP.filter(c => c.d > 7 && c.d <= 30).length,
mas: COMP.filter(c => c.d > 30).length,
}), [mech]);
return (
{org}
Reportes y analítica
Panel de gestión del cumplimiento país en los mecanismos anticorrupción evaluados —
MESICIC, UNCAC y OCDE. Indicadores y gráficas para el seguimiento general de la Contraloría;
los filtros recalculan todo en vivo.
Exportar (PDF)
Exportar (Excel)
{/* Filtros dinámicos */}
Mecanismo
setMech(e.target.value)}>
Todos los mecanismos
{MECH.map(m => {m.label} )}
Período
setPeriodo(e.target.value)}>
2024 — 2026
2026 (año en curso)
Último trimestre
Histórico completo
Mostrando {dist.total} compromisos{mech !== 'Todos' ? ` · ${mech}` : ''}
{/* KPIs */}
= 0 ? '+' : ''}${evoDelta} pts en el período`} gauge={cumplGlobal} />
{/* Fila 1: semáforo + evolución + plazos */}
Estado de cumplimiento {mech === 'Todos' ? 'País' : mech}
Evolución del cumplimiento
{evoNow}%
{evoDelta >= 0 ? '↑' : '↓'} {Math.abs(evoDelta)} pts {rango === '12m' ? '(12 meses)' : 'acumulado'}
{evoData[0] && evoData[0].m} {evoData[evoData.length - 1] && evoData[evoData.length - 1].m}
Plazos y riesgo en seguimiento
{plazos.venc} Vencidos
{plazos.sem} Vencen ≤ 7 días
{plazos.mes} 8 – 30 días
{plazos.mas} Más de 30 días
{plazos.venc + plazos.sem} compromisos requieren atención esta semana.
{/* Fila 2: por mecanismo + ranking instituciones */}
Cumplimiento por mecanismo 3 mecanismos
{/* Reportes generados */}
Reportes generados
Exportaciones recientes · disponibles por 90 días
Ver todos
ID Reporte Generado Por Formato Tamaño
{recientes.map(r => (
{r.id}
{r.titulo}{r.filas} registros
{r.generado}
{r.autor}
{r.formato.toUpperCase()}
{r.peso}
))}
);
}
Object.assign(window, { ReportesScreen });