/* global React */ // Alertas del sistema — centro de control del administrador. // Bandeja (lista única con filtros + acciones) + Configuración de reglas. const { useState: useStateAl, useMemo: useMemoAl } = React; const SEV_META = { dark: { grupo: 'vencidas', label: 'Vencida' }, danger: { grupo: 'urgentes', label: 'Urgente' }, warn: { grupo: 'proximas', label: 'Recordatorio' }, info: { grupo: 'proximas', label: 'Aviso' }, muted: { grupo: 'sistema', label: 'Sistema' }, }; const SEV_RANK = { dark: 0, danger: 1, warn: 2, info: 3, muted: 4 }; const TIPO_META = { vencimiento: { icon: 'Clock', label: 'Vencimiento' }, sin_avance: { icon: 'AlertTriangle', label: 'Sin avance' }, devolucion: { icon: 'AlertCircle', label: 'Devolución' }, sistema: { icon: 'Settings', label: 'Sistema' }, }; function codeLabel(c) { return c === 'sin' ? '—' : c === 'devol' ? '↩' : c; } function AlertasScreen({ roleId, user, onOpenDetalle }) { const I = window.Icons; const all = window.getAlertas ? (window.getAlertas(roleId) || []) : (window.ALERTAS_ADMIN || []); const org = (user && user.org) || 'Contraloría General de la República'; const esAdmin = roleId !== 'institucion'; const [tab, setTab] = useStateAl('bandeja'); const [readMap, setReadMap] = window.usePersistentState('alertas_read_' + roleId, () => { const m = {}; all.forEach(a => { m[a.id] = a.read; }); return m; }); const [sel, setSel] = useStateAl({}); const [q, setQ] = useStateAl(''); const [sev, setSev] = useStateAl('todas'); const [tipo, setTipo] = useStateAl('todos'); const [inst, setInst] = useStateAl('todas'); const [soloNoLeidas, setSolo] = useStateAl(false); const [orden, setOrden] = useStateAl('urgencia'); const [cfg, setCfg] = window.usePersistentState('alertas_cfg', () => (window.ALERTAS_CONFIG || []).reduce((m, h) => { m[h.code] = h.enabled; return m; }, {})); const [pol, setPol] = window.usePersistentState('alertas_pol', { escala: true, copiaAdmin: true, resumen: true }); const grupoDe = (a) => (SEV_META[a.severity] || SEV_META.info).grupo; const isRead = (a) => !!readMap[a.id]; const counts = useMemoAl(() => { const c = { todas: all.length, noLeidas: 0, vencidas: 0, urgentes: 0, proximas: 0, sistema: 0 }; all.forEach(a => { if (!readMap[a.id]) c.noLeidas++; const g = grupoDe(a); if (c[g] != null) c[g]++; }); return c; }, [all, readMap]); const instituciones = useMemoAl(() => { const s = []; all.forEach(a => { if (a.institucion && s.indexOf(a.institucion) === -1) s.push(a.institucion); }); return s.sort(); }, [all]); const filtered = useMemoAl(() => { let list = all.filter(a => { if (sev !== 'todas' && grupoDe(a) !== sev) return false; if (tipo !== 'todos' && a.tipo !== tipo) return false; if (inst !== 'todas' && a.institucion !== inst) return false; if (soloNoLeidas && readMap[a.id]) return false; if (q.trim()) { const t = (a.recomendacion + ' ' + a.institucion + ' ' + a.compromisoId).toLowerCase(); if (t.indexOf(q.trim().toLowerCase()) === -1) return false; } return true; }); if (orden === 'urgencia') list = [...list].sort((a, b) => (SEV_RANK[a.severity] - SEV_RANK[b.severity])); return list; }, [all, sev, tipo, inst, soloNoLeidas, q, orden, readMap]); const selIds = Object.keys(sel).filter(k => sel[k]); const toggleSel = (id) => setSel(s => ({ ...s, [id]: !s[id] })); const clearSel = () => setSel({}); const selectAllVisible = () => { const s = {}; filtered.forEach(a => { s[a.id] = true; }); setSel(s); }; const toggleRead = (id) => setReadMap(m => ({ ...m, [id]: !m[id] })); const markRead = (ids) => setReadMap(m => { const n = { ...m }; ids.forEach(id => { n[id] = true; }); return n; }); const markAllRead = () => markRead(all.map(a => a.id)); const limpiarFiltros = () => { setQ(''); setSev('todas'); setTipo('todos'); setInst('todas'); setSolo(false); }; return (
{org}

Alertas del sistema

Centro de control de las notificaciones automáticas sobre los plazos de carga de avances. Filtra, prioriza y gestiona todas las alertas desde un solo lugar; cada una enlaza al compromiso correspondiente.

{/* Pestañas */}
{esAdmin && ( )}
{tab === 'bandeja' ? ( <> {/* KPIs */}
Sin leer
{counts.noLeidas}
de {counts.todas} activas
Vencidas
{counts.vencidas}
sin avance · escalamiento
Urgentes
{counts.urgentes}
vencen en ≤ 24 h
Próximas
{counts.proximas}
recordatorios y avisos
{/* Filtros */}
setQ(e.target.value)} /> {q && }
{[ { v: 'todas', l: 'Todas', n: counts.todas }, { v: 'vencidas', l: 'Vencidas', n: counts.vencidas }, { v: 'urgentes', l: 'Urgentes', n: counts.urgentes }, { v: 'proximas', l: 'Próximas', n: counts.proximas }, { v: 'sistema', l: 'Sistema', n: counts.sistema }, ].map(s => ( ))}
{/* Barra de selección */} {selIds.length > 0 ? (
{selIds.length} seleccionada{selIds.length === 1 ? '' : 's'}
) : (
{filtered.length} de {counts.todas} alertas {(q || sev !== 'todas' || tipo !== 'todos' || inst !== 'todas' || soloNoLeidas) && ( )}
)} {/* Lista */} {filtered.length === 0 ? (

No hay alertas con estos filtros

Ajusta la búsqueda o limpia los filtros para ver el conjunto completo.

) : ( )} ) : ( )}
); } // ---------------- Item de alerta ---------------- function AlxItem({ a, read, selected, onToggleSel, onToggleRead, onOpen, esAdmin }) { const I = window.Icons; const tm = TIPO_META[a.tipo] || { icon: 'Bell', label: 'Notificación' }; const ICON = I[tm.icon]; return (
  • {codeLabel(a.code)}
    {tm.label} {a.mecanismo} {a.compromisoId} {a.institucion} {!read && } {a.sentAt}
    {a.msg}
    Plazo {a.plazo}
    {esAdmin && a.tipo === 'vencimiento' && }
  • ); } // ---------------- Configuración ---------------- function ConfigAlertas({ cfg, setCfg, pol, setPol }) { const I = window.Icons; const hitos = window.ALERTAS_CONFIG || []; const sevDe = (code) => code === 'D-7' ? 'info' : code === 'D-3' ? 'warn' : (code === 'D-1' || code === 'D') ? 'danger' : 'dark'; return ( <>
    Hitos de recordatorio

    Definí qué avisos dispara el sistema sobre el plazo interno de cada acción y por qué canales.

    {hitos.map(h => { const on = !!cfg[h.code]; return (
    {h.code}
    {h.label}{h.desc}
    Correo Plataforma
    ); })}
    Escalamiento y resumen

    Reglas globales de qué pasa cuando una alerta no se atiende y cómo se mantiene informado al administrador.

    setPol(p => ({ ...p, escala: !p.escala }))} icon="AlertTriangle" title="Escalar al punto focal si pasa D+1 sin avance" desc="Si el plazo vence sin carga, se notifica al contacto principal de la institución." /> setPol(p => ({ ...p, copiaAdmin: !p.copiaAdmin }))} icon="Shield" title="Copiar al administrador en alertas críticas" desc="Las alertas D-1, D y vencidas también llegan a la CGR." /> setPol(p => ({ ...p, resumen: !p.resumen }))} icon="Inbox" title="Resumen diario de alertas al administrador" desc="Un correo cada mañana con lo pendiente y lo vencido del día." />
    ); } function PolRow({ on, onToggle, icon, title, desc }) { const I = window.Icons; const ICON = I[icon] || I.Bell; return (
    {title}{desc}
    ); } Object.assign(window, { AlertasScreen });