/* ============================================================ 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 */}
Panel ejecutivo
{/* 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 (

{title}

{sub &&
{sub}
}
{children}
); } /* —— 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 (
{label}
{a.name}
); } /* —— 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 (
{/* cuadrantes */} {/* ejes */} {/* etiquetas cuadrante */} URGENTE IMPORTANTE OPORTUNIDAD MONITOREAR {/* ejes labels */} PREVALENCIA → PRIORIDAD → {/* burbujas */} {pts.map(p => { const r = 16 + (p.v / 100) * 14; return ( {IHE.byId[p.id].name} ); })}
Lectura ejecutiva
); } Object.assign(window, { ScreenExec, Card, Kpi, PriorityMatrix });