Reporte Vacunación MSPAS (V44 - Zero Latency)

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

当前为 2025-12-01 提交的版本,查看 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

您需要先安装一个扩展,例如 篡改猴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);
})();