/* 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 => ( ))}
); } 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.

{/* Filtros dinámicos */}
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

Ranking de instituciones

{/* Reportes generados */}

Reportes generados

Exportaciones recientes · disponibles por 90 días
{recientes.map(r => ( ))}
IDReporteGeneradoPorFormatoTamaño
{r.id} {r.titulo}
{r.filas} registros
{r.generado} {r.autor} {r.formato.toUpperCase()} {r.peso}
); } Object.assign(window, { ReportesScreen });