/* global React */ // Instituciones (HU.4.3) — Catálogo en formato lista, editar y eliminar const { useState: useStateIn, useMemo: useMemoIn } = React; const PAGE_SIZE = 8; // ---------------- Advanced filters config ---------------- const ADV_DEFAULT = { tipo: [], // array de tipos seleccionados (multi-select) estado: 'todos', // todos | activo | desactivado orden: 'az', // az | za }; // Grupos de chips (estado + orden). El "tipo de institución" se filtra con un // multi-select aparte (permite elegir varios a la vez) y sus opciones vienen del catálogo. const ADV_GROUPS = [ { key: 'estado', label: 'Estado', icon: 'CheckCircle2', options: [ { v: 'todos', l: 'Todos' }, { v: 'activo', l: 'Activas' }, { v: 'desactivado', l: 'Desactivadas' }, ], }, { key: 'orden', label: 'Ordenar por nombre', icon: 'ListChecks', options: [ { v: 'az', l: 'A → Z' }, { v: 'za', l: 'Z → A' }, ], }, ]; // Convierte "Hoy" / "Ayer" / "12 abr" / "Nunca" a una fecha con año (para el Excel). const MESES_AB = ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']; function fmtUltimoAcceso(v) { if (!v || String(v).toLowerCase() === 'nunca') return v || ''; const hoy = new Date(); const fmt = (d) => `${d.getDate()} ${MESES_AB[d.getMonth()]} ${d.getFullYear()}`; const s = String(v).toLowerCase().trim(); if (s === 'hoy') return fmt(hoy); if (s === 'ayer') { const d = new Date(hoy); d.setDate(d.getDate() - 1); return fmt(d); } const m = /^(\d{1,2})\s+([a-záéíóú]{3,})/.exec(s); if (m) { const mi = MESES_AB.indexOf(m[2].slice(0, 3)); return `${parseInt(m[1], 10)} ${MESES_AB[mi >= 0 ? mi : 0]} ${hoy.getFullYear()}`; } return v; } function InstitucionesScreen({ user, onOpenNueva }) { const I = window.Icons; const [items, setItems] = window.usePersistentState('instituciones', () => window.INSTITUCIONES_CATALOG.slice()); const [q, setQ] = useStateIn(''); const [page, setPage] = useStateIn(1); // Advanced filters const [adv, setAdv] = useStateIn(ADV_DEFAULT); const [advOpen, setAdvOpen] = useStateIn(false); // Form view (crear / editar) + activar/desactivar confirmation state const [view, setView] = useStateIn('list'); // 'list' | 'form' const [editing, setEditing] = useStateIn(null); // null = creando const [toggling, setToggling] = useStateIn(null); const [deleting, setDeleting] = useStateIn(null); const tipoOptions = (window.cgrData ? window.cgrData.tiposInstitucion() : []); const openCreate = () => { setEditing(null); setView('form'); }; const openEdit = (inst) => { setEditing(inst); setView('form'); }; const backToList = () => { setView('list'); setEditing(null); }; const advCount = (adv.tipo.length ? 1 : 0) + (adv.estado !== 'todos' ? 1 : 0) + (adv.orden !== 'az' ? 1 : 0); const filtered = useMemoIn(() => { const list = items.filter(i => { if (q && !`${i.nombre} ${i.sigla} ${i.contacto}`.toLowerCase().includes(q.toLowerCase())) return false; if (adv.tipo.length && adv.tipo.indexOf(i.tipo) === -1) return false; if (adv.estado !== 'todos' && i.estado !== adv.estado) return false; return true; }); list.sort((a, b) => a.nombre.localeCompare(b.nombre, 'es', { sensitivity: 'base' })); if (adv.orden === 'za') list.reverse(); return list; }, [items, q, adv]); const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); const safePage = Math.min(page, totalPages); const pageStart = (safePage - 1) * PAGE_SIZE; const pageItems = filtered.slice(pageStart, pageStart + PAGE_SIZE); const onSearch = (v) => { setQ(v); setPage(1); }; const setAdvKey = (k, v) => { setAdv(prev => ({ ...prev, [k]: v })); setPage(1); }; const setAdvTipo = (arr) => { setAdv(prev => ({ ...prev, tipo: arr })); setPage(1); }; const freshAdv = () => ({ tipo: [], estado: 'todos', orden: 'az' }); const resetAdv = () => { setAdv(freshAdv()); setPage(1); }; const clearAll = () => { setQ(''); setAdv(freshAdv()); setPage(1); }; const handleSave = (data) => { if (editing) { // Edición de una institución existente setItems(list => list.map(it => it.id === editing.id ? { ...it, ...data } : it)); } else { // Alta de una nueva institución const id = (data.sigla || '').toUpperCase().replace(/[^A-Z0-9]+/g, '-').replace(/^-|-$/g, ''); setItems(list => [ { id: id || `INST-${list.length + 1}`, usuarios: 0, compromisos: 0, ultimo: 'Nunca', ...data }, ...list, ]); setPage(1); } backToList(); }; const handleToggleConfirm = () => { if (!toggling) return; const next = toggling.estado === 'desactivado' ? 'activo' : 'desactivado'; setItems(list => list.map(it => it.id === toggling.id ? { ...it, estado: next } : it)); setToggling(null); }; const handleDeleteConfirm = () => { if (!deleting) return; const inst = deleting; setItems(list => list.filter(it => it.id !== inst.id)); // Cascada: eliminar también los usuarios vinculados a esta institución. try { const sig = (inst.sigla || '').toLowerCase(); const nom = (inst.nombre || '').toLowerCase(); const usuarios = window.cgrStore.load('usuarios', window.USUARIOS || []); const restante = usuarios.filter(u => { const seg = (u.institucion || '').toLowerCase().split('·')[0].trim(); return !(seg === sig || seg === nom || (nom && nom.indexOf(seg) === 0) || (nom && seg.indexOf(nom) === 0)); }); window.cgrStore.save('usuarios', restante); } catch (e) {} setDeleting(null); }; // Active advanced-filter chips for the bar above the list const activeAdv = []; if (adv.tipo.length) activeAdv.push({ key: 'tipo', group: 'Tipo', label: adv.tipo.length === 1 ? adv.tipo[0] : `${adv.tipo.length} tipos`, reset: () => setAdvTipo([]) }); ['estado', 'orden'].forEach(k => { if (adv[k] !== ADV_DEFAULT[k]) { const group = ADV_GROUPS.find(g => g.key === k); const opt = group.options.find(o => o.v === adv[k]); activeAdv.push({ key: k, group: group.label, label: opt.l, reset: () => setAdvKey(k, ADV_DEFAULT[k]) }); } }); // Vista de formulario (crear / editar) — ocupa la misma área que la lista if (view === 'form') { return ( ); } return (
Catálogo institucional

Instituciones

Consulta, edita y administra las instituciones del Estado habilitadas como reportantes.
Búscalas por nombre o sigla y gestiona cada ficha desde la lista.

{/* Search row + advanced filters toggle */}
onSearch(e.target.value)} />
{/* Advanced filter panel */} {advOpen && (
Filtros avanzados Encuentra una institución más rápido: filtra por tipo o estado y ordena la lista alfabéticamente. Los cambios se aplican al instante y se combinan con el buscador.
Tipo de institución
{ADV_GROUPS.map(g => { const Icon = I[g.icon] || I.Filter; return (
{g.label}
{g.options.map(o => ( ))}
); })}
)} {/* Active advanced filter chips (when panel is collapsed) */} {!advOpen && advCount > 0 && (
{advCount} filtro{advCount === 1 ? '' : 's'} activo{advCount === 1 ? '' : 's'}: {activeAdv.map(a => ( ))}
)} {filtered.length === 0 ? (

Sin resultados

No hay instituciones que coincidan con la combinación actual de filtros. {(q || advCount > 0) && ( <> {' '} . )}

) : ( <>
Mostrando {pageStart + 1}–{Math.min(pageStart + PAGE_SIZE, filtered.length)} de {filtered.length} instituciones
)} {/* Activar / Desactivar confirmation */} {toggling && ( setToggling(null)} onConfirm={handleToggleConfirm} /> )} {/* Eliminar (cascada) */} {deleting && ( setDeleting(null)} onConfirm={handleDeleteConfirm} /> )}
); } // ---------------- Eliminar institución (cascada) ---------------- function DeleteInstitucionDialog({ inst, onCancel, onConfirm }) { const I = window.Icons; return (
e.stopPropagation()}>

¿Eliminar “{inst.sigla}” definitivamente?

Esta acción no se puede deshacer. Se eliminará {inst.nombre} y todo lo relacionado: sus usuarios, las asignaciones de compromisos, y los avances y evidencias vinculados a esta institución.

); } // Ícono de encendido/apagado (toggle) — reusable function PowerGlyph({ size = 15 }) { return ( ); } // ---------------- Pager ---------------- function Pager({ page, total, onChange }) { if (total <= 1) return null; const set = new Set([1, total, page, page - 1, page + 1]); const pages = [...set].filter(p => p >= 1 && p <= total).sort((a, b) => a - b); const items = []; let prev = 0; for (const p of pages) { if (p - prev > 1) items.push({ sep: true, key: `s-${p}` }); items.push({ p, key: `p-${p}` }); prev = p; } return (
{items.map(it => it.sep ? … : )}
); } // ---------------- Activar / Desactivar confirmation modal ---------------- function ToggleInstitucionDialog({ inst, onCancel, onConfirm }) { const I = window.Icons; const isActive = inst.estado !== 'desactivado'; return (
e.stopPropagation()}>
{isActive ? : }

{isActive ? `¿Desactivar “${inst.sigla}”?` : `¿Activar “${inst.sigla}”?`}

{isActive ? ( <>{inst.nombre} perderá el acceso a la plataforma, pero conserva todo su historial de avances y evidencias. Podrás reactivarla cuando lo necesites. ) : ( <>{inst.nombre} recuperará el acceso a la plataforma y volverá a recibir asignaciones y alertas de plazos. )}

); } Object.assign(window, { InstitucionesScreen });