Reporte Vacunación MSPAS (V38 - Filter Logic Fixed)

V37 + Corrección lógica de filtros: "Seleccionar Todo" reactivo y filtrado exacto de valores seleccionados.

当前为 2025-11-30 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Reporte Vacunación MSPAS (V38 - Filter Logic Fixed)
// @namespace    http://tampermonkey.net/
// @version      38.0
// @description  V37 + Corrección lógica de filtros: "Seleccionar Todo" reactivo y filtrado exacto de valores seleccionados.
// @author       Gemini AI & Jonatan H.
// @match        *://*.oraclecloudapps.com/ords/r/vacunacion/vacunacion/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    /* jshint esversion: 6 */

    // ================= CONFIGURACIÓN =================
    const ID_NOMBRE_DISPLAY = "P701_NOMBRE_DISPLAY";
    const ID_CUI_DISPLAY    = "P701_CUI_P_DISPLAY";
    const ID_SEXO_DISPLAY   = "P701_SEXO_DISPLAY";
    const ID_NAC_DISPLAY    = "P701_NACIONALIDAD_DISPLAY";
    const ID_FEC_NAC_DISP   = "P701_FECHA_NACIMIENTO_DISPLAY";

    const ID_FECHA_INPUT    = "P702_FECHA_VACUNA_input";
    const ID_VACUNA_SELECT  = "P702_ID_CONFIGURACION_VACUNA_DOSIS";

    const TEXTO_EXITO = "El registro ha sido procesado exitosamente";
    const ID_INPUT_SERVICIO_PARAM = "P216_IDTS";
    // =================================================

    // --- UTILS ---
    async function obtenerFechaHoraReal() {
        try {
            const response = await fetch(window.location.href, { method: 'HEAD', cache: 'no-store' });
            const dateHeader = response.headers.get('Date');
            if (dateHeader) return new Date(dateHeader);
        } catch (e) { }
        return new Date();
    }

    function obtenerDatos(tipo) {
        const key = tipo === 'regular' ? 'reporte_regular_diario' : 'reporte_vacunacion_diario';
        return JSON.parse(localStorage.getItem(key) || '[]');
    }
    function obtenerAcumulado(tipo) {
        const key = tipo === 'regular' ? 'reporte_regular_acumulado' : 'reporte_vacunacion_acumulado';
        return JSON.parse(localStorage.getItem(key) || '[]');
    }
    function getServicioGuardado() { return localStorage.getItem('mspas_servicio_actual') || "Detectando..."; }

    function guardarRegistro(nuevoRegistro, tipo) {
        const diario = obtenerDatos(tipo);
        diario.push(nuevoRegistro);
        const keyDiario = tipo === 'regular' ? 'reporte_regular_diario' : 'reporte_vacunacion_diario';
        localStorage.setItem(keyDiario, JSON.stringify(diario));

        const acumulado = obtenerAcumulado(tipo);
        acumulado.push(nuevoRegistro);
        const keyAcum = tipo === 'regular' ? 'reporte_regular_acumulado' : 'reporte_vacunacion_acumulado';
        localStorage.setItem(keyAcum, JSON.stringify(acumulado));

        localStorage.setItem('mspas_fecha_activa', new Date().toLocaleDateString('es-GT'));
        window.dispatchEvent(new Event('storage'));
    }

    function mostrarNotificacion(mensaje, colorFondo = "#27ae60", duracion = 3000) {
        const notif = document.createElement('div');
        notif.innerText = mensaje;
        notif.style = `position: fixed; top: 20px; right: 20px; background: ${colorFondo}; color: white; padding: 12px 20px; border-radius: 5px; box-shadow: 0 4px 10px rgba(0,0,0,0.3); z-index: 999999; font-family: Arial; font-weight: bold; opacity: 0; transition: opacity 0.3s;`;
        document.body.appendChild(notif);
        requestAnimationFrame(() => { notif.style.opacity = "1"; });
        setTimeout(() => { notif.style.opacity = "0"; setTimeout(() => notif.remove(), 300); }, duracion);
    }

    function capturarDatosCompletos() {
        const leer = (id) => {
            let el = document.getElementById(id);
            if (!el && window.parent && window.parent.document) {
                el = window.parent.document.getElementById(id);
            }
            return el ? el.innerText.trim() : "";
        };

        const rawNac = leer(ID_FEC_NAC_DISP);
        let fechaNac = rawNac;
        let edad = "";

        if (rawNac.includes("(")) {
            const p = rawNac.split("(");
            fechaNac = p[0].trim();
            edad = p[1].replace(")", "").trim();
        }

        if (edad === "") {
             const hiddenEdad = document.getElementById("P701_EDAD_ANOS");
             if(hiddenEdad) edad = hiddenEdad.value;
        }

        return {
            servicio: getServicioGuardado(),
            cui: leer(ID_CUI_DISPLAY),
            nombre: leer(ID_NOMBRE_DISPLAY),
            sexo: leer(ID_SEXO_DISPLAY),
            nacionalidad: leer(ID_NAC_DISPLAY),
            nacimiento_fecha: fechaNac,
            nacimiento_edad: edad
        };
    }

    // ========================================================================
    // LOGICA MAESTRA: DETECCIÓN ESTRICTA & TURBO
    // ========================================================================
    function agendarGuardado(datos, tipo) {
        const datosPaciente = capturarDatosCompletos();
        localStorage.setItem('mspas_pendiente_guardar', JSON.stringify({
            datos: datos,
            tipo: tipo,
            paciente: datosPaciente,
            timestamp: Date.now()
        }));
        mostrarNotificacion("⏳ Esperando respuesta del servidor...", "#f39c12", 5000);
    }

    async function procesarExito(pendiente) {
        if (!pendiente) return;

        const fechaReal = await obtenerFechaHoraReal();
        const datosBase = pendiente.paciente || capturarDatosCompletos();

        datosBase.servicio = getServicioGuardado();
        datosBase.fecha_registro = fechaReal.toLocaleDateString('es-GT');
        datosBase.hora = fechaReal.toLocaleTimeString('es-GT');

        if (Array.isArray(pendiente.datos)) {
            pendiente.datos.forEach(v => {
                const reg = { ...datosBase, vacuna: v.nombre, fecha_vacuna: v.fecha };
                guardarRegistro(reg, 'regular');
            });
            mostrarNotificacion(`✅ REGISTRADO EXCEL: ${datosBase.nombre || 'Paciente'} (${pendiente.datos.length} vacunas)`, "#27ae60", 6000);
        } else {
            const reg = { ...datosBase, vacuna: pendiente.datos.vacuna, fecha_vacuna: pendiente.datos.fecha };
            guardarRegistro(reg, 'otras');
            mostrarNotificacion(`✅ REGISTRADO EXCEL: ${datosBase.nombre || 'Paciente'}`, "#27ae60", 6000);
        }
        localStorage.removeItem('mspas_pendiente_guardar');
    }

    function verificarGuardadoPendiente() {
        const pendienteRaw = localStorage.getItem('mspas_pendiente_guardar');
        if (!pendienteRaw) return;

        const pendiente = JSON.parse(pendienteRaw);
        if (Date.now() - pendiente.timestamp > 300000) {
            localStorage.removeItem('mspas_pendiente_guardar');
            return;
        }

        let procesado = false;

        const chequearAhora = () => {
            if (procesado) return true;

            const successEl = document.getElementById("APEX_SUCCESS_MESSAGE");
            if (successEl &&
                !successEl.classList.contains("u-hidden") &&
                successEl.offsetParent !== null &&
                successEl.innerText.includes(TEXTO_EXITO)) {

                procesado = true;
                procesarExito(pendiente);
                return true;
            }

            const popupMsg = document.querySelector(".a-AlertMessage-details");
            if (popupMsg && popupMsg.textContent.includes(TEXTO_EXITO)) {
                procesado = true;
                procesarExito(pendiente);
                return true;
            }

            if (document.body.innerText.includes(TEXTO_EXITO)) {
                procesado = true;
                procesarExito(pendiente);
                return true;
            }

            if (document.body.innerText.includes("Error") || document.body.innerText.includes("ORA-")) {
                procesado = true;
                localStorage.removeItem('mspas_pendiente_guardar');
                mostrarNotificacion("❌ Error del servidor detectado.", "#c0392b", 5000);
                return true;
            }
            return false;
        };

        if (chequearAhora()) return;

        const observer = new MutationObserver((mutations) => {
            if (chequearAhora()) observer.disconnect();
        });

        observer.observe(document.body, {
            attributes: true,
            childList: true,
            subtree: true,
            characterData: true
        });

        let intentos = 0;
        const intervalo = setInterval(() => {
            intentos++;
            if (chequearAhora() || intentos > 300) {
                clearInterval(intervalo);
                observer.disconnect();
            }
        }, 30);
    }

    verificarGuardadoPendiente();

    // ========================================================================
    // CAPTURAS
    // ========================================================================
    setInterval(() => {
        const inputFecha = document.getElementById(ID_FECHA_INPUT);
        const selectVacuna = document.getElementById(ID_VACUNA_SELECT);
        if (inputFecha && selectVacuna) {
            const botones = Array.from(document.querySelectorAll("button"));
            const btnGuardar = botones.find(b => b.innerText.trim() === "Guardar");
            if (btnGuardar && !btnGuardar.classList.contains('monitor-listo')) {
                btnGuardar.classList.add('monitor-listo');
                btnGuardar.style.border = "3px solid #e67e22";
                btnGuardar.addEventListener('click', function() {
                    const vacunaTxt = selectVacuna.options[selectVacuna.selectedIndex].text;
                    const fechaTxt = inputFecha.value;
                    if (vacunaTxt && fechaTxt) {
                        agendarGuardado({ vacuna: vacunaTxt, fecha: fechaTxt }, 'otras');
                    }
                });
            }
        }
    }, 1000);

    function parsearTextoConfirmacion(textoHTML) {
        const textoLimpio = textoHTML.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]*>?/gm, '');
        const lineas = textoLimpio.split('\n');
        const vacunasDetectadas = [];
        lineas.forEach(linea => {
            if (linea.includes("Fecha Administración:")) {
                const partes = linea.split("Fecha Administración:");
                if (partes.length >= 2) {
                    vacunasDetectadas.push({ nombre: partes[0].trim(), fecha: partes[1].trim() });
                }
            }
        });
        return vacunasDetectadas;
    }

    const obsPopup = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.addedNodes.length) {
                const dialogos = document.querySelectorAll('.ui-dialog-content');
                dialogos.forEach(dialogContent => {
                    const textoContenedor = dialogContent.querySelector('.a-AlertMessage-details');
                    if (textoContenedor && !dialogContent.classList.contains('procesado')) {
                        let botonFinal = dialogContent.parentElement.querySelector('button.js-confirmBtn');
                        if (!botonFinal) {
                            const botones = dialogContent.parentElement.querySelectorAll('button');
                            for (let b of botones) { if (b.innerText.trim() === "Aceptar") { botonFinal = b; break; } }
                        }
                        if (botonFinal && !botonFinal.classList.contains('monitor-listo')) {
                            dialogContent.classList.add('procesado');
                            botonFinal.classList.add('monitor-listo');
                            const vacunasEnCola = parsearTextoConfirmacion(textoContenedor.innerHTML);
                            botonFinal.addEventListener('click', function() {
                                if (vacunasEnCola.length > 0) agendarGuardado(vacunasEnCola, 'regular');
                            });
                        }
                    }
                });
            }
        });
    });
    obsPopup.observe(document.body, { childList: true, subtree: true });

    // ========================================================================
    // DASHBOARD PANEL V38
    // ========================================================================
    const elementoNombre = document.getElementById(ID_NOMBRE_DISPLAY) || document.querySelector(".t-Header-branding");
    if (elementoNombre) {
        const panel = document.createElement('div');
        panel.style = "position: fixed; bottom: 10px; left: 10px; background: #2c3e50; color: white; padding: 10px; z-index: 99999; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.5); font-family: Arial; font-size: 11px; width: 230px; border: 2px solid #3498db;";
        panel.innerHTML = `
            <div style="font-weight:bold; border-bottom:1px solid #aaa; margin-bottom:5px;">💉 Contador de Vacunas
            <button id="btnAbrirDash" style="background:#8e44ad; color:white; width:100%; border:none; padding:5px; margin-bottom:5px; cursor:pointer; font-weight:bold; border-radius:4px;">🖥️ ABRIR DASHBOARD</button>
            <div style="font-size:10px; color:#bdc3c7;">Servicio: <span id="lblServicio" style="color:white; font-weight:bold;">...</span></div>

            <div style="margin-top:5px; background:#2980b9; padding:4px; border-radius:4px;">
                <div style="font-weight:bold; color:white;">👶 Esquema Regular</div>
                <div style="display:flex; justify-content:space-between;"><span>Hoy: <b id="cntRegHoy" style="color:#f1c40f">0</b></span><span>Total: <b id="cntRegTot">0</b></span></div>
                <div style="margin-top:2px; display:flex; gap:2px;">
                    <button id="btnRegHoy" style="flex:1; background:#fff; color:#333; border:none; padding:1px; font-size:9px;">📥 HOY</button>
                    <button id="btnRegTot" style="flex:1; background:#fff; color:#333; border:none; padding:1px; font-size:9px;">📥 TOTAL</button>
                </div>
            </div>

            <div style="margin-top:5px; background:#d35400; padding:4px; border-radius:4px;">
                <div style="font-weight:bold; color:white;">💉 Otras Vacunas</div>
                <div style="display:flex; justify-content:space-between;"><span>Hoy: <b id="cntOtrHoy" style="color:#f1c40f">0</b></span><span>Total: <b id="cntOtrTot">0</b></span></div>
                <div style="margin-top:2px; display:flex; gap:2px;">
                    <button id="btnOtrHoy" style="flex:1; background:#fff; color:#333; border:none; padding:1px; font-size:9px;">📥 HOY</button>
                    <button id="btnOtrTot" style="flex:1; background:#fff; color:#333; border:none; padding:1px; font-size:9px;">📥 TOTAL</button>
                </div>
            </div>

            <div style="text-align:center; margin-top:5px; display:flex; justify-content:space-around;">
                 <small id="btnResetHoy" style="cursor:pointer; color:#bdc3c7;">🗑 Reset Hoy</small>
                 <small id="btnResetAll" style="cursor:pointer; color:#e74c3c;">🗑 Reset TOTAL</small>
            </div>
        `;
        document.body.appendChild(panel);
        document.getElementById('btnAbrirDash').onclick = abrirDashboard;

        const updatePanel = () => {
            document.getElementById('lblServicio').innerText = getServicioGuardado();
            document.getElementById('cntRegHoy').innerText = obtenerDatos('regular').length;
            document.getElementById('cntRegTot').innerText = obtenerAcumulado('regular').length;
            document.getElementById('cntOtrHoy').innerText = obtenerDatos('otras').length;
            document.getElementById('cntOtrTot').innerText = obtenerAcumulado('otras').length;
        };

        const generarCSV = (datos, nombre) => {
             if(datos.length === 0) return alert("Sin datos");
             let csv = "\uFEFFSERVICIO;FECHA_REGISTRO;HORA_REGISTRO;CUI/DPI;NOMBRE_PACIENTE;SEXO;NACIONALIDAD;FECHA_NACIMIENTO;EDAD;TIPO_VACUNA;FECHA_VACUNACION\n";
             datos.forEach(d => {
                const clean = (txt) => (txt || "").toString().replace(/;/g, " ");
                const cuiFormateado = `="${clean(d.cui)}"`;
                csv += `"${clean(d.servicio)}";"${clean(d.fecha_registro)}";"${clean(d.hora)}";${cuiFormateado};"${clean(d.nombre)}";"${clean(d.sexo)}";"${clean(d.nacionalidad)}";"${clean(d.nacimiento_fecha)}";"${clean(d.nacimiento_edad)}";"${clean(d.vacuna)}";"${clean(d.fecha_vacuna)}"\n`;
            });
            const blob = new Blob([csv], {type: 'text/csv;charset=utf-8;'});
            const link = document.createElement("a");
            link.href = URL.createObjectURL(blob);
            link.download = nombre;
            link.click();
        };

        document.getElementById('btnRegHoy').onclick = () => generarCSV(obtenerDatos('regular'), `Regular_HOY_${new Date().toLocaleDateString('es-GT').replace(/\//g,'-')}.csv`);
        document.getElementById('btnRegTot').onclick = () => generarCSV(obtenerAcumulado('regular'), `Regular_TOTAL.csv`);
        document.getElementById('btnOtrHoy').onclick = () => generarCSV(obtenerDatos('otras'), `Otras_HOY_${new Date().toLocaleDateString('es-GT').replace(/\//g,'-')}.csv`);
        document.getElementById('btnOtrTot').onclick = () => generarCSV(obtenerAcumulado('otras'), `Otras_TOTAL.csv`);

        const logicaReset = (modo) => {
            const seleccion = prompt(`⚠️ RESET ${modo}\n\nEscribe el número de la opción:\n1 - Solo Esquema Regular\n2 - Solo Otras Vacunas\n3 - Todo (Ambas)`);
            if (!seleccion) return;
            let borrarRegular = (seleccion === '1' || seleccion === '3');
            let borrarOtras = (seleccion === '2' || seleccion === '3');
            if (modo === 'HOY') {
                if (borrarRegular) {
                    const diario = obtenerDatos('regular');
                    const acumulado = obtenerAcumulado('regular');
                    if (diario.length === acumulado.length) localStorage.setItem('reporte_regular_acumulado', '[]');
                    localStorage.setItem('reporte_regular_diario', '[]');
                }
                if (borrarOtras) {
                    const diario = obtenerDatos('otras');
                    const acumulado = obtenerAcumulado('otras');
                    if (diario.length === acumulado.length) localStorage.setItem('reporte_vacunacion_acumulado', '[]');
                    localStorage.setItem('reporte_vacunacion_diario', '[]');
                }
            }
            else if (modo === 'TOTAL') {
                if (confirm(`¿SEGURO QUE DESEAS BORRAR EL ACUMULADO?`)) {
                    if (borrarRegular) {
                        localStorage.setItem('reporte_regular_diario', '[]');
                        localStorage.setItem('reporte_regular_acumulado', '[]');
                    }
                    if (borrarOtras) {
                        localStorage.setItem('reporte_vacunacion_diario', '[]');
                        localStorage.setItem('reporte_vacunacion_acumulado', '[]');
                    }
                }
            }
            window.dispatchEvent(new Event('storage'));
            alert(`✅ Reset ${modo} completado.`);
        };
        document.getElementById('btnResetHoy').onclick = () => logicaReset('HOY');
        document.getElementById('btnResetAll').onclick = () => logicaReset('TOTAL');

        window.addEventListener('storage', updatePanel);
        setInterval(updatePanel, 1000);
        updatePanel();
    }

    // ========================================================================
    // DASHBOARD V38 - FILTER LOGIC FIXED
    // ========================================================================
    function abrirDashboard() {
        const win = window.open("", "DashboardVacunasV38", "width=1350,height=900");
        if (!win) return alert("⚠️ Permite Pop-ups");
        const htmlContent = `<!DOCTYPE html><html lang="es"><head><meta charset="UTF-8"><title>🖥️ Dashboard V38</title><link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"><style>body{background:#2c3e50;color:#ecf0f1;font-family:'Segoe UI',sans-serif}.section-card{background:#34495e;border-radius:10px;padding:15px;margin-bottom:20px;box-shadow:0 4px 6px rgba(0,0,0,0.3)}.card-header-custom{border-bottom:2px solid #7f8c8d;padding-bottom:10px;margin-bottom:15px;display:flex;justify-content:space-between;align-items:center}.table{color:#ecf0f1;font-size:0.85rem}.table thead{background:#2c3e50;position:sticky;top:0;z-index:2}.table-hover tbody tr:hover{color:#fff;background:#576574}.stat-box{background:rgba(0,0,0,0.2);border-radius:5px;padding:5px 10px;text-align:center;min-width:70px}.stat-num{font-size:1.1rem;font-weight:bold}.search-bar{width:300px}.filter-group{display:flex;gap:5px;align-items:center}.filter-count{font-size:0.9rem;font-weight:bold;padding:2px 8px;border-radius:4px}.dropdown-menu{max-height:250px;overflow-y:auto;background:#2c3e50;border:1px solid #7f8c8d;color:#ecf0f1}.dropdown-item:hover{background:#34495e;color:white}.form-check-input:checked{background-color:#3498db;border-color:#3498db}</style></head><body>
        <div class="container-fluid pt-3">
            <div class="d-flex justify-content-between align-items-center mb-3">
                <h3 class="m-0">Dashboard V38</h3>
                <div class="input-group search-bar mx-3">
                    <input type="text" id="searchInput" class="form-control bg-dark text-white border-secondary" placeholder="Buscar por Nombre o CUI..." onkeyup="refrescarTablas()">
                </div>
                <div class="text-end">
                    <small class="d-block text-info" id="servName">...</small>
                    <button onclick="cargarDatos()" class="btn btn-sm btn-outline-light">🔄 Refrescar Datos</button>
                </div>
            </div>

            <div class="row">
                <div class="col-lg-6">
                    <div class="section-card" style="border-top:4px solid #3498db">
                        <div class="card-header-custom">
                            <h5 class="text-white m-0">Regular</h5>
                            <div class="d-flex gap-2">
                                <div class="stat-box"><small>HOY</small><div id="regHoy" class="stat-num text-info">0</div></div>
                                <div class="stat-box"><small>TOTAL</small><div id="regTotal" class="stat-num text-white">0</div></div>
                            </div>
                        </div>
                        <div class="filter-group mb-2" id="divFiltrosReg"></div>
                        <div class="mb-2 text-end">
                            <span id="regViendo" class="filter-count bg-info text-dark">Viendo: 0</span>
                        </div>
                        <div class="table-responsive" style="max-height:600px;overflow-y:auto">
                            <table class="table table-hover table-sm" id="tableReg">
                                <thead><tr><th>#</th><th>Hora</th><th>Servicio</th><th>Paciente</th><th>Vacuna / Fecha</th><th>Acción</th></tr></thead>
                                <tbody id="bodyRegular"></tbody>
                            </table>
                        </div>
                    </div>
                </div>

                <div class="col-lg-6">
                    <div class="section-card" style="border-top:4px solid #e67e22">
                        <div class="card-header-custom">
                            <h5 class="text-white m-0">Otras</h5>
                            <div class="d-flex gap-2">
                                <div class="stat-box"><small>HOY</small><div id="otrHoy" class="stat-num text-warning">0</div></div>
                                <div class="stat-box"><small>TOTAL</small><div id="otrTotal" class="stat-num text-white">0</div></div>
                            </div>
                        </div>
                        <div class="filter-group mb-2" id="divFiltrosOtr"></div>
                        <div class="mb-2 text-end">
                            <span id="otrViendo" class="filter-count bg-warning text-dark">Viendo: 0</span>
                        </div>
                        <div class="table-responsive" style="max-height:600px;overflow-y:auto">
                            <table class="table table-hover table-sm" id="tableOtr">
                                <thead><tr><th>#</th><th>Hora</th><th>Servicio</th><th>Paciente</th><th>Vacuna / Fecha</th><th>Acción</th></tr></thead>
                                <tbody id="bodyOtras"></tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <div class="modal fade" id="editModal" tabindex="-1"><div class="modal-dialog"><div class="modal-content text-dark"><div class="modal-header bg-warning"><h5 class="modal-title">Editar</h5><button type="button" class="btn-close" onclick="cerrarModal()"></button></div><div class="modal-body"><input type="hidden" id="editType"><input type="hidden" id="editIndex"><div class="mb-2"><label>Paciente:</label><input type="text" id="editNombre" class="form-control" readonly></div><div class="mb-2"><label>Vacuna:</label><input type="text" id="editVacuna" class="form-control"></div><div class="mb-2"><label>Fecha:</label><input type="date" id="editFecha" class="form-control"></div></div><div class="modal-footer"><button class="btn btn-primary" onclick="guardarEdicion()">Guardar</button></div></div></div></div>

        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
        <script>
            let dataReg = [], dataOtr = [];
            let acumReg = [], acumOtr = [];

            // MEMORIA DE FILTROS (Estado activo)
            let activeFilters = {
                regular: { vacuna: 'ALL', fecha_vacuna: 'ALL', servicio: 'ALL' },
                otras: { vacuna: 'ALL', fecha_vacuna: 'ALL', servicio: 'ALL' }
            };

            function cargarDatos(){
                const servicio = localStorage.getItem('mspas_servicio_actual')||"N/A";
                document.getElementById('servName').innerText = servicio;

                dataReg = JSON.parse(localStorage.getItem('reporte_regular_diario')||'[]');
                dataOtr = JSON.parse(localStorage.getItem('reporte_vacunacion_diario')||'[]');
                acumReg = JSON.parse(localStorage.getItem('reporte_regular_acumulado')||'[]');
                acumOtr = JSON.parse(localStorage.getItem('reporte_vacunacion_acumulado')||'[]');

                document.getElementById('regHoy').innerText = dataReg.length;
                document.getElementById('regTotal').innerText = acumReg.length;
                document.getElementById('otrHoy').innerText = dataOtr.length;
                document.getElementById('otrTotal').innerText = acumOtr.length;

                // Generamos los filtros con el estado actual
                generarFiltros('regular', dataReg);
                generarFiltros('otras', dataOtr);

                // Renderizamos la tabla usando la MEMORIA
                renderizarTabla('regular');
                renderizarTabla('otras');
            }

            function actualizarSoloTablas(){
                const servicio = localStorage.getItem('mspas_servicio_actual')||"N/A";
                document.getElementById('servName').innerText = servicio;

                dataReg = JSON.parse(localStorage.getItem('reporte_regular_diario')||'[]');
                dataOtr = JSON.parse(localStorage.getItem('reporte_vacunacion_diario')||'[]');
                acumReg = JSON.parse(localStorage.getItem('reporte_regular_acumulado')||'[]');
                acumOtr = JSON.parse(localStorage.getItem('reporte_vacunacion_acumulado')||'[]');

                document.getElementById('regHoy').innerText = dataReg.length;
                document.getElementById('regTotal').innerText = acumReg.length;
                document.getElementById('otrHoy').innerText = dataOtr.length;
                document.getElementById('otrTotal').innerText = acumOtr.length;

                renderizarTabla('regular');
                renderizarTabla('otras');
            }

            function getUnique(data, key) {
                return [...new Set(data.map(item => item[key]))].sort();
            }

            function generarFiltros(tipo, data) {
                const containerId = tipo === 'regular' ? 'divFiltrosReg' : 'divFiltrosOtr';
                const container = document.getElementById(containerId);
                container.innerHTML = '';

                const unicasVacunas = getUnique(data, 'vacuna');
                const unicasFechas = getUnique(data, 'fecha_vacuna');
                const unicosServicios = getUnique(data, 'servicio');

                container.appendChild(crearDropdownFiltro(tipo, 'Vacuna', unicasVacunas, 'vacuna'));
                container.appendChild(crearDropdownFiltro(tipo, 'Fecha', unicasFechas, 'fecha_vacuna'));
                container.appendChild(crearDropdownFiltro(tipo, 'Servicio', unicosServicios, 'servicio'));
            }

            function crearDropdownFiltro(tipo, etiqueta, unicos, campoDatos) {
                const groupId = \`grp_\${tipo}_\${campoDatos}\`;
                const div = document.createElement('div');
                div.className = 'dropdown d-inline-block';

                const currentState = activeFilters[tipo][campoDatos];
                const isAllSelected = currentState === 'ALL';

                let html = \`
                    <button class="btn btn-sm btn-secondary dropdown-toggle text-light" type="button" id="\${groupId}" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
                        \${etiqueta}s
                    </button>
                    <div class="dropdown-menu p-2 shadow" aria-labelledby="\${groupId}" style="width: 250px;">
                        <div class="form-check mb-2 border-bottom pb-2">
                            <input class="form-check-input select-all" type="checkbox" id="all_\${groupId}" \${isAllSelected ? 'checked' : ''} onchange="toggleAll('\${groupId}')">
                            <label class="form-check-label text-white fw-bold" for="all_\${groupId}">Seleccionar Todo</label>
                        </div>
                        <div id="list_\${groupId}" style="max-height: 150px; overflow-y: auto;">\`;

                if (unicos.length === 0) {
                     html += \`<div class="text-white small p-1">Sin datos aún</div>\`;
                } else {
                    unicos.forEach((val, idx) => {
                        const chkId = \`chk_\${groupId}_\${idx}\`;
                        let isChecked = false;
                        if (currentState === 'ALL') {
                            isChecked = true;
                        } else if (Array.isArray(currentState) && currentState.includes(String(val))) {
                            isChecked = true;
                        }

                        // AGREGADO: onchange="updateMaster(...)" para sincronizar
                        html += \`
                            <div class="form-check">
                                <input class="form-check-input item-check" type="checkbox" value="\${val}" id="\${chkId}" \${isChecked ? 'checked' : ''} onchange="updateMaster('\${groupId}')">
                                <label class="form-check-label text-light" for="\${chkId}" style="font-size:0.85rem">\${val}</label>
                            </div>\`;
                    });
                }

                html += \`
                        </div>
                        <div class="mt-2 pt-2 border-top text-end">
                            <button class="btn btn-primary btn-sm w-100" onclick="confirmarFiltro('\${tipo}', '\${campoDatos}', '\${groupId}')">Aceptar</button>
                        </div>
                    </div>
                \`;
                div.innerHTML = html;
                return div;
            }

            // NUEVA FUNCIÓN: Sincroniza el "Seleccionar Todo" cuando se toca un item individual
            window.updateMaster = function(groupId) {
                const master = document.getElementById('all_' + groupId);
                const container = document.getElementById('list_' + groupId);
                if(!container) return;

                const totalItems = container.querySelectorAll('.item-check').length;
                const checkedItems = container.querySelectorAll('.item-check:checked').length;

                // Si todos están marcados y hay al menos uno, marca el master
                master.checked = (totalItems > 0 && totalItems === checkedItems);
            };

            // Función al dar clic en el Master "Seleccionar Todo"
            window.toggleAll = function(groupId) {
                const master = document.getElementById('all_' + groupId);
                const items = document.querySelectorAll(\`#list_\${groupId} .item-check\`);
                items.forEach(chk => chk.checked = master.checked);
            };

            function refrescarTablas() {
                renderizarTabla('regular');
                renderizarTabla('otras');
            }

            // ESTA FUNCIÓN SE EJECUTA AL DAR CLICK EN "ACEPTAR"
            window.confirmarFiltro = function(tipo, campo, groupId) {
                const containerList = document.getElementById(\`list_\${groupId}\`);

                if (!containerList) {
                    activeFilters[tipo][campo] = 'ALL';
                } else {
                    const allChecks = containerList.querySelectorAll('.item-check');
                    const checkedBoxes = containerList.querySelectorAll('.item-check:checked');

                    // Si la cantidad de marcados es igual al total, es ALL
                    if (allChecks.length > 0 && allChecks.length === checkedBoxes.length) {
                        activeFilters[tipo][campo] = 'ALL';
                    } else {
                        // Guardamos valores como Strings para asegurar comparación
                        activeFilters[tipo][campo] = Array.from(checkedBoxes).map(cb => String(cb.value));
                    }
                }

                renderizarTabla(tipo);

                const btnToggle = document.getElementById(groupId);
                if(btnToggle) {
                    const dropdown = bootstrap.Dropdown.getOrCreateInstance(btnToggle);
                    dropdown.hide();
                }
            };

            function renderizarTabla(tipo) {
                const term = document.getElementById('searchInput').value.toLowerCase();
                let rawData = tipo === 'regular' ? dataReg : dataOtr;
                let indexedData = rawData.map((item, idx) => ({...item, originalIndex: idx}));

                const filtros = activeFilters[tipo];

                let filtered = indexedData.filter(item => {
                    const matchText = (item.nombre && item.nombre.toLowerCase().includes(term)) || (item.cui && item.cui.includes(term));

                    // Aseguramos conversión a String para la comparación
                    const matchVac = filtros.vacuna === 'ALL' ? true : filtros.vacuna.includes(String(item.vacuna));
                    const matchFec = filtros.fecha_vacuna === 'ALL' ? true : filtros.fecha_vacuna.includes(String(item.fecha_vacuna));
                    const matchSer = filtros.servicio === 'ALL' ? true : filtros.servicio.includes(String(item.servicio));

                    return matchText && matchVac && matchFec && matchSer;
                });

                const spanViendo = document.getElementById(tipo === 'regular' ? 'regViendo' : 'otrViendo');
                spanViendo.innerText = \`Viendo: \${filtered.length}\`;

                const tbodyId = tipo === 'regular' ? 'bodyRegular' : 'bodyOtras';
                const tbody = document.getElementById(tbodyId);
                tbody.innerHTML = '';

                filtered.slice().reverse().forEach((reg, i) => {
                    const row = \`<tr>
                        <td>\${filtered.length - i}</td>
                        <td><small>\${reg.hora}</small></td>
                        <td><small class="text-info">\${reg.servicio || '-'}</small></td>
                        <td><small>\${reg.cui}</small><br><strong>\${reg.nombre}</strong></td>
                        <td>\${reg.vacuna}<br><span class="badge bg-secondary">\${reg.fecha_vacuna}</span></td>
                        <td>
                            <button class="btn btn-warning btn-sm py-0" onclick="abrirEdit('\${tipo}', \${reg.originalIndex})">✎</button>
                            <button class="btn btn-danger btn-sm py-0" onclick="borrar('\${tipo}', \${reg.originalIndex})">×</button>
                        </td>
                    </tr>\`;
                    tbody.innerHTML += row;
                });
            }

            function borrar(tipo, index){
                if(!confirm("¿Borrar este registro?")) return;
                const keyDiario = tipo === 'regular' ? 'reporte_regular_diario' : 'reporte_vacunacion_diario';
                const keyAcum = tipo === 'regular' ? 'reporte_regular_acumulado' : 'reporte_vacunacion_acumulado';

                let diario = JSON.parse(localStorage.getItem(keyDiario));
                let acumulado = JSON.parse(localStorage.getItem(keyAcum));

                const target = diario[index];
                diario.splice(index, 1);
                localStorage.setItem(keyDiario, JSON.stringify(diario));

                if(target){
                    const idxAcum = acumulado.findIndex(r => r.cui === target.cui && r.hora === target.hora && r.vacuna === target.vacuna);
                    if(idxAcum !== -1){
                        acumulado.splice(idxAcum, 1);
                        localStorage.setItem(keyAcum, JSON.stringify(acumulado));
                    }
                }
                window.dispatchEvent(new Event('storage'));
                cargarDatos();
            }

            let modal;
            function abrirEdit(tipo, index){
                const keyDiario = tipo === 'regular' ? 'reporte_regular_diario' : 'reporte_vacunacion_diario';
                const diario = JSON.parse(localStorage.getItem(keyDiario));
                const reg = diario[index];
                document.getElementById('editType').value = tipo;
                document.getElementById('editIndex').value = index;
                document.getElementById('editNombre').value = reg.nombre;
                document.getElementById('editVacuna').value = reg.vacuna;
                let fechaIso = "";
                if(reg.fecha_vacuna && reg.fecha_vacuna.includes('/')){
                    const partes = reg.fecha_vacuna.split('/');
                    if(partes.length === 3) fechaIso = \`\${partes[2]}-\${partes[1]}-\${partes[0]}\`;
                }
                document.getElementById('editFecha').value = fechaIso;
                modal = new bootstrap.Modal(document.getElementById('editModal'));
                modal.show();
            }

            function cerrarModal(){ if(modal) modal.hide(); }

            function guardarEdicion(){
                const tipo = document.getElementById('editType').value;
                const idx = document.getElementById('editIndex').value;
                const nVacuna = document.getElementById('editVacuna').value;
                const nFechaIso = document.getElementById('editFecha').value;
                let nFechaFinal = nFechaIso;
                if(nFechaIso && nFechaIso.includes('-')){
                    const partes = nFechaIso.split('-');
                    nFechaFinal = \`\${partes[2]}/\${partes[1]}/\${partes[0]}\`;
                }
                const keyDiario = tipo === 'regular' ? 'reporte_regular_diario' : 'reporte_vacunacion_diario';
                const keyAcum = tipo === 'regular' ? 'reporte_regular_acumulado' : 'reporte_vacunacion_acumulado';
                let diario = JSON.parse(localStorage.getItem(keyDiario));
                let acumulado = JSON.parse(localStorage.getItem(keyAcum));
                const target = diario[idx];
                diario[idx].vacuna = nVacuna;
                diario[idx].fecha_vacuna = nFechaFinal;
                localStorage.setItem(keyDiario, JSON.stringify(diario));
                const idxAcum = acumulado.findIndex(r => r.cui === target.cui && r.hora === target.hora);
                if(idxAcum !== -1){
                    acumulado[idxAcum].vacuna = nVacuna;
                    acumulado[idxAcum].fecha_vacuna = nFechaFinal;
                    localStorage.setItem(keyAcum, JSON.stringify(acumulado));
                }
                window.dispatchEvent(new Event('storage'));
                cerrarModal();
                cargarDatos();
            }

            window.addEventListener('storage', actualizarSoloTablas);
            setInterval(actualizarSoloTablas, 3000);
            cargarDatos();
        </script></body></html>`;
        win.document.write(htmlContent);
        win.document.close();
    }

    // --- RESET DIARIO & DETECTOR SERVICIO ---
    const hoy = new Date().toLocaleDateString('es-GT');
    if (localStorage.getItem('mspas_fecha_activa') !== hoy) {
        localStorage.setItem('reporte_regular_diario', '[]');
        localStorage.setItem('reporte_vacunacion_diario', '[]');
        localStorage.setItem('mspas_fecha_activa', hoy);
    }
    setInterval(() => {
        // DETECTOR ORIGINAL POR TEXTO DE PÁGINA
        if (document.body.innerText.includes("SERVICIO DE SALUD:")) {
           try {
               const match = document.body.innerText.match(/SERVICIO DE SALUD:\s*(.+)/i);
               if (match && match[1]) {
                   const serv = match[1].trim();
                   if (serv !== localStorage.getItem('mspas_servicio_actual')) {
                       localStorage.setItem('mspas_servicio_actual', serv);
                       window.dispatchEvent(new Event('storage'));
                   }
               }
           } catch (e) {}
        }

        // NUEVA LOGICA V33: DETECCION CAMBIO DE SERVICIO (VENTANA PARAMETRIZACION)
        const inputServicioManual = document.getElementById(ID_INPUT_SERVICIO_PARAM);
        if (inputServicioManual) {
            // Buscamos el botón Guardar si el input existe (Modal abierto)
            const botones = Array.from(document.querySelectorAll("button"));
            const btnGuardarParam = botones.find(b => b.innerText.trim() === "Guardar");

            // Aseguramos que solo agregamos el listener una vez
            if (btnGuardarParam && !btnGuardarParam.classList.contains('servicio-monitor')) {
                btnGuardarParam.classList.add('servicio-monitor');

                btnGuardarParam.addEventListener('click', function() {
                    const nuevoServicio = inputServicioManual.value;
                    if (nuevoServicio) {
                        localStorage.setItem('mspas_servicio_actual', nuevoServicio);
                        window.dispatchEvent(new Event('storage'));
                        mostrarNotificacion(`🏥 Servicio Actualizado: ${nuevoServicio}`, "#3498db", 4000);
                    }
                });
            }
        }
    }, 1000);
})();