/* ============================================================
DASHBOARD EJECUTIVO — Panel de dirección
Filtros · KPIs · Radar por área · Distribución · Mapa de calor
Ranking · Histórico · Matriz de prioridades · Recomendaciones
Exporta: ScreenExec
============================================================ */
const IHE = window.IH;
const { useState: useStateE, useEffect: useEffectE } = React;
function ScreenExec({ onBack, onReport, onLogout }) {
const [f, setF] = useStateE({ area: 'Todas', sucursal: 'Todas', puesto: 'Todos', periodo: 'Mes actual' });
const [data, setData] = useStateE(null);
const [loading, setLoading] = useStateE(true);
const [error, setError] = useStateE(null);
const set = (k, v) => setF(p => ({ ...p, [k]: v }));
useEffectE(() => {
setLoading(true);
setError(null);
IHA.getDashboardReport(f)
.then(d => { setData(d); setLoading(false); })
.catch(e => { setError(e.message || 'Error al cargar datos'); setLoading(false); });
}, [f]);
const avg = data?.scores || {};
const dist = data?.distribution || {};
const rank = data?.ranking || [];
const hist = data?.historical || [];
const reco = data?.recommendations || {};
const total = data?.summary?.total || 0;
const evolIdx = data?.summary?.evolIndex || 0;
const dominant = data?.summary?.dominant || 'guerrero';
const rseries = buildRadarSeries(data, f);
const puestos = ['Todos', ...new Set(Object.values(IHE.puestosByArea).flat())];
return (
{/* header */}
{/* filtros */}
Filtrar
set('area', v)} />
set('sucursal', v)} />
set('puesto', v)} />
set('periodo', v)} />
{loading ? 'Cargando…' : `${total} colaboradores`}
{error && (
{error}
)}
{/* KPIs */}
{/* fila 1: radar + distribución */}
{rseries.map((s, i) => (
{s.label}
))}
{/* fila 2: mapa de calor */}
{/* fila 3: ranking + histórico */}
{/* fila 4: matriz de prioridades */}
{/* fila 5: recomendaciones org */}
);
}
function buildRadarSeries(data, f) {
if (!data?.scores) return [];
const avg = data.scores;
const rank = data.ranking || [];
if (f.area === 'Todas') {
const out = [{ scores: avg, color: 'var(--gold)', fillOpacity: 0.12, label: 'Organización', width: 2.5 }];
if (rank[0]) out.push({ scores: rank[0].scores, color: IHE.byId.sabio.hex, fillOpacity: 0.04, dash: '4 4', dots: false, label: `${rank[0].area} (líder)` });
const bottom = rank[rank.length - 1];
if (bottom && bottom !== rank[0]) out.push({ scores: bottom.scores, color: IHE.byId.guerrero.hex, fillOpacity: 0.04, dash: '4 4', dots: false, label: `${bottom.area} (rezago)` });
return out;
}
return [{ scores: avg, color: 'var(--gold)', fillOpacity: 0.13, label: f.area, width: 2.5 }];
}
/* —— Filtro select —— */
function Filt({ label, value, opts, onChange, disabled }) {
return (
);
}
/* —— Card —— */
function Card({ title, sub, children, style }) {
return (
);
}
/* —— KPI —— */
function Kpi({ label, value, unit, accent, small, sub }) {
return (
{label}
{value}
{unit && {unit}}
{sub &&
{sub}
}
);
}
function KpiArch({ label, id }) {
const a = IHE.byId[id] || IHE.byId.guerrero;
return (
);
}
/* —— Distribución mini (junto a la dona) —— */
function DistMini({ dist, total }) {
const ranked = [...IHE.order].sort((a, b) => (dist[b] || 0) - (dist[a] || 0));
return (
{ranked.map(id => {
const a = IHE.byId[id];
const v = dist[id] || 0;
return (
{a.name}
{v}
);
})}
);
}
/* —— Leyenda mapa de calor —— */
function HeatLegend() {
return (
Riesgo
{['huerfano', 'vagabundo', 'martir'].map(id => )}
·
Evolución
{['guerrero', 'sabio', 'mago'].map(id => )}
Valor 0–100 · opacidad ∝ presencia
);
}
/* —— Matriz de prioridades (2×2 consultoría) —— */
function PriorityMatrix({ avg }) {
const W = 760, H = 420, pad = 54;
const innerW = W - pad * 2, innerH = H - pad * 2;
const priorityOf = (id, v) => {
const risk = ['huerfano', 'vagabundo', 'martir'].includes(id);
return risk ? v : (100 - v);
};
const pts = IHE.order.map(id => {
const v = avg[id] || 0;
const prev = v;
const pr = priorityOf(id, v);
return { id, x: pad + (prev / 100) * innerW, y: pad + innerH - (pr / 100) * innerH, v, pr, prev };
});
const cx = pad + innerW / 2, cy = pad + innerH / 2;
return (
);
}
Object.assign(window, { ScreenExec, Card, Kpi, PriorityMatrix });