Reporte Vacunación MSPAS (V44 - Zero Latency)

V43 + Optimización extrema de velocidad de detección (textContent + 10ms polling).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Reporte Vacunación MSPAS (V44 - Zero Latency)
// @namespace    http://tampermonkey.net/
// @version      44.0
// @description  V43 + Optimización extrema de velocidad de detección (textContent + 10ms polling).
// @author       Gemini AI
// @match        *://*.oraclecloudapps.com/ords/r/vacunacion/vacunacion/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    /* jshint esversion: 6 */

    // ================= CONFIGURACIÓN =================
    // PEGA AQUÍ TU URL DE GOOGLE APPS SCRIPT
    const URL_GOOGLE_SHEET = "https://script.google.com/macros/s/AKfycbzT3rWlONdKIOQfbaTqABKA1Y4PNj4iZ1LvVZsOVIV_Xi_IKIUv5_99gKIOrzj9XlQQ/exec";
    // =================================================

    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) {
        nuevoRegistro.synced = false;
        nuevoRegistro.wasEdited = false;

        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
        };
    }

    // --- SINCRONIZACIÓN GLOBAL ---
    window.sincronizarDrive = function() {
        if (URL_GOOGLE_SHEET.includes("PON_AQUI")) return alert("⚠️ Configura la URL de Google Sheets en el script.");

        const regularAll = obtenerAcumulado('regular').map(r => ({...r, synced: r.synced === true}));
        const otrasAll = obtenerAcumulado('otras').map(r => ({...r, synced: r.synced === true}));

        const pendientesRegular = regularAll.filter(r => !r.synced);
        const pendientesOtras = otrasAll.filter(r => !r.synced);

        const payload = [...pendientesRegular, ...pendientesOtras];

        if (payload.length === 0) return mostrarNotificacion("✅ Todo sincronizado.", "#2980b9", 3000);
        if(!confirm(`Se enviarán ${payload.length} registros a Drive. ¿Continuar?`)) return;

        mostrarNotificacion("☁️ Enviando...", "#f39c12", 10000);

        fetch(URL_GOOGLE_SHEET, {
            method: "POST", mode: "no-cors", headers: { "Content-Type": "application/json" },
            body: JSON.stringify(payload)
        }).then(() => {
            const updateSync = (key) => {
                let data = JSON.parse(localStorage.getItem(key)||'[]');
                data = data.map(item => {
                    if (payload.some(p => p.cui === item.cui && p.hora === item.hora && p.vacuna === item.vacuna)) {
                        item.synced = true; item.wasEdited = false;
                    }
                    return item;
                });
                localStorage.setItem(key, JSON.stringify(data));
            };
            updateSync('reporte_regular_diario'); updateSync('reporte_regular_acumulado');
            updateSync('reporte_vacunacion_diario'); updateSync('reporte_vacunacion_acumulado');

            mostrarNotificacion(`🚀 Éxito! ${payload.length} enviados.`, "#27ae60", 5000);
            window.dispatchEvent(new Event('storage'));
        }).catch(err => alert("Error red."));
    };

    // ========================================================================
    // LOGICA MAESTRA V44: DETECCIÓN ZERO LATENCY (textContent + 10ms)
    // ========================================================================
    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...", "#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} (${pendiente.datos.length})`, "#27ae60", 6000);
        } else {
            const reg = { ...datosBase, vacuna: pendiente.datos.vacuna, fecha_vacuna: pendiente.datos.fecha };
            guardarRegistro(reg, 'otras');
            mostrarNotificacion(`✅ REGISTRADO EXCEL: ${datosBase.nombre}`, "#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;

            // 1. BUSQUEDA QUIRURGICA (Optimizado con textContent)
            const successEl = document.getElementById("APEX_SUCCESS_MESSAGE");
            if (successEl && !successEl.classList.contains("u-hidden")) {
                 if (successEl.textContent.includes(TEXTO_EXITO)) {
                     procesado = true; procesarExito(pendiente); return true;
                 }
            }

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

            // 3. BUSQUEDA GLOBAL RAPIDA (textContent es mas rapido que innerText)
            if (document.body.textContent.includes(TEXTO_EXITO)) {
                procesado = true; procesarExito(pendiente); return true;
            }

            // DETECCION ERROR
            const bodyText = document.body.textContent; // Leer una sola vez por ciclo
            if (bodyText.includes("Error") || bodyText.includes("ORA-")) {
                procesado = true; localStorage.removeItem('mspas_pendiente_guardar');
                mostrarNotificacion("❌ Error detectado.", "#c0392b", 5000); return true;
            }
            return false;
        };

        if (chequearAhora()) return;

        // OBSERVER MAS AGRESIVO (Detecta cambios de atributos)
        const observer = new MutationObserver(() => { if (chequearAhora()) observer.disconnect(); });
        observer.observe(document.body, { attributes: true, childList: true, subtree: true, characterData: true });

        // INTERVALO HIPER-RAPIDO (10ms)
        let intentos = 0;
        const intervalo = setInterval(() => {
            intentos++;
            if (chequearAhora() || intentos > 1000) { // 10 segs max
                clearInterval(intervalo); observer.disconnect();
            }
        }, 10);
    }

    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 V43
    // ========================================================================
    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;">💉 Panel de Control 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="margin-top:8px;">
                <button id="btnSyncDrive" style="width:100%; background:#27ae60; color:white; border:none; padding:5px; border-radius:4px; font-weight:bold; cursor:pointer;">☁️ Sincronizar Drive</button>
            </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;
        document.getElementById('btnSyncDrive').onclick = window.sincronizarDrive;

        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 V43 - RESCUE MODE
    // ========================================================================
    function abrirDashboard() {
        const win = window.open("", "DashboardVacunasV43", "width=1350,height=900");
        if (!win) return alert("⚠️ Permite Pop-ups");

        // Pasamos la URL_SHEET al dashboard para que pueda sincronizar por su cuenta
        const sheetUrl = URL_GOOGLE_SHEET;

        const htmlContent = `<!DOCTYPE html><html lang="es"><head><meta charset="UTF-8"><title>🖥️ Dashboard Vacunas V43</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 Vacunas V43</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">
                    <button onclick="sincronizarDriveDash()" class="btn btn-sm btn-success me-2">☁️ Sincronizar Drive</button>
                    <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 = [];
            const URL_SHEET = "${URL_GOOGLE_SHEET}"; // Inyectado

            let activeFilters = {
                regular: { vacuna: 'ALL', fecha_vacuna: 'ALL', servicio: 'ALL' },
                otras: { vacuna: 'ALL', fecha_vacuna: 'ALL', servicio: 'ALL' }
            };

            // V43: Sanitizador ROBUSTO (Cura datos corruptos)
            function safeLoad(key) {
                try {
                    let raw = localStorage.getItem(key);
                    if (!raw) return [];
                    let parsed = JSON.parse(raw);
                    if (!Array.isArray(parsed)) return [];
                    return parsed.map(d => ({
                        ...d,
                        synced: (d.synced === true), // Si falta, es false
                        wasEdited: (d.wasEdited === true)
                    }));
                } catch(e) {
                    console.error("Error cargando datos", e);
                    return [];
                }
            }

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

                // Usamos safeLoad en lugar de JSON.parse directo
                dataReg = safeLoad('reporte_regular_diario');
                dataOtr = safeLoad('reporte_vacunacion_diario');
                acumReg = safeLoad('reporte_regular_acumulado');
                acumOtr = safeLoad('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;

                // Reset Filtros
                activeFilters = {
                    regular: { vacuna: 'ALL', fecha_vacuna: 'ALL', servicio: 'ALL' },
                    otras: { vacuna: 'ALL', fecha_vacuna: 'ALL', servicio: 'ALL' }
                };

                generarFiltros('regular', dataReg);
                generarFiltros('otras', dataOtr);

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

            function actualizarSoloTablas(){
                // Re-leer de memoria
                dataReg = safeLoad('reporte_regular_diario');
                dataOtr = safeLoad('reporte_vacunacion_diario');
                acumReg = safeLoad('reporte_regular_acumulado');
                acumOtr = safeLoad('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');
            }

            // V43: Sincronización AUTÓNOMA (Sin window.opener)
            function sincronizarDriveDash() {
                if (URL_SHEET.includes("PON_AQUI")) return alert("Configura URL en el script principal.");

                // Leemos acumulados completos
                const regAll = safeLoad('reporte_regular_acumulado');
                const otrAll = safeLoad('reporte_vacunacion_acumulado');

                const pendReg = regAll.filter(r => !r.synced);
                const pendOtr = otrAll.filter(r => !r.synced);
                const payload = [...pendReg, ...pendOtr];

                if (payload.length === 0) return alert("✅ Todo sincronizado.");
                if(!confirm(\`Enviar \${payload.length} registros a Drive?\`)) return;

                fetch(URL_SHEET, {
                    method: "POST", mode: "no-cors", headers: { "Content-Type": "application/json" },
                    body: JSON.stringify(payload)
                }).then(() => {
                    const marcar = (key) => {
                        let data = safeLoad(key);
                        data = data.map(item => {
                            if (payload.some(p => p.cui === item.cui && p.hora === item.hora)) {
                                item.synced = true; item.wasEdited = false;
                            }
                            return item;
                        });
                        localStorage.setItem(key, JSON.stringify(data));
                    };
                    marcar('reporte_regular_diario'); marcar('reporte_regular_acumulado');
                    marcar('reporte_vacunacion_diario'); marcar('reporte_vacunacion_acumulado');

                    alert("🚀 Enviado con éxito!");
                    window.dispatchEvent(new Event('storage'));
                    cargarDatos();
                }).catch(e => alert("Error red."));
            }

            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;
                        }

                        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;
            }

            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;
                master.checked = (totalItems > 0 && totalItems === checkedItems);
            };

            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');
            }

            window.confirmarFiltro = function(tipo, campo, groupId) {
                const containerList = document.getElementById(\`list_\${groupId}\`);
                const masterCheck = document.getElementById('all_' + groupId);

                if (masterCheck && masterCheck.checked) {
                    activeFilters[tipo][campo] = 'ALL';
                } else if (containerList) {
                    const checkedBoxes = containerList.querySelectorAll('.item-check:checked');
                    if (checkedBoxes.length === 0) {
                         activeFilters[tipo][campo] = [];
                    } else {
                        activeFilters[tipo][campo] = Array.from(checkedBoxes).map(cb => String(cb.value));
                    }
                } else {
                    activeFilters[tipo][campo] = 'ALL';
                }

                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));

                    const matchVac = filtros.vacuna === 'ALL' ? true : (Array.isArray(filtros.vacuna) ? filtros.vacuna.includes(String(item.vacuna)) : false);
                    const matchFec = filtros.fecha_vacuna === 'ALL' ? true : (Array.isArray(filtros.fecha_vacuna) ? filtros.fecha_vacuna.includes(String(item.fecha_vacuna)) : false);
                    const matchSer = filtros.servicio === 'ALL' ? true : (Array.isArray(filtros.servicio) ? filtros.servicio.includes(String(item.servicio)) : false);

                    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) => {
                    let statusDot = '🔴'; // Nuevo (synced=false)
                    if (reg.synced) {
                        statusDot = '🟢'; // Sincronizado
                    } else if (reg.wasEdited) {
                        statusDot = '🟡'; // Editado
                    }

                    const row = \`<tr>
                        <td>\${filtered.length - i} \${statusDot}</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];

                if (target.synced === true) {
                    alert("⚠️ ALERTA: Registro editado. Se re-enviará a Drive.");
                    target.synced = false;
                    target.wasEdited = true;
                }

                target.vacuna = nVacuna;
                target.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;
                    if (acumulado[idxAcum].synced === true) {
                        acumulado[idxAcum].synced = false;
                        acumulado[idxAcum].wasEdited = true;
                    }
                    localStorage.setItem(keyAcum, JSON.stringify(acumulado));
                }
                window.dispatchEvent(new Event('storage'));
                cerrarModal();
                cargarDatos();
            }

            window.addEventListener('storage', actualizarSoloTablas);
            window.onload = cargarDatos;
            setInterval(actualizarSoloTablas, 3000);
        </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);
})();