WME Places Name Normalizer

Herramienta de asistencia para normalizar nombres de lugares en Waze Map Editor. Detecta inconsistencias, sugiere correcciones, valida categorías y permite aplicar cambios masivos con un solo click.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         WME Places Name Normalizer
// @namespace    https://greasyfork.org/en/users/mincho77
// @author       Mincho77
// @version      9.3.1
// @license      MIT
// @description  Herramienta de asistencia para normalizar nombres de lugares en Waze Map Editor. Detecta inconsistencias, sugiere correcciones, valida categorías y permite aplicar cambios masivos con un solo click.
// @match        https://www.waze.com/editor*
// @match        https://www.waze.com/*/editor*
// @include      https://beta.waze.com/*
// @include      https://www.waze.com/editor*
// @include      https://www.waze.com/*/editor*
// @exclude      https://www.waze.com/user/editor*
// @grant        GM_xmlhttpRequest
// @connect      sheets.googleapis.com
// @connect      script.google.com
// @connect      script.googleusercontent.com
// @connect      wme-normalizer-ai.cotalvaro.workers.dev
// @connect      cotalvaro.workers.dev
// @run-at       document-end
// @require      https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// ==/UserScript==

// Se establecen las variables de control con valores iniciales claros
let isProcessingActive = false;
let isNormalizationActive = true;
let isResultsPanelOpen = false;
let activeScanning = false; // Bandera para indicar si se encuentra en curso un escaneo
window.__PLN_DECISION_DEBUG_ON = false; // Desactivado por defecto para evitar spam en consola



(function () {
    //window.normalizeWordInternal = normalizeWordInternal;
    // Variables globales básicas
    const SCRIPT_NAME = GM_info.script.name;
    const VERSION = GM_info.script.version.toString();
    // Variable global para el ícono de la pestaña principal
    const MAIN_TAB_ICON_BASE64 = "data:image/jpeg;base64,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"
    // === [PLN] Daily Usage Log → Google Sheets (via Apps Script) ===
    // 1) Configure your Apps Script Web App endpoint (POST, Anyone w/ link)
    const PLN_USAGE_LOG = {
        endpoint: 'https://script.google.com/macros/s/AKfycbxkBUFI8RuxrtksAIpvpzmr-ZyeO1huncmG6w-BKKBpcrtXIKEemMAai3zyg1OOYqWJOg/exec',
        timeoutMs: 8000
    };

    // === [PLN] User Tracking → Google Sheets (Separate Sheet) ===
    // 2) Configure your User Tracking Apps Script Web App endpoint
    // NOTA: Debes configurar este endpoint después de crear el Apps Script para usuarios
    const PLN_USER_TRACKING = {
        endpoint: 'https://script.google.com/macros/s/AKfycbxcsZrbJHDX8ic2dcZxt4BmglWdSTt-kfOstGqa65-WGIrrFGwcYAPR6edBmDwGro62/exec', 
        timeoutMs: 20000, // Aumentado a 20 segundos para diagnóstico
        enabled: true
    };

    // === [PLN] Access Control → Validación de lista blanca desde el servidor ===
    // 3) Configure your Access Control Apps Script Web App endpoint
    // Este endpoint valida si un usuario está en la lista blanca (Whitelist)
    const PLN_ACCESS_CONTROL = {
        endpoint: 'https://script.google.com/macros/s/AKfycbxJHNeIJBuroF_Y51po74uYu41VCa9i_6T7kQJv0zWmgg-h0021EaOOetiZVkocz8or/exec', 
        timeoutMs: 10000,
        enabled: true,
        // Estos valores se sobrescriben con lo que diga la hoja "Config"
        defaultMinLevel: 3,
        easyShortcutsMinLevel: 4
    };
    window.PLN_ACCESS_CONTROL = PLN_ACCESS_CONTROL;
    var PLN_ACC_LVL = 2; // Nivel mínimo 3 (rank 2)
    var PLN_ACC_LVL_ES = 4; // Nivel mínimo para EasyShortCuts

    // Basic date helpers (yyyy-mm-dd, local)
    function plnTodayStr() {
        try {
            const d = new Date();
            const y = d.getFullYear();
            const m = String(d.getMonth() + 1).padStart(2, '0');
            const day = String(d.getDate()).padStart(2, '0');
            return `${y}-${m}-${day}`;
        } catch (_) { return ''; }
    }

    // Date + Time helper (yyyy-mm-dd HH:MM:SS, local)
    function plnNowStr() {
        try {
            const d = new Date();
            const y = d.getFullYear();
            const m = String(d.getMonth() + 1).padStart(2, '0');
            const day = String(d.getDate()).padStart(2, '0');
            const h = String(d.getHours()).padStart(2, '0');
            const min = String(d.getMinutes()).padStart(2, '0');
            const s = String(d.getSeconds()).padStart(2, '0');
            return `${y}-${m}-${day} ${h}:${min}:${s}`;
        } catch (_) { return ''; }
    }

    // Persisted list of places where the editor has worked (cities)
    // Stored as a Set in localStorage to be "permanent".
    function plnLoadWorkCities() {
        try {
            const raw = localStorage.getItem('wme_pln_work_cities');
            const arr = raw ? JSON.parse(raw) : [];
            return new Set(Array.isArray(arr) ? arr : []);
        } catch (_) { return new Set(); }
    }
    function plnSaveWorkCities(set) {
        try { localStorage.setItem('wme_pln_work_cities', JSON.stringify(Array.from(set || []))); } catch (_) { }
    }
    function plnAddWorkCity(city) {
        try {
            const c = String(city || '').trim();
            if (!c) return;
            const set = plnLoadWorkCities();
            if (!set.has(c)) {
                set.add(c);
                plnSaveWorkCities(set);
            }
        } catch (_) { }
    }

    // Export helper to register a work city from anywhere (console/UI)
    try {
        window.plnRegisterWorkCity = plnAddWorkCity;
        if (typeof unsafeWindow !== 'undefined') unsafeWindow.plnRegisterWorkCity = plnAddWorkCity;
    } catch (_) { }

    let blinkingPlaces = new Set();
    let blinkState = true;
    let lastBlinkTime = 0;
    let pendingRequests = [];
    const BLINK_INTERVAL = 500; // Tiempo en milisegundos entre cada titilación (0.5 segundos)
    const FORCE_SHEET_RELOAD = true; // Cambia a true para forzar la lectura directa por defecto

    // === Debug helpers for City Apply ===
    const PLN_DEBUG = true; // poner false para silenciar
    // PLN_ACC_LVL declarado arriba
    function plnLog(...args) { if (PLN_DEBUG) console.log('[WME PLN][CityApply]', ...args); }
    function plnWarn(...args) { if (PLN_DEBUG) console.warn('[WME PLN][CityApply]', ...args); }
    function plnErr(...args) {
        console.error('[WME PLN][CityApply]', ...args);
    }

    // Log de guardado global del WME
    window.addEventListener('wme-save-finished', (ev) => {
        plnLog('wme-save-finished', ev && ev.detail);
    });


    // Variables globales para el diccionario de palabras excluidas
    //Permite inicializar el diccionario de palabras intercambiadas
    if (!window.swapWords) {
        const stored = localStorage.getItem("wme_swapWords");
        window.swapWords = stored ? JSON.parse(stored) : [];
    }

    // === Swap Debug Helpers ===
    (function plnSwapDebugInit() {
        try {
            if (window.plnSwapDebug && window.plnSwapExplain) return;
            window.__PLN_SWAP_DEBUG_ON = window.__PLN_SWAP_DEBUG_ON || false;
            window.plnSwapDebug = function (on) {
                window.__PLN_SWAP_DEBUG_ON = !!on;
                try { localStorage.setItem('wme_pln_debug_swap', on ? '1' : '0'); } catch (_) { }
                //console.log('[PLN Swap] debug', on ? 'ON' : 'OFF');
            };
            try { if (typeof unsafeWindow !== 'undefined') unsafeWindow.plnSwapDebug = window.plnSwapDebug; } catch (_) { }
            window.plnSwapExplain = function (name) {
                const prev = window.__PLN_SWAP_DEBUG_ON;
                window.__PLN_SWAP_DEBUG_ON = true;
                console.group('[PLN Swap] TRACE for', name);
                const out = applySwapRules(name);
                //console.log('TRACE result =>', out);
                console.groupEnd();
                window.__PLN_SWAP_DEBUG_ON = prev;
                return out;
            };
            try { if (typeof unsafeWindow !== 'undefined') unsafeWindow.plnSwapExplain = window.plnSwapExplain; } catch (_) { }
        } catch (_) { /* noop */ }
    })();
    // === Decision Debug Helpers (pipeline end‑to‑end) ===
    (function plnDecisionDebugInit() {
        try {
            if (window.plnDecisionDebug && window.plnExplainDecision) return;
            window.__PLN_DECISION_DEBUG_ON = window.__PLN_DECISION_DEBUG_ON || false;

            function noDiacritics(s) {
                return String(s || '')
                    .normalize('NFD')
                    .replace(/[\u0300-\u036f]/g, '')
                    .replace(/\s+/g, ' ')
                    .trim()
                    .toLowerCase();
            }

            window.plnDecisionDebug = function (on) {
                window.__PLN_DECISION_DEBUG_ON = !!on;
                try { localStorage.setItem('wme_pln_debug_decision', on ? '1' : '0'); } catch (_) { }
                //console.log('[PLN Decision] debug', on ? 'ON' : 'OFF');
            };
            try { if (typeof unsafeWindow !== 'undefined') unsafeWindow.plnDecisionDebug = window.plnDecisionDebug; } catch (_) { }

            window.plnExplainDecision = function (name) {
                const origFn = (typeof window.__PLNNormalizeOriginal === 'function') ? window.__PLNNormalizeOriginal
                    : (typeof window.__plnNormalizeOriginal === 'function') ? window.__plnNormalizeOriginal
                        : (typeof window.normalizePlaceName === 'function' ? window.normalizePlaceName : null);

                const input = String(name || '');
                const base = origFn ? origFn(input) : input;
                const baseCap = plnPostSwapCap(base);
                const afterEx = plnApplyExclusions(baseCap);
                const afterSw = applySwapRules(afterEx);
                const renorm = origFn ? origFn(afterSw) : afterSw;
                const finalCap = plnPostSwapCap(renorm);
                const final = plnApplyExclusions(finalCap);

                const equalExact = input === final;
                const noDia = s => String(s || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/\s+/g, ' ').trim().toLowerCase();
                const equalNoCaseNoSpacesNoDiacritics = noDia(input) === noDia(final);

                const out = {
                    original: input, baseNormalized: base, baseCap, afterExclusions: afterEx,
                    afterSwap: afterSw, renormalized: renorm, finalSuggested: final,
                    equalExact, equalNoCaseNoSpacesNoDiacritics
                };
                console.group('[PLN Decision] explain', name);
                console.table(out);
                console.groupEnd();
                return out;
            };
            try { if (typeof unsafeWindow !== 'undefined') unsafeWindow.plnExplainDecision = window.plnExplainDecision; } catch (_) { }
        } catch (_) { /* noop */ }
    })();



    // Muestra un mensaje tipo "toast" en la esquina inferior derecha
    function plnToast(message, duration = 3000) {
        try {
            // --- Lógica de creación de elementos ---
            const createToastElement = () => {
                // Estilo Allen: Prioriza el SDK si está disponible.
                if (window.WME_PLN_SDK && window.WME_PLN_SDK.UI && window.WME_PLN_SDK.UI.createElement) {
                    return window.WME_PLN_SDK.UI.createElement('div', {
                        textContent: message,
                        style: {
                            backgroundColor: '#333',
                            color: 'white',
                            padding: '10px 20px',
                            borderRadius: '8px',
                            boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
                            fontFamily: 'sans-serif',
                            fontSize: '14px',
                            opacity: '0',
                            transition: 'opacity 0.3s ease-in-out, transform 0.3s ease-in-out',
                            transform: 'translateY(20px)'
                        }
                    });
                }
                // Fallback al método tradicional si el SDK no está listo.
                else {
                    const toast = document.createElement('div');
                    toast.textContent = message;
                    toast.style.cssText = `
                    background-color: #333; color: white; padding: 10px 20px;
                    border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.2);
                    font-family: sans-serif; font-size: 14px; opacity: 0;
                    transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
                    transform: translateY(20px);
                `;
                    return toast;
                }
            };

            // --- Lógica para mostrar y ocultar (sin cambios) ---
            let container = document.getElementById('pln-toast-container');
            if (!container) {
                container = document.createElement('div');
                container.id = 'pln-toast-container';
                container.style.cssText = 'position: fixed; bottom: 20px; right: 20px; z-index: 99999; display: flex; flex-direction: column; gap: 10px;';
                document.body.appendChild(container);
            }

            const toast = createToastElement();
            container.appendChild(toast);

            // Animación de entrada
            setTimeout(() => {
                toast.style.opacity = '1';
                toast.style.transform = 'translateY(0)';
            }, 10);

            // Desaparición automática
            setTimeout(() => {
                toast.style.opacity = '0';
                toast.style.transform = 'translateY(20px)';
                toast.addEventListener('transitionend', () => toast.remove());
            }, duration);

        } catch (e) {
            console.error("Error en plnToast:", e);
            alert(message); // Fallback final
        }
    }

    // Exponer plnToast globalmente para addWordToDictionary
    window.plnToast = plnToast;

    // === Hyphen cleanup: keep all hyphens except a dangling one at the end ===
    function plnShouldPreserveTrailingHyphen(name) {
        // Enforce removal of ALL trailing hyphens, even if they match a replacement rule.
        // This fixes issues like "Texaco ⛽" -> "Texaco -" where the hyphen was preserved.
        return false;
    }

    function plnFixDanglingHyphen(s) {
        try {
            let out = String(s == null ? '' : s);

            // Primero, eliminar guiones al final (lógica original)
            const trailingPattern = /\s*-\s*$/;
            if (trailingPattern.test(out) && !plnShouldPreserveTrailingHyphen(out)) {
                out = out.replace(trailingPattern, '');
            }

            // Limpieza adicional: eliminar otros caracteres especiales comunes al final
            // Esto captura restos de emojis convertidos, puntuación, etc.
            // Caracteres a eliminar: -, ., ,, ;, :, /, \, |, _, +, =, *, ~, `, ', ", etc.
            out = out.replace(/[\-.,;:/\\|_+=*~`'"!?]+\s*$/g, '');

            // normaliza espacios múltiples; NO toca guiones internos
            out = out.replace(/\s{2,}/g, ' ').trim();
            return out;
        } catch (_) { return s; }
    }

    // === Row visibility control (hide only when finalized) ===
    function plnSetRowHidden(row, hidden) {
        try {
            if (!row || row.nodeType !== 1) return;
            row.style.display = hidden ? 'none' : '';
        } catch (_) {/* noop */ }
    }

    function plnMarkRowFinalized(row, reason) {
        try {
            if (!row || row.nodeType !== 1) return;
            // mark as finalized ONLY for explicit reasons (manual-apply, auto-rule)
            const r = String(reason || '').trim();
            row.dataset.normalized = (r && r !== 'ai-preview') ? 'true' : 'false';
            if (r) row.dataset.normalizedReason = r;
            // hide only when it's actually finalized (not preview)
            plnSetRowHidden(row, (r && r !== 'ai-preview'));
        } catch (_) {/* noop */ }
    }

    function plnMaybeHideRow(row) {
        try {
            if (!row || row.nodeType !== 1) return;
            const isManual = row.dataset.manualNormalized === 'true';
            const isNormalized = row.dataset.normalized === 'true';
            const reason = String(row.dataset.normalizedReason || '').trim();
            const isPreview = reason === 'ai-preview' || row.dataset.aiPreview === '1' || row.dataset.aiProcessing === 'true';
            // Only hide if manual OR normalized for a non-preview reason
            if ((isManual) || (isNormalized && !isPreview)) {
                plnSetRowHidden(row, true);
            } else {
                plnSetRowHidden(row, false);
            }
        } catch (_) {/* noop */ }
    }

    /** [PLN][UI] Detección de enlaces de Google Maps dentro de un ámbito (fila/contendedor/document) */
    function plnDetectGoogleMapsLinkInScope(scope) {
        try {
            const root = (scope && scope.nodeType === 1) ? scope : document;

            // 1) Enlaces visibles y botones con data-url
            const nodes = Array.from(root.querySelectorAll('a[href],button[data-url],div[data-url]'));
            const hrefMatches = (u) => {
                const s = String(u || '');
                return /https?:\/\/(www\.)?google\.[^\/]+\/maps/i.test(s)
                    || /https?:\/\/goo\.gl\/maps/i.test(s)
                    || (/google\.[^\/]+\/search\?/i.test(s) && /(cid=|q=place_id:|ftid=)/i.test(s))
                    || /\/maps\/place\//i.test(s);
            };
            if (nodes.some(n => hrefMatches(n.getAttribute('href') || n.getAttribute('data-url') || ''))) return true;

            // 2) Metadatos comunes en DOM (cuando el link no es visible)
            if (root.querySelector('[data-placeid],[data-place-id],[data-google-cid],[data-cid],[data-gcid]')) return true;
        } catch (_) { }
        return false;
    }

    /** [PLN][UI] Detecta vía SDK si el Venue tiene Google Place asociado */
    async function plnHasGooglePlaceAssociation(placeId, scope) {
        try {
            // 0) Heurística DOM primero (más barata)
            if (plnDetectGoogleMapsLinkInScope(scope)) return true;

            // 1) Resolver ID
            let id = placeId;
            if (!id) {
                try {
                    const r = (scope && scope.nodeType === 1) ? scope : null;
                    id = (r && (r.dataset.placeId || r.dataset.venueId)) || null;
                    if (!id && r) {
                        const cand = r.querySelector('[data-place-id],[data-placeid],[data-venue-id],[data-id]');
                        if (cand) id = cand.dataset.placeId || cand.dataset.venueId || cand.getAttribute('data-id');
                    }
                } catch (_) { id = null; }
            }
            if (!id) return false;

            // 2) SDK
            const sdk = await plnEnsureSdkReady(4000);
            if (!sdk?.DataModel?.Venues?.getById) return false;
            const v = await sdk.DataModel.Venues.getById({ venueId: String(id) });
            if (!v) return false;

            const hasGoogle = (val) => {
                try {
                    if (val == null) return false;
                    if (typeof val === 'string') return /google|gplace|place_id|cid/i.test(val);
                    if (Array.isArray(val)) return val.some(hasGoogle);
                    if (typeof val === 'object') {
                        const s = JSON.stringify(val).toLowerCase();
                        return s.includes('google') && (s.includes('place') || s.includes('cid') || s.includes('gmb'));
                    }
                    return false;
                } catch (_) { return false; }
            };

            // Campos comunes observados en builds de WME
            if (hasGoogle(v.externalProviderRefs)) return true;
            if (hasGoogle(v.externalProviders)) return true;
            if (hasGoogle(v.externalRefs)) return true;
            if (hasGoogle(v.providers)) return true;
            if (hasGoogle(v.googlePlaceId || v.googlePlace || v.googlePlaces)) return true;
            if (hasGoogle(v.attributes && v.attributes.externalProviderRefs)) return true;
            if (hasGoogle(v.providerReferences || v.providerIds)) return true;
        } catch (_) { }
        return false;
    }

    /** [PLN][Google] Servicio de autocompletado (sugerir cuando no hay enlace de Google) */
    function plnGetPlaceAutocomplete() {
        try {
            const g = (typeof google !== 'undefined') ? google : null;
            const places = g && g.maps && g.maps.places ? g.maps.places : null;

            // Builds nuevas: AutocompleteSuggestion
            if (places && typeof places.AutocompleteSuggestion === 'function') {
                return new places.AutocompleteSuggestion();
            }
            // Compat: cuentas antiguas
            if (places && typeof places.AutocompleteService === 'function') {
                return new places.AutocompleteService();
            }
        } catch (e) {
            console.warn('[PLN] Error inicializando Google Places autocomplete.', e);
            return null;
        }
        console.warn('[PLN] Google Places autocomplete no disponible en esta sesión.');
        return null;
    }



    // === [PLN] Daily Usage Log core ===
    async function plnResolveCurrentUser() {
        // Try SDK → WazeWrap → WME internal (functions already in this file)
        try {
            if (window.__PLN_DECISION_DEBUG_ON) console.log('[WME PLN] 🕵️‍♂️ plnResolveCurrentUser: Starting detection...');
            let u = await getCurrentEditorViaSdk();
            if (u && window.__PLN_DECISION_DEBUG_ON) console.log('[WME PLN] ✅ User found via SDK:', u);

            if (!u) {
                if (window.__PLN_DECISION_DEBUG_ON) console.log('[WME PLN] ⚠️ User not found via SDK, trying WazeWrap...');
                u = getCurrentEditorViaWazeWrap();
                if (u && window.__PLN_DECISION_DEBUG_ON) console.log('[WME PLN] ✅ User found via WazeWrap:', u);
            }

            if (!u) {
                if (window.__PLN_DECISION_DEBUG_ON) console.log('[WME PLN] ⚠️ User not found via WazeWrap, trying WME Internal...');
                u = getCurrentEditorViaWmeInternal();
                if (u && window.__PLN_DECISION_DEBUG_ON) console.log('[WME PLN] ✅ User found via WME Internal:', u);
            }

            if (u && typeof u.id !== 'undefined') {
                currentGlobalUserInfo = { id: u.id, name: u.name || 'No detectado', privilege: u.privilege || 'N/A' };
                if (window.__PLN_DECISION_DEBUG_ON) console.log('[WME PLN] 🏁 plnResolveCurrentUser: Final result:', currentGlobalUserInfo);
                return currentGlobalUserInfo;
            }
        } catch (e) {
            console.error('[WME PLN] ❌ plnResolveCurrentUser Error:', e);
        }
        console.warn('[WME PLN] ⚠️ plnResolveCurrentUser: Failed to detect user.');
        return { id: null, name: 'No detectado', privilege: 'N/A' };
    }

    function plnHasLoggedToday(userId) {
        try {
            const key = `wme_pln_last_usage_${userId}`;
            const last = localStorage.getItem(key);
            return last === plnTodayStr();
        } catch (_) { return false; }
    }

    function plnMarkLoggedToday(userId) {
        try {
            const key = `wme_pln_last_usage_${userId}`;
            localStorage.setItem(key, plnTodayStr());
        } catch (_) { }
    }

    /**
     * Send a single POST to Apps Script only once per day per user.
     * Payload fields requested:
     *  - date (yyyy-mm-dd), userId, privilege (level), userName
     *  - workCities (permanent list from localStorage)
     */
    function plnLogUsageOncePerDay() {
        try {
            if (!PLN_USAGE_LOG || !PLN_USAGE_LOG.endpoint || !/^https?:\/\//i.test(PLN_USAGE_LOG.endpoint)) {
                // Not configured → nothing to do
                return;
            }
            const u = currentGlobalUserInfo || {};
            if (u.id == null || u.id === 0) return; // Need a user id
            if (plnHasLoggedToday(u.id)) return;    // Already logged today

            const cities = Array.from(plnLoadWorkCities());
            const payload = {
                date: plnTodayStr(),
                userId: u.id,
                privilege: u.privilege || 'N/A',
                userName: u.name || 'No detectado',
                workCities: cities,
                tz: Intl.DateTimeFormat().resolvedOptions().timeZone || 'America/Bogota',
                scriptVersion: (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.version) ? String(GM_info.script.version) : 'N/A'
            };

            // Send POST via GM_xmlhttpRequest (with visible error toasts)
            makeRequest({
                method: 'POST',
                url: PLN_USAGE_LOG.endpoint,
                data: JSON.stringify(payload),
                headers: {
                    'Content-Type': 'text/plain;charset=utf-8'   // evita preflight/CORS
                },
                timeout: PLN_USAGE_LOG.timeoutMs || 8000,
                onload: function (resp) {
                    try {
                        const status = String(resp && resp.status || '');
                        if (status.startsWith('2')) {
                            plnMarkLoggedToday(u.id);
                            try { plnToast('✅ Uso registrado en nube', 1800); } catch (_) { }
                        } else {
                            const msg = `[PLN] HTTP ${resp && resp.status} al registrar uso`;
                            try { plnToast(`⚠️ ${msg}`, 3000); } catch (_) { }
                            try { console.warn(msg, resp && resp.responseText); } catch (_) { }
                        }
                    } catch (err) {
                        try { plnToast('⚠️ Error inesperado al procesar respuesta', 3000); } catch (_) { }
                        try { console.error('[PLN] onload handler error', err); } catch (_) { }
                    }
                },
                onerror: function (err) {
                    try { plnToast('🚫 Fallo de conexión al registrar uso', 3200); } catch (_) { }
                    try { console.error('[PLN] Error de red al registrar uso:', err); } catch (_) { }
                },
                onabort: function () {
                    try { plnToast('⏹️ Envío cancelado (abort)', 2500); } catch (_) { }
                },
                ontimeout: function () {
                    try { plnToast('⏱️ Tiempo de espera agotado al registrar uso', 3000); } catch (_) { }
                }
            });
        } catch (e) {
            // console.error('[PLN Usage] unexpected error', e);
        }
    }

    /**
     * [PLN] User Activity Tracking - Separate from daily usage log
     * Registers user info in a separate Google Sheet to track:
     * - Username, User ID, Privilege Level
     * - Last usage date (updated every time the script runs)
     * - Total number of times the script has been used
     *
     * Unlike daily log, this runs EVERY TIME the script loads (not just once per day)
     */
    function plnTrackUserActivity() {
        try {
            // Check if tracking is enabled and configured
            if (!PLN_USER_TRACKING || !PLN_USER_TRACKING.enabled) {
                return; // Tracking disabled
            }

            if (!PLN_USER_TRACKING.endpoint || !/^https?:\/\//i.test(PLN_USER_TRACKING.endpoint)) {
                console.warn('[PLN User Tracking] Endpoint no configurado');
                return;
            }

            const u = currentGlobalUserInfo || {};
            if (u.id == null || u.id === 0) {
                console.warn('[PLN User Tracking] No se detectó ID de usuario');
                return;
            }

            // console.log('[PLN User Tracking] 🔍 DEBUG - currentGlobalUserInfo:', currentGlobalUserInfo);
            // console.log('[PLN User Tracking] 🔍 DEBUG - u.id:', u.id, 'tipo:', typeof u.id);
            // console.log('[PLN User Tracking] 🔍 DEBUG - u.privilege:', u.privilege, 'tipo:', typeof u.privilege);

            // WME usa índices base-0 para rank (0-5), convertir a nivel real (1-6)
            let displayLevel = 'N/A';
            if (typeof u.privilege === 'number') {
                displayLevel = u.privilege + 1;
            } else if (u.privilege && u.privilege !== 'N/A') {
                displayLevel = u.privilege;
            }

            // Obtener estadísticas de ediciones del usuario
            // IMPORTANTE: editorStats usa userName como clave, no userId
            const userStats = editorStats[u.name] || { daily_count: 0, total_count: 0 };

            // DEBUG: Verificar que editorStats se esté cargando correctamente
            // console.log('[PLN User Tracking] 🔍 DEBUG - editorStats completo:', editorStats);
            // console.log('[PLN User Tracking] 🔍 DEBUG - Buscando con userName:', u.name);
            // console.log('[PLN User Tracking] 🔍 DEBUG - userStats encontrado:', userStats);
            // console.log('[PLN User Tracking] 🔍 DEBUG - daily_count:', userStats.daily_count, 'total_count:', userStats.total_count);

            const payload = {
                userId: u.id,
                userName: u.name || 'No detectado',
                privilege: displayLevel,
                date: plnNowStr(), // Fecha y hora completa
                scriptVersion: (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.version)
                    ? String(GM_info.script.version)
                    : 'N/A',
                dailyEdits: userStats.daily_count || 0,  // Ediciones del día
                totalEdits: userStats.total_count || 0   // Ediciones totales
            };

            // console.log('[PLN User Tracking] 📤 Enviando datos:', payload);

            // Send POST request
            makeRequest({
                method: 'POST',
                url: PLN_USER_TRACKING.endpoint,
                data: JSON.stringify(payload),
                headers: {
                    'Content-Type': 'text/plain;charset=utf-8'
                },
                timeout: PLN_USER_TRACKING.timeoutMs || 8000,
                onload: function (resp) {
                    try {
                        const status = String(resp && resp.status || '');
                        if (status.startsWith('2')) {
                            // Success - silent (no toast to avoid spam)
                            // console.log('[PLN User Tracking] ✅ Usuario registrado/actualizado');

                            // Optional: Parse response to see if user was created or updated
                            try {
                                const result = JSON.parse(resp.responseText);
                                if (result.action === 'created') {
                                    console.log('[PLN User Tracking] 🆕 Nuevo usuario registrado');
                                } else if (result.action === 'updated') {
                                    // console.log('[PLN User Tracking] ♻️ Usuario actualizado. Total usos:', result.totalUses);
                                }
                            } catch (_) { }
                        } else {
                            console.warn('[PLN User Tracking] ⚠️ HTTP', resp && resp.status, resp && resp.responseText);
                        }
                    } catch (err) {
                        console.error('[PLN User Tracking] Error procesando respuesta:', err);
                    }
                },
                onerror: function (err) {
                    console.error('[PLN User Tracking] 🚫 Error de conexión:', err);
                },
                onabort: function () {
                    console.warn('[PLN User Tracking] ⏹️ Envío cancelado');
                },
                ontimeout: function () {
                    console.warn('[PLN User Tracking] ⏱️ Timeout al registrar usuario');
                }
            });
        } catch (e) {
            console.error('[PLN User Tracking] Error inesperado:', e);
        }
    }

    // === Debug helper: force a log now (clears today's guard for current user) ===
    try {
        window.plnForceLogNow = function () {
            plnResolveCurrentUser().then(() => {
                try {
                    const uid = (currentGlobalUserInfo && currentGlobalUserInfo.id != null) ? currentGlobalUserInfo.id : 'test';
                    localStorage.removeItem(`wme_pln_last_usage_${uid}`);
                } catch (_) { }
                plnLogUsageOncePerDay();
                try { plnToast('Intentando enviar log ahora...', 2000); } catch (_) { }
            });
        };
        if (typeof unsafeWindow !== 'undefined') unsafeWindow.plnForceLogNow = window.plnForceLogNow;
    } catch (_) { }

    // === Debug helper: force user tracking now ===
    try {
        window.plnForceTrackUser = function () {
            plnResolveCurrentUser().then(() => {
                plnTrackUserActivity();
                try { plnToast('📊 Intentando registrar usuario ahora...', 2000); } catch (_) { }
            });
        };
        if (typeof unsafeWindow !== 'undefined') unsafeWindow.plnForceTrackUser = window.plnForceTrackUser;
    } catch (_) { }

    // ===================================================================
    // [PLN][SDK] Helper
    // ===================================================================
    /** [PLN][SDK] Espera a que el SDK esté 100% listo antes de operar.
     *  - Verifica existencia de getWmeSdk / wmeSDK
     *  - Espera DataModel y los métodos básicos de Venues
     *  - Opcionalmente reintenta si el modelo aún está cargando
     */
    async function plnEnsureSdkReady(maxMs = 5000) {
        const t0 = Date.now();
        // 1) Asegurar instancia
        if (!window.WME_PLN_SDK) {
            try {
                if (typeof getWmeSdk === 'function') {
                    window.WME_PLN_SDK = getWmeSdk({ scriptId: 'WMEPlacesNameInspector', scriptName: 'WME PLN' });
                } else if (typeof wmeSDK !== 'undefined') {
                    window.WME_PLN_SDK = wmeSDK;
                }
            } catch (_) { }
        }
        let sdk = window.WME_PLN_SDK || (typeof wmeSDK !== 'undefined' ? wmeSDK : null);

        // 2) Esperar a que exista DataModel y Venues.getById
        while (Date.now() - t0 < maxMs) {
            try {
                if (!sdk) {
                    if (typeof getWmeSdk === 'function') {
                        window.WME_PLN_SDK = getWmeSdk({ scriptId: 'WMEPlacesNameInspector', scriptName: 'WME PLN' });
                        sdk = window.WME_PLN_SDK;
                    } else if (typeof wmeSDK !== 'undefined') {
                        sdk = wmeSDK;
                    }
                }
                if (sdk?.DataModel?.Venues?.getById) return sdk;

                // Algunas builds exponen ready()
                if (typeof sdk?.ready === 'function') {
                    try { await sdk.ready(); } catch (_) { }
                    if (sdk?.DataModel?.Venues?.getById) return sdk;
                }
            } catch (_) { }
            await new Promise(r => setTimeout(r, 100));
        }
        // Devuelve lo que haya; los callers validan capacidades
        return sdk;
    }
    // === [PLN][AI] Robust JSON parsing (tolerant to extra bytes, logs, or fences) ===
    function plnExtractFirstJsonObject(text) {
        try {
            const s = String(text || '').trim().replace(/^\uFEFF/, '');
            // Strip markdown fences like ```json ... ```
            const fenced = s.match(/```(?:json)?\s*([\s\S]*?)```/i);
            const source = fenced ? fenced[1].trim() : s;

            // Fast path
            try { return JSON.parse(source); } catch (_) { }

            // Try to find the first balanced JSON object { ... }
            let inStr = false, esc = false, depth = 0, start = -1, end = -1;
            for (let i = 0; i < source.length; i++) {
                const ch = source[i];
                if (inStr) {
                    if (esc) { esc = false; continue; }
                    if (ch === '\\') { esc = true; continue; }
                    if (ch === '"') { inStr = false; continue; }
                    continue;
                }
                if (ch === '"') { inStr = true; continue; }
                if (ch === '{') {
                    if (depth === 0) start = i;
                    depth++;
                    continue;
                }
                if (ch === '}') {
                    depth--;
                    if (depth === 0) {
                        end = i;
                        const candidate = source.slice(start, end + 1);
                        try { return JSON.parse(candidate); } catch (_) { /* keep scanning */ }
                    }
                }
                // also tolerate arrays as top-level
                if (ch === '[') {
                    if (depth === 0) start = i;
                    depth++;
                }
                if (ch === ']') {
                    depth--;
                    if (depth === 0) {
                        end = i;
                        const candidate = source.slice(start, end + 1);
                        try { return JSON.parse(candidate); } catch (_) { /* keep scanning */ }
                    }
                }
            }

            // Last fallback: cut at the last closing brace/ bracket
            const lastCurly = source.lastIndexOf('}');
            const lastBracket = source.lastIndexOf(']');
            const cut = Math.max(lastCurly, lastBracket);
            if (cut >= 0) {
                const slice = source.slice(0, cut + 1);
                try { return JSON.parse(slice); } catch (_) { }
            }
        } catch (_) { }
        return null;
    }

    function plnSafeJsonParse(text) {
        try {
            // First attempt
            return JSON.parse(text);
        } catch (err1) {
            const recovered = plnExtractFirstJsonObject(text);
            if (recovered !== null) return recovered;
            return {
                error: 'Respuesta de IA no válida (parser tolerante no pudo recuperar JSON).',
                details: String(err1 && err1.message || err1),
                rawHead: String(text || '').slice(0, 400)
            };
        }
    }

    // Llama al Worker de IA para obtener sugerencias de nombres normalizados.
    async function getAiSuggestions(request) {
        const WORKER_URL = 'https://wme-normalizer-ai.cotalvaro.workers.dev/';
        const DATA_SOURCES = ['foursquare', 'googlePlaces', 'yelp', 'openStreetMap'];

        return new Promise((resolve) => {
            if (typeof makeRequest !== 'function') {
                console.error('[WME PLN][AI] makeRequest no está disponible.', { request });
                resolve({ error: "La función 'makeRequest' no está disponible." });
                return;
            }

            const safeRequest = typeof request === 'object' && request !== null ? request : {};
            const payload = {
                placeName: safeRequest.placeName,
                // Compat: aceptar cityName pero enviar 'city' al Worker
                city: (safeRequest.city || safeRequest.cityName || ''),
                // Compat: aceptar categoryKeys pero enviar 'wazeCategories' al Worker
                wazeCategories: Array.isArray(safeRequest.wazeCategories)
                    ? safeRequest.wazeCategories
                    : (Array.isArray(safeRequest.categoryKeys) ? safeRequest.categoryKeys : []),
                sources: DATA_SOURCES,
                metadata: { requestedAt: new Date().toISOString() }
            };
            console.debug('[WME PLN][AI] → request', payload);
            const requestDebug = { url: WORKER_URL, payload };

            makeRequest({
                method: "POST",
                url: WORKER_URL,
                headers: { "Content-Type": "application/json" },
                data: JSON.stringify(payload),
                timeout: 20000,
                onload: (response) => {
                    const isOk = response.status >= 200 && response.status < 300;
                    if (isOk) {
                        try {
                            const parsed = plnSafeJsonParse(response.responseText);
                            console.debug('[WME PLN][AI] ← response', parsed);
                            resolve(parsed);
                        } catch (e) {
                            console.error('[WME PLN][AI] Error parseando respuesta del agente.', {
                                error: e,
                                responseText: response.responseText,
                                request: requestDebug
                            });
                            resolve({
                                error: "Respuesta de IA no válida.",
                                responseText: response.responseText
                            });
                        }
                    } else {
                        console.error('[WME PLN][AI] El agente respondió con error HTTP.', {
                            status: response.status,
                            responseText: response.responseText,
                            request: requestDebug
                        });
                        try {
                            resolve(plnSafeJsonParse(response.responseText));
                        } catch (_) {
                            resolve({
                                error: `Error HTTP ${response.status}.`,
                                responseText: response.responseText
                            });
                        }
                    }
                },
                onerror: (error) => {
                    console.error('[WME PLN][AI] Error de red al consultar el agente.', {
                        error,
                        request: requestDebug
                    });
                    try { plnToast('🚫 Error de red con el agente IA', 2800); } catch (_) { }
                    resolve({ error: "Error de red.", details: error });
                },
                ontimeout: () => {
                    console.error('[WME PLN][AI] Timeout al consultar el agente.', requestDebug);
                    try { plnToast('⏱️ Timeout consultando el agente IA', 2800); } catch (_) { }
                    resolve({ error: "Timeout.", request: requestDebug });
                }
            });
        });
    }//getAiSuggestions


    /** [PLN][AI] Resolver ciudad real del Venue usando solo el SDK.
     *  Cubre:
     *   1) address.city.name / primaryName
     *   2) address.street.city (si la ciudad viene derivada de la calle)
     *   3) address.streetId -> Streets.getById().city
     *   4) entryPoints[0].street?.city
     *   5) freeformAddress o address.text: último token como heurística
     */
    async function plnResolveCityNameViaSdk(placeId) {
        try {
            if (!window.WME_PLN_SDK && typeof getWmeSdk === "function") {
                window.WME_PLN_SDK = getWmeSdk({ scriptId: "WMEPlacesNameInspector", scriptName: "WME PLN" });
            }
            const sdk = window.WME_PLN_SDK || wmeSDK;
            const venue = await sdk.DataModel.Venues.getById({ venueId: String(placeId) });
            if (!venue) return "Desconocida";

            // 1) Ciudad directa en la dirección
            const directCity = (venue.address && (venue.address.city?.name || venue.address.city?.primaryName));
            if (directCity) return String(directCity);

            // 2) Ciudad desde la calle embebida en la address (WME suele derivarla así)
            const addrStreetCity = venue.address && venue.address.street && (venue.address.street.city?.name || venue.address.street.city?.primaryName);
            if (addrStreetCity) return String(addrStreetCity);

            // 3) address.streetId -> Streets.getById().city
            const streetId = venue.address && (venue.address.streetId || venue.address.street?.id || venue.address.street?.ID || venue.address.street?.streetId);
            if (streetId && sdk.DataModel.Streets && sdk.DataModel.Streets.getById) {
                try {
                    const st = await sdk.DataModel.Streets.getById({ streetId: String(streetId) });
                    const stCity = st && st.city && (st.city.name || st.city.primaryName);
                    if (stCity) return String(stCity);
                } catch (_) { }
            }

            // 4) entryPoints → street.city
            if (Array.isArray(venue.entryPoints) && venue.entryPoints.length) {
                const ep = venue.entryPoints[0];
                const epCity = ep && ep.street && (ep.street.city?.name || ep.street.city?.primaryName);
                if (epCity) return String(epCity);
                const epStreetId = ep && (ep.streetId || ep.street?.id);
                if (epStreetId && sdk.DataModel.Streets && sdk.DataModel.Streets.getById) {
                    try {
                        const st = await sdk.DataModel.Streets.getById({ streetId: String(epStreetId) });
                        const stCity = st && st.city && (st.city.name || st.city.primaryName);
                        if (stCity) return String(stCity);
                    } catch (_) { }
                }
            }

            // 5) Heurística de última coma en la dirección libre
            const free = (venue.address && (venue.address.freeformAddress || venue.address.text)) || '';
            if (free) {
                const last = String(free).split(',').map(s => s.trim()).filter(Boolean).pop();
                if (last) return last;
            }

            return "Desconocida";
        } catch (e) {
            console.warn("[PLN][AI] plnResolveCityNameViaSdk fallo:", e);
            return "Desconocida";
        }
    }

    /** [PLN][AI] Resolver ciudad leyendo el MISMO origen visual del “chulito de ciudad”
     *  Busca en el row cualquier elemento con `title` o texto que empiece por "Ciudad:"
     *  Ej.: title="Ciudad: Bello (derivada de la calle)"  →  "Bello"
     */
    function plnResolveCityFromRowDom(row) {
        try {
            if (!row || row.nodeType !== 1) return '';
            // 1) Revisa elementos con title
            const els = Array.from(row.querySelectorAll('[title]'));
            for (const el of els) {
                const t = String(el.getAttribute('title') || '').trim();
                if (t && /^Ciudad\s*:/.test(t)) {
                    // Extrae lo que va tras "Ciudad:" hasta un paréntesis o fin
                    const m = t.match(/^Ciudad\s*:\s*([^()]+?)(?:\s*\(|$)/i);
                    if (m && m[1]) return m[1].trim();
                }
            }
            // 2) Revisa texto directo en la celda Tipo/Ciudad si existe
            const maybeCell = row.querySelector('td') || null;
            if (maybeCell) {
                const txt = String(maybeCell.textContent || '').trim();
                const m2 = txt.match(/\bCiudad\s*:\s*([^()]+?)(?:\s*\(|$)/i);
                if (m2 && m2[1]) return m2[1].trim();
            }
        } catch (_) { }
        return '';
    }

    /** [PLN][AI] Heurística local de categorías cuando el agente no devuelve nada */
    function plnGuessCategoriesByName(name) {
        try {
            const n = String(name || '').toLowerCase();

            const out = new Set();

            // Transporte / logística / encomiendas
            if (/(transporte|transportes|transportadora|mensajer(ia|ía)|paqueter[ií]a|log[ií]stic|carga|fletes|mudanz)/.test(n)) {
                out.add('TRANSPORTATION');
                out.add('CAR_SERVICES'); // fallback general
            }
            // Gobierno, oficina pública
            if (/(alcald[ií]a|gobernaci[oó]n|secretar[ií]a|oficina\s+(de\s+)?(tr[áa]nsito|hacienda|planeaci[oó]n)|impuest|municipalidad|ayuntamiento)/.test(n)) {
                out.add('GOVERNMENT');
                out.add('CITY_HALL');
            }
            // Religioso
            if (/(parroquia|iglesia|templo|capilla|comunidad\s+cristiana)/.test(n)) out.add('RELIGIOUS_CENTER');
            // Personal care
            if (/(est[ée]tica|peluquer[ií]a|barber[ií]a|manicur|spa|belleza|odontolog|dent|cl[íi]nica\s+dental)/.test(n)) out.add('PERSONAL_CARE');
            // Banco/financiera
            if (/(banco|cooperativa\s+de\s+cr[eé]dito|financier[ao]|caja\s+de\s+compensaci[oó]n)/.test(n)) { out.add('BANK'); out.add('BANK_FINANCIAL'); }
            // Educación
            if (/(colegio|escuela|universidad|institut[oa]|jard[ií]n\s+infantil|kinder|kinderg|preescolar)/.test(n)) { out.add('SCHOOL'); out.add('UNIVERSITY'); }
            // Salud
            if (/(cl[íi]nica|hospital|urgencias|m[eé]dica|doct\.?|odontolog)/.test(n)) { out.add('HOSPITAL'); out.add('DOCTOR_CLINIC'); }

            // Normaliza a array en mayúsculas
            const arr = Array.from(out).map(k => String(k).toUpperCase());
            return arr.length ? arr : [];
        } catch (_) { return []; }
    }

    /** [PLN][AI] Aplicar categorías al Venue usando SOLO el SDK, con rutas de compatibilidad */
    async function plnApplyCategoriesViaSdk(placeId, categoryKeysUpperArray) {
        if (!placeId) throw new Error('placeId requerido');

        // Esperar SDK listo y normalizar ID
        const sdk = await plnEnsureSdkReady(6000);
        const venueIdStr = String(placeId);
        const venueIdNum = Number.isNaN(Number(placeId)) ? null : Number(placeId);
        if (!sdk) throw new Error('SDK no disponible');

        // Normaliza a UPPER y prepara dos formatos: objetos {key} y strings "KEY"
        const keysUpper = (categoryKeysUpperArray || [])
            .filter(Boolean)
            .map(k => String(k).toUpperCase());
        if (!keysUpper.length) return { applied: false, reason: 'No hay categorías para aplicar' };

        const catsObj = keysUpper.map(k => ({ key: k }));
        const catsStr = keysUpper.slice(0);

        // Log de capacidades detectadas para diagnosticar rápidamente
        try {
            const venuePeek = await sdk?.DataModel?.Venues?.getById?.({ venueId: venueIdStr });
            const caps = {
                hasSDK: !!sdk,
                namespaces: Object.keys(sdk || {}),
                hasDataModel: !!sdk?.DataModel,
                hasCommands: !!sdk?.Commands,
                hasOperations: !!sdk?.Operations
            };
            console.debug('[WME PLN][AI] SDK namespaces/caps:', caps);
        } catch (_) { }

        // Helper interno para intentar una llamada y reportar método usado
        async function tryMethod(label, fn) {
            try {
                await fn();
                return { ok: true, method: label };
            } catch (e) {
                console.warn('[WME PLN][AI] Categorías método falló:', label, e);
                return { ok: false, err: e };
            }
        }

        // 1) Ruta directa sobre el objeto Venue
        try {
            const venue = await sdk.DataModel?.Venues?.getById?.({ venueId: venueIdStr });
            if (venue) {
                // Algunas versiones requieren refresh() antes de mutar
                try { if (typeof venue.refresh === 'function') await venue.refresh(); } catch (_) { }

                // Probar con payloads usando {key} y strings y ambos tipos de id
                if (typeof venue.setCategories === 'function') {
                    let r = await tryMethod('venue.setCategories([{key}])', () => venue.setCategories(catsObj));
                    if (r.ok) return { applied: true, method: r.method, payload: catsObj };

                    r = await tryMethod('venue.setCategories(["KEY"])', () => venue.setCategories(catsStr));
                    if (r.ok) return { applied: true, method: r.method, payload: catsStr };

                    if (typeof venue.update === 'function') {
                        r = await tryMethod('venue.update({categories:[{key}]})', () => venue.update({ categories: catsObj }));
                        if (r.ok) return { applied: true, method: r.method, payload: catsObj };

                        r = await tryMethod('venue.update({categories:["KEY"]})', () => venue.update({ categories: catsStr }));
                        if (r.ok) return { applied: true, method: r.method, payload: catsStr };
                    }
                }
                // 1.b update({categories:[{key}]}) y update({categories:["KEY"]})
                if (typeof venue.update === 'function') {
                    let r = await tryMethod('venue.update({categories:[{key}]})', () => venue.update({ categories: catsObj }));
                    if (r.ok) return { applied: true, method: r.method, payload: catsObj };
                    r = await tryMethod('venue.update({categories:["KEY"]})', () => venue.update({ categories: catsStr }));
                    if (r.ok) return { applied: true, method: r.method, payload: catsStr };
                }
            }
        } catch (e1) {
            console.warn('[WME PLN][AI] Acceso directo al Venue falló', e1);
        }

        // 2) DataModel global
        try {
            if (sdk.DataModel?.Venues?.update) {
                let r = await tryMethod('DataModel.Venues.update({categories:[{key}]})',
                    () => sdk.DataModel.Venues.update({ venueId: venueIdStr, categories: catsObj }));
                if (r.ok) return { applied: true, method: r.method, payload: catsObj };

                r = await tryMethod('DataModel.Venues.update({categories:["KEY"]})',
                    () => sdk.DataModel.Venues.update({ venueId: venueIdStr, categories: catsStr }));
                if (r.ok) return { applied: true, method: r.method, payload: catsStr };

                r = await tryMethod('DataModel.Venues.update({id, categories:[{key}]})',
                    () => sdk.DataModel.Venues.update({ id: venueIdStr, categories: catsObj }));
                if (r.ok) return { applied: true, method: r.method, payload: catsObj };

                if (venueIdNum !== null) {
                    r = await tryMethod('DataModel.Venues.update({id:Number, categories:["KEY"]})',
                        () => sdk.DataModel.Venues.update({ id: venueIdNum, categories: catsStr }));
                    if (r.ok) return { applied: true, method: r.method, payload: catsStr };
                }
            }
        } catch (e2) {
            console.warn('[WME PLN][AI] DataModel.Venues.update falló', e2);
        }

        // 3) Operations (algunas versiones exponen esto)
        try {
            if (sdk.Operations?.Venues?.updateCategories) {
                let r = await tryMethod('Operations.Venues.updateCategories([{key}])',
                    () => sdk.Operations.Venues.updateCategories({ venueId: venueIdStr, categories: catsObj }));
                if (r.ok) return { applied: true, method: r.method, payload: catsObj };

                r = await tryMethod('Operations.Venues.updateCategories(["KEY"])',
                    () => sdk.Operations.Venues.updateCategories({ venueId: venueIdStr, categories: catsStr }));
                if (r.ok) return { applied: true, method: r.method, payload: catsStr };
            }
            if (sdk.Operations?.Venues?.setCategories) {
                let r = await tryMethod('Operations.Venues.setCategories([{key}])',
                    () => sdk.Operations.Venues.setCategories({ venueId: venueIdStr, categories: catsObj }));
                if (r.ok) return { applied: true, method: r.method, payload: catsObj };
            }
        } catch (e3) {
            console.warn('[WME PLN][AI] Operations.Venues.updateCategories/setCategories falló', e3);
        }

        // 4) Commands genérico (nombres varían entre builds)
        try {
            if (sdk.Commands?.apply) {
                const idVariants = [venueIdStr].concat(venueIdNum !== null ? [venueIdNum] : []);
                const cmdPayloads = [];
                for (const idv of idVariants) {
                    cmdPayloads.push(['Commands.apply:Venue.SetCategories([{key}])', [{ type: 'Venue.SetCategories', venueId: idv, categories: catsObj }]]);
                    cmdPayloads.push(['Commands.apply:Venue.SetCategories(["KEY"])', [{ type: 'Venue.SetCategories', venueId: idv, categories: catsStr }]]);
                    cmdPayloads.push(['Commands.apply:Venues.Update({categories:[{key}]})', [{ type: 'Venues.Update', venueId: idv, categories: catsObj }]]);
                    cmdPayloads.push(['Commands.apply:SetVenueCategories(["KEY"])', [{ type: 'SetVenueCategories', venueId: idv, categories: catsStr }]]);
                    // otros alias que he visto
                    cmdPayloads.push(['Commands.apply:Venues.SetCategories([{key}])', [{ type: 'Venues.SetCategories', venueId: idv, categories: catsObj }]]);
                    cmdPayloads.push(['Commands.apply:EditVenueCategories', [{ type: 'EditVenueCategories', venueId: idv, categories: catsStr }]]);
                }
                for (const [label, payload] of cmdPayloads) {
                    const r = await tryMethod(label, () => sdk.Commands.apply(payload));
                    if (r.ok) return { applied: true, method: label, payload };
                }
            }
        } catch (e4) {
            console.warn('[WME PLN][AI] Commands.apply falló', e4);
        }

        // Ninguna ruta funcionó
        const err = new Error('Venue SDK inválido');
        err.sdkCaps = {
            hasSDK: !!(await plnEnsureSdkReady(1)),
            hasDataModel: !!(await plnEnsureSdkReady(1))?.DataModel,
            triedRoutes: ['venue.setCategories', 'venue.update', 'DataModel.Venues.update', 'Operations.Venues.updateCategories|setCategories', 'Commands.apply(...) variants']
        };
        console.warn('[WME PLN][AI] Ninguna ruta de SDK funcionó', err.sdkCaps);
        throw err;
    }

    //Maneja la lógica de la UI para una solicitud de IA en una fila específica.
    /** [PLN][AI] Aplicar NOMBRE al Venue usando SOLO el SDK, con fallback UI */
    async function plnApplyNameViaSdk(row, placeId, newName) {
        const name = plnFixDanglingHyphen(String(newName || '').trim());
        if (!name) return { applied: false, reason: 'Nombre vacío' };
        const sdk = await plnEnsureSdkReady(6000);
        if (!sdk) throw new Error('SDK no disponible');

        try {
            const venue = await sdk?.DataModel?.Venues?.getById?.({ venueId: String(placeId) });
            if (!venue) throw new Error('Venue no encontrado por SDK');
            try { if (typeof venue.refresh === 'function') await venue.refresh(); } catch (_) { }
            // Intentos comunes
            if (typeof venue.setPrimaryName === 'function') { await venue.setPrimaryName(name); return { applied: true, method: 'venue.setPrimaryName', value: name }; }
            if (typeof venue.setName === 'function') { await venue.setName(name); return { applied: true, method: 'venue.setName', value: name }; }
            if (typeof venue.update === 'function') { await venue.update({ name }); return { applied: true, method: 'venue.update', value: name }; }
        } catch (e) {
            console.warn('[WME PLN][AI] No se pudo aplicar nombre mediante SDK directo, fallback UI', e);
        }

        // Fallback: dejar el nombre en el textarea de sugerencia para que el flujo normal lo aplique
        try {
            const ta = row && row.querySelector('.replacement-input');
            if (ta) { ta.value = name; return { applied: true, method: 'ui.textarea', value: name }; }
        } catch (_) { }

        // Fallback final: intentar Commands.apply con Venue.Update
        if (sdk?.Commands?.apply) {
            const idv = Number.isNaN(Number(placeId)) ? String(placeId) : Number(placeId);
            try {
                await sdk.Commands.apply([{ type: 'Venue.Update', venueId: idv, name }]);
                return { applied: true, method: 'Commands.apply:Venue.Update', value: name };
            } catch (_) { }
        }

        return { applied: false, reason: 'No se pudo aplicar el nombre' };
    }

    // === Limpieza de guiones colgantes/desalineados (Unicode-safe) ===
    function plnFixDanglingHyphens(s) {
        let out = String(s || '');

        // 1) Guión al final → eliminar (salvo casos bloqueados)
        const trailingPattern = /\s*-\s*$/u;
        if (trailingPattern.test(out) && !plnShouldPreserveTrailingHyphen(out)) {
            out = out.replace(trailingPattern, '');
        }

        // 2) Normalizar espacios en guiones válidos
        out = out.replace(/\s*-\s*/gu, ' - ');

        // 3) Compactar espacios y recortar
        out = out.replace(/\s+/g, ' ').trim();

        return out;
    }
    // === Swap Engine: move configured tokens before/after ===
    // Respeta el arreglo global window.swapWords guardado en localStorage.
    // Cada item puede definir la palabra en `word` | `text` | `token`
    // y la dirección en `position` | `where` | `dir` | `direction` con valores
    // "before" | "after" o "antes" | "despues/después".
    function applySwapRules(originalName) {
        try {
            const DBG = false; // Cambia esto de "!!(window.__PLN_SWAP_DEBUG_ON || localStorage...)" a simplemente false
            let name = String(originalName || '');
            const swaps = (typeof plnCollectSwapRules === 'function')
                ? plnCollectSwapRules()
                : (Array.isArray(window.swapWords) ? window.swapWords : []);

            //if (DBG) console.group('[PLN Swap] applySwapRules', { originalName, swapsCount: Array.isArray(swaps)?swaps.length:0 });
            if (!swaps.length) { if (DBG) { console.warn('[PLN Swap] skip: no swaps configured'); console.groupEnd?.(); } return name; }

            const normalizeSpace = s => s.replace(/\s+/g, ' ').replace(/\s*-\s*/g, ' - ').trim();

            for (const raw of swaps) {
                if (!raw) { if (DBG) console.warn('skip: null item'); continue; }
                const token = String((raw.word || raw.text || raw.token || '').trim());
                if (!token) { if (DBG) console.warn('skip: empty token', raw); continue; }

                let where = String((raw.position || raw.where || raw.dir || raw.direction || '')).toLowerCase();
                if (where === 'antes' || where === 'before' || where === 'pre') where = 'before';
                if (where === 'despues' || where === 'después' || where === 'after' || where === 'post') where = 'after';
                if (where !== 'before' && where !== 'after') { if (DBG) console.warn(`skip [${token}]: invalid position`, raw); continue; }

                const esc = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '\\s+');
                const SEP = '[\\s,.;:()\\[\\]\\-–—\/]';

                // RegEx para buscar (sin 'g') y para reemplazar (con 'g')
                const reFind = new RegExp(`(?:^|${SEP})${esc}(?=$|${SEP})`, 'iu');
                const reAnywhere = new RegExp(`(?:^|${SEP})${esc}(?=$|${SEP})`, 'giu');
                const reStart = new RegExp(`^\\s*${esc}(?=$|${SEP})`, 'iu');
                const reEnd = new RegExp(`(?:^|${SEP})${esc}\\s*$`, 'iu');

                if (DBG) console.groupCollapsed(`[${token}] → ${where}`);
                if (!reFind.test(name)) {
                    if (DBG) { console.info('no-op: token not present in name', { name, token }); console.groupEnd?.(); }
                    continue;
                }

                if ((where === 'before' && reStart.test(name)) || (where === 'after' && reEnd.test(name))) {
                    if (DBG) { console.info('no-op: already at target edge', { name }); console.groupEnd?.(); }
                    name = normalizeSpace(name);
                    continue;
                }

                const before = name;
                // Eliminar apariciones previas del token como palabra independiente
                name = name.replace(reAnywhere, ' ').replace(/\s{2,}/g, ' ').trim();
                // Colocar en el borde solicitado y normalizar espacios
                name = where === 'before' ? `${token} ${name}`.trim() : `${name} ${token}`.trim();
                name = normalizeSpace(name);
                // if (DBG) console.log('moved', { before, after: name });
                if (DBG) console.groupEnd?.();
            }

            if (DBG) { console.log('result =>', name); console.groupEnd?.(); }
            // 👉 Limpieza final de guiones colgantes
            name = plnFixDanglingHyphens(name);
            return name;
        }
        catch (e) {
            //if (window.__PLN_SWAP_DEBUG_ON) console.error('[PLN Swap] error', e);
            return originalName;
        }
    }

    // --- Post-swap capitalization helpers ---
    function plnCapitalizeStart(str) {
        try { return String(str || '').replace(/^\s*([a-záéíóúñ])/iu, (m, c) => m.replace(c, c.toUpperCase())); } catch { return str; }
    }

    // Capitaliza la primera letra después de " - "
    function plnCapitalizeAfterHyphen(str) {
        try {
            // Capitaliza PALABRAS COMPLETAS después de guion con espacios: " - la" → " - La"
            // Esto sobrescribe STOP words como "la", "el", "de", etc.
            let result = String(str || '').replace(/(\s-\s*)([a-záéíóúñ][\wáéíóúñ]*)/giu, (m, sep, word) => {
                return sep + word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
            });
            // Capitaliza después de guion sin espacio antes: "word-a" → "word-A"
            result = result.replace(/([^\s])-([a-záéíóúñ][\wáéíóúñ]*)/giu, (m, before, word) => {
                return before + '-' + word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
            });
            return result;
        } catch (_) { return String(str || ''); }
    }

    // Title-case en español: stopwords en minúscula salvo si van al inicio; respeta siglas
    // REEMPLAZA o AÑADE esta función en tu script

    function plnTitleCaseEs(str) {
        try {
            const STOP = new Set(['de', 'la', 'las', 'el', 'los', 'y', 'e', 'o', 'u', 'un', 'una', 'unos', 'unas', 'a', 'en', 'con', 'tras', 'por', 'al', 'lo']);
            const isAllCaps = w => w.length > 1 && w === w.toUpperCase();
            const cap = w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
            let i = 0;

            return String(str || '').replace(/([\p{L}\p{M}][\p{L}\p{M}\.'’]*)/gu, (m) => {
                const w = m, lw = w.toLowerCase(), atStart = (i === 0);
                i += w.length;

                const excl = (typeof isExcludedWord === 'function') ? isExcludedWord(w) : null;
                if (excl) return excl;
                if (isAllCaps(w)) return w;

                if (STOP.has(lw) && !atStart) {
                    // ✅ **LA MEJORA ESTÁ AQUÍ** ✅
                    // Si la palabra original ya estaba en mayúscula (ej. "La"),
                    // la respetamos y no la convertimos a minúscula.
                    if (w.charAt(0) === w.charAt(0).toUpperCase()) {
                        return w;
                    }
                    return lw; // De lo contrario, sí la convertimos a minúscula.
                }
                return cap(w);
            });
        }
        catch {
            return str;
        }
    }//plnTitleCaseEs

    function plnPostSwapCap(str) {
        let out = String(str || '');
        out = plnTitleCaseEs(out);
        out = plnCapitalizeStart(out);
        out = plnCapitalizeAfterHyphen(out); // <-- asegura mayúscula tras " - "
        return out.trim();
    }

    // === Ocultar filas ya normalizadas / sin acción requerida ===
    // ✅ REEMPLAZA EL BLOQUE ANTERIOR CON ESTE ✅
    function plnPruneNormalizedRowsManager() {
        try {
            if (window.__plnPruneRowsActive) return;
            window.__plnPruneRowsActive = true;

            const HIDE_CLASS = 'pln-hidden-normalized';
            if (!document.getElementById('pln-hide-style')) {
                const st = document.createElement('style');
                st.id = 'pln-hide-style';
                // Oculta las filas solo si el body tiene la clase 'pln-hide-normalized-rows'
                st.textContent = `
                body.pln-hide-normalized-rows tr.${HIDE_CLASS} {
                    display: none !important;
                }
            `;
                //st.textContent = `tr.${HIDE_CLASS}{display:none !important;}`;
                document.head.appendChild(st);
            }

            function expectedOf(s) {
                try {
                    const orig = (typeof window.__plnNormalizeOriginal === 'function') ?
                        window.__plnNormalizeOriginal :
                        (typeof window.normalizePlaceName === 'function' ? window.normalizePlaceName : null);

                    let out = String(s || '');
                    if (orig) out = orig(out);
                    out = plnPostSwapCap(out);
                    out = plnApplyExclusions(out);
                    out = applySwapRules(out);
                    if (orig) out = orig(out);
                    out = plnPostSwapCap(out);
                    out = plnApplyExclusions(out);
                    return out.trim();
                } catch (_) {
                    return String(s || '').trim();
                }
            }

            function processRow(tr) {
                if (!tr || tr.nodeType !== 1) return;
                const tas = tr.querySelectorAll('textarea');
                if (!tas || tas.length === 0) return;

                const current = (tas[0].value || '').trim();
                const suggested = (tas.length > 1 ? tas[1].value : '').trim();
                if (!current) return;

                const expected = expectedOf(current);
                const actionDisabled = Array
                    .from(tr.querySelectorAll('button[disabled], input[disabled], .disabled, [aria-disabled="true"]'))
                    .length > 0;
                const noChange = (suggested && suggested === current) || (current === expected) || actionDisabled;

                if (noChange) tr.classList.add(HIDE_CLASS);
                else tr.classList.remove(HIDE_CLASS);
            }

            function processAll() {
                document.querySelectorAll('tr').forEach(processRow);
            }

            // Exportamos el observador y la función para usarlos externamente
            let debounceTimer;
            window.plnPruneObserver = new MutationObserver(() => {
                clearTimeout(debounceTimer);
                debounceTimer = setTimeout(processAll, 250); // Mantenemos el debounce
            });

            window.plnPruneProcessAll = processAll;

            // YA NO SE OBSERVA document.body AQUÍ

            setTimeout(processAll, 300);
            let ticks = 0;
            const iv = setInterval(() => {
                processAll();
                if (++ticks > 10) clearInterval(iv);
            }, 400);

            window.__plnHideNormalizedRows = processAll;
        } catch (_) { /* noop */ }
    }

    // Llamamos a la función para que prepare las herramientas
    plnPruneNormalizedRowsManager();

    // Exponer para pruebas rápidas en consola
    //  window.__pln_applySwapRulesTest = applySwapRules;
    (function plnAutoSwapSuggestionInputs() {
        try {
            document.addEventListener('input', (ev) => {
                const t = ev.target;
                if (t && t.matches && t.matches('textarea.replacement-input')) {
                    t.dataset.userEdited = '1'; // no pisar ediciones manuales
                }
            }, true);

            function applyOnceTo(el) {
                // DESHABILITADO: Esto causaba normalización doble
                // Los textareas ya tienen los valores correctos desde processPlaceName en renderRow
                // Llamar normalizePlaceName de nuevo aquí causa que los reemplazos se apliquen dos veces
                return;

                /* CÓDIGO ORIGINAL - DESHABILITADO
                if (!el || el.dataset.userEdited === '1') return;
                const tr = el.closest('tr[data-place-id]') || el.closest('tr');
                let currentName = '';
                if (tr) {
                    const tas = tr.querySelectorAll('textarea');
                    if (tas && tas.length) currentName = tas[0].value || '';
                }
                // Usa el normalizador oficial ya parcheado con swap
                const norm = (typeof normalizePlaceName === 'function')
                    ? normalizePlaceName
                    : (typeof window.__plnNormalizeOriginal === 'function' ? window.__plnNormalizeOriginal : null);
                const expected = norm ? norm(currentName) : currentName;
                if (expected && el.value !== expected) el.value = expected;
                */
            }

            let debounceTimer;
            const debouncedHandler = () => {
                // Esta función se asegura de que solo se procesen los textareas una vez.
                document.querySelectorAll('textarea.replacement-input:not([data-pln-processed])').forEach(el => {
                    applyOnceTo(el);
                    el.setAttribute('data-pln-processed', 'true'); // Marcar como procesado
                });
            };

            const obs = new MutationObserver(muts => {
                for (const m of muts) {
                    if (m.addedNodes.length > 0) {
                        clearTimeout(debounceTimer);
                        // Agrupa todas las llamadas en una sola ejecución 100ms después del último cambio.
                        debounceTimer = setTimeout(debouncedHandler, 100);
                        break; // Un solo nodo añadido es suficiente para activar el debounce.
                    }
                }
            });
            obs.observe(document.body, { childList: true, subtree: true });

            window.plnApplySwapToSuggestionInputs = function () {
                document.querySelectorAll('textarea.replacement-input').forEach(applyOnceTo);
            };
        } catch (_) { }
    })();
    // Hook: encadena las reglas de swap al resultado de normalizePlaceName
    (function plnPatchNormalizeForSwap() {
        try {
            if (window.__plnSwapPatched) return;
            let tries = 0;
            const iv = setInterval(() => {
                tries++;
                const fn = (typeof normalizePlaceName === 'function') ? normalizePlaceName : (typeof window !== 'undefined' && typeof window.normalizePlaceName === 'function' ? window.normalizePlaceName : null);
                if (fn) {
                    clearInterval(iv);
                    const original = fn;
                    // Exponer el normalizador original para usarlo post‑swap sin recursión
                    try { window.__plnNormalizeOriginal = original; } catch (_) { }

                    // ✅ CÓDIGO CORRECTO DE REEMPLAZO
                    const patched = function (...args) {
                        const inStr = args && args.length ? String(args[0] || '') : '';

                        // 1. Usar la normalización ORIGINAL (sin processPlaceName que ya aplicó reemplazos)
                        // Esto evita que se apliquen los reemplazos dos veces
                        const baseNormalizada = original(inStr);

                        // 2. Aplicar las reglas de 'swap' (mover palabras) al resultado ya normalizado.
                        const swapped = applySwapRules(baseNormalizada);

                        // 3. Aplicar una capitalización final y limpieza post-swap.
                        const finalCap = plnPostSwapCap(swapped);
                        const finalStr = plnApplyExclusions(finalCap); // Reponer palabras excluidas al final.

                        // (Opcional) Bloque de depuración para trazar el flujo
                        try {
                            const DBG = window.__PLN_DECISION_DEBUG_ON || localStorage.getItem('wme_pln_debug_decision') === '1';
                            if (DBG) {
                                console.group('[PLN Decision] normalizePlaceName patched (CORREGIDO)');
                                console.table({
                                    "Input": inStr,
                                    "1. After original normalize": baseNormalizada,
                                    "2. After applySwapRules": swapped,
                                    "3. Final Result": finalStr
                                });
                                console.groupEnd();
                            }
                        } catch (_) { }

                        // 4. Devolver el resultado final y unificado.
                        return finalStr.trim();
                    };  // Colgar en ambos scopes por seguridad
                    try { window.normalizePlaceName = patched; } catch (_) { }
                    try { normalizePlaceName = patched; } catch (_) { }
                    window.__plnSwapPatched = true;
                    //console.log('[WME PLN] Swap rules hooked into normalizePlaceName.');
                }
                if (tries > 60) { clearInterval(iv); }
            }, 200);
        } catch (_) { /* noop */ }
    })();
    // Variables globales para el panel flotante
    let floatingPanelElement = null;
    let dynamicCategoriesLoaded = false;
    const tempSelectedCategories = new Map(); // Mapa para placeId -> categoryKey seleccionada
    const placesForDuplicateCheckGlobal = []; // Nueva variable global para almacenar datos de lugares para verificar duplicados
    let filterUpdateRequestsOnly = false; // Variable global para el filtro de update requests
    let recommendCategoriesWrapperGlobal = null; // Referencia global al contenedor de checkboxes para crear URs dinámicamente

    const processingPanelDimensions = { width: '400px', height: '200px' };  // Panel pequeño para procesamiento
    const resultsPanelDimensions = { width: '1400px', height: '700px' };    // Panel grande para resultados
    // ✨ OPTIMIZACIÓN: Regex Precompiladas (28-nov-2024)
    const RE_HYPHEN_BETWEEN_LETTERS = /\p{L}-\p{L}/u;
    const RE_ACRONYM_PART = /^[A-ZÁÉÍÓÚÑ0-9.]+$/;
    const RE_ACRONYM_WORD = /^[A-ZÁÉÍÓÚÑ0-9.&]+$/;
    const RE_DIGIT = /\d/;
    const RE_SYMBOL = /[\&.]/;
    const RE_ROMAN = /^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/i;
    const RE_ROMAN_WRAPPED = /^([(\["'¿¡]*)([MDCLXVI]+)([)"'\]°ºª.,;:!?]*)$/i;

    const commonWords = new Set([//Palabras comunes en español que no deberían ser consideradas para normalización
        'es', 'de', 'del', 'el', 'la', 'los', 'las', 'y', 'e',
        'o', 'u', 'un', 'una', 'unos', 'unas', 'a', 'en',
        'con', 'tras', 'por', 'al', 'lo'
    ]);

    // Hacer commonWords accesible globalmente para addWordToDictionary
    window.commonWords = commonWords;
    const tabNames = [//Definir nombres de pestañas cortos antes de la generación de botones
        { label: "Gene", icon: "⚙️" },
        { label: "Espe", icon: "🏷️" },
        { label: "Dicc", icon: "📘" },
        { label: "Reemp", icon: "🔂" }
    ]; let statsPanelElement = null; // Para el panel flotante de estadísticas
    let editorStats = {}; // Para almacenar las estadísticas en memoria
    const STATS_STORAGE_KEY = 'wme_pln_editor_stats'; // Clave para localStorage
    const STATS_ENABLED_KEY = 'wme_pln_stats_enabled'; // Clave para el checkbox de visibilidad

    // Cargar estadísticas inmediatamente al iniciar el script
    loadEditorStats();

    let wmeSDK = null; // Almacena la instancia del SDK de WME.
    //Variable global para almacenar la información del usuario actual
    let currentGlobalUserInfo = { id: null, name: 'Cargando...', privilege: 'N/A' };

    // Exponer para debugging (solo en desarrollo)
    if (typeof window !== 'undefined') {
        window.plnDebugUserInfo = function() {
            return currentGlobalUserInfo;
        };
    }

    // Set para rastrear palabras añadidas localmente por el usuario
    // CORRECCIÓN: Verificar si está vacío para intentar cargar, por si se inicializó antes
    if (!window.userAddedDictionaryWords || window.userAddedDictionaryWords.size === 0) {
        if (!window.userAddedDictionaryWords) window.userAddedDictionaryWords = new Set();

        // Cargar desde localStorage si existe
        try {
            const saved = localStorage.getItem('userAddedDictionaryWords');
            if (saved) {
                const parsed = JSON.parse(saved);
                // Usar add para no perder referencia si ya existía el objeto
                parsed.forEach(w => window.userAddedDictionaryWords.add(w));
                // console.log(`[WME PLN] Cargadas ${parsed.length} palabras de usuario desde localStorage.`);
            }
        } catch (e) {
            console.error('[WME PLN] Error cargando palabras de usuario:', e);
        }
    }

    // ✨ OPTIMIZACIÓN: Mapa de palabras normalizadas a palabras con tilde (28-nov-2024)
    // Permite búsqueda O(1) para corrección de tildes en lugar de iterar sobre el diccionario
    if (!window.normalizedToDictionaryMap) window.normalizedToDictionaryMap = new Map();

    /**
     * Construye el mapa inverso de palabras normalizadas (sin tildes) -> palabras originales
     * Se debe llamar cada vez que se actualiza el diccionario.
     */
    function buildNormalizedDictionaryMap() {
        if (!window.dictionaryWords) return;

        window.normalizedToDictionaryMap.clear();
        window.dictionaryWords.forEach(word => {
            const lowerWord = word.toLowerCase();
            const normalized = removeDiacritics(lowerWord);

            // Solo nos interesa si la palabra tiene tildes y su versión normalizada es diferente
            if (lowerWord !== normalized) {
                // Guardamos la versión normalizada apuntando a la versión con tilde
                // Ej: "camion" -> "camión"
                window.normalizedToDictionaryMap.set(normalized, lowerWord);
            }
        });
        // console.log(`[WME PLN] Mapa de tildes construido. ${window.normalizedToDictionaryMap.size} entradas.`);
    }

    // ✨ OPTIMIZACIÓN: Cache de Lookups Frecuentes (28-nov-2024)
    // Evita repetir búsquedas de las mismas palabras (mejora ~20-25% en rendimiento)
    const wordLookupCache = new Map();
    const CACHE_MAX_SIZE = 500;

    /**
     * Verifica si una palabra está excluida o en el diccionario usando cache LRU
     * @param {string} word - Palabra a verificar (se convertirá a lowercase)
     * @returns {Object} { isExcluded: boolean, isInDictionary: boolean }
     */
    function isWordExcludedOrInDictionary(word) {
        const lowerWord = word.toLowerCase();

        // Intentar obtener del cache
        if (wordLookupCache.has(lowerWord)) {
            return wordLookupCache.get(lowerWord);
        }

        // Si no está en cache, hacer la búsqueda completa
        const result = {
            isExcluded: excludedWords.has(lowerWord),
            isInDictionary: window.dictionaryWords?.has(lowerWord) || false
        };

        // Guardar en cache
        wordLookupCache.set(lowerWord, result);

        // Limitar tamaño del cache (LRU simple: eliminar primera entrada)
        if (wordLookupCache.size > CACHE_MAX_SIZE) {
            const firstKey = wordLookupCache.keys().next().value;
            wordLookupCache.delete(firstKey);
        }

        return result;
    }

    /**
     * Limpia el cache de lookups (llamar cuando se modifica diccionario o palabras excluidas)
     */
    function clearWordLookupCache() {
        wordLookupCache.clear();
    }

    // Exponer clearWordLookupCache globalmente para addWordToDictionary
    window.clearWordLookupCache = clearWordLookupCache;

    // ✨ OPTIMIZACIÓN: Paginación de Listas (28-nov-2024)
    // Solo renderiza palabras visibles en lugar de miles de elementos DOM
    const WORDS_PER_PAGE = 100; // Palabras por página
    let dictionaryCurrentPage = 1;
    let excludedWordsCurrentPage = 1;

    // ✨ OPTIMIZACIÓN: Cache de Similitud (28-nov-2024)
    // Cache para almacenar resultados de Levenshtein entre pares de palabras
    const similarityCache = new Map();
    const SIMILARITY_CACHE_MAX_SIZE = 2000; // Guardar hasta 2000 comparaciones recientes

    //Novedades de cada version del script esto permitirá una pantalla la primera vez que se abra el script
    const myChangelog = {
        [VERSION]: {
            "✨ Novedades": [
                "📋 Agregados logs de diagnóstico detallados para verificar permisos de EasyShortCuts."
            ],
            "🐛 Correcciones": [                   
                "⏰ EasyShortCuts: Implementada espera inteligente (hasta 15s) para asegurar que la configuración del servidor cargue correctamente antes de verificar accesos.",
                "🔧 Solucionado problema donde usuarios con nivel permitido veían el módulo desactivado por carga rápida de configuración."
            ]
        }
    }; // myChangelog



    function checkForOverlappingHours(venueSDKObject) {
        if (!venueSDKObject || !venueSDKObject.openingHours) {
            return false; // No hay horarios que analizar.
        }

        const openingHours = venueSDKObject.openingHours;
        let hasOverlap = false;

        const timeToMinutes = (timeStr) => {
            if (typeof timeStr !== 'string' || !timeStr.includes(':')) return 0;
            const [hours, minutes] = timeStr.split(':').map(Number);
            return hours * 60 + minutes;
        };

        for (const day in openingHours.days) {
            const dayRanges = openingHours.days[day];

            if (Array.isArray(dayRanges) && dayRanges.length > 1) {
                const intervals = dayRanges.map(range => ({
                    start: timeToMinutes(range.from),
                    end: timeToMinutes(range.to)
                }));

                for (let i = 0; i < intervals.length; i++) {
                    for (let j = i + 1; j < intervals.length; j++) {
                        const interval1 = intervals[i];
                        const interval2 = intervals[j];

                        if (interval1.start < interval2.end && interval1.end > interval2.start) {
                            hasOverlap = true;
                            break;
                        }
                    }
                    if (hasOverlap) break;
                }
            }
            if (hasOverlap) break;
        }
        return hasOverlap;
    }// checkForOverlappingHours
    // Verifica si hay ediciones pendientes en el objeto del lugar
    function checkForPendingEdits(venueSDKObject) {
        // Estilo Allen: Ser defensivo. Si no hay objeto o no tiene la propiedad, no hay ediciones pendientes.
        if (!venueSDKObject || !venueSDKObject.venueUpdateRequests) {
            return false;
        }

        // La propiedad 'venueUpdateRequests' es un array. Si tiene uno o más elementos, hay ediciones pendientes.
        if (Array.isArray(venueSDKObject.venueUpdateRequests) && venueSDKObject.venueUpdateRequests.length > 0) {
            return true;
        }

        return false;
    }// checkForPendingEdits


    // --- Línea de referencia (NO la copies, es solo para ubicarte) ---
    // function applySwapRules(originalName) { ...
    // Modified GM_xmlhttpRequest wrapper that tracks requests
    function makeRequest(options) {
        const requestId = Date.now() + Math.random();

        const wrappedOptions = {
            ...options,
            onload: function (response) {
                // Remove this request from pending list
                pendingRequests = pendingRequests.filter(id => id !== requestId);
                if (typeof options.onload === 'function') {
                    options.onload(response);
                }
            },
            onerror: function (error) {
                // Remove this request from pending list
                pendingRequests = pendingRequests.filter(id => id !== requestId);
                if (typeof options.onerror === 'function') {
                    options.onerror(error);
                }
            }
        };

        // Add to pending requests
        pendingRequests.push(requestId);

        // Make the actual request
        return GM_xmlhttpRequest(wrappedOptions);
    }
    // Variables globales para el diccionario de lugares excluidos
    function loadExcludedPlacesFromStorage() {
        try {
            const savedExcludedPlaces = localStorage.getItem("excludedPlacesList");
            if (savedExcludedPlaces) {
                const parsedData = JSON.parse(savedExcludedPlaces);
                excludedPlaces = new Map(parsedData);
                window.excludedPlaces = excludedPlaces;
                console.log("[WME PLN] Lugares excluidos cargados:", excludedPlaces.size);
            } else {
                excludedPlaces = new Map();
                window.excludedPlaces = excludedPlaces;
                console.log("[WME PLN] No se encontraron lugares excluidos guardados.");
            }
        } catch (e) {
            console.error("[WME PLN] Error cargando lugares excluidos desde localStorage:", e);
            excludedPlaces = new Map();
            window.excludedPlaces = excludedPlaces;
        }
        if (typeof window.updateExcludedPlacesCountLabel === 'function') {
            window.updateExcludedPlacesCountLabel();
        }
    }//loadExcludedPlacesFromStorage
    // [PLN] Cerca eléctrica: impedir que reasignaciones borren pares del usuario
    (function () {
        try {
            // Backing stores
            let __plnRW = window.replacementWords || {};
            let __plnRS = (typeof window.replacementSources === 'object' && window.replacementSources) ? window.replacementSources : {};

            // Asegurar objeto de fuentes existente
            if (!window.replacementSources || typeof window.replacementSources !== 'object') window.replacementSources = (__plnRS = {});

            Object.defineProperty(window, 'replacementWords', {
                configurable: true,
                enumerable: true,
                get() { return __plnRW; },
                set(next) {
                    try {
                        const prevMap = __plnRW || {};
                        const prevSrc = __plnRS || {};
                        const incoming = (next && typeof next === 'object') ? { ...next } : {};

                        // Reinyectar SOLO entradas previas cuyo source != 'sheet' y que no estén en el nuevo mapa
                        for (const k in prevMap) {
                            if (prevSrc[k] !== 'sheet' && !(k in incoming)) {
                                incoming[k] = prevMap[k];
                                if (!window.replacementSources || typeof window.replacementSources !== 'object') window.replacementSources = (__plnRS = {});
                                if (window.replacementSources[k] !== 'sheet') window.replacementSources[k] = 'user';
                            }
                        }
                        __plnRW = incoming;
                    } catch (e) { __plnRW = next || {}; }
                }
            });

            Object.defineProperty(window, 'replacementSources', {
                configurable: true,
                enumerable: true,
                get() { return __plnRS; },
                set(next) { __plnRS = (next && typeof next === 'object') ? next : {}; }
            });
        } catch (_) { /* noop */ }
    })();
    // --- Funciones auxiliares para manejar fechas ---
    // Obtiene la fecha actual en formato AAAA-MM-DD
    function getCurrentDateString() {
        const now = new Date();
        const year = now.getFullYear();
        const month = String(now.getMonth() + 1).padStart(2, '0');
        const day = String(now.getDate()).padStart(2, '0');
        return `${year}-${month}-${day}`;
    }
    // Obtiene la semana actual en formato AAAA-WW (ISO 8601)
    function getCurrentISOWeekString() {
        const date = new Date();
        date.setHours(0, 0, 0, 0);
        date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
        const week1 = new Date(date.getFullYear(), 0, 4);
        const weekNumber = 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
        return `${date.getFullYear()}-${String(weekNumber).padStart(2, '0')}`;
    }
    // Obtiene el mes actual en formato AAAA-MM
    function getCurrentMonthString() {
        const now = new Date();
        const year = now.getFullYear();
        const month = String(now.getMonth() + 1).padStart(2, '0');
        return `${year}-${month}`;
    }

    // --- Funciones principales de estadísticas ---
    // Carga las estadísticas desde localStorage
    function loadEditorStats() {
        const savedStats = localStorage.getItem(STATS_STORAGE_KEY);
        if (savedStats) {
            try {
                editorStats = JSON.parse(savedStats);
                if (typeof editorStats !== 'object' || editorStats === null) {
                    editorStats = {};
                }
            }
            catch (e) {
                console.error('[WME PLN Stats] Error al parsear estadísticas desde localStorage:', e);
                editorStats = {};
            }
        }
        else {
            editorStats = {};
        }
    }
    // Guarda las estadísticas en localStorage
    function saveEditorStats() {
        try {
            localStorage.setItem(STATS_STORAGE_KEY, JSON.stringify(editorStats));
        }
        catch (e) {
            console.error('[WME PLN Stats] Error al guardar estadísticas en localStorage:', e);
        }
    }

    // Devuelve la palabra excluida original si existe en la lista (case-insensitive, ignora tildes y espacios)
    function isExcludedWord(word) {
        if (!word || !excludedWords) return null;
        // Normaliza quitando tildes y pasa a minúsculas
        const clean = w => w.trim().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
        const cleanedWord = clean(word);

        for (const excl of excludedWords) {
            if (clean(excl) === cleanedWord) {
                return excl; // Devuelve la versión guardada, con sus mayúsculas/tildes originales
            }
        }
        return null;
    }// isExcludedWord

    // Aplica las palabras excluidas devolviendo su forma exacta guardada
    function plnApplyExclusions(str) {
        try {
            const reWord = /([\p{L}\p{M}][\p{L}\p{M}\.'’]*)/gu;
            return String(str || '').replace(reWord, (m) => {
                try {
                    const excl = typeof isExcludedWord === 'function' ? isExcludedWord(m) : null;
                    return excl ? excl : m;
                } catch (_) { return m; }
            });
        } catch (_) { return String(str || ''); }
    }

    // FIN: Bloque De Funciones Para Estadísticas

    //Calcula el área de un polígono en metros cuadrados
    // Utiliza la fórmula de Shoelace (fórmula de área de Gauss
    function calculateAreaNoTurf(shape) {
        if (!shape || !shape.geometry) {
            return Infinity; // retorna un valor que no active el titilado
        }

        try {
            // verifica si la geometría es un polígono
            if (shape.geometry.type === 'Polygon') {
                // extrae las coordenadas del primer anillo del polígono
                const coordinates = shape.geometry.coordinates[0]; // primer anillo del polígono

                // usar la fórmula de Shoelace para calcular el área
                let area = 0;
                for (let i = 0; i < coordinates.length - 1; i++) {
                    area += coordinates[i][0] * coordinates[i + 1][1];
                    area -= coordinates[i][1] * coordinates[i + 1][0];
                }

                area = Math.abs(area) / 2;
                //Convierte el área a metros cuadrados
                // Aproximación: 1 grado de latitud es aproximadamente 111.32 km
                // 1 grado de longitud varía según la latitud, pero al nivel del equador es aproximadamente 111.32 km
                // Para simplificar, usamos un valor promedio
                // 1 grado de latitud = 111,319.9 metros
                // 1 grado de longitud = 111,319.9 metros (en el ecuador)
                // Entonces, el área en metros cuadrados es:
                // area * (111319.9 * 111319.9)
                // O simplemente multiplicamos por el cuadrado de la conversión de metros por grado
                // Nota: Esta es una aproximación y no es precisa para áreas grandes o cerca de los polos
                // Para áreas pequeñas, esta aproximación es suficiente
                const metersPerDegree = 111319.9; //aproximadamente 111,319.9 metros por grado
                return area * Math.pow(metersPerDegree, 2);
            }
        }
        catch (error) {
            console.warn("[WME PLN] Error calculating area:", error);
            return Infinity; // Return a value that won't trigger blinking
        }

        return Infinity; // Default return for non-polygon shapes
    }// calculateAreaMeters

    // Modifica esta función para implementar correctamente el procesamiento de titilación
    // Reemplaza completamente la función processingLoop
    function processingLoop() {
        const currentTime = Date.now();

        // Actualizar el estado de titilación cada BLINK_INTERVAL milisegundos
        if (currentTime - lastBlinkTime > BLINK_INTERVAL) {
            blinkState = !blinkState;
            lastBlinkTime = currentTime;

            // Recorrer todos los lugares en la lista de lugares para titilar
            blinkingPlaces.forEach(placeId => {
                // Seleccionar específicamente el elemento con la clase area-value-element
                const areaElement = document.querySelector(`tr[data-place-id="${placeId}"] .area-value-element`);
                if (areaElement) {
                    // Solo aplicar el estilo de titilación al elemento del área
                    areaElement.style.opacity = blinkState ? '1' : '0.3';
                }
            });
        }

        requestAnimationFrame(processingLoop);
    }// processingLoop



    // Registra una edición y actualiza los contadores
    function recordNormalizationEvent() {
        const userId = currentGlobalUserInfo.id;
        const userName = currentGlobalUserInfo.name;

        if (!userId || userId === 0 || userName === 'No detectado') {
            return;
        }

        // Obtiene las estadísticas del usuario o las inicializa si no existen
        let userStats = editorStats[userId];
        if (!userStats) {
            userStats = {
                userName: userName,
                total_count: 0,
                monthly_count: 0,
                monthly_period: "N/A",
                weekly_count: 0,
                weekly_period: "N/A",
                daily_count: 0,
                daily_period: "N/A",
                last_update: 0
            };
            editorStats[userId] = userStats;
        }

        // Obtiene los periodos de tiempo actuales
        const todayStr = getCurrentDateString();
        const weekStr = getCurrentISOWeekString();
        const monthStr = getCurrentMonthString();

        // --- Lógica de reseteo de contadores ---
        // Si la fecha guardada es diferente a la de hoy, resetea el contador diario.
        if (userStats.daily_period !== todayStr) {
            userStats.daily_count = 0;
            userStats.daily_period = todayStr;
        }

        // Si la semana guardada es diferente a la de hoy, resetea el contador semanal.
        if (userStats.weekly_period !== weekStr) {
            userStats.weekly_count = 0;
            userStats.weekly_period = weekStr;
        }

        // Si el mes guardado es diferente al de hoy, resetea el contador mensual.
        if (userStats.monthly_period !== monthStr) {
            userStats.monthly_count = 0;
            userStats.monthly_period = monthStr;
        }

        // --- Incrementar los contadores ---
        userStats.daily_count++;
        userStats.weekly_count++;
        userStats.monthly_count++;
        userStats.total_count++;
        userStats.last_update = Date.now();
        userStats.userName = userName; // Asegurarse de que el nombre esté actualizado

        // Guardar los nuevos datos y actualizar la pantalla
        saveEditorStats();
        updateStatsDisplay();
    }

    // Función modificada para filtrar palabras antes de mostrarlas en la lista de selección
    function openAddSpecialWordPopup(name, listType = "excludeWords") {
        // Dividir el nombre en palabras
        const clean = s => String(s || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '')
            .replace(/\s+/g, ' ').trim().toLowerCase();
        const exclSet = new Set(Array.from(window.excludedWords || []).map(w => clean(w)));

        let words = name.split(/\s+/)
            .filter(word => word.length > 1);

        words = words.filter(word => {
            const lowerWord = word.toLowerCase();

            // Verificar que la palabra no sea común
            if (commonWords.has(lowerWord)) return false;

            // Verificar que no esté ya en palabras excluidas (insensible a mayúsculas/minúsculas)
            if (exclSet.has(clean(word))) {
                return false; // Ya está en especiales
            }

            // Verificar que no esté en el diccionario (insensible a mayúsculas/minúsculas)
            if (window.dictionaryWords && Array.from(window.dictionaryWords).some(dictWord =>
                dictWord.toLowerCase() === lowerWord)) {
                return false;
            }

            // Si la palabra coincide con algún "texto de reemplazo" (columna B) cargado desde Google Sheet,
            // y ese reemplazo proviene de hoja (bloqueado), no mostrarla en la lista
            try {
                if (window.replacementWords) {
                    const isReplacementTargetFromSheet = Object.entries(window.replacementWords)
                        .some(([from, to]) => (to || '').toLowerCase() === lowerWord && (!window.replacementSources || window.replacementSources[from] === 'sheet'));
                    if (isReplacementTargetFromSheet) return false;
                }
            } catch (_) { /* noop */ }

            return true; // Si pasa todos los filtros, mostrar la palabra
        });

        if (words.length === 0) {
            alert("No hay palabras nuevas para agregar. Todas las palabras ya están en el diccionario o en la lista de especiales.");
            return;
        }

    }



    // Muestra los contadores en el panel flotante
    function updateStatsDisplay() {
        if (!statsPanelElement || !currentGlobalUserInfo.id) return;

        const userId = currentGlobalUserInfo.id;
        // Obtiene los datos guardados o valores por defecto si no existen
        const stats = editorStats[userId] || {
            daily_count: 0,
            weekly_count: 0,
            monthly_count: 0,
            total_count: 0
        };

        // Actualiza los elementos de la UI con los valores guardados
        const summaryText = statsPanelElement.querySelector('#stats-summary-text');
        const todayCountSpan = statsPanelElement.querySelector('#stats-count-today');
        const weekCountSpan = statsPanelElement.querySelector('#stats-count-week');
        const monthCountSpan = statsPanelElement.querySelector('#stats-count-month');
        const totalCountSpan = statsPanelElement.querySelector('#stats-count-total');

        if (summaryText) summaryText.textContent = `📊 ${stats.daily_count || 0} Places NrmliZed`;
        if (todayCountSpan) todayCountSpan.textContent = stats.daily_count || 0;
        if (weekCountSpan) weekCountSpan.textContent = stats.weekly_count || 0;
        if (monthCountSpan) monthCountSpan.textContent = stats.monthly_count || 0;
        if (totalCountSpan) totalCountSpan.textContent = stats.total_count || 0;
    }

    // Crea el panel de estadísticas flotante en la interfaz de usuario.
    function createStatsPanel() {
        if (document.getElementById('wme-pln-stats-panel')) return;

        // Contenedor principal del panel
        statsPanelElement = document.createElement('div');
        statsPanelElement.id = 'wme-pln-stats-panel';
        Object.assign(statsPanelElement.style, {
            position: 'fixed',
            bottom: '60px',
            left: '23%', // <-- Ancla el panel a 20px del borde izquierdo
            // Se elimina la propiedad 'transform' que ya no es necesaria
            backgroundColor: 'rgba(45, 45, 45, 0.9)',
            color: 'white',
            padding: '5px 12px',
            borderRadius: '15px',
            fontSize: '13px',
            fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif",
            zIndex: '10000',
            cursor: 'pointer',
            display: 'none', // Oculto inicialmente
            border: '1px solid #555',
            boxShadow: '0 2px 10px rgba(0,0,0,0.5)',
            userSelect: 'none',
            whiteSpace: 'nowrap'
        });
        // Vista de resumen (la que siempre está visible)
        const summaryView = document.createElement('div');
        summaryView.id = 'stats-summary-view';
        Object.assign(summaryView.style, {
            display: 'flex',
            alignItems: 'center',
            gap: '6px'
        });
        const summaryText = document.createElement('span');
        summaryText.id = 'stats-summary-text';
        summaryText.textContent = '📊 0 NrmliZer Stats';

        const dropdownArrow = document.createElement('span');
        dropdownArrow.id = 'stats-arrow';
        dropdownArrow.textContent = '▼';
        dropdownArrow.style.fontSize = '10px';

        summaryView.appendChild(summaryText);
        summaryView.appendChild(dropdownArrow);

        // Vista detallada (la que se expande)
        const detailView = document.createElement('div');
        detailView.id = 'stats-detail-view';
        Object.assign(detailView.style, {
            display: 'none',
            marginTop: '8px',
            paddingTop: '8px',
            borderTop: '1px solid #666'
        });

        const list = document.createElement('ul');
        Object.assign(list.style, {
            margin: '0',
            padding: '0',
            listStyle: 'none',
            textAlign: 'left'
        });

        // Crear elementos de la lista
        const items = {
            'Hoy': 'stats-count-today',
            'Esta Semana': 'stats-count-week',
            'Este Mes': 'stats-count-month',
            'Total': 'stats-count-total'
        };

        for (const [label, id] of Object.entries(items)) {
            const listItem = document.createElement('li');
            listItem.style.marginBottom = '4px';

            const countBold = document.createElement('b');
            countBold.id = id;
            countBold.textContent = '0';

            listItem.append(`${label}: `, countBold);
            list.appendChild(listItem);
        }

        detailView.appendChild(list);

        // Ensamblar el panel
        statsPanelElement.appendChild(summaryView);
        statsPanelElement.appendChild(detailView);
        document.body.appendChild(statsPanelElement);

        // Lógica para desplegar/contraer
        statsPanelElement.addEventListener('click', () => {
            const isHidden = detailView.style.display === 'none';
            detailView.style.display = isHidden ? 'block' : 'none';
            dropdownArrow.textContent = isHidden ? '▲' : '▼';
        });

        // Lógica para cerrar al hacer clic fuera
        document.addEventListener('click', (e) => {
            // Ignorar clicks en botones del disclaimer
            const btn = e.target.closest('button');
            if (btn && btn.hasAttribute('data-disclaimer-btn')) return;
            
            if (!statsPanelElement.contains(e.target)) {
                detailView.style.display = 'none';
                dropdownArrow.textContent = '▼';
            }
        }, true);

        toggleStatsPanelVisibility();
    }// createStatsPanel
    // Función para alternar la visibilidad del panel de estadísticas basado en el estado del checkbox.
    function toggleStatsPanelVisibility() {
        if (!statsPanelElement) return;
        const isEnabled = localStorage.getItem(STATS_ENABLED_KEY) === 'true';
        statsPanelElement.style.display = isEnabled ? 'block' : 'none';
    }// toggleStatsPanelVisibility
    // FIN: Bloque De Funciones Para Estadísticas

    // Función que construirá el HTML del changelog
    function getChangelogHtml(versionData) {
        let html = '';
        if (!versionData) return html;

        // Iterar dinámicamente sobre todas las categorías
        Object.keys(versionData).forEach(category => {
            const items = versionData[category];
            if (Array.isArray(items) && items.length > 0) {
                // Usar la categoría como título (incluye emojis si los tiene)
                html += `<h6 style="color: #2196F3; margin-top: 10px; margin-bottom: 5px; font-size: 14px;">${category}</h6>`;
                html += `<ul style="margin-bottom: 10px; list-style-type: disc; margin-left: 20px;">`;
                items.forEach(item => {
                    html += `<li>${item}</li>`;
                });
                html += `</ul>`;
            }
        });
        return html;
    }//getChangelogHtml

    // Función para mostrar el changelog al actualizar el script
    function showChangelogOnUpdate() {
        const LAST_SEEN_VERSION_KEY = `${SCRIPT_NAME}_last_seen_version`;
        const lastSeenVersion = localStorage.getItem(LAST_SEEN_VERSION_KEY);
        const currentScriptVersion = VERSION; // Variable global VERSION
        // Obtener la versión actual del script desde GM_info
        const versionData = myChangelog[currentScriptVersion];
        // Verificar si hay datos de versión y si la versión actual es diferente a la última vista
        if (versionData && currentScriptVersion !== lastSeenVersion) {
            const title = `${SCRIPT_NAME} v${currentScriptVersion}`;
            const bodyHtml = getChangelogHtml(versionData); // Genera el HTML del cuerpo
            // Crear el modal
            const modal = document.createElement("div");
            modal.style.position = "fixed";
            modal.style.top = "50%";
            modal.style.left = "50%";
            modal.style.transform = "translate(-50%, -50%)";
            modal.style.backgroundColor = "#fff";
            modal.style.border = "1px solid #ccc";
            modal.style.borderRadius = "8px";
            modal.style.boxShadow = "0 5px 15px rgba(0,0,0,0.3)";
            modal.style.padding = "20px";
            modal.style.fontFamily = "'Helvetica Neue', Helvetica, Arial, sans-serif";
            modal.style.zIndex = "20000"; // Por encima de casi todo
            modal.style.width = "450px";
            modal.style.maxHeight = "80vh";
            modal.style.overflowY = "auto";
            // Estilos adicionales para el modal
            const modalTitle = document.createElement("h3");
            modalTitle.textContent = title;
            modalTitle.style.marginTop = "0";
            modalTitle.style.marginBottom = "15px";
            modalTitle.style.textAlign = "center";
            modalTitle.style.color = "#333";
            // Crear el cuerpo del modal con el contenido del changelog
            const modalBody = document.createElement("div");
            modalBody.innerHTML = bodyHtml;
            // Estilos para el cuerpo del modal
            const closeButton = document.createElement("button");
            closeButton.textContent = "Entendido";
            closeButton.style.display = "block";
            closeButton.style.margin = "20px auto 0 auto";
            closeButton.style.padding = "10px 20px";
            closeButton.style.backgroundColor = "#007bff";
            closeButton.style.color = "#fff";
            closeButton.style.border = "none";
            closeButton.style.borderRadius = "5px";
            closeButton.style.cursor = "pointer";
            //
            closeButton.addEventListener("click", () => {
                modal.remove();
                localStorage.setItem(LAST_SEEN_VERSION_KEY, currentScriptVersion); // Guarda la versión
            });
            // Añadir todo al modal y al body
            modal.appendChild(modalTitle);
            modal.appendChild(modalBody);
            modal.appendChild(closeButton);
            document.body.appendChild(modal);
        }
        else if (!versionData) {//
            // Si no hay datos de versión, no se hace nada
            localStorage.setItem(LAST_SEEN_VERSION_KEY, currentScriptVersion);
        }
    }//showChangelogOnUpdate

    // Función para mostrar el disclaimer de términos de uso
    function showDisclaimerPopup(callback) {
        // Guardia global para evitar múltiples popups
        if (window.__PLN_DISCLAIMER_SHOWN__) {
            console.log('[WME PLN] Disclaimer ya mostrado, ignorando llamada duplicada');
            return;
        }
        
        const DISCLAIMER_KEY = `wme_pln_disclaimer_v${VERSION}_accepted`;
        const hasAcceptedDisclaimer = localStorage.getItem(DISCLAIMER_KEY);
        
        // Si ya aceptó o rechazó anteriormente, no mostrar
        if (hasAcceptedDisclaimer !== null) {
            if (hasAcceptedDisclaimer === 'true') {
                if (typeof callback === 'function') callback(true);
            } else {
                if (typeof callback === 'function') callback(false);
            }
            return;
        }
        
        // Marcar que el popup está siendo mostrado
        window.__PLN_DISCLAIMER_SHOWN__ = true;

        // Crear el overlay oscuro
        const overlay = document.createElement("div");
        overlay.style.position = "fixed";
        overlay.style.top = "0";
        overlay.style.left = "0";
        overlay.style.width = "100%";
        overlay.style.height = "100%";
        overlay.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
        overlay.style.zIndex = "29999";
        overlay.style.pointerEvents = "none"; // Permite que los clicks pasen al modal

        // Crear el modal
        const modal = document.createElement("div");
        modal.style.position = "fixed";
        modal.style.top = "50%";
        modal.style.left = "50%";
        modal.style.transform = "translate(-50%, -50%)";
        modal.style.backgroundColor = "#fff";
        modal.style.border = "2px solid #2196F3";
        modal.style.borderRadius = "8px";
        modal.style.boxShadow = "0 8px 24px rgba(0,0,0,0.4)";
        modal.style.padding = "25px";
        modal.style.fontFamily = "'Helvetica Neue', Helvetica, Arial, sans-serif";
        modal.style.zIndex = "30000";
        modal.style.width = "600px";
        modal.style.maxHeight = "85vh";
        modal.style.overflowY = "auto";
        modal.style.pointerEvents = "auto"; // El modal sí recibe clicks

        // Título
        const modalTitle = document.createElement("h2");
        modalTitle.innerHTML = "⚠️ Términos de Uso - WME Places Name Normalizer";
        modalTitle.style.marginTop = "0";
        modalTitle.style.marginBottom = "20px";
        modalTitle.style.textAlign = "center";
        modalTitle.style.color = "#2196F3";
        modalTitle.style.fontSize = "20px";

        // Contenido del disclaimer
        const modalBody = document.createElement("div");
        modalBody.style.lineHeight = "1.6";
        modalBody.style.fontSize = "14px";
        modalBody.style.color = "#333";
        modalBody.innerHTML = `
            <div style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #333;">
                <!-- Section: Tool Usage -->
                <div style="margin-bottom: 15px;">
                    <h4 style="color: #2196F3; border-bottom: 1px solid #e0e0e0; padding-bottom: 5px; margin: 0 0 10px 0; font-size: 15px;">
                        🔧 Buen Uso de la Herramienta
                    </h4>
                    <p style="font-size: 13px; line-height: 1.4; margin: 0;">
                        Este script es una herramienta de <strong>asistencia</strong> para la normalización de nombres y categorías. <strong>Su uso no exime al editor de su responsabilidad de revisar detalladamente cada edición.</strong>
                    </p>
                </div>

                <!-- Section: Responsibilities -->
                <div style="margin-bottom: 15px;">
                    <h4 style="color: #2196F3; border-bottom: 1px solid #e0e0e0; padding-bottom: 5px; margin: 0 0 10px 0; font-size: 15px;">
                        📝 Responsabilidades del Editor
                    </h4>
                    <ul style="font-size: 13px; line-height: 1.4; padding-left: 0; margin: 0; list-style-type: none;">
                        <li style="margin-bottom: 4px; display: flex; align-items: flex-start;">
                            <span style="margin-right: 6px;">✅</span>
                            <span><strong>Revisar cuidadosamente</strong> sugerencias antes de aplicar</span>
                        </li>
                        <li style="margin-bottom: 4px; display: flex; align-items: flex-start;">
                            <span style="margin-right: 6px;">✅</span>
                            <span><strong>Verificar</strong> que las categorías sean correctas</span>
                        </li>
                        <li style="margin-bottom: 4px; display: flex; align-items: flex-start;">
                            <span style="margin-right: 6px;">✅</span>
                            <span><strong>Evaluar</strong> ediciones individualmente (no masivas)</span>
                        </li>
                         <li style="margin-bottom: 4px; display: flex; align-items: flex-start;">
                            <span style="margin-right: 6px;">✅</span>
                            <span><strong>Garantizar</strong> la calidad de la información</span>
                        </li>
                    </ul>
                </div>

                <!-- Section: Data Usage -->
                <div style="margin-bottom: 15px;">
                    <h4 style="color: #2196F3; border-bottom: 1px solid #e0e0e0; padding-bottom: 5px; margin: 0 0 10px 0; font-size: 15px;">
                        📊 Uso de Datos
                    </h4>
                     <ul style="font-size: 13px; line-height: 1.4; padding-left: 0; margin: 0; list-style-type: none;">
                        <li style="margin-bottom: 4px; display: flex; align-items: flex-start;">
                            <span style="margin-right: 6px;">📊</span>
                            <span>Registro de <strong>usuario y nivel</strong> para estadísticas</span>
                        </li>
                        <li style="margin-bottom: 4px; display: flex; align-items: flex-start;">
                            <span style="margin-right: 6px;">📊</span>
                            <span><strong>Control de uso</strong> para prevenir abuso</span>
                        </li>
                         <li style="margin-bottom: 4px; display: flex; align-items: flex-start;">
                            <span style="margin-right: 6px;">📊</span>
                            <span>Uso exclusivo para <strong>monitoreo y mejora</strong></span>
                        </li>
                    </ul>
                </div>

                <!-- Warning Block -->
                <div style="background-color: #FFF3E0; border-left: 4px solid #FF9800; padding: 10px 12px; font-size: 13px; margin-bottom: 15px; border-radius: 0 4px 4px 0;">
                    <div style="color: #E65100; font-weight: bold; margin-bottom: 4px;">⚠️ IMPORTANTE: Ediciones Masivas</div>
                    <div style="color: #5d4037;">
                        Las ediciones masivas no supervisadas están <strong>estrictamente prohibidas</strong>. El uso indebido puede resultar en:
                        <ul style="margin: 4px 0 0 0; padding-left: 20px; color: #5d4037;">
                            <li>Pérdida de acceso al script</li>
                            <li>Reporte a la comunidad</li>
                            <li>Sanciones en la cuenta de editor</li>
                        </ul>
                    </div>
                </div>

                <div style="text-align: center; font-size: 14px; font-weight: bold; color: #333; margin-top: 15px; padding-top: 10px; border-top: 1px solid #eee;">
                    ¿Aceptas estos términos de uso y te comprometes a utilizar la herramienta de manera responsable?
                </div>
            </div>
        `;

        // Botones
        const buttonContainer = document.createElement("div");
        buttonContainer.style.display = "flex";
        buttonContainer.style.justifyContent = "center";
        buttonContainer.style.gap = "15px";
        buttonContainer.style.marginTop = "25px";

        const acceptButton = document.createElement("button");
        acceptButton.setAttribute('type', 'button');
        acceptButton.setAttribute('data-disclaimer-btn', 'true');
        acceptButton.textContent = "✅ Aceptar y Continuar";
        acceptButton.style.padding = "12px 30px";
        acceptButton.style.fontSize = "14px";
        acceptButton.style.fontWeight = "bold";
        acceptButton.style.backgroundColor = "#4CAF50";
        acceptButton.style.color = "#fff";
        acceptButton.style.border = "none";
        acceptButton.style.borderRadius = "5px";
        acceptButton.style.cursor = "pointer";
        acceptButton.style.outline = "none";
        acceptButton.style.display = "inline-flex";
        acceptButton.style.alignItems = "center";
        acceptButton.style.justifyContent = "center";
        acceptButton.style.gap = "6px";
        acceptButton.style.lineHeight = "1";
        acceptButton.style.transition = "background-color 0.3s";

        acceptButton.onmouseover = () => acceptButton.style.backgroundColor = "#45a049";
        acceptButton.onmouseout = () => acceptButton.style.backgroundColor = "#4CAF50";

        const rejectButton = document.createElement("button");
        rejectButton.setAttribute('type', 'button');
        rejectButton.setAttribute('data-disclaimer-btn', 'true');
        rejectButton.textContent = "❌ Rechazar";
        rejectButton.style.padding = "12px 30px";
        rejectButton.style.fontSize = "14px";
        rejectButton.style.backgroundColor = "#9E9E9E";
        rejectButton.style.color = "#fff";
        rejectButton.style.border = "none";
        rejectButton.style.borderRadius = "5px";
        rejectButton.style.cursor = "pointer";
        rejectButton.style.outline = "none";
        rejectButton.style.display = "inline-flex";
        rejectButton.style.alignItems = "center";
        rejectButton.style.justifyContent = "center";
        rejectButton.style.gap = "6px";
        rejectButton.style.lineHeight = "1";
        rejectButton.style.transition = "background-color 0.3s";

        rejectButton.onmouseover = () => rejectButton.style.backgroundColor = "#757575";
        rejectButton.onmouseout = () => rejectButton.style.backgroundColor = "#9E9E9E";

        // Handlers para los botones con flag para evitar ejecución múltiple
        let disclaimerHandled = false;
        
        const handleAccept = (e) => {
            if (disclaimerHandled) return;
            disclaimerHandled = true;
            e.stopImmediatePropagation();
            e.stopPropagation();
            e.preventDefault();
            localStorage.setItem(DISCLAIMER_KEY, 'true');
            sendDisclaimerResponse(true);
            try { overlay.remove(); } catch (_) {}
            try { modal.remove(); } catch (_) {}
            plnToast('✅ ¡Gracias por aceptar los términos de uso! El script se cargará ahora...', 3000);
            if (typeof callback === 'function') callback(true);
        };
        
        const handleReject = (e) => {
            if (disclaimerHandled) return;
            disclaimerHandled = true;
            e.stopImmediatePropagation();
            e.stopPropagation();
            e.preventDefault();
            localStorage.setItem(DISCLAIMER_KEY, 'false');
            sendDisclaimerResponse(false);
            try { overlay.remove(); } catch (_) {}
            try { modal.remove(); } catch (_) {}
            plnToast('❌ El script no se ha habilitado porque no aceptaste los términos y condiciones.', 5000);
            if (typeof callback === 'function') callback(false);
        };
        
        // Solo usar click (el mouseup causaba ejecuciones duplicadas)
        acceptButton.addEventListener('click', handleAccept, true);
        rejectButton.addEventListener('click', handleReject, true);

        // Ensamblar el modal
        buttonContainer.appendChild(acceptButton);
        buttonContainer.appendChild(rejectButton);
        modal.appendChild(modalTitle);
        modal.appendChild(modalBody);
        modal.appendChild(buttonContainer);

        // Añadir al DOM
        document.body.appendChild(overlay);
        document.body.appendChild(modal);
    }//showDisclaimerPopup

    // Función para enviar la respuesta del disclaimer al servidor
    function sendDisclaimerResponse(accepted) {
        if (!currentGlobalUserInfo || !currentGlobalUserInfo.id) {
            console.warn('[WME PLN] No se pudo enviar respuesta del disclaimer: usuario no detectado');
            return;
        }

        const payload = {
            userId: currentGlobalUserInfo.id,
            userName: currentGlobalUserInfo.name,
            accepted: accepted,
            date: new Date().toISOString().split('T')[0]
        };

        makeRequest({
            method: 'POST',
            url: PLN_ACCESS_CONTROL.endpoint + '?action=recordDisclaimer',
            data: JSON.stringify(payload),
            headers: { 'Content-Type': 'text/plain;charset=utf-8' },  // Evita CORS preflight
            timeout: PLN_ACCESS_CONTROL.timeoutMs,
            onload: function (response) {
                try {
                    const result = JSON.parse(response.responseText);
                    if (result.success) {
                        console.log(`[WME PLN] Respuesta del disclaimer registrada: ${accepted ? 'Aceptado' : 'Rechazado'}`);
                    } else {
                        console.error('[WME PLN] Error al registrar respuesta del disclaimer:', result.error);
                    }
                } catch (e) {
                    console.error('[WME PLN] Error al procesar respuesta del disclaimer:', e);
                }
            },
            onerror: function (error) {
                console.error('[WME PLN] Error de red al enviar respuesta del disclaimer:', error);
            }
        });
    }//sendDisclaimerResponse

    //Permite inicializar el SDK de WME
    function tryInitializeSDK(finalCallback) {
        let attempts = 0;
        const maxAttempts = 20; // Reduced from 60 to 20
        const intervalTime = 500;
        let sdkAttemptInterval = null;

        // Function to clear interval safely
        function clearSDKInterval() {
            if (sdkAttemptInterval) {
                clearInterval(sdkAttemptInterval);
                sdkAttemptInterval = null;
            }
        }

        // Function to call callback safely
        function safeCallback() {
            clearSDKInterval();
            try {
                if (typeof finalCallback === 'function') {
                    finalCallback();
                }
            } catch (e) {
                console.error("[WME PLN] Error in SDK callback:", e);
            }
        }

        // Function to attempt SDK initialization
        function attempt() {
            // Prefer unsafeWindow.getWmeSdk in TM sandbox. Fallback to page getWmeSdk.
            const getWmeSdkFn = (typeof unsafeWindow !== 'undefined' && typeof unsafeWindow.getWmeSdk === 'function')
                ? unsafeWindow.getWmeSdk
                : (typeof getWmeSdk === 'function' ? getWmeSdk : null);

            if (getWmeSdkFn) {
                clearSDKInterval();
                try {
                    wmeSDK = getWmeSdkFn({
                        scriptId: 'WMEPlacesNameInspector',
                        scriptName: SCRIPT_NAME,
                    });
                    //console.log("[WME PLN] SDK initialized successfully");

                    // === Export stable globals for console and other scripts ===
                    try {
                        window.WME_PLN_SDK = wmeSDK;
                        if (typeof unsafeWindow !== 'undefined') unsafeWindow.WME_PLN_SDK = wmeSDK;
                        window.WME_PLN_getSDK = function () { return window.WME_PLN_SDK; };
                        window.WME_PLN_reinitSDK = function () { tryInitializeSDK(() => { }); };
                        //console.log("[WME PLN] SDK exported: window.WME_PLN_SDK, WME_PLN_getSDK(), WME_PLN_reinitSDK()");
                    } catch (e) {
                        console.warn("[WME PLN] Could not export SDK globals:", e);
                    }
                } catch (e) {
                    console.error("[WME PLN] Error initializing SDK:", e);
                    wmeSDK = null;
                }
                safeCallback();
                return;
            }

            attempts++;
            if (attempts >= maxAttempts) {
                console.warn(`[WME PLN] Could not find getWmeSdk() after ${maxAttempts} attempts.`);
                wmeSDK = null;
                safeCallback();
            }
        }


        sdkAttemptInterval = setInterval(attempt, intervalTime);
        attempt();


        if (!wmeSDK && typeof window.WME_PLN_SDK !== 'undefined' && window.WME_PLN_SDK) {
            try {
                wmeSDK = window.WME_PLN_SDK;
                window.WME_PLN_getSDK = function () { return window.WME_PLN_SDK; };
                window.WME_PLN_reinitSDK = function () { tryInitializeSDK(() => { }); };
                //console.log("[WME PLN] Reused existing window.WME_PLN_SDK");
                safeCallback();
            } catch (e) { /* noop */ }
        }

        // Safety timeout to ensure callback is called
        setTimeout(() => {
            if (sdkAttemptInterval) {
                console.warn("[WME PLN] Safety timeout for SDK initialization");
                safeCallback();
            }
        }, maxAttempts * intervalTime + 1000);
    }//tryInitializeSDK

    // Función para obtener la ciudad de un lugar usando WME API
    function getPlaceCity(venue) {
        try {
            // Intentar obtener la ciudad del venue
            if (venue && venue.getAddress) {
                const address = venue.getAddress();
                if (address && address.city && address.city.name) {
                    plnAddWorkCity(address.city.name);
                    return address.city.name;
                }
            }

            // Intentar obtener la ciudad desde el SDK
            if (wmeSDK && wmeSDK.DataModel && wmeSDK.DataModel.Venues) {
                const venueId = venue.getID();
                const venueSDK = wmeSDK.DataModel.Venues.getById({ venueId });
                if (venueSDK && venueSDK.address && venueSDK.address.city && venueSDK.address.city.name) {
                    plnAddWorkCity(venueSDK.address.city.name);
                    return venueSDK.address.city.name;
                }
            }

            return "Sin Ciudad";
        }
        catch (e) {
            console.warn("[WME PLN] Error al obtener ciudad del lugar:", e);
            return "Sin Ciudad";
        }
    }// getPlaceCity
    // Función segura para obtener un lugar por ID usando el SDK
    function getVenueByIdSafe(placeId) {
        try {
            const id = (typeof placeId === 'string' || typeof placeId === 'number') ? String(placeId) : null;
            if (!id) return null;
            if (wmeSDK && wmeSDK.DataModel && wmeSDK.DataModel.Venues && typeof wmeSDK.DataModel.Venues.getById === 'function') {
                return wmeSDK.DataModel.Venues.getById({ venueId: id }) || null;
            }
        } catch (_) { }
        return null;
    }// getVenueByIdSafe
    //-----------------------------------------------------------------------------------------------------------
    // 1) Con el WME SDK
    // Funciones de obtención de usuario
    async function getCurrentEditorViaSdk() {
        if (!wmeSDK) {
            return null;
        }
        if (!wmeSDK.DataModel || !wmeSDK.DataModel.User || typeof wmeSDK.DataModel.User.getCurrentUser !== 'function') {
            return null;
        }
        try {
            const user = await wmeSDK.DataModel.User.getCurrentUser();
            console.log('[WME PLN][DEBUG] SDK: user obtenido:', user);
            if (user && user.id && user.name) {
                const result = { id: user.id, name: user.name, privilege: user.rank || user.privilege };
                console.log('[WME PLN][DEBUG] SDK: retornando:', result);
                return result;
            } else {
                console.warn('[WME PLN][DEBUG] SDK: getCurrentUser() devolvió datos incompletos o null:', user);
                return null;
            }
        } catch (e) {
            console.error('[WME PLN][DEBUG] SDK ERROR al obtener usuario:', e);
            return null;
        }
    }// getCurrentEditorViaSdk

    // 2) Con WazeWrap
    function getCurrentEditorViaWazeWrap() {
        if (typeof WazeWrap === 'undefined') {
            return null;
        }
        if (!WazeWrap.Login || typeof WazeWrap.Login.getLoggedInUser !== 'function') {
            return null;
        }
        const wrapUser = WazeWrap.Login.getLoggedInUser();
        console.log('[WME PLN][DEBUG] WazeWrap: wrapUser obtenido:', wrapUser);
        if (wrapUser && wrapUser.userId && wrapUser.username) {
            const result = { id: wrapUser.userId, name: wrapUser.username, privilege: wrapUser.rank || wrapUser.privilege };
            console.log('[WME PLN][DEBUG] WazeWrap: retornando:', result);
            return result;
        }
        else {
            console.warn('[WME PLN][DEBUG] WazeWrap: datos incompletos');
            return null;
        }
    }// getCurrentEditorViaWazeWrap

    // 3) Fallback a la API nativa de WME
    function getCurrentEditorViaWmeInternal() {
        if (typeof W === 'undefined' || !W.loginManager) {
            return null;
        }

        // Check W.loginManager.user.attributes first (most reliable)
        if (W.loginManager.user && W.loginManager.user.attributes) {
            const attrs = W.loginManager.user.attributes;
            if (attrs.id) {
                const result = {
                    id: attrs.id,
                    name: attrs.userName || W.loginManager.userName || 'Unknown',
                    privilege: attrs.rank != null ? attrs.rank : (W.loginManager.userPrivilege || 0)
                };
                // console.log('[WME PLN][DEBUG] WME Internal: Found via user.attributes:', result);
                return result;
            }
        }

        if (W.loginManager.userId && W.loginManager.userName) {
            // Priorizar rank sobre userPrivilege
            const rank = (W.loginManager.user && W.loginManager.user.attributes && W.loginManager.user.attributes.rank) || W.loginManager.userPrivilege;
            const result = { id: W.loginManager.userId, name: W.loginManager.userName, privilege: rank };
            console.log('[WME PLN][DEBUG] WME Internal: Found via loginManager props:', result);
            return result;
        }
        else {
            console.warn('[WME PLN][DEBUG] WME Internal: W.loginManager sin userId/userName');
            return null;
        }
    }// getCurrentEditorViaWmeInternal

    // Función para obtener el nivel (rank) del editor actual
    function getEditorRank() {
        try {
            // DEBUG: Ver qué hay en W.loginManager.user
            if (typeof W !== 'undefined' && W.loginManager?.user) {
                // console.log('[WME PLN] 🔍 DEBUG - W.loginManager.user.attributes:', W.loginManager.user.attributes);
                if (W.loginManager.user.attributes) {
                    // console.log('[WME PLN] 🔍 DEBUG - Propiedades de attributes:', Object.keys(W.loginManager.user.attributes));
                    // console.log('[WME PLN] 🔍 DEBUG - attributes.rank:', W.loginManager.user.attributes.rank);
                    // console.log('[WME PLN] 🔍 DEBUG - attributes.userRank:', W.loginManager.user.attributes.userRank);
                }
            }

            // Intento 1: Desde W.loginManager.user.attributes.rank (NUEVO)
            if (typeof W !== 'undefined' && W.loginManager?.user?.attributes?.rank != null) {
                // console.log('[WME PLN] ✅ Rank obtenido desde W.loginManager.user.attributes.rank:', W.loginManager.user.attributes.rank);
                return Number(W.loginManager.user.attributes.rank);
            }

            // Intento 2: Desde el SDK usando State.getUser()
            if (typeof wmeSDK !== 'undefined' && wmeSDK?.State?.getUser) {
                const userSDK = wmeSDK.State.getUser();
                if (userSDK?.rank != null) {
                    console.log('[WME PLN] ✅ Rank obtenido desde wmeSDK.State.getUser():', userSDK.rank);
                    return Number(userSDK.rank);
                }
            }

            // Intento 3: Desde el SDK usando State.User.getUser()
            if (typeof wmeSDK !== 'undefined' && wmeSDK?.State?.User?.getUser) {
                const userSDK = wmeSDK.State.User.getUser();
                if (userSDK?.rank != null) {
                    console.log('[WME PLN] ✅ Rank obtenido desde wmeSDK.State.User.getUser():', userSDK.rank);
                    return Number(userSDK.rank);
                }
            }

            // Intento 4: Desde W.loginManager.user.rank (directo)
            if (typeof W !== 'undefined' && W.loginManager?.user?.rank != null) {
                console.log('[WME PLN] ✅ Rank obtenido desde W.loginManager.user.rank:', W.loginManager.user.rank);
                return Number(W.loginManager.user.rank);
            }

            // Intento 5: Desde W.loginManager.getUser()
            if (typeof W !== 'undefined' && W.loginManager?.getUser) {
                const user = W.loginManager.getUser();
                if (user?.rank != null) {
                    console.log('[WME PLN] ✅ Rank obtenido desde W.loginManager.getUser().rank:', user.rank);
                    return Number(user.rank);
                }
                if (user?.attributes?.rank != null) {
                    console.log('[WME PLN] ✅ Rank obtenido desde W.loginManager.getUser().attributes.rank:', user.attributes.rank);
                    return Number(user.attributes.rank);
                }
            }

            // Intento 6: Desde W.model.users.getObjectById (user ID actual)
            if (typeof W !== 'undefined' && W.model?.users?.getObjectById && W.loginManager?.user?.attributes?.id) {
                const userId = W.loginManager.user.attributes.id;
                const userObj = W.model.users.getObjectById(userId);
                if (userObj?.attributes?.rank != null) {
                    console.log('[WME PLN] ✅ Rank obtenido desde W.model.users.getObjectById().attributes.rank:', userObj.attributes.rank);
                    return Number(userObj.attributes.rank);
                }
            }

            // Intento 7: Desde currentGlobalUserInfo si ya lo tenemos
            if (currentGlobalUserInfo?.rank != null) {
                console.log('[WME PLN] ✅ Rank obtenido desde currentGlobalUserInfo:', currentGlobalUserInfo.rank);
                return Number(currentGlobalUserInfo.rank);
            }

            console.warn('[WME PLN] ⚠️ No se pudo obtener rank del editor desde ninguna fuente disponible');
        } catch (e) {
            console.error('[WME PLN] ❌ Error obteniendo rank del editor:', e);
        }
        return 0; // Por defecto, rank 0 si no se puede obtener
    }// getEditorRank

    //-----------------------------------------------------------------------------------------------------------
    // Función para crear dinámicamente el checkbox de Update Requests (después de detectar rank)
    function createUpdateRequestsCheckbox() {
        try {
            // Usar la referencia global al contenedor
            if (!recommendCategoriesWrapperGlobal) {
                console.warn('[WME PLN] ⚠️ recommendCategoriesWrapperGlobal no está disponible aún');
                return;
            }

            // Obtener rank del usuario
            const editorRank = getEditorRank();
            // console.log('[WME PLN] 🔍 Editor Rank detectado para checkbox URs:', editorRank);

            // Verificar si el checkbox ya existe
            if (document.getElementById('chk-update-requests-only')) {
                console.log('[WME PLN] Checkbox de URs ya existe, no se creará duplicado');
                return;
            }

            // Acceso restringido: Solo editores nivel 4, 5 y 6
            if (editorRank === 4 || editorRank === 5 || editorRank === 6) {
                // console.log(`[WME PLN] ✅ Creando checkbox de URs pendientes (Rank ${editorRank} autorizado)`);

                const updateRequestsContainer = document.createElement('div');
                updateRequestsContainer.style.marginTop = '8px';

                const updateRequestsCheckboxRow = document.createElement("div");
                updateRequestsCheckboxRow.style.display = "flex";
                updateRequestsCheckboxRow.style.alignItems = "center";

                const updateRequestsCheckbox = document.createElement("input");
                updateRequestsCheckbox.type = "checkbox";
                updateRequestsCheckbox.id = "chk-update-requests-only";
                updateRequestsCheckbox.style.marginRight = "8px";

                // Restaurar estado guardado
                const savedUpdateRequestsState = localStorage.getItem("wme_pln_update_requests_only");
                updateRequestsCheckbox.checked = (savedUpdateRequestsState === "true");
                filterUpdateRequestsOnly = updateRequestsCheckbox.checked; // Sincronizar con variable global

                updateRequestsCheckboxRow.appendChild(updateRequestsCheckbox);

                const updateRequestsLabel = document.createElement("label");
                updateRequestsLabel.htmlFor = "chk-update-requests-only";
                updateRequestsLabel.style.fontSize = "16px";
                updateRequestsLabel.style.cursor = "pointer";
                updateRequestsLabel.style.fontWeight = "bold";
                updateRequestsLabel.style.color = "#d32f2f"; // Rojo para destacar
                updateRequestsLabel.innerHTML = `🔔 Solo lugares con URs pendientes`;
                updateRequestsLabel.title = `Solo disponible para editores nivel 4, 5 y 6. Filtra solo lugares con solicitudes de actualización pendientes de aprobación.`;

                updateRequestsCheckboxRow.appendChild(updateRequestsLabel);
                updateRequestsContainer.appendChild(updateRequestsCheckboxRow);
                recommendCategoriesWrapperGlobal.appendChild(updateRequestsContainer);

                // Listener para guardar estado y actualizar variable global
                updateRequestsCheckbox.addEventListener("change", () => {
                    filterUpdateRequestsOnly = updateRequestsCheckbox.checked;
                    localStorage.setItem("wme_pln_update_requests_only", updateRequestsCheckbox.checked ? "true" : "false");
                    console.log('[WME PLN] ✅ Update requests filter activado:', updateRequestsCheckbox.checked);
                });

                console.log('[WME PLN] ✅ Checkbox de URs creado exitosamente');

            } else {
                console.log(`[WME PLN] ❌ Checkbox de URs NO creado. Rank actual: ${editorRank} (Se requiere nivel 4, 5 o 6)`);
                // Asegurar que el filtro esté desactivado si no tiene permisos
                filterUpdateRequestsOnly = false;
            }
        } catch (error) {
            console.error('[WME PLN] ❌ Error al crear checkbox de URs:', error);
        }
    }// createUpdateRequestsCheckbox

    //-----------------------------------------------------------------------------------------------------------
    // Esperar a que la API principal de Waze esté completamente cargada
    async function waitForWazeAPI(callbackPrincipalDelScript) {
        let wAttempts = 0;
        const wMaxAttempts = 40;
        const wInterval = setInterval(async () => {
            wAttempts++;
            if (typeof W !== 'undefined' && W.map && W.loginManager && W.model && W.model.venues && W.userscripts && typeof W.userscripts.registerSidebarTab === 'function') {
                clearInterval(wInterval);
                if (!dynamicCategoriesLoaded) // solo carga las categorías por defecto si no se han cargado aún
                {
                    // INICIO OPTIMIZACIÓN: Carga en background (sin await) para no bloquear la UI
                    loadDynamicCategoriesFromSheet(FORCE_SHEET_RELOAD)
                        .then(() => {
                            dynamicCategoriesLoaded = true; // <-- Marcar como cargado
                            // console.log('[WME PLN] Categorías dinámicas cargadas en segundo plano.');
                        })
                        .catch(error => {
                            console.error("[WME PLN] No se pudieron cargar las categorías dinámicas (background):", error);
                        });
                    // FIN OPTIMIZACIÓN
                }
                // : Esperar a que tryInitializeSDK se complete
                tryInitializeSDK(() => { //
                    // === [PLN] Daily Usage Log: resolve user and log once per day ===
                    // Resolve user info and log daily usage (once per day)
                    plnResolveCurrentUser().then(() => {
                        /*  // Guard: only try to log after we have some user id
                          if (currentGlobalUserInfo && currentGlobalUserInfo.id != null) {
                            plnLogUsageOncePerDay();
                          }*/
                    });
                    // Una vez que el SDK ha intentado inicializarse (exitosamente o no),
                    // y las APIs de WazeWrap y W.loginManager deberían estar cargadas,
                    // llamamos al callback principal del script (createSidebarTab).
                    callbackPrincipalDelScript();
                    // [PLN] Snapshot SOLO-usuario: pares previos cuyo source != 'sheet'
                    const __plnPreUserOnly = {};
                    {
                        const _m = window.replacementWords || {};
                        const _s = window.replacementSources || {};
                        for (const k in _m) {
                            if (_s && _s[k] !== 'sheet') __plnPreUserOnly[k] = _m[k];
                        }
                    }
                    // [PLN] Cargar reemplazos por defecto tras crear la UI y re-render según modo
                    loadReplacementsFromSheet(true).finally(() => {
                        // [PLN] Protector post-loader: reinsertar pares del usuario que el loader haya quitado
                        try {
                            const curMap = window.replacementWords || {};
                            const curSrc = window.replacementSources || {};

                            // Reinsertar SOLO entradas del usuario que falten tras la carga de la hoja
                            for (const k in __plnPreUserOnly) {
                                if (!(k in curMap)) {
                                    curMap[k] = __plnPreUserOnly[k];
                                    if (curSrc && typeof curSrc === 'object') {
                                        curSrc[k] = (curSrc[k] && curSrc[k] !== 'sheet') ? curSrc[k] : 'user';
                                    }
                                }
                            }
                            window.replacementWords = curMap;
                            window.replacementSources = curSrc;

                            // Canoniza (reencamina TO==FROM de hoja), sin borrar locales válidos
                            if (typeof plnCanonicalizeReplacementsBySheet === 'function') plnCanonicalizeReplacementsBySheet();
                        } catch (_) { /* noop */ }

                        // Re-render según modo (evita carreras con RAF)
                        requestAnimationFrame(() => {
                            const _el = document.getElementById("replacementsListUL") || document.querySelector("#replacementsContainer ul");
                            const _sel = document.getElementById("replacementModeSelector");
                            if (_el) {
                                if (_sel && _sel.value === "swapStart" && typeof renderSwapList === "function") {
                                    renderSwapList(_el);
                                }
                                else {
                                    renderReplacementsList(_el);
                                }
                            }
                        });
                    });
                }); //
            }
            else if (wAttempts >= wMaxAttempts) // Si no se ha cargado la API de Waze después de 20 segundos
            {
                clearInterval(wInterval);
                console.error("[WME PLN] Waze API no se cargó completamente después de múltiples intentos."); //
                callbackPrincipalDelScript(); // Llama al callback de todas formas si se agotaron los intentos
            }
        }, 500);
    }//waitforWazeAPI

    //+++++++++Funciones de Georeferenciación y Distancia++++++++++++++
    // Función para calcular la distancia en metros entre dos puntos geográficos (latitud, longitud)
    function calculateDistance(lat1, lon1, lat2, lon2) {
        const earthRadiusMeters = 6371e3; // Radio de la Tierra en metros
        // Convertir latitudes y diferencias de longitudes de grados a radianes
        const lat1Rad = lat1 * Math.PI / 180;// Convertir latitud 1 a radianes
        const lat2Rad = lat2 * Math.PI / 180;// Convertir latitud 2 a radianes
        const deltaLatRad = (lat2 - lat1) * Math.PI / 180;// Convertir diferencia de latitudes a radianes
        const deltaLonRad = (lon2 - lon1) * Math.PI / 180;// Convertir diferencia de longitudes a radianes

        // Fórmula de Haversine para calcular la distancia entre dos puntos en una esfera
        // a = sin²(Δlat/2) + cos(lat1) * cos(lat2) * sin²(Δlon/2)
        const a = Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) + Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.sin(deltaLonRad / 2) * Math.sin(deltaLonRad / 2);//
        // c = 2 * atan2(√a, √(1−a))
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        // Calcular la distancia final en metros
        const distanceMeters = earthRadiusMeters * c;
        return distanceMeters;
    }//calculateDistance

    //Función para obtener coordenadas de un lugar
    // --- Reemplaza esta función completa ---

    function getPlaceCoordinates(venueOldModel, venueSDK) {
        let lat = null;
        let lon = null;
        const placeId = venueOldModel ? venueOldModel.getID() : (venueSDK ? venueSDK.id : 'N/A');

        // --- CORRECCIÓN ---
        // PRIORIDAD 1: Usar el método recomendado getOLGeometry() del modelo antiguo, es el más estable.
        if (venueOldModel && typeof venueOldModel.getOLGeometry === 'function') {
            try {
                const geometry = venueOldModel.getOLGeometry();
                if (geometry && typeof geometry.getCentroid === 'function') {
                    const centroid = geometry.getCentroid();
                    if (centroid && typeof centroid.x === 'number' && typeof centroid.y === 'number') {
                        // La geometría de OpenLayers (OL) está en proyección Mercator (EPSG:3857)
                        // Necesitamos transformarla a coordenadas geográficas WGS84 (EPSG:4326)
                        if (typeof OpenLayers !== 'undefined' && OpenLayers.Projection) {
                            const mercatorPoint = new OpenLayers.Geometry.Point(centroid.x, centroid.y);
                            const wgs84Point = mercatorPoint.transform(
                                new OpenLayers.Projection("EPSG:3857"),
                                new OpenLayers.Projection("EPSG:4326")
                            );

                            lat = wgs84Point.y;
                            lon = wgs84Point.x;

                            // Validar que las coordenadas resultantes sean válidas
                            if (typeof lat === 'number' && typeof lon === 'number' && Math.abs(lat) <= 90 && Math.abs(lon) <= 180) {
                                return { lat, lon };
                            }
                        }
                    }
                }
            } catch (e) {
                console.error(`[WME PLN] Error obteniendo coordenadas con getOLGeometry() para ID ${placeId}:`, e);
            }
        }

        // PRIORIDAD 2: Fallback al objeto del SDK si el método anterior falló.
        // Esto es menos ideal porque .geometry está obsoleto, pero sirve como respaldo.
        if (venueSDK && venueSDK.geometry && Array.isArray(venueSDK.geometry.coordinates)) {
            lon = venueSDK.geometry.coordinates[0];
            lat = venueSDK.geometry.coordinates[1];

            if (typeof lat === 'number' && typeof lon === 'number' && Math.abs(lat) <= 90 && Math.abs(lon) <= 180) {
                return { lat, lon };
            }
        }

        // Si todo falló, retornar nulls
        console.warn(`[WME PLN] No se pudieron obtener coordenadas válidas para el ID ${placeId}.`);
        return { lat: null, lon: null };
    }//getPlaceCoordinates

    // Nueva función robusta para agregar datos para verificación de duplicados
    function addPlaceDataForDuplicateCheck(venue, venueSDK, normalizedName) {
        // Usa una variable global para almacenar los datos de lugares para comparar duplicados
        if (typeof window.duplicatePlacesData === "undefined") window.duplicatePlacesData = [];
        const duplicatePlacesData = window.duplicatePlacesData;
        let geometry = null;
        if (venueSDK && venueSDK.geometry && venueSDK.geometry.coordinates) {
            const [lon, lat] = venueSDK.geometry.coordinates;
            duplicatePlacesData.push({ name: normalizedName, lat, lon, venueId: venueSDK.id });
            return;
        }
        else if (venue && typeof venue.getOLGeometry === 'function') {
            geometry = venue.getOLGeometry();
            if (geometry) {
                const lonLat = geometry.getCoordinates();
                const lon = lonLat[0];
                const lat = lonLat[1];
                duplicatePlacesData.push({ name: normalizedName, lat, lon, venueId: venue.getID() });
                return;
            }
        }
        console.warn("[WME_PLN][WARNING] No se pudo obtener geometría válida para el lugar:", venue, venueSDK);
    }

    // Función para detectar nombres duplicados cercanos y generar alertas
    function detectAndAlertDuplicateNames(allScannedPlacesData) {
        const DISTANCE_THRESHOLD_METERS = 50; // Umbral de distancia para considerar "cerca" (en metros)
        const duplicatesGroupedForAlert = new Map(); // Almacenará {normalizedName: [{places}, {places}]}

        // Paso 1: Agrupar por nombre NORMALIZADO y encontrar duplicados cercanos
        allScannedPlacesData.forEach(p1 => {
            if (p1.lat === null || p1.lon === null) return; // Saltar si no tiene coordenadas

            // Buscar otros lugares con el mismo nombre normalizado
            const nearbyMatches = allScannedPlacesData.filter(p2 => {
                if (p2.id === p1.id || p2.lat === null || p2.lon === null) {
                    return false;
                }
                
                // ✅ NUEVA LÓGICA: Si ambos no tienen nombre o tienen el mismo nombre vacío
                const p1HasName = p1.normalized && p1.normalized.trim() !== '';
                const p2HasName = p2.normalized && p2.normalized.trim() !== '';
                
                // Si tienen nombres diferentes, no son duplicados
                if (p1HasName && p2HasName && p1.normalized !== p2.normalized) {
                    return false;
                }
                
                const distance = calculateDistance(p1.lat, p1.lon, p2.lat, p2.lon);
                
                // Si ambos tienen nombre, usar la lógica normal de distancia
                if (p1HasName && p2HasName) {
                    return distance <= DISTANCE_THRESHOLD_METERS;
                }
                
                // ✅ CASO ESPECIAL: Si uno o ambos no tienen nombre
                // Solo considerar duplicados si están MUY cerca (superpuestos)
                // y son del mismo tipo/tamaño aproximado
                if (!p1HasName || !p2HasName) {
                    // Deben estar prácticamente encima (< 5 metros)
                    if (distance > 5) return false;
                    
                    // Verificar que sean del mismo tipo de geometría
                    const p1IsArea = p1.isArea === true;
                    const p2IsArea = p2.isArea === true;
                    
                    // Deben ser ambos áreas o ambos puntos
                    if (p1IsArea !== p2IsArea) return false;
                    
                    // Si son áreas, verificar que sean de tamaño similar
                    // (esto requeriría calcular área, para simplificar asumimos que sí)
                    
                    return true; // Son duplicados sospechosos (mismo lugar, sin nombre, superpuestos)
                }
                
                return false;
            });

            if (nearbyMatches.length > 0) {
                // Si encontramos duplicados cercanos para p1, agruparlos
                const groupKey = (p1.normalized && p1.normalized.trim() !== '') 
                    ? p1.normalized.toLowerCase() 
                    : `__empty_${p1.id}`; // Clave única para lugares sin nombre
                
                if (!duplicatesGroupedForAlert.has(groupKey)) {
                    duplicatesGroupedForAlert.set(groupKey, new Set());
                }
                duplicatesGroupedForAlert.get(groupKey).add(p1); // Añadir p1
                nearbyMatches.forEach(p => duplicatesGroupedForAlert.get(groupKey).add(p)); // Añadir todos sus duplicados
            }
        });
        // Paso 2: Generar el mensaje de alerta final
        if (duplicatesGroupedForAlert.size > 0) {
            let totalNearbyDuplicateGroups = 0; // Para contar la cantidad de "nombres" con duplicados
            const duplicateEntriesHtml = []; // Para almacenar las líneas HTML de la alerta formateadas

            duplicatesGroupedForAlert.forEach((placesSet, normalizedName) => {
                const uniquePlacesInGroup = Array.from(placesSet); // Convertir Set a Array
                if (uniquePlacesInGroup.length > 1) { // Solo si realmente hay más de un lugar en el grupo
                    totalNearbyDuplicateGroups++;


                    // Obtener los números de línea para cada lugar en este grupo
                    const lineNumbers = uniquePlacesInGroup.map(p => {
                        const originalPlaceInInconsistents = allScannedPlacesData.find(item => item.id === p.id);
                        return originalPlaceInInconsistents ? (allScannedPlacesData.indexOf(originalPlaceInInconsistents) + 1) : 'N/A';
                    }).filter(num => num !== 'N/A').sort((a, b) => a - b); // Asegurarse que son números y ordenarlos

                    // Marcar los lugares en `allScannedPlacesData` y guardar sus compañeros
                    uniquePlacesInGroup.forEach(p => {
                        const originalPlaceInInconsistents = allScannedPlacesData.find(item => item.id === p.id);
                        if (originalPlaceInInconsistents) {
                            originalPlaceInInconsistents.isDuplicate = true;

                            // ✨ Guardar información de duplicados compañeros (28-nov-2024)
                            originalPlaceInInconsistents.duplicatePartners = uniquePlacesInGroup
                                .filter(partner => partner.id !== p.id)
                                .map(partner => {
                                    const partnerInList = allScannedPlacesData.find(item => item.id === partner.id);
                                    const line = partnerInList ? (allScannedPlacesData.indexOf(partnerInList) + 1) : null;
                                    return {
                                        id: partner.id,
                                        originalName: partner.original,
                                        line: line
                                    };
                                });
                        }
                    });
                    // Construir la línea para el modal
                    duplicateEntriesHtml.push(`
                        <div style="margin-bottom: 5px; font-size: 15px; text-align: left;">
                            <b>${totalNearbyDuplicateGroups}.</b> Nombre: <b>${normalizedName}</b><br>
                            <span style="font-weight: bold; color: #007bff;">Registros: [${lineNumbers.join("],[")}]</span>
                        </div>
                    `);
                }
            });
            // Solo mostrar la alerta si realmente hay grupos de más de 1 duplicado cercano
            if (duplicateEntriesHtml.length > 0) {
                // Crear el modal
                const modal = document.createElement("div");
                modal.style.position = "fixed";
                modal.style.top = "50%";
                modal.style.left = "50%";
                modal.style.transform = "translate(-50%, -50%)";
                modal.style.background = "#fff";
                modal.style.border = "1px solid #aad";
                modal.style.padding = "28px 32px 20px 32px";
                modal.style.zIndex = "20000"; // Z-INDEX ALTO para asegurar que esté encima
                modal.style.boxShadow = "0 4px 24px rgba(0,0,0,0.18)";
                modal.style.fontFamily = "sans-serif";
                modal.style.borderRadius = "10px";
                modal.style.textAlign = "center";
                modal.style.minWidth = "400px";
                modal.style.maxWidth = "600px";
                modal.style.maxHeight = "80vh"; // Para scroll si hay muchos duplicados
                modal.style.overflowY = "auto"; // Para scroll si hay muchos duplicados

                // Ícono visual
                const iconElement = document.createElement("div");
                iconElement.innerHTML = "⚠️"; // Signo de advertencia
                iconElement.style.fontSize = "38px";
                iconElement.style.marginBottom = "10px";
                modal.appendChild(iconElement);

                // Mensaje principal
                const messageTitle = document.createElement("div");
                messageTitle.innerHTML = `<b>¡Atención! Se encontraron ${duplicateEntriesHtml.length} nombres duplicados.</b>`;
                messageTitle.style.fontSize = "20px";
                messageTitle.style.marginBottom = "8px";
                modal.appendChild(messageTitle);

                const messageExplanation = document.createElement("div");
                messageExplanation.textContent = `Los siguientes grupos de lugares se encuentran a menos de ${DISTANCE_THRESHOLD_METERS}m uno del otro. El algoritmo asume que son el mismo lugar, por favor revisa los registros indicados en el panel flotante:`;
                messageExplanation.style.fontSize = "15px";
                messageExplanation.style.color = "#555";
                messageExplanation.style.marginBottom = "18px";
                messageExplanation.style.textAlign = "left"; // Alinear texto explicativo a la izquierda
                modal.appendChild(messageExplanation);

                // Lista de duplicados
                const duplicatesListDiv = document.createElement("div");
                duplicatesListDiv.style.textAlign = "left"; // Alinear la lista a la izquierda
                duplicatesListDiv.style.paddingLeft = "10px"; // Pequeño padding para los números
                duplicatesListDiv.innerHTML = duplicateEntriesHtml.join('');
                modal.appendChild(duplicatesListDiv);

                // Botón OK
                const buttonWrapper = document.createElement("div");
                buttonWrapper.style.display = "flex";
                buttonWrapper.style.justifyContent = "center";
                buttonWrapper.style.gap = "18px";
                buttonWrapper.style.marginTop = "20px"; // Espacio superior

                const okBtn = document.createElement("button");
                okBtn.textContent = "OK";
                okBtn.style.padding = "7px 18px";
                okBtn.style.background = "#007bff";
                okBtn.style.color = "#fff";
                okBtn.style.border = "none";
                okBtn.style.borderRadius = "4px";
                okBtn.style.cursor = "pointer";
                okBtn.style.fontWeight = "bold";

                okBtn.addEventListener("click", () => modal.remove()); // Cierra el modal

                buttonWrapper.appendChild(okBtn);
                modal.appendChild(buttonWrapper);

                document.body.appendChild(modal); // Añadir el modal al body
            }
        }
    }
    //+++++++++FIN Funciones de Georeferenciación y Distancia++++++++++++++

    // === Compat: parche para Venue.getAddress() (evita firma antigua sin W.model) ===
    // Motivo: "Venue.getAddress() must be called with a W.model as a first argument" (definición antigua deprecada)
    // Estrategia: localizar el prototipo de Venue y envolver getAddress para que, si no recibe modelo, use W.model
    (function patchVenueGetAddressDeprecated() {
        let tries = 0;
        const maxTries = 12;
        const tick = setInterval(() => {
            tries++;
            try {
                if (typeof W === 'undefined' || !W.model || !W.model.venues) return; // esperar a W

                let sampleVenue = null;
                // 1) getObjectArray si existe
                if (W.model.venues && typeof W.model.venues.getObjectArray === 'function') {
                    const arr = W.model.venues.getObjectArray();
                    if (arr && arr.length) sampleVenue = arr[0];
                }
                // 2) repo.objects como fallback
                if (!sampleVenue && W.model.venues && W.model.venues.objects) {
                    const ids = Object.keys(W.model.venues.objects);
                    if (ids.length) sampleVenue = W.model.venues.objects[ids[0]];
                }
                if (!sampleVenue || typeof sampleVenue.getAddress !== 'function') {
                    if (tries >= maxTries) clearInterval(tick);
                    return;
                }

                const proto = Object.getPrototypeOf(sampleVenue);
                if (!proto || typeof proto.getAddress !== 'function' || proto.getAddress.__pln_patched) {
                    if (tries >= maxTries) clearInterval(tick);
                    return;
                }

                const originalGetAddress = proto.getAddress;
                function patchedGetAddress(modelArg) {
                    // Si no se pasa modelo, usar W.model para mantener compatibilidad
                    if (arguments.length === 0 && typeof W !== 'undefined' && W.model) {
                        try { return originalGetAddress.call(this, W.model); } catch (e) { /* cae abajo */ }
                    }
                    return originalGetAddress.apply(this, arguments);
                }
                patchedGetAddress.__pln_patched = true;
                proto.getAddress = patchedGetAddress;
                //console.log('[WME PLN] Compat patch aplicado: Venue.getAddress() acepta llamada sin argumento y usa W.model.');
                clearInterval(tick);
            } catch (e) {
                if (tries >= maxTries) clearInterval(tick);
            }
        }, 500);
    })();

    //**************************************************************************
    //Nombre: plnExtractAddressIds
    //Fecha modificación: 2025-08-10
    //Descripción: SDK‑only. Obtiene countryID y stateID desde sdkVenue.address,
    //             incluso cuando Street/City están vacíos.
    //**************************************************************************
    function plnExtractAddressIds(venueId, sdkVenue) {
        plnLog('extractIds:start', { venueId, hasSdkVenue: !!sdkVenue });
        const out = { countryID: null, stateID: null, streetName: '', houseNumber: '' };
        if (sdkVenue && sdkVenue.address) {
            const addr = sdkVenue.address;
            out.countryID = addr?.country?.id ?? addr?.countryID ?? addr?.countryId ?? null;
            out.stateID = addr?.state?.id ?? addr?.stateID ?? addr?.stateId ?? null;
            out.streetName = addr?.street?.name ?? addr?.streetName ?? '';
            out.houseNumber = addr?.houseNumber ?? '';
        }
        plnLog('extractIds:fromSDK', out);
        return out;
    }

    //**************************************************************************
    //Nombre: plnResolveIdsFromCity
    //Fecha modificación: 2025-08-10
    //Descripción: SDK-only. A partir de cityId intenta obtener stateID y countryID
    //             usando los repositorios del SDK (Cities → States → Countries).
    //**************************************************************************
    function plnResolveIdsFromCity(cityId) {
        const out = { countryID: null, stateID: null };
        try {
            if (!wmeSDK || !wmeSDK.DataModel) return out;
            const cityIdNum = Number(cityId);

            let city = null;
            try {
                if (wmeSDK.DataModel.Cities?.getById) {
                    city = wmeSDK.DataModel.Cities.getById({ cityId: cityIdNum }); // <-- number
                }
            } catch (_) { }
            plnLog('resolveFromCity:city', { requested: cityIdNum, found: !!city });
            if (!city) return out;

            let stateId = city.state?.id ?? city.stateID ?? city.stateId ?? city.attributes?.state?.attributes?.id ?? city.attributes?.state?.id ?? null;
            let countryId = city.country?.id ?? city.countryID ?? city.countryId ?? city.attributes?.country?.attributes?.id ?? city.attributes?.country?.id ?? null;

            if (!countryId && stateId && wmeSDK.DataModel.States?.getById) {
                try {
                    const state = wmeSDK.DataModel.States.getById({ stateId: Number(stateId) }); // <-- number
                    countryId = state?.country?.id ?? state?.countryID ?? state?.countryId ?? null;
                } catch (_) { }
            }

            if (stateId) out.stateID = Number(stateId);
            if (countryId) out.countryID = Number(countryId);
            plnLog('resolveFromCity:result', out);
        } catch (e) {
            plnErr('resolveFromCity:error', e);
        }
        return out;
    }//plnResolveIdsFromCity

    //**************************************************************************
    //Nombre: plnGetBaseVenueId
    //Descripción: Devuelve el ID base (antes del primer punto) como string
    //**************************************************************************
    function plnGetBaseVenueId(id) {
        return String(id).split('.')[0];
    }

    //**************************************************************************
    //Nombre: plnGetEmptyStreetIdForCity
    //Descripción: Obtiene el streetId "vacío" (No street) para una ciudad dada usando solo el SDK
    //             Intenta varias rutas del repositorio y hace pequeños reintentos síncronos.
    //**************************************************************************
    function plnGetEmptyStreetIdForCity(cityId) {
        const cidNum = Number(cityId);

        try {
            if (wmeSDK?.DataModel?.Streets?.getStreet) {
                const st = wmeSDK.DataModel.Streets.getStreet({ cityId: cidNum, streetName: '' }); // <-- number
                if (st && st.id != null) { plnLog('streets:emptyFound', { cityId: cidNum, streetId: Number(st.id) }); return Number(st.id); }
            }
        } catch (_) { }

        try {
            const all = (wmeSDK?.DataModel?.Streets?.getAll?.() || []);
            const found = all.find(s => Number(s?.city?.id) === cidNum && (s?.isEmpty || s?.name === '' || s?.streetName === ''));
            if (found) { plnLog('streets:emptyFound', { cityId: cidNum, streetId: Number(found.id) }); return Number(found.id); }
        } catch (_) { }

        try {
            for (let i = 0; i < 8; i++) {
                const all = (wmeSDK?.DataModel?.Streets?.getAll?.() || []);
                const found = all.find(s => Number(s?.city?.id) === cidNum && (s?.isEmpty || s?.name === '' || s?.streetName === ''));
                if (found) { plnLog('streets:emptyFound', { cityId: cidNum, streetId: Number(found.id) }); return Number(found.id); }
            }
        } catch (_) { }

        return null;
    }//plnGetEmptyStreetIdForCity

    /**************************************************************************
     * Nombre: plnGetVenueCityIdNow
     * Descripción: Lee (sincrónicamente) el cityID actual del venue vía SDK.
     **************************************************************************/
    function plnGetVenueCityIdNow(venueIdStr) {
        try {
            const v = wmeSDK?.DataModel?.Venues?.getById?.({ venueId: String(venueIdStr) });
            const cid = v?.address?.city?.id ?? v?.address?.cityID ?? v?.address?.cityId ?? null;
            return (cid != null) ? Number(cid) : null;
        } catch (_) { return null; }
    }

    /**************************************************************************
     * Nombre: plnWaitVenueCity
     * Descripción: Espera (polling corto) a que el venue muestre el cityID esperado.
     **************************************************************************/
    function plnWaitVenueCity(venueIdStr, expectedCityId, timeoutMs = 1500) {
        return new Promise(resolve => {
            const start = Date.now();
            const target = Number(expectedCityId);
            const tick = setInterval(() => {
                const cid = plnGetVenueCityIdNow(venueIdStr);
                if (cid === target) { clearInterval(tick); return resolve(true); }
                if (Date.now() - start > timeoutMs) { clearInterval(tick); return resolve(false); }
            }, 120);
        });
    }

    /**************************************************************************
     * Nombre: plnFindBridgeCityIdInState
     * Descripción: Busca una ciudad del mismo estado que tenga "No street" (vacía)
     **************************************************************************/
    function plnFindBridgeCityIdInState(stateId) {
        try {
            const all = (wmeSDK?.DataModel?.Streets?.getAll?.() || []);
            const match = all.find(s =>
                (s?.isEmpty || s?.name === '' || s?.streetName === '') &&
                Number(s?.city?.state?.id ?? s?.city?.stateID ?? s?.city?.stateId) === Number(stateId)
            );
            return match?.city?.id != null ? Number(match.city.id) : null;
        } catch (_) { return null; }
    }

    /**************************************************************************
     * Nombre: plnApplyCityOnce
     * Descripción: Aplica una ciudad UNA vez:
     *   - Si existe street vacío en esa ciudad → usa streetId
     *   - Si no, usa country/state/city + emptyStreet:true
     * Devuelve: 'streetId' o { type:'ids', ids } o null si no pudo construir args.
     **************************************************************************/
    function plnApplyCityOnce(venueIdStr, cityIdNum, houseNumber) {
        // Ruta 1: street vacío específico
        const emptyStreetId = plnGetEmptyStreetIdForCity(cityIdNum);
        if (emptyStreetId != null) {
            const args = { venueId: venueIdStr, streetId: Number(emptyStreetId) };
            if (houseNumber) args.houseNumber = houseNumber;
            plnLog('apply:updateAddress(args)', args);
            wmeSDK.DataModel.Venues.updateAddress(args);
            setTimeout(() => { try { plnTryAutoApplyAddressPanel?.(); } catch { } }, 200);
            return 'streetId';
        }

        // Ruta 2: IDs completos con emptyStreet:true
        const ids = plnResolveIdsFromCity(cityIdNum);
        plnLog('apply:fallbackIds', ids);
        if (ids.countryID && ids.stateID) {
            const args2 = {
                venueId: venueIdStr,
                countryID: Number(ids.countryID),
                stateID: Number(ids.stateID),
                cityID: Number(cityIdNum),
                emptyStreet: true
            };
            if (houseNumber) args2.houseNumber = houseNumber;
            plnLog('apply:updateAddress(args2)', args2);
            wmeSDK.DataModel.Venues.updateAddress(args2);
            setTimeout(() => { try { plnTryAutoApplyAddressPanel?.(); } catch { } }, 200);
            return { type: 'ids', ids };
        }

        return null;
    }

    // ===== INICIO: REEMPLAZAR ESTA FUNCIÓN COMPLETA =====
    async function plnApplyCityToVenue(venueId, selectedCityId, selectedCityName) {
        plnLog('apply:start', { venueId, selectedCityId, selectedCityName });
        if (!wmeSDK?.DataModel?.Venues?.updateAddress) {
            plnErr('apply:sdkNotReady');
            return;
        }

        try {
            const venueIdStr = String(venueId);
            const cityIdNum = Number(selectedCityId) || 0;

            // Intento obtener houseNumber (no bloqueante), es una buena práctica mantenerlo.
            let houseNumber = '';
            try {
                const v0 = wmeSDK.DataModel.Venues.getById?.({ venueId: venueIdStr });
                if (v0?.address?.houseNumber) houseNumber = String(v0.address.houseNumber);
            } catch (_) { /* noop */ }

            // MODIFICACIÓN CLAVE: Se elimina la lógica de espera y el "Plan B (bridge)".
            // Simplemente llamamos a la función que aplica la ciudad y confiamos en que funciona.
            const attemptKind = plnApplyCityOnce(venueIdStr, cityIdNum, houseNumber);

            if (attemptKind) {
                // Si attemptKind no es nulo, significa que se pudo construir y enviar la solicitud al SDK.
                // Asumimos el éxito aquí, ya que la espera en la UI es el punto de fallo.
                plnLog('apply:doneWithSDK: optimistic success');

                // El llamado a plnTryAutoApplyAddressPanel ya está dentro de plnApplyCityOnce,
                // por lo que se ejecutará automáticamente.
                return;
            }

            // Si plnApplyCityOnce devuelve null, significa que no pudo encontrar los IDs necesarios.
            // Solo en este caso, lanzamos el error.
            plnErr('apply:noSdkVenueOrAddress', { reason: "Could not resolve IDs for city.", cityIdNum });

        } catch (e) {
            plnErr('apply:sdkBranchError', e);
        }
    }
    // ===== FIN: REEMPLAZAR ESTA FUNCIÓN COMPLETA =====


    // DEBUG: Detectar llamadas a getElementById('') y registrar stack
    (function patchGetElementByIdDebug() {
        try {
            const _origGetById = document.getElementById.bind(document);
            document.getElementById = function (id) {
                if (id === '') {
                    console.warn('[WME PLN][DEBUG] getElementById(\"\") llamada. Stack:', new Error().stack);
                    return null;
                }
                return _origGetById(id);
            };
        } catch (_) { }
    })();

    //**************************************************************************
    //Nombre: plnTryAutoApplyAddressPanel
    //Fecha modificación: 2025-08-09
    //Descripción: Si el editor de Dirección está abierto, pulsa "Apply/Aplicar" para que la UI deje de mostrar "No address".
    //Notas: No guarda en servidor; solo confirma los campos dentro del panel de dirección.
    //**************************************************************************
    function plnTryAutoApplyAddressPanel() {
        plnLog('ui:autoApply:begin');
        try {
            // Buscar botones candidatos con texto Apply/Aplicar que estén visibles
            const btns = Array.from(document.querySelectorAll('button'))
                .filter(b => b && b.offsetParent !== null) // visibles
                .filter(b => /^(apply|aplicar)$/i.test((b.textContent || b.innerText || '').trim()));

            plnLog('ui:autoApply:candidateButtons', btns.length);
            for (const b of btns) {
                // Asegurar que pertenece al editor de dirección: debe haber inputs de calle/house en su contenedor
                const container = (function findContainer(node) {
                    let n = node;
                    for (let i = 0; i < 6 && n; i++) {
                        if (n.querySelector && (
                            n.querySelector('input[placeholder*="street" i]') ||
                            n.querySelector('input[placeholder*="calle" i]') ||
                            n.querySelector('input[placeholder*="house" i]') ||
                            n.querySelector('input[placeholder*="número" i]')
                        )) return n;
                        n = n.parentElement;
                    }
                    return null;
                })(b);

                if (!container) continue; // botón Apply no es del editor de dirección

                // Evitar doble click si el botón está deshabilitado
                if (b.disabled) continue;

                // Log texto del resumen de dirección antes y después
                const beforeSummary = (function () {
                    const el = Array.from(document.querySelectorAll('div,span')).find(x => /no (address|street|city)/i.test((x.textContent || '')));
                    return el ? el.textContent.trim() : null;
                })();
                plnLog('ui:autoApply:beforeClickSummary', beforeSummary);

                // Click seguro
                b.click();
                setTimeout(() => {
                    const afterSummary = (function () {
                        const el = Array.from(document.querySelectorAll('div,span')).find(x => /no (address|street|city)/i.test((x.textContent || '')));
                        return el ? el.textContent.trim() : null;
                    })();
                    plnLog('ui:autoApply:afterClickSummary', afterSummary);
                }, 300);
                //   console.log('[WME PLN] Address panel Apply pulsado automáticamente para reflejar cambios.');
                return; // una sola vez
            }
        } catch (e) {
            console.warn('[WME PLN] No se pudo auto-aplicar el panel de dirección:', e);
        }
    }

    //**************************************************************************
    //Nombre: registerCityModalHelpers
    //Fecha modificación: 2025-08-09
    //Hora: 17:00
    //Autor: mincho77
    //Entradas: ninguna
    //Salidas: ninguna
    //Prerrequisitos si existen: DOM disponible
    //Descripción: Delegado de click para botón 'Aplicar Ciudad' y observador para mostrar ID junto al nombre de ciudad.
    //**************************************************************************
    function registerCityModalHelpers() {
        let _plnCityClickTs = 0; // Allen-style: debounce timestamp for "Aplicar ciudad"
        function findCityContainer(start) {
            let node = start;
            for (let i = 0; i < 8 && node; i++) {
                if (node.querySelector && node.querySelector('input[type="radio"]')) {
                    const hasCityRadios = node.querySelector(
                        'input[type="radio"][name*="city"], input[type="radio"][name^="city-"], input[type="radio"][name^="city_selection"], input[type="radio"][name^="city-selection-"]'
                    );
                    if (hasCityRadios) return node;
                }
                node = node.parentElement;
            }
            return document.body;
        }
        // Añade "(ID: ####)" a cada opción si aún no lo tiene
        function annotateCityIds(container) {
            try {
                const radios = container.querySelectorAll('input[type="radio"][name*="city"], input[type="radio"][name^="city-"], input[type="radio"][name^="city_selection"], input[type="radio"][name^="city-selection-"]');
                radios.forEach(r => {
                    const lbl = container.querySelector(`label[for="${r.id}"]`) || r.closest('label');
                    if (!lbl) return;
                    if (/\(ID:\s*\d+\)/.test(lbl.textContent)) return; // ya está
                    lbl.appendChild(document.createTextNode(` (ID: ${r.value})`));
                });
            } catch (_) { /* noop */ }
        }

        // Resuelve el venueId como string; nunca devuelve número
        function resolveVenueIdString(btn, container, selected) {
            // 1) data-venue-id en botón o contenedor
            let id = btn.getAttribute('data-venue-id') || container.getAttribute('data-venue-id');
            if (id) return String(id);

            // 2) name del radio: city-selection-<venueId>
            const nameAttr = selected?.name || '';
            let m = nameAttr.match(/city[-_]?selection[-_]?([0-9.]+)/i);
            if (m && m[1]) return String(m[1]);

            // 3) atributo id del radio
            const selId = selected?.id || '';
            m = selId.match(/city[-_]?selection[-_]?([0-9.]+)/i);
            if (m && m[1]) return String(m[1]);

            // 4) URL ?venues=186384446. ...
            const h = String(location.href || '');
            m = h.match(/[?&]venues=([0-9.]+)/);
            if (m && m[1]) return String(m[1]);

            return '';
        }

        // Click en "Aplicar ciudad"
        document.addEventListener('click', function (e) {
            const btn = e.target.closest('button');
            if (!btn) return;
            const txt = (btn.textContent || btn.innerText || '').trim().toLowerCase();
            // Ignorar botones del disclaimer
            if (btn.hasAttribute('data-disclaimer-btn')) return;
            plnLog('ui:click', { buttonText: txt });
            if (!/^(aplicar ciudad|apply city)$/i.test(txt)) return;
            // Allen-style: Debounce to avoid double handling when WME/UI fires multiple events
            const _nowTs = Date.now();
            if (_nowTs - _plnCityClickTs < 400) { // 400ms debounce window
                plnLog('ui:debounce');
                return;
            }
            _plnCityClickTs = _nowTs;
            e.preventDefault();
            e.stopImmediatePropagation?.();
            e.stopPropagation?.();

            const container = findCityContainer(btn);
            annotateCityIds(container);

            let selected = container.querySelector(
                'input[type="radio"][name*="city"]:checked, input[type="radio"][name^="city-"]:checked, input[type="radio"][name^="city_selection"]:checked, input[type="radio"][name^="city-selection-"]:checked'
            ) || container.querySelector('input[type="radio"]:checked');

            plnLog('ui:selectedRadio', { exists: !!selected, name: selected && selected.name, value: selected && selected.value });
            if (!selected) { alert('Selecciona una ciudad.'); return; }

            const selectedCityId = Number(selected.value);

            // Lee el nombre mostrado (limpia “(ID: 123) 3.7 km” si existe)
            let selectedCityName = '';
            const label = container.querySelector(`label[for="${selected.id}"]`) || selected.closest('label');
            if (label) {
                selectedCityName = (label.textContent || '').trim();
                selectedCityName = selectedCityName.replace(/\s*\(ID:\s*\d+\)\s*[\d\.,]*\s*(km|m)?/i, '').trim();
            }

            const venueIdStr = resolveVenueIdString(btn, container, selected);
            plnLog('ui:resolvedData', { venueId: venueIdStr, selectedCityId, selectedCityName });
            plnApplyCityToVenue(venueIdStr, selectedCityId, selectedCityName);
            // NUEVO BLOQUE: Cambiar el ícono a chulito en la tabla de resultados
            setTimeout(() => {
                // Busca la fila correspondiente por data-place-id
                const row = document.querySelector(`tr[data-place-id="${venueIdStr}"]`);
                if (row) {
                    const cityStatusIcon = row.querySelector('.city-status-icon');
                    if (cityStatusIcon) {
                        cityStatusIcon.innerHTML = '✅';
                        cityStatusIcon.style.color = 'green';
                        cityStatusIcon.title = `Ciudad: ${selectedCityName}`; // Actualiza el tooltip con el nombre de la ciudad
                    }
                }
            }, 500); // Espera breve para asegurar que el cambio de ciudad se haya aplicado
        }, true);
    }

    try { window.WME_PLN_registerCityModalHelpers = registerCityModalHelpers; } catch (_) { }
    try { registerCityModalHelpers(); } catch (e) { console.warn('[WME PLN] registerCityModalHelpers init error', e); }
    // Permite crear un panel flotante en WME
    function updateScanProgressBar(currentIndex, totalPlaces) {
        if (totalPlaces === 0) // Si no hay lugares, no actualiza la barra de progreso
            return;
        let progressPercent = Math.floor(((currentIndex + 1) / totalPlaces) * 100); // Calcular el porcentaje de progreso
        progressPercent = Math.min(progressPercent, 100);
        const progressBarInnerTab = document.getElementById("progressBarInnerTab"); // Actualizar la barra de progreso
        const progressBarTextTab = document.getElementById("progressBarTextTab"); // Actualizar el texto de la barra de progreso
        if (progressBarInnerTab && progressBarTextTab) // Asegurarse de que los elementos existen antes de intentar actualizarlos
        {
            progressBarInnerTab.style.width = `${progressPercent}%`; // Actualizar el ancho de la barra de progreso
            const currentDisplay = Math.min(currentIndex + 1, totalPlaces); // Mostrar el número actual de lugares procesados
            progressBarTextTab.textContent = `Progreso: ${progressPercent}% (${currentDisplay}/${totalPlaces})`; // Actualizar el texto de la barra de progreso
        }
    }//updateScanProgressBar

    function updateInconsistenciesCount(delta) {
        const resultsCounterDiv = document.querySelector("#wme-place-inspector-panel .results-counter-display");
        if (resultsCounterDiv) {
            // Recalcular basado en filas visibles y total global si es posible
            let currentVisible = 0;
            const tbody = document.querySelector("#wme-place-inspector-panel tbody");
            if (tbody) {
                currentVisible = Array.from(tbody.querySelectorAll('tr')).filter(row => row.style.display !== 'none').length;
            }

            // Usar el global si existe, sino fallback a dataset
            const totalPending = (typeof window.plnAllInconsistents !== 'undefined')
                ? window.plnAllInconsistents.length
                : parseInt(resultsCounterDiv.dataset.totalOriginal || '0', 10);

            const batchSize = 30; // Hardcoded or read from somewhere
            const currentBatchTotal = Math.min(batchSize, totalPending);
            const processedInBatch = currentBatchTotal - currentVisible;
            const realPending = totalPending - processedInBatch;

            if (currentVisible === 0 && totalPending === 0) {
                resultsCounterDiv.innerHTML = `<span style="color: green;">✔</span> Todos los lugares visibles están correctamente normalizados o excluidos.`;
                const outputDiv = document.querySelector("#wme-place-inspector-output");
                if (outputDiv) outputDiv.innerHTML = `<div style='color:green; padding:10px;'>✔ Todos los lugares visibles están correctamente normalizados o excluidos.</div>`;
            } else {
                // "Inconsistencias pendientes: 84. En pantalla: 29 (Normalizados: 1)"
                resultsCounterDiv.innerHTML = `Inconsistencias pendientes: <b style="color: #ff0000;">${realPending}</b>. En pantalla: <b style="color: #ff0000;">${currentVisible}</b> (Normalizados: <b style="color: green;">${processedInBatch}</b>).`;
            }
        }
    }//updateInconsistenciesCount


    // Permite crear un panel flotante en WME
    function escapeRegExp(string) {
        return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }//escapeRegExp



    // Función para cargar palabras del diccionario por defecto (Hoja "Dictionary")
    async function loadDictionaryWordsFromSheet(forceReload = false) {
        const SPREADSHEET_ID = "10HSyonrHd__sC0wO0hmOQyBbNRlWF8UFr6kkOuSru00";
        const API_KEY = "AIzaSyAQbvIQwSPNWfj6CcVEz5BmwfNkao533i8";
        const RANGE = "Dictionary!A2:B";

        // usa window.dictionaryWords y window.dictionaryIndex para almacenar las palabras y su índice
        // Si no existen, las inicializa como un Set y un objeto vacío
        if (!window.dictionaryWords) window.dictionaryWords = new Set();
        if (!window.dictionaryIndex) window.dictionaryIndex = {};

        const url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${RANGE}?key=${API_KEY}`;

        return new Promise((resolve) => {
            if (SPREADSHEET_ID === "TU_SPREADSHEET_ID" || API_KEY === "TU_API_KEY") {
                console.warn('[WME PLN] SPREADSHEET_ID o API_KEY no configurados para el diccionario.');
                resolve();
                return;
            }

            // verifica si hay datos en caché
            // Si hay datos en caché y no se fuerza la recarga, los usa
            // Si no hay datos en caché o se fuerza la recarga, hace la solicitud
            const cachedData = localStorage.getItem("wme_pln_dictionary_cache");
            if (!forceReload && cachedData) {
                try {
                    const { data, timestamp } = JSON.parse(cachedData);
                    // usar caché si tiene menos de 24 horas
                    if (data && timestamp && (Date.now() - timestamp < 24 * 60 * 60 * 1000)) {
                        //console.log('[WME PLN] Usando datos en caché. Tiempo restante para expirar:', ((timestamp + 24 * 60 * 60 * 1000) - Date.now())/1000/60, 'minutos');

                        // console.log('[WME PLN] Usando diccionario en caché');

                        // restaura las palabras y el índice del diccionario desde la caché
                        window.dictionaryWords = new Set(data.words);
                        window.dictionaryIndex = data.index;

                        // Construir mapa de tildes optimizado
                        buildNormalizedDictionaryMap();

                        updateDictionaryWordsCountLabel();

                        resolve();
                        return;
                    }
                } catch (e) {
                    console.warn('[WME PLN] Error al leer caché del diccionario:', e);
                }
            }

            makeRequest({
                method: "GET",
                url: url,
                timeout: 10000,
                onload: function (response) {
                    if (response.status >= 200 && response.status < 300) {
                        try {
                            const data = JSON.parse(response.responseText);
                            let newWordsAdded = 0;

                            if (data.values) {
                                data.values.forEach(row => {
                                    const word = (row[0] || '').trim();
                                    if (word && !window.dictionaryWords.has(word.toLowerCase())) {
                                        window.dictionaryWords.add(word.toLowerCase());
                                        const firstChar = word.charAt(0).toLowerCase();
                                        if (!window.dictionaryIndex[firstChar])
                                            window.dictionaryIndex[firstChar] = [];
                                        window.dictionaryIndex[firstChar].push(word.toLowerCase());
                                        newWordsAdded++;
                                    }
                                });

                                // Cache the dictionary
                                try {
                                    localStorage.setItem("wme_pln_dictionary_cache", JSON.stringify({
                                        data: {
                                            words: Array.from(window.dictionaryWords),
                                            index: window.dictionaryIndex
                                        },
                                        timestamp: Date.now()
                                    }));
                                } catch (e) {
                                    console.warn('[WME PLN] Error al guardar caché del diccionario:', e);
                                }

                                // también guarda en localStorage para uso rápido
                                try {
                                    localStorage.setItem("dictionaryWordsList", JSON.stringify(Array.from(window.dictionaryWords)));
                                } catch (e) {
                                    console.error("[WME PLN] Error guardando diccionario en localStorage:", e);
                                }

                                // Construir mapa de tildes optimizado
                                buildNormalizedDictionaryMap();

                                // ✨ Limpiar palabras propias que ahora están en el diccionario oficial
                                if (window.userAddedDictionaryWords && window.userAddedDictionaryWords.size > 0) {
                                    const wordsToRemove = [];
                                    window.userAddedDictionaryWords.forEach(userWord => {
                                        if (window.dictionaryWords.has(userWord.toLowerCase())) {
                                            wordsToRemove.push(userWord);
                                        }
                                    });

                                    if (wordsToRemove.length > 0) {
                                        wordsToRemove.forEach(word => window.userAddedDictionaryWords.delete(word));
                                        // Guardar la lista actualizada
                                        try {
                                            localStorage.setItem('userAddedDictionaryWords',
                                                JSON.stringify(Array.from(window.userAddedDictionaryWords)));
                                            console.log(`[WME PLN] ${wordsToRemove.length} palabra(s) propia(s) eliminada(s) porque ya están en el diccionario oficial:`, wordsToRemove);
                                        } catch (e) {
                                            console.error('[WME PLN] Error guardando palabras de usuario limpiadas:', e);
                                        }
                                    }
                                }

                                if (window.updateDictionaryWordsCountLabel) {
                                    window.updateDictionaryWordsCountLabel();
                                }

                                //console.log(`[WME PLN] Diccionario cargado: ${newWordsAdded} palabras nuevas añadidas.`);
                            }
                        } catch (e) {
                            console.error('[WME PLN] Error al procesar datos del diccionario:', e);
                        }
                    }
                    resolve();
                },
                // Añade esto en ambas funciones, justo después del try/catch en onload:
                onerror: function (error) {
                    console.error('[WME PLN] Error de red al cargar datos por defecto:', error);
                    //console.log('[WME PLN] URL que falló:', url);
                    resolve(); // Resolver la promesa para no bloquear
                },
                ontimeout: function () {
                    console.error('[WME PLN] Timeout al cargar diccionario');
                    resolve();
                }
            });
        });
    }//loadDictionaryWordsFromSheet

    // Función para cargar palabras especiales/excluidas por defecto
    async function loadExcludedWordsFromSheet(forceReload = false) {
        const SPREADSHEET_ID = "10HSyonrHd__sC0wO0hmOQyBbNRlWF8UFr6kkOuSru00";
        const API_KEY = "AIzaSyAQbvIQwSPNWfj6CcVEz5BmwfNkao533i8";
        // Se prueban varias pestañas candidatas para compatibilidad hacia atrás
        const CANDIDATE_RANGES = [
            "SpecialWords!A2:A",
        ];
        const CACHE_KEY = "wme_pln_excluded_cache";
        const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 horas
        const SYNC_FLAG_KEY = "wme_pln_special_words_synced";

        if (SPREADSHEET_ID === "TU_SPREADSHEET_ID" || API_KEY === "TU_API_KEY") {
            console.warn('[WME PLN] SPREADSHEET_ID o API_KEY no configurados para palabras especiales.');
            return;
        }

        if (!(window.excludedWords instanceof Set)) window.excludedWords = new Set(Array.isArray(window.excludedWords) ? window.excludedWords : []);
        if (!(excludedWordsMap instanceof Map)) excludedWordsMap = new Map();

        // Check if this is the first run (flag not present)
        const isFirstRun = !localStorage.getItem(SYNC_FLAG_KEY);

        if (!forceReload && !isFirstRun) {
            try {
                const cachedRaw = localStorage.getItem(CACHE_KEY);
                if (cachedRaw) {
                    const { timestamp, data } = JSON.parse(cachedRaw);
                    if (timestamp && data && Array.isArray(data) && (Date.now() - timestamp) < CACHE_TTL_MS) {
                        applyExcludedWordsFromSheet(data, false);
                        return;
                    }
                }
            }
            catch (cacheErr) {
                console.warn('[WME PLN] Error leyendo caché de palabras especiales:', cacheErr);
            }
        }

        await new Promise((resolve) => {
            let resolved = false;

            const tryRange = (idx) => {
                if (idx >= CANDIDATE_RANGES.length) {
                    if (!resolved) {
                        resolve();
                        resolved = true;
                    }
                    return;
                }

                const range = CANDIDATE_RANGES[idx];
                const url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${range}?key=${API_KEY}`;

                makeRequest({
                    method: "GET",
                    url,
                    timeout: 10000,
                    onload: (response) => {
                        if (response.status >= 200 && response.status < 300) {
                            try {
                                const payload = JSON.parse(response.responseText);
                                const values = Array.isArray(payload?.values) ? payload.values : [];
                                const sheetWords = values
                                    .map(row => (row?.[0] || '').trim())
                                    .filter(Boolean);

                                if (sheetWords.length) {
                                    try {
                                        localStorage.setItem(CACHE_KEY, JSON.stringify({ timestamp: Date.now(), data: sheetWords }));
                                    }
                                    catch (cacheErr) {
                                        console.warn('[WME PLN] No se pudo guardar caché de palabras especiales:', cacheErr);
                                    }

                                    applyExcludedWordsFromSheet(sheetWords, isFirstRun);

                                    // If it was first run, mark as synced now
                                    if (isFirstRun) {
                                        try {
                                            localStorage.setItem(SYNC_FLAG_KEY, "true");
                                            console.log('[WME PLN] Primera sincronización de palabras especiales completada. Flag establecido.');
                                        } catch (e) { }
                                    }

                                    if (!resolved) {
                                        resolve();
                                        resolved = true;
                                    }
                                    return;
                                }
                            }
                            catch (parseErr) {
                                console.error('[WME PLN] Error procesando palabras especiales desde Sheets:', parseErr);
                            }
                        }

                        // Intentar con la siguiente pestaña si esta no devolvió datos útiles
                        tryRange(idx + 1);
                    },
                    onerror: () => tryRange(idx + 1),
                    ontimeout: () => tryRange(idx + 1)
                });
            };

            tryRange(0);
        });

        function applyExcludedWordsFromSheet(wordsArray, isFirstRun) {
            try {
                const sheetSet = new Map();
                wordsArray.forEach(word => {
                    const normalizedKey = word.toLowerCase();
                    if (!sheetSet.has(normalizedKey)) sheetSet.set(normalizedKey, word);
                });

                // Logic:
                // If isFirstRun: Overwrite local with sheet (sheetSet becomes the base)
                // If !isFirstRun: Merge sheet into local (keep local additions)

                let combined;
                if (isFirstRun) {
                    // Overwrite: Use only words from sheet
                    combined = new Set(sheetSet.values());
                    console.log('[WME PLN] Primera ejecución: Sobrescribiendo palabras especiales locales con las de la hoja.');
                } else {
                    // Merge: Keep existing local words + add new ones from sheet
                    const existing = window.excludedWords instanceof Set ? Array.from(window.excludedWords) : [];
                    existing.forEach(word => {
                        const key = String(word || '').toLowerCase();
                        if (!sheetSet.has(key)) sheetSet.set(key, word);
                    });
                    combined = new Set(sheetSet.values());
                }

                window.excludedWords = combined;

                if (!(excludedWordsMap instanceof Map)) excludedWordsMap = new Map();
                excludedWordsMap.clear();
                combined.forEach(word => {
                    const firstChar = word.charAt(0).toLowerCase();
                    if (!excludedWordsMap.has(firstChar)) excludedWordsMap.set(firstChar, new Set());
                    excludedWordsMap.get(firstChar).add(word);
                });

                try { saveExcludedWordsToLocalStorage(); } catch (e) { console.warn('[WME PLN] Error guardando palabras especiales tras cargar Sheets:', e); }

                // Refrescar la UI si el panel ya está construido
                requestAnimationFrame(() => {
                    try {
                        const ul = document.getElementById("excludedWordsList");
                        if (ul && typeof renderExcludedWordsList === 'function') renderExcludedWordsList(ul);
                        updateSpecialWordsCountLabel();
                    }
                    catch (uiErr) {
                        console.warn('[WME PLN] No se pudo refrescar la lista de palabras especiales tras recarga:', uiErr);
                    }
                });
            }
            catch (err) {
                console.error('[WME PLN] Error aplicando palabras especiales desde Sheets:', err);
            }
        }
    }// loadExcludedWordsFromSheet
    try { window.loadExcludedWordsFromSheet = loadExcludedWordsFromSheet; } catch (_) { }


    //Función Para Cargar Categorías por defecto
    async function loadDynamicCategoriesFromSheet(forceReload = false) {
        const SPREADSHEET_ID = "10HSyonrHd__sC0wO0hmOQyBbNRlWF8UFr6kkOuSru00";
        const API_KEY = "AIzaSyAQbvIQwSPNWfj6CcVEz5BmwfNkao533i8";
        const RANGE = "Categories!A2:F";
        const CACHE_VERSION = 2; // Increment this when schema changes (added prefixWords)
        window.dynamicCategoryRules = []; // Definimos la variable global para guardar las reglas
        const url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${RANGE}?key=${API_KEY}`;

        return new Promise((resolve) => {
            if (SPREADSHEET_ID === "TU_SPREADSHEET_ID" || API_KEY === "TU_API_KEY") {
                console.warn('[WME PLN] No se ha configurado SPREADSHEET_ID o API_KEY. Se omitirá la carga de categorías dinámicas.');
                resolve();
                return;
            }

            // Check for cached data first
            const cachedData = localStorage.getItem("wme_pln_categories_cache");
            if (!forceReload && cachedData) {
                try {
                    const { data, timestamp, version } = JSON.parse(cachedData);
                    // Use cache if less than 24 hours old AND version matches
                    if (data && timestamp && version === CACHE_VERSION && (Date.now() - timestamp < 24 * 60 * 60 * 1000)) {
                        //console.log('[WME PLN] Usando categorías en caché. Reconstruyendo RegExp...');

                        // ===================== INICIO DE LA CORRECCIÓN =====================
                        // Se itera sobre los datos de la caché para reconstruir las expresiones regulares
                        const buildKeywordPattern = (keywordRaw) => {
                            const tokens = keywordRaw.split(/\s+/).filter(Boolean);
                            const tokenPatterns = tokens.map(token => {
                                const base = escapeRegExp(token);
                                if (/s$/i.test(token)) return base;
                                if (/[aeiouáéíóú]$/i.test(token)) return `${base}s?`;
                                if (/[nrldct]$/i.test(token)) return `${base}(?:es)?`;
                                return `${base}(?:es|s)?`;
                            });
                            return `\\b${tokenPatterns.join('\\s+')}\\b`;
                        };

                        window.dynamicCategoryRules = data.map(rule => {
                            if (rule.keyword) { // Asegurarse de que la regla tenga keywords
                                const keywords = rule.keyword.split(';').map(k => k.trim()).filter(k => k.length > 0);
                                const regexParts = keywords.map(k => buildKeywordPattern(k));
                                const combinedRegex = new RegExp(`(${regexParts.join('|')})`, 'i');

                                // Devolver la regla con la propiedad compiledRegex correctamente creada
                                return { ...rule, compiledRegex: combinedRegex };
                            }
                            return rule; // Devuelve la regla sin cambios si no tiene keyword
                        });
                        // ===================== FIN DE LA CORRECCIÓN =====================

                        window.dynamicCategoryRules.sort((a, b) => b.keyword.length - a.keyword.length);
                        resolve();
                        return;
                    }
                } catch (e) {
                    console.warn('[WME PLN] Error al leer caché de categorías:', e);
                }
            }

            makeRequest({
                method: "GET",
                url: url,
                timeout: 10000, // Add timeout
                onload: function (response) {
                    if (response.status >= 200 && response.status < 300) {
                        try {
                            const data = JSON.parse(response.responseText);
                            if (data.values) {
                                // El procesamiento de los datos de la API ya era correcto
                                const buildKeywordPattern = (keywordRaw) => {
                                    const tokens = keywordRaw.split(/\s+/).filter(Boolean);
                                    const tokenPatterns = tokens.map(token => {
                                        const base = escapeRegExp(token);
                                        if (/s$/i.test(token)) return base;
                                        if (/[aeiouáéíóú]$/i.test(token)) return `${base}s?`;
                                        if (/[nrldct]$/i.test(token)) return `${base}(?:es)?`;
                                        return `${base}(?:es|s)?`;
                                    });
                                    return `\\b${tokenPatterns.join('\\s+')}\\b`;
                                };

                                window.dynamicCategoryRules = data.values.map(row => {
                                    const keyword = (row[0] || '').toLowerCase().trim();
                                    const keywords = keyword.split(';').map(k => k.trim()).filter(k => k.length > 0);
                                    const regexParts = keywords.map(k => buildKeywordPattern(k));
                                    const combinedRegex = new RegExp(`(${regexParts.join('|')})`, 'i');
                                    return {
                                        keyword: keyword,
                                        categoryKey: row[1] || '',
                                        icon: row[2] || '⚪',
                                        desc_es: row[3] || 'Sin descripción',
                                        desc_en: row[4] || 'No description',
                                        prefixWords: row[5] || '', // Columna F: Palabras para botones
                                        compiledRegex: combinedRegex
                                    };
                                });
                                window.dynamicCategoryRules.sort((a, b) => b.keyword.length - a.keyword.length);

                                // La lógica para guardar en caché también es correcta
                                try {
                                    localStorage.setItem("wme_pln_categories_cache", JSON.stringify({
                                        data: window.dynamicCategoryRules,
                                        timestamp: Date.now(),
                                        version: CACHE_VERSION
                                    }));
                                } catch (e) {
                                    console.warn('[WME PLN] Error al guardar caché de categorías:', e);
                                }

                                //console.log('[WME PLN] Categorías cargadas desde API');
                            }
                        } catch (e) {
                            console.error('[WME PLN] Error al procesar datos de categorías:', e);
                        }
                    } else {
                        console.warn(`[WME PLN] Error HTTP ${response.status} al cargar categorías`);
                    }
                    resolve();
                },
                onerror: function (error) {
                    console.error('[WME PLN] Error de red al cargar categorías:', error);
                    resolve();
                },
                ontimeout: function () {
                    console.error('[WME PLN] Timeout al cargar categorías');
                    resolve();
                }
            });
        });
    }//loadDynamicCategoriesFromSheet

    // === Debug helper: force category cache reload ===
    try {
        window.plnReloadCategories = async function () {
            console.log('[PLN] Limpiando caché de categorías...');
            localStorage.removeItem('wme_pln_categories_cache');
            console.log('[PLN] Recargando categorías desde Google Sheets...');
            await loadDynamicCategoriesFromSheet(true);
            console.log('[PLN] Categorías recargadas. Total de reglas:', window.dynamicCategoryRules?.length || 0);
            // Log a sample to verify prefixWords is present
            if (window.dynamicCategoryRules && window.dynamicCategoryRules.length > 0) {
                console.log('[PLN] Ejemplo de regla (primera):', {
                    keyword: window.dynamicCategoryRules[0].keyword,
                    categoryKey: window.dynamicCategoryRules[0].categoryKey,
                    icon: window.dynamicCategoryRules[0].icon,
                    desc_es: window.dynamicCategoryRules[0].desc_es,
                    prefixWords: window.dynamicCategoryRules[0].prefixWords
                });
            }
            plnToast('✅ Categorías recargadas desde Google Sheets', 2500);
        };
        if (typeof unsafeWindow !== 'undefined') unsafeWindow.plnReloadCategories = window.plnReloadCategories;
    } catch (_) { }

    // Función para encontrar la categoría de un lugar basado en su nombre
    function findCategoryForPlace(placeName) {
        if (!placeName || typeof placeName !== 'string' || !window.dynamicCategoryRules || window.dynamicCategoryRules.length === 0) // Si el nombre del lugar es inválido o no hay reglas de categoría cargadas, devuelve un array vacío de sugerencias.
            return [];
        const lowerCasePlaceName = placeName.toLowerCase();// Convertir el nombre del lugar a minúsculas para comparaciones insensibles a mayúsculas
        const allMatchingRules = []; // Este array almacenará todas las reglas de categoría que coincidan.
        const placeWords = lowerCasePlaceName.split(/\s+/).filter(w => w.length > 0); // Descomponer el nombre del lugar en palabras
        const SIMILARITY_THRESHOLD_FOR_KEYWORDS = 0.95; // Puedes ajustar este umbral (ej. 0.90 para 90% de similitud)
        // PASO 0: Normalizar el nombre del lugar eliminando diacríticos y caracteres especiales
        for (const rule of window.dynamicCategoryRules) {
            if (!rule.compiledRegex) continue; // Si la regla no tiene una expresión regular compilada (lo cual no debería pasar si se cargó correctamente), salta a la siguiente regla.
            // **PASO 1: Búsqueda por Regex Exacta
            if (rule.compiledRegex.test(lowerCasePlaceName)) {
                if (!allMatchingRules.some(mr => mr.categoryKey === rule.categoryKey)) {
                    allMatchingRules.push(rule);
                }
                // Si Ya Añadimos La Regla Por Regex Exacta, Pasar A La Siguiente Regla Para Ahorrar Cálculos De Similitud
                continue;
            }
            // **PASO 2: Búsqueda por Similitud para CADA palabra del lugar vs CADA palabra clave de la regla**
            const ruleKeywords = rule.keyword.split(';').map(k => k.trim().toLowerCase()).filter(k => k.length > 0);
            let foundSimilarityForThisRule = false; // Bandera para saber si ya encontramos una buena similitud para esta regla, para no seguir buscando más palabras clave de la regla.
            for (const pWord of placeWords) // Cada palabra del nombre del lugar
            { // Cada palabra del nombre del lugar
                if (foundSimilarityForThisRule) break; // Si ya encontramos una buena similitud para esta regla, pasamos a la siguiente.
                for (const rKeyword of ruleKeywords) { // Cada palabra clave de la regla
                    // Asegurarse de que rKeyword no sea una expresión regular, sino la palabra literal para Levenshtein
                    const similarity = calculateSimilarity(pWord, rKeyword); // Calcular la similitud entre la palabra del lugar y la palabra clave de la regla
                    if (similarity >= SIMILARITY_THRESHOLD_FOR_KEYWORDS && !allMatchingRules.some(mr => mr.categoryKey === rule.categoryKey)) // Si la similitud es alta y aún no hemos añadido esta categoría
                    {
                        allMatchingRules.push(rule);
                        foundSimilarityForThisRule = true; // Marcamos que ya la encontramos para esta regla
                        break; // Salimos del bucle de rKeyword y pWord
                    }
                }
            }
        }
        //console.log(`[WME PLN][DEBUG] findCategoryForPlace para "${placeName}" devolvió: `, allMatchingRules);
        return allMatchingRules;
    }//findCategoryForPlace

    // Permite obtener el icono de una categoría
    function getWazeLanguage() {
        // 1. Intento principal con el SDK (método recomendado)
        if (wmeSDK && typeof wmeSDK.getWazeLocale === 'function') {
            const locale = wmeSDK.getWazeLocale(); // ej: 'es-419'
            if (locale)
                return locale.split('-')[0].toLowerCase(); // -> 'es'
        }
        // 2. Fallback al objeto global 'W' si el SDK falla
        if (typeof W !== 'undefined' && W.locale)
            return W.locale.split('-')[0].toLowerCase();
        // 3. Último recurso si nada funciona
        return 'es';
    }//getWazeLanguage

    //Permite obtener el icono y descripción de una categoría
    function getCategoryDetails(categoryKey) {
        const lang = getWazeLanguage();
        // 1. Intento con la hoja de Google (window.dynamicCategoryRules)
        if (window.dynamicCategoryRules && window.dynamicCategoryRules.length > 0) {
            const rule = window.dynamicCategoryRules.find(r => r.categoryKey.toUpperCase() === categoryKey.toUpperCase());
            if (rule) {
                const description = (lang === 'es' && rule.desc_es) ? rule.desc_es : rule.desc_en;
                return { icon: rule.icon, description: description };
            }
        }
        // 2. Fallback a la lista interna del script si no se encontró en la hoja
        const hardcodedInfo = getCategoryIcon(categoryKey); // Llama a la función original
        if (hardcodedInfo && hardcodedInfo.icon !== '⚪' && hardcodedInfo.icon !== '❓') {
            // La función original devuelve un título "Español / English", lo separamos.
            const descriptions = hardcodedInfo.title.split(' / ');
            const description = (lang === 'es' && descriptions[0]) ? descriptions[0] : descriptions[1] || descriptions[0];
            return { icon: hardcodedInfo.icon, description: description };
        }
        // 3. Si no se encuentra en ninguna parte, devolver un valor por defecto.
        const defaultDescription = lang === 'es' ? `Categoría no encontrada (${categoryKey})` : `Category not found (${categoryKey})`;
        return { icon: '⚪', description: defaultDescription };
    }//getCategoryDetails

    // Función para eliminar diacríticos de una cadena
    function removeDiacritics(str) {
        return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
    }//removeDiacritics

    // Función para validar una palabra excluida
    // Modificación de isValidExcludedWord para hacer verificaciones más estrictas
    function isValidExcludedWord(newWord) {
        if (!newWord) // Si la palabra está vacía, no es válida
            return { valid: false, msg: "La palabra no puede estar vacía." };

        const lowerNewWord = newWord.toLowerCase(); // Convertir a minúsculas para comparaciones insensibles a mayúsculas

        if (newWord.length === 1) // No permitir palabras de un solo caracter
            return { valid: false, msg: "No se permite agregar palabras de un solo caracter." };

        if (/^\d+$/.test(newWord)) // Bloquear números puros
            return { valid: false, msg: "No se permite agregar solo números a la lista." };

        if (/[-']/.test(newWord)) // Permitir palabras con "-" o "'" sin separarlas
            return { valid: true };

        if (/^[^a-zA-Z0-9áéíóúÁÉÍÓÚñÑ]+$/.test(newWord)) // No permitir caracteres especiales solos
            return { valid: false, msg: "No se permite agregar solo caracteres especiales." };

        // VERIFICACIÓN MÁS ESTRICTA DEL DICCIONARIO
        // Verificar tanto coincidencia exacta como insensible a mayúsculas/minúsculas
        if (window.dictionaryWords) {
            // Verificar si la palabra existe en el diccionario (insensible a mayúsculas/minúsculas)
            const dictionaryHasWordInsensitive = Array.from(window.dictionaryWords).some(w => w.toLowerCase() === lowerNewWord);
            if (dictionaryHasWordInsensitive) {
                return { valid: false, msg: "La palabra ya existe en el diccionario (sin considerar mayúsculas/minúsculas). No se puede agregar a especiales." };
            }

            // Verificar coincidencia exacta (esto ya está en código original)
            const dictionaryHasWordExact = Array.from(window.dictionaryWords).some(w => w === newWord);
            if (dictionaryHasWordExact) {
                return { valid: false, msg: "La palabra (con esta capitalización exacta) ya existe en el diccionario. No se puede agregar a especiales." };
            }
        }

        // Verificar si la palabra es una palabra común
        if (commonWords.has(lowerNewWord))
            return { valid: false, msg: "Esa palabra es muy común y no debe agregarse a la lista." };

        // Verificar si la palabra ya está en la lista de excluidas (tanto exacta como insensible a mayúsculas/minúsculas)
        if (excludedWords) {
            // Verificar coincidencia exacta
            if (excludedWords.has(newWord)) {
                return { valid: false, msg: "La palabra (con esta capitalización exacta) ya está en la lista." };
            }
            // Verificar coincidencia insensible a mayúsculas/minúsculas
            const firstChar = lowerNewWord.charAt(0);
            const candidatesForFirstChar = excludedWordsMap.get(firstChar);

            if (candidatesForFirstChar) {
                for (const existingWord of candidatesForFirstChar) {
                    if (existingWord.toLowerCase() === lowerNewWord) {
                        return { valid: false, msg: "Esta palabra ya existe en la lista (con diferente capitalización)." };
                    }
                }
            }


        }

        return { valid: true };
    }
    /*function isValidExcludedWord(newWord)
    {
        if (!newWord) // Si la palabra está vacía, no es válida
            return { valid : false, msg : "La palabra no puede estar vacía." };
        const lowerNewWord = newWord.toLowerCase(); // Convertir a minúsculas para comparaciones insensibles a mayúsculas
        if (newWord.length === 1) // No permitir palabras de un solo caracter
            return { valid: false, msg: "No se permite agregar palabras de un solo caracter." };
        if (/[-']/.test(newWord)) // Permitir palabras con "-" o "'" sin separarlas
            return { valid: true };
        if (/^[^a-zA-Z0-9áéíóúÁÉÍÓÚñÑ]+$/.test(newWord)) // No permitir caracteres especiales solos
            return { valid : false, msg : "No se permite agregar solo caracteres especiales." };
        // La verificación del diccionario ahora será sensible a mayúsculas/minúsculas
        // Si la palabra existe EXACTAMENTE como está en el diccionario, no permitirla como excluida.
        // Si el diccionario tiene 'sos' y la excluida es 'SOS', SÍ se permitirá.
        if (window.dictionaryWords && Array.from(window.dictionaryWords).some(w => w === newWord)) // Comparación sensible a mayúsculas/minúsculas
            return { valid : false, msg :"La palabra (con esta capitalización exacta) ya existe en el diccionario. No se puede agregar a especiales." };
        // Verificar si la palabra es una palabra común
        if (commonWords.has(lowerNewWord))        // No permitir palabras comunes
            return { valid: false, msg: "Esa palabra es muy común y no debe agregarse a la lista." };
       // Verificar si la palabra ya está en la lista de excluidas
        if (excludedWords && Array.from(excludedWords).some(w => w === newWord)) // No permitir duplicados exactos en excluidas
            return { valid : false, msg : "La palabra (con esta capitalización exacta) ya está en la lista." };
        return { valid : true };
    *///isValidExcludeWord

    // La función removeEmoticons con una regex más segura o un paso extraremoveEmoticons solo para emojis (sin afectar números)
    function removeEmoticons(text) {
        if (!text || typeof text !== 'string') {
            return '';
        }
        // Esta es una regex moderna y más robusta que utiliza propiedades de Unicode.
        // \p{Emoji_Presentation}: Coincide con emojis que se muestran como imágenes por defecto (Ej: 😊, 🏨, 🚗).
        // \p{Extended_Pictographic}: Coincide con un conjunto más amplio de símbolos que pueden ser emojis.
        // El flag 'u' es CRUCIAL para que la sintaxis \p{...} funcione.
        const emojiRegex = /[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu;

        let cleanedText = text.replace(emojiRegex, '');

        // Limpieza final de espacios extra que puedan quedar.
        cleanedText = cleanedText.replace(/\s{2,}/g, ' ').trim();

        // Si al retirar el emoji queda un guion colgando al final, elimínalo.
        // Mejora: Usar regex más robusta que capture guiones con o sin espacios
        cleanedText = cleanedText.replace(/\s*-\s*$/g, '').trim();

        // Evita devolver solo un guion si el emoji dejó el nombre vacío.
        if (/^-\s*$/.test(cleanedText)) {
            cleanedText = '';
        }

        return cleanedText;
    }// removeEmoticons


    // Modify aplicarReemplazosGenerales
    function aplicarReemplazosGenerales(name) {
        if (typeof window.skipGeneralReplacements === "boolean" && window.skipGeneralReplacements)
            return name;
        // Paso 1: Eliminar emoticones al inicio de los reemplazos generales.
        name = removeEmoticons(name);
        const reglas = [
            // Nueva regla: reemplazar | por espacio, guion y espacio
            { buscar: /\|/g, reemplazar: " - " },
            // Nueva regla: reemplazar / por espacio, barra y espacio, eliminando espacios alrededor
            { buscar: /\s*\/\s*/g, reemplazar: " - " },
            // Corrección: Para buscar [P] o [p] literalmente
            { buscar: /\[[Pp]\]/g, reemplazar: "" },
            // 1. Convertir guiones pegados a palabras o con un solo espacio a ' - '
            // Esto convierte "Palabra-Otra" -> "Palabra - Otra"
            // y "Palabra -Otra" -> "Palabra - Otra"
            // y "Palabra- Otra" -> "Palabra - Otra"
            { buscar: /(\p{L}|\p{N})\s*-\s*(\p{L}|\p{N})/gu, reemplazar: "$1 - $2" },
            // 2. Limpiar guiones que no estén entre palabras, convirtiéndolos en un solo ' - '
            // Esto estandariza " -- " a " - ", y " - " a " - ".
            // Asegura que siempre haya un espacio en cada lado del guion si no es un guion interno.
            { buscar: /\s*-\s*/g, reemplazar: " - " },
            { buscar: /\s{2,}/g, reemplazar: ' ' }, // Asegura espacios únicos antes de trim
        ];
        reglas.forEach(regla => { // Itera sobre cada regla de reemplazo
            if (regla.buscar.source === '\\|') // Si la regla es para el carácter '|', usa replaceAll
                name = name.replace(regla.buscar, regla.reemplazar);
            else
                name = name.replace(regla.buscar, regla.reemplazar);
        });
        name = name.replace(/\s{2,}/g, ' ').trim(); // Asegura el recorte final y espacios únicos

        // ****** INICIO DE LA MODIFICACIÓN: Limpieza de guiones dobles ******
        // Eliminar guiones dobles si están seguidos, reemplazándolos por un solo guion
        // Maneja '--', ' - - ', etc. y los convierte a un solo ' - '
        name = name.replace(/\s*-\s*-\s*/g, ' - ');
        name = name.replace(/--/g, '-'); // También para guiones pegados 'a--b' -> 'a-b'
        // ****** FIN DE LA MODIFICACIÓN ******

        return name;
    }// aplicarReemplazosGenerales

    // función auxiliar para capitalizar cada palabra en una frase
    function capitalizeEachWord(phrase) {
        if (!phrase || typeof phrase !== 'string') return "";
        return phrase.split(/\s+/) // Dividir por uno o más espacios
            .map((word, index) => { // Añadir index
                if (word.length === 0) return "";
                // La capitalización de palabras comunes y otras reglas complejas
                // ya las maneja normalizeWordInternal. Aquí solo capitalizamos la primera letra
                // si no es un caso especial.
                // Llamar a normalizeWordInternal para asegurar consistencia
                return normalizeWordInternal(word, index === 0, false); // Pasar isFirstWordInSequence
            })
            .join(' '); // Unir de nuevo con un solo espacio
    }

    //Permite aplicar reglas especiales de capitalización y puntuación a un nombre
    function aplicarReglasEspecialesNombre(newName) {
        // Regla de capitalización después de GUION
        newName = newName.replace(/-(\s*)([^\s]+)/g, (match, spaces, nextWord) => {
            // Esta lógica reutiliza la función principal de normalización para la palabra que sigue al guion.
            // Se pasa 'true' para isFirstWordInSequence, lo que le indica a la función que debe
            // capitalizar palabras comunes como "de", "la", "el", etc.
            const normalizedNextWord = normalizeWordInternal(nextWord, true, false);
            return `-${spaces}${normalizedNextWord}`;
        });
        // Capitalizar Después De Punto
        newName = newName.replace(/\.\s+([a-z])/g, (match, letter) => `. ${letter.toUpperCase()}`);
        // Capitalizar Después De Paréntesis De Apertura
        newName = newName.replace(/(\(\s*)([a-zA-Z])/g, (match, P1, P2) => {
            return P1 + P2.toUpperCase();
        });
        // Asegura que la última letra de una cadena esté en mayúsculas si es una letra sola al final
        newName = newName.replace(/\s([a-zA-Z])$/, (match, letter) => ` ${letter.toUpperCase()}`);
        // Asegurarse de que no haya espacios dobles creados y trim final
        newName = newName.replace(/\s{2,}/g, ' ').trim();
        return newName;
    }//aplicarReglasEspecialesNombre

    // Permite normalizar un nombre de lugar
    function processPlaceName(originalName) {
        //console.log(`[WME PLN] --- INICIANDO ANÁLISIS PARA: "${originalName}" ---`);
        //console.log(`[WME PLN - processPlaceName] Recibido para normalizar: "${originalName}"`); // LOG INICIO

        let processedName = originalName.trim();

        // Primero, reemplazamos el pipe por un espacio, para que las palabras se separen correctamente.
        // Hacemos esto ANTES de dividir en palabras para que normalizeWordInternal las vea por separado.
        processedName = processedName.replace(/\|/g, ' - '); // Reemplaza | por un espacio
        processedName = processedName.replace(/\s{2,}/g, ' ').trim(); // Limpia espacios dobles que puedan generarse

        //console.log(`[WME PLN - processPlaceName] Después de reemplazo de pipe: "${processedName}"`); // LOG PIPE REEMPLAZADO

        // Si el nombre está vacío después de los reemplazos, no hacemos nada más.
        const words = processedName.split(/\s+/).filter(word => word.length > 0);

        //console.log(`[WME PLN - processPlaceName] Palabras extraídas:`, words); // LOG PALABRAS EXTRAÍDAS


        // PASO 1: Normalización palabra por palabra (capitalización, reglas especiales)
        const normalizedWords = words.map((word, index) => {
            if (word === '-') return '-';

            const excl = isExcludedWord(word);
            if (excl) {
                if (excl.endsWith('-') && excl.replace(/-+$/, '').length > 0) {
                    return excl.replace(/-+$/, '');
                }
                return excl;
            }

            const lower = (word || '').toLowerCase();
            if (commonWords.has(lower)) {
                // minúscula, salvo si es la 1ª palabra o si viene justo después de "-" o "("
                const prevIsHyphen = index > 0 && words[index - 1] === '-';
                const prevIsOpenParen = index > 0 && words[index - 1] === '(';
                if (index === 0 || prevIsHyphen || prevIsOpenParen) {
                    return lower.charAt(0).toUpperCase() + lower.slice(1);
                }
                return lower;
            }

            return normalizeWordInternal(word, index === 0, false);
        });
        processedName = normalizedWords.join(" ");
        //console.log(`[WME PLN] [Paso 1] Después de normalizar cada palabra: "${processedName}"`); // LOG PALABRAS NORMALIZADAS

        //console.log(`[WME PLN] [Paso 1] Después de normalizar cada palabra: "${processedName}"`);

        // PASO 2: Aplicar reglas especiales de nombre (capitalización después de guion, etc.)
        // Aquí es donde `aplicarReglasEspecialesNombre` manejará el guion y capitalizará "Bolívar".
        processedName = aplicarReglasEspecialesNombre(processedName);
        //console.log(`[WME PLN] [Paso 2] Después de aplicar reglas especiales: "${processedName}"`);


        // PASO 3: Procesar comillas y paréntesis
        processedName = postProcessQuotesAndParentheses(processedName);
        //console.log(`[WME PLN] [Paso 3] Después de procesar comillas/paréntesis: "${processedName}"`);

        // PASO 4: Aplicar reemplazos definidos por el usuario
        if (typeof replacementWords === 'object' && Object.keys(replacementWords).length > 0) {
            //console.log("[WME PLN] [Paso 4] ANTES de aplicarReemplazosDefinidos:", processedName, replacementWords);
            processedName = aplicarReemplazosDefinidos(processedName, replacementWords);
            //console.log("[WME PLN] [Paso 4]DESPUÉS de aplicarReemplazosDefinidos:", processedName);
        }
        //console.log(`[WME PLN] [Paso 4] DESPUÉS de aplicar reemplazos: "${processedName}"`);

        // PASO 5: Aplicar reemplazos generales (barras, corchetes, etc.)
        // La regla para el pipe '|' en `aplicarReemplazosGenerales` ahora es redundante si solo se busca el pipe,
        // pero es inofensiva si ya lo reemplazamos. Asegúrate de que no haya otras reglas en aplicarReemplazosGenerales
        // que interfieran negativamente.
        processedName = aplicarReemplazosGenerales(processedName); // Esto ya no afectaría el `|`
        //console.log(`[WME PLN] [Paso 5] Después de aplicar reemplazos generales: "${processedName}"`);

        //console.log(`[WME PLN] [Paso 6] Después de corregir tildes: "${processedName}"`);
        // ******************************************************************************

        // PASO FINAL: Mover palabras según la configuración de swap (inicio o final)
        processedName = applySwapMovement(processedName);

        //console.log(`[WME PLN] [Paso 7] Después de applyWordsToStartMovement: "${processedName}"`);

        let finalName = processedName.replace(/\s{2,}/g, ' ').trim();

        const trailingHyphenPattern = /\s*-\s*$/;
        if (trailingHyphenPattern.test(finalName) && !plnShouldPreserveTrailingHyphen(finalName)) {
            finalName = finalName.replace(trailingHyphenPattern, '');
        }

        // Quitar el punto final si existe.
        if (finalName.endsWith('.')) {
            finalName = finalName.slice(0, -1);
        }


        //console.log(`[WME PLN - pPN] Resultado final de pPN: "${finalName}" y processedName: "${processedName}"`); // LOG FINAL

        return finalName;
    }// processPlaceName
    // Permite minimizar el panel de estadísticas
    function minimizeStatsPanel() {
        if (statsPanelElement) {
            statsPanelElement.style.display = 'block';
            statsPanelElement.style.width = '120px';
            statsPanelElement.style.height = '28px';
            statsPanelElement.style.overflow = 'hidden';
            statsPanelElement.style.left = '8px';
            statsPanelElement.style.bottom = '8px';
            statsPanelElement.style.cursor = 'pointer';
            statsPanelElement.querySelector('#stats-summary-view').style.display = 'flex';
            statsPanelElement.querySelector('#stats-detail-view').style.display = 'none';
        }
    }// minimizeStatsPanel

    // Permite maximizar el panel de estadísticas
    function maximizeStatsPanel() {
        if (statsPanelElement) {
            statsPanelElement.style.display = 'block';
            statsPanelElement.style.width = '';
            statsPanelElement.style.height = '';
            statsPanelElement.style.overflow = '';
            statsPanelElement.style.left = '23%';
            statsPanelElement.style.bottom = '60px';
            statsPanelElement.style.cursor = 'pointer';
            statsPanelElement.querySelector('#stats-summary-view').style.display = 'flex';
        }
    }// maximizeStatsPanel
    //Permite normalizar una palabra individual
    // Versión MODIFICADA
    // FUNCIÓN MODIFICADA
    function updateApplyButtonState(row, originalName) {
        // Encontrar los elementos necesarios dentro de la fila
        const inputReplacement = row.querySelector('.replacement-input'); // Usaremos una clase para identificarlo
        const applyButton = row.querySelector('button[title="Aplicar sugerencia"]');
        const applyButtonWrapper = applyButton?.parentElement;
        if (!inputReplacement || !applyButton || !applyButtonWrapper) return;

        const nameIsDifferent = inputReplacement.value.trim() !== originalName.trim();
        const categoryWasChanged = row.dataset.categoryChanged === 'true';
        const addressWasChanged = row.dataset.addressChanged === 'true'; // <-- NUEVA COMPROBACIÓN

        // Si el nombre, la categoría O la dirección han cambiado, habilitar el botón
        if (nameIsDifferent || categoryWasChanged || addressWasChanged) {
            // Habilitar botón
            applyButton.disabled = false;
            applyButton.style.opacity = "1";
            // Quitar el chulo verde de éxito si existe
            const successIcon = applyButtonWrapper.querySelector('span');
            if (successIcon) {
                successIcon.remove();
            }
        }
        else {
            // Deshabilitar botón
            applyButton.disabled = true;
            applyButton.style.opacity = "0.5";
        }
    }// updateApplyButtonState

    //Permite aplicar reemplazos definidos por el usuario a un texto
    function aplicarReemplazosDefinidos(text, replacementRules) {
        let newText = text;

        if (typeof replacementRules !== 'object' || replacementRules === null || Object.keys(replacementRules).length === 0) {
            return newText;
        }

        const sortedFromKeys = Object.keys(replacementRules).sort((a, b) => b.length - a.length);

        for (const fromKey of sortedFromKeys) {
            const toValue = replacementRules[fromKey];

            // CORRECCIÓN: Asegurar que fromKey es una string antes de pasarla a escapeRegExp
            const escapedFromKey = escapeRegExp(String(fromKey));

            let regex;
            const wordCharSet = '[\\p{L}\\p{N}_-]';

            // CORRECCIÓN (28-nov-2024): Usar Lookbehind/Lookahead para evitar coincidencias parciales
            // Esto evita que "ie" coincida dentro de "Barbie" -> "BarbIE"
            // (?<![\p{L}\p{N}_-])  -> No precedido por letra, número, _ o -
            // (?![[\p{L}\p{N}_-])   -> No seguido por letra, número, _ o -

            if (toValue.endsWith(' -')) {
                // Caso especial para reglas que terminan en " -" (ej: "Texaco -")
                // Se mantiene la lógica de grupos para capturar espacios y palabra siguiente
                regex = new RegExp(`(^|[^\\p{L}\\p{N}_\\-])(${escapedFromKey})(\\s+)(${wordCharSet}+)?(?=$|[^\\p{L}\\p{N}_-])`, 'giu');
            }
            else {
                // Caso estándar: Usar Lookaround para límites de palabra estrictos
                regex = new RegExp(`(?<![\\p{L}\\p{N}_-])(${escapedFromKey})(?![\\p{L}\\p{N}_-])`, 'giu');
            }

            // CORRECCIÓN CLAVE: Usar la sintaxis '...args' para capturar todos los argumentos
            // y luego extraerlos de forma robusta.
            newText = newText.replace(regex, (match, ...args) => {
                // El último argumento de `args` es la cadena original completa.
                // El penúltimo argumento de `args` es el offset.
                const originalString = args[args.length - 1]; // Captura el string original
                const offset = args[args.length - 2];       // Captura el offset

                // Los grupos de captura vienen antes del offset y originalString.
                // Reasignar los grupos de captura según el tipo de regex.
                let delimitadorPrevio, matchedFromKey, capturedSpaces, nextWordIfCaptured;

                if (toValue.endsWith(' -')) {
                    // Para la regex con 4 grupos de captura (lógica antigua mantenida para este caso)
                    delimitadorPrevio = args[0]; // p1
                    matchedFromKey = args[1];    // p2
                    capturedSpaces = args[2];    // p3
                    nextWordIfCaptured = args[3]; // p4
                } else {
                    // Para la regex con Lookaround (NUEVA LÓGICA)
                    // match es la palabra completa encontrada (ej: "ie")
                    // No hay grupos de captura extraños al inicio
                    delimitadorPrevio = ""; // No capturamos el delimitador con lookbehind
                    matchedFromKey = match;
                }

                const offsetOfMatchInCurrentText = offset;
                const stringBeingProcessedActual = originalString; // Ya es la cadena correcta

                // --- Lógica Anti-Duplicación de palabra anterior ---
                const textoAntesDelMatch = stringBeingProcessedActual.substring(0, offsetOfMatchInCurrentText + delimitadorPrevio.length);
                const palabrasAntes = textoAntesDelMatch.trim().split(/\s+/);
                const ultimaPalabraAntes = palabrasAntes.length > 0 ? palabrasAntes[palabrasAntes.length - 1] : "";
                const palabrasDelReemplazo = toValue.trim().split(/\s+/);
                const primeraPalabraReemplazo = palabrasDelReemplazo.length > 0 ? palabrasDelReemplazo[0] : "";
                if (ultimaPalabraAntes && primeraPalabraReemplazo) {
                    const semejanza = calculateSimilarity(ultimaPalabraAntes, primeraPalabraReemplazo);
                    if (semejanza > 0.9) {
                        return match;
                    }
                }

                // --- Evitar que reglas que quitan guiones actúen cuando sigue otra palabra ---
                try {
                    const trimmedFromKey = String(fromKey || '').trim();
                    const trimmedToValue = String(toValue || '').trim();
                    const afterMatchSlice = stringBeingProcessedActual.slice(offsetOfMatchInCurrentText + match.length);
                    const nextVisibleChar = afterMatchSlice.replace(/^\s+/, '').charAt(0);
                    if (
                        trimmedFromKey.endsWith('-') &&
                        !trimmedToValue.endsWith('-') &&
                        nextVisibleChar &&
                        /[\p{L}\p{N}]/u.test(nextVisibleChar)
                    ) {
                        return match;
                    }
                } catch (_) { /* noop */ }

                // --- Lógica para evitar auto-reemplazo infinito y duplicación de frases ---
                // (ej: "Terpel" -> "Terpel -", "Tostao" -> "Tostao' Café & Pan")
                if (toValue.toLowerCase().startsWith(fromKey.toLowerCase()) && toValue.length > fromKey.length) {
                    const suffix = toValue.substring(fromKey.length).trim();
                    const textAfterMatchRaw = stringBeingProcessedActual.substring(offsetOfMatchInCurrentText + match.length);

                    // Función local para normalizar (sin tildes, minúsculas, espacios simples)
                    const normalizeForCheck = (s) => {
                        return removeDiacritics(String(s || '')).toLowerCase().replace(/\s+/g, ' ').trim();
                    };

                    const suffixNorm = normalizeForCheck(suffix);
                    const textAfterNorm = normalizeForCheck(textAfterMatchRaw);

                    // 1. Chequeo exacto (starts with)
                    if (textAfterNorm.startsWith(suffixNorm)) {
                        return match;
                    }

                    // 2. Chequeo difuso (Fuzzy) para casos como "Café & Pan" vs "Café y Pan"
                    // Tomamos un substring del texto posterior de longitud similar al sufijo
                    const candidate = textAfterNorm.substring(0, suffixNorm.length + 2); // +2 de margen
                    if (candidate.length >= suffixNorm.length * 0.5) { // Solo si tiene una longitud comparable
                        const similarity = calculateSimilarity(suffixNorm, candidate);
                        // Si es muy similar (> 0.75), asumimos que es lo mismo y no reemplazamos
                        if (similarity > 0.75) {
                            return match;
                        }
                    }
                }

                // --- Lógica específica para el reemplazo que termina en ' -' ---
                // FIX (28-nov-2024): Capitalizar la primera letra de la palabra siguiente al guion
                // Esto asegura que "ara el estadio" con regla "ara" → "Tiendas ARA -"
                // produzca "Tiendas ARA - El Estadio" (no "Tiendas ARA - el Estadio")
                if (toValue.endsWith(' -')) {
                    let capitalizedNext = nextWordIfCaptured || '';
                    if (capitalizedNext && capitalizedNext.length > 0) {
                        capitalizedNext = capitalizedNext.charAt(0).toUpperCase() + capitalizedNext.slice(1);
                    }
                    return delimitadorPrevio + toValue + capitalizedNext;
                }
                // --- Para otros reemplazos que no terminan en ' -' ---
                return delimitadorPrevio + toValue;
            });
        }
        newText = plnFixDanglingHyphens(newText);
        return newText;
    }//aplicarReemplazosDefinidos

    //Permite crear un panel flotante en WME
    function getVisiblePlaces() {
        if (typeof W === 'undefined' || !W.map || !W.model || !W.model.venues) {// Si Waze Map Editor no está completamente cargado, retornar un array vacío
            console.warn('[WME_PLN][WARNING] Waze Map Editor no está completamente cargado.');
            return [];
        }
        // Obtener los lugares visibles en el mapa
        const venues = W.model.venues.objects;
        const visiblePlaces = Object.values(venues).filter(venue => { // Filtrar los lugares que están visibles en el mapa
            const olGeometry = venue.getOLGeometry?.();// Obtener la geometría del lugar
            const bounds = olGeometry?.getBounds?.(); // Obtener los límites del lugar
            return bounds && W.map.getExtent().intersectsBounds(bounds);
        });
        return visiblePlaces;
    }// getVisiblePlaces

    // Devuelve la lista de places sin los excluidos y el total omitido.
    function filterOutExcludedPlaces(placesList) {
        if (!Array.isArray(placesList) || placesList.length === 0)
            return { filtered: [], excludedCount: 0 };

        const filtered = [];
        let excludedCount = 0;

        for (const place of placesList) {
            try {
                const placeId = place?.getID?.();
                if (placeId && excludedPlaces.has(placeId)) {
                    excludedCount++;
                    continue;
                }
            }
            catch (err) {
                console.warn('[WME PLN] Error comprobando lugar excluido:', err);
            }
            filtered.push(place);
        }

        return { filtered, excludedCount };
    }// filterOutExcludedPlaces

    // Filtra la lista de lugares para devolver solo aquellos con update requests pendientes
    function filterByUpdateRequests(placesList) {
        if (!Array.isArray(placesList) || placesList.length === 0) {
            return { filtered: [], filteredOutCount: 0 };
        }

        const filtered = [];
        let filteredOutCount = 0;

        for (const place of placesList) {
            try {
                const placeId = place?.getID?.();
                if (!placeId) {
                    filteredOutCount++;
                    continue;
                }

                // Usar W.model.venues.getObjectById en lugar del SDK
                let venueObj = null;
                if (W?.model?.venues?.getObjectById) {
                    try {
                        venueObj = W.model.venues.getObjectById(placeId);
                    } catch (err) {
                        // Error silencioso
                    }
                }

                // Verificar si tiene update requests pendientes
                // La ruta correcta es: attributes.venueUpdateRequests
                let hasPendingEdits = false;
                if (venueObj?.attributes?.venueUpdateRequests) {
                    const venueUpdateRequests = venueObj.attributes.venueUpdateRequests;
                    if (Array.isArray(venueUpdateRequests) && venueUpdateRequests.length > 0) {
                        hasPendingEdits = true;
                    }
                }

                if (hasPendingEdits) {
                    filtered.push(place);
                } else {
                    filteredOutCount++;
                }
            } catch (err) {
                filteredOutCount++;
            }
        }

        return { filtered, filteredOutCount };
    }// filterByUpdateRequests

    //Permite renderizar los lugares en el panel flotante
    function renderPlacesInFloatingPanel(places, scanStats = {}) {
        // Limpiar la lista global de duplicados antes de llenarla de nuevo
        placesForDuplicateCheckGlobal.length = 0;
        createFloatingPanel("processing"); // Mostrar panel en modo "procesando"
        const maxPlacesToScan = parseInt(document.getElementById("maxPlacesInput")?.value || "100", 10);  //Obtiene el número total de lugares a procesar

        const totalVisibleProvided = typeof scanStats.totalVisibleCount === 'number' ? scanStats.totalVisibleCount : null;
        const excludedProvided = typeof scanStats.excludedCount === 'number' ? scanStats.excludedCount : null;
        const skipExcludedFiltering = scanStats.skipExcludedFiltering === true;

        let workingPlaces = Array.isArray(places) ? [...places] : [];
        const initialVisiblePlacesCount = totalVisibleProvided ?? workingPlaces.length;

        let excludedByInitialFilterCount = excludedProvided ?? 0;

        if (!skipExcludedFiltering) {
            const { filtered, excludedCount } = filterOutExcludedPlaces(workingPlaces);
            workingPlaces = filtered;
            if (excludedProvided === null)
                excludedByInitialFilterCount = excludedCount;
        }
        else if (excludedProvided === null) {
            excludedByInitialFilterCount = Math.max(0, initialVisiblePlacesCount - workingPlaces.length);
        }

        const filteredPlacesCount = workingPlaces.length;
        const limitedPlacesCount = Math.min(filteredPlacesCount, maxPlacesToScan);

        if (filteredPlacesCount > maxPlacesToScan) // Limitar el número de lugares a escanear
            workingPlaces = workingPlaces.slice(0, maxPlacesToScan); // Limitar el número de places a escanear

        places = workingPlaces;
        const lockRankEmojis = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣"]; // Definir los emojis de nivel de bloqueo
        // Permite obtener el nombre de la categoría de un lugar, ya sea del modelo antiguo o del SDK

        function getPlaceCategoryName(venueFromOldModel, venueSDKObject) { // Acepta ambos tipos de venue
            let categoryId = null;
            let categoryName = null;
            // Intento 1: Usar el venueSDKObject si está disponible y tiene la info
            if (venueSDKObject) {
                if (venueSDKObject.mainCategory && venueSDKObject.mainCategory.id) {// Si venueSDKObject tiene mainCategory con ID
                    categoryId = venueSDKObject.mainCategory.id; // source = "SDK (mainCategory.id)";
                    //Limpiar comillas aquí
                    if (typeof categoryId === 'string') categoryId = categoryId.replace(/'/g, '');
                    if (venueSDKObject.mainCategory.name) // Si mainCategory tiene nombre
                        categoryName = venueSDKObject.mainCategory.name;// source = "SDK (mainCategory.name)";
                    if (typeof categoryName === 'string') categoryName = categoryName.replace(/'/g, '');
                }
                else if (Array.isArray(venueSDKObject.categories) && venueSDKObject.categories.length > 0) {// Si venueSDKObject tiene un array de categorías y al menos una categoría
                    const firstCategorySDK = venueSDKObject.categories[0]; // source = "SDK (categories[0])";
                    if (typeof firstCategorySDK === 'object' && firstCategorySDK.id) {// Si la primera categoría es un objeto con ID
                        categoryId = firstCategorySDK.id;
                        // Limpiar comillas aquí
                        if (typeof categoryId === 'string') categoryId = categoryId.replace(/'/g, '');

                        if (firstCategorySDK.name)  // Si la primera categoría tiene nombre
                            categoryName = firstCategorySDK.name;
                        if (typeof categoryName === 'string') categoryName = categoryName.replace(/'/g, '');
                    }
                    else if (typeof firstCategorySDK === 'string') // Si la primera categoría es una cadena (nombre de categoría)
                    {
                        categoryName = firstCategorySDK;
                        if (typeof categoryName === 'string') categoryName = categoryName.replace(/'/g, '');
                    }
                }
                else if (venueSDKObject.primaryCategoryID) {
                    categoryId = venueSDKObject.primaryCategoryID;
                    if (typeof categoryName === 'string') categoryName = categoryName.replace(/'/g, '');
                }
            }
            if (categoryName) {// Si se obtuvo el nombre de categoría del SDK

                return categoryName;
            }
            // Intento 2: Usar W.model si no se obtuvo del SDK
            if (!categoryId && venueFromOldModel && venueFromOldModel.attributes && Array.isArray(venueFromOldModel.attributes.categories) && venueFromOldModel.attributes.categories.length > 0)
                categoryId = venueFromOldModel.attributes.categories[0];
            if (!categoryId)// Si no se pudo obtener el ID de categoría de ninguna fuente
                return "Sin categoría";
            let categoryObjWModel = null; // Intentar obtener el objeto de categoría del modelo Waze
            if (typeof W !== 'undefined' && W.model) {// Si Waze Map Editor está disponible
                if (W.model.venueCategories && typeof W.model.venueCategories.getObjectById === "function") // Si venueCategories está disponible en W.model
                    categoryObjWModel = W.model.venueCategories.getObjectById(categoryId);
                if (!categoryObjWModel && W.model.categories && typeof W.model.categories.getObjectById === "function") // Si no se encontró en venueCategories, intentar en categories
                    categoryObjWModel = W.model.categories.getObjectById(categoryId);
            }
            if (categoryObjWModel && categoryObjWModel.attributes && categoryObjWModel.attributes.name) {// Si se encontró el objeto de categoría en W.model
                let nameToReturn = categoryObjWModel.attributes.name;
                //  Limpiar comillas aquí
                if (typeof nameToReturn === 'string') nameToReturn = nameToReturn.replace(/'/g, '');
                return nameToReturn;
            }
            if (typeof categoryId === 'number' || (typeof categoryId === 'string' && categoryId.trim() !== '')) {// Si no se pudo obtener el nombre de categoría de ninguna fuente, devolver el ID
                return `${categoryId}`; // Devuelve el ID si no se encuentra el nombre.
            }
            return "Sin categoría";
        }//getPlaceCategoryName

        // Convierte un lugar de tipo Punto a Área (Polígono)
        // TODO: TEMPORALMENTE DESHABILITADO - La API de WME cambió y necesita investigación
        async function convertPointToArea(venueId) {
            // Mostrar mensaje informativo en lugar de intentar la conversión
            if (typeof plnToast === 'function') {
                plnToast("⚠️ Conversión punto→área temporalmente no disponible (API en revisión)", 3000);
            }
            console.warn("[WME PLN] convertPointToArea: Funcionalidad temporalmente deshabilitada - API de geometría WME en revisión");
            return;
            
            /* CÓDIGO ORIGINAL COMENTADO PARA INVESTIGACIÓN:
            try {
                if (typeof W === 'undefined' || !W.model || !W.model.venues) return;
                const venue = W.model.venues.getObjectById(venueId);
                if (!venue) {
                    console.error("[WME PLN] Venue no encontrado:", venueId);
                    return;
                }

                const olGeometry = venue.getOLGeometry();
                if (!olGeometry) {
                    console.error("[WME PLN] No se pudo obtener la geometría OL del venue");
                    return;
                }
                
                const center = olGeometry.getCentroid();
                const x = center.x;
                const y = center.y;
                const offset = 10;

                // INVESTIGAR: ¿Qué formato espera UpdateFeatureGeometry ahora?
                // - OpenLayers.Geometry.Polygon?
                // - GeoJSON object?
                // - Algo más?
                
            } catch (e) {
                console.error("[WME PLN] Error converting to area", e);
            }
            */
        }

        //Permite obtener el tipo de lugar (área o punto) y su icono
        function getPlaceTypeInfo(venueSDKObject) // <--- AHORA RECIBE venueSDKObject
        {
            let isArea = false;
            let icon = "⊙"; // Icono por defecto para punto
            let title = "Punto"; // Título por defecto para punto

            if (venueSDKObject && venueSDKObject.geometry && venueSDKObject.geometry.type) {
                const geometryType = venueSDKObject.geometry.type;
                if (geometryType === 'Polygon' || geometryType === 'MultiPolygon') {
                    isArea = true;
                    icon = "⭔"; // Icono para área
                    title = "Área"; // Título para área
                }
                // Para otros tipos como 'Point', 'LineString', etc., se mantienen los valores por defecto (Punto).
            }
            return { isArea, icon, title };
        }// getPlaceTypeInfo

        //Permite procesar un lugar y generar un objeto con sus detalles
        function shouldForceSuggestionForReview(word) {
            if (typeof word !== 'string') // Si la palabra no es una cadena, no forzar sugerencia por esta regla
                return false;
            const lowerWord = word.toLowerCase(); // Convertir la palabra a minúsculas para evitar problemas de mayúsculas/minúsculas
            const hasTilde = /[áéíóúÁÉÍÓÚ]/.test(word); // Verificar si la palabra tiene alguna tilde (incluyendo mayúsculas acentuadas)
            if (!hasTilde)  // Si no tiene tilde, no forzar sugerencia por esta regla
                return false; // Si no hay tilde, no forzar sugerencia por esta regla
            const problematicSubstrings = ['c', 's', 'x', 'cc', 'sc', 'cs', 'g', 'j', 'z', 'ñ']; // Lista de patrones de letras/combinaciones que, junto con una tilde, fuerzan la sugerencia (insensible a mayúsculas debido a lowerWord)
            for (const sub of problematicSubstrings) {// Verificar si la palabra contiene alguna de las letras/combinaciones problemáticas
                if (lowerWord.includes(sub))
                    return true; // Tiene tilde y una de las letras/combinaciones problemáticas
            }
            return false; // Tiene tilde, pero no una de las letras/combinaciones problemáticas
        }//shouldForceSuggestionForReview

        // Procesa un lugar y genera un objeto con sus detalles
        async function getPlaceCityInfo(venueFromOldeModel, venueSDKObject, currentCategoryKey = null) {
            let cityName = null;
            let source = "desconocida";

            // --- Función auxiliar para obtener el nombre de la ciudad desde un cityID ---
            function getCityNameFromId(cityId) {
                if (!cityId || typeof W === 'undefined' || !W.model || !W.model.cities || !W.model.cities.getObjectById) {
                    return null;
                }
                const cityObject = W.model.cities.getObjectById(cityId);
                if (cityObject && cityObject.attributes && typeof cityObject.attributes.name === 'string' && cityObject.attributes.name.trim() !== '') {
                    return cityObject.attributes.name.trim();
                }
                return null;
            }

            try {
                // --- INTENTO 1: OBTENER CIUDAD A TRAVÉS DEL streetID (EL MÉTODO MÁS COMÚN) ---
                const streetID = venueFromOldeModel?.attributes?.streetID || venueSDKObject?.address?.street?.id || null;

                if (streetID) {
                    const streetObject = W.model.streets.getObjectById(streetID);
                    if (streetObject && streetObject.attributes && streetObject.attributes.cityID) {
                        cityName = getCityNameFromId(streetObject.attributes.cityID);
                        if (cityName) {
                            source = "derivada de la calle";
                        }
                    }
                }

                // --- INTENTO 2: OBTENER CIUDAD DESDE EL cityID DIRECTO (FALLBACK) ---
                if (!cityName) {
                    const cityID = venueFromOldeModel?.attributes?.cityID || venueSDKObject?.address?.city?.id || null;
                    if (cityID) {
                        cityName = getCityNameFromId(cityID);
                        if (cityName) {
                            source = "explícita";
                        }
                    }
                }

            } catch (e) {
                console.error("[WME PLN] Error crítico en getPlaceCityInfo:", e);
                cityName = null;
            }

            // ✨ LÓGICA ESPECIAL PARA RÍOS/ARROYOS (RIVER_STREAM)
            if (currentCategoryKey === 'RIVER_STREAM') {
                if (cityName) {
                    // ⚠️ Río/Arroyo NO debe tener ciudad
                    return {
                        icon: '⚠️',
                        title: `⚠️ Río/Arroyo no debe tener ciudad (${cityName}) - Click para limpiar`,
                        hasCity: true,
                        hasInvalidCity: true  // Nueva propiedad para identificar este caso
                    };
                } else {
                    // ✅ Río/Arroyo sin ciudad = correcto
                    return {
                        icon: '✅',
                        title: '✓ Río/Arroyo sin ciudad (Correcto)',
                        hasCity: false,
                        hasValidNoCity: true  // Nueva propiedad para identificar este caso
                    };
                }
            }

            // --- Lógica final para devolver el ícono y el título (PARA OTROS TIPOS) ---
            if (cityName) {
                return {
                    icon: '✅',
                    title: `Ciudad: ${cityName} (${source})`,
                    hasCity: true
                };
            } else {
                return {
                    icon: '🚩',
                    title: "Sin ciudad asignada. Haz clic para buscar y asignar la más cercana.",
                    hasCity: false
                };
            }
        }// getPlaceCityInfo

        /*async function getPlaceCityInfo(venueFromOldModel, venueSDKObject)
        {
            let hasExplicitCity = false; // Indica si hay una ciudad explícita definida
            let explicitCityName = null; // Nombre de la ciudad explícita, si se encuentra
            let hasStreetInfo = false; // Indica si hay información de calle disponible
            let cityAssociatedWithStreet = null; // Nombre de la ciudad asociada a la calle, si se encuentra
            // 1. Check for EXPLICIT city  SDK
            if (venueSDKObject && venueSDKObject.address) {
            //console.log("[DEBUG] venueSDKObject.address:", venueSDKObject.address);

                if (venueSDKObject.address.city && typeof venueSDKObject.address.city.name === 'string' && venueSDKObject.address.city.name.trim() !== '') {
                    // Si hay una ciudad explícita en el SDK
                    explicitCityName = venueSDKObject.address.city.name.trim(); // Nombre de la ciudad explícita
                    hasExplicitCity = true; // source = "SDK (address.city.name)";
                //console.log("[DEBUG] Ciudad explícita encontrada en SDK (address.city.name):", explicitCityName);
                } else if (typeof venueSDKObject.address.cityName === 'string' && venueSDKObject.address.cityName.trim() !== '') {
                    // Si hay una ciudad explícita en el SDK (cityName)
                    explicitCityName = venueSDKObject.address.cityName.trim(); // Nombre de la ciudad explícita
                    hasExplicitCity = true; // source = "SDK (address.cityName)";
                //console.log("[DEBUG] Ciudad explícita encontrada en SDK (address.cityName):", explicitCityName);
                } else {
                //console.log("[DEBUG] No se encontró ciudad explícita en SDK.");
                }
            }

            if (!hasExplicitCity && venueFromOldModel && venueFromOldModel.attributes)
            {
                //console.log("[DEBUG] venueFromOldModel.attributes:", venueFromOldModel.attributes);

                    const cityID = venueFromOldModel.attributes.cityID;
                //console.log("[DEBUG] cityID del modelo antiguo:", cityID);

                if (cityID && typeof W !== 'undefined' && W.model && W.model.cities && W.model.cities.getObjectById)
             {
                    //console.log("[DEBUG] Intentando obtener el objeto de ciudad con cityID:", cityID);

                        const cityObject = W.model.cities.getObjectById(cityID); // Obtener el objeto de ciudad del modelo Waze
                    //console.log("[DEBUG] cityObject obtenido:", cityObject);

                        if (cityObject && cityObject.attributes && typeof cityObject.attributes.name === 'string' && cityObject.attributes.name.trim() !== '') {
                            // Si el objeto de ciudad tiene un nombre válido
                            explicitCityName = cityObject.attributes.name.trim(); // Nombre de la ciudad explícita
                            hasExplicitCity = true; // source = "W.model.cities (cityID)";
                        //console.log("[DEBUG] Ciudad explícita encontrada en modelo antiguo (cityID):", explicitCityName);
                        } else {
                        //console.log("[DEBUG] cityObject no tiene un nombre válido.");
                        }
                    }
                    else
                    {
                    //console.log("[DEBUG] cityID no válido o W.model.cities.getObjectById no disponible.");
                    }
            }

            // 2. Check for STREET information (and any city derived from it) // SDK street check
            if (venueSDKObject && venueSDKObject.address)
                if ((venueSDKObject.address.street && typeof venueSDKObject.address.street.name === 'string' && venueSDKObject.address.street.name.trim() !== '') ||
                    (typeof venueSDKObject.address.streetName === 'string' && venueSDKObject.address.streetName.trim() !== ''))
                    hasStreetInfo = true; // source = "SDK (address.street.name or streetName)";
            if (venueFromOldModel && venueFromOldModel.attributes && venueFromOldModel.attributes.streetID)
            {// Old Model street check (if not found via SDK or to supplement)
                hasStreetInfo = true; // Street ID exists in old model
                const streetID = venueFromOldModel.attributes.streetID; // Obtener el streetID del modelo antiguo
                if (typeof W !== 'undefined' && W.model && W.model.streets && W.model.streets.getObjectById)
                {// Si hay un streetID en el modelo antiguo
                    const streetObject = W.model.streets.getObjectById(streetID); // Obtener el objeto de calle del modelo Waze
                    if (streetObject && streetObject.attributes && streetObject.attributes.cityID)
                    {// Si el objeto de calle tiene un cityID asociado
                        const cityIDFromStreet = streetObject.attributes.cityID;// Obtener el cityID de la calle
                        if (W.model.cities && W.model.cities.getObjectById)
                        {// Si W.model.cities está disponible y tiene el método getObjectById
                            const cityObjectFromStreet = W.model.cities.getObjectById(cityIDFromStreet);// Obtener el objeto de ciudad asociado a la calle
                            // Si el objeto de ciudad tiene un nombre válido
                            if (cityObjectFromStreet && cityObjectFromStreet.attributes && typeof cityObjectFromStreet.attributes.name === 'string' && cityObjectFromStreet.attributes.name.trim() !== '')
                                cityAssociatedWithStreet = cityObjectFromStreet.attributes.name.trim(); // Nombre de la ciudad asociada a la calle
                        }
                    }
                }
            }
            // --- 3. Determine icon, title, and returned hasCity based on user's specified logic ---
            let icon;
            let title;
            const returnedHasCityBoolean = hasExplicitCity; // To be returned, indicates if an *explicit* city is set.
            const hasAnyAddressInfo = hasExplicitCity || hasStreetInfo; // Determina si hay alguna información de dirección (ciudad explícita o calle).
            if (hasAnyAddressInfo)
            {// Si hay información de dirección (ciudad explícita o calle)
                if (hasExplicitCity)
                {
                    // Tiene ciudad explícita
                    icon = "🏙️";
                    title = `Ciudad: ${explicitCityName}`;
                }
                else if (cityAssociatedWithStreet)
                {
                    // No tiene ciudad explícita, pero la calle sí está asociada a ciudad
                    icon = "🏙️";
                    title = `Ciudad (por calle): ${cityAssociatedWithStreet}`;
                }
                else {
                    // No hay ciudad explícita ni ciudad por calle
                    icon = "🚫";
                    title = "Sin ciudad asignada";
                }
                return {
                    icon: icon || "❓",
                    title: title || "Info no disponible",
                    hasCity: (hasExplicitCity || !!cityAssociatedWithStreet) // Ahora true si tiene ciudad por calle
                };
            }
            else
            { // No tiene ni ciudad explícita ni información de calle
                icon = "🚫";
                title = "El campo dirección posee inconsistencias"; // Título para "no tiene ciudad ni calle"
            }
            return {
                icon: icon || "❓", // Usar '?' si icon es undefined/null/empty
                title: title || "Info no disponible", // Usar "Info no disponible" si title es undefined/null/empty
                hasCity: returnedHasCityBoolean || false // Asegurarse de que sea un booleano
            };
        }*///getPlaceCityInfo

        //Renderizar barra de progreso en el TAB PRINCIPAL justo después del slice
        const tabOutput = document.querySelector("#wme-normalization-tab-output");
        if (tabOutput) {// Si el tab de salida ya existe, limpiar su contenido
            // Reiniciar el estilo del mensaje en el tab al valor predeterminado
            tabOutput.style.color = "#000";
            tabOutput.style.fontWeight = "normal";
            // Crear barra de progreso visual
            const progressBarWrapperTab = document.createElement("div");
            progressBarWrapperTab.style.margin = "10px 0";
            progressBarWrapperTab.style.marginTop = "10px";
            progressBarWrapperTab.style.height = "18px";
            progressBarWrapperTab.style.backgroundColor = "transparent";
            // Crear el contenedor de la barra de progreso
            const progressBarTab = document.createElement("div");
            progressBarTab.style.height = "100%";
            progressBarTab.style.width = "0%";
            progressBarTab.style.backgroundColor = "#007bff";
            progressBarTab.style.transition = "width 0.2s";
            progressBarTab.id = "progressBarInnerTab";
            progressBarWrapperTab.appendChild(progressBarTab);
            // Crear texto de progreso
            const progressTextTab = document.createElement("div");
            progressTextTab.style.fontSize = "12px";
            progressTextTab.style.marginTop = "5px";
            progressTextTab.id = "progressBarTextTab";
            tabOutput.appendChild(progressBarWrapperTab);
            tabOutput.appendChild(progressTextTab);
        }
        // Asegurar que la barra de progreso en el tab se actualice desde el principio
        const progressBarInnerTab = document.getElementById("progressBarInnerTab"); // Obtener la barra de progreso del tab
        const progressBarTextTab = document.getElementById("progressBarTextTab"); // Obtener el texto de progreso del tab
        if (progressBarInnerTab && progressBarTextTab) {// Si ambos elementos existen, reiniciar su estado
            progressBarInnerTab.style.width = "0%";
            progressBarTextTab.textContent = `Progreso: 0% (0/${places.length})`; // Reiniciar el texto de progreso
        }
        // --- PANEL FLOTANTE: limpiar y preparar salida ---
        const output = document.querySelector("#wme-place-inspector-output");//
        if (!output) {// Si el panel flotante no está disponible, mostrar un mensaje de error
            console.error("[WME_PLN][ERROR]❌ Panel flotante no está disponible");
            return;
        }
        output.innerHTML = ""; // Limpia completamente el contenido del panel flotante
        if (window.WME_PLN_SDK && window.WME_PLN_SDK.UI && typeof window.WME_PLN_SDK.UI.createElement === 'function') {
            // Usar el SDK de WME para crear el div de procesamiento
            const sdkDiv = window.WME_PLN_SDK.UI.createElement('div', {
                style: {
                    display: 'flex',
                    alignItems: 'center',
                    gap: '10px'
                }
            });

            const spinner = window.WME_PLN_SDK.UI.createElement('span', {
                className: 'loader-spinner',
                style: {
                    width: '16px',
                    height: '16px',
                    border: '2px solid #ccc',
                    borderTop: '2px solid #007bff',
                    borderRadius: '50%',
                    animation: 'spin 0.8s linear infinite'
                }
            });

            const infoDiv = window.WME_PLN_SDK.UI.createElement('div');
            const processingText = window.WME_PLN_SDK.UI.createElement('div', {
                id: 'processingText'
            });
            processingText.textContent = 'Procesando lugares visibles';

            const dotsSpan = window.WME_PLN_SDK.UI.createElement('span', { className: 'dots' });
            dotsSpan.textContent = '.';

            processingText.appendChild(dotsSpan);

            const processingStep = window.WME_PLN_SDK.UI.createElement('div', {
                id: 'processingStep',
                style: {
                    fontSize: '13px',
                    color: '#555'
                }
            });
            processingStep.textContent = 'Inicializando escaneo...';

            infoDiv.appendChild(processingText);
            infoDiv.appendChild(processingStep);

            sdkDiv.appendChild(spinner);
            sdkDiv.appendChild(infoDiv);

            output.appendChild(sdkDiv);
        } else {
            // Fallback al HTML tradicional si el SDK no está disponible
            output.innerHTML = "<div style='display:flex; align-items:center; gap:10px;'><span class='loader-spinner' style='width:16px; height:16px; border:2px solid #ccc; border-top:2px solid #007bff; border-radius:50%; animation:spin 0.8s linear infinite;'></span><div><div id='processingText'>Procesando lugares visibles<span class='dots'>.</span></div><div id='processingStep' style='font-size:13px; color:#555;'>Inicializando escaneo...</div></div></div>";
        }
        //output.innerHTML = "<div style='display:flex; align-items:center; gap:10px;'><span class='loader-spinner' style='width:16px; height:16px; border:2px solid #ccc; border-top:2px solid #007bff; border-radius:50%; animation:spin 0.8s linear infinite;'></span><div><div id='processingText'>Procesando lugares visibles<span class='dots'>.</span></div><div id='processingStep' style='font-size:13px; color:#555;'>Inicializando escaneo...</div></div></div>";
        // Asegurar que el panel flotante tenga un alto mínimo
        const processingStepLabel = document.getElementById("processingStep");
        if (processingStepLabel) {
            const infoParts = [`Visibles: ${initialVisiblePlacesCount}`];
            // Agregar información adicional si está disponible
            if (excludedByInitialFilterCount > 0)
                infoParts.push(`Excluidos que serán omitidos: ${excludedByInitialFilterCount}`);
            infoParts.push(`Restantes tras excluir: ${filteredPlacesCount}`);
            infoParts.push(`Se analizarán: ${limitedPlacesCount}`);
            processingStepLabel.textContent = infoParts.join(' · ');
        }
        // Animación de puntos suspensivos
        const dotsSpan = output.querySelector(".dots");
        if (dotsSpan) {// Si el span de puntos existe, iniciar la animación de puntos
            const dotStates = ["", ".", "..", "..."];
            let dotIndex = 0;
            window.processingDotsInterval = setInterval(() => {
                dotIndex = (dotIndex + 1) % dotStates.length;
                dotsSpan.textContent = dotStates[dotIndex];
            }, 500);
        }
        output.style.height = "calc(55vh - 40px)";
        if (!places.length) {// Si no hay places, mostrar mensaje y salir
            output.appendChild(document.createTextNode("No hay places visibles para analizar."));
            const existingOverlay = document.getElementById("scanSpinnerOverlay");
            if (existingOverlay)// Si ya existe un overlay de escaneo, removerlo
                existingOverlay.remove();
            
            // --- FIX: Resetear UI para evitar que se quede pegado ---
            if (window.processingDotsInterval) {
                clearInterval(window.processingDotsInterval);
                window.processingDotsInterval = null;
            }
            if (scanBtn) {
             scanTitle.innerText = "Análisis de Nombres de Places";
             scanBtn.innerHTML = "Escanear";
             scanBtn.disabled = false;
             scanBtn.style.backgroundColor = "#2196F3"; // Color original
             scanBtn.style.cursor = "pointer";
            }
            // ----------------------------------------------------
            return;
        }
        // Procesamiento incremental para evitar congelamiento
        let inconsistents = []; // Array para almacenar inconsistencias encontradas
        let index = 0; // Índice para iterar sobre los lugares
        const scanBtn = document.querySelector("button[type='button']"); // Remover ícono de ✔ previo si existe
        if (scanBtn) {// Si el botón de escaneo existe, remover el ícono de ✔ previo si está presente
            const existingCheck = scanBtn.querySelector("span");
            if (existingCheck) // Si hay un span dentro del botón, removerlo
                existingCheck.remove();
        }
        // --- Sugerencias por palabra global para toda la ejecución ---
        let sugerenciasPorPalabra = {};
        // Convertir excludedWords a array solo una vez al inicio del análisis, seguro ante undefined
        const excludedArray = (typeof excludedWords !== "undefined" && Array.isArray(excludedWords)) ? excludedWords : (typeof excludedWords !== "undefined" ? Array.from(excludedWords) : []);

        async function processNextPlace() {
            // ID del lugar actual que se está procesando
            const currentPlaceForLog = places[index];
            const currentVenueId = currentPlaceForLog ? currentPlaceForLog.getID() : 'ID Desconocido';

            //console.log(`\n[WME PLN - processNextPlace] --- INICIANDO PROCESAMIENTO PARA LUGAR ID: ${currentVenueId} (Índice: ${index}) ---`); // <--- USAR currentVenueId
            //console.log(`[WME PLN - processNextPlace] Total de lugares a procesar: ${places.length}`);

            // Inicialización de variables de estado
            let cityInfo = {
                icon: "❓",
                title: "Información de ciudad no disponible",
                hasCity: false
            };
            let resolvedEditorName = "Desconocido";
            let lastEditorIdForComparison = null;
            let currentLoggedInUserId = currentGlobalUserInfo.id;
            let wasEditedByMe = false;
            let shouldSkipThisPlace = false;
            let skipReasonLog = "";

            // Declaración de avoidMyEdits, typeInfo, areaMeters al inicio
            const avoidMyEdits = document.getElementById("chk-avoid-my-edits")?.checked ?? false; // <-- MOVIDO AQUÍ
            let typeInfo = { isArea: false, icon: "⊙", title: "Punto" };
            let areaMeters = null;

            // --- Obtener venueSDK lo antes posible ---
            let venueSDK = null;
            if (wmeSDK && wmeSDK.DataModel && wmeSDK.DataModel.Venues && wmeSDK.DataModel.Venues.getById)
                try {
                    venueSDK = await wmeSDK.DataModel.Venues.getById({ venueId: currentVenueId });
                }
                catch (sdkError) {
                    console.error(`[WME_PLN] Error al obtener venueSDK para ID ${currentVenueId}:`, sdkError);
                }

            let originalNameRaw;
            if (venueSDK && venueSDK.name)
                originalNameRaw = venueSDK.name;
            else
                originalNameRaw = currentPlaceForLog && currentPlaceForLog.attributes ? (currentPlaceForLog.attributes.name?.value || currentPlaceForLog.attributes.name || '') : '';
            originalNameRaw = originalNameRaw.trim();
            const nameForProcessing = removeEmoticons(originalNameRaw);

            // Asegurarse de que typeInfo y areaMeters se obtengan si venueSDK está disponible
            if (venueSDK) {
                typeInfo = getPlaceTypeInfo(venueSDK);
                areaMeters = calculateAreaMeters(venueSDK);
            }

            //console.log(`[WME PLN - processNextPlace] Nombre Original Raw (de Waze/SDK): "${originalNameRaw}"`);
            //console.log(`[WME PLN - processNextPlace] Nombre para Procesamiento (sin emojis): "${nameForProcessing}"`);
            //console.log(`[WME PLN - DEBUG] Place ID: ${currentVenueId}, Name: "${originalNameRaw}"`);
            //console.log(`[WME PLN - DEBUG]   -> typeInfo:`, typeInfo);
            //console.log(`[WME PLN - DEBUG]   -> areaMeters:`, areaMeters);

            //Obtener el ID del usuario actual (si está disponible globalmente de forma confiable)
            const useFullPipeline = true; // Siempre usar el pipeline completo para este flujo
            const applyGeneralReplacements = useFullPipeline || (document.getElementById("chk-general-replacements")?.checked ?? true); // Aplicar reemplazos generales por defecto
            const checkExcludedWords = useFullPipeline || (document.getElementById("chk-check-excluded")?.checked ?? false); // Verificar palabras excluidas por defecto
            const checkDictionaryWords = true;// Siempre verificar palabras del diccionario para este flujo
            const restoreCommas = document.getElementById("chk-restore-commas")?.checked ?? false;// Restaurar comas por defecto
            const similarityThreshold = parseFloat(document.getElementById("similarityThreshold")?.value || "85") / 100;//  Umbral de similitud por defecto (convertido a porcentaje)

            // 2. Condición de salida principal (todos los lugares procesados)
            if (index >= places.length) {

                finalizeRender(inconsistents, places, sugerenciasPorPalabra);
                return;
            }
            // 1. Verificar si el lugar actual es válido y tiene un ID
            const venueFromOldModel = places[index];
            const currentVenueNameObj = venueFromOldModel?.attributes?.name;
            const nameValue = typeof currentVenueNameObj === 'object' && currentVenueNameObj !== null && typeof currentVenueNameObj.value === 'string' ? currentVenueNameObj.value.trim() !== ''
                ? currentVenueNameObj.value : undefined : typeof currentVenueNameObj === 'string' && currentVenueNameObj.trim() !== '' ? currentVenueNameObj : undefined;
            //
            // Validación mejorada: Detectar lugares sin nombre
            // En lugar de saltar estos lugares, los procesamos y marcamos especialmente
            const hasEmptyName = !nameForProcessing || nameForProcessing.trim() === '';

            if (hasEmptyName) {
                // Verificar si es RESIDENCE_HOME para saltarlo
                const categories = venueFromOldModel?.attributes?.categories || [];
                if (categories.includes('RESIDENCE_HOME')) {
                    // console.log(`[WME_PLN] Saltando lugar residencial sin nombre: ${currentVenueId}`);
                    index++;
                    setTimeout(() => processNextPlace(), 0);
                    return;
                }

                console.warn(`[WME_PLN] ⚠️ Lugar sin nombre detectado en el índice ${index}:`, {
                    venueId: currentVenueId,
                    categories: categories,
                    originalNameRaw: originalNameRaw
                });
            }

            // 3. Continuar procesando incluso si no tiene nombre (para mostrarlo en el panel)

            // --- NUEVO: Filtro por Nivel de Bloqueo ---
            const maxLockRankInput = document.getElementById("maxLockRankInput");
            if (maxLockRankInput && maxLockRankInput.value) {
                const maxRank = parseInt(maxLockRankInput.value, 10);
                const placeLockRank = (venueFromOldModel && venueFromOldModel.attributes && venueFromOldModel.attributes.lockRank)
                    ? venueFromOldModel.attributes.lockRank
                    : 0; // Asumir 0 si no tiene lockRank definido

                // El lockRank en WME suele ser 0-based (0=L1, 1=L2, etc) o 1-based dependiendo de la propiedad.
                // venue.attributes.lockRank suele ser 0-based (0 es Auto/L1).
                // El input del usuario es 1-based (1 a 6).
                // Ajustamos: si el usuario pone 1, quiere ver hasta lockRank 0.
                // Si pone 2, hasta lockRank 1.
                // Entonces: si (placeLockRank + 1) > maxRank -> Saltar.

                if (!isNaN(maxRank) && (placeLockRank + 1) > maxRank) {
                    // Saltar silenciosamente o con log debug
                    // console.log(`[WME_PLN] Saltando lugar ${currentVenueId} por nivel de bloqueo (${placeLockRank + 1} > ${maxRank})`);
                    index++;
                    setTimeout(() => processNextPlace(), 0);
                    return;
                }
            }

            // Se usa la variable limpia de emojis para generar el nombre normalizado.
            const originalName = nameForProcessing; // 'originalName' ahora es explícitamente para el pipeline de procesamiento.
            const normalizedName = processPlaceName(originalName); // Normalizar el nombre del lugar
            //console.log(`[WME PLN - processNextPlace] Nombre Original para pipeline (sin emojis): "${originalName}"`);
            //console.log(`[WME PLN - processNextPlace] Nombre Normalizado (después de processPlaceName): "${normalizedName}"`);

            // 4. Verificar si el nombre ya está normalizado (sin emojis) y no requiere cambios

            const { lat: placeLat, lon: placeLon } = getPlaceCoordinates(venueFromOldModel, venueSDK); // Obtener las coordenadas del lugar
            // `isNameEffectivelyNormalized` debe calcularse DESPUÉS de `processPlaceName` y `aplicarReemplazosGenerales`
            // y todas las transformaciones que definen el `suggestedName` final.
            // Por lo tanto, esta línea no debería estar aquí para definir la variable `isNameEffectivelyNormalized` globalmente
            // o para las condiciones de salto que se evalúan ANTES de que `suggestedName` sea final.
            // Asegúrate de que `isNameEffectivelyNormalized` se calcula una vez al final, como en código original.

            // Lógica unificada y robusta para obtener resolvedEditorName, lastEditorIdForComparison y calcular wasEditedByMe
            resolvedEditorName = "Desconocido"; // Reinicializar para cada place
            lastEditorIdForComparison = null; // Reinicializar para cada place
            // Obtener el ID del usuario actual de forma robusta
            if (venueSDK && venueSDK.modificationData) {
                const updatedByDataFromSDK = venueSDK.modificationData.updatedBy;
                if (typeof updatedByDataFromSDK === 'string' && updatedByDataFromSDK.trim() !== '') {
                    resolvedEditorName = updatedByDataFromSDK; // El nombre del editor es una cadena
                }
                else if (typeof updatedByDataFromSDK === 'number') {
                    lastEditorIdForComparison = updatedByDataFromSDK; // El ID numérico es la fuente principal
                    resolvedEditorName = `ID ${updatedByDataFromSDK}`; // Nombre temporal
                    if (W && W.model && W.model.users) {
                        const userObjectW = W.model.users.getObjectById(updatedByDataFromSDK);
                        if (userObjectW && userObjectW.userName) { // Si el usuario está en el modelo Waze
                            resolvedEditorName = userObjectW.userName; // Obtener nombre real del usuario si está en el modelo
                        }
                    }
                }
            }
            else if (venueFromOldModel && venueFromOldModel.attributes && (venueFromOldModel.attributes.updatedBy !== null && venueFromOldModel.attributes.updatedBy !== undefined)) {
                // Fallback al modelo antiguo si el SDK no dio datos de editor
                const oldModelUpdatedBy = venueFromOldModel.attributes.updatedBy;
                lastEditorIdForComparison = oldModelUpdatedBy; // El ID numérico es la fuente principal
                resolvedEditorName = `ID ${oldModelUpdatedBy}`; // Nombre temporal
                if (W && W.model && W.model.users) {
                    const userObjectW = W.model.users.getObjectById(oldModelUpdatedBy);
                    if (userObjectW && userObjectW.userName) {
                        resolvedEditorName = userObjectW.userName; // Obtener nombre real del usuario si está en el modelo
                    }
                }
            }
            else {
                resolvedEditorName = "N/D"; // No hay información de editor
            }
            // Calcular wasEditedByMe de forma robusta aquí mismo
            wasEditedByMe = false; // Resetear para este place
            if (currentLoggedInUserId !== null && currentLoggedInUserId !== undefined && resolvedEditorName !== "N/D") { // Solo si tenemos un nombre de usuario logueado y el resolvedEditorName no es N/D
                if (lastEditorIdForComparison !== null && lastEditorIdForComparison !== undefined && typeof lastEditorIdForComparison === 'number') {
                    // PRIORIDAD 1: Comparar IDs numéricos si ambos están disponibles y son válidos
                    if (typeof currentLoggedInUserId === 'number') { // Si el ID también es numérico
                        wasEditedByMe = (lastEditorIdForComparison === currentLoggedInUserId); //
                    }
                    else { // Si el ID es string (userName) y el del place es number
                        wasEditedByMe = (String(lastEditorIdForComparison) === currentLoggedInUserId); // Convertir solo el del place a string
                    }
                }
                else if (resolvedEditorName && typeof resolvedEditorName === 'string') {
                    // PRIORIDAD 2: Si no hay ID numérico del editor del place, pero sí su nombre, comparar por nombre
                    wasEditedByMe = (resolvedEditorName.toLowerCase() === String(currentLoggedInUserId).toLowerCase()); //
                }
            }

            //console.log(`[WME_PLN] Nombre sugerido final: "${suggestedName}"`); // Comentario opcional.

            // --- Lógica para generar sugerencias del diccionario ---
            const originalWords = originalName.split(/\s+/).filter(word => word.length > 0);
            let sugerenciasLugar = {};
            let suggestedName = normalizedName; // Usa el resultado correcto y final de processPlaceName
            const suggestedNameWords = suggestedName.split(/\s+/).filter(word => word.length > 0);

            const processedWordKeys = new Set();
            originalWords.forEach((originalWord, wordIndex) => {
                //console.log(`\n[WME PLN - processNextPlace] Procesando palabra original: "${originalWord}" (Índice: ${wordIndex})`);
                if (!originalWord) return;

                const normalizedKeyForDuplicates = removeDiacritics(originalWord.toLowerCase());
                if (processedWordKeys.has(normalizedKeyForDuplicates)) {
                    return;
                }
                processedWordKeys.add(normalizedKeyForDuplicates);

                const lowerOriginalWord = originalWord.toLowerCase();
                const cleanedLowerNoDiacritics = removeDiacritics(lowerOriginalWord);
                //console.log(`[WME PLN - processNextPlace] lowerOriginalWord: "${lowerOriginalWord}", cleanedLowerNoDiacritics: "${cleanedLowerNoDiacritics}"`);

                let tildeCorrectionSuggested = false; // Bandera para saber si ya sugerimos tilde para esta palabra

                const currentSuggestedWord = suggestedNameWords[wordIndex] || '';
                const lowerCurrentSuggestedWord = currentSuggestedWord.toLowerCase();
                const currentSuggestedWordHasDiacritics = /[áéíóúÁÉÍÓÚüÜñÑ]/.test(lowerCurrentSuggestedWord);
                //console.log(`[WME PLN - processNextPlace] currentSuggestedWord (del nombre sugerido): "${currentSuggestedWord}", lowerCurrentSuggestedWord: "${lowerCurrentSuggestedWord}", currentSuggestedWordHasDiacritics: ${currentSuggestedWordHasDiacritics}`);
                // *******************************************************************
                // PASO 1: PRIORIDAD - SUGERIR CORRECCIÓN DE TILDES
                // PASO 1: PRIORIDAD - SUGERIR CORRECCIÓN DE TILDES
                // ✨ OPTIMIZACIÓN: Búsqueda O(1) usando mapa inverso (28-nov-2024)
                if (window.normalizedToDictionaryMap && window.normalizedToDictionaryMap.has(cleanedLowerNoDiacritics)) {
                    const dictWord = window.normalizedToDictionaryMap.get(cleanedLowerNoDiacritics);
                    const lowerDictWord = dictWord.toLowerCase();
                    const originalHasDiacritics = /[áéíóúÁÉÍÓÚüÜñÑ]/.test(lowerOriginalWord);

                    // Replicar condiciones originales:
                    // - La palabra del diccionario debe ser diferente a la actual sugerida
                    // - La actual sugerida no debe tener tildes (para no sobreescribir si ya tiene)
                    // - La original no debe tener tildes (solo agregamos tildes faltantes)
                    if (lowerDictWord !== lowerCurrentSuggestedWord &&
                        !currentSuggestedWordHasDiacritics &&
                        !originalHasDiacritics) {

                        let suggestedTildeWord = normalizeWordInternal(dictWord, true, false);

                        if (!sugerenciasLugar[originalWord]) sugerenciasLugar[originalWord] = [];
                        sugerenciasLugar[originalWord].push({
                            word: suggestedTildeWord,
                            similarity: 0.999,
                            fuente: 'dictionary_tilde'
                        });
                        tildeCorrectionSuggested = true;
                    }
                }
                // *******************************************************************

                // *******************************************************************
                // PASO 2: OTRAS SUGERENCIAS DEL DICCIONARIO (SOLO SI NO SE SUGIRIÓ CORRECCIÓN DE TILDE)
                if (!tildeCorrectionSuggested && checkDictionaryWords && window.dictionaryWords) {
                    //console.log(`[WME PLN - processNextPlace] Buscando otras sugerencias del diccionario para: "${originalWord}" (No se sugirió tilde).`);
                    const similarDictionary = findSimilarWords(cleanedLowerNoDiacritics, window.dictionaryIndex, similarityThreshold);
                    if (similarDictionary.length > 0) {
                        const finalSuggestions = similarDictionary.filter(d =>
                            d.word.toLowerCase() !== lowerOriginalWord && // No es la misma palabra original
                            d.word.toLowerCase() !== lowerCurrentSuggestedWord && // No es la misma palabra que ya está en el sugerido
                            !sugerenciasLugar[originalWord]?.some(s => s.word === normalizeWordInternal(d.word, true, false)) // No duplica una sugerencia de tilde ya agregada
                        );
                        //console.log(`[WME PLN - processNextPlace]   Otras sugerencias filtradas:`, finalSuggestions);

                        if (finalSuggestions.length > 0) {
                            if (!sugerenciasLugar[originalWord]) sugerenciasLugar[originalWord] = [];
                            finalSuggestions.forEach(dictSuggestion => {
                                if (!sugerenciasLugar[originalWord].some(s => s.word === normalizeWordInternal(dictSuggestion.word, true, false))) {
                                    sugerenciasLugar[originalWord].push({ ...dictSuggestion, fuente: 'dictionary' });
                                }
                            });
                        }
                    }
                }
                // *******************************************************************
            });
            // console.log(`[WME_PLN] Nombre sugerido después de trim/espacios múltiples: "${suggestedName}"`);
            // 6.1 --- QUITAR PUNTO FINAL SI EXISTE ---
            if (suggestedName.endsWith('.')) {
                suggestedName = suggestedName.slice(0, -1);
                // console.log(`[WME_PLN] Nombre sugerido después de quitar punto final: "${suggestedName}"`);
            }
            // 6.2 --- QUITAR ESPACIOS MÚLTIPLES ---
            //console.log(`[WME_PLN] Evaluando lógica de salto...`);
            const tieneSugerencias = Object.keys(sugerenciasLugar).length > 0;
            // Comparación estricta: solo colapsa espacios. Respeta mayúsculas, tildes y orden
            const cleanedOriginalName = String(nameForProcessing || '').replace(/\s+/g, ' ').trim();
            const cleanedSuggestedName = String(suggestedName || '').replace(/\s+/g, ' ').trim();
            const originalHasEmoticon = originalNameRaw !== nameForProcessing;

            // Un lugar es inconsistente si el nombre cambia O si tenía un emoticón.
            const isTrulyInconsistent = (cleanedOriginalName !== cleanedSuggestedName) || originalHasEmoticon;

            // Esta variable ahora refleja la verdadera condición de normalización
            const isNameEffectivelyNormalized = !isTrulyInconsistent;

            if (isNameEffectivelyNormalized && !tieneSugerencias && !hasEmptyName) {
                shouldSkipThisPlace = true;
                skipReasonLog = `[SKIP NORMALIZED]`;
            }
            const equalExact = (cleanedOriginalName === cleanedSuggestedName);
            const equalCaseInsensitive = (cleanedOriginalName.toLowerCase() === cleanedSuggestedName.toLowerCase());
            const equalNoDiacritics = (removeDiacritics(cleanedOriginalName.toLowerCase()) === removeDiacritics(cleanedSuggestedName.toLowerCase()));
            //let isNameEffectivelyNormalized = equalExact; // solo si es EXACTO tras colapsar espacios


            // PASO 1: Comprobar si se debe excluir por ser una edición tuya DENTRO del rango de fecha.
            if (avoidMyEdits && wasEditedByMe) {
                // Es un lugar editado por mí y el filtro está activo.
                const dateFilterValue = document.getElementById("dateFilterSelect")?.value || "all";
                const placeEditDate = (venueSDK && venueSDK.modificationData && venueSDK.modificationData.updatedOn)
                    ? new Date(venueSDK.modificationData.updatedOn)
                    : null;
                // Comprobar si la fecha de edición del lugar está dentro del rango seleccionado
                if (placeEditDate && isDateWithinRange(placeEditDate, dateFilterValue)) {
                    // Está DENTRO del rango, por lo tanto, se omite. La decisión es final.
                    shouldSkipThisPlace = true;
                    skipReasonLog = `[SKIP MY OWN EDIT - In Range: ${dateFilterValue}]`;
                }
                // Si está FUERA del rango, no hacemos nada aquí. Dejamos que 'shouldSkipThisPlace' siga siendo 'false'
                // y pase al siguiente filtro de abajo.
            }
            // Condición de Salto 2: Lugar está en la lista de excluidos (por ID).
            //console.log(`[WME PLN - SKIP] Verificando exclusión para ID: "${currentVenueId}"`); // <--- USAR currentVenueId
            //console.log(`[WME PLN - SKIP] 'excludedPlaces' contiene ID: ${excludedPlaces.has(currentVenueId)}`); // true/false // <--- USAR currentVenueId
            //console.log(`[WME PLN - SKIP] Contenido de 'excludedPlaces' (primeros 5 entries):`, Array.from(excludedPlaces.entries()).slice(0, 5)); // Ver algunos IDs guardados
            if (!shouldSkipThisPlace && excludedPlaces.has(currentVenueId)) { // <--- USAR currentVenueId
                shouldSkipThisPlace = true;
                skipReasonLog = `[SKIP EXCLUDED PLACE]`;
            }
            // PASO 2: Comprobar si el lugar ya está normalizado.
            // Esta regla se aplica a TODOS los lugares que NO fueron omitidos en el PASO 1.
            // (Incluye los lugares de otros editores y  propias ediciones fuera del rango de fecha).
            if (!shouldSkipThisPlace && isNameEffectivelyNormalized && !hasEmptyName) {
                shouldSkipThisPlace = true;
                skipReasonLog = `[SKIP NORMALIZED]`;
            }

            // PASO ADICIONAL DE SALTO: Si es un área y no se pudo calcular su área
            if (!shouldSkipThisPlace && typeInfo.isArea && areaMeters === null) { // <-- typeInfo se usa aquí
                shouldSkipThisPlace = true;
                skipReasonLog = `[SKIP AREA_CALC_FAILED]`;
            }
            //console.log(`[WME PLN - processNextPlace] LUGAR NO SALTADO. Procediendo. ID: ${currentVenueId}. Nombre: "${originalNameRaw}"`);



            //console.log(`[WME PLN - SKIP] Verificando exclusión para ID: "${currentVenueId}"`);
            //console.log(`[WME PLN - SKIP] 'excludedPlaces' contiene ID: ${excludedPlaces.has(currentVenueId)}`); // true/false
            //console.log(`[WME PLN - SKIP] Contenido de 'excludedPlaces' (primeros 5 entries):`, Array.from(excludedPlaces.entries()).slice(0, 5)); // Ver algunos IDs guardados

            //  PASO 2.5: Comprobar si el lugar está en la lista de excluidos
            if (!shouldSkipThisPlace && excludedPlaces.has(currentVenueId)) {
                shouldSkipThisPlace = true;
                skipReasonLog = `[SKIP EXCLUDED PLACE]`;
            }

            // --- Salto temprano si se determinó omitir el lugar ---
            if (shouldSkipThisPlace) {
                //console.log(`[WME PLN - processNextPlace] LUGAR SALTADO. Razón: ${skipReasonLog}. ID: ${currentVenueId}. Nombre: "${originalNameRaw}"`); // <--- USAR currentVenueId

                //  if (skipReasonLog) console.log(`[WME_PLN] ${skipReasonLog} Descartado "${originalName}" (ID: ${currentVenueId})`); //Añadir ID al log
                const updateFrequency = 3; // Actualiza cada 3 lugares la barra de progreso
                if ((index + 1) % updateFrequency === 0 || (index + 1) === places.length) {
                    updateScanProgressBar(index, places.length);
                }
                index++;
                setTimeout(() => processNextPlace(), 0); // Continúa con el siguiente lugar
                return;
            }
            else {
                //console.log(`[WME PLN - processNextPlace] LUGAR NO SALTADO. Procediendo. ID: ${currentVenueId}. Nombre: "${originalNameRaw}"`); // <--- USAR currentVenueId
            }
            //console.log(`[WME_PLN] Decisión de salto: ${shouldSkipThisPlace} (${skipReasonLog})`);
            // 8. Registrar o no en la lista de inconsistentes
            //console.log(`[WME_PLN] Registrando lugar con inconsistencias...`);
            // *** Si Llegamos Aquí, El Lugar No Se Salta Y Necesitamos Su Info Completa Para La Tabla ***
            if (processingStepLabel) {
                processingStepLabel.textContent = "Registrando lugar(es) con inconsistencias...";
            }
            // Lógica de Categorías (solo para lugares no saltados)
            const shouldRecommendCategories = document.getElementById("chk-recommend-categories")?.checked ?? true;
            let currentCategoryKey;
            let currentCategoryIcon;
            let currentCategoryTitle;
            let currentCategoryName;
            let dynamicSuggestions;
            try {
                const lang = getWazeLanguage();
                currentCategoryKey = getPlaceCategoryName(venueFromOldModel, venueSDK);
                const categoryDetails = getCategoryDetails(currentCategoryKey);
                currentCategoryIcon = categoryDetails.icon;
                currentCategoryTitle = categoryDetails.description;
                currentCategoryName = categoryDetails.description;

                if (shouldRecommendCategories)
                    dynamicSuggestions = findCategoryForPlace(originalName);
                else
                    dynamicSuggestions = [];
            }
            catch (e) {
                console.error("[WME PLN] Error procesando las categorías:", e);
                currentCategoryName = "Error";
                currentCategoryIcon = "❓";
                currentCategoryTitle = "Error al obtener categoría";
                dynamicSuggestions = [];
                currentCategoryKey = "UNKNOWN";
            }
            // Lógica unificada y robusta para obtener resolvedEditorName, lastEditorIdForComparison y calcular wasEditedByMe
            resolvedEditorName = "Desconocido"; // Reinicializar para cada place
            lastEditorIdForComparison = null; // Reinicializar para cada place
            if (venueSDK && venueSDK.modificationData) {
                const updatedByDataFromSDK = venueSDK.modificationData.updatedBy;
                if (typeof updatedByDataFromSDK === 'string' && updatedByDataFromSDK.trim() !== '') {
                    resolvedEditorName = updatedByDataFromSDK; // El nombre del editor es una cadena
                }
                else if (typeof updatedByDataFromSDK === 'number') {
                    lastEditorIdForComparison = updatedByDataFromSDK; // El ID numérico es la fuente principal
                    resolvedEditorName = `ID ${updatedByDataFromSDK}`; // Nombre temporal
                    if (W && W.model && W.model.users) {
                        const userObjectW = W.model.users.getObjectById(updatedByDataFromSDK);
                        if (userObjectW && userObjectW.userName) {
                            resolvedEditorName = userObjectW.userName; // Obtener nombre real del usuario si está en el modelo
                        }
                    }
                }
            }
            else if (venueFromOldModel && venueFromOldModel.attributes && (venueFromOldModel.attributes.updatedBy !== null && venueFromOldModel.attributes.updatedBy !== undefined)) {
                // Fallback al modelo antiguo si el SDK no dio datos de editor
                const oldModelUpdatedBy = venueFromOldModel.attributes.updatedBy;
                lastEditorIdForComparison = oldModelUpdatedBy; // El ID numérico es la fuente principal
                resolvedEditorName = `ID ${oldModelUpdatedBy}`; // Nombre temporal
                if (W && W.model && W.model.users) {
                    const userObjectW = W.model.users.getObjectById(oldModelUpdatedBy);
                    if (userObjectW && userObjectW.userName) {
                        resolvedEditorName = userObjectW.userName; // Obtener nombre real del usuario si está en el modelo
                    }
                }
            }
            else {
                resolvedEditorName = "N/D"; // No hay información de editor
            }
            wasEditedByMe = false; // Resetear para este place
            // Calcular wasEditedByMe de forma robusta aquí mismo
            if (currentLoggedInUserId !== null && currentLoggedInUserId !== undefined && resolvedEditorName !== "N/D") { // Solo si tenemos un nombre de usuario logueado y el resolvedEditorName no es N/D
                if (lastEditorIdForComparison !== null && lastEditorIdForComparison !== undefined && typeof lastEditorIdForComparison === 'number') {
                    // PRIORIDAD 1: Comparar IDs numéricos si ambos están disponibles y son válidos
                    if (typeof currentLoggedInUserId === 'number') { // Si el ID también es numérico
                        wasEditedByMe = (lastEditorIdForComparison === currentLoggedInUserId);
                    }
                    else { // Si el ID es string (userName) y el del place es number
                        wasEditedByMe = (String(lastEditorIdForComparison) === currentLoggedInUserId); // Convertir solo el del place a string
                    }
                }
                else if (resolvedEditorName && typeof resolvedEditorName === 'string') {
                    // PRIORIDAD 2: Si no hay ID numérico del editor del place, pero sí su nombre, comparar por nombre
                    wasEditedByMe = (resolvedEditorName.toLowerCase() === String(currentLoggedInUserId).toLowerCase());
                }
            }
            // Obtener información de la ciudad (esto ya estaba bien, solo reubicado)
            try {
                cityInfo = await getPlaceCityInfo(venueFromOldModel, venueSDK, currentCategoryKey);
            }
            catch (e) {
                console.error(`[WME_PLN] Error al obtener información de la ciudad para el venue ID ${currentVenueId}:`, e);
            }
            //Determinar nivel de bloqueo correspondiente
            let lockRank = 0; // Valor por defecto
            if (venueSDK && venueSDK.lockRank !== undefined && venueSDK.lockRank !== null)
                lockRank = venueSDK.lockRank;
            else if (venueFromOldModel && venueFromOldModel.attributes && venueFromOldModel.attributes.lockRank !== undefined && venueFromOldModel.attributes.lockRank !== null)
                lockRank = venueFromOldModel.attributes.lockRank;
            // Asignar el emoji correspondiente al nivel de bloqueo
            let lockRankEmoji = (lockRank >= 0 && lockRank <= 5) ? lockRankEmojis[lockRank + 1] : lockRankEmojis[0];
            // Calcular si el lugar tiene horarios superpuestos (solo si venueSDK está disponible)
            const hasOverlappingHours = checkForOverlappingHours(venueSDK); // Llama a la nueva función.
            // Calcular si el lugar tiene ediciones pendientes (solo si venueSDK está disponible)
            const hasPendingEdits = checkForPendingEdits(venueSDK); // Llama a la nueva función.




            //console.log(`[WME_PLN][DEBUG] Assigned LockRankEmoji: ${lockRankEmoji}`);

            // Limpiar guiones colgantes del suggestedName antes de mostrarlo
            // Esto evita que nombres como "Terpel ⛽" se conviertan en "Terpel -"
            if (typeof plnFixDanglingHyphen === 'function') {
                suggestedName = plnFixDanglingHyphen(suggestedName);
            }

            // Manejar lugares sin nombre: asignar nombre especial y marcador
            const displayOriginalName = hasEmptyName ? '⚠️ Sin nombre' : originalNameRaw;

            // Lógica especial para FOREST_GROVE: sugerencia vacía
            let displayNormalizedName = hasEmptyName ? 'Requiere corrección manual' : suggestedName;
            const categoriesForSuggestion = venueFromOldModel?.attributes?.categories || [];
            if (categoriesForSuggestion.includes('FOREST_GROVE')) {
                displayNormalizedName = "";
            }

            // ✨ REGLAS ESPECIALES PARA LUGARES SIN NOMBRE (28-nov-2024)
            if (hasEmptyName) {
                // REGLA 1: PARK tipo AREA sin nombre → Recomendar categoría FOREST
                if (categoriesForSuggestion.includes('PARK') && typeInfo.type === 'AREA') {
                    // Añadir FOREST a las sugerencias de categoría si no está ya
                    const forestSuggestion = {
                        categoryKey: 'FOREST_GROVE',
                        icon: '🌳',
                        desc_es: 'Bosque',
                        desc_en: 'Forest',
                        score: '⭐⭐⭐ Regla automática: Parques sin nombre tipo AREA son bosques'
                    };

                    if (!dynamicSuggestions.some(s => s.categoryKey === 'FOREST_GROVE')) {
                        dynamicSuggestions.unshift(forestSuggestion); // Añadir al inicio
                    }
                }

                if (categoriesForSuggestion.includes('PARKING_LOT')) {
                    displayNormalizedName = 'Parqueadero';
                }
            }

            // REGLA 3: FOREST_GROVE tipo PUNTO (no AREA) -> Requiere corrección
            // Si es un bosque y es un punto, estructuralmente está mal en Waze.
            if (categoriesForSuggestion && categoriesForSuggestion.includes('FOREST_GROVE') && typeInfo && !typeInfo.isArea) {
                displayNormalizedName = 'Requiere corrección manual';
            }

            // REGLA 4: PARKING_LOT tipo PUNTO -> Error, debe ser AREA
            let isParkingPointError = false;
            if (categoriesForSuggestion && categoriesForSuggestion.includes('PARKING_LOT') && typeInfo && !typeInfo.isArea) {
                isParkingPointError = true;
            }

            // Agregar a la lista de inconsistencias
            inconsistents.push({
                lockRankEmoji: lockRankEmoji,
                id: currentVenueId,
                original: displayOriginalName,
                normalized: displayNormalizedName,
                editor: resolvedEditorName, // Usamos el nombre del editor resuelto
                cityIcon: cityInfo.icon,
                cityTitle: cityInfo.title,
                hasCity: cityInfo.hasCity,
                venueSDKForRender: venueSDK,
                currentCategoryName: currentCategoryName,
                currentCategoryIcon: currentCategoryIcon,
                currentCategoryTitle: currentCategoryTitle,
                currentCategoryKey: currentCategoryKey,
                dynamicCategorySuggestions: dynamicSuggestions,
                // Asegurarse de incluir lat y lon obtenidos de getPlaceCoordinates
                lat: placeLat,
                lon: placeLon,
                typeInfo: typeInfo, // Guardar el objeto completo para su uso en el render
                areaMeters: areaMeters, // Ya se calcula con venueSDK
                hasOverlappingHours: hasOverlappingHours,
                hasPendingEdits: hasPendingEdits,
                hasEmptyName: hasEmptyName,  // Marcador especial para lugares sin nombre
                isParkingPointError: isParkingPointError
            });
            // 9. Agregar datos del lugar para la verificación de duplicados
            if (!hasEmptyName) {
                sugerenciasPorPalabra[currentVenueId] = sugerenciasLugar;// Guardar sugerencias por palabra para este lugar
            }
            // 10. Finalizar procesamiento del 'place' actual y pasar al siguiente
            const updateFrequency = 5;
            if ((index + 1) % updateFrequency === 0 || (index + 1) === places.length)
                updateScanProgressBar(index, places.length);
            index++;
            setTimeout(() => processNextPlace(), 0);
        }
        // console.log("[WME_PLN] Iniciando primer processNextPlace...");
        try {
            setTimeout(() => { processNextPlace(); }, 10);
        }
        catch (error) {
            console.error("[WME_PLN][ERROR_CRITICAL] Fallo al iniciar processNextPlace:", error, error.stack);
            enableScanControls();
            const outputFallback = document.querySelector("#wme-place-inspector-output");
            if (outputFallback) {
                outputFallback.innerHTML = `<div style='color:red; padding:10px;'><b>Error Crítico:</b> El script de normalización encontró un problema grave y no pudo continuar. Revise la consola para más detalles (F12).<br>Detalles: ${error.message}</div>`;
            }
            const scanBtn = document.querySelector("button[type='button']"); // Asumiendo que es el botón de Start Scan
            if (scanBtn) {
                scanBtn.disabled = false;
                scanBtn.textContent = "Start Scan... (Error Previo)";
            }
            if (window.processingDotsInterval) {
                clearInterval(window.processingDotsInterval);
            }
        }// processNextPlace

        // Función para re-aplicar la lógica de palabras excluidas al texto normalizado
        function reapplyExcludedWordsLogic(text, excludedWordsSet) {
            if (typeof text !== 'string' || !excludedWordsSet || excludedWordsSet.size === 0) {
                return text;
            }
            const wordsInText = text.split(/\s+/);
            const processedWordsArray = wordsInText.map(word => {
                if (word === "") return "";
                const wordWithoutDiacriticsLower = removeDiacritics(word.toLowerCase());
                // Encontrar la palabra excluida que coincida (insensible a may/min y diacríticos)
                const matchingExcludedWord = Array.from(excludedWordsSet).find(
                    w_excluded => removeDiacritics(w_excluded.toLowerCase()) === wordWithoutDiacriticsLower);
                if (matchingExcludedWord) {
                    // Si coincide, DEVOLVER LA FORMA EXACTA DE LA LISTA DE EXCLUIDAS
                    return matchingExcludedWord;
                }
                // Si no, devolver la palabra como estaba (ya normalizada por pasos previos)
                return word;
            });
            return processedWordsArray.join(' ');
        }// reapplyExcludedWordsLogic


        //Función para finalizar renderizado una vez completado el análisis
        function finalizeRender(inconsistents, placesArr, allSuggestions) {   // Limpiar el mensaje de procesamiento y spinner al finalizar el análisis
            try {
                // Pre-declare para evitar TDZ cuando se referencian antes de asignar
                // Evitar TDZ y nulls intermitentes
                //const typeInfo = venueSDK?.typeInfo || {};
                enableScanControls();
                // Detener animación de puntos suspensivos si existe
                if (window.processingDotsInterval) {
                    clearInterval(window.processingDotsInterval);
                    window.processingDotsInterval = null;
                }

                // Refuerza el restablecimiento del botón de escaneo al entrar
                const scanBtn = document.querySelector("button[type='button']");
                if (scanBtn) {
                    scanBtn.textContent = "Start Scan...";
                    scanBtn.disabled = false;
                    scanBtn.style.opacity = "1";
                    scanBtn.style.cursor = "pointer";
                }
                // Verificar si el botón de escaneo existe
                const output = document.querySelector("#wme-place-inspector-output");
                if (!output) {
                    //  console.error("[WME_PLN]❌ No se pudo montar el panel flotante. Revisar estructura del DOM.");
                    alert("Hubo un problema al mostrar los resultados. Intenta recargar la página.");
                    return;
                }
                // Limpiar el mensaje de procesamiento y spinner
                const undoRedoHandler = function () {// Maneja el evento de deshacer/rehacer
                    if (floatingPanelElement && floatingPanelElement.style.display !== 'none') {
                        waitForWazeAPI(() => {
                            const places = getVisiblePlaces();
                            renderPlacesInFloatingPanel(places); // Esto mostrará el panel de "procesando" y luego resultados
                            reactivateAllActionButtons(); // No necesitamos setTimeout aquí si renderPlacesInFloatingPanel es síncrono.
                        });
                    }
                    else {
                        //console.log("[WME PLN] Undo/Redo: Panel de resultados no visible, no se re-escanea.");
                    }
                };
                // Objeto para almacenar referencias de listeners para desregistro
                if (!window._wmePlnUndoRedoListeners) {
                    window._wmePlnUndoRedoListeners = {};
                }
                // Desregistrar listeners previos si existen
                if (window._wmePlnUndoRedoListeners.undo) {
                    W.model.actionManager.events.unregister("afterundoaction", null, window._wmePlnUndoRedoListeners.undo);
                }
                if (window._wmePlnUndoRedoListeners.redo) {
                    W.model.actionManager.events.unregister("afterredoaction", null, window._wmePlnUndoRedoListeners.redo);
                }
                // Registrar nuevos listeners
                W.model.actionManager.events.register("afterundoaction", null, undoRedoHandler);
                W.model.actionManager.events.register("afterredoaction", null, undoRedoHandler);
                // Almacenar referencias para poder desregistrar en el futuro
                window._wmePlnUndoRedoListeners.undo = undoRedoHandler;
                window._wmePlnUndoRedoListeners.redo = undoRedoHandler;
                // Esta llamada se hace ANTES de limpiar el output. El primer argumento es el estado, el segundo es el número de inconsistencias.
                createFloatingPanel("results", inconsistents.length);
                // Limpiar el mensaje de procesamiento y spinner
                if (output) {
                    // Mostrar el panel flotante al terminar el procesamiento se usa para mostrar los resultados y llamados al console.log
                }
                // Limitar a 30 resultados y mostrar advertencia si excede
                const maxRenderLimit = 30;
                const totalInconsistentsOriginal = inconsistents.length; // Guardar el total original
                let isLimited = false; // Declarar e inicializar isLimited
                // Si hay más de 30 resultados, limitar a 30 y mostrar mensaje
                // Limit logic removed to support batching
                /*
                if (totalInconsistentsOriginal > maxRenderLimit) {
                    // ... (removed slicing and modal)
                }
                */
                // Llamar a la función para detectar y alertar nombres duplicados
                detectAndAlertDuplicateNames(inconsistents);

                // ✨ OPTIMIZACIÓN: Reagrupar lugares duplicados juntos al inicio (28-nov-2024)
                // Los duplicados se colocan al principio, agrupados por nombre
                const duplicates = [];
                const nonDuplicates = [];

                // Separar duplicados de no duplicados
                inconsistents.forEach(place => {
                    if (place.isDuplicate) {
                        duplicates.push(place);
                    } else {
                        nonDuplicates.push(place);
                    }
                });

                // Ordenar duplicados por nombre normalizado para agruparlos
                duplicates.sort((a, b) => a.normalized.localeCompare(b.normalized));

                // Reconstruir array: duplicados primero, luego no duplicados
                inconsistents = [...duplicates, ...nonDuplicates];

                // Crear un contenedor para los elementos fijos de la cabecera del panel de resultados
                const fixedHeaderContainer = document.createElement("div");

                fixedHeaderContainer.style.background = "#fff"; // Fondo para que no se vea el scroll debajo
                fixedHeaderContainer.style.padding = "0 10px 8px 10px"; // Padding para espacio y que no esté pegado
                fixedHeaderContainer.style.borderBottom = "1px solid #ccc"; // Un borde para separarlo de la tabla
                fixedHeaderContainer.style.zIndex = "11"; // Asegurarse de que esté por encima de la tabla
                // Añadir Estas Dos Líneas Clave Al FixedHeaderContainer
                fixedHeaderContainer.style.position = "sticky"; // Hacer Que Este Contenedor Sea Sticky
                fixedHeaderContainer.style.top = "0";            // Pegado A La Parte Superior Del Contenedor De Scroll

                // =======================================================
                // INICIO DEL BLOQUE CORREGIDO
                // =======================================================
                // 1. Contenedor Flex para el texto y el botón
                const headerControlsContainer = document.createElement("div");
                headerControlsContainer.style.display = "flex";
                // LA SIGUIENTE LÍNEA ES EL CAMBIO PRINCIPAL:
                headerControlsContainer.style.justifyContent = "flex-start"; // Alinea los elementos al inicio
                headerControlsContainer.style.alignItems = "center";
                headerControlsContainer.style.gap = "15px"; // Mantiene el espacio entre texto y botón

                const resultsCounter = document.createElement("div");
                resultsCounter.className = "results-counter-display";
                resultsCounter.style.fontSize = "13px";
                resultsCounter.style.color = "#555";
                resultsCounter.style.textAlign = "left";

                resultsCounter.dataset.currentCount = inconsistents.length;
                resultsCounter.dataset.totalOriginal = totalInconsistentsOriginal;
                resultsCounter.dataset.maxRenderLimit = maxRenderLimit;


                // Mostrar el contador y el botón solo si hay inconsistencias
                if (totalInconsistentsOriginal > 0) {
                    // 1. Configura el mensaje del contador
                    if (isLimited) {
                        resultsCounter.innerHTML = `<span style="color: #ff0000;">Inconsistencias encontradas: <b>${totalInconsistentsOriginal}</b></span>. Mostrando las primeras <span style="color: #ff0000;"><b>${inconsistents.length}</b></span>.`;
                    }
                    else {
                        resultsCounter.innerHTML = `Inconsistencias encontradas: <b style="color: #ff0000;">${inconsistents.length}</b> de <b style="color: #ff0000;">${totalInconsistentsOriginal}</b>. Mostrando <b style="color: #ff0000;">${inconsistents.length}</b>.`;
                    }
                    headerControlsContainer.appendChild(resultsCounter);

                    // 2. Muestra el botón "Mostrar/Ocultar procesados"
                    const toggleBtn = document.getElementById('pln-toggle-hidden-btn');
                    if (toggleBtn) {
                        headerControlsContainer.appendChild(toggleBtn);
                        toggleBtn.style.display = 'inline-block'; // Asegurarse de que sea visible
                    }

                }
                else {
                    // Si no hay inconsistencias, muestra el mensaje de éxito
                    const outputDiv = document.querySelector("#wme-place-inspector-output");
                    if (outputDiv) {
                        outputDiv.innerHTML = `<div style='color:green; padding:10px;'>✔ Todos los lugares visibles están correctamente normalizados o excluidos.</div>`;
                    }
                    // Y nos aseguramos de que el botón esté oculto
                    const toggleBtn = document.getElementById('pln-toggle-hidden-btn');
                    if (toggleBtn) {
                        toggleBtn.style.display = 'none';
                    }
                }

                fixedHeaderContainer.appendChild(headerControlsContainer);


                // 2. Lógica del botón (sin cambios respecto a la corrección anterior)
                // En la función donde creas el botón toggle
                // Lógica unificada para el botón "Mostrar/Ocultar Normalizados"
                if (totalInconsistentsOriginal > 0) {
                    let toggleBtn = document.getElementById('pln-toggle-hidden-btn');
                    if (!toggleBtn) {
                        toggleBtn = document.createElement("button");
                        toggleBtn.id = 'pln-toggle-hidden-btn';
                        toggleBtn.style.padding = "5px 10px";
                        toggleBtn.style.marginLeft = "15px";
                        toggleBtn.style.fontSize = "12px"; // Mantenemos el tamaño de letra

                        // Aseguramos que el estado inicial sea ocultar las filas
                        document.body.classList.add('pln-hide-normalized-rows');

                        toggleBtn.addEventListener('click', function () {
                            // Simplemente alterna la clase en el body
                            document.body.classList.toggle('pln-hide-normalized-rows');

                            // Y actualiza el texto del botón según el estado
                            this.textContent = document.body.classList.contains('pln-hide-normalized-rows')
                                ? "Mostrar Normalizados"
                                : "Ocultar Normalizados";
                        });
                    }

                    // Sincroniza siempre el texto del botón con el estado actual
                    toggleBtn.textContent = document.body.classList.contains('pln-hide-normalized-rows')
                        ? "Mostrar Normalizados"
                        : "Ocultar Normalizados";

                    headerControlsContainer.appendChild(toggleBtn);
                }

                fixedHeaderContainer.appendChild(headerControlsContainer);

                if (inconsistents.length === 0) {
                    if (totalInconsistentsOriginal === 0) {
                        const checkIcon = document.createElement("div");
                        checkIcon.innerHTML = "";
                        checkIcon.style.marginTop = "10px";
                        checkIcon.style.fontSize = "14px";
                        checkIcon.style.color = "green";
                        output.appendChild(checkIcon);
                        const successMsg = document.createElement("div");
                        successMsg.textContent = "";
                        successMsg.style.marginTop = "10px";
                        successMsg.style.fontSize = "14px";
                        successMsg.style.color = "green";
                        successMsg.style.fontWeight = "bold";
                        output.appendChild(successMsg);
                    }
                    const existingOverlay = document.getElementById("scanSpinnerOverlay");
                    if (existingOverlay) existingOverlay.remove();
                    const progressBarInnerTab = document.getElementById("progressBarInnerTab");
                    const progressBarTextTab = document.getElementById("progressBarTextTab");
                    if (progressBarInnerTab && progressBarTextTab) {
                        progressBarInnerTab.style.width = "100%";
                        progressBarTextTab.textContent = `Progreso: 100% (${placesArr.length}/${placesArr.length})`;
                    }
                    const outputTab = document.getElementById("wme-normalization-tab-output");
                    if (outputTab) {
                        outputTab.innerHTML = `✔ Todos los nombres están normalizados. Se analizaron ${placesArr.length} lugares.`;
                        outputTab.style.color = "green";
                        outputTab.style.fontWeight = "bold";
                    }
                    const scanBtn = document.querySelector("button[type='button']");
                    if (scanBtn) {
                        scanBtn.textContent = "Start Scan...";
                        scanBtn.disabled = false;
                        scanBtn.style.opacity = "1";
                        scanBtn.style.cursor = "pointer";
                        const iconCheck = document.createElement("span");
                        iconCheck.textContent = " ✔";
                        iconCheck.style.marginLeft = "8px";
                        iconCheck.style.color = "green";
                        scanBtn.appendChild(iconCheck);
                    }
                    return;
                }
                if (output) {
                    output.style.display = 'flex';
                    output.style.flexDirection = 'column';
                    output.style.position = 'relative';
                    output.appendChild(fixedHeaderContainer);
                }
                const table = document.createElement("table");
                table.style.width = "100%";
                table.style.borderCollapse = "collapse";
                table.style.fontSize = "12px";
                const thead = document.createElement("thead");
                const headerRow = document.createElement("tr");
                [
                    "N°",
                    "Perma",
                    "Tipo/Ciudad",
                    "LL",
                    "Editor",
                    "Nombre Actual",
                    "⚠️",
                    "Nombre Sugerido",
                    "Sugerencias<br>de reemplazo",
                    "Categoría",
                    "Categoría<br>Recomendada",
                    "Acción"
                ].forEach(header => {
                    const th = document.createElement("th");
                    th.innerHTML = header;
                    th.style.borderBottom = "1px solid #ccc";
                    th.style.padding = "4px";
                    th.style.textAlign = "center";
                    th.style.fontSize = "14px";
                    if (header === "N°") {
                        th.style.width = "30px";
                    }
                    else if (header === "LL") {
                        th.title = "Nivel de Bloqueo (Lock Level)";
                        th.style.width = "40px";
                    }
                    else if (header === "Perma" || header === "Tipo/Ciudad") {
                        th.style.width = "65px";
                    }
                    else if (header === "⚠️") {
                        th.title = "Alertas y advertencias";
                        th.style.width = "30px";
                    }
                    else if (header === "Categoría") {
                        th.style.width = "110px";
                    }
                    else if (header === "Categoría<br>Recomendada" || header === "Sugerencias<br>de reemplazo") {
                        th.style.width = "150px";
                    }
                    else if (header === "Editor") {
                        th.style.width = "100px";
                    }
                    else if (header === "Acción") {
                        th.style.width = "100px";
                    }
                    else if (header === "Nombre Actual" || header === "Nombre Sugerido") {
                        th.style.width = "250px";
                    }
                    headerRow.appendChild(th);
                });
                thead.appendChild(headerRow);
                table.appendChild(thead);
                thead.style.position = "sticky";
                thead.style.top = "0";
                thead.style.background = "#f1f1f1";
                thead.style.zIndex = "10";
                headerRow.style.backgroundColor = "#003366";
                headerRow.style.color = "#ffffff";
                const tbody = document.createElement("tbody");

                const buildPlacePermalink = (placeId) => {
                    try {
                        const { protocol, host, pathname, search } = window.location;
                        const origin = `${protocol}//${host}`;
                        const editorKeyword = '/editor';
                        let editorPath = editorKeyword;
                        const editorIndex = pathname.toLowerCase().indexOf(editorKeyword);
                        if (editorIndex !== -1) {
                            editorPath = pathname.slice(0, editorIndex + editorKeyword.length);
                        }
                        let envParam = 'row';
                        if (typeof URLSearchParams === "function") {
                            const params = new URLSearchParams(search);
                            envParam = params.get('env') || envParam;
                        }
                        else {
                            const envMatch = /env=([^&]+)/i.exec(search);
                            if (envMatch) {
                                envParam = envMatch[1];
                            }
                        }
                        return `${origin}${editorPath}?env=${envParam}&venueId=${placeId}`;
                    }
                    catch (_) {
                        return `https://www.waze.com/editor?env=row&venueId=${placeId}`;
                    }
                };

                function handlePlaceLinkNavigation(event, placeId, placeName, placePermalink, venueSDKForRender) {
                    if (event) {
                        if (event.ctrlKey || event.metaKey || event.shiftKey || event.button === 1) {
                            return; // allow default behavior to open in new tab/window
                        }
                        event.preventDefault();
                        event.stopPropagation();
                    }

                    const venueObj = W.model.venues.getObjectById(placeId);
                    const venueSDKForUse = venueSDKForRender;

                    let targetLat = null;
                    let targetLon = null;

                    if (venueSDKForUse && venueSDKForUse.geometry && Array.isArray(venueSDKForUse.geometry.coordinates) && venueSDKForUse.geometry.coordinates.length >= 2) {
                        targetLon = venueSDKForUse.geometry.coordinates[0];
                        targetLat = venueSDKForUse.geometry.coordinates[1];
                    }

                    if ((targetLat === null || targetLon === null) && venueObj && typeof venueObj.getOLGeometry === 'function') {
                        try {
                            const geometryOL = venueObj.getOLGeometry();
                            if (geometryOL && typeof geometryOL.getCentroid === 'function') {
                                const centroidOL = geometryOL.getCentroid();
                                if (typeof OpenLayers !== 'undefined' && OpenLayers.Projection) {
                                    const transformedPoint = new OpenLayers.Geometry.Point(centroidOL.x, centroidOL.y).transform(
                                        new OpenLayers.Projection("EPSG:3857"),
                                        new OpenLayers.Projection("EPSG:4326")
                                    );
                                    targetLat = transformedPoint.y;
                                    targetLon = transformedPoint.x;
                                } else {
                                    targetLat = centroidOL.y;
                                    targetLon = centroidOL.x;
                                }
                            }
                        } catch (e) {
                            console.error("[WME PLN] Error al obtener/transformar geometría OL para navegación:", e);
                        }
                    }

                    let navigated = false;

                    if (venueObj && W.selectionManager && typeof W.selectionManager.select === "function") {
                        W.selectionManager.select(venueObj);
                        navigated = true;
                    }
                    else if (venueObj && W.selectionManager && typeof W.selectionManager.setSelectedModels === "function") {
                        W.selectionManager.setSelectedModels([venueObj]);
                        navigated = true;
                    }

                    if (!navigated) {
                        const confirmOpen = confirm(`El lugar "${placeName}" (ID: ${placeId}) no se pudo seleccionar o centrar directamente. ¿Deseas abrirlo en una nueva pestaña del editor?`);
                        if (confirmOpen) {
                            window.open(placePermalink, '_blank');
                        }
                        else {
                            showTemporaryMessage("El lugar podría estar fuera de vista o no cargado.", 4000, 'warning');
                        }
                    }
                    else {
                        showTemporaryMessage("Presentando detalles del lugar...", 2000, 'info');
                    }
                }

                const renderRow = (item, index) => {
                    const { lockRankEmoji, id, original, normalized, editor, cityIcon, cityTitle, hasCity, currentCategoryName, currentCategoryIcon, currentCategoryTitle, currentCategoryKey, dynamicCategorySuggestions, venueSDKForRender, isDuplicate = false, duplicatePartners = [], typeInfo, areaMeters, hasOverlappingHours, hasPendingEdits, hasEmptyName } = item;
                    const progressPercent = Math.floor(((index + 1) / inconsistents.length) * 100);
                    const progressBarInnerTab = document.getElementById("progressBarInnerTab");
                    const progressBarTextTab = document.getElementById("progressBarTextTab");
                    if (progressBarInnerTab && progressBarTextTab) {
                        progressBarInnerTab.style.width = `${progressPercent}%`;
                        progressBarTextTab.textContent = `Progreso: ${progressPercent}% (${index + 1}/${inconsistents.length})`;
                    }
                    const row = document.createElement("tr");

                    // Aplicar estilo para lugares sin nombre
                    if (hasEmptyName) {
                        row.classList.add("pln-empty-name-row");
                        row.title = "⚠️ Este lugar no tiene nombre y requiere corrección manual";
                    }
                    const placePermalink = buildPlacePermalink(id);
                    row.querySelectorAll("td").forEach(td => td.style.verticalAlign = "top");
                    row.dataset.placeId = id;
                    const numberCell = document.createElement("td");
                    numberCell.textContent = index + 1;
                    numberCell.style.textAlign = "center";
                    numberCell.style.padding = "4px";
                    row.appendChild(numberCell);
                    const permalinkCell = document.createElement("td");
                    const link = document.createElement("a");
                    link.href = placePermalink;
                    link.target = "_blank";
                    link.rel = "noopener noreferrer";
                    link.addEventListener("click", (e) => {
                        handlePlaceLinkNavigation(e, id, original, placePermalink, venueSDKForRender);
                    });
                    link.title = "Seleccionar lugar en el mapa";
                    link.textContent = "🔗";
                    permalinkCell.appendChild(link);
                    permalinkCell.style.padding = "4px";
                    permalinkCell.style.fontSize = "18px";
                    permalinkCell.style.textAlign = "center";
                    permalinkCell.style.width = "65px";
                    row.appendChild(permalinkCell);

                    const typeCityCell = document.createElement("td");
                    typeCityCell.style.padding = "4px";
                    typeCityCell.style.width = "65px";
                    typeCityCell.style.verticalAlign = "middle";

                    const cellContentWrapper = document.createElement("div");
                    cellContentWrapper.style.display = "flex";
                    cellContentWrapper.style.justifyContent = "space-around";
                    cellContentWrapper.style.alignItems = "center";

                    const typeContainer = document.createElement("div");
                    typeContainer.style.display = "flex";
                    typeContainer.style.flexDirection = "column";
                    typeContainer.style.alignItems = "center";
                    typeContainer.style.justifyContent = "center";
                    typeContainer.style.gap = "2px";

                    const typeIconSpan = document.createElement("span");
                    typeIconSpan.textContent = typeInfo.icon;
                    typeIconSpan.style.fontSize = "20px";

                    let tooltipText = `Tipo: ${typeInfo.title}`;

                    // Lógica para convertir Parking Punto a Área
                    if (item.isParkingPointError) {
                        typeIconSpan.style.cursor = "pointer";
                        typeIconSpan.style.border = "1px solid red";
                        typeIconSpan.style.borderRadius = "50%";
                        typeIconSpan.style.backgroundColor = "#ffe6e6";
                        tooltipText += "\n⚠️ Parqueadero tipo PUNTO. Click para convertir a ÁREA.";

                        typeIconSpan.addEventListener("click", async (e) => {
                            e.stopPropagation();
                            if (confirm(`¿Convertir este parqueadero a tipo ÁREA?\n\nSe creará un cuadrado de 30x30m alrededor del punto.`)) {
                                await convertPointToArea(id);
                            }
                        });
                    }

                    typeIconSpan.title = tooltipText;

                    typeContainer.appendChild(typeIconSpan);

                    if (typeInfo.isArea && areaMeters !== null && areaMeters !== undefined) {
                        const areaSpan = document.createElement("span");
                        const areaFormatted = areaMeters.toLocaleString('es-ES', { maximumFractionDigits: 0 });
                        areaSpan.textContent = `${areaFormatted} m²`;
                        areaSpan.style.fontSize = "10px";
                        areaSpan.style.fontWeight = "bold";
                        areaSpan.style.textAlign = "center";
                        areaSpan.style.lineHeight = "1";
                        areaSpan.style.whiteSpace = "nowrap";

                        if (areaMeters < 400) {
                            areaSpan.style.color = "red";
                            areaSpan.classList.add("area-blink");
                        }
                        else {
                            areaSpan.style.color = "blue";
                        }
                        areaSpan.title = `Área: ${areaFormatted} m²`;
                        typeContainer.appendChild(areaSpan);
                    }
                    cellContentWrapper.appendChild(typeContainer);

                    const cityStatusIconSpan = document.createElement("span");
                    cityStatusIconSpan.className = 'city-status-icon';
                    cityStatusIconSpan.style.fontSize = "18px";
                    cityStatusIconSpan.style.cursor = "pointer";

                    // ✅ LÓGICA ESPECIAL PARA RÍOS: Si es RIVER_STREAM y tiene ciudad, mostrar bandera para eliminar
                    const isRiver = currentCategoryKey === 'RIVER_STREAM';

                    if (isRiver && hasCity) {
                        // ❗ Mostrar bandera ROJA para ríos con ciudad (incorrecto, se debe eliminar)
                        cityStatusIconSpan.innerHTML = '❗';
                        cityStatusIconSpan.style.color = 'red';
                        cityStatusIconSpan.title = `${cityTitle}\n\n⚠️ Los ríos no deben tener ciudad.\nClick para limpiar ciudad.`;

                        // Al hacer click, mostrar modal de confirmación para eliminar ciudad
                        cityStatusIconSpan.addEventListener("click", async () => {
                            const confirmModal = document.createElement("div");
                            confirmModal.style.position = "fixed";
                            confirmModal.style.top = "50%";
                            confirmModal.style.left = "50%";
                            confirmModal.style.transform = "translate(-50%, -50%)";
                            confirmModal.style.background = "#fff";
                            confirmModal.style.border = "1px solid #aad";
                            confirmModal.style.padding = "28px 32px 20px 32px";
                            confirmModal.style.zIndex = "20000";
                            confirmModal.style.boxShadow = "0 4px 24px rgba(0,0,0,0.18)";
                            confirmModal.style.fontFamily = "sans-serif";
                            confirmModal.style.borderRadius = "10px";
                            confirmModal.style.textAlign = "center";
                            confirmModal.style.minWidth = "340px";

                            const iconElement = document.createElement("div");
                            iconElement.innerHTML = "🌊";
                            iconElement.style.fontSize = "38px";
                            iconElement.style.marginBottom = "10px";
                            confirmModal.appendChild(iconElement);

                            const message = document.createElement("div");
                            const venue = W.model.venues.getObjectById(id);
                            const placeName = venue?.attributes?.name?.value || venue?.attributes?.name || "este río";
                            message.innerHTML = `<b>¿Eliminar ciudad de "${placeName}"?</b>`;
                            message.style.fontSize = "20px";
                            message.style.marginBottom = "8px";
                            confirmModal.appendChild(message);

                            const cityDiv = document.createElement("div");
                            cityDiv.textContent = `Ciudad actual: ${cityTitle.replace('Ciudad: ', '')}`;
                            cityDiv.style.fontSize = "15px";
                            cityDiv.style.color = "#007bff";
                            cityDiv.style.marginBottom = "18px";
                            confirmModal.appendChild(cityDiv);

                            const buttonWrapper = document.createElement("div");
                            buttonWrapper.style.display = "flex";
                            buttonWrapper.style.justifyContent = "center";
                            buttonWrapper.style.gap = "18px";

                            const cancelBtn = document.createElement("button");
                            cancelBtn.textContent = "Cancelar";
                            cancelBtn.style.padding = "7px 18px";
                            cancelBtn.style.background = "#eee";
                            cancelBtn.style.border = "none";
                            cancelBtn.style.borderRadius = "4px";
                            cancelBtn.style.cursor = "pointer";
                            cancelBtn.addEventListener("click", () => confirmModal.remove());

                            const confirmBtn = document.createElement("button");
                            confirmBtn.textContent = "Eliminar";
                            confirmBtn.style.padding = "7px 18px";
                            confirmBtn.style.background = "#d9534f";
                            confirmBtn.style.color = "#fff";
                            confirmBtn.style.border = "none";
                            confirmBtn.style.borderRadius = "4px";
                            confirmBtn.style.cursor = "pointer";
                            confirmBtn.style.fontWeight = "bold";

                            confirmBtn.addEventListener("click", async () => {
                                const venue = W.model.venues.getObjectById(id);
                                if (!venue) {
                                    console.error("[WME_PLN]El lugar no está disponible o ya fue eliminado.");
                                    confirmModal.remove();
                                    return;
                                }
                                try {
                                    const UpdateObject = require("Waze/Action/UpdateObject");
                                    const action = new UpdateObject(venue, {
                                        cityID: null,
                                        streetID: null
                                    });
                                    W.model.actionManager.add(action);

                                    recordNormalizationEvent();
                                    const row = document.querySelector(`tr[data-place-id="${id}"]`);
                                    if (row) {
                                        const iconToUpdate = row.querySelector('.city-status-icon');
                                        if (iconToUpdate) {
                                            iconToUpdate.innerHTML = '✅';
                                            iconToUpdate.style.color = 'green';
                                            iconToUpdate.title = 'Sin ciudad (correcto para ríos)';
                                            iconToUpdate.style.pointerEvents = 'none';
                                        }
                                    }
                                } catch (e) {
                                    console.error("[WME PLN] Error al eliminar ciudad del río: " + e.message, e);
                                }
                                confirmModal.remove();
                            });

                            buttonWrapper.appendChild(cancelBtn);
                            buttonWrapper.appendChild(confirmBtn);
                            confirmModal.appendChild(buttonWrapper);

                            document.body.appendChild(confirmModal);
                        });
                    }
                    else if (isRiver && !hasCity) {
                        // ✅ RÍOS SIN CIUDAD: ESTADO CORRECTO
                        cityStatusIconSpan.innerHTML = '✅';
                        cityStatusIconSpan.style.color = 'green';
                        cityStatusIconSpan.title = 'Río sin ciudad (Correcto)';
                        cityStatusIconSpan.style.cursor = 'default';
                    }
                    else if (hasCity) {
                        // ✅ Lugares normales (NO ríos) con ciudad: mostrar check verde
                        cityStatusIconSpan.innerHTML = '✅';
                        cityStatusIconSpan.style.color = 'green';
                        cityStatusIconSpan.title = cityTitle;
                    }
                    else {
                        // 🚩 Lugares sin ciudad: mostrar bandera para asignar ciudad
                        cityStatusIconSpan.innerHTML = '🚩';
                        cityStatusIconSpan.style.color = 'red';
                        cityStatusIconSpan.title = cityTitle;

                        cityStatusIconSpan.addEventListener("click", async () => {
                            const coords = getPlaceCoordinates(W.model.venues.getObjectById(id), venueSDKForRender);
                            const placeLat = coords.lat;
                            const placeLon = coords.lon;

                            if (placeLat === null || placeLon === null) {
                                alert("No se pudieron obtener las coordenadas del lugar.");
                                return;
                            }
                            const allCities = Object.values(W.model.cities.objects)
                                .filter(city =>
                                    city &&
                                    city.attributes &&
                                    typeof city.attributes.name === 'string' &&
                                    city.attributes.name.trim() !== ''
                                );
                            const citiesWithDistance = allCities.map(city => {
                                if (!city.attributes.geoJSONGeometry ||
                                    !Array.isArray(city.attributes.geoJSONGeometry.coordinates) ||
                                    city.attributes.geoJSONGeometry.coordinates.length < 2)
                                    return null;
                                const cityLon = city.attributes.geoJSONGeometry.coordinates[0];
                                const cityLat = city.attributes.geoJSONGeometry.coordinates[1];
                                const distanceInMeters = calculateDistance(placeLat, placeLon, cityLat, cityLon);
                                const distanceInKm = distanceInMeters / 1000;
                                return {
                                    name: city.attributes.name,
                                    distance: distanceInKm,
                                    cityId: city.getID()
                                };
                            }).filter(Boolean);
                            const closestCities = citiesWithDistance.sort((a, b) => a.distance - b.distance).slice(0, 5);
                            const modal = document.createElement("div");
                            modal.style.position = "fixed";
                            modal.style.top = "50%";
                            modal.style.left = "50%";
                            modal.style.transform = "translate(-50%, -50%)";
                            modal.style.background = "#fff";
                            modal.style.border = "1px solid #aad";
                            modal.style.padding = "28px 32px 20px 32px";
                            modal.style.zIndex = "20000";
                            modal.style.boxShadow = "0 4px 24px rgba(0,0,0,0.18)";
                            modal.style.fontFamily = "sans-serif";
                            modal.style.borderRadius = "10px";
                            modal.style.textAlign = "center";
                            modal.style.minWidth = "340px";

                            const iconElement = document.createElement("div");
                            iconElement.innerHTML = "🏙️";
                            iconElement.style.fontSize = "38px";
                            iconElement.style.marginBottom = "10px";
                            modal.appendChild(iconElement);

                            const messageTitle = document.createElement("div");
                            messageTitle.innerHTML = `<b>Asignar ciudad al lugar</b>`;
                            messageTitle.style.fontSize = "20px";
                            messageTitle.style.marginBottom = "8px";
                            modal.appendChild(messageTitle);

                            const listDiv = document.createElement("div");
                            listDiv.style.textAlign = "left";
                            listDiv.style.marginTop = "10px";

                            if (closestCities.length === 0) {
                                const noCityLine = document.createElement("div");
                                noCityLine.textContent = "No se encontraron ciudades cercanas para mostrar.";
                                noCityLine.style.color = "#888";
                                listDiv.appendChild(noCityLine);
                            }
                            else {
                                closestCities.forEach((city, idx) => {
                                    const cityLine = document.createElement("div");
                                    cityLine.style.marginBottom = "8px";
                                    cityLine.style.display = "flex";
                                    cityLine.style.alignItems = "center";

                                    const radioInput = document.createElement("input");
                                    radioInput.type = "radio";
                                    radioInput.name = `city-selection-${id}`;
                                    radioInput.value = city.cityId;
                                    radioInput.id = `city-radio-${city.cityId}`;
                                    radioInput.style.marginRight = "10px";
                                    radioInput.style.marginTop = "0";
                                    if (idx === 0) radioInput.checked = true;

                                    const radioLabel = document.createElement("label");
                                    radioLabel.htmlFor = `city-radio-${city.cityId}`;
                                    radioLabel.style.cursor = "pointer";
                                    radioLabel.innerHTML = `<b>${city.name}</b> <span style="color: #666; font-size: 11px;">(ID: ${city.cityId})</span> <span style="color: #007bff;">${city.distance.toFixed(1)} km</span>`;
                                    cityLine.appendChild(radioInput);
                                    cityLine.appendChild(radioLabel);
                                    listDiv.appendChild(cityLine);
                                });
                            }
                            modal.appendChild(listDiv);

                            const buttonWrapper = document.createElement("div");
                            buttonWrapper.style.display = "flex";
                            buttonWrapper.style.justifyContent = "flex-end";
                            buttonWrapper.style.gap = "12px";
                            buttonWrapper.style.marginTop = "20px";

                            const applyBtn = document.createElement("button");
                            applyBtn.textContent = "Aplicar Ciudad";
                            applyBtn.style.padding = "8px 16px";
                            applyBtn.style.background = "#28a745";
                            applyBtn.style.color = "#fff";
                            applyBtn.style.border = "none";
                            applyBtn.style.borderRadius = "4px";
                            applyBtn.style.cursor = "pointer";
                            applyBtn.style.fontWeight = "bold";

                            applyBtn.addEventListener('click', () => {
                                const selectedRadio = modal.querySelector(`input[name="city-selection-${id}"]:checked`);
                                if (!selectedRadio) {
                                    alert("Por favor, selecciona una ciudad de la lista.");
                                    return;
                                }
                                const selectedCityId = parseInt(selectedRadio.value, 10);
                                const selectedCityName = selectedRadio.parentElement.querySelector('label b').textContent;

                                const venueToUpdate = W.model.venues.getObjectById(id);
                                if (!venueToUpdate) {
                                    alert("Error: No se pudo encontrar el lugar para actualizar. Puede que ya no esté visible.");
                                    modal.remove();
                                    return;
                                }

                                try {
                                    const UpdateObject = require("Waze/Action/UpdateObject");
                                    const action = new UpdateObject(venueToUpdate, { cityID: selectedCityId });
                                    W.model.actionManager.add(action);

                                    const row = document.querySelector(`tr[data-place-id="${id}"]`);
                                    if (row) {
                                        row.dataset.addressChanged = 'true';

                                        const iconToUpdate = row.querySelector('.city-status-icon');
                                        if (iconToUpdate) {
                                            iconToUpdate.innerHTML = '✅';
                                            iconToUpdate.style.color = 'green';
                                            iconToUpdate.title = `Ciudad asignada: ${selectedCityName}`;
                                            iconToUpdate.style.pointerEvents = 'none';
                                        }

                                        updateApplyButtonState(row, original);
                                    }

                                    modal.remove();

                                    showTemporaryMessage("Ciudad asignada correctamente. No olvides Guardar los cambios.", 4000, 'success');

                                } catch (e) {
                                    console.error("[WME PLN] Error al crear o ejecutar la acción de actualizar ciudad:", e);
                                    alert("Ocurrió un error al intentar asignar la ciudad: " + e.message);
                                    modal.remove();
                                }
                            });


                            const closeBtn = document.createElement("button");
                            closeBtn.textContent = "Cerrar";
                            closeBtn.style.padding = "8px 16px";
                            closeBtn.style.background = "#888";
                            closeBtn.style.color = "#fff";
                            closeBtn.style.border = "none";
                            closeBtn.style.borderRadius = "4px";
                            closeBtn.style.cursor = "pointer";
                            closeBtn.style.fontWeight = "bold";
                            closeBtn.addEventListener("click", () => modal.remove());

                            buttonWrapper.appendChild(applyBtn);
                            buttonWrapper.appendChild(closeBtn);
                            modal.appendChild(buttonWrapper);

                            closeBtn.style.padding = "8px 16px";
                            closeBtn.style.background = "#888";
                            closeBtn.style.color = "#fff";
                            closeBtn.style.border = "none";
                            closeBtn.style.borderRadius = "4px";
                            closeBtn.style.cursor = "pointer";
                            closeBtn.style.fontWeight = "bold";
                            closeBtn.addEventListener("click", () => modal.remove());

                            buttonWrapper.appendChild(applyBtn);
                            buttonWrapper.appendChild(closeBtn);
                            modal.appendChild(buttonWrapper);

                            document.body.appendChild(modal);
                        });
                    }

                    cellContentWrapper.appendChild(cityStatusIconSpan);
                    typeCityCell.appendChild(cellContentWrapper);
                    row.appendChild(typeCityCell);
                    const lockCell = document.createElement("td");
                    lockCell.textContent = lockRankEmoji;
                    lockCell.style.textAlign = "center";
                    lockCell.style.padding = "4px";
                    lockCell.style.width = "40px";
                    lockCell.style.fontSize = "18px";
                    row.appendChild(lockCell);
                    // Editor
                    const editorCell = document.createElement("td");
                    editorCell.textContent = editor || "Desconocido";
                    editorCell.title = "Último editor";
                    editorCell.style.padding = "4px";
                    editorCell.style.width = "140px";
                    editorCell.style.textAlign = "center";
                    row.appendChild(editorCell);
                    // Nombre Actual
                    const originalCell = document.createElement("td");
                    const inputOriginal = document.createElement("textarea");
                    inputOriginal.rows = 3; inputOriginal.readOnly = true;
                    inputOriginal.style.whiteSpace = "pre-wrap";
                    const venueLive = W.model.venues.getObjectById(id);
                    const currentLiveName = venueLive?.attributes?.name?.value || venueLive?.attributes?.name || "";
                    inputOriginal.value = currentLiveName || original;
                    if (currentLiveName.trim().toLowerCase() !== normalized.trim().toLowerCase()) {
                        inputOriginal.style.border = "1px solid red";
                        inputOriginal.title = "Este nombre es distinto del original mostrado en el panel";
                    }
                    inputOriginal.disabled = true;
                    inputOriginal.style.width = "270px";
                    inputOriginal.style.backgroundColor = "#eee";
                    originalCell.style.padding = "4px";
                    originalCell.style.width = "270px";
                    originalCell.style.display = "flex";
                    originalCell.style.alignItems = "flex-start";
                    originalCell.style.verticalAlign = "middle";
                    inputOriginal.style.flex = "1";
                    inputOriginal.style.height = "100%";
                    inputOriginal.style.boxSizing = "border-box";
                    originalCell.appendChild(inputOriginal);
                    row.appendChild(originalCell);
                    const alertCell = document.createElement("td");
                    alertCell.style.width = "30px";
                    alertCell.style.textAlign = "center";
                    alertCell.style.verticalAlign = "middle";
                    alertCell.style.padding = "4px";

                    // Alerta de Parqueadero Punto (debe ser Área)
                    if (item.isParkingPointError) {
                        const parkingAlert = document.createElement("div");
                        parkingAlert.textContent = "🅿️";
                        parkingAlert.title = "⚠️ Parqueadero tipo PUNTO. Debe ser ÁREA.\nHaz click en el icono de tipo (⊙) para convertirlo.";
                        parkingAlert.style.cursor = "help";
                        parkingAlert.style.fontSize = "16px";
                        alertCell.appendChild(parkingAlert);
                    }

                    if (isDuplicate) {
                        const warningIcon = document.createElement("span");
                        warningIcon.textContent = " ⚠️";
                        warningIcon.style.fontSize = "16px";
                        let tooltipText = `Nombre de lugar duplicado cercano.`;
                        if (duplicatePartners && duplicatePartners.length > 0) {
                            const partnerDetails = duplicatePartners.map(p => `Línea ${p.line}: "${p.originalName}"`).join(", ");
                            tooltipText += ` Duplicado(s) con: ${partnerDetails}.`;
                        }
                        else {
                            tooltipText += ` No se encontraron otros duplicados cercanos específicos.`;
                        }
                        warningIcon.title = tooltipText;
                        alertCell.appendChild(warningIcon);
                    }
                    // Lógica para el icono de advertencia por horarios que se cruzan
                    if (hasOverlappingHours) {
                        const clockIcon = document.createElement("span");
                        clockIcon.textContent = " ⏰";
                        clockIcon.style.fontSize = "16px";
                        clockIcon.style.color = "red";
                        clockIcon.title = "¡Alerta! Este lugar tiene horarios que se cruzan.";
                        alertCell.appendChild(clockIcon);
                    }
                    // Lógica para el icono de advertencia por ediciones pendientes
                    if (hasPendingEdits) {
                        const editLink = document.createElement("a");
                        editLink.href = placePermalink;
                        editLink.style.display = "inline-flex";
                        editLink.style.alignItems = "center";
                        editLink.style.justifyContent = "center";
                        editLink.style.textDecoration = "none";
                        editLink.style.marginLeft = "2px";
                        editLink.title = `¡Alerta! Este lugar tiene ediciones pendientes de aprobación.\nPerma: ${placePermalink}`;
                        editLink.dataset.permalink = placePermalink;
                        editLink.addEventListener("click", (event) => {
                            handlePlaceLinkNavigation(event, id, original, placePermalink, venueSDKForRender);
                        });

                        const editIcon = document.createElement("span");
                        editIcon.textContent = "📝";
                        editIcon.style.fontSize = "16px";
                        editIcon.style.color = "#ff9800"; // Un color naranja para destacar

                        editLink.appendChild(editIcon);
                        alertCell.appendChild(editLink);
                    }
                    row.appendChild(alertCell);
                    const suggestionCell = document.createElement("td");
                    suggestionCell.style.display = "flex";
                    suggestionCell.style.flexDirection = "column"; // Para que los botones queden debajo
                    suggestionCell.style.alignItems = "flex-start";
                    suggestionCell.style.justifyContent = "flex-start";
                    suggestionCell.style.padding = "4px";
                    suggestionCell.style.width = "270px";
                    const inputReplacement = document.createElement("textarea");
                    inputReplacement.className = 'replacement-input';

                    // Manejar "Requiere corrección manual" como placeholder
                    if (normalized === 'Requiere corrección manual') {
                        // NO asignar el texto como value, usar placeholder
                        inputReplacement.value = '';
                        inputReplacement.placeholder = 'Requiere corrección manual';
                        inputReplacement.style.border = "2px solid red";
                        // El color del placeholder se controla con CSS ::placeholder
                        inputReplacement.style.setProperty('--placeholder-color', 'red');
                    } else {
                        // Para nombres normales, asignar el valor
                        try {
                            inputReplacement.value = normalized;
                        }
                        catch (_) {
                            inputReplacement.value = normalized;
                        }
                    }

                    inputReplacement.style.width = "100%";
                    inputReplacement.style.height = "100%";
                    inputReplacement.style.boxSizing = "border-box";
                    inputReplacement.style.whiteSpace = "pre-wrap";
                    inputReplacement.rows = 3;

                    suggestionCell.appendChild(inputReplacement);

                    // ✨ BOTONES DE PALABRAS CLAVE POR CATEGORÍA (28-nov-2024)

                    // Crear contenedor para botones de prefijos rápidos
                    const quickPrefixContainer = document.createElement("div");
                    quickPrefixContainer.style.display = "flex";
                    quickPrefixContainer.style.flexWrap = "wrap";
                    quickPrefixContainer.style.gap = "2px";
                    quickPrefixContainer.style.marginTop = "2px";
                    quickPrefixContainer.style.width = "100%";
                    quickPrefixContainer.className = "quick-prefix-buttons"; // Identificador para encontrarlo

                    // Función para generar botones según categoría
                    const generatePrefixButtons = (categoryKey) => {
                        quickPrefixContainer.innerHTML = ""; // Limpiar botones existentes

                        if (!window.dynamicCategoryRules || !categoryKey) return;

                        const categoryRule = window.dynamicCategoryRules.find(r =>
                            r.categoryKey.toUpperCase() === categoryKey.toUpperCase()
                        );

                        // ✨ LÓGICA DINÁMICA (28-nov-2024): Usar palabras de columna F (prefixWords)
                        if (categoryRule && categoryRule.prefixWords) {
                            // Las palabras están separadas por ";"
                            const words = categoryRule.prefixWords.split(';')
                                .map(w => w.trim())
                                .filter(w => w.length > 0);


                            words.forEach(word => {
                                // ✨ VERIFICAR si la palabra ya está en el nombre actual
                                // Solo crear el botón si la palabra NO está presente
                                const currentName = inputReplacement.value.trim().toLowerCase();
                                const wordLower = word.toLowerCase();
                                const nameWords = currentName.split(/\s+/);

                                // Si alguna palabra del nombre contiene esta keyword, no crear el botón
                                if (nameWords.some(w => w.includes(wordLower))) {
                                    return; // Saltar este botón
                                }

                                // Usar la palabra EXACTA como pidió el usuario
                                const btn = document.createElement("button");
                                btn.textContent = word;
                                btn.style.padding = "1px 4px";
                                btn.style.fontSize = "9px";
                                btn.style.backgroundColor = "#007bff";
                                btn.style.color = "white";
                                btn.style.border = "none";
                                btn.style.borderRadius = "2px";
                                btn.style.cursor = "pointer";
                                btn.style.whiteSpace = "nowrap";
                                btn.style.transition = "background-color 0.2s";
                                btn.title = `Click para añadir "${word}" al inicio del nombre`;

                                btn.addEventListener("mouseenter", () => {
                                    btn.style.backgroundColor = "#0056b3";
                                });
                                btn.addEventListener("mouseleave", () => {
                                    btn.style.backgroundColor = "#007bff";
                                });

                                btn.addEventListener("click", (e) => {
                                    e.preventDefault();
                                    e.stopPropagation();

                                    // Desenfocar el input para evitar que se inserte en la posición del cursor
                                    inputReplacement.blur();

                                    let currentValue = inputReplacement.value.trim();

                                    // Buscar y reemplazar cualquier keyword existente en el nombre (no solo al inicio)
                                    let wasReplaced = false;
                                    words.forEach(kw => {
                                        // Saltar la palabra actual que estamos añadiendo
                                        if (kw === word) return;

                                        const kwCap = kw.charAt(0).toUpperCase() + kw.slice(1).toLowerCase();
                                        // ✨ Usar regex ESTRICTO con lookbehind/lookahead para evitar coincidencias parciales
                                        // Solo coincide si la palabra está rodeada de NO-word characters
                                        const kwPattern = new RegExp(`(?<![\\p{L}\\p{N}_-])${kwCap.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\p{L}\\p{N}_-])`, 'giu');
                                        if (kwPattern.test(currentValue)) {
                                            currentValue = currentValue.replace(kwPattern, word);
                                            wasReplaced = true;
                                        }
                                    });

                                    // Si no se reemplazó ninguna, añadir al inicio
                                    let newValue;
                                    if (!wasReplaced) {
                                        // SIEMPRE al inicio, sin importar dónde esté el cursor
                                        newValue = currentValue.length > 0
                                            ? `${word} ${currentValue.trim()}`
                                            : word;
                                    } else {
                                        newValue = currentValue;
                                    }

                                    // Actualizar valor y mover cursor al final
                                    inputReplacement.value = newValue;
                                    inputReplacement.setSelectionRange(newValue.length, newValue.length);
                                    inputReplacement.dispatchEvent(new Event('input'));

                                    // Feedback visual
                                    btn.style.backgroundColor = "#28a745";
                                    setTimeout(() => {
                                        btn.style.backgroundColor = "#007bff";
                                    }, 300);
                                });

                                quickPrefixContainer.appendChild(btn);
                            });
                        }

                        // ✨ LÓGICA DE ARTÍCULOS (30-nov-2024): Botones morados para capitalizar artículos
                        const articles = ['el', 'la', 'los', 'las', 'un', 'una', 'unos', 'unas', 'del', 'al', 'de'];
                        const currentNameLower = inputReplacement.value.trim().toLowerCase();
                        const nameWords = currentNameLower.split(/\s+/);

                        // Encontrar artículos presentes en el nombre
                        const foundArticles = articles.filter(art => nameWords.includes(art));

                        if (foundArticles.length > 0) {
                            // Separador visual si hay botones previos
                            if (quickPrefixContainer.children.length > 0) {
                                const sep = document.createElement("span");
                                sep.style.borderLeft = "1px solid #ccc";
                                sep.style.margin = "0 4px";
                                quickPrefixContainer.appendChild(sep);
                            }

                            foundArticles.forEach(art => {
                                const artCap = art.charAt(0).toUpperCase() + art.slice(1);
                                const btn = document.createElement("button");
                                btn.textContent = artCap;
                                btn.style.padding = "1px 4px";
                                btn.style.fontSize = "9px";
                                btn.style.backgroundColor = "#6f42c1"; // Morado
                                btn.style.color = "white";
                                btn.style.border = "none";
                                btn.style.borderRadius = "2px";
                                btn.style.cursor = "pointer";
                                btn.style.whiteSpace = "nowrap";
                                btn.style.transition = "background-color 0.2s";
                                btn.title = `Click para cambiar "${art}" a "${artCap}"`;

                                btn.addEventListener("mouseenter", () => {
                                    btn.style.backgroundColor = "#5a32a3";
                                });
                                btn.addEventListener("mouseleave", () => {
                                    btn.style.backgroundColor = "#6f42c1";
                                });

                                btn.addEventListener("click", (e) => {
                                    e.preventDefault();
                                    e.stopPropagation();
                                    inputReplacement.blur();

                                    let currentValue = inputReplacement.value;
                                    // Reemplazar solo la instancia exacta de la palabra minúscula
                                    // Usamos regex con word boundaries para no reemplazar dentro de otras palabras
                                    const regex = new RegExp(`\\b${art}\\b`, 'g');
                                    const newValue = currentValue.replace(regex, artCap);

                                    inputReplacement.value = newValue;
                                    inputReplacement.dispatchEvent(new Event('input'));

                                    // Feedback visual
                                    btn.style.backgroundColor = "#28a745";
                                    setTimeout(() => {
                                        btn.style.backgroundColor = "#6f42c1";
                                    }, 300);
                                });

                                quickPrefixContainer.appendChild(btn);
                            });
                        }
                    };

                    // Generar botones iniciales
                    generatePrefixButtons(currentCategoryKey);

                    // Guardar referencia para poder regenerar después
                    suggestionCell.dataset.regenerateButtons = id; // Usar el ID del lugar
                    suggestionCell._generatePrefixButtons = generatePrefixButtons;

                    // Añadir contenedor (siempre, aunque esté vacío inicialmente)
                    suggestionCell.appendChild(quickPrefixContainer);

                    // Añade la celda de sugerencias al renglón


                    function debounce(func, delay) {
                        let timeout;
                        return function (...args) {
                            clearTimeout(timeout);
                            timeout = setTimeout(() => func.apply(this, args), delay);
                        };
                    }
                    const checkAndUpdateApplyButton = () => {
                        const nameIsDifferent = inputReplacement.value.trim() !== original.trim();
                        const categoryWasChanged = row.dataset.categoryChanged === 'true';
                        if (nameIsDifferent || categoryWasChanged) {
                            applyButton.disabled = false;
                            applyButton.style.opacity = "1";
                            const successIcon = applyButtonWrapper.querySelector('span');
                            if (successIcon) successIcon.remove();
                        }
                        else {
                            applyButton.disabled = true;
                            applyButton.style.opacity = "0.5";
                        }
                    };

                    inputReplacement.addEventListener('input', debounce(checkAndUpdateApplyButton, 300));
                    let autoApplied = false;
                    if (Object.values(allSuggestions).flat().some(s => s.fuente === 'excluded' && s.similarity === 1)) {
                        autoApplied = true;
                    }
                    if (autoApplied) {
                        inputReplacement.style.backgroundColor = "#c8e6c9";
                        inputReplacement.title = "Reemplazo automático aplicado (palabra especial con 100% similitud)";
                    }
                    else if (Object.values(allSuggestions).flat().some(s => s.fuente === 'excluded')) {
                        inputReplacement.style.backgroundColor = "#fff3cd";
                        inputReplacement.title = "Contiene palabra especial reemplazada";
                    }
                    function debounce(func, delay) {
                        let timeout;
                        return function (...args) {
                            clearTimeout(timeout);
                            timeout = setTimeout(() => func.apply(this, args), delay);
                        };
                    }
                    inputReplacement.addEventListener('input', debounce(() => {
                        if (inputReplacement.value.trim() !== original) {
                            applyButton.disabled = false;
                            applyButton.style.color = "";
                        }
                        else {
                            applyButton.disabled = true;
                            applyButton.style.color = "#bbb";
                        }
                    }, 300));
                    inputOriginal.addEventListener('input', debounce(() => {
                    }, 300));
                    const suggestionListCell = document.createElement("td");
                    suggestionListCell.style.padding = "4px";
                    suggestionListCell.style.width = "180px";
                    const suggestionContainer = document.createElement('div');
                    const palabrasYaProcesadas = new Set();
                    const currentPlaceSuggestions = allSuggestions[id];
                    if (currentPlaceSuggestions) {
                        Object.entries(currentPlaceSuggestions).forEach(([originalWordForThisPlace, suggestionsArray]) => {
                            if (Array.isArray(suggestionsArray)) {
                                suggestionsArray.forEach(s => {
                                    let icono = '';
                                    let textoSugerencia = '';
                                    let colorFondo = '#f9f9f9';
                                    let esSugerenciaValida = false;
                                    let palabraAReemplazar = originalWordForThisPlace;
                                    let palabraAInsertar = s.word;
                                    switch (s.fuente) {
                                        case 'original_preserved':
                                            esSugerenciaValida = true;
                                            icono = '⚙️';
                                            textoSugerencia = `¿"${originalWordForThisPlace}" x "${s.word}"?`;
                                            colorFondo = '#f0f0f0';
                                            palabraAReemplazar = originalWordForThisPlace;
                                            palabraAInsertar = s.word;
                                            break;
                                        case 'excluded':
                                            if (s.similarity < 1 || (s.similarity === 1 && originalWordForThisPlace.toLowerCase() !== s.word.toLowerCase())) {
                                                esSugerenciaValida = true;
                                                icono = '🏷️';
                                                textoSugerencia = `¿"${originalWordForThisPlace}" x "${s.word}"? (sim. ${(s.similarity * 100).toFixed(0)}%)`;
                                                colorFondo = '#f3f9ff';
                                                palabraAReemplazar = originalWordForThisPlace;
                                                palabraAInsertar = s.word;
                                                palabrasYaProcesadas.add(originalWordForThisPlace.toLowerCase());
                                            }
                                            break;
                                        case 'dictionary':
                                            esSugerenciaValida = true;
                                            icono = '📘';
                                            colorFondo = '#e6ffe6';
                                            const normalizedSuggestedWordForDisplay = normalizeWordInternal(s.word, true, false);

                                            textoSugerencia = `¿"${originalWordForThisPlace}" x "${normalizedSuggestedWordForDisplay}"? (sim. ${(s.similarity * 100).toFixed(0)}%)`;
                                            palabraAReemplazar = originalWordForThisPlace;
                                            palabraAInsertar = normalizedSuggestedWordForDisplay;
                                            break;

                                        case 'dictionary_tilde':
                                            esSugerenciaValida = true;
                                            icono = '✍️';
                                            colorFondo = '#ffe6e6';
                                            textoSugerencia = `¿"${originalWordForThisPlace}" x "${s.word}"? (Corregir Tilde)`;
                                            palabraAReemplazar = originalWordForThisPlace;
                                            palabraAInsertar = s.word;
                                            break;
                                    }
                                    if (esSugerenciaValida) {
                                        const suggestionDiv = document.createElement("div");
                                        suggestionDiv.innerHTML = `${icono} ${textoSugerencia}`;
                                        suggestionDiv.style.cursor = "pointer";
                                        suggestionDiv.style.padding = "2px 4px";
                                        suggestionDiv.style.margin = "2px 0";
                                        suggestionDiv.style.border = "1px solid #ddd";
                                        suggestionDiv.style.borderRadius = "3px";
                                        suggestionDiv.style.backgroundColor = colorFondo;

                                        suggestionDiv.addEventListener("click", () => {
                                            const currentSuggestedValue = inputReplacement.value;
                                            const searchRegex = new RegExp("\\b" + escapeRegExp(palabraAReemplazar) + "\\b", "gi");
                                            const newSuggestedValue = currentSuggestedValue.replace(searchRegex, palabraAInsertar);
                                            if (inputReplacement.value !== newSuggestedValue) {
                                                inputReplacement.value = newSuggestedValue;
                                            }
                                            checkAndUpdateApplyButton();
                                        });
                                        suggestionContainer.appendChild(suggestionDiv);
                                    }
                                });
                            }
                            else {
                                console.warn(`[WME_PLN][DEBUG] suggestionsArray para "${originalWordForThisPlace}" no es un array o es undefined:`, suggestionsArray);
                            }
                        });
                    }
                    suggestionListCell.appendChild(suggestionContainer);
                    row.appendChild(suggestionCell);
                    row.appendChild(suggestionListCell);
                    const categoryCell = document.createElement("td");
                    categoryCell.style.padding = "4px";
                    categoryCell.style.width = "130px";
                    categoryCell.style.textAlign = "center";
                    const currentCategoryDiv = document.createElement("div");
                    currentCategoryDiv.style.display = "flex";
                    currentCategoryDiv.style.flexDirection = "column";
                    currentCategoryDiv.style.alignItems = "center";
                    currentCategoryDiv.style.gap = "2px";
                    const currentCategoryText = document.createElement("span");
                    currentCategoryText.textContent = currentCategoryTitle;
                    currentCategoryText.title = `Categoría Actual: ${currentCategoryTitle}`;
                    currentCategoryDiv.appendChild(currentCategoryText);
                    const currentCategoryIconDisplay = document.createElement("span");
                    currentCategoryIconDisplay.textContent = currentCategoryIcon;
                    currentCategoryIconDisplay.style.fontSize = "20px";
                    currentCategoryDiv.appendChild(currentCategoryIconDisplay);
                    categoryCell.appendChild(currentCategoryDiv);
                    row.appendChild(categoryCell);
                    const recommendedCategoryCell = document.createElement("td");
                    recommendedCategoryCell.style.padding = "4px";
                    recommendedCategoryCell.style.width = "130px";
                    recommendedCategoryCell.style.textAlign = "left";
                    const categoryDropdown = createRecommendedCategoryDropdown(
                        id,
                        currentCategoryKey,
                        dynamicCategorySuggestions
                    );
                    recommendedCategoryCell.appendChild(categoryDropdown);
                    row.appendChild(recommendedCategoryCell);

                    const actionCell = document.createElement("td");
                    actionCell.style.padding = "4px";
                    actionCell.style.width = "120px";
                    const buttonGroup = document.createElement("div");
                    buttonGroup.style.display = "flex";
                    buttonGroup.style.flexDirection = "column";
                    buttonGroup.style.gap = "4px";
                    buttonGroup.style.alignItems = "flex-start";
                    const commonButtonStyle = {
                        width: "40px",
                        height: "30px",
                        minWidth: "40px",
                        minHeight: "30px",
                        padding: "4px",
                        border: "1px solid #ccc",
                        borderRadius: "4px",
                        backgroundColor: "#f0f0f0",
                        color: "#555",
                        cursor: "pointer",
                        fontSize: "18px",
                        display: "flex",
                        justifyContent: "center",
                        alignItems: "center",
                        boxSizing: "border-box"
                    };
                    const applyButton = document.createElement("button");
                    Object.assign(applyButton.style, commonButtonStyle);
                    applyButton.textContent = "✔";
                    applyButton.title = "Aplicar sugerencia";
                    applyButton.disabled = true;
                    applyButton.style.opacity = "0.5";
                    const applyButtonWrapper = document.createElement("div");
                    applyButtonWrapper.style.display = "flex";
                    applyButtonWrapper.style.alignItems = "center";
                    applyButtonWrapper.style.gap = "5px";
                    applyButtonWrapper.appendChild(applyButton);
                    buttonGroup.appendChild(applyButtonWrapper);
                    let deleteButton = document.createElement("button");
                    Object.assign(deleteButton.style, commonButtonStyle);
                    deleteButton.textContent = "🗑️";
                    deleteButton.title = "Eliminar lugar";
                    const deleteButtonWrapper = document.createElement("div");
                    Object.assign(deleteButtonWrapper.style, {
                        display: "flex",
                        alignItems: "center",
                        gap: "5px"
                    });
                    deleteButtonWrapper.appendChild(deleteButton);
                    buttonGroup.appendChild(deleteButtonWrapper);
                    const addToExclusionBtn = document.createElement("button");
                    Object.assign(addToExclusionBtn.style, commonButtonStyle);
                    addToExclusionBtn.textContent = "🏷️";
                    addToExclusionBtn.title = "Marcar palabra como especial (no se modifica)";
                    buttonGroup.appendChild(addToExclusionBtn);
                    actionCell.appendChild(buttonGroup);
                    row.appendChild(actionCell);

                    // Contenedor para alinear el botón de excluir y el nuevo checkbox
                    const excludeContainer = document.createElement("div");
                    excludeContainer.style.display = "flex";
                    excludeContainer.style.alignItems = "center";
                    excludeContainer.style.gap = "8px"; // Espacio entre el botón y el checkbox

                    const excludePlaceBtn = document.createElement("button");
                    Object.assign(excludePlaceBtn.style, commonButtonStyle);
                    excludePlaceBtn.textContent = "📵";
                    excludePlaceBtn.title = "Excluir este lugar inmediatamente"; // Tooltip actualizado

                    const excludeOnApplyCheckbox = document.createElement("input");
                    excludeOnApplyCheckbox.type = "checkbox";
                    excludeOnApplyCheckbox.id = `pln-exclude-checkbox-${id}`; // ID único
                    excludeOnApplyCheckbox.title = "Marcar para excluir al presionar 'Aplicar'";
                    excludeOnApplyCheckbox.style.width = "18px";
                    excludeOnApplyCheckbox.style.height = "18px";
                    excludeOnApplyCheckbox.style.cursor = "pointer";

                    // LEER LA PREFERENCIA LOCAL DEL ITEM O USAR EL DEFAULT GLOBAL
                    const defaultExcludeOnApply = localStorage.getItem('wme_pln_default_exclude_on_apply') !== 'false';

                    // Inicializar propiedad local si no existe
                    if (typeof item._excludeOnApply === 'undefined') {
                        item._excludeOnApply = defaultExcludeOnApply;
                    }

                    excludeOnApplyCheckbox.checked = item._excludeOnApply;

                    // ACTUALIZAR SOLO EL ESTADO LOCAL AL CAMBIAR
                    excludeOnApplyCheckbox.addEventListener('change', () => {
                        item._excludeOnApply = excludeOnApplyCheckbox.checked;
                    });

                    excludeContainer.appendChild(excludePlaceBtn);
                    excludeContainer.appendChild(excludeOnApplyCheckbox);
                    buttonGroup.appendChild(excludeContainer); // Añadimos el contenedor al grupo de botones

                    actionCell.appendChild(buttonGroup);
                    row.appendChild(actionCell);
                    // Eventos de los botones
                    applyButton.addEventListener("click", async () => {
                        // Validación de seguridad: No permitir guardar el nombre placeholder
                        if (inputReplacement.value.trim() === "Requiere corrección manual") {
                            alert("⚠️ Debes modificar el nombre antes de guardar.\n\n'Requiere corrección manual' es solo un indicador y no puede ser usado como nombre del lugar.");
                            return;
                        }

                        const row = applyButton.closest('tr');
                        const venueObj = W.model.venues.getObjectById(id);
                        let anActionWasPerformed = false; // Usaremos esta bandera para saber si se hizo algo

                        // 1. Manejar la exclusión si el checkbox está marcado
                        const excludeCheckbox = document.getElementById(`pln-exclude-checkbox-${id}`);
                        if (excludeCheckbox && excludeCheckbox.checked) {
                            const placeNameToExclude = row.querySelectorAll('textarea')[1].value || original || `ID: ${id}`;
                            excludedPlaces.set(id, placeNameToExclude);

                            saveExcludedPlacesToLocalStorage();
                            showTemporaryMessage(`'${placeNameToExclude}' añadido a excluidos`, 3000, 'warning');
                            //plnToast(`'${placeNameToExclude}' añadido a excluidos.`, 2500);
                            anActionWasPerformed = true; // Marcamos que se realizó una acción
                        }

                        // 2. Manejar cambios de nombre y categoría
                        const newName = inputReplacement.value.trim();
                        const nameWasChanged = venueObj && (newName !== (venueObj.attributes.name?.value || venueObj.attributes.name || ""));
                        const categoryWasChanged = row.dataset.categoryChanged === 'true';

                        if (nameWasChanged || categoryWasChanged) {
                            if (!venueObj) {
                                plnToast("Error: No se pudo aplicar el cambio porque el lugar no está disponible.", 4000);
                            }
                            else {
                                try {
                                    if (nameWasChanged) {
                                        const UpdateObject = require("Waze/Action/UpdateObject");
                                        const action = new UpdateObject(venueObj, { name: newName });
                                        W.model.actionManager.add(action);
                                    }
                                    showTemporaryMessage("Cambios aplicados. Presione 'Guardar' en WME.", 3000, 'success');
                                    recordNormalizationEvent();
                                    anActionWasPerformed = true; // Marcamos que se realizó una acción
                                }
                                catch (e) {
                                    alert("Error al actualizar: " + e.message);
                                    console.error("[WME_PLN] Error al actualizar lugar:", e);
                                }
                            }
                        }

                        // 3. Procesamiento visual final SOLO SI se realizó alguna acción
                        if (anActionWasPerformed) {
                            markRowAsProcessed(row, 'applied'); // Marcamos la fila como procesada
                            if (window.checkBatchCompletion) window.checkBatchCompletion(); // Actualizar contador
                        }
                        else {
                            // Este mensaje solo aparecerá si no se hizo NADA
                            showTemporaryMessage("No hay cambios para aplicar.", 3000, 'warning');
                        }
                    });
                    deleteButton.addEventListener("click", () => {
                        const confirmModal = document.createElement("div");
                        confirmModal.style.position = "fixed";
                        confirmModal.style.top = "50%";
                        confirmModal.style.left = "50%";
                        confirmModal.style.transform = "translate(-50%, -50%)";
                        confirmModal.style.background = "#fff";
                        confirmModal.style.border = "1px solid #aad";
                        confirmModal.style.padding = "28px 32px 20px 32px";
                        confirmModal.style.zIndex = "20000";
                        confirmModal.style.boxShadow = "0 4px 24px rgba(0,0,0,0.18)";
                        confirmModal.style.fontFamily = "sans-serif";
                        confirmModal.style.borderRadius = "10px";
                        confirmModal.style.textAlign = "center";
                        confirmModal.style.minWidth = "340px";
                        const iconElement = document.createElement("div");
                        iconElement.innerHTML = "⚠️";
                        iconElement.style.fontSize = "38px";
                        iconElement.style.marginBottom = "10px";
                        confirmModal.appendChild(iconElement);
                        const message = document.createElement("div");
                        const venue = W.model.venues.getObjectById(id);
                        const placeName = venue?.attributes?.name?.value || venue?.attributes?.name || "este lugar";
                        message.innerHTML = `<b>¿Eliminar "${placeName}"?</b>`;
                        message.style.fontSize = "20px";
                        message.style.marginBottom = "8px";
                        confirmModal.appendChild(message);
                        const nameDiv = document.createElement("div");
                        nameDiv.textContent = `"${placeName}"`;
                        nameDiv.style.fontSize = "15px";
                        nameDiv.style.color = "#007bff";
                        nameDiv.style.marginBottom = "18px";
                        confirmModal.appendChild(nameDiv);
                        const buttonWrapper = document.createElement("div");
                        buttonWrapper.style.display = "flex";
                        buttonWrapper.style.justifyContent = "center";
                        buttonWrapper.style.gap = "18px";
                        const cancelBtn = document.createElement("button");
                        cancelBtn.textContent = "Cancelar";
                        cancelBtn.style.padding = "7px 18px";
                        cancelBtn.style.background = "#eee";
                        cancelBtn.style.border = "none";
                        cancelBtn.style.borderRadius = "4px";
                        cancelBtn.style.cursor = "pointer";
                        cancelBtn.addEventListener("click", () => confirmModal.remove());
                        const confirmBtn = document.createElement("button");
                        confirmBtn.textContent = "Eliminar";
                        confirmBtn.style.padding = "7px 18px";
                        confirmBtn.style.background = "#d9534f";
                        confirmBtn.style.color = "#fff";
                        confirmBtn.style.border = "none";
                        confirmBtn.style.borderRadius = "4px";
                        confirmBtn.style.cursor = "pointer";
                        confirmBtn.style.fontWeight = "bold";
                        confirmBtn.addEventListener("click", () => {
                            const venue = W.model.venues.getObjectById(id);
                            if (!venue) {
                                console.error("[WME_PLN]El lugar no está disponible o ya fue eliminado.");
                                confirmModal.remove();
                                return;
                            }
                            try {
                                const DeleteObject = require("Waze/Action/DeleteObject");
                                const action = new DeleteObject(venue);
                                W.model.actionManager.add(action);
                                recordNormalizationEvent();
                                const row = deleteButton.closest('tr');
                                markRowAsProcessed(row, 'deleted');
                                if (window.checkBatchCompletion) window.checkBatchCompletion();

                                deleteButton.disabled = true;
                                deleteButton.style.color = "#bbb";
                                deleteButton.style.opacity = "0.5";
                                applyButton.disabled = true;
                                applyButton.style.color = "#bbb";
                                applyButton.style.opacity = "0.5";
                                const successIcon = document.createElement("span");
                                successIcon.textContent = " 🗑️";
                                successIcon.style.marginLeft = "0";
                                successIcon.style.fontSize = "20px";
                                deleteButtonWrapper.appendChild(successIcon);
                            }
                            catch (e) {
                                console.error("[WME_PLN] Error al eliminar lugar: " + e.message, e);
                            }
                            confirmModal.remove();
                        });
                        buttonWrapper.appendChild(cancelBtn);
                        buttonWrapper.appendChild(confirmBtn);
                        confirmModal.appendChild(buttonWrapper);
                        document.body.appendChild(confirmModal);
                    });
                    // Add to Exclusion Button
                    addToExclusionBtn.addEventListener("click", () => {
                        const words = original.split(/\s+/);
                        const modal = document.createElement("div");

                        modal.style.position = "fixed";
                        modal.style.top = "50%";
                        modal.style.left = "50%";
                        modal.style.transform = "translate(-50%, -50%)";
                        modal.style.background = "#fff";
                        modal.style.border = "1px solid #aad";
                        modal.style.padding = "28px 32px 20px 32px";
                        modal.style.zIndex = "20000";
                        modal.style.boxShadow = "0 4px 24px rgba(0,0,0,0.18)";
                        modal.style.fontFamily = "sans-serif";
                        modal.style.borderRadius = "10px";
                        modal.style.textAlign = "center";
                        modal.style.minWidth = "340px";
                        const title = document.createElement("h4");
                        title.textContent = "Agregar palabra a especiales";
                        modal.appendChild(title);
                        const instructions = document.createElement("p");
                        const list = document.createElement("ul");
                        list.style.listStyle = "none";
                        list.style.padding = "0";
                        words.forEach(w => {
                            const wordToTest = w.trim();
                            if (wordToTest === '') return;

                            const lowerW = w.trim().toLowerCase();
                            const isCommon = commonWords.has(lowerW);

                            // ✨ OPTIMIZACIÓN: Usar cache en lugar de Array.from + búsqueda directa
                            const lookup = isWordExcludedOrInDictionary(lowerW);
                            const isExcluded = lookup.isExcluded;
                            const isInDictionary = lookup.isInDictionary;

                            // Si la palabra es común, ya está excluida O está en el diccionario, no la mostramos.
                            if (isCommon || isExcluded || isInDictionary) return;


                            if (!/[a-zA-ZáéíóúÁÉÍÓÚñÑüÜ0-9]/.test(lowerW) || /^[^a-zA-Z0-9]+$/.test(lowerW)) return;
                            // Ya verificamos isExcluded arriba, no necesitamos hacerlo de nuevo
                            if (isCommon) return;
                            const li = document.createElement("li");
                            const checkbox = document.createElement("input");
                            checkbox.type = "checkbox";
                            checkbox.value = w;
                            checkbox.id = `cb-exc-${w.replace(/[^a-zA-Z0-9]/g, "")}`;
                            li.appendChild(checkbox);
                            const label = document.createElement("label");
                            label.htmlFor = checkbox.id;
                            label.appendChild(document.createTextNode(" " + w));
                            li.appendChild(label);
                            list.appendChild(li);
                        });
                        // Si después de filtrar no quedan palabras, se notifica al usuario.
                        if (list.childElementCount === 0) {
                            plnToast("No hay palabras nuevas o válidas para añadir a especiales.", 3000);
                            return;
                        }
                        modal.appendChild(list);
                        const confirmBtn = document.createElement("button");
                        confirmBtn.textContent = "Añadir Seleccionadas";
                        confirmBtn.addEventListener("click", () => {
                            const checked = modal.querySelectorAll("input[type=checkbox]:checked");
                            let wordsActuallyAdded = false;
                            checked.forEach(c => {
                                if (!excludedWords.has(c.value)) {
                                    excludedWords.add(c.value);
                                    wordsActuallyAdded = true;
                                }
                            });
                            if (wordsActuallyAdded) {
                                if (typeof renderExcludedWordsList === 'function') {
                                    const excludedListElement = document.getElementById("excludedWordsList");
                                    if (excludedListElement) {
                                        renderExcludedWordsList(excludedListElement);
                                    }
                                    else {
                                        renderExcludedWordsList();
                                    }
                                }
                            }
                            modal.remove();
                            if (wordsActuallyAdded) {
                                saveExcludedWordsToLocalStorage();
                                showTemporaryMessage("Palabra(s) añadida(s) a especiales y guardada(s).", 3000, 'success');
                            }
                            else {
                                showTemporaryMessage("No se seleccionaron palabras o ya estaban en la lista.", 3000, 'info');
                            }
                        });
                        modal.appendChild(confirmBtn);
                        const cancelBtn = document.createElement("button");
                        cancelBtn.textContent = "Cancelar";
                        cancelBtn.style.marginLeft = "8px";
                        cancelBtn.addEventListener("click", () => modal.remove());
                        modal.appendChild(cancelBtn);
                        document.body.appendChild(modal);
                        // --- Lógica de posicionamiento dinámico ---
                        const triggerButton = event.currentTarget;
                        if (triggerButton) {
                            const modalRect = modal.getBoundingClientRect();
                            const triggerRect = triggerButton.getBoundingClientRect();

                            // Abrimos el modal hacia la izquierda alineando su borde derecho con el botón
                            let newLeft = triggerRect.right - modalRect.width;
                            let newTop = triggerRect.bottom + 5; // 5px debajo del botón

                            if ((newTop + modalRect.height) > window.innerHeight) {
                                newTop = triggerRect.top - modalRect.height - 5; // Posicionar arriba si no cabe
                            }
                            if (newTop < 5) newTop = 5; // Ajuste final si se sale por arriba

                            // Mantener un margen mínimo dentro de la ventana
                            const horizontalPadding = 10;
                            if (newLeft < horizontalPadding) {
                                newLeft = horizontalPadding;
                            }
                            const maxLeft = window.innerWidth - modalRect.width - horizontalPadding;
                            if (newLeft > maxLeft) {
                                newLeft = maxLeft;
                            }

                            modal.style.left = `${newLeft}px`;
                            modal.style.top = `${newTop}px`;
                            modal.style.transform = 'none';
                        }
                    });
                    buttonGroup.appendChild(addToExclusionBtn);

                    // Exclude Place Button
                    excludePlaceBtn.addEventListener("click", (event) => { // <-- Añadimos "event"
                        const row = excludePlaceBtn.closest('tr');

                        const placeName = row.querySelector('textarea').value || original || `ID: ${id}`;

                        const confirmModal = document.createElement("div");
                        // Estilos básicos del modal
                        confirmModal.style.position = "fixed";
                        confirmModal.style.background = "#fff";
                        confirmModal.style.border = "1px solid #aad";
                        confirmModal.style.padding = "28px 32px 20px 32px";
                        confirmModal.style.zIndex = "20000";
                        confirmModal.style.boxShadow = "0 4px 24px rgba(0,0,0,0.18)";
                        confirmModal.style.fontFamily = "sans-serif";
                        confirmModal.style.borderRadius = "10px";
                        confirmModal.style.textAlign = "center";
                        confirmModal.style.minWidth = "340px";


                        const iconElement = document.createElement("div");
                        iconElement.innerHTML = "🚫";
                        iconElement.style.fontSize = "38px";
                        iconElement.style.marginBottom = "10px";
                        confirmModal.appendChild(iconElement);

                        const messageTitle = document.createElement("div");
                        messageTitle.innerHTML = `<b>¿Excluir "${placeName}"?</b>`;
                        messageTitle.style.fontSize = "20px";
                        messageTitle.style.marginBottom = "8px";
                        confirmModal.appendChild(messageTitle);

                        const explanationDiv = document.createElement("div");
                        explanationDiv.textContent = `Este lugar no aparecerá en futuras búsquedas del normalizador.`;
                        explanationDiv.style.fontSize = "15px";
                        explanationDiv.style.color = "#555";
                        explanationDiv.style.marginBottom = "18px";
                        confirmModal.appendChild(explanationDiv);

                        const buttonWrapper = document.createElement("div");
                        buttonWrapper.style.display = "flex";
                        buttonWrapper.style.justifyContent = "center";
                        buttonWrapper.style.gap = "18px";

                        const cancelBtn = document.createElement("button");
                        cancelBtn.textContent = "Cancelar";
                        cancelBtn.style.padding = "7px 18px";
                        cancelBtn.style.background = "#eee";
                        cancelBtn.style.border = "none";
                        cancelBtn.style.borderRadius = "4px";
                        cancelBtn.style.cursor = "pointer";
                        cancelBtn.addEventListener("click", () => confirmModal.remove());

                        const confirmExcludeBtn = document.createElement("button");
                        confirmExcludeBtn.textContent = "Excluir";
                        confirmExcludeBtn.style.padding = "7px 18px";
                        confirmExcludeBtn.style.background = "#d9534f";
                        confirmExcludeBtn.style.color = "#fff";
                        confirmExcludeBtn.style.border = "none";
                        confirmExcludeBtn.style.borderRadius = "4px";
                        confirmExcludeBtn.style.cursor = "pointer";
                        confirmExcludeBtn.style.fontWeight = "bold";

                        confirmExcludeBtn.addEventListener("click", () => {
                            excludedPlaces.set(id, placeName);
                            saveExcludedPlacesToLocalStorage();
                            showTemporaryMessage("Lugar excluido de futuras búsquedas.", 3000, 'success');
                            const row = excludePlaceBtn.closest('tr');
                            if (row) {
                                markRowAsProcessed(row, 'excluded');
                                if (window.checkBatchCompletion) window.checkBatchCompletion();
                            }
                            confirmModal.remove();
                        });

                        buttonWrapper.appendChild(cancelBtn);
                        buttonWrapper.appendChild(confirmExcludeBtn);
                        confirmModal.appendChild(buttonWrapper);

                        // Añadimos el modal al DOM para poder medirlo
                        document.body.appendChild(confirmModal);


                        const triggerButton = event.currentTarget;

                        // 1. Encontrar la columna de referencia ("Categoría Recomendada", la 11ª)
                        const headerCell = document.querySelector("#wme-place-inspector-panel thead th:nth-child(11)");

                        if (headerCell && triggerButton) {
                            const headerRect = headerCell.getBoundingClientRect();
                            const modalRect = confirmModal.getBoundingClientRect();
                            const triggerRect = triggerButton.getBoundingClientRect();

                            // Calcular 'left' para alinear el borde derecho del modal con el de la columna
                            let newLeft = headerRect.right - modalRect.width;
                            // Calcular 'top' para que aparezca cerca del botón
                            let newTop = triggerRect.top - modalRect.height - 10;

                            // Ajustar si se sale de la pantalla
                            if (newTop < 10) newTop = triggerRect.bottom + 10;
                            if (newLeft < 10) newLeft = 10;

                            // Aplicar los nuevos estilos
                            confirmModal.style.left = `${newLeft}px`;
                            confirmModal.style.top = `${newTop}px`;
                            confirmModal.style.transform = 'none'; // Importante: anular el centrado
                        } else {
                            // Fallback al centrado si algo falla
                            confirmModal.style.left = "50%";
                            confirmModal.style.top = "50%";
                            confirmModal.style.transform = "translate(-50%, -50%)";
                        }

                    });



                    actionCell.appendChild(buttonGroup);
                    row.appendChild(actionCell);
                    row.style.borderBottom = "1px solid #ddd";
                    row.style.backgroundColor = index % 2 === 0 ? "#f9f9f9" : "#ffffff";
                    row.querySelectorAll("td").forEach(td => {
                        td.style.verticalAlign = "top";
                    });
                    tbody.appendChild(row);
                    checkAndUpdateApplyButton();
                    setTimeout(() => {
                        const progress = Math.floor(((index + 1) / inconsistents.length) * 100);
                        const progressElem = document.getElementById("scanProgressText");
                        if (progressElem) {
                            progressElem.textContent = `Analizando lugares: ${progress}% (${index + 1}/${inconsistents.length})`;
                        }
                    }, 0);
                }; // End renderRow

                // Batching Logic
                window.plnAllInconsistents = inconsistents;
                const batchSize = 30;
                let totalProcessedPlaces = 0; // Contador de lugares ya procesados/eliminados

                function renderCurrentBatch() {
                    // Clear existing rows
                    tbody.innerHTML = "";

                    // Take top 30
                    const batch = window.plnAllInconsistents.slice(0, batchSize);

                    batch.forEach((item, i) => {
                        // Calcular el índice absoluto: total procesados + posición en batch actual
                        const absoluteIndex = totalProcessedPlaces + i;
                        renderRow(item, absoluteIndex);
                    });

                    updateBatchUI();
                    checkBatchCompletion(); // Check immediately
                }

                function loadNextBatch() {
                    // Remove hidden rows from master list
                    const rows = Array.from(tbody.querySelectorAll('tr'));
                    const hiddenIds = rows.map(r => r.dataset.placeId);

                    // Contar cuántos lugares se van a eliminar (ya procesados)
                    const placesToRemove = window.plnAllInconsistents.filter(item => hiddenIds.includes(item.id)).length;

                    // Remove items with these IDs from master list
                    window.plnAllInconsistents = window.plnAllInconsistents.filter(item => !hiddenIds.includes(item.id));

                    // Incrementar el contador de lugares procesados
                    totalProcessedPlaces += placesToRemove;

                    renderCurrentBatch();
                }

                function checkBatchCompletion() {
                    const rows = Array.from(tbody.querySelectorAll('tr'));
                    // Batch is complete when ALL rows in the current batch are normalized
                    // regardless of whether they are physically hidden or not (user might have "Show Normalized" ON)
                    const pendingInBatch = rows.filter(r => r.dataset.normalized !== 'true').length;

                    if (pendingInBatch === 0 && window.plnAllInconsistents.length > 0) {
                        // Automatically load next batch
                        setTimeout(() => {
                            loadNextBatch();
                        }, 500); // Small delay for better UX
                    }

                    // Update UI to reflect current counts
                    updateBatchUI();
                }

                function updateBatchUI() {
                    const resultsCounter = document.querySelector(".results-counter-display");
                    if (resultsCounter) {
                        // Calculate visible rows robustly
                        const rows = Array.from(tbody.querySelectorAll('tr'));
                        const isHidingNormalized = document.body.classList.contains('pln-hide-normalized-rows');

                        const visibleRows = rows.filter(row => {
                            if (row.style.display === 'none') return false;
                            if (isHidingNormalized && row.dataset.normalized === 'true') return false;
                            return true;
                        }).length;

                        const currentBatchTotal = Math.min(batchSize, window.plnAllInconsistents.length);
                        // Processed in batch = Total in batch - Visible (assuming visible means not processed yet)
                        // But wait, if "Mostrar Normalizados" is ON, normalized rows are visible.
                        // We want "Normalizados" to count items that ARE normalized, regardless of visibility.

                        const normalizedInBatch = rows.filter(r => r.dataset.normalized === 'true').length;
                        const realPending = window.plnAllInconsistents.length - normalizedInBatch;

                        // "En pantalla" should strictly mean "Not Normalized" for the purpose of the user workflow?
                        // Or literally visible? User said "en pantalla no hay 30 hay 30 menos 6".
                        // So "En pantalla" should probably mean "Pending in this batch".

                        const pendingInBatch = currentBatchTotal - normalizedInBatch;

                        // "Inconsistencias pendientes: 84. En pantalla: 29 (Normalizados: 1)"
                        resultsCounter.innerHTML = `Inconsistencias pendientes: <b style="color: #ff0000;">${realPending}</b>. En pantalla: <b style="color: #ff0000;">${pendingInBatch}</b> (Normalizados: <b style="color: green;">${normalizedInBatch}</b>).`;
                    }

                    // Remove button if it exists (cleanup)
                    const loadNextBtn = document.getElementById('pln-load-next-btn');
                    if (loadNextBtn) loadNextBtn.remove();

                    if (window.plnAllInconsistents.length === 0) {
                        // Optional: Show "All done" message
                    }
                }

                // Observer setup
                if (!window.plnPruneObserver) {
                    window.plnPruneObserver = new MutationObserver(() => {
                        checkBatchCompletion();
                    });
                }

                // Expose batch UI functions globally so markRowAsProcessed can call them
                window.checkBatchCompletion = checkBatchCompletion;
                window.updateBatchUI = updateBatchUI;

                // Initial Render
                renderCurrentBatch();

                table.appendChild(tbody);

                // Connect observer
                window.plnPruneObserver.disconnect();
                window.plnPruneObserver.observe(tbody, {
                    childList: true,
                    subtree: true,
                    attributes: true,
                    attributeFilter: ['style', 'class']
                });
                output.appendChild(table);
                const existingOverlay = document.getElementById("scanSpinnerOverlay");
                if (existingOverlay) {
                    existingOverlay.remove();
                }
                const progressBarInnerTab = document.getElementById("progressBarInnerTab");
                const progressBarTextTab = document.getElementById("progressBarTextTab");
                if (progressBarInnerTab && progressBarTextTab) {
                    progressBarInnerTab.style.width = "100%";
                    progressBarTextTab.textContent = `Progreso: 100% (${inconsistents.length}/${placesArr.length})`;
                }

                function reactivateAllActionButtons() {
                    document.querySelectorAll("#wme-place-inspector-output button")
                        .forEach(btn => {
                            btn.disabled = false;
                            btn.style.color = "";
                            btn.style.opacity = "";
                        });
                }

                W.model.actionManager.events.register("afterundoaction", null, () => {
                    if (floatingPanelElement && floatingPanelElement.style.display !== 'none') {
                        waitForWazeAPI(() => {
                            const places = getVisiblePlaces();
                            renderPlacesInFloatingPanel(places);
                            setTimeout(reactivateAllActionButtons, 250);
                        });
                    }
                    else {
                        //console.log("[WME PLN] Undo/Redo: Panel de resultados no visible, no se re-escanea.");
                    }
                });
                W.model.actionManager.events.register("afterredoaction", null, () => {
                    if (floatingPanelElement && floatingPanelElement.style.display !== 'none') {
                        waitForWazeAPI(() => {
                            const places = getVisiblePlaces();
                            renderPlacesInFloatingPanel(places);
                            setTimeout(reactivateAllActionButtons, 250);
                        });
                    }
                    else {
                        // console.log("[WME PLN] Undo/Redo: Panel de resultados no visible, no se re-escanea.");
                    }
                });
            } catch (e) {
                console.error("[WME PLN] Error in finalizeRender:", e);
            }
        }// finalizerender

    }// renderPlacesInFloatingPanel

    // Normaliza una palabra eliminando diacríticos (tildes) y caracteres especiales
    function getLevenshteinDistance(a, b) {
        const matrix = Array.from(
            { length: b.length + 1 },
            (_, i) => Array.from({ length: a.length + 1 }, (_, j) => (i === 0 ? j : (j === 0 ? i : 0))));
        for (let i = 1; i <= b.length; i++) {
            for (let j = 1; j <= a.length; j++) {
                if (b.charAt(i - 1) === a.charAt(j - 1)) {
                    matrix[i][j] = matrix[i - 1][j - 1];
                }
                else {
                    matrix[i][j] = Math.min(
                        matrix[i - 1][j] + 1,    // deletion
                        matrix[i][j - 1] + 1,    // insertion
                        matrix[i - 1][j - 1] + 1 // substitution
                    );
                }
            }
        }
        return matrix[b.length][a.length];
    }// getLevenshteinDistance

    // Normaliza una palabra eliminando caracteres especiales y convirtiéndola a minúsculas
    function calculateSimilarity(word1, word2) {
        const w1_lower = word1.toLowerCase();
        const w2_lower = word2.toLowerCase();

        // Optimización rápida para palabras idénticas
        if (w1_lower === w2_lower) return 1;

        // ✨ CACHE DE SIMILITUD: Verificar si ya calculamos esto
        // Ordenar para que calculateSimilarity(a,b) sea igual a calculateSimilarity(b,a)
        const cacheKey = w1_lower < w2_lower ? `${w1_lower}|${w2_lower}` : `${w2_lower}|${w1_lower}`;

        if (similarityCache.has(cacheKey)) {
            return similarityCache.get(cacheKey);
        }

        // Si las palabras son diferentes, pero al quitarles las tildes son idénticas,
        // dales una similitud muy alta (99%) para priorizarlas siempre.
        if (removeDiacritics(w1_lower) === removeDiacritics(w2_lower)) {
            similarityCache.set(cacheKey, 0.99);
            return 0.99; // Prioridad máxima para corrección de tildes
        }

        // Si no es un caso de tildes, procede con el cálculo normal de Levenshtein.
        const distance = getLevenshteinDistance(w1_lower, w2_lower);
        const maxLen = Math.max(w1_lower.length, w2_lower.length);

        let similarity;
        if (maxLen === 0) {
            similarity = 1;
        } else {
            similarity = 1 - distance / maxLen;
        }

        // Guardar en cache
        similarityCache.set(cacheKey, similarity);

        // Limpieza LRU si excede tamaño
        if (similarityCache.size > SIMILARITY_CACHE_MAX_SIZE) {
            const firstKey = similarityCache.keys().next().value;
            similarityCache.delete(firstKey);
        }

        return similarity;
    }// calculateSimilarity

    // Verifica si una fecha de edición está dentro del rango especificado
    function isDateWithinRange(editDate, filterRange) {
        if (!(editDate instanceof Date) || isNaN(editDate)) {
            console.warn("[WME PLN] Se proporcionó una fecha de edición inválida a isDateWithinRange.");
            return false; // No se puede comparar una fecha inválida.
        }
        const now = new Date();
        let cutoffDate = new Date();
        switch (filterRange) {
            case "all": // Si es "Elegir una opción", siempre se cumple la condición
                return true;
            case "6_months":
                cutoffDate.setMonth(now.getMonth() - 6);
                break;
            case "3_months":
                cutoffDate.setMonth(now.getMonth() - 3);
                break;
            case "1_month":
                cutoffDate.setMonth(now.getMonth() - 1);
                break;
            case "1_week":
                cutoffDate.setDate(now.getDate() - 7);
                break;
            case "1_day":
                cutoffDate.setDate(now.getDate() - 1);
                break;
            default:
                return true; // Si el filtro es desconocido, por seguridad no se filtra.
        }
        return editDate >= cutoffDate;
    }//isDateWithinRange

    // Encuentra palabras similares a una palabra dada en una lista o array indexado
    function findSimilarWords(word, indexedListOrArray, threshold) {
        const lowerWord = word.toLowerCase();
        const firstChar = lowerWord.charAt(0);
        let candidates = [];

        // Si el segundo argumento es un objeto literal (como window.dictionaryIndex)
        if (indexedListOrArray && typeof indexedListOrArray === 'object' && !Array.isArray(indexedListOrArray) && !(indexedListOrArray instanceof Map) && indexedListOrArray[firstChar]) {
            candidates = Array.from(indexedListOrArray[firstChar] || []);
        }
        // ✨ OPTIMIZACIÓN: Soporte para Map (como excludedWordsMap) (28-nov-2024)
        else if (indexedListOrArray instanceof Map) {
            if (indexedListOrArray.has(firstChar)) {
                candidates = Array.from(indexedListOrArray.get(firstChar));
            } else {
                candidates = []; // Si no hay bucket para esa letra, no hay candidatos
            }
        }
        // Si es un Set o Array (menos óptimo, pero fallback)
        else if (indexedListOrArray instanceof Set || Array.isArray(indexedListOrArray)) {
            candidates = Array.from(indexedListOrArray).filter(candidate => {
                // CORREGIDO: Extraer la palabra si es un objeto
                const candidateWord = typeof candidate === 'object' ? candidate.word : candidate;
                // CORREGIDO: Asegurar que es una string antes de llamar a charAt
                return typeof candidateWord === 'string' && candidateWord.charAt(0).toLowerCase() === firstChar;
            });
        }
        else {
            return [];
        }

        return candidates
            .map(candidate => {
                // CORREGIDO: Extraer la palabra si es un objeto
                const candidateWord = typeof candidate === 'object' ? candidate.word : candidate;
                // CORREGIDO: Asegurar que es una string antes de llamar a toLowerCase
                const candidateLower = typeof candidateWord === 'string' ? candidateWord.toLowerCase() : '';
                const similarity = calculateSimilarity(lowerWord, candidateLower);
                return { word: candidateWord, similarity };
            })
            .filter(item => item.similarity >= threshold)
            .sort((a, b) => b.similarity - a.similarity);
    }// findSimilarWords


    // Sugiere palabras excluidas basadas en el nombre actual y las palabras excluidas
    function suggestExcludedReplacements(currentName, excludedWords) {
        const words = currentName.split(/\s+/);
        const suggestions = {};
        const threshold = parseFloat(document.getElementById("similarityThreshold")?.value || "85") / 100;
        words.forEach(word => {
            // ✨ OPTIMIZACIÓN: Usar índice (Map) si está disponible (28-nov-2024)
            const sourceList = (window.excludedWordsMap && window.excludedWordsMap.size > 0)
                ? window.excludedWordsMap
                : Array.from(excludedWords);

            const similar = findSimilarWords(word, sourceList, threshold);
            if (similar.length > 0) {
                suggestions[word] = similar;
            }
        });
        return suggestions;
    }// suggestExcludedReplacements

    // Reset del inspector: progreso y texto de tab
    function resetInspectorState() {
        const inner = document.getElementById("progressBarInnerTab");
        const text = document.getElementById("progressBarTextTab");
        const outputTab = document.getElementById("wme-normalization-tab-output");
        if (inner)
            inner.style.width = "0%";
        if (text)
            text.textContent = `Progreso: 0% (0/0)`;
        if (outputTab)
            outputTab.textContent = "Presiona 'Start Scan...' para analizar los lugares visibles.";
    }// resetInspectorState

    // Función auxiliar para marcar una fila de la tabla como procesada/eliminada
    // Función auxiliar para marcar una fila de la tabla como procesada/eliminada
    function markRowAsProcessed(rowElement, actionType) {
        if (!rowElement) return;

        // CRITICAL: Set data-normalized to track processed state
        rowElement.dataset.normalized = 'true';
        rowElement.dataset.normalizedReason = actionType || 'manual-apply';

        // Estilos para atenuar y tachar la fila
        rowElement.style.opacity = '0.4';
        rowElement.style.textDecoration = 'line-through';
        rowElement.style.transition = 'opacity 0.5s ease'; // Transición suave

        // NUEVA LÓGICA: Verificar estado del botón toggle y ocultar si es necesario
        const toggleBtn = document.getElementById('pln-toggle-hidden-btn');
        if (toggleBtn && toggleBtn.dataset.state === 'hidden') {
            // Si el botón está en estado "hidden" (lo que significa que los elementos procesados deben ocultarse)
            rowElement.classList.add('pln-hidden-normalized');
        }

        // Deshabilitar todos los botones de acción en esta fila
        const buttons = rowElement.querySelectorAll('button');
        buttons.forEach(btn => {
            btn.disabled = true;
            btn.style.cursor = 'not-allowed';
            btn.style.opacity = '0.3';
        });

        // Opcional: Mostrar un pequeño icono de confirmación en la fila
        const numberCell = rowElement.querySelector('td:first-child');
        if (numberCell) {
            let icon = '';
            let tooltip = '';
            if (actionType === 'applied') {
                icon = '✓';
                tooltip = 'Cambios aplicados';
            } else if (actionType === 'deleted') {
                icon = '🗑️';
                tooltip = 'Lugar eliminado';
            } else if (actionType === 'excluded') {
                icon = '🚫';
                tooltip = 'Lugar excluido';
            }

            if (icon) {
                const iconSpan = document.createElement('span');
                iconSpan.textContent = ' ' + icon;
                iconSpan.title = tooltip;
                iconSpan.style.marginLeft = '3px';
                iconSpan.style.fontSize = '12px';
                numberCell.appendChild(iconSpan);
            }
        }
    }// markRowAsProcessed

    // Muestra un mensaje temporal en la parte superior de la pantalla


    function showTemporaryMessage(message, duration = 3000, type = 'info') {
        const CONTAINER_ID = 'pln-temp-message-container';

        // 1. Busca o crea el contenedor de notificaciones en la parte superior.
        let container = document.getElementById(CONTAINER_ID);
        if (!container) {
            container = document.createElement('div');
            container.id = CONTAINER_ID;
            container.style.cssText = `
            position: fixed;
            top: 90px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 25000;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 10px; /* Espacio entre notificaciones */
        `;
            document.body.appendChild(container);
        }

        // 2. Crea el popup individual para este mensaje.
        const popup = document.createElement('div');
        popup.textContent = message;
        // Estilos base (no cambian)
        popup.style.padding = '12px 25px';
        popup.style.borderRadius = '6px';
        popup.style.color = 'white';
        popup.style.fontWeight = 'bold';
        popup.style.boxShadow = '0 4px 10px rgba(0,0,0,0.15)';
        popup.style.opacity = '0';
        popup.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
        popup.style.transform = 'translateY(-20px)'; // Animación de entrada

        // Estilos según el tipo de mensaje (no cambian)
        switch (type) {
            case 'success':
                popup.style.backgroundColor = '#28a745'; // Verde
                break;
            case 'warning':
                popup.style.backgroundColor = '#ffc107'; // Naranja
                popup.style.color = '#212529';
                break;
            case 'error':
                popup.style.backgroundColor = '#dc3545'; // Rojo
                break;
            default: // 'info'
                popup.style.backgroundColor = '#17a2b8'; // Azul
                break;
        }

        // 3. Añade el popup al contenedor y anímalo.
        container.appendChild(popup);
        setTimeout(() => {
            popup.style.opacity = '1';
            popup.style.transform = 'translateY(0)';
        }, 50);

        // 4. Configura su desaparición.
        setTimeout(() => {
            popup.style.opacity = '0';
            popup.style.transform = 'translateY(-20px)';
            popup.addEventListener('transitionend', () => popup.remove());
            // Fallback por si el evento no se dispara
            setTimeout(() => { if (popup.parentElement) popup.remove(); }, 500);
        }, duration);
    }// showTemporaryMessage

    //Permite crear un panel flotante para mostrar los resultados del escaneo
    function createFloatingPanel(status = "processing", numInconsistents = 0) {
        if (!floatingPanelElement) {
            floatingPanelElement = document.createElement("div");
            floatingPanelElement.id = "wme-place-inspector-panel";
            floatingPanelElement.style.position = "fixed";
            floatingPanelElement.style.zIndex = "10005"; // Z-INDEX DEL PANEL DE RESULTADOS
            floatingPanelElement.style.background = "#fff";
            floatingPanelElement.style.border = "1px solid #ccc";
            floatingPanelElement.style.borderRadius = "8px";
            floatingPanelElement.style.boxShadow = "0 5px 15px rgba(0,0,0,0.2)";
            floatingPanelElement.style.padding = "10px";
            floatingPanelElement.style.fontFamily = "'Helvetica Neue', Helvetica, Arial, sans-serif";
            floatingPanelElement.style.display = 'none';
            floatingPanelElement.style.transition = "width 0.25s, height 0.25s, left 0.25s, top 0.25s"; // Agregado left y top a la transición
            floatingPanelElement.style.overflow = "hidden";

            // Variables para almacenar el estado del panel
            floatingPanelElement._isMaximized = false;
            floatingPanelElement._isMinimized = false;
            floatingPanelElement._originalState = {};
            floatingPanelElement._isDragging = false;
            floatingPanelElement._currentStatus = status;

            // Crear barra de título con controles
            const titleBar = document.createElement("div");
            titleBar.style.display = "flex";
            titleBar.style.justifyContent = "space-between";
            titleBar.style.alignItems = "center";
            titleBar.style.marginBottom = "10px";
            titleBar.style.userSelect = "none";
            titleBar.style.cursor = "move";
            titleBar.style.padding = "5px 0";

            // Título del panel
            const titleElement = document.createElement("h4");
            titleElement.id = "wme-pln-panel-title";
            titleElement.style.margin = "0";
            titleElement.style.fontSize = "20px";
            titleElement.style.color = "#333";
            titleElement.style.fontWeight = "bold";
            titleElement.style.flex = "1";
            titleElement.style.textAlign = "center";

            // Contenedor de controles estilo macOS
            const controlsContainer = document.createElement("div");
            controlsContainer.style.display = "flex";
            controlsContainer.style.gap = "8px";
            controlsContainer.style.alignItems = "center";
            controlsContainer.style.position = "absolute";
            controlsContainer.style.left = "15px";
            controlsContainer.style.top = "15px";

            // Función para crear botones estilo macOS
            function createMacButton(color, action, tooltip) {
                const btn = document.createElement("div");
                btn.style.width = "12px";
                btn.style.height = "12px";
                btn.style.borderRadius = "50%";
                btn.style.backgroundColor = color;
                btn.style.cursor = "pointer";
                btn.style.border = "1px solid rgba(0,0,0,0.1)";
                btn.style.display = "flex";
                btn.style.alignItems = "center";
                btn.style.justifyContent = "center";
                btn.style.fontSize = "8px";
                btn.style.color = "rgba(0,0,0,0.6)";
                btn.style.transition = "all 0.2s";
                btn.title = tooltip;

                // Efectos hover
                btn.addEventListener("mouseenter", () => {
                    btn.style.transform = "scale(1.1)";
                    if (color === "#ff5f57") btn.textContent = "×";
                    else if (color === "#ffbd2e") btn.textContent = "−";
                    else if (color === "#28ca42") btn.textContent = action === "maximize" ? "⬜" : "🗗";
                });

                btn.addEventListener("mouseleave", () => {
                    btn.style.transform = "scale(1)";
                    btn.textContent = "";
                });

                btn.addEventListener("click", action);
                return btn;
            }

            // Botón cerrar (rojo)
            const closeBtn = createMacButton("#ff5f57", () => {
                if (floatingPanelElement._currentStatus === "processing") {
                    // Confirmar cancelación de búsqueda
                    const confirmCancel = confirm("¿Estás seguro de que quieres detener la búsqueda en progreso?");
                    if (!confirmCancel) return;

                    // Aquí puedes agregar lógica para cancelar la búsqueda actual
                    // Por ejemplo, detener cualquier proceso en curso
                    resetInspectorState();
                }

                if (floatingPanelElement) floatingPanelElement.style.display = 'none';
                resetInspectorState();
            }, "Cerrar panel");

            // Botón minimizar (amarillo)
            const minimizeBtn = createMacButton("#ffbd2e", () => {
                const outputDiv = floatingPanelElement.querySelector("#wme-place-inspector-output");

                if (!floatingPanelElement._isMinimized) {
                    // Guardar estado actual antes de minimizar
                    floatingPanelElement._originalState = {
                        width: floatingPanelElement.style.width,
                        height: floatingPanelElement.style.height,
                        top: floatingPanelElement.style.top,
                        left: floatingPanelElement.style.left,
                        transform: floatingPanelElement.style.transform,
                        outputHeight: outputDiv ? outputDiv.style.height : 'auto'
                    };

                    // Minimizar - mover a la parte superior
                    floatingPanelElement.style.top = "20px";
                    floatingPanelElement.style.left = "50%";
                    floatingPanelElement.style.transform = "translateX(-50%)";
                    floatingPanelElement.style.height = "50px";
                    floatingPanelElement.style.width = "300px";
                    if (outputDiv) outputDiv.style.display = "none";

                    floatingPanelElement._isMinimized = true;
                    updateButtonVisibility();
                } else {
                    // Restaurar desde minimizado
                    const originalState = floatingPanelElement._originalState;
                    floatingPanelElement.style.width = originalState.width;
                    floatingPanelElement.style.height = originalState.height;
                    floatingPanelElement.style.top = originalState.top;
                    floatingPanelElement.style.left = originalState.left;
                    floatingPanelElement.style.transform = originalState.transform;

                    if (outputDiv) {
                        outputDiv.style.display = "block";
                        outputDiv.style.height = originalState.outputHeight;
                    }

                    floatingPanelElement._isMinimized = false;
                    updateButtonVisibility();
                }
            }, "Minimizar panel");

            // Botón maximizar (verde)
            //  const maximizeBtn = createMacButton("#28ca42", () => {
            const outputDiv = floatingPanelElement.querySelector("#wme-place-inspector-output");

            // Función para actualizar visibilidad de botones
            // Replace the updateButtonVisibility function in createFloatingPanel
            function updateButtonVisibility() {
                const isProcessing = floatingPanelElement._currentStatus === "processing";

                // Limpiar contenedor
                controlsContainer.innerHTML = "";

                if (isProcessing) {
                    // Solo botón cerrar durante la búsqueda
                    controlsContainer.appendChild(closeBtn);
                } else if (floatingPanelElement._isMinimized) {
                    // Minimizado: cerrar y restaurar
                    controlsContainer.appendChild(closeBtn);

                    // Crear botón de restaurar si estamos minimizados
                    const restoreBtn = createMacButton("#28ca42", () => {
                        // Restaurar desde minimizado
                        const originalState = floatingPanelElement._originalState;
                        floatingPanelElement.style.width = originalState.width;
                        floatingPanelElement.style.height = originalState.height;
                        floatingPanelElement.style.top = originalState.top;
                        floatingPanelElement.style.left = originalState.left;
                        floatingPanelElement.style.transform = originalState.transform;

                        const outputDiv = floatingPanelElement.querySelector("#wme-place-inspector-output");
                        if (outputDiv) {
                            outputDiv.style.display = "block";
                            outputDiv.style.height = originalState.outputHeight;
                        }

                        floatingPanelElement._isMinimized = false;
                        updateButtonVisibility();
                    }, "Restaurar panel");

                    restoreBtn.textContent = "🗗";
                    controlsContainer.appendChild(restoreBtn);
                } else {
                    // Normal: cerrar y minimizar
                    controlsContainer.appendChild(closeBtn);
                    controlsContainer.appendChild(minimizeBtn);
                }
            }// updateButtonVisibility

            // Funcionalidad de arrastrar
            let isDragging = false;
            let dragOffset = { x: 0, y: 0 };

            titleBar.addEventListener("mousedown", (e) => {
                if (e.target === titleBar || e.target === titleElement) {
                    isDragging = true;
                    const rect = floatingPanelElement.getBoundingClientRect();
                    dragOffset.x = e.clientX - rect.left;
                    dragOffset.y = e.clientY - rect.top;
                    floatingPanelElement.style.transition = "none";
                    e.preventDefault();
                }
            });

            document.addEventListener("mousemove", (e) => {
                if (isDragging && !floatingPanelElement._isMaximized) {
                    const newLeft = e.clientX - dragOffset.x;
                    const newTop = e.clientY - dragOffset.y;

                    floatingPanelElement.style.left = `${newLeft}px`;
                    floatingPanelElement.style.top = `${newTop}px`;
                    floatingPanelElement.style.transform = "none";
                }
            });

            document.addEventListener("mouseup", () => {
                if (isDragging) {
                    isDragging = false;
                    floatingPanelElement.style.transition = "width 0.25s, height 0.25s, left 0.25s, top 0.25s";
                }
            });

            // Agregar controles y título a la barra
            titleBar.appendChild(controlsContainer);
            titleBar.appendChild(titleElement);

            // Agregar barra de título al panel
            floatingPanelElement.appendChild(titleBar);

            // Contenido del panel
            const outputDivLocal = document.createElement("div");
            outputDivLocal.id = "wme-place-inspector-output";
            outputDivLocal.style.fontSize = "18px";
            outputDivLocal.style.backgroundColor = "#fdfdfd";
            outputDivLocal.style.overflowY = "auto";
            outputDivLocal.style.flex = "1";
            floatingPanelElement.appendChild(outputDivLocal);

            // Función para actualizar botones (hacer accesible)
            floatingPanelElement._updateButtonVisibility = updateButtonVisibility;

            document.body.appendChild(floatingPanelElement);
        }

        // Actualizar estado actual
        floatingPanelElement._currentStatus = status;

        // Referencias a elementos existentes
        const titleElement = floatingPanelElement.querySelector("#wme-pln-panel-title");
        const outputDiv = floatingPanelElement.querySelector("#wme-place-inspector-output");

        // Limpiar contenido
        if (outputDiv) outputDiv.innerHTML = "";

        // Actualizar visibilidad de botones
        if (floatingPanelElement._updateButtonVisibility) {
            floatingPanelElement._updateButtonVisibility();
        }

        // Configurar según el estado
        if (status === "processing") {
            // Solo actualizar si no está maximizado o minimizado
            if (!floatingPanelElement._isMaximized && !floatingPanelElement._isMinimized) {
                floatingPanelElement.style.width = processingPanelDimensions.width;
                floatingPanelElement.style.height = processingPanelDimensions.height;
                floatingPanelElement.style.top = "50%";
                floatingPanelElement.style.left = "50%";
                floatingPanelElement.style.transform = "translate(-50%, -50%)";
            }

            if (outputDiv && !floatingPanelElement._isMinimized) {
                outputDiv.style.height = floatingPanelElement._isMaximized ? "calc(100vh - 100px)" : "150px";
                outputDiv.style.display = "block";
            }

            if (titleElement) titleElement.textContent = "Buscando...";

            // Oculta el botón si existe de una ejecución anterior.
            const toggleBtn = floatingPanelElement.querySelector('#pln-toggle-hidden-btn');
            if (toggleBtn) toggleBtn.style.display = 'none';

            if (outputDiv && !floatingPanelElement._isMinimized) {
                outputDiv.innerHTML = "<div style='display:flex; align-items:center; justify-content:center; height:100%;'><span class='loader-spinner' style='width:32px; height:32px; border:4px solid #ccc; border-top:4px solid #007bff; border-radius:50%; animation:spin 0.8s linear infinite;'></span></div>";
            }
        }
        else { // status === "results"
            // Solo actualizar si no está maximizado o minimizado
            if (!floatingPanelElement._isMaximized && !floatingPanelElement._isMinimized) {
                floatingPanelElement.style.width = resultsPanelDimensions.width;
                floatingPanelElement.style.height = resultsPanelDimensions.height;
                floatingPanelElement.style.top = "50%";
                floatingPanelElement.style.left = "60%";
                floatingPanelElement.style.transform = "translate(-50%, -50%)";
            }

            if (outputDiv && !floatingPanelElement._isMinimized) {
                outputDiv.style.height = floatingPanelElement._isMaximized ? "calc(100vh - 100px)" : "660px";
                outputDiv.style.display = "block";
            }
            if (titleElement) titleElement.textContent = "NrmliZer: Resultados";

            // --- BOTÓN MOSTRAR/OCULTAR NORMALIZADOS ---
            /* let showHidden = false;
             let toggleBtn = document.getElementById('pln-toggle-hidden-btn');
             if (!toggleBtn) {
                 toggleBtn = document.createElement('button');
                 toggleBtn.id = 'pln-toggle-hidden-btn';
                 toggleBtn.textContent = 'Mostrar normalizados';
                 toggleBtn.style.marginLeft = '12px';
                 toggleBtn.style.padding = '4px 10px';
                 toggleBtn.style.fontSize = '12px';
                 toggleBtn.style.border = '1px solid #bbb';
                 toggleBtn.style.borderRadius = '5px';
                 toggleBtn.style.background = '#f4f4f4';
                 toggleBtn.style.cursor = 'pointer';
                 toggleBtn.addEventListener('click', () => {
                     showHidden = !showHidden;
                     if (showHidden) {
                         // Mostrar lo oculto
                         const st = document.getElementById('pln-hide-style'); if (st) st.remove();
                         document.querySelectorAll('tr.pln-hidden-normalized')
                             .forEach(tr => tr.classList.remove('pln-hidden-normalized'));
                         toggleBtn.textContent = 'Ocultar normalizados';
                     } else {
                         // Volver a ocultar normalizados
                         if (!document.getElementById('pln-hide-style')) {
                             const st = document.createElement('style');
                             st.id = 'pln-hide-style';
                             st.textContent = `tr.pln-hidden-normalized{display:none !important;}`;
                             document.head.appendChild(st);
                         }
                         document.querySelectorAll('tr').forEach(tr => {
                             // Reaplicar la lógica de ocultar si corresponde
                             if (tr.dataset && tr.dataset.placeId) {
                                 // Si la fila ya estaba normalizada, volver a ocultarla
                                 // (esto depende de lógica de marcado, aquí solo se vuelve a aplicar la clase si no tiene cambios)
                                 // Si quieres forzar el ocultamiento, puedes volver a llamar a processAll() si la tienes global
                                 if (typeof window.__plnHideNormalizedRows === 'function') {
                                     window.__plnHideNormalizedRows();
                                 }
                             }
                         });
                         toggleBtn.textContent = 'Mostrar normalizados';
                     }
                 });
                 // Insertar el botón en la barra de título del panel
                 const titleBar = floatingPanelElement.querySelector('div');
                 if (titleBar) titleBar.appendChild(toggleBtn);
             }*/
        }

        floatingPanelElement.style.display = 'flex';
        floatingPanelElement.style.flexDirection = 'column';
    }
    // Escuchar el botón Guardar de WME para resetear el inspector
    const wmeSaveBtn = document.querySelector(
        "button.action.save, button[title='Guardar'], button[aria-label='Guardar']");
    if (wmeSaveBtn) {
        wmeSaveBtn.addEventListener("click", () => {
            // Ocultar el panel flotante de resultados si está visible
            if (floatingPanelElement) {
                floatingPanelElement.style.display = 'none';
            }
            // Resetear el estado del inspector en la pestaña lateral
            resetInspectorState();
        });
    }
    // Función para crear la pestaña lateral del script
    function createSidebarTab() {
        try {
            // 1. Verificar si WME y la función para registrar pestañas están listos
            if (!W || !W.userscripts || typeof W.userscripts.registerSidebarTab !== 'function') {
                console.error("[WME PLN] WME (userscripts o registerSidebarTab) no está listo para crear la pestaña lateral.");
                return;
            }
            // 2. Registrar la pestaña principal del script en WME y obtener tabPane
            let registration;
            try {
                registration = W.userscripts.registerSidebarTab("NrmliZer"); // Nombre del Tab que aparece en WME
            }
            catch (e) {
                if (e.message.includes("already been registered")) {
                    console.warn("[WME PLN] Tab 'NrmliZer' ya registrado. El script puede no funcionar como se espera si hay múltiples instancias.");
                    // Podrías intentar obtener el tabPane existente o simplemente
                    // retornar. Para evitar mayor complejidad, si ya está
                    // registrado, no continuaremos con la creación de la UI de la
                    // pestaña.
                    return;
                }

                throw e; // Relanzar otros errores para que se vean en consola
            }
            const { tabLabel, tabPane } = registration;
            if (!tabLabel || !tabPane) {

                return;
            }
            // Configurar el ícono y nombre de la pestaña principal del script
            // Corrección aquí: usar directamente MAIN_TAB_ICON_BASE64 en el src
            tabLabel.innerHTML = `
                <img src="${MAIN_TAB_ICON_BASE64}" style="height: 16px; vertical-align: middle; margin-right: 5px;">
                NrmliZer
            `;
            // 3. Inicializar las pestañas internas (General, Especiales,
            // Diccionario, Reemplazos)
            const tabsContainer = document.createElement("div");
            tabsContainer.style.display = "flex";
            tabsContainer.style.marginBottom = "8px";
            tabsContainer.style.gap = "8px";
            const tabButtons = {};
            const tabContents = {}; // Objeto para guardar los divs de contenido
            // Crear botones para cada pestaña
            tabNames.forEach(({ label, icon }) => {
                const btn = document.createElement("button");
                btn.innerHTML = icon
                    ? `<span style="display: inline-flex; align-items: center; font-size: 11px;">
                    <span style="font-size: 12px; margin-right: 4px;">${icon}</span>${label}
                </span>`
                    : `<span style="font-size: 11px;">${label}</span>`;
                btn.style.fontSize = "11px";
                btn.style.padding = "4px 8px";
                btn.style.marginRight = "4px";
                btn.style.minHeight = "28px";
                btn.style.border = "1px solid #ccc";
                btn.style.borderRadius = "4px 4px 0 0";
                btn.style.cursor = "pointer";
                btn.style.borderBottom = "none"; // Para que la pestaña activa se vea mejor integrada
                btn.className = "custom-tab-style";
                // Agrega el tooltip personalizado para cada tab
                if (label === "Gene") btn.title = "Configuración general";
                else if (label === "Espe") btn.title = "Palabras especiales (Excluidas)";
                else if (label === "Dicc") btn.title = "Diccionario de palabras válidas";
                else if (label === "Reemp") btn.title = "Gestión de reemplazos automáticos";
                // Estilo inicial: la primera pestaña es la activa
                if (label === tabNames[0].label) {
                    btn.style.backgroundColor = "#ffffff"; // Color de fondo activo (blanco)
                    btn.style.borderBottom = "2px solid #007bff"; // Borde inferior distintivo para la activa
                    btn.style.fontWeight = "bold";
                }
                else {
                    btn.style.backgroundColor = "#f0f0f0"; // Color de fondo inactivo (gris claro)
                    btn.style.fontWeight = "normal";
                }
                btn.addEventListener("click", () => {
                    tabNames.forEach(({ label: tabLabel_inner }) => {
                        const isActive = (tabLabel_inner === label);
                        const currentButton = tabButtons[tabLabel_inner];
                        if (tabContents[tabLabel_inner]) {
                            tabContents[tabLabel_inner].style.display = isActive ? "block" : "none";
                        }
                        if (currentButton) {
                            // Aplicar/Quitar estilos de pestaña activa directamente
                            if (isActive) {
                                currentButton.style.backgroundColor = "#ffffff"; // Activo
                                currentButton.style.borderBottom = "2px solid #007bff";
                                currentButton.style.fontWeight = "bold";
                            }
                            else {
                                currentButton.style.backgroundColor = "#f0f0f0"; // Inactivo
                                currentButton.style.borderBottom = "none";
                                currentButton.style.fontWeight = "normal";
                            }
                        }
                        // Llamar a la función de renderizado correspondiente
                        if (isActive) {
                            if (tabLabel_inner === "Espe") {
                                const ul = document.getElementById("excludedWordsList");
                                if (ul && typeof renderExcludedWordsList === 'function') renderExcludedWordsList(ul);
                            }
                            else if (tabLabel_inner === "Dicc") {
                                const ulDict = document.getElementById("dictionaryWordsList");
                                if (ulDict && typeof renderDictionaryList === 'function') renderDictionaryList(ulDict);
                            }
                            else if (tabLabel_inner === "Reemp") {
                                try {
                                    const ulReemplazos = document.getElementById("replacementsListElementID");
                                    if (ulReemplazos && typeof renderReplacementsList === 'function') renderReplacementsList(ulReemplazos);
                                } catch (e) {
                                    console.error("[WME PLN] Error rendering replacements list:", e);
                                }
                            }
                        }
                    });
                });
                tabButtons[label] = btn;
                tabsContainer.appendChild(btn);
            });
            tabPane.appendChild(tabsContainer);
            // Crear los divs contenedores para el contenido de cada pestaña
            tabNames.forEach(({ label }) => {
                const contentDiv = document.createElement("div");
                contentDiv.style.display = label === tabNames[0].label ? "block" : "none"; // Mostrar solo la primera
                contentDiv.style.padding = "10px";
                tabContents[label] = contentDiv; // Guardar referencia
                tabPane.appendChild(contentDiv);
            });
            // --- POBLAR EL CONTENIDO DE CADA PESTAÑA ---
            // 4. Poblar el contenido de la pestaña "General"
            const containerGeneral = tabContents["Gene"];
            if (containerGeneral) {
                // Crear el contenedor principal
                const mainTitle = document.createElement("h3");
                mainTitle.textContent = "NormliZer";
                mainTitle.style.textAlign = "center";
                mainTitle.style.fontSize = "20px";
                mainTitle.style.marginBottom = "2px";
                containerGeneral.appendChild(mainTitle);
                // Crear el subtítulo (información de la versión)
                const versionInfo = document.createElement("div");
                versionInfo.textContent = "V. " + VERSION; // VERSION global
                versionInfo.style.textAlign = "right";
                versionInfo.style.fontSize = "10px";
                versionInfo.style.color = "#777";
                versionInfo.style.marginBottom = "15px";
                containerGeneral.appendChild(versionInfo);
                //Crear un div para mostrar el ID del usuario
                const userIdInfo = document.createElement("div"); //
                userIdInfo.id = "wme-pln-user-id"; //
                userIdInfo.textContent = "Cargando usuario..."; //
                userIdInfo.style.textAlign = "right"; //
                userIdInfo.style.fontSize = "10px"; //
                userIdInfo.style.color = "#777"; //
                userIdInfo.style.marginBottom = "15px"; //
                containerGeneral.appendChild(userIdInfo); //
                // Esta función reemplaza la necesidad de las funciones getCurrentEditorViaSdk, etc.
                const pollAndDisplayUserInfo = () => {
                    let pollingAttempts = 0;
                    const maxPollingAttempts = 60;
                    const pollInterval = setInterval(async () => {
                        let currentUserInfoLocal = null; //: Usar una variable local temporal
                        // Primero intentar con wmeSDK.State.getUserInfo() ***
                        if (wmeSDK && wmeSDK.State && typeof wmeSDK.State.getUserInfo === 'function') {
                            try {
                                const sdkUserInfo = await wmeSDK.State.getUserInfo();
                                if (sdkUserInfo && sdkUserInfo.userName) {
                                    currentUserInfoLocal = {
                                        // Si sdkUserInfo.id NO existe, usar sdkUserInfo.userName DIRECTAMENTE (sin Number())
                                        id: sdkUserInfo.id !== undefined ? sdkUserInfo.id : sdkUserInfo.userName, //
                                        name: sdkUserInfo.userName,
                                        privilege: sdkUserInfo.privilege || 'N/A'
                                    };
                                    // Asegurarse de que el ID es válido para el log
                                    const displayId = typeof currentUserInfoLocal.id === 'number' ? currentUserInfoLocal.id : `"${currentUserInfoLocal.id}"`; //

                                }
                                else {

                                }
                            }
                            catch (e) {

                            }
                        }
                        else {
                            //console.warn(`[WME_PLN][DEBUG] SDK.State.getUserInfo no disponible. wmeSDK:`, wmeSDK);
                        }
                        // Fallback a W.loginManager (si SDK.State no funcionó)
                        if (!currentUserInfoLocal && typeof W !== 'undefined' && W.loginManager && W.loginManager.userName && W.loginManager.userId) { //: Usar currentUserInfoLocal
                            currentUserInfoLocal = {
                                id: Number(W.loginManager.userId), // Convertir a número
                                name: W.loginManager.userName,
                                privilege: W.loginManager.userPrivilege || 'N/A'
                            };
                            //console.log(`[WME PLN][DEBUG] W.loginManager SUCCESS: Usuario obtenido: ${currentUserInfoLocal.name} (ID: ${currentUserInfoLocal.id})`);
                        }
                        else if (!currentUserInfoLocal) { //: Solo logear si aún no se encontró en ningún método
                            // console.warn(`[WME_PLN][DEBUG] W.loginManager devolvió datos incompletos o null:`, W?.loginManager);
                        }
                        if (currentUserInfoLocal && currentUserInfoLocal.id && currentUserInfoLocal.name) {
                            clearInterval(pollInterval);
                            currentGlobalUserInfo = currentUserInfoLocal;
                            userIdInfo.textContent = `Editor Actual: ${currentGlobalUserInfo.name}`;
                            userIdInfo.title = `Privilegio: ${currentGlobalUserInfo.privilege}`;
                            updateStatsDisplay();//: Actualizar estadísticas con el nuevo usuario
                            //  console.log('[WME_PLN][DEBUG] USUARIO CARGADO EXITOSAMENTE mediante polling.');
                            const labelToUpdate = document.querySelector('label[for="chk-avoid-my-edits"]');
                            if (labelToUpdate) {
                                labelToUpdate.innerHTML = `Excluir lugares cuya última edición sea del Editor: <span style="color: #007bff; font-weight: normal;">${currentGlobalUserInfo.name}</span>`;
                            }
                            const avoidMyEditsCheckbox = document.getElementById("chk-avoid-my-edits");
                            if (avoidMyEditsCheckbox) {
                                avoidMyEditsCheckbox.disabled = false;
                                avoidMyEditsCheckbox.style.opacity = "1";
                                avoidMyEditsCheckbox.style.cursor = "pointer";
                            }
                        }
                        else if (pollingAttempts >= maxPollingAttempts - 1) {
                            clearInterval(pollInterval);
                            userIdInfo.textContent = "Usuario no detectado (agotados intentos)";
                            //console.log('[WME PLN][DEBUG]  Polling agotado. Usuario no detectado después de varios intentos.');
                            // Asignar el estado de fallo a currentGlobalUserInfo
                            currentGlobalUserInfo = { id: 0, name: 'No detectado', privilege: 'N/A' }; // Usar 0 o null como number
                            // Actualizar el texto del checkbox para evitar ediciones del usuario
                            const avoidTextSpanToUpdate = document.querySelector("#chk-avoid-my-edits + label span");
                            //: Actualizar el texto del checkbox para evitar ediciones del usuario
                            if (avoidTextSpanToUpdate) {
                                //: Usa innerHTML y estilo atenuado para el nombre "No detectado"
                                avoidTextSpanToUpdate.innerHTML = `Excluir lugares cuya última edición sea del Editor: <span style="color: #777; opacity: 0.5;">No detectado</span>`; //
                                avoidTextSpanToUpdate.style.opacity = "1"; //: Asegurar opacidad base para el span principal
                                // avoidTextSpanToUpdate.style.color = "#777"; //: Puedes quitar esta línea si el color del span es suficiente
                            }
                            const avoidMyEditsCheckbox = document.getElementById("chk-avoid-my-edits");
                            //: Deshabilitar el checkbox si no se detecta el usuario
                            if (avoidMyEditsCheckbox) {
                                avoidMyEditsCheckbox.disabled = true;
                                avoidMyEditsCheckbox.style.opacity = "0.5";
                                avoidMyEditsCheckbox.style.cursor = "not-allowed";
                            }
                        }
                        pollingAttempts++;
                    }, 200);

                };
                // Iniciar el polling para la información del usuario
                pollAndDisplayUserInfo(); //Llamada directa a la nueva función de polling
                // Título de la sección de normalización
                const normSectionTitle = document.createElement("h4");
                normSectionTitle.textContent = "Análisis de Nombres de Places";
                normSectionTitle.style.fontSize = "16px";
                normSectionTitle.style.marginTop = "10px";
                normSectionTitle.style.marginBottom = "5px";
                normSectionTitle.style.borderBottom = "1px solid #eee";
                normSectionTitle.style.paddingBottom = "3px";
                containerGeneral.appendChild(normSectionTitle);
                // Descripción de la sección
                const scanButton = document.createElement("button");
                scanButton.id = "pln-start-scan-btn";
                scanButton.textContent = "Start Scan...";
                scanButton.setAttribute("type", "button");
                scanButton.style.marginBottom = "10px";
                scanButton.style.fontSize = "14px";
                scanButton.style.width = "100%";
                scanButton.style.padding = "8px";
                scanButton.style.border = "none";
                scanButton.style.borderRadius = "4px";
                scanButton.style.backgroundColor = "#007bff";
                scanButton.style.color = "#fff";
                scanButton.style.cursor = "pointer";
                scanButton.addEventListener("click", () => {
                    disableScanControls(); // Deshabilitar controles durante el escaneo
                    scanButton.textContent = "Escaneando..."; // Cambia el texto del botón
                    const places = getVisiblePlaces();
                    const outputDiv = document.getElementById("wme-normalization-tab-output");
                    if (!outputDiv) { // Mover esta verificación antes
                        return;
                    }
                    if (places.length === 0) {
                        outputDiv.textContent = "No se encontraron lugares visibles para analizar.";
                        setTimeout(() => { renderPlacesInFloatingPanel([], { totalVisibleCount: 0, excludedCount: 0, skipExcludedFiltering: true }); }, 10);
                        return;
                    }


                    const { filtered: placesWithoutExcluded, excludedCount } = filterOutExcludedPlaces(places);
                    let workingPlaces = placesWithoutExcluded;
                    const totalVisibleCount = places.length;

                    // Aplicar filtro de update requests si está activo
                    let updateRequestsFilteredOutCount = 0;
                    if (filterUpdateRequestsOnly) {
                        const { filtered: placesWithUpdateRequests, filteredOutCount } = filterByUpdateRequests(workingPlaces);
                        workingPlaces = placesWithUpdateRequests;
                        updateRequestsFilteredOutCount = filteredOutCount;
                    }

                    const maxPlacesInput = document.getElementById("maxPlacesInput");
                    const maxPlacesToScan = parseInt(maxPlacesInput?.value || "100", 10);
                    const scannedCount = Math.min(workingPlaces.length, maxPlacesToScan);

                    if (workingPlaces.length === 0) {
                        if (filterUpdateRequestsOnly && updateRequestsFilteredOutCount > 0) {
                            outputDiv.textContent = `No se encontraron lugares con update requests pendientes.`;
                        } else if (excludedCount > 0) {
                            outputDiv.textContent = `Todos los ${totalVisibleCount} lugares visibles están en la lista de excluidos.`;
                        } else {
                            outputDiv.textContent = "No se encontraron lugares visibles para analizar.";
                        }
                    }
                    else {
                        let message = `Escaneando ${scannedCount} lugares...`;
                        if (filterUpdateRequestsOnly) {
                            message += ` (Filtrado: ${updateRequestsFilteredOutCount} sin update requests`;
                            if (excludedCount > 0) {
                                message += `, ${excludedCount} excluidos`;
                            }
                            message += `)`;
                        } else if (excludedCount > 0) {
                            message += ` (${excludedCount} excluidos que serán omitidos)`;
                        }
                        outputDiv.textContent = message;
                    }

                    setTimeout(() => {
                        renderPlacesInFloatingPanel(workingPlaces, {
                            totalVisibleCount,
                            excludedCount,
                            skipExcludedFiltering: true
                        });
                    }, 10);
                });
                containerGeneral.appendChild(scanButton);
                // Crear el contenedor para el checkbox de usuario
                const maxWrapper = document.createElement("div");
                maxWrapper.style.display = "flex";
                maxWrapper.style.alignItems = "center";
                maxWrapper.style.gap = "8px";
                maxWrapper.style.marginBottom = "8px";
                const maxLabel = document.createElement("label");
                maxLabel.textContent = "Máximo de places a revisar:";
                maxLabel.style.fontSize = "13px";
                maxWrapper.appendChild(maxLabel);
                const maxInput = document.createElement("input");
                maxInput.type = "number";
                maxInput.id = "maxPlacesInput";
                maxInput.min = "1";
                maxInput.value = "100";
                maxInput.style.width = "80px";
                maxWrapper.appendChild(maxInput);
                containerGeneral.appendChild(maxWrapper);
                const presets = [25, 50, 100, 250, 500];
                const presetContainer = document.createElement("div");
                presetContainer.style.textAlign = "center";
                presetContainer.style.marginBottom = "8px";
                presets.forEach(preset => {
                    const btn = document.createElement("button");
                    btn.className = "pln-preset-btn"; // Clase para aplicar estilos comunes
                    btn.textContent = preset.toString();
                    btn.style.margin = "2px";
                    btn.style.padding = "4px 6px";
                    btn.addEventListener("click", () => {
                        if (maxInput)
                            maxInput.value = preset.toString();
                    });
                    presetContainer.appendChild(btn);
                });
                containerGeneral.appendChild(presetContainer);

                // --- Input para Nivel Máximo de Bloqueo ---
                const lockRankWrapper = document.createElement("div");
                lockRankWrapper.style.marginBottom = "10px";
                lockRankWrapper.style.textAlign = "center";

                const lockRankLabel = document.createElement("label");
                lockRankLabel.textContent = "Nivel Máximo de Bloqueo (opcional): ";
                lockRankLabel.style.fontSize = "12px";
                lockRankWrapper.appendChild(lockRankLabel);

                const lockRankInput = document.createElement("input");
                lockRankInput.type = "number";
                lockRankInput.id = "maxLockRankInput";
                lockRankInput.min = "1";
                lockRankInput.max = "6";
                lockRankInput.placeholder = "1-6";
                lockRankInput.style.width = "50px";
                lockRankInput.style.marginLeft = "5px";

                // Cargar valor guardado
                const savedMaxLockRank = localStorage.getItem("wme_pln_max_lock_rank");
                if (savedMaxLockRank) {
                    lockRankInput.value = savedMaxLockRank;
                }

                // Guardar al cambiar
                lockRankInput.addEventListener("change", () => {
                    localStorage.setItem("wme_pln_max_lock_rank", lockRankInput.value);
                });

                lockRankWrapper.appendChild(lockRankInput);
                containerGeneral.appendChild(lockRankWrapper);
                // Checkbox para recomendar categorías
                const recommendCategoriesWrapper = document.createElement("div");
                recommendCategoriesWrapper.style.marginTop = "10px";
                recommendCategoriesWrapper.style.marginBottom = "5px";
                recommendCategoriesWrapper.style.display = "flex";
                recommendCategoriesWrapper.style.flexDirection = "column"; //Cambiar a columna para apilar checkboxes
                recommendCategoriesWrapper.style.alignItems = "flex-start"; //Alinear ítems al inicio
                recommendCategoriesWrapper.style.padding = "6px 8px"; // Añadir padding
                recommendCategoriesWrapper.style.backgroundColor = "#e0f7fa"; // Fondo claro para destacar
                recommendCategoriesWrapper.style.border = "1px solid #00bcd4"; // Borde azul
                recommendCategoriesWrapper.style.borderRadius = "4px"; // Bordes redondeados
                containerGeneral.appendChild(recommendCategoriesWrapper); //Añadir el wrapper aquí, antes de sus contenidos

                // Guardar referencia global para crear checkbox de URs dinámicamente
                recommendCategoriesWrapperGlobal = recommendCategoriesWrapper;

                // Contenedor para el checkbox "Recomendar categorías"
                const recommendCategoryCheckboxRow = document.createElement("div"); //
                recommendCategoryCheckboxRow.style.display = "flex"; //Fila para checkbox y etiqueta
                recommendCategoryCheckboxRow.style.alignItems = "center"; //
                recommendCategoryCheckboxRow.style.marginBottom = "5px"; //Margen inferior
                // Crear el checkbox y la etiqueta
                const recommendCategoriesCheckbox = document.createElement("input");
                recommendCategoriesCheckbox.type = "checkbox";
                recommendCategoriesCheckbox.id = "chk-recommend-categories";
                recommendCategoriesCheckbox.style.marginRight = "8px";
                const savedCategoryRecommendationState = localStorage.getItem("wme_pln_recommend_categories");
                recommendCategoriesCheckbox.checked = (savedCategoryRecommendationState === "true");
                const recommendCategoriesLabel = document.createElement("label");
                recommendCategoriesLabel.htmlFor = "chk-recommend-categories";
                recommendCategoriesLabel.style.fontSize = "14px";
                recommendCategoriesLabel.style.cursor = "pointer";
                recommendCategoriesLabel.style.fontWeight = "bold";
                recommendCategoriesLabel.style.color = "#00796b";
                recommendCategoriesLabel.style.display = "flex";
                recommendCategoriesLabel.style.alignItems = "center";
                const iconSpan = document.createElement("span");
                iconSpan.innerHTML = "✨ ";
                iconSpan.style.marginRight = "4px";
                iconSpan.style.fontSize = "16px";
                iconSpan.appendChild(document.createTextNode("Recomendar categorías"));
                recommendCategoriesLabel.appendChild(iconSpan);
                recommendCategoryCheckboxRow.appendChild(recommendCategoriesCheckbox); //
                recommendCategoryCheckboxRow.appendChild(recommendCategoriesLabel); //
                recommendCategoriesWrapper.appendChild(recommendCategoryCheckboxRow); //Añadir la fila al wrapper
                recommendCategoriesCheckbox.addEventListener("change", () => {
                    localStorage.setItem("wme_pln_recommend_categories", recommendCategoriesCheckbox.checked ? "true" : "false");
                });
                // --- Contenedor para AGRUPAR las opciones de exclusión ---
                const excludeContainer = document.createElement('div');
                excludeContainer.style.marginTop = '8px'; // Espacio que lo separa de la opción de arriba
                // --- Fila para el checkbox "Excluir lugares..." ---
                const avoidMyEditsCheckboxRow = document.createElement("div");
                avoidMyEditsCheckboxRow.style.display = "flex";
                avoidMyEditsCheckboxRow.style.alignItems = "center";
                //: Añadir un margen inferior para separar del checkbox de categorías
                const avoidMyEditsCheckbox = document.createElement("input");
                avoidMyEditsCheckbox.type = "checkbox";
                avoidMyEditsCheckbox.id = "chk-avoid-my-edits";
                avoidMyEditsCheckbox.style.marginRight = "8px";
                const savedAvoidMyEditsState = localStorage.getItem("wme_pln_avoid_my_edits");
                avoidMyEditsCheckbox.checked = (savedAvoidMyEditsState === "true");
                avoidMyEditsCheckboxRow.appendChild(avoidMyEditsCheckbox);
                //: Añadir un label con el texto de la opción
                const avoidMyEditsLabel = document.createElement("label");
                avoidMyEditsLabel.htmlFor = "chk-avoid-my-edits";
                avoidMyEditsLabel.style.fontSize = "16px"; // Tamaño de fuente consistente
                avoidMyEditsLabel.style.cursor = "pointer";
                avoidMyEditsLabel.style.fontWeight = "bold";
                avoidMyEditsLabel.style.color = "#00796b";
                avoidMyEditsLabel.innerHTML = `Excluir lugares cuya última edición sea del Editor: <span style="color: #007bff; font-weight: normal;">Cargando...</span>`;
                avoidMyEditsCheckboxRow.appendChild(avoidMyEditsLabel);
                // --- Fila para el dropdown de fecha (sub-menú) ---
                const dateFilterRow = document.createElement("div");
                dateFilterRow.style.display = "flex";
                dateFilterRow.style.alignItems = "center";
                dateFilterRow.style.marginTop = "8px"; // Espacio entre el checkbox y esta fila
                dateFilterRow.style.paddingLeft = "25px"; // Indentación para que parezca una sub-opción
                dateFilterRow.style.gap = "8px";
                //: Añadir un label para el dropdown
                const dateFilterLabel = document.createElement("label");
                dateFilterLabel.htmlFor = "dateFilterSelect";
                dateFilterLabel.textContent = "Excluir solo ediciones de:";
                dateFilterLabel.style.fontSize = "13px";
                dateFilterLabel.style.fontWeight = "500";
                dateFilterLabel.style.color = "#334";
                dateFilterRow.appendChild(dateFilterLabel);
                //: Crear el dropdown para seleccionar el filtro de fecha
                const dateFilterSelect = document.createElement("select");
                dateFilterSelect.id = "dateFilterSelect";
                dateFilterSelect.style.padding = "5px 8px";
                dateFilterSelect.style.border = "1px solid #b0c4de";
                dateFilterSelect.style.borderRadius = "4px";
                dateFilterSelect.style.backgroundColor = "#fff";
                dateFilterSelect.style.flexGrow = "1";
                dateFilterSelect.style.fontSize = "13px";
                dateFilterSelect.style.cursor = "pointer";
                // Añadir opciones al dropdown
                const dateOptions = {
                    "all": "Elegir una opción",
                    "6_months": "Últimos 6 meses",
                    "3_months": "Últimos 3 meses",
                    "1_month": "Último mes",
                    "1_week": "Última Semana",
                    "1_day": "Último día"
                };
                // Añadir las opciones al dropdown
                for (const [value, text] of Object.entries(dateOptions)) {
                    const option = document.createElement("option");
                    option.value = value;
                    option.textContent = text;
                    dateFilterSelect.appendChild(option);
                }
                // Cargar el valor guardado del localStorage
                const savedDateFilter = localStorage.getItem("wme_pln_date_filter");
                if (savedDateFilter) {
                    dateFilterSelect.value = savedDateFilter;
                }
                dateFilterSelect.addEventListener("change", () => {
                    localStorage.setItem("wme_pln_date_filter", dateFilterSelect.value);
                });
                dateFilterRow.appendChild(dateFilterSelect);
                // --- Añadir AMBAS filas al contenedor de exclusión ---
                excludeContainer.appendChild(avoidMyEditsCheckboxRow);
                excludeContainer.appendChild(dateFilterRow);
                // --- Añadir el contenedor AGRUPADO al wrapper principal (el cuadro azul) ---
                recommendCategoriesWrapper.appendChild(excludeContainer);
                // --- Lógica para habilitar/deshabilitar el dropdown ---
                const toggleDateFilterState = () => {
                    const isChecked = avoidMyEditsCheckbox.checked;
                    dateFilterSelect.disabled = !isChecked;
                    dateFilterRow.style.opacity = isChecked ? "1" : "0.5";
                    dateFilterRow.style.pointerEvents = isChecked ? "auto" : "none";
                };
                // --- Listener unificado para el checkbox ---
                avoidMyEditsCheckbox.addEventListener("change", () => {
                    toggleDateFilterState(); // Actualiza la UI del dropdown
                    localStorage.setItem("wme_pln_avoid_my_edits", avoidMyEditsCheckbox.checked ? "true" : "false"); // Guarda el estado
                });
                // Llamada inicial para establecer el estado correcto al cargar
                toggleDateFilterState();


                // --- Checkbox para filtrar solo lugares con Update Requests (Movido a función dinámica) ---
                // NOTA: Este checkbox ahora se crea dinámicamente después de detectar el rank del usuario
                // Ver función createUpdateRequestsCheckbox() que se llama después de getCurrentEditorViaSdk()
                /*
                const editorRank = getEditorRank();
                console.log('[WME PLN] Editor Rank detectado para update requests filter:', editorRank);

                // Acceso restringido: Solo editores nivel 4, 5 y 6
                if (editorRank === 4 || editorRank === 5 || editorRank === 6) {
                    console.log(`[WME PLN] Creando checkbox de URs pendientes (Rank ${editorRank} autorizado)`);
                    const updateRequestsContainer = document.createElement('div');
                    updateRequestsContainer.style.marginTop = '8px';

                    const updateRequestsCheckboxRow = document.createElement("div");
                    updateRequestsCheckboxRow.style.display = "flex";
                    updateRequestsCheckboxRow.style.alignItems = "center";

                    const updateRequestsCheckbox = document.createElement("input");
                    updateRequestsCheckbox.type = "checkbox";
                    updateRequestsCheckbox.id = "chk-update-requests-only";
                    updateRequestsCheckbox.style.marginRight = "8px";

                    // Restaurar estado guardado
                    const savedUpdateRequestsState = localStorage.getItem("wme_pln_update_requests_only");
                    updateRequestsCheckbox.checked = (savedUpdateRequestsState === "true");
                    filterUpdateRequestsOnly = updateRequestsCheckbox.checked; // Sincronizar con variable global

                    updateRequestsCheckboxRow.appendChild(updateRequestsCheckbox);

                    const updateRequestsLabel = document.createElement("label");
                    updateRequestsLabel.htmlFor = "chk-update-requests-only";
                    updateRequestsLabel.style.fontSize = "16px";
                    updateRequestsLabel.style.cursor = "pointer";
                    updateRequestsLabel.style.fontWeight = "bold";
                    updateRequestsLabel.style.color = "#d32f2f"; // Rojo para destacar
                    updateRequestsLabel.innerHTML = `🔔 Solo lugares con URs pendientes`;
                    updateRequestsLabel.title = `Solo disponible para editores nivel 4, 5 y 6. Filtra solo lugares con solicitudes de actualización pendientes de aprobación.`;

                    updateRequestsCheckboxRow.appendChild(updateRequestsLabel);
                    updateRequestsContainer.appendChild(updateRequestsCheckboxRow);
                    recommendCategoriesWrapper.appendChild(updateRequestsContainer);

                    // Listener para guardar estado y actualizar variable global
                    updateRequestsCheckbox.addEventListener("change", () => {
                        filterUpdateRequestsOnly = updateRequestsCheckbox.checked;
                        localStorage.setItem("wme_pln_update_requests_only", updateRequestsCheckbox.checked ? "true" : "false");
                        console.log('[WME PLN] Update requests filter:', updateRequestsCheckbox.checked);
                    });
                } else {
                    console.log(`[WME PLN] Checkbox de URs NO creado. Rank actual: ${editorRank} (Se requiere nivel 4, 5 o 6)`);
                    // Asegurar que el filtro esté desactivado si no tiene permisos
                    filterUpdateRequestsOnly = false;
                }
                */


                // --- Contenedor para el checkbox de estadísticas ---
                const statsContainer = document.createElement('div');
                statsContainer.style.marginTop = '8px';
                // Añadir un borde y fondo para destacar
                const statsCheckboxRow = document.createElement("div");
                statsCheckboxRow.style.display = "flex";
                statsCheckboxRow.style.alignItems = "center";
                // Añadir un margen inferior para separar del checkbox de exclusión
                const statsCheckbox = document.createElement("input");
                statsCheckbox.type = "checkbox";
                statsCheckbox.id = "chk-enable-stats";
                statsCheckbox.style.marginRight = "8px";
                statsCheckbox.checked = localStorage.getItem(STATS_ENABLED_KEY) === 'true';
                statsCheckboxRow.appendChild(statsCheckbox);
                // Crear la etiqueta para el checkbox de estadísticas
                const statsLabel = document.createElement("label");
                statsLabel.htmlFor = "chk-enable-stats";
                statsLabel.style.fontSize = "16px"; // Tamaño consistente
                statsLabel.style.cursor = "pointer";
                statsLabel.style.fontWeight = "bold";
                statsLabel.style.color = "#00796b";
                statsLabel.innerHTML = `📊 Habilitar panel de estadísticas`;
                statsCheckboxRow.appendChild(statsLabel);
                // Añadir un tooltip al checkbox de estadísticas
                statsContainer.appendChild(statsCheckboxRow);
                // Añadir el contenedor de estadísticas al wrapper principal (el cuadro azul)
                recommendCategoriesWrapper.appendChild(statsContainer);
                // Listener para el checkbox de estadísticas
                statsCheckbox.addEventListener("change", () => {
                    localStorage.setItem(STATS_ENABLED_KEY, statsCheckbox.checked ? "true" : "false");
                    toggleStatsPanelVisibility();
                });
                //===========================Finaliza bloque de estadísticas
                // Listener para guardar el estado del nuevo checkbox
                avoidMyEditsCheckbox.addEventListener("change", () => { //
                    localStorage.setItem("wme_pln_avoid_my_edits", avoidMyEditsCheckbox.checked ? "true" : "false"); //
                });
                // Barra de progreso y texto
                const tabProgressWrapper = document.createElement("div");
                tabProgressWrapper.style.margin = "10px 0";
                tabProgressWrapper.style.height = "18px";
                tabProgressWrapper.style.backgroundColor = "transparent";
                const tabProgressBar = document.createElement("div");
                tabProgressBar.style.height = "100%";
                tabProgressBar.style.width = "0%";
                tabProgressBar.style.backgroundColor = "#007bff";
                tabProgressBar.style.transition = "width 0.2s";
                tabProgressBar.id = "progressBarInnerTab";
                tabProgressWrapper.appendChild(tabProgressBar);
                containerGeneral.appendChild(tabProgressWrapper);
                // Texto de progreso
                const tabProgressText = document.createElement("div");
                tabProgressText.style.fontSize = "13px";
                tabProgressText.style.marginTop = "5px";
                tabProgressText.id = "progressBarTextTab";
                tabProgressText.textContent = "Progreso: 0% (0/0)";
                containerGeneral.appendChild(tabProgressText);
                // Div para mostrar el resultado del análisis
                const outputNormalizationInTab = document.createElement("div");
                outputNormalizationInTab.id = "wme-normalization-tab-output";
                outputNormalizationInTab.style.fontSize = "12px";
                outputNormalizationInTab.style.minHeight = "20px";
                outputNormalizationInTab.style.padding = "5px";
                outputNormalizationInTab.style.marginBottom = "15px";
                outputNormalizationInTab.textContent = "Presiona 'Start Scan...' para analizar los places visibles.";
                containerGeneral.appendChild(outputNormalizationInTab);
            }
            else {
                console.error("[WME PLN] No se pudo poblar la pestaña 'General' porque su contenedor no existe.");
            }
            // 5. Poblar las otras pestañas
            if (tabContents["Espe"])
                createSpecialItemsManager(tabContents["Espe"]);
            else {
                console.error("[WME PLN] No se pudo encontrar el contenedor para la pestaña 'Especiales'.");
            }
            // --- Llamada A La Función Para Poblar La Nueva Pestaña "Diccionario"
            if (tabContents["Dicc"]) {
                createDictionaryManager(tabContents["Dicc"]);
            }
            else {
                console.error("[WME PLN] No se pudo encontrar el contenedor para la pestaña 'Diccionario'.");
            }
            // --- Llamada A La Función Para Poblar La Nueva Pestaña "Reemplazos"
            if (tabContents["Reemp"]) {
                createReplacementsManager(tabContents["Reemp"]); // Esta es la llamada clave
            }
            else {
                console.error("[WME PLN] No se pudo encontrar el contenedor para la pestaña 'Reemplazos'.");
            }
        }
        catch (error) {
            console.error("[WME PLN] Error creando la pestaña lateral:", error, error.stack);
        }
    } // Fin de createSidebarTab

    // 2. Esperar a que Waze API esté disponible
    function waitForSidebarAPI() {
        // Comprobar si Waze API está disponible
        if (W && W.userscripts && W.userscripts.registerSidebarTab) {
            const savedExcluded = localStorage.getItem("excludedWordsList");
            if (savedExcluded) {
                try {
                    const parsed = JSON.parse(savedExcluded);
                    excludedWords = new Set(parsed);
                    // Reconstruir el mapa optimizado
                    excludedWordsMap.clear();
                    excludedWords.forEach(word => {
                        const firstChar = word.charAt(0).toLowerCase();
                        if (!excludedWordsMap.has(firstChar)) {
                            excludedWordsMap.set(firstChar, new Set());
                        }
                        excludedWordsMap.get(firstChar).add(word);
                    });
                }
                catch (e) {
                    console.error("[WME PLN] Error al cargar palabras excluidas:", e);
                    excludedWords = new Set();
                    excludedWordsMap.clear();
                }
            }
            else {
                excludedWords = new Set();
                excludedWordsMap.clear();
            }

            // --- Cargar Lugares Excluidos desde localStorage ---
            loadExcludedPlacesFromStorage(); // Llamar a la función directamente

            // --- Refrescar palabras especiales por defecto si está habilitado ---
            loadExcludedWordsFromSheet(FORCE_SHEET_RELOAD)
                .catch(err => console.warn('[WME PLN] No se pudo cargar palabras especiales desde Sheets:', err));



            // --- Cargar diccionario desde localStorage ---
            const savedDictionary = localStorage.getItem("dictionaryWordsList");
            if (savedDictionary) {
                try {
                    const parsed = JSON.parse(savedDictionary);
                    window.dictionaryWords = new Set(parsed);
                    // Reconstruir el índice del diccionario
                    window.dictionaryIndex = {};
                    window.dictionaryWords.forEach(word => {
                        const firstChar = word.charAt(0).toLowerCase();
                        if (!window.dictionaryIndex[firstChar]) {
                            window.dictionaryIndex[firstChar] = [];
                        }
                        window.dictionaryIndex[firstChar].push(word);
                    });
                }
                catch (e) {
                    console.error("[WME PLN] Error al cargar diccionario:", e);
                    window.dictionaryWords = new Set();
                    window.dictionaryIndex = {};
                }
            }
            else {
                window.dictionaryWords = new Set();
                window.dictionaryIndex = {};
            }

            updateDictionaryWordsCountLabel();
            // Esto añadirá nuevas palabras del Excel a window.dictionaryWords y se encarga de guardar en localStorage después.
            // Se hace de forma asíncrona pero no bloquea la UI.
            loadDictionaryWordsFromSheet(FORCE_SHEET_RELOAD).then(() => {
                console.log("[WME PLN] Diccionario por defecto cargado y combinado.");
            }).catch(err => {
                console.error("[WME PLN] No se pudo cargar el diccionario por defecto:", err);
            });
            // Cargar estadísticas del editor
            loadEditorStats();
            // --- Cargar palabras de reemplazo desde localStorage ---
            loadReplacementWordsFromStorage();
            // Cargar reemplazos por defecto y fusionar (bloqueando los de hoja)
            loadReplacementsFromSheet(FORCE_SHEET_RELOAD).then(() => {
                const ul = document.getElementById("replacementsListElementID");
                if (ul && typeof renderReplacementsList === 'function') renderReplacementsList(ul);
            }).catch(err => console.warn('[WME PLN] No se pudo cargar Replacements desde Sheets:', err));
            // Cargar Swap por defecto (bloqueado) y fusionar con los del usuario
            loadSwapWordsFromSheet(FORCE_SHEET_RELOAD).then(() => {
                try { if (typeof saveSwapWordsToStorage === 'function') saveSwapWordsToStorage(); } catch (_) { }
            }).catch(err => console.warn('[WME PLN] No se pudo cargar Swap desde Sheets:', err));

            // === [PLN] Verificación de acceso basada en nivel de editor ===
            let accessAttempts = 0;
            const maxAccessAttempts = 10;

            /**
             * Registra un intento de acceso en la whitelist del servidor
             * Se llama cuando un usuario con nivel insuficiente intenta usar el script
             * @param {string} userName - Nombre de usuario
             * @param {number} userLevel - Nivel actual del usuario
             */
            function registerAccessAttempt(userName, userLevel) {
                const accessCtrl = window.PLN_ACCESS_CONTROL || (typeof PLN_ACCESS_CONTROL !== 'undefined' ? PLN_ACCESS_CONTROL : {});
                if (!accessCtrl.enabled || !accessCtrl.endpoint) {
                    console.log('[WME PLN] Access Control no habilitado, no se registra intento.');
                    return;
                }

                const currentVersion = (typeof VERSION !== 'undefined') ? VERSION : '0.0.0';
                const userId = (currentGlobalUserInfo && currentGlobalUserInfo.id) ? currentGlobalUserInfo.id : '';
                
                const url = `${accessCtrl.endpoint}?action=registerAttempt&user=${encodeURIComponent(userName)}&userId=${encodeURIComponent(userId)}&level=${userLevel}&version=${encodeURIComponent(currentVersion)}`;

                makeRequest({
                    method: 'GET',
                    url: url,
                    timeout: accessCtrl.timeoutMs || 8000,
                    onload: function(response) {
                        try {
                            const txt = (response.responseText || '').trim();
                            if (txt.startsWith('{')) {
                                const data = JSON.parse(txt);
                                if (data.success) {
                                    if (data.action === 'registered') {
                                        console.log(`[WME PLN] 📝 Usuario ${userName} registrado en lista blanca (pendiente de activación).`);
                                        try { plnToast(`📝 Tu solicitud de acceso ha sido registrada. Contacta al administrador para activación.`, 7000); } catch (_) { }
                                    } else if (data.action === 'already_exists') {
                                        console.log(`[WME PLN] ⏳ Usuario ${userName} ya está registrado en lista blanca.`);
                                        try { plnToast(`⏳ Ya estás registrado. Si tu acceso no está activo, contacta al administrador.`, 6000); } catch (_) { }
                                    }
                                }
                            }
                        } catch (e) {
                            console.warn('[WME PLN] Error procesando respuesta de registerAttempt:', e);
                        }
                    },
                    onerror: function(err) {
                        console.warn('[WME PLN] Error registrando intento de acceso:', err);
                    }
                });
            }

            /**
             * Consulta al servidor si el usuario está en la lista blanca
             * @param {string} userName - Nombre de usuario
             * @param {string} module - 'main' o 'easyshortcuts'
             * @returns {Promise<Object>} Resultado de la verificación
             */
            async function checkWhitelistAccess(userName, module = 'main') {
                const accessCtrl = window.PLN_ACCESS_CONTROL || (typeof PLN_ACCESS_CONTROL !== 'undefined' ? PLN_ACCESS_CONTROL : {});
                if (!accessCtrl.enabled || !accessCtrl.endpoint) {
                    return { success: false, isWhitelisted: false, reason: 'Access Control no configurado' };
                }

                return new Promise((resolve) => {
                    // [PLN] Enviar versión para control de actualizaciones forzadas
                    const currentVersion = (typeof VERSION !== 'undefined') ? VERSION : '0.0.0';
                    const url = `${accessCtrl.endpoint}?action=checkAccess&user=${encodeURIComponent(userName)}&module=${module}&version=${encodeURIComponent(currentVersion)}`;

                    makeRequest({
                        method: 'GET',
                        url: url,
                        timeout: accessCtrl.timeoutMs,
                        onload: function(response) {
                            try {
                                // Verificar si parece JSON antes de parsear
                                const txt = (response.responseText || '').trim();
                                if (!txt.startsWith('{') && !txt.startsWith('[')) {
                                    // console.warn('[WME PLN] ⚠️ Access Control NO devolvió JSON. Probablemente el Apps Script no está actualizado.');
                                    // console.warn('[WME PLN] ⚠️ Respuesta (primeros 200 chars):', txt.slice(0, 200));
                                    resolve({ success: false, isWhitelisted: false, error: 'Respuesta no es JSON' });
                                    return;
                                }
                                const data = JSON.parse(txt);
                                // console.log('[WME PLN] 🔐 Respuesta de Access Control:', data);
                                resolve(data);
                            } catch (e) {
                                console.warn('[WME PLN] Error parseando respuesta de Access Control:', e);
                                console.warn('[WME PLN] Respuesta cruda:', response.responseText);
                                resolve({ success: false, isWhitelisted: false, error: e.message });
                            }
                        },
                        onerror: function(err) {
                            console.warn('[WME PLN] Error conectando a Access Control:', err);
                            resolve({ success: false, isWhitelisted: false, error: 'Error de conexión' });
                        },
                        ontimeout: function() {
                            console.warn('[WME PLN] Timeout en Access Control');
                            resolve({ success: false, isWhitelisted: false, error: 'Timeout' });
                        }
                    });
                });
            }

            /**
             * Obtiene la configuración global desde el servidor
             */
            async function fetchGlobalConfig() {
                const accessCtrl = window.PLN_ACCESS_CONTROL || (typeof PLN_ACCESS_CONTROL !== 'undefined' ? PLN_ACCESS_CONTROL : {});
                if (!accessCtrl.enabled || !accessCtrl.endpoint) return;

                return new Promise((resolve) => {
                    const url = `${accessCtrl.endpoint}?action=getConfig`;
                    makeRequest({
                        method: 'GET',
                        url: url,
                        timeout: accessCtrl.timeoutMs,
                        onload: function(response) {
                            try {
                                // Verificar si la respuesta parece JSON antes de parsear
                                const txt = (response.responseText || '').trim();
                                if (!txt.startsWith('{') && !txt.startsWith('[')) {
                                    // No es JSON, probablemente HTML de error o página de login
                                    // Silenciosamente ignorar - el servidor no tiene getConfig implementado
                                    resolve(false);
                                    return;
                                }
                                const data = JSON.parse(txt);
                                if (data.success && data.config) {
                                    // console.log('[WME PLN] ⚙️ Configuración recibida:', data.config);
                                    if (data.config.defaultMinLevel) accessCtrl.defaultMinLevel = Number(data.config.defaultMinLevel);
                                    if (data.config.easyShortcutsMinLevel) accessCtrl.easyShortcutsMinLevel = Number(data.config.easyShortcutsMinLevel);

                                    // Actualizar variables globales si existen
                                    if (typeof PLN_ACC_LVL !== 'undefined') PLN_ACC_LVL = accessCtrl.defaultMinLevel - 1; // rank is level - 1
                                    // EasyShortCuts usa accessCtrl directamente
                                }
                                resolve(true);
                            } catch (e) {
                                // Fallo silencioso - la configuración dinámica es opcional
                                // El script funcionará con los valores por defecto
                                resolve(false);
                            }
                        },
                        onerror: function() { resolve(false); },
                        ontimeout: function() { resolve(false); }
                    });
                });
            }

            async function plnValidateAccess(callback) {
                let userRank = -1;
                let userName = '';

                // Intentar obtener rank y nombre desde múltiples fuentes
                try {
                    if (typeof W !== 'undefined' && W.loginManager) {
                        // Obtener nombre de usuario desde múltiples fuentes
                        userName = W.loginManager.userName || '';
                        if (!userName && W.loginManager.user) {
                            userName = W.loginManager.user.userName || W.loginManager.user.name || '';
                        }
                        if (!userName && W.loginManager.user && W.loginManager.user.attributes) {
                            userName = W.loginManager.user.attributes.userName || '';
                        }

                        if (W.loginManager.user && W.loginManager.user.rank != null) {
                            userRank = Number(W.loginManager.user.rank);
                        } else if (W.loginManager.user && W.loginManager.user.attributes && W.loginManager.user.attributes.rank != null) {
                            userRank = Number(W.loginManager.user.attributes.rank);
                        } else if (W.loginManager.getUser) {
                            const u = W.loginManager.getUser();
                            if (u && u.rank != null) userRank = Number(u.rank);
                            else if (u && u.attributes && u.attributes.rank != null) userRank = Number(u.attributes.rank);
                        }
                    }

                    console.log(`[WME PLN] 🔍 DEBUG plnValidateAccess: userName="${userName}", userRank=${userRank}`);
                } catch (e) {
                    console.warn('[WME PLN] Error obteniendo rank del usuario:', e);
                }

                // Si aún no se puede determinar el rank, reintentar
                if (userRank < 0) {
                    accessAttempts++;
                    if (accessAttempts < maxAccessAttempts) {
                        console.log(`[WME PLN] Esperando información del usuario (intento ${accessAttempts}/${maxAccessAttempts})...`);
                        setTimeout(() => plnValidateAccess(callback), 1000);
                        return;
                    } else {
                        console.error('[WME PLN] ⛔ No se pudo determinar el nivel del usuario. Acceso denegado.');
                        try { plnToast('⛔ WME PLN: No se pudo verificar tu nivel de editor', 4000); } catch (_) { }
                        return;
                    }
                }

                const userLevel = userRank + 1; // rank 0 = nivel 1, rank 2 = nivel 3

            // 📌 Asegurar que currentGlobalUserInfo esté poblada ANTES del disclaimer
            if (!currentGlobalUserInfo || !currentGlobalUserInfo.id) {
                try {
                    let userId = null;
                    if (typeof W !== 'undefined' && W.loginManager) {
                        const u = W.loginManager.user || W.loginManager.getUser?.();
                        if (u) {
                            userId = u.id || u.attributes?.id || null;
                        }
                    }
                    currentGlobalUserInfo = {
                        id: userId,
                        name: userName || 'No detectado',
                        privilege: userRank >= 0 ? userRank : 'N/A'
                    };
                    console.log(`[WME PLN] 📌 currentGlobalUserInfo poblada:`, currentGlobalUserInfo);
                } catch (e) {
                    console.warn('[WME PLN] Error poblando currentGlobalUserInfo:', e);
                }
            }

            // 🔐 PASO 0: Obtener configuración global (si está habilitado)
            if (PLN_ACCESS_CONTROL.enabled) {
                await fetchGlobalConfig();
            }

            // 📋 PASO 0.5: Verificar disclaimer LOCAL para TODOS los usuarios (antes de cualquier otra validación)
            const DISCLAIMER_KEY = `wme_pln_disclaimer_v${VERSION}_accepted`;
            const localDisclaimerAccepted = localStorage.getItem(DISCLAIMER_KEY);
            
            // Si no ha respondido al disclaimer localmente (null), mostrar popup
            if (localDisclaimerAccepted === null) {
                console.log(`[WME PLN] 📋 Usuario ${userName} debe aceptar términos y condiciones (localStorage check).`);
                if (typeof showDisclaimerPopup === 'function') {
                    showDisclaimerPopup((accepted) => {
                        if (accepted) {
                            console.log(`[WME PLN] ✅ Términos aceptados localmente. Continuando validación...`);
                            // Reiniciar la validación ahora que aceptó
                            plnValidateAccess(callback);
                        } else {
                            console.log(`[WME PLN] ❌ Términos rechazados. Script no habilitado.`);
                            try { plnToast(`❌ Debes aceptar los términos para usar el script`, 5000); } catch (_) { }
                        }
                    });
                    return; // Esperar respuesta del popup
                }
            } else if (localDisclaimerAccepted === 'false') {
                // Si rechazó anteriormente, bloquear
                console.log(`[WME PLN] ❌ Usuario ${userName} rechazó términos anteriormente.`);
                try { plnToast(`❌ El script no se ha habilitado porque rechazaste los términos`, 5000); } catch (_) { }
                return;
            }
            // Si localDisclaimerAccepted === 'true', continuar con la validación normal

            // 🔐 PASO 1: Verificar lista blanca desde el servidor
                if (userName && PLN_ACCESS_CONTROL.enabled) {
                    // console.log(`[WME PLN] 🔐 Verificando lista blanca para usuario: ${userName}`);
                    const whitelistResult = await checkWhitelistAccess(userName, 'main');

                    // Si llegamos aquí, localStorage ya validó el disclaimer, así que solo verificamos whitelist
                    if (whitelistResult.success && whitelistResult.isWhitelisted && (whitelistResult.hasAccess || whitelistResult.disclaimerPending)) {
                        // ✅ Usuario en lista blanca - permitir uso (localStorage ya manejó disclaimer)
                        console.log(`[WME PLN] ✅ Usuario ${userName} en LISTA BLANCA. Habilitando script.`);
                        if (typeof callback === 'function') callback();
                        return;
                    } else if (whitelistResult.success && whitelistResult.isWhitelisted && !whitelistResult.hasAccess) {
                        // 🚫 Usuario en lista blanca pero sin acceso - verificar razón
                        // console.log(`[WME PLN] 🚫 Usuario ${userName} está en lista blanca pero sin acceso. Razón: ${whitelistResult.reason}`);
                        
                        // Mostrar mensaje específico según la razón
                        if (whitelistResult.disclaimerAccepted === false) {
                            try { plnToast(`❌ El script no se ha habilitado porque no aceptaste los términos y condiciones`, 8000); } catch (_) { }
                        } else {
                            try { plnToast(`🚫 Tu acceso a WME PLN ha sido desactivado por el administrador`, 8000); } catch (_) { }
                        }
                        return; // NO CONTINUAR - bloquear completamente
                    } else if (whitelistResult.success && !whitelistResult.isWhitelisted) {
                        // Usuario no está en lista blanca - usar validación de nivel normal
                        console.log(`[WME PLN] 📋 Usuario ${userName} no está en lista blanca. Verificando nivel...`);
                    } else {
                        // Error de servidor - usar validación de nivel como fallback
                        console.warn(`[WME PLN] ⚠️ No se pudo verificar lista blanca. Usando validación de nivel local.`);
                    }
                }

                // 🔐 PASO 2: Verificación de nivel local (fallback o no-whitelist)
                if (userRank >= PLN_ACC_LVL) {
                    console.log(`[WME PLN] ✅ Usuario Nivel ${userLevel} detectado. Habilitando script.`);
                    // Proceder con la inicialización
                    if (typeof callback === 'function') callback();
                } else {
                    console.log(`[WME PLN] ⛔ Usuario Nivel ${userLevel}. El script requiere Nivel ${PLN_ACC_LVL + 1}+.`);
                    try { plnToast(`⛔ WME PLN requiere nivel de editor ${PLN_ACC_LVL + 1} o superior`, 5000); } catch (_) { }
                    
                    // 🔐 PASO 2.1: Registrar intento de acceso en la whitelist para que el admin pueda activarlo
                    registerAccessAttempt(userName, userLevel);
                    return;
                }
            }

            // Validar acceso antes de inicializar
            plnValidateAccess(() => {
                // La llamada a waitForWazeAPI ya se encarga de la lógica de dynamicCategoriesLoaded.
                waitForWazeAPI(() => {
                if (!dynamicCategoriesLoaded) {
                    loadDynamicCategoriesFromSheet(FORCE_SHEET_RELOAD).then(() => {
                        dynamicCategoriesLoaded = true;
                        console.log("[WME PLN] Categorías dinámicas cargadas.");
                    }).catch(err => {
                        console.error("[WME PLN] Error al cargar categorías dinámicas:", err);
                    });
                }
                createSidebarTab();
                createStatsPanel();
                tryInitializeSDK(() => {
                    if (window.__PLN_DECISION_DEBUG_ON) console.log("[WME PLN] SDK inicializado, obteniendo usuario...");
                    getCurrentEditorViaSdk().then(userInfo => {
                        if (userInfo) {
                            currentGlobalUserInfo = userInfo;
                            console.log(`[WME PLN] Usuario SDK detectado: ${userInfo.name} (ID: ${userInfo.id})`);
                            console.log('[WME PLN] 🔍 DEBUG - userInfo completo:', userInfo);
                        } else {
                            console.warn("[WME PLN] No se pudo obtener usuario del SDK, intentando otros métodos.");
                            const wrapUser = getCurrentEditorViaWazeWrap();
                            if (wrapUser) {
                                currentGlobalUserInfo = wrapUser;
                                console.log(`[WME PLN] Usuario WazeWrap detectado: ${wrapUser.name}`);
                                console.log('[WME PLN] 🔍 DEBUG - wrapUser completo:', wrapUser);
                            } else {
                                const internalUser = getCurrentEditorViaWmeInternal();
                                if (internalUser) {
                                    currentGlobalUserInfo = internalUser;
                                    // console.log(`[WME PLN] Usuario WME interno detectado: ${internalUser.name}`);
                                    // console.log('[WME PLN] 🔍 DEBUG - internalUser completo:', internalUser);
                                } else {
                                    console.error("[WME PLN] No se pudo detectar el usuario.");
                                    currentGlobalUserInfo = { name: "No detectado", id: null, privilege: null };
                                }
                            }
                        }
                        updateStatsDisplay();

                        // 🆕 Track user activity in separate Google Sheet
                        // Asegurar que las estadísticas estén cargadas antes de enviar
                        loadEditorStats();
                        // console.log('[WME PLN] 📊 EditorStats cargado antes de tracking:', editorStats);

                        // This runs every time the script loads (unlike daily usage log)
                        plnTrackUserActivity();

                        //  Crear checkbox de URs después de detectar usuario y rank
                        createUpdateRequestsCheckbox();
                    });
                });
            });
            }); // Fin del callback de plnValidateAccess
        }
        else {
            // console.log("[WME PLN] Esperando W.userscripts API...");
            setTimeout(waitForSidebarAPI, 1000);
        }

        window.PLN_READY = { ts: Date.now(), version: VERSION };

        window.dispatchEvent(new CustomEvent('PLN_READY', { detail: { version: VERSION } }));
        console.log('[WME PLN] Señal PLN_READY emitida');
    }// Fin de waitForSidebarAPI

    // 1. normalizePlaceName
    // REEMPLAZA ESTA FUNCIÓN EN TU ARCHIVO wme_pln_8.2.0.js
    function normalizePlaceName(originalName) {
        if (isProcessingActive) {
            //plnToast('Ya hay un proceso de normalización en curso. Espera a que termine.', 3000);
            return;
        }
        isProcessingActive = true;


        // --- Helpers de capitalización (integrados para que la función sea autocontenida) ---
        function plnCapitalizeStart(str) {
            try { return String(str || '').replace(/^\s*([a-záéíóúñ])/iu, (m, c) => m.replace(c, c.toUpperCase())); } catch { return str; }
        }

        function plnCapitalizeAfterHyphen(str) {
            try {
                // Capitaliza PALABRAS COMPLETAS después de guion con espacios: " - la" → " - La"
                // Esto sobrescribe STOP words como "la", "el", "de", etc.
                let result = String(str || '').replace(/(\s-\s*)([a-záéíóúñ][\wáéíóúñ]*)/giu, (m, sep, word) => {
                    return sep + word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
                });
                // Capitaliza después de guion sin espacio antes: "word-a" → "word-A"
                result = result.replace(/([^\s])-([a-záéíóúñ][\wáéíóúñ]*)/giu, (m, before, word) => {
                    return before + '-' + word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
                });
                return result;
            } catch (_) { return String(str || ''); }
        }

        function plnTitleCaseEs(str) {
            try {
                const STOP = new Set(['de', 'la', 'las', 'el', 'los', 'y', 'e', 'o', 'u', 'un', 'una', 'unos', 'unas', 'a', 'en', 'con', 'tras', 'por', 'al', 'lo']);
                const isAllCaps = w => w.length > 1 && w === w.toUpperCase();
                const cap = w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
                let i = 0;
                return String(str || '').replace(/([\p{L}\p{M}][\p{L}\p{M}\.'']*)/gu, (m) => {
                    const w = m, lw = w.toLowerCase(), atStart = (i === 0); i += w.length;
                    const excl = (typeof isExcludedWord === 'function') ? isExcludedWord(w) : null;
                    if (excl) return excl;
                    if (isAllCaps(w)) return w;
                    if (STOP.has(lw) && !atStart) return lw;
                    return cap(w);
                });
            } catch { return str; }
        }

        function plnPostSwapCap(str) {
            let out = String(str || '');
            out = plnTitleCaseEs(out);
            out = plnCapitalizeStart(out);
            // Aplicar capitalización después de guion AL FINAL para sobrescribir STOP words
            out = plnCapitalizeAfterHyphen(out);
            return out.trim();
        }

        // --- Inicia el proceso de normalización ---
        const DBG = !!(window.__PLN_DECISION_DEBUG_ON || localStorage.getItem('wme_pln_debug_decision') === '1');
        if (DBG) console.group('[PLN Normalize] Proceso para:', originalName);

        // 1. Sanitiza la entrada (emoji-free) antes de aplicar reglas
        const sanitizedInput = (typeof removeEmoticons === 'function')
            ? removeEmoticons(originalName)
            : String(originalName || '');
        if (DBG && sanitizedInput !== originalName) console.log('0. Después de removeEmoticons:', sanitizedInput);

        // 2. Procesa el nombre base (capitalización inicial, reglas de palabras, etc.)
        let processedName = processPlaceName(sanitizedInput);
        if (DBG) console.log('1. Después de processPlaceName:', processedName);

        // 2. Aplica las reglas de SWAP para mover palabras
        let swappedName = applySwapRules(processedName);
        if (DBG) console.log('2. Después de applySwapRules:', swappedName);

        // 3. ✨ **CORRECCIÓN CLAVE**: Vuelve a aplicar la capitalización después del swap
        let finalName = plnPostSwapCap(swappedName);
        if (DBG) console.log('3. Después de plnPostSwapCap (capitalización final):', finalName);

        // 4. Limpieza final y restauración de palabras especiales
        finalName = plnApplyExclusions(finalName);
        if (typeof plnFixDanglingHyphen === 'function') {
            finalName = plnFixDanglingHyphen(finalName);
            if (DBG) console.log('4.1 Después de plnFixDanglingHyphen:', finalName);
        }

        // 5. PASO FINAL: Capitalización después de guiones (sobrescribe STOP words)
        // Esto se ejecuta después de todas las exclusiones y limpiezas para asegurar
        // que palabras como "la", "el", "de" después de guiones queden en mayúscula
        finalName = plnCapitalizeAfterHyphen(finalName);
        if (DBG) console.log('4.2 Después de capitalización final post-guion:', finalName);

        // 6. LIMPIEZA FINAL: Eliminar guiones colgantes que puedan quedar
        // Ejemplo: "Terpel -" (sin nada después del guion) → "Terpel"
        // Pero preserva: "Terpel - La Ceiba" (con palabras después del guion)
        if (typeof plnFixDanglingHyphen === 'function') {
            finalName = plnFixDanglingHyphen(finalName);
            if (DBG) console.log('4.3 Limpieza final de guiones colgantes:', finalName);
        }

        if (DBG) {
            console.log('4. Resultado final (tras exclusiones):', finalName);
            console.groupEnd();
        }

        return finalName.trim();
    }// Fin de normalizePlaceName

    // Función para aplicar el movimiento de palabras al inicio del nombre.
    function applyWordsToStartMovement(name, wordsArray = null) {
        let newName = name;
        const wordsToProcess = wordsArray || (window.swapWords || []);
        if (wordsToProcess.length === 0) {
            return newName;
        }
        const sortedWords = [...wordsToProcess].sort((a, b) => {
            const aWord = typeof a === 'object' ? a.word : a;
            const bWord = typeof b === 'object' ? b.word : b;
            return bWord.length - aWord.length;
        });

        for (const item of sortedWords) {
            const word = typeof item === 'object' ? item.word : item;
            const regex = new RegExp(`\\s*(${escapeRegExp(String(word))})\\s*$`, 'i');
            if (regex.test(newName)) {
                const match = newName.match(regex);
                const matchedWord = match[1];
                const remainingName = newName.replace(regex, '').trim();
                const capitalizedWord = capitalizeEachWord(matchedWord);

                // ✅ CORRECCIÓN: No se modifica la capitalización del resto del nombre.
                // Se asume que "La Calleja" ya viene correctamente capitalizado.
                newName = `${capitalizedWord} ${remainingName}`.trim();
                break;
            }
        }
        return newName;
    }//applyWordsToStartMovement


    // Esta función aplica el movimiento de palabras al final del nombre.
    function applyWordsToEndMovement(name, wordsArray = null) {
        let newName = name;
        const wordsToProcess = wordsArray || (window.swapWords || []);
        if (wordsToProcess.length === 0) {
            return newName;
        }
        const sortedWords = [...wordsToProcess].sort((a, b) => {
            const aWord = typeof a === 'object' ? a.word : a;
            const bWord = typeof b === 'object' ? b.word : b;
            return bWord.length - aWord.length;
        });

        for (const item of sortedWords) {
            const word = typeof item === 'object' ? item.word : item;
            const regex = new RegExp(`^\\s*(${escapeRegExp(String(word))})\\s+`, 'i');
            if (regex.test(newName)) {
                const match = newName.match(regex);
                const matchedWord = match[1];
                const remainingName = newName.replace(regex, '').trim();
                const capitalizedWord = capitalizeEachWord(matchedWord);

                // ✅ CORRECCIÓN: Se mantiene la capitalización del resto del nombre.
                newName = `${remainingName} ${capitalizedWord}`.trim();
                break;
            }
        }
        return newName;
    }//applyWordsToEndMovement

    // Función para aplicar el movimiento de palabras (unificada)
    function applySwapMovement(name) {
        let newName = name;

        if (!window.swapWords || window.swapWords.length === 0) {
            return newName;
        }

        // Separar palabras por dirección
        const startWords = window.swapWords
            .filter(item => item.direction === "start")
            .map(item => item.word);
        const endWords = window.swapWords
            .filter(item => item.direction === "end")
            .map(item => item.word);

        // Aplicar movimiento al inicio (mover del final al inicio)
        if (startWords.length > 0) {
            newName = applyWordsToStartMovement(newName, startWords);
        }

        // Aplicar movimiento al final (mover del inicio al final)
        if (endWords.length > 0) {
            newName = applyWordsToEndMovement(newName, endWords);
        }

        return newName;
    }//applySwapMovement

    //---------------------------------------------------------------------
    // Función para capitalizar la primera letra de cada palabra en una cadena
    function implementarCargaBajoDemanda(contenedor, datos, tamañoPagina = 50) {
        let indiceActual = 0;
        const contenedorElementos = document.createElement('div');
        contenedorElementos.className = 'diccionario-contenido';
        contenedor.appendChild(contenedorElementos);

        // Cargar batch inicial
        cargarSiguienteLote();

        // Agregar detector de scroll
        contenedor.addEventListener('scroll', manejarScroll);

        // Función para manejar el evento de scroll
        function manejarScroll() {
            if (contenedor.scrollTop + contenedor.clientHeight >= contenedorElementos.offsetHeight - 200) {
                cargarSiguienteLote();
            }
        }//manejarScroll
        // Función para cargar el siguiente lote de elementos
        function cargarSiguienteLote() {
            const fragmento = document.createDocumentFragment();
            const limite = Math.min(indiceActual + tamañoPagina, datos.length);

            for (let i = indiceActual; i < limite; i++) {
                const elemento = crearElementoDiccionario(datos[i]);
                fragmento.appendChild(elemento);
            }

            contenedorElementos.appendChild(fragmento);
            indiceActual = limite;

            // Eliminar detector de scroll si ya se cargaron todos los elementos
            if (indiceActual >= datos.length) {
                contenedor.removeEventListener('scroll', manejarScroll);
            }
        }//cargarSiguienteLote
        // Función para crear un elemento del diccionario
        function crearElementoDiccionario(datoElemento) {
            const elemento = document.createElement('div');
            elemento.className = 'diccionario-item';
            elemento.innerHTML = `
            <h3>${datoElemento.termino}</h3>
            <p>${datoElemento.definicion}</p>
        `;
            return elemento;
        }//crearElementoDiccionario
    }
    // Función para agregar un indicador de carga al contenedor
    function agregarIndicadorCarga(contenedor) {
        const indicador = document.createElement('div');
        indicador.className = 'indicador-carga';
        indicador.innerHTML = '<span>Cargando más términos...</span>';
        indicador.style.display = 'none';
        contenedor.appendChild(indicador);

        return {
            mostrar: () => { indicador.style.display = 'block'; },
            ocultar: () => { indicador.style.display = 'none'; }
        };
    }//agregarIndicadorCarga
    function agregarEstilosDiccionario() {
        // Creamos los elementos del diccionario dinámico con estilos aplicados directamente

        // Estilos para el contenedor de diccionario
        const dictionaryContainerStyle = {
            maxHeight: "250px",
            overflowY: "auto",
            border: "1px solid #ddd",
            padding: "5px",
            margin: "0",
            background: "#fff",
            borderRadius: "4px"
        };

        // Estilos para cada elemento de diccionario
        const dictionaryItemStyle = {
            padding: "8px 10px",
            borderBottom: "1px solid #eee",
            transition: "background-color 0.2s",
            cursor: "default",
            marginBottom: "4px",
            borderRadius: "3px"
        };

        // Estilos para el estado hover de los elementos
        const dictionaryItemHoverStyle = {
            backgroundColor: "#f5f5f5"
        };

        // Estilos para el título de cada elemento
        const dictionaryItemTitleStyle = {
            margin: "0 0 3px 0",
            fontSize: "14px",
            color: "#333",
            fontWeight: "500"
        };

        // Estilos para la descripción de cada elemento (si existe)
        const dictionaryItemDescriptionStyle = {
            margin: "0",
            fontSize: "12px",
            color: "#666",
            fontStyle: "italic"
        };

        // Estilos para el indicador de carga
        const indicadorCargaStyle = {
            textAlign: "center",
            padding: "10px",
            color: "#777",
            fontStyle: "italic",
            borderTop: "1px dashed #ddd",
            margin: "5px 0 0 0",
            fontSize: "12px"
        };

        // Aplicar estilos al contenedor principal cuando se cree
        const applyContainerStyles = (container) => {
            if (!container) return;

            Object.entries(dictionaryContainerStyle).forEach(([property, value]) => {
                container.style[property] = value;
            });
        };

        // Función para aplicar estilos a cada elemento del diccionario
        window.plnApplyDictionaryItemStyles = (element) => {
            if (!element) return;

            // Aplicar estilos base
            Object.entries(dictionaryItemStyle).forEach(([property, value]) => {
                element.style[property] = value;
            });

            // Añadir eventos para el hover
            element.addEventListener('mouseenter', () => {
                Object.entries(dictionaryItemHoverStyle).forEach(([property, value]) => {
                    element.style[property] = value;
                });
            });

            element.addEventListener('mouseleave', () => {
                element.style.backgroundColor = dictionaryItemStyle.backgroundColor || '';
            });

            // Aplicar estilos al título si existe
            const title = element.querySelector('h3');
            if (title) {
                Object.entries(dictionaryItemTitleStyle).forEach(([property, value]) => {
                    title.style[property] = value;
                });
            }

            // Aplicar estilos a la descripción si existe
            const description = element.querySelector('p');
            if (description) {
                Object.entries(dictionaryItemDescriptionStyle).forEach(([property, value]) => {
                    description.style[property] = value;
                });
            }
        };

        // Función para crear y aplicar estilos al indicador de carga
        window.plnCreateLoadingIndicator = (container) => {
            if (!container) return null;

            const indicador = document.createElement('div');
            indicador.className = 'indicador-carga';

            // Aplicar estilos al indicador
            Object.entries(indicadorCargaStyle).forEach(([property, value]) => {
                indicador.style[property] = value;
            });

            // Crear el contenido del indicador
            const spinner = document.createElement('div');
            spinner.style.width = "16px";
            spinner.style.height = "16px";
            spinner.style.border = "2px solid #ccc";
            spinner.style.borderTop = "2px solid #007bff";
            spinner.style.borderRadius = "50%";
            spinner.style.animation = "spin 0.8s linear infinite";
            spinner.style.display = "inline-block";
            spinner.style.marginRight = "8px";
            spinner.style.verticalAlign = "middle";

            const texto = document.createElement('span');
            texto.textContent = 'Cargando más términos...';
            texto.style.verticalAlign = "middle";

            indicador.appendChild(spinner);
            indicador.appendChild(texto);
            indicador.style.display = 'none';
            container.appendChild(indicador);

            return {
                mostrar: () => { indicador.style.display = 'block'; },
                ocultar: () => { indicador.style.display = 'none'; }
            };
        };

        // Inicializar aplicación de estilos al contenedor de diccionario
        const dictionaryContainer = document.getElementById('dictionaryContainer');
        if (dictionaryContainer) {
            applyContainerStyles(dictionaryContainer);
        } else {
            // Si el contenedor aún no existe, preparar un observador para detectarlo cuando se cree
            const observer = new MutationObserver((mutations) => {
                for (const mutation of mutations) {
                    if (mutation.type === 'childList') {
                        const dictionaryContainer = document.getElementById('dictionaryContainer');
                        if (dictionaryContainer) {
                            applyContainerStyles(dictionaryContainer);
                            observer.disconnect();
                            break;
                        }
                    }
                }
            });

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

        // Asegurarse de que existe la animación de spin en la página
        if (!document.getElementById('wme-pln-animations')) {
            const styleElement = document.createElement('style');
            styleElement.id = 'wme-pln-animations';
            styleElement.textContent = `
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
        `;
            document.head.appendChild(styleElement);
        }
    }

    // Modificar la función inicializarDiccionarioDinamico para usar estos estilos
    function inicializarDiccionarioDinamico(contenedorId, datos) {
        const contenedor = document.getElementById(contenedorId);
        if (!contenedor) return;

        // Aplicar estilos al contenedor (por si se llamó antes de que existiera el observador)
        if (window.plnApplyDictionaryContainerStyles) {
            window.plnApplyDictionaryContainerStyles(contenedor);
        }

        // Usar el nuevo creador de indicador
        const indicadorCarga = window.plnCreateLoadingIndicator ?
            window.plnCreateLoadingIndicator(contenedor) :
            agregarIndicadorCarga(contenedor);

        let indiceActual = 0;
        const tamañoPagina = 50;
        const contenedorElementos = document.createElement('div');
        contenedorElementos.className = 'diccionario-contenido';
        contenedor.appendChild(contenedorElementos);

        // Cargar batch inicial
        cargarSiguienteLote();

        // Agregar detector de scroll
        contenedor.addEventListener('scroll', manejarScroll);

        // Función para manejar el evento de scroll
        function manejarScroll() {
            if (contenedor.scrollTop + contenedor.clientHeight >= contenedorElementos.offsetHeight - 200) {
                cargarSiguienteLote();
            }
        }

        // Función para cargar el siguiente lote de elementos
        function cargarSiguienteLote() {
            if (indiceActual >= datos.length) return;

            indicadorCarga.mostrar();

            // Simulamos un pequeño retraso para ver el indicador de carga
            setTimeout(() => {
                const fragmento = document.createDocumentFragment();
                const limite = Math.min(indiceActual + tamañoPagina, datos.length);

                for (let i = indiceActual; i < limite; i++) {
                    const elemento = crearElementoDiccionario(datos[i]);
                    fragmento.appendChild(elemento);
                }

                contenedorElementos.appendChild(fragmento);
                indiceActual = limite;

                indicadorCarga.ocultar();

                // Eliminar detector de scroll si ya se cargaron todos los elementos
                if (indiceActual >= datos.length) {
                    contenedor.removeEventListener('scroll', manejarScroll);
                }
            }, 300);
        }

        // Función para crear un elemento del diccionario
        function crearElementoDiccionario(datoElemento) {
            const elemento = document.createElement('div');
            elemento.className = 'diccionario-item';

            // Crear estructura interna
            const titulo = document.createElement('h3');
            titulo.textContent = datoElemento.termino;

            const descripcion = document.createElement('p');
            descripcion.textContent = datoElemento.definicion || '';

            elemento.appendChild(titulo);
            elemento.appendChild(descripcion);

            // Aplicar estilos si existe la función
            if (window.plnApplyDictionaryItemStyles) {
                window.plnApplyDictionaryItemStyles(elemento);
            }

            return elemento;
        }
    }

    // Llamar a la función para añadir estilos al inicio
    agregarEstilosDiccionario();

    //---------------------------------------------------------------------
    // Esta función normaliza una palabra individual, considerando palabras excluidas, tildes y capitalización
    function normalizeWordInternal(word, isFirstWordInSequence = false, isInsideQuotesOrParentheses = false) {
        //console.log(`[WME PLN - NWI] Inicia procesamiento de palabra: "${word}"`); // LOG INICIO
        if (!word || typeof word !== 'string') {
            return "";
        }
        // PRioridad 1: Palabras Especiales (Excluidas)
        if (excludedWords && excludedWordsMap) {
            //console.log(`[WME PLN - NWI] Intentando Prioridad 1 (Excluidas) para: "${word}"`); // LOG INICIO EXCLUIDAS

            // La limpieza para comparación ahora SÓLO quita tildes y convierte a minúsculas.
            // Ya no elimina símbolos como '&' o '.', haciendo la comparación más estricta.
            const cleanedInputWord = removeDiacritics(word.toLowerCase());
            const firstChar = word.charAt(0).toLowerCase();
            const excludedCandidates = excludedWordsMap.get(firstChar);
            //console.log(`[WME PLN - NWI]   cleanedInputWord: "${cleanedInputWord}", firstChar: "${firstChar}"`); // LOG CLEANED
            //console.log(`[WME PLN - NWI]   excludedCandidates para '${firstChar}':`, excludedCandidates ? Array.from(excludedCandidates) : 'Ninguno'); // LOG CANDIDATOS
            // Verifica si hay candidatos excluidos para la primera letra de la palabra.
            if (excludedCandidates) {
                for (const excludedWord of excludedCandidates) {
                    // Limpia la palabra de la lista de la misma manera estricta.
                    const cleanedExcludedWord = removeDiacritics(excludedWord.toLowerCase());
                    if (cleanedExcludedWord === cleanedInputWord) {
                        return excludedWord; // Si es una palabra excluida, devuelve su forma exacta y termina.
                    }
                }
                //console.log(`[WME PLN - NWI]   🚫 No se encontró coincidencia exacta para excluida: "${word}"`); // LOG NO COINCIDENCIA

            }
        }
        // FIN PRIORIDAD 1

        // Prioridad 2: Manejo De Guiones Dentro De Palabras (solo si no fue excluida completa)
        // La condición RE_HYPHEN_BETWEEN_LETTERS.test(word) es crucial: asegura que el guion esté entre letras
        if (word.includes('-') && RE_HYPHEN_BETWEEN_LETTERS.test(word)) {
            //console.log(`[WME PLN - NWI] Aplicando Prioridad 2: Manejo de guiones para: "${word}"`);
            const parts = word.split('-');
            const normalizedParts = parts.map((part, partIndex) => {
                let normalizedPart = part;
                const isAcronymLikePart = RE_ACRONYM_PART.test(part);
                if (isAcronymLikePart && part.length > 1) {
                    normalizedPart = part; // Mantener como acrónimo si lo es.
                } else {
                    normalizedPart = part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
                }
                return normalizedPart;
            });
            // ✨ Cambio: Unir con " - " en lugar de "-" para normalizar el formato de guiones
            return normalizedParts.join(' - ');
        }
        // Prioridad 3: Palabras Con Apóstrofe
        if (word.includes("'")) {
            //console.log(`[WME PLN - NWI] Aplicando Prioridad 3 (Apóstrofe): "${word}"`);
            return handleApostropheWord(word);
        }
        // Prioridad 4: Acrónimos Y Palabras Con Mayúsculas/Puntos/& (después de guiones y apóstrofes)
        const isAcronymLike = RE_ACRONYM_WORD.test(word);
        if (isAcronymLike && word.length > 1) {
            //console.log(`[WME PLN - NWI] Aplicando Prioridad 4 (Acrónimo): "${word}"`);
            const shouldKeepAllCaps =
                word.length <= 3 || RE_DIGIT.test(word) || RE_SYMBOL.test(word);
            if (shouldKeepAllCaps) {
                return word; // Mantener como está si es sigla corta o contiene números/símbolos
            }
            // De lo contrario, continuar con las reglas normales de capitalización
        }
        // Prioridad 5: Números Romanos
        const romanWrappedMatch = word.match(RE_ROMAN_WRAPPED);
        if (romanWrappedMatch && RE_ROMAN.test(romanWrappedMatch[2])) {
            const [, romanPrefix = '', romanCore = '', romanSuffix = ''] = romanWrappedMatch;
            //console.log(`[WME PLN - NWI] Aplicando Prioridad 5 (Números Romanos): "${word}"`);
            return `${romanPrefix}${romanCore.toUpperCase()}${romanSuffix}`;
        }
        // Prioridad 6: Palabras Comunes
        const lowerWord = word.toLowerCase().replace('.', '');
        // `commonWords` es una constante global.
        if (commonWords.has(lowerWord)) // Si es una palabra común
        {
            //console.log(`[WME PLN - NWI] Aplicando Prioridad 6 (Palabra Común): "${word}"`);
            // Artículos y preposiciones que siempre queremos capitalizar si están en commonWords
            const alwaysCapitalizeCommonWords = ["el", "la", "los", "las", "de", "y", "e", "o", "u", "al", "en", "con", "por"];

            // Solo capitalizar "y" si es la primera palabra o después de guion o punto
            if (lowerWord === "y") {
                if (isFirstWordInSequence) {
                    return "Y";
                }
                else {
                    return "y";
                }
            }
            // Solo capitalizar "e" si es la primera palabra o después de guion o punto
            if (lowerWord === "e") {
                if (isFirstWordInSequence) {
                    return "E";
                }
                else {
                    return "e";
                }
            }

            if (alwaysCapitalizeCommonWords.includes(lowerWord)) {
                // Si es un artículo/preposición de la lista, SIEMPRE capitalizar su primera letra.
                // Esto forzará "el" -> "El", "de" -> "De", incluso si no es la primera palabra.
                return lowerWord.charAt(0).toUpperCase() + lowerWord.slice(1);
            } else if (isFirstWordInSequence && !isInsideQuotesOrParentheses) {
                // Para otras palabras comunes (ej. "un", "una"), solo capitalizar si es la primera palabra
                return lowerWord.charAt(0).toUpperCase() + lowerWord.slice(1);
            } else {
                // Si es una palabra común que NO es un artículo/preposición de la lista,
                // y NO es la primera palabra, la minúsculas (comportamiento actual).
                return lowerWord;
            }
        }
        // Prioridad 7: Capitalización Estándar (Regla Por Defecto)
        let wordWithoutPunctuation = word.endsWith('.') ? word.slice(0, -1) : word;

        let result = wordWithoutPunctuation.charAt(0).toUpperCase() + wordWithoutPunctuation.slice(1).toLowerCase();

        //console.log(`[WME PLN - NWI] Aplicando Prioridad 7 (Capitalización Estándar). Resultado: "${result}"`); // LOG Capitalización estándar

        return result;
    }//normalizeWordInternal


    window.normalizeWordInternal = normalizeWordInternal;

    // Maneja la capitalización de palabras que contienen un apóstrofe.
    function handleApostropheWord(word) {
        const parts = word.split("'");
        // Solo aplica si hay exactamente un apóstrofe.
        if (parts.length === 2) {
            const before = parts[0];
            const after = parts[1];

            if (after.toLowerCase() === 's') {
                // Caso posesivo como McDonald's, la 's' va en minúscula.
                return before + "'s";
            }
            else {
                // Caso como Q'Menú, se capitaliza la parte después del apóstrofe.
                const capitalizedAfter = after.charAt(0).toUpperCase() + after.slice(1).toLowerCase();
                return before + "'" + capitalizedAfter;
            }
        }
        // Si no es un caso manejable, devuelve la palabra original para que la procesen otras reglas.
        return word;
    }// handleApostropheWord

    // Función para crear un dropdown de categorías
    function createCategoryDropdown(currentCategoryKey, rowIndex, venue) {
        const select = document.createElement("select");
        select.style.padding = "4px";
        select.style.borderRadius = "4px";
        select.style.fontSize = "12px";
        select.title = "Selecciona una categoría";
        select.id = `categoryDropdown-${rowIndex}`;

        Object.entries(categoryIcons).forEach(([key, value]) => {
            const option = document.createElement("option");
            option.value = key;
            option.textContent = `${value.icon} ${value.en}`;
            if (key === currentCategoryKey)
                option.selected = true;
            select.appendChild(option);
        });
        // Evento: al cambiar la categoría
        select.addEventListener("change", (e) => {
            const selectedCategory = e.target.value;
            if (!venue || !venue.model || !venue.model.attributes) {
                //console.error("[WME_PLN] Venue inválido al intentar actualizar la categoría");
                return;
            }
            // Actualizar la categoría en el modelo
            venue.model.attributes.categories = [selectedCategory];
            venue.model.save();
            // Mensaje opcional de confirmación
            WazeWrap.Alerts.success("Categoría actualizada", `Nueva categoría: ${categoryIcons[selectedCategory].en}`);
        });
        return select;
    }

    // 3. La función postProcessQuotesAndParentheses (CORREGIDA de la respuesta anterior)
    function postProcessQuotesAndParentheses(text) {
        if (typeof text !== 'string') return text;

        // Función auxiliar para capitalizar la primera letra de una cadena
        function capitalizeFirstLetter(string) {
            if (!string) return string;
            return string.charAt(0).toUpperCase() + string.slice(1);
        }

        // Normalizar contenido dentro de comillas dobles
        text = text.replace(/"([^"]*)"/g, (match, content) => {
            const trimmedContent = content.trim();
            if (trimmedContent === "") return '""';

            // Capitaliza la primera letra de todo el contenido interno
            const capitalizedContent = capitalizeFirstLetter(trimmedContent);

            return `"${capitalizedContent}"`; // Sin espacios extra
        });

        // Normalizar contenido dentro de paréntesis
        text = text.replace(/\(([^)]*)\)/g, (match, content) => {
            const trimmedContent = content.trim();
            if (trimmedContent === "") return '()';

            // Capitaliza la primera letra de todo el contenido interno
            const capitalizedContent = capitalizeFirstLetter(trimmedContent);

            return `(${capitalizedContent})`; // Sin espacios extra
        });

        return text.replace(/\s+/g, ' ').trim(); // Limpieza final general
    }// postProcessQuotesAndParentheses// postProcessQuotesAndParentheses

    // === Palabras especiales ===
    let excludedWords = new Set(); // Mantenemos el Set para facilitar el renderizado original
    let excludedWordsMap = new Map(); // Para la búsqueda optimizada
    let excludedPlaces = new Map(); // Nuevo Map para IDs de lugares excluidos
    let dictionaryWords = new Set(); // O window.dictionaryWords = un Set global
    let dictionaryWordsCountLabelElement = null;
    let specialWordsCountLabelElement = null;
    let excludedPlacesCountLabelElement = null; // Nuevo elemento para el conteo de lugares excluidos
    let replacementsCountLabelElement = null; // Nuevo elemento para el conteo de reemplazos
    let swapWordsCountLabelElement = null; // Nuevo elemento para el conteo de swap words
    // === Palabras especiales ===
    // --- ADICIÓN PARA DEPURACIÓN EN CONSOLA ---
    window.excludedWords = excludedWords;
    window.excludedWordsMap = excludedWordsMap;
    window.excludedPlaces = excludedPlaces;

    // Función para crear el gestor de palabras excluidas y lugares excluidos
    function createSpecialItemsManager(parentContainer) {
        const mainSection = document.createElement("div"); // <--- Nueva sección principal para la pestaña "Espe"
        mainSection.id = "specialItemsManagerSection";
        mainSection.style.marginTop = "20px";
        mainSection.style.borderTop = "1px solid #ccc";
        mainSection.style.paddingTop = "10px";

        // --- Dropdown para seleccionar el tipo de gestión ---
        const typeSelectorWrapper = document.createElement("div");
        typeSelectorWrapper.style.marginBottom = "15px";
        typeSelectorWrapper.style.textAlign = "center";

        const typeSelectorLabel = document.createElement("label");
        typeSelectorLabel.textContent = "Gestionar:";
        typeSelectorLabel.style.marginRight = "10px";
        typeSelectorLabel.style.fontWeight = "bold";
        typeSelectorWrapper.appendChild(typeSelectorLabel);

        const typeSelector = document.createElement("select");
        typeSelector.id = "specialTypeSelector";
        typeSelector.style.padding = "5px";
        typeSelector.style.borderRadius = "4px";
        typeSelector.style.fontSize = "13px";

        const optionWords = document.createElement("option");
        optionWords.value = "words";
        optionWords.textContent = "Palabras Especiales";
        typeSelector.appendChild(optionWords);

        const optionPlaces = document.createElement("option");
        optionPlaces.value = "places";
        optionPlaces.textContent = "Lugares Excluidos";
        typeSelector.appendChild(optionPlaces);

        typeSelectorWrapper.appendChild(typeSelector);
        mainSection.appendChild(typeSelectorWrapper); // Añadir a mainSection

        // --- Contenedores para las dos vistas ---
        const wordsView = document.createElement("div");
        wordsView.id = "specialWordsView";
        wordsView.style.display = "block"; // Visible por defecto

        const placesView = document.createElement("div");
        placesView.id = "excludedPlacesView";
        placesView.style.display = "none"; // Oculto por defecto

        mainSection.appendChild(wordsView); // Añadir a mainSection
        mainSection.appendChild(placesView); // Añadir a mainSection

        // ***********************************************************************************
        // INICIO DEL CONTENIDO DE LA VISTA DE PALABRAS ESPECIALES (Antigua createExcludedWordsManager)
        // ***********************************************************************************

        // Título de la sección
        const wordsTitle = document.createElement("h4");
        wordsTitle.textContent = "Gestión de Palabras Especiales";
        wordsTitle.style.fontSize = "15px";
        wordsTitle.style.marginBottom = "10px";
        wordsView.appendChild(wordsTitle); // AÑADIDO A wordsView

        // Contenedor para los controles de añadir palabra
        const addWordsControlsContainer = document.createElement("div"); // Renombrado para claridad
        addWordsControlsContainer.style.display = "flex";
        addWordsControlsContainer.style.gap = "8px";
        addWordsControlsContainer.style.marginBottom = "8px";
        addWordsControlsContainer.style.alignItems = "center"; // Alinear verticalmente
        // Input para añadir nueva palabra o frase
        const wordsInput = document.createElement("input"); // Renombrado para claridad
        wordsInput.type = "text";
        wordsInput.placeholder = "Nueva palabra o frase";
        wordsInput.style.flexGrow = "1";
        wordsInput.style.padding = "6px";
        wordsInput.style.border = "1px solid #ccc";
        wordsInput.style.borderRadius = "3px";
        addWordsControlsContainer.appendChild(wordsInput); // AÑADIDO A addWordsControlsContainer

        // Botón para añadir la palabra
        const addWordBtn = document.createElement("button"); // Renombrado para claridad
        addWordBtn.textContent = "Añadir";
        addWordBtn.style.padding = "6px 10px";
        addWordBtn.style.cursor = "pointer";
        // Añadir tooltip al botón
        addWordBtn.addEventListener("click", function () {
            const newWord = wordsInput.value.trim(); // Usa wordsInput
            const validation = isValidExcludedWord(newWord);
            if (!validation.valid) {
                alert(validation.msg);
                return;
            }
            excludedWords.add(newWord);
            const firstCharNew = newWord.charAt(0).toLowerCase();
            if (!excludedWordsMap.has(firstCharNew)) {
                excludedWordsMap.set(firstCharNew, new Set());
            }
            excludedWordsMap.get(firstCharNew).add(newWord);
            wordsInput.value = ""; // Limpia wordsInput

            // ✨ Limpiar cache porque las palabras excluidas cambiaron
            clearWordLookupCache();

            renderExcludedWordsList(document.getElementById("excludedWordsList"));
            saveExcludedWordsToLocalStorage();
            updateSpecialWordsCountLabel();
        });
        addWordsControlsContainer.appendChild(addWordBtn); // AÑADIDO A addWordsControlsContainer
        wordsView.appendChild(addWordsControlsContainer); // AÑADIDO A wordsView

        // Contenedor para los botones de acción (Exportar/Limpiar para Palabras)
        const wordsActionButtonsContainer = document.createElement("div"); // Renombrado
        wordsActionButtonsContainer.style.display = "flex";
        wordsActionButtonsContainer.style.gap = "8px";
        wordsActionButtonsContainer.style.alignItems = "center"; // Alineación vertical centrada
        wordsActionButtonsContainer.style.flexWrap = "wrap"; // Permitir que los elementos bajen si no caben
        wordsActionButtonsContainer.style.marginBottom = "10px";

        const exportWordsBtn = document.createElement("button"); // Renombrado
        exportWordsBtn.textContent = "Exportar";
        exportWordsBtn.title = "Exportar Lista a XML";
        exportWordsBtn.style.padding = "6px 10px";
        exportWordsBtn.style.cursor = "pointer";
        exportWordsBtn.addEventListener("click", () => exportSharedDataToXml("words")); // Pasa el tipo
        wordsActionButtonsContainer.appendChild(exportWordsBtn); // AÑADIDO A wordsActionButtonsContainer

        const clearWordsBtn = document.createElement("button"); // Renombrado
        clearWordsBtn.textContent = "Limpiar";
        clearWordsBtn.title = "Limpiar toda la lista";
        clearWordsBtn.style.padding = "6px 10px";
        clearWordsBtn.style.cursor = "pointer";
        clearWordsBtn.addEventListener("click", function () {
            if (confirm("¿Estás seguro de que deseas eliminar TODAS las palabras de la lista?")) {
                excludedWords.clear();
                excludedWordsMap.clear();
                renderExcludedWordsList(document.getElementById("excludedWordsList"));
                saveExcludedWordsToLocalStorage();
                updateSpecialWordsCountLabel();
            }
        });
        wordsActionButtonsContainer.appendChild(clearWordsBtn); // AÑADIDO A wordsActionButtonsContainer

        // Label para el conteo de palabras especiales
        const specialCountLabel = document.createElement("span");
        specialCountLabel.id = "specialWordsCountLabel";
        specialCountLabel.style.marginLeft = "5px"; // Margen fijo en lugar de auto para evitar empujarlo fuera
        specialCountLabel.style.fontSize = "12px";
        specialCountLabel.style.color = "#333";
        specialCountLabel.style.whiteSpace = "nowrap";
        specialWordsCountLabelElement = specialCountLabel;
        updateSpecialWordsCountLabel();
        wordsActionButtonsContainer.appendChild(specialCountLabel);

        wordsView.appendChild(wordsActionButtonsContainer); // AÑADIDO A wordsView

        // Contenedor para la lista de palabras excluidas (buscador y UL)
        const wordsSearchInput = document.createElement("input"); // Renombrado
        wordsSearchInput.type = "text";
        wordsSearchInput.placeholder = "Buscar en especiales...";
        wordsSearchInput.style.display = "block";
        wordsSearchInput.style.width = "calc(100% - 14px)";
        wordsSearchInput.style.padding = "6px";
        wordsSearchInput.style.border = "1px solid #ccc";
        wordsSearchInput.style.borderRadius = "3px";
        wordsSearchInput.style.marginBottom = "5px";
        wordsSearchInput.addEventListener("input", () => {
            renderExcludedWordsList(document.getElementById("excludedWordsList"), wordsSearchInput.value.trim()); // Usa wordsSearchInput
        });
        wordsView.appendChild(wordsSearchInput); // AÑADIDO A wordsView

        // UL para palabras excluidas
        const wordsListUL = document.createElement("ul"); // Renombrado
        wordsListUL.id = "excludedWordsList"; // Mantiene el ID original para compatibilidad con renderExcludedWordsList
        wordsListUL.style.maxHeight = "150px";
        wordsListUL.style.overflowY = "auto";
        wordsListUL.style.border = "1px solid #ddd";
        wordsListUL.style.padding = "5px";
        wordsListUL.style.margin = "0";
        wordsListUL.style.background = "#fff";
        wordsListUL.style.listStyle = "none";
        wordsView.appendChild(wordsListUL); // AÑADIDO A wordsView

        // Drop Area para XML de palabras
        const wordsDropArea = document.createElement("div"); // Renombrado
        wordsDropArea.textContent = "Arrastra aquí el archivo XML de palabras especiales";
        wordsDropArea.style.border = "2px dashed #ccc";
        wordsDropArea.style.borderRadius = "4px";
        wordsDropArea.style.padding = "15px";
        wordsDropArea.style.marginTop = "10px";
        wordsDropArea.style.textAlign = "center";
        wordsDropArea.style.background = "#f9f9f9";
        wordsDropArea.style.color = "#555";
        wordsDropArea.addEventListener("dragover", (e) => {
            e.preventDefault();
            wordsDropArea.style.background = "#e9e9e9";
            wordsDropArea.style.borderColor = "#aaa";
        });
        wordsDropArea.addEventListener("dragleave", () => {
            wordsDropArea.style.background = "#f9f9f9";
            wordsDropArea.style.borderColor = "#ccc";
        });
        wordsDropArea.addEventListener("drop", (e) => {
            e.preventDefault();
            wordsDropArea.style.background = "#f9f9f9";
            handleXmlFileDrop(e.dataTransfer.files[0], "words"); // Pasar el tipo de importación
        });
        wordsView.appendChild(wordsDropArea); // AÑADIDO A wordsView

        // ***********************************************************************************
        // FIN DEL CONTENIDO DE LA VISTA DE PALABRAS ESPECIALES
        // ***********************************************************************************

        // ***********************************************************************************
        // INICIO DEL CONTENIDO DE LA VISTA DE LUGARES EXCLUIDOS (Nueva lógica)
        // ***********************************************************************************

        // Título de la sección
        const placesTitle = document.createElement("h4");
        placesTitle.textContent = "Gestión de Lugares Excluidos";
        placesTitle.style.fontSize = "15px";
        placesTitle.style.marginBottom = "10px";
        placesView.appendChild(placesTitle);

        // Contador de lugares excluidos (debajo del título)
        const placesCountLabel = document.createElement("div");
        placesCountLabel.id = "excludedPlacesCountLabel";
        placesCountLabel.style.marginBottom = "10px";
        placesCountLabel.style.marginTop = "5px";
        placesCountLabel.style.fontSize = "13px";
        placesCountLabel.style.color = "#666";
        placesCountLabel.style.fontWeight = "500";
        excludedPlacesCountLabelElement = placesCountLabel;
        updateExcludedPlacesCountLabel();
        placesView.appendChild(placesCountLabel);

        // Controles de búsqueda y lista de lugares
        const placesSearchInput = document.createElement("input");
        placesSearchInput.type = "text";
        placesSearchInput.placeholder = "Buscar lugar excluido...";
        placesSearchInput.style.display = "block";
        placesSearchInput.style.width = "calc(100% - 14px)";
        placesSearchInput.style.padding = "6px";
        placesSearchInput.style.border = "1px solid #ccc";
        placesSearchInput.style.borderRadius = "3px";
        placesSearchInput.style.marginBottom = "5px";
        placesSearchInput.addEventListener("input", () => {
            renderExcludedPlacesList(document.getElementById("excludedPlacesListUL"), placesSearchInput.value.trim());
        });
        placesView.appendChild(placesSearchInput);

        const placesListUL = document.createElement("ul");
        placesListUL.id = "excludedPlacesListUL"; // Nuevo ID para la lista de Places
        placesListUL.style.maxHeight = "200px"; // Un poco más grande
        placesListUL.style.overflowY = "auto";
        placesListUL.style.border = "1px solid #ddd";
        placesListUL.style.padding = "5px";
        placesListUL.style.margin = "0";
        placesListUL.style.background = "#fff";
        placesListUL.style.listStyle = "none";
        placesView.appendChild(placesListUL);

        // Botones de acción para Lugares Excluidos
        const placesActionButtonsContainer = document.createElement("div");
        placesActionButtonsContainer.style.display = "flex";
        placesActionButtonsContainer.style.gap = "8px";
        placesActionButtonsContainer.style.alignItems = "center"; // Alineación vertical
        placesActionButtonsContainer.style.flexWrap = "wrap"; // Permitir wrap
        placesActionButtonsContainer.style.marginTop = "10px";

        const exportPlacesBtn = document.createElement("button");
        exportPlacesBtn.textContent = "Exportar";
        exportPlacesBtn.title = "Exportar Lugares Excluidos a XML";
        exportPlacesBtn.style.padding = "6px 10px";
        exportPlacesBtn.style.cursor = "pointer";
        exportPlacesBtn.addEventListener("click", () => exportSharedDataToXml("places")); // Pasa el tipo
        placesActionButtonsContainer.appendChild(exportPlacesBtn);

        const clearPlacesBtn = document.createElement("button");
        clearPlacesBtn.textContent = "Limpiar";
        clearPlacesBtn.title = "Limpiar lista de lugares excluidos";
        clearPlacesBtn.style.padding = "6px 10px";
        clearPlacesBtn.style.cursor = "pointer";
        clearPlacesBtn.addEventListener("click", () => {
            if (confirm("¿Estás seguro de que deseas eliminar TODOS los lugares de la lista?")) {
                excludedPlaces.clear();
                renderExcludedPlacesList(document.getElementById("excludedPlacesListUL"));
                saveExcludedPlacesToLocalStorage();
                updateExcludedPlacesCountLabel();
            }
        });
        placesActionButtonsContainer.appendChild(clearPlacesBtn);

        placesView.appendChild(placesActionButtonsContainer);

        // Drop Area para XML de Lugares Excluidos
        const placesDropArea = document.createElement("div");
        placesDropArea.textContent = "Arrastra aquí el archivo XML de lugares excluidos";
        placesDropArea.style.border = "2px dashed #ccc";
        placesDropArea.style.borderRadius = "4px";
        placesDropArea.style.padding = "15px";
        placesDropArea.style.marginTop = "10px";
        placesDropArea.style.textAlign = "center";
        placesDropArea.style.background = "#f9f9f9";
        placesDropArea.style.color = "#555";
        placesDropArea.addEventListener("dragover", (e) => {
            e.preventDefault();
            placesDropArea.style.background = "#e9e9e9";
            placesDropArea.style.borderColor = "#aaa";
        });
        placesDropArea.addEventListener("dragleave", () => {
            placesDropArea.style.background = "#f9f9f9";
            placesDropArea.style.borderColor = "#ccc";
        });
        placesDropArea.addEventListener("drop", (e) => {
            e.preventDefault();
            placesDropArea.style.background = "#f9f9f9";
            handleXmlFileDrop(e.dataTransfer.files[0], "places"); // Pasa el tipo de importación
        });
        placesView.appendChild(placesDropArea);

        // ***********************************************************************************
        // FIN DEL CONTENIDO DE LA VISTA DE LUGARES EXCLUIDOS
        // ***********************************************************************************

        // --- Lógica de alternancia del selector ---
        typeSelector.addEventListener("change", () => {
            if (typeSelector.value === "words") {
                wordsView.style.display = "block";
                placesView.style.display = "none";
                renderExcludedWordsList(document.getElementById("excludedWordsList"), wordsSearchInput.value.trim()); // Renderiza lista de palabras
            } else {
                wordsView.style.display = "none";
                placesView.style.display = "block";
                renderExcludedPlacesList(document.getElementById("excludedPlacesListUL"), placesSearchInput.value.trim()); // Renderiza lista de lugares
            }
        });

        // --- Renderizado inicial de las listas al cargar ---
        renderExcludedWordsList(wordsListUL, ""); // Usa la referencia directa a wordsListUL
        renderExcludedPlacesList(placesListUL, ""); // Usa la referencia directa a placesListUL

        parentContainer.appendChild(mainSection); // <--- AÑADE SOLO ESTA SECCIÓN PRINCIPAL AL PARENT CONTAINER
    }

    // Actualiza la etiqueta que muestra el conteo de palabras especiales
    function updateSpecialWordsCountLabel() {
        if (!specialWordsCountLabelElement) {
            specialWordsCountLabelElement = document.getElementById("specialWordsCountLabel");
        }
        if (!specialWordsCountLabelElement) {
            return;
        }

        const count = window.excludedWords ? window.excludedWords.size : 0;
        specialWordsCountLabelElement.innerHTML = `<b style="color: #333;">Palabras Cargadas:</b> <b style="color: #0066cc;">${count}</b>`;
    }

    // Actualiza la etiqueta que muestra el conteo de reemplazos
    function updateReplacementsCountLabel() {
        if (!replacementsCountLabelElement) {
            replacementsCountLabelElement = document.getElementById("replacementsCountLabel");
        }

        if (!replacementsCountLabelElement) {
            return;
        }

        const count = window.replacementWords ? Object.keys(window.replacementWords).length : 0;
        replacementsCountLabelElement.innerHTML = `<b style="color: #333;">Reemplazos Cargados:</b> <b style="color: #0066cc;">${count}</b>`;
    }
    window.updateReplacementsCountLabel = updateReplacementsCountLabel;

    // Actualiza la etiqueta que muestra el conteo de lugares excluidos
    function updateExcludedPlacesCountLabel() {
        if (!excludedPlacesCountLabelElement) {
            excludedPlacesCountLabelElement = document.getElementById("excludedPlacesCountLabel");
        }

        if (!excludedPlacesCountLabelElement) {
            return;
        }

        const count = window.excludedPlaces ? window.excludedPlaces.size : 0;
        excludedPlacesCountLabelElement.innerHTML = `<b style="color: #333;">Lugares Cargados:</b> <b style="color: #0066cc;">${count}</b>`;
    }
    window.updateExcludedPlacesCountLabel = updateExcludedPlacesCountLabel;

    // Actualiza la etiqueta que muestra el conteo de swap words
    function updateSwapWordsCountLabel() {
        if (!swapWordsCountLabelElement) {
            swapWordsCountLabelElement = document.getElementById("swapWordsCountLabel");
        }

        if (!swapWordsCountLabelElement) {
            return;
        }

        const count = window.swapWords ? window.swapWords.length : 0;
        swapWordsCountLabelElement.innerHTML = `<b style="color: #333;">Palabras de Intercambio (Swap):</b> <b style="color: #0066cc;">${count}</b>`;
    }
    window.updateSwapWordsCountLabel = updateSwapWordsCountLabel;


    // Función para validar una palabra o frase antes de añadirla a las palabras excluidas
    function prepararDatosDiccionario() {
        // Convertir el Set de palabras a un array de objetos con el formato requerido
        const datos = Array.from(window.dictionaryWords || []).map(palabra => {
            return {
                termino: palabra,
                definicion: "" // Si no tiene definición, dejar vacío
            };
        });
        // Ordenar alfabéticamente para mejor navegación
        return datos.sort((a, b) => a.termino.localeCompare(b.termino));
    }//prepararDatosDiccionario
    // Actualiza la etiqueta que muestra el conteo de palabras en el diccionario
    function updateDictionaryWordsCountLabel() {
        if (!dictionaryWordsCountLabelElement) {
            dictionaryWordsCountLabelElement = document.getElementById("dictionaryWordsCountLabel");
        }

        if (!dictionaryWordsCountLabelElement) {
            return;
        }

        const count = window.dictionaryWords ? window.dictionaryWords.size : 0;
        dictionaryWordsCountLabelElement.innerHTML = `<b style="color: #333;">Palabras Cargadas:</b> <b style="color: #0066cc;">${count}</b>`;
    }//updateDictionaryWordsCountLabel

    // Exponer globalmente para addWordToDictionary
    window.updateDictionaryWordsCountLabel = updateDictionaryWordsCountLabel;

    // === Diccionario ===
    function createDictionaryManager(parentContainer) {
        const section = document.createElement("div");
        section.id = "dictionaryManagerSection";
        section.style.marginTop = "20px";
        section.style.borderTop = "1px solid #ccc";
        section.style.paddingTop = "10px";

        // Título de la sección
        const title = document.createElement("h4");
        title.textContent = "Gestión del Diccionario";
        title.style.fontSize = "15px";
        title.style.marginBottom = "10px";
        section.appendChild(title);

        // Contenedor para los controles de añadir palabra
        const addControlsContainer = document.createElement("div");
        addControlsContainer.style.display = "flex";
        addControlsContainer.style.gap = "8px";
        addControlsContainer.style.marginBottom = "8px";
        addControlsContainer.style.alignItems = "center";

        // Input para añadir nueva palabra
        const input = document.createElement("input");
        input.type = "text";
        input.placeholder = "Nueva palabra";
        input.style.flexGrow = "1";
        input.style.padding = "6px";
        input.style.border = "1px solid #ccc";
        input.style.borderRadius = "3px";
        addControlsContainer.appendChild(input);

        // Botón para añadir la palabra
        const addBtn = document.createElement("button");
        addBtn.textContent = "Añadir";
        addBtn.style.padding = "6px 10px";
        addBtn.style.cursor = "pointer";
        addBtn.addEventListener("click", function () {
            // Usar la función centralizada que maneja correctamente userAddedDictionaryWords y persistencia
            addWordToDictionary(input);
        });
        addControlsContainer.appendChild(addBtn);
        section.appendChild(addControlsContainer);

        // Contenedor para los botones de acción
        const actionButtonsContainer = document.createElement("div");
        actionButtonsContainer.style.display = "flex";
        actionButtonsContainer.style.gap = "8px";
        actionButtonsContainer.style.alignItems = "center";
        actionButtonsContainer.style.marginBottom = "10px";

        /*
                // Botón para limpiar
                const clearBtn = document.createElement("button");
                clearBtn.textContent = "Limpiar";
                clearBtn.title = "Limpiar toda la lista";
                clearBtn.style.padding = "6px 10px";
                clearBtn.style.cursor = "pointer";
                clearBtn.addEventListener("click", function() {
                    if (confirm("¿Estás seguro de que deseas eliminar TODAS las palabras del diccionario?")) {
                        window.dictionaryWords.clear();

                        // Reinicializar el diccionario dinámico con los datos actualizados
                        const listContainer = document.getElementById("dictionaryContainer");
                        if (listContainer) {
                            listContainer.innerHTML = "";
                            inicializarDiccionarioDinamico("dictionaryContainer", prepararDatosDiccionario());
                        }

                        updateDictionaryWordsCountLabel();
                    }
                });
                actionButtonsContainer.appendChild(clearBtn);*/

        const dictionaryCountLabel = document.createElement("span");
        dictionaryCountLabel.id = "dictionaryWordsCountLabel";
        dictionaryCountLabel.style.marginLeft = "auto";
        dictionaryCountLabel.style.fontSize = "12px";
        dictionaryCountLabel.style.color = "#333";
        dictionaryCountLabel.style.whiteSpace = "nowrap";
        dictionaryWordsCountLabelElement = dictionaryCountLabel;
        updateDictionaryWordsCountLabel();
        actionButtonsContainer.appendChild(dictionaryCountLabel);

        section.appendChild(actionButtonsContainer);

        // Campo de búsqueda
        const search = document.createElement("input");
        search.type = "text";
        search.placeholder = "Buscar en diccionario...";
        search.style.display = "block";
        search.style.width = "calc(100% - 14px)";
        search.style.padding = "6px";
        search.style.border = "1px solid #ccc";
        search.style.borderRadius = "3px";
        search.style.marginTop = "5px";
        search.style.marginBottom = "10px";

        // Implementar búsqueda en tiempo real
        search.addEventListener("input", () => {
            const searchTerm = search.value.trim().toLowerCase();
            const filteredData = prepararDatosDiccionario().filter(item =>
                item.termino.toLowerCase().includes(searchTerm)
            );

            // Actualizar la visualización con datos filtrados
            const listContainer = document.getElementById("dictionaryContainer");
            if (listContainer) {
                listContainer.innerHTML = "";
                inicializarDiccionarioDinamico("dictionaryContainer", filteredData);
            }
        });
        section.appendChild(search);

        // Contenedor para el diccionario con carga bajo demanda
        const dictionaryContainer = document.createElement("div");
        dictionaryContainer.id = "dictionaryContainer";
        dictionaryContainer.style.maxHeight = "250px";
        dictionaryContainer.style.overflowY = "auto";
        dictionaryContainer.style.border = "1px solid #ddd";
        dictionaryContainer.style.padding = "5px";
        dictionaryContainer.style.margin = "0";
        dictionaryContainer.style.background = "#fff";
        section.appendChild(dictionaryContainer);

        // Función helper para crear modales de confirmación personalizadas
        function showCustomConfirm(message, title = "Confirmación", icon = "⚠️") {
            return new Promise((resolve) => {
                // Crear overlay
                const overlay = document.createElement("div");
                overlay.style.position = "fixed";
                overlay.style.top = "0";
                overlay.style.left = "0";
                overlay.style.width = "100%";
                overlay.style.height = "100%";
                overlay.style.backgroundColor = "rgba(0,0,0,0.5)";
                overlay.style.zIndex = "19999";

                // Crear modal
                const modal = document.createElement("div");
                modal.style.position = "fixed";
                modal.style.top = "50%";
                modal.style.left = "50%";
                modal.style.transform = "translate(-50%, -50%)";
                modal.style.background = "#fff";
                modal.style.border = "1px solid #aad";
                modal.style.padding = "28px 32px 20px 32px";
                modal.style.zIndex = "20000";
                modal.style.boxShadow = "0 4px 24px rgba(0,0,0,0.18)";
                modal.style.fontFamily = "sans-serif";
                modal.style.borderRadius = "10px";
                modal.style.textAlign = "center";
                modal.style.minWidth = "400px";
                modal.style.maxWidth = "500px";

                // Ícono
                const iconElement = document.createElement("div");
                iconElement.textContent = icon;
                iconElement.style.fontSize = "38px";
                iconElement.style.marginBottom = "10px";
                modal.appendChild(iconElement);

                // Título
                const titleElement = document.createElement("div");
                titleElement.innerHTML = `<b>${title}</b>`;
                titleElement.style.fontSize = "20px";
                titleElement.style.marginBottom = "15px";
                modal.appendChild(titleElement);

                // Mensaje
                const messageElement = document.createElement("div");
                messageElement.textContent = message;
                messageElement.style.fontSize = "15px";
                messageElement.style.color = "#555";
                messageElement.style.marginBottom = "20px";
                messageElement.style.lineHeight = "1.5";
                messageElement.style.whiteSpace = "pre-line";
                modal.appendChild(messageElement);

                // Botones
                const buttonWrapper = document.createElement("div");
                buttonWrapper.style.display = "flex";
                buttonWrapper.style.justifyContent = "center";
                buttonWrapper.style.gap = "12px";

                const cancelBtn = document.createElement("button");
                cancelBtn.textContent = "Cancelar";
                cancelBtn.style.padding = "7px 18px";
                cancelBtn.style.background = "#6c757d";
                cancelBtn.style.color = "#fff";
                cancelBtn.style.border = "none";
                cancelBtn.style.borderRadius = "4px";
                cancelBtn.style.cursor = "pointer";
                cancelBtn.style.fontWeight = "bold";
                cancelBtn.addEventListener("click", () => {
                    overlay.remove();
                    modal.remove();
                    resolve(false);
                });

                const confirmBtn = document.createElement("button");
                confirmBtn.textContent = "Confirmar";
                confirmBtn.style.padding = "7px 18px";
                confirmBtn.style.background = "#007bff";
                confirmBtn.style.color = "#fff";
                confirmBtn.style.border = "none";
                confirmBtn.style.borderRadius = "4px";
                confirmBtn.style.cursor = "pointer";
                confirmBtn.style.fontWeight = "bold";
                confirmBtn.addEventListener("click", () => {
                    overlay.remove();
                    modal.remove();
                    resolve(true);
                });

                buttonWrapper.appendChild(cancelBtn);
                buttonWrapper.appendChild(confirmBtn);
                modal.appendChild(buttonWrapper);

                document.body.appendChild(overlay);
                document.body.appendChild(modal);
            });
        }

        // Función helper para mostrar alertas personalizadas
        function showCustomAlert(message, title = "Información", icon = "ℹ️", type = "info") {
            return new Promise((resolve) => {
                // Crear overlay
                const overlay = document.createElement("div");
                overlay.style.position = "fixed";
                overlay.style.top = "0";
                overlay.style.left = "0";
                overlay.style.width = "100%";
                overlay.style.height = "100%";
                overlay.style.backgroundColor = "rgba(0,0,0,0.5)";
                overlay.style.zIndex = "19999";

                // Crear modal
                const modal = document.createElement("div");
                modal.style.position = "fixed";
                modal.style.top = "50%";
                modal.style.left = "50%";
                modal.style.transform = "translate(-50%, -50%)";
                modal.style.background = "#fff";
                modal.style.border = "1px solid #aad";
                modal.style.padding = "28px 32px 20px 32px";
                modal.style.zIndex = "20000";
                modal.style.boxShadow = "0 4px 24px rgba(0,0,0,0.18)";
                modal.style.fontFamily = "sans-serif";
                modal.style.borderRadius = "10px";
                modal.style.textAlign = "center";
                modal.style.minWidth = "400px";
                modal.style.maxWidth = "500px";

                // Ícono
                const iconElement = document.createElement("div");
                iconElement.textContent = icon;
                iconElement.style.fontSize = "38px";
                iconElement.style.marginBottom = "10px";
                modal.appendChild(iconElement);

                // Título
                const titleElement = document.createElement("div");
                titleElement.innerHTML = `<b>${title}</b>`;
                titleElement.style.fontSize = "20px";
                titleElement.style.marginBottom = "15px";
                modal.appendChild(titleElement);

                // Mensaje
                const messageElement = document.createElement("div");
                messageElement.textContent = message;
                messageElement.style.fontSize = "15px";
                messageElement.style.color = "#555";
                messageElement.style.marginBottom = "20px";
                messageElement.style.lineHeight = "1.5";
                messageElement.style.whiteSpace = "pre-line";
                modal.appendChild(messageElement);

                // Botón OK
                const okBtn = document.createElement("button");
                okBtn.textContent = "OK";
                okBtn.style.padding = "7px 18px";
                okBtn.style.background = type === "error" ? "#dc3545" : (type === "success" ? "#28a745" : "#007bff");
                okBtn.style.color = "#fff";
                okBtn.style.border = "none";
                okBtn.style.borderRadius = "4px";
                okBtn.style.cursor = "pointer";
                okBtn.style.fontWeight = "bold";
                okBtn.addEventListener("click", () => {
                    overlay.remove();
                    modal.remove();
                    resolve(true);
                });

                modal.appendChild(okBtn);

                document.body.appendChild(overlay);
                document.body.appendChild(modal);
            });
        }

        // ✨ Función helper para mostrar prompts personalizados con input (28-nov-2024)
        function showCustomPrompt(message, title = "Entrada de Datos", defaultValue = "", icon = "✏️") {
            return new Promise((resolve) => {
                const overlay = document.createElement("div");
                overlay.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:19999";

                const modal = document.createElement("div");
                modal.style.cssText = "position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;border:1px solid #aad;padding:28px 32px 20px 32px;z-index:20000;box-shadow:0 4px 24px rgba(0,0,0,0.18);font-family:sans-serif;border-radius:10px;text-align:center;min-width:400px;max-width:500px";

                const iconElement = document.createElement("div");
                iconElement.textContent = icon;
                iconElement.style.cssText = "font-size:38px;margin-bottom:10px";
                modal.appendChild(iconElement);

                const titleElement = document.createElement("div");
                titleElement.innerHTML = `<b>${title}</b>`;
                titleElement.style.cssText = "font-size:20px;margin-bottom:15px";
                modal.appendChild(titleElement);

                const messageElement = document.createElement("div");
                messageElement.textContent = message;
                messageElement.style.cssText = "font-size:15px;color:#555;margin-bottom:15px;line-height:1.5;white-space:pre-line";
                modal.appendChild(messageElement);

                const inputElement = document.createElement("input");
                inputElement.type = "text";
                inputElement.value = defaultValue;
                inputElement.style.cssText = "width:100%;padding:8px 12px;font-size:14px;border:1px solid #ccc;border-radius:4px;margin-bottom:20px;box-sizing:border-box";
                modal.appendChild(inputElement);

                const buttonWrapper = document.createElement("div");
                buttonWrapper.style.cssText = "display:flex;justify-content:center;gap:12px";

                const cancelBtn = document.createElement("button");
                cancelBtn.textContent = "Cancelar";
                cancelBtn.style.cssText = "padding:7px 18px;background:#6c757d;color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:bold";
                cancelBtn.addEventListener("click", () => {
                    overlay.remove();
                    modal.remove();
                    resolve(null);
                });

                const confirmBtn = document.createElement("button");
                confirmBtn.textContent = "Aceptar";
                confirmBtn.style.cssText = "padding:7px 18px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:bold";
                confirmBtn.addEventListener("click", () => {
                    overlay.remove();
                    modal.remove();
                    resolve(inputElement.value);
                });

                buttonWrapper.appendChild(cancelBtn);
                buttonWrapper.appendChild(confirmBtn);
                modal.appendChild(buttonWrapper);

                document.body.appendChild(overlay);
                document.body.appendChild(modal);

                setTimeout(() => {
                    inputElement.focus();
                    inputElement.select();
                }, 100);

                inputElement.addEventListener("keydown", (e) => {
                    if (e.key === "Enter") confirmBtn.click();
                    else if (e.key === "Escape") cancelBtn.click();
                });
            });
        }

        // Botón para recargar Diccionario Base (antes del área de arrastrar)
        const reloadFromSheetBtn = document.createElement("button");
        reloadFromSheetBtn.textContent = "Recargar Diccionario Base";
        reloadFromSheetBtn.title = "Limpiar diccionario local y recargar el Básico";
        reloadFromSheetBtn.style.padding = "8px 12px";
        reloadFromSheetBtn.style.cursor = "pointer";
        reloadFromSheetBtn.style.backgroundColor = "#ff9800";
        reloadFromSheetBtn.style.color = "white";
        reloadFromSheetBtn.style.border = "none";
        reloadFromSheetBtn.style.borderRadius = "4px";
        reloadFromSheetBtn.style.marginTop = "10px";
        reloadFromSheetBtn.style.width = "100%";
        reloadFromSheetBtn.style.fontSize = "13px";
        reloadFromSheetBtn.style.fontWeight = "bold";
        reloadFromSheetBtn.addEventListener("click", async function () {
            // Primera confirmación
            const firstConfirm = await showCustomConfirm(
                "Esta acción eliminará TODAS las palabras del diccionario local (incluyendo las que hayas agregado manualmente) y solo cargará las del Diccionario Base.\n\n¿Deseas continuar?",
                "⚠️ ADVERTENCIA",
                "⚠️"
            );

            if (!firstConfirm) return;

            // Segunda confirmación
            const secondConfirm = await showCustomConfirm(
                "¿Estás COMPLETAMENTE SEGURO de que deseas eliminar el diccionario local y recargar el Diccionario por Defecto?\n\nEsta acción NO se puede deshacer.",
                "⚠️ ÚLTIMA CONFIRMACIÓN",
                "⚠️"
            );

            if (!secondConfirm) return;

            try {
                // Deshabilitar el botón durante la operación
                reloadFromSheetBtn.disabled = true;
                reloadFromSheetBtn.textContent = "Recargando...";

                // 1. Limpiar el diccionario en memoria
                if (window.dictionaryWords) {
                    window.dictionaryWords.clear();
                }
                if (window.dictionaryIndex) {
                    window.dictionaryIndex = {};
                }

                // 2. Limpiar el localStorage
                localStorage.removeItem("dictionaryWordsList");
                localStorage.removeItem("wme_pln_dictionary_cache");
                localStorage.removeItem("wme_pln_dictionary_cache");
                // localStorage.removeItem("userAddedDictionaryWords"); // NO BORRAR palabras de usuario al recargar base

                // 3. Reinicializar las estructuras
                window.dictionaryWords = new Set();
                window.dictionaryIndex = {};
                window.dictionaryIndex = {};
                // window.userAddedDictionaryWords = new Set(); // NO REINICIALIZAR palabras de usuario

                // 4. Recargar Diccionario Base (forzando la recarga)
                await loadDictionaryWordsFromSheet(true);

                // ✨ Limpiar cache de lookups después de recargar diccionario
                clearWordLookupCache();

                // 5. Actualizar la UI
                const listContainer = document.getElementById("dictionaryContainer");
                if (listContainer) {
                    listContainer.innerHTML = "";
                    inicializarDiccionarioDinamico("dictionaryContainer", prepararDatosDiccionario());
                }

                updateDictionaryWordsCountLabel();

                // Mensaje de éxito
                await showCustomAlert(
                    `Diccionario por defecto Recargado con Éxito.\n\nTotal de palabras: ${window.dictionaryWords.size}`,
                    "✅ Éxito",
                    "✅",
                    "success"
                );

            } catch (error) {
                console.error("[WME PLN] Error recargando diccionario Base:", error);
                await showCustomAlert(
                    "Error al recargar el diccionario Base.\n\nRevisa la consola para más detalles.",
                    "❌ Error",
                    "❌",
                    "error"
                );
            } finally {
                // Rehabilitar el botón
                reloadFromSheetBtn.disabled = false;
                reloadFromSheetBtn.textContent = "Recargar Diccionario Base";
            }
        });
        section.appendChild(reloadFromSheetBtn);

        // ✨ Botón para exportar palabras propias para diccionario (28-nov-2024)
        const exportUserWordsBtn = document.createElement("button");
        exportUserWordsBtn.textContent = "📤 Exportar Mis Palabras";
        exportUserWordsBtn.title = "Exporta las palabras que has añadido manualmente al diccionario local en formato PALABRA;ES";
        exportUserWordsBtn.style.padding = "8px 12px";
        exportUserWordsBtn.style.cursor = "pointer";
        exportUserWordsBtn.style.backgroundColor = "#4CAF50";
        exportUserWordsBtn.style.color = "white";
        exportUserWordsBtn.style.border = "none";
        exportUserWordsBtn.style.borderRadius = "4px";
        exportUserWordsBtn.style.marginTop = "8px";
        exportUserWordsBtn.style.width = "100%";
        exportUserWordsBtn.style.fontSize = "12px";
        exportUserWordsBtn.style.fontWeight = "bold";

        exportUserWordsBtn.addEventListener("click", function () {
            if (!window.userAddedDictionaryWords || window.userAddedDictionaryWords.size === 0) {
                plnToast("⚠️ No hay palabras propias para exportar.", 3000);
                return;
            }

            // Generar el contenido en formato PALABRA;ES
            const lines = Array.from(window.userAddedDictionaryWords)
                .sort()
                .map(word => `${word};ES`);

            const content = lines.join('\n');

            // Crear el archivo para descargar
            const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `diccionario_palabras_propias_${new Date().toISOString().slice(0, 10)}.txt`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);

            // Mensaje de confirmación
            const count = window.userAddedDictionaryWords.size;
            plnToast(`✅ Exportadas ${count} palabras propias.`, 3000);
        });

        section.appendChild(exportUserWordsBtn);

        // Área para soltar archivos XML
        const dropArea = document.createElement("div");
        dropArea.textContent = "Arrastra aquí el archivo XML del diccionario";
        dropArea.style.border = "2px dashed #ccc";
        dropArea.style.borderRadius = "4px";
        dropArea.style.padding = "15px";
        dropArea.style.marginTop = "10px";
        dropArea.style.textAlign = "center";
        dropArea.style.background = "#f9f9f9";
        dropArea.style.color = "#555";

        // Eventos de arrastrar y soltar
        dropArea.addEventListener("dragover", (e) => {
            e.preventDefault();
            dropArea.style.background = "#e9e9e9";
            dropArea.style.borderColor = "#aaa";
        });

        dropArea.addEventListener("dragleave", () => {
            dropArea.style.background = "#f9f9f9";
            dropArea.style.borderColor = "#ccc";
        });

        dropArea.addEventListener("drop", (e) => {
            e.preventDefault();
            dropArea.style.background = "#f9f9f9";
            const file = e.dataTransfer.files[0];
            if (file && (file.type === "text/xml" || file.name.endsWith(".xml"))) {
                const reader = new FileReader();
                reader.onload = function (evt) {
                    try {
                        const parser = new DOMParser();
                        const xmlDoc = parser.parseFromString(evt.target.result, "application/xml");
                        const parserError = xmlDoc.querySelector("parsererror");
                        if (parserError) {
                            console.error("[WME PLN] Error parseando XML:", parserError.textContent);
                            alert("Error al parsear el archivo XML del diccionario.");
                            return;
                        }
                        const xmlWords = xmlDoc.querySelectorAll("word");
                        let newWordsAddedCount = 0;
                        for (let i = 0; i < xmlWords.length; i++) {
                            const val = xmlWords[i].textContent.trim();
                            if (val && !window.dictionaryWords.has(val)) {
                                window.dictionaryWords.add(val);
                                newWordsAddedCount++;
                            }
                        }

                        // Actualizar la visualización después de importar
                        const listContainer = document.getElementById("dictionaryContainer");
                        if (listContainer) {
                            listContainer.innerHTML = "";
                            inicializarDiccionarioDinamico("dictionaryContainer", prepararDatosDiccionario());
                        }

                        updateDictionaryWordsCountLabel();

                        if (newWordsAddedCount > 0) {
                            alert(`${newWordsAddedCount} nuevas palabras añadidas desde XML.`);
                        } else {
                            alert("No se encontraron palabras nuevas para añadir.");
                        }
                    } catch (err) {
                        alert("Error procesando el diccionario XML.");
                        console.error("[WME PLN] Error procesando XML:", err);
                    }
                };
                reader.readAsText(file);
            } else {
                alert("Por favor, arrastra un archivo XML válido.");
            }
        });
        section.appendChild(dropArea);

        // Añadir todo al contenedor principal
        parentContainer.appendChild(section);

        // Inicializar el diccionario con carga bajo demanda
        setTimeout(() => {
            inicializarDiccionarioDinamico("dictionaryContainer", prepararDatosDiccionario());
            updateDictionaryWordsCountLabel();
        }, 100);
    }//createDictionaryManager

    // Helper de normalización para comparación de reemplazos (insensible a mayúsculas, tildes y espacios extra)
    function plnNormalizeReplacementKey(s) {
        try {
            return String(s || '')
                .normalize('NFD')
                .replace(/[\u0300-\u036f]/g, '')  // quitar diacríticos
                .replace(/\s+/g, ' ')              // normalizar espacios consecutivos
                .trim()
                .toLowerCase();
        } catch (_) { return String(s || '').toLowerCase().trim(); }
    }

    // Carga las palabras excluidas desde localStorage
    function loadReplacementWordsFromStorage() {
        const savedReplacements = localStorage.getItem("replacementWordsList");
        if (savedReplacements) {
            try {
                replacementWords = JSON.parse(savedReplacements);
                window.replacementWords = replacementWords;
                if (typeof replacementWords !== 'object' || replacementWords === null) { // Asegurar que sea un objeto
                    replacementWords = {};
                    window.replacementWords = replacementWords;
                }
                // Cargar mapa de fuentes (usuario/hoja) y asegurar valores por defecto
                try {
                    const savedSources = localStorage.getItem("replacementSources");
                    window.replacementSources = savedSources ? JSON.parse(savedSources) : {};
                } catch (e) {
                    window.replacementSources = {};
                }
                // Para cualquier reemplazo existente sin fuente, asumir que es del usuario
                Object.keys(replacementWords).forEach(k => {
                    if (!window.replacementSources || typeof window.replacementSources !== 'object') {
                        window.replacementSources = {};
                    }
                    if (!window.replacementSources[k]) {
                        window.replacementSources[k] = 'user';
                    }
                });

                // --- NUEVO: Normalización y eliminación de conflictos con reglas de hoja ---
                try {
                    // Construir índice normalizado de claves para evitar duplicados por mayúsculas/puntos/tildes
                    const seen = new Map(); // norm(from) -> canonical from
                    Object.keys(replacementWords).forEach(from => {
                        const lc = plnNormalizeReplacementKey(from);
                        if (seen.has(lc)) {
                            // Preferir la regla de hoja si hay duplicado, si no mantener la primera
                            const keepKey = (window.replacementSources[seen.get(lc)] === 'sheet') ? seen.get(lc)
                                : (window.replacementSources[from] === 'sheet' ? from : seen.get(lc));
                            const dropKey = keepKey === from ? seen.get(lc) : from;
                            if (dropKey && dropKey !== keepKey) {
                                delete replacementWords[dropKey];
                                if (window.replacementSources) delete window.replacementSources[dropKey];
                            }
                            seen.set(lc, keepKey);
                        } else {
                            seen.set(lc, from);
                        }
                    });
                } catch (_) { }
                // Guardar por si hubo saneo
                saveReplacementWordsToStorage();
            }
            catch (e) {
                console.error("[WME PLN] Error cargando lista de reemplazos desde localStorage:", e);
                replacementWords = {};
                window.replacementWords = replacementWords;
                window.replacementSources = {};
            }
        }
        else {
            replacementWords = {}; // Inicializar si no hay nada guardado
            window.replacementWords = replacementWords;
            window.replacementSources = {};
        }
        if (typeof window.updateReplacementsCountLabel === 'function') {
            window.updateReplacementsCountLabel();
        }
        //console.log("[WME PLN] Reemplazos cargados:",    Object.keys(replacementWords).length, "reglas.");
    }// loadReplacementWordsFromStorage

    // Carga las palabras excluidas desde localStorage
    // Función para guardar las palabras swap en localStorage (formato nuevo)
    function saveSwapWordsToStorage() {
        try {
            localStorage.setItem("swapWords", JSON.stringify(window.swapWords || []));
            //console.log("[WME PLN] SwapWords guardadas en localStorage:", window.swapWords ? window.swapWords.length : 0, "palabras");
        }
        catch (e) {
            console.error("[WME PLN] Error guardando swapWords en localStorage:", e);
        }
    }// saveSwapWordsToStorage

    // Carga las palabras reemplazo
    function saveReplacementWordsToStorage() {
        try {
            localStorage.setItem("replacementWordsList",
                JSON.stringify(replacementWords));
            // Guardar también las fuentes de reemplazo
            try {
                localStorage.setItem("replacementSources", JSON.stringify(window.replacementSources || {}));
            } catch (e) {
                console.warn('[WME PLN] Error guardando replacementSources en localStorage:', e);
            }
            // console.log("[WME PLN] Lista de reemplazos guardada en localStorage.");
        }
        catch (e) {
            console.error("[WME PLN] Error guardando lista de reemplazos en localStorage:", e);
        }
    }// saveReplacementWordsToStorage

    // Carga reemplazos base y fusiona con los del usuario, bloqueando los de hoja
    async function loadReplacementsFromSheet(forceReload = false) {
        const SPREADSHEET_ID = "10HSyonrHd__sC0wO0hmOQyBbNRlWF8UFr6kkOuSru00";
        const API_KEY = "AIzaSyAQbvIQwSPNWfj6CcVEz5BmwfNkao533i8";
        const SHEET_CANDIDATES = [
            "Replace!A2:B",        // Solicitud del usuario: la hoja se llama "Replace"
        ]; // A=from, B=to

        // Asegurar estructuras
        if (typeof replacementWords !== 'object' || !replacementWords) replacementWords = {};

        if (typeof window.replacementSources !== 'object' || !window.replacementSources) window.replacementSources = {};
        // Evitar si no se configuró
        if (SPREADSHEET_ID === "TU_SPREADSHEET_ID" || API_KEY === "TU_API_KEY") {
            console.warn('[WME PLN] SPREADSHEET_ID o API_KEY no configurados para reemplazos.');
            return;
        }

        // Intentar usar caché (24h)
        const cacheKey = 'wme_pln_replacements_cache';
        const cached = localStorage.getItem(cacheKey);
        if (!forceReload && cached) {
            try {
                const { data, timestamp } = JSON.parse(cached);
                if (data && timestamp && (Date.now() - timestamp < 24 * 60 * 60 * 1000)) {
                    // Exponer Set con orígenes fijos de hoja (exactos) para validaciones UI
                    try {
                        window.fixedReplacementFroms = new Set(Array.isArray(data) ? data.map(d => d.from) : []);
                        // También mantener sets normalizados para comparaciones flexibles (tildes, puntos, espacios, mayúsculas)
                        const rows = Array.isArray(data) ? data : [];
                        window.fixedReplacementFromsNorm = new Set(rows.map(d => plnNormalizeReplacementKey(d.from)));
                        // NUEVO: proteger también los destinos (columna B) del Sheet
                        window.fixedReplacementTargets = new Set(rows.map(d => d.to));
                        window.fixedReplacementTargetsNorm = new Set(rows.map(d => plnNormalizeReplacementKey(d.to)));
                        // NUEVO: índice normalizado de hoja y reverso para detectar contradicciones de forma global
                        try {
                            window.fixedReplacementIndex = new Map(rows.map(d => [plnNormalizeReplacementKey(d.from), plnNormalizeReplacementKey(d.to)]));
                            window.fixedReplacementReverseIndex = new Map(rows.map(d => [plnNormalizeReplacementKey(d.to), plnNormalizeReplacementKey(d.from)]));
                            window._fixedReplacementPairsRaw = rows.slice();
                        } catch (_) { window.fixedReplacementIndex = null; window.fixedReplacementReverseIndex = null; }
                    } catch (_) {
                        window.fixedReplacementFroms = new Set();
                        window.fixedReplacementFromsNorm = new Set();
                        window.fixedReplacementTargets = new Set();
                        window.fixedReplacementTargetsNorm = new Set();
                    }

                    // Fusionar datos en memoria
                    mergeSheetReplacementsIntoLocal(data);
                    // NUEVO: poda adicional de reglas de usuario en contradicción
                    try { plnPruneContradictoryUserReplacements?.(); } catch (_) { }
                    // Re-render si existe la lista
                    {
                        const _el = document.getElementById("replacementsListUL") || document.querySelector("#replacementsContainer ul");
                        const _sel = document.getElementById("replacementModeSelector");
                        if (_el) {
                            if (_sel && _sel.value === "swapStart" && typeof renderSwapList === "function") {
                                renderSwapList(_el);
                            }
                            else {
                                renderReplacementsList(_el);
                            }
                        }
                    }
                    return;
                }
            }
            catch (e) {
                console.warn('[WME PLN] Error leyendo caché de reemplazos:', e);
            }
        }

        // Intentar con múltiples posibles rangos por si el nombre de la pestaña difiere
        return new Promise((resolve) => {
            const tryNext = (idx) => {
                if (idx >= SHEET_CANDIDATES.length) {
                    console.warn('[WME PLN] No se encontraron reemplazos en ninguna pestaña candidata.');
                    {
                        try {
                            if (typeof plnCanonicalizeReplacementsBySheet === 'function') plnCanonicalizeReplacementsBySheet();
                        }
                        catch (_) {
                        }
                        requestAnimationFrame(() => {
                            const _el = document.getElementById("replacementsListUL") || document.querySelector("#replacementsContainer ul");
                            const _sel = document.getElementById("replacementModeSelector");
                            if (_el) {
                                if (_sel && _sel.value === "swapStart" && typeof renderSwapList === "function") {
                                    renderSwapList(_el);
                                }
                                else {
                                    renderReplacementsList(_el);
                                }
                            }
                        });
                    }
                    resolve();
                    return;
                }
                const RANGE = SHEET_CANDIDATES[idx];
                const url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${RANGE}?key=${API_KEY}`;
                // console.log('[WME PLN] Cargando reemplazos desde:', RANGE);
                makeRequest({
                    method: 'GET',
                    url,
                    timeout: 10000,
                    onload: function (response) {
                        if (response.status >= 200 && response.status < 300) {
                            try {
                                const payload = JSON.parse(response.responseText);
                                const rows = Array.isArray(payload.values) ? payload.values : [];
                                const sheetData = rows
                                    .map(r => ({ from: (r[0] ?? '').toString().trim(), to: (r[1] ?? '').toString() }))
                                    .filter(item => item.from.length > 0);
                                if (sheetData.length === 0) {
                                    // Intentar con la siguiente candidata
                                    tryNext(idx + 1);
                                    return;
                                }
                                // Guardar caché y fusionar
                                try { localStorage.setItem(cacheKey, JSON.stringify({ data: sheetData, timestamp: Date.now() })); } catch { }

                                // Exponer Set con orígenes fijos de hoja (exactos) para validaciones UI
                                try {
                                    window.fixedReplacementFroms = new Set(sheetData.map(d => d.from));
                                    // Sets adicionales con claves normalizadas para detección/lock por equivalencia
                                    window.fixedReplacementFromsNorm = new Set(sheetData.map(d => plnNormalizeReplacementKey(d.from)));
                                    window.fixedReplacementTargets = new Set(sheetData.map(d => d.to));
                                    window.fixedReplacementTargetsNorm = new Set(sheetData.map(d => plnNormalizeReplacementKey(d.to)));
                                    // Índices globales normalizados para detección de contradicciones
                                    try {
                                        window.fixedReplacementIndex = new Map(sheetData.map(d => [plnNormalizeReplacementKey(d.from), plnNormalizeReplacementKey(d.to)]));
                                        window.fixedReplacementReverseIndex = new Map(sheetData.map(d => [plnNormalizeReplacementKey(d.to), plnNormalizeReplacementKey(d.from)]));
                                        window._fixedReplacementPairsRaw = sheetData.slice();
                                    } catch (_) { window.fixedReplacementIndex = null; window.fixedReplacementReverseIndex = null; }
                                } catch (_) {
                                    window.fixedReplacementFroms = new Set();
                                    window.fixedReplacementFromsNorm = new Set();
                                    window.fixedReplacementTargets = new Set();
                                    window.fixedReplacementTargetsNorm = new Set();
                                }
                                mergeSheetReplacementsIntoLocal(sheetData);

                                // Poda adicional (defensa en profundidad) de reglas de usuario contradictorias
                                try { plnPruneContradictoryUserReplacements?.(); } catch (_) { }

                                // NUEVO: eliminar reglas de usuario que contradigan reglas de hoja (reverse mapping)
                                try {
                                    const sheetIndex = new Map(); // norm(from) -> norm(to)
                                    sheetData.forEach(({ from, to }) => {
                                        sheetIndex.set(plnNormalizeReplacementKey(from), plnNormalizeReplacementKey(to));
                                    });
                                    let changed = false;
                                    Object.keys(replacementWords).forEach(userFrom => {
                                        if (window.replacementSources[userFrom] === 'sheet') return; // solo usuario
                                        const userTo = String(replacementWords[userFrom] || '');
                                        // Si existe una regla de hoja que sea el reverso (normalizada)
                                        const sheetTo = sheetIndex.get(plnNormalizeReplacementKey(userTo));
                                        if (sheetTo && sheetTo === plnNormalizeReplacementKey(userFrom)) {
                                            delete replacementWords[userFrom];
                                            if (window.replacementSources) delete window.replacementSources[userFrom];
                                            changed = true;
                                        }
                                    });
                                    if (changed) saveReplacementWordsToStorage();
                                }
                                catch (_) {
                                }
                            }
                            catch (e) {
                                console.error('[WME PLN] Error procesando reemplazos básicos:', e);
                            }
                        }
                        else {

                            // Intentar siguiente pestaña si 404/400 (rango inválido)
                            // Para otros códigos, solo avisar y continuar
                            // console.warn(`[WME PLN] Error HTTP ${response.status} al cargar rango ${RANGE}`);
                            tryNext(idx + 1);
                            return;
                        }
                        {
                            try {
                                if (typeof plnCanonicalizeReplacementsBySheet === 'function') plnCanonicalizeReplacementsBySheet();
                            }
                            catch (_) {
                            }
                            requestAnimationFrame(() => {
                                const _el = document.getElementById("replacementsListUL") || document.querySelector("#replacementsContainer ul");
                                const _sel = document.getElementById("replacementModeSelector");
                                if (_el) {
                                    if (_sel && _sel.value === "swapStart" && typeof renderSwapList === "function") {
                                        renderSwapList(_el);
                                    }
                                    else {
                                        renderReplacementsList(_el);
                                    }
                                }
                            });
                        }
                        resolve();
                    },
                    onerror: function () {
                        tryNext(idx + 1);
                    },
                    ontimeout: function () {
                        tryNext(idx + 1);
                    }
                });
            };
            tryNext(0);
        });

        // Fusión de reemplazos de hoja con los del usuario. Previene duplicados; respeta los del usuario.
        function mergeSheetReplacementsIntoLocal(sheetPairs) {
            // Construir un mapa temporal para eliminar duplicados (case-insensitive, último gana)
            const tempMap = new Map(); // key: norm(from), value: {from, to}
            sheetPairs.forEach(({ from, to }) => {
                const key = plnNormalizeReplacementKey(from || '');
                if (!key) return;
                tempMap.set(key, { from, to });
            });

            // NUEVO: construir set de destinos normalizados de la hoja (columna B)
            let sheetTargetsNorm = new Set();
            try {
                sheetTargetsNorm = new Set(sheetPairs.map(p => plnNormalizeReplacementKey(p.to || '')));
            } catch (_) { }

            // Limpiar del caché local (usuario) cualquier regla cuyo origen coincida
            // con una regla de hoja, usando comparación normalizada para evitar
            // variantes de mayúsculas, puntos o tildes.
            try {
                const sheetNormSet = new Set(Array.from(tempMap.keys())); // claves normalizadas
                const keys = Object.keys(replacementWords);
                keys.forEach(fromKey => {
                    const isFromSheet = window.replacementSources && window.replacementSources[fromKey] === 'sheet';
                    if (isFromSheet) return;
                    const normLocal = plnNormalizeReplacementKey(fromKey);
                    if (sheetNormSet.has(normLocal) || sheetTargetsNorm.has(normLocal)) {
                        delete replacementWords[fromKey];
                        if (window.replacementSources) delete window.replacementSources[fromKey];
                    }
                });
            } catch (_) { }

            // Fusionar en replacementWords: la lista fija de hoja SIEMPRE prevalece
            for (const { from, to } of tempMap.values()) {
                replacementWords[from] = to;
                window.replacementSources[from] = 'sheet';

                // ✨ NUEVO: Agregar versión sin tildes para robustez (Institución -> Institucion)
                // Esto permite que reglas con tilde funcionen en entradas sin tilde
                try {
                    const fromNoDiacritics = removeDiacritics(from);
                    // Solo agregar si es diferente y no existe ya una regla específica
                    if (fromNoDiacritics !== from && !replacementWords[fromNoDiacritics]) {
                        replacementWords[fromNoDiacritics] = to;
                        window.replacementSources[fromNoDiacritics] = 'sheet';
                    }
                } catch (_) { }
            }

            // Persistir cambios
            saveReplacementWordsToStorage();
        }
    }// loadReplacementsFromSheet

    // Carga las palabras excluidas desde localStorage
    function saveExcludedWordsToLocalStorage() {
        try {
            localStorage.setItem("excludedWordsList", JSON.stringify(Array.from(excludedWords)));
            // console.log("[WME PLN] Lista de palabras especiales guardada en localStorage.");
        }
        catch (e) {
            console.error("[WME PLN] Error guardando palabras especiales en localStorage:", e);
        }
    }// saveExcludedWordsToLocalStorage

    // Función para guardar los IDs de lugares excluidos en localStorage
    function saveExcludedPlacesToLocalStorage() {
        try {
            // Convertir el Map a un array de arrays antes de stringify
            localStorage.setItem("excludedPlacesList", JSON.stringify(Array.from(excludedPlaces.entries())));
            console.log('[WME PLN] Lugares excluidos guardados:', excludedPlaces.size);
        } catch (e) {
            console.error('[WME PLN] Error guardando lugares excluidos en localStorage:', e);
        }
    }// saveExcludedPlacesToLocalStorage



    // Renderiza la lista de reemplazos
    function renderReplacementsList(ulElement) {
        //console.log("[WME_PLN][DEBUG] renderReplacementsList llamada para:", ulElement ? ulElement.id : "Elemento UL nulo");
        if (!ulElement) {
            //console.error("[WME PLN] Elemento UL para reemplazos no proporcionado a renderReplacementsList.");
            return;
        }
        // Asegurar depuración de contradicciones antes de pintar
        try { plnPruneContradictoryUserReplacements?.(); } catch (_) { }
        ulElement.innerHTML = ""; // Limpiar lista actual
        const entries = Object.entries(replacementWords);
        // Si no hay reemplazos, mostrar mensaje
        if (entries.length === 0) {
            const li = document.createElement("li");
            li.textContent = "No hay reemplazos definidos.";
            li.style.textAlign = "center";
            li.style.color = "#777";
            li.style.padding = "5px";
            ulElement.appendChild(li);
            return;
        }
        // Ordenar alfabéticamente por la palabra original (from)
        entries.sort((a, b) => a[0].toLowerCase().localeCompare(b[0].toLowerCase()));
        entries.forEach(([from, to]) => {
            const li = document.createElement("li");
            li.style.display = "flex";
            li.style.justifyContent = "space-between";
            li.style.alignItems = "center";
            li.style.padding = "4px 2px";
            li.style.borderBottom = "1px solid #f0f0f0";
            // Añadir un tooltip al elemento li
            const textContainer = document.createElement("div");
            textContainer.style.flexGrow = "1";
            textContainer.style.overflow = "hidden";
            textContainer.style.textOverflow = "ellipsis";
            textContainer.style.whiteSpace = "nowrap";
            // Determinar si esta regla proviene de hoja. Además del mapa de fuentes,
            // considerar el set normalizado para bloquear equivalentes (p.ej. "I.E" vs "i.e.").
            const isSheetLocked = (
                (window.replacementSources && window.replacementSources[from] === 'sheet') ||
                (window.fixedReplacementFromsNorm && window.fixedReplacementFromsNorm.has(plnNormalizeReplacementKey(from)))
            );
            const source = isSheetLocked ? 'sheet' : 'user';
            textContainer.title = `Reemplazar "${from}" con "${to}"` + (source === 'sheet' ? ' [bloqueado de hoja]' : '');
            // Crear los spans para mostrar el texto
            const fromSpan = document.createElement("span");
            fromSpan.textContent = from;
            fromSpan.style.fontWeight = "bold";
            textContainer.appendChild(fromSpan);
            // Añadir un espacio entre el "from" y el "to"
            const arrowSpan = document.createElement("span");
            arrowSpan.textContent = " → ";
            arrowSpan.style.margin = "0 5px";
            textContainer.appendChild(arrowSpan);
            // Span para el texto de reemplazo
            const toSpan = document.createElement("span");
            toSpan.textContent = to;
            toSpan.style.color = "#007bff";
            textContainer.appendChild(toSpan);
            // Indicador de bloqueo si es de hoja
            if (source === 'sheet') {
                const lockSpan = document.createElement('span');
                lockSpan.textContent = ' 🔒';
                lockSpan.title = 'Reemplazo bloqueado por definición de la Wazeopedia Colombia';
                textContainer.appendChild(lockSpan);
            }
            // Añadir el contenedor de texto al li
            li.appendChild(textContainer);
            // Botón Editar
            const editBtn = document.createElement("button");
            editBtn.innerHTML = "✏️";
            editBtn.title = "Editar este reemplazo";
            editBtn.style.border = "none";
            editBtn.style.background = "transparent";
            editBtn.style.cursor = "pointer";
            editBtn.style.padding = "2px 4px";
            editBtn.style.fontSize = "14px";
            editBtn.style.marginLeft = "4px";
            if (source === 'sheet') {
                editBtn.disabled = true;
                editBtn.style.opacity = '0.4';
                editBtn.style.cursor = 'not-allowed';
                editBtn.title = 'Reemplazo bloqueado por definición de la Wazeopedia Colombia';
            }
            editBtn.addEventListener("click", async () => {
                if ((window.replacementSources && window.replacementSources[from] === 'sheet')) {
                    alert('Este reemplazo proviene de reglas de la Wazeopedia Colombia y no puede editarse.');
                    return;
                }
                const newFrom = await showCustomPrompt("Editar texto original:", "📝 Editar Reemplazo", from, "✏️");
                if (newFrom === null) return;
                const newTo = await showCustomPrompt("Editar texto de reemplazo:", "📝 Editar Reemplazo", to, "✏️");
                if (newTo === null) return;
                if (!newFrom.trim()) {
                    alert("El campo 'Texto Original' es requerido.");
                    return;
                }
                if (newFrom === newTo) {
                    alert("El texto original y el de reemplazo no pueden ser iguales.");
                    return;
                }
                // Permitir que el destino contenga al origen y viceversa (ej. "Av" → "Av.", "CED." → "CED").
                // La prevención de bucles y conflictos se maneja más adelante con reglas
                // de hoja (reversa) y con el motor de reemplazo.
                // Regla: impedir editar para usar un ORIGEN que está en la lista fija si esta regla no es de hoja
                try {
                    if (
                        (window.fixedReplacementFroms && window.fixedReplacementFroms.has(newFrom)) ||
                        (window.fixedReplacementFromsNorm && window.fixedReplacementFromsNorm.has(plnNormalizeReplacementKey(newFrom))) ||
                        (window.fixedReplacementTargets && window.fixedReplacementTargets.has(newFrom)) ||
                        (window.fixedReplacementTargetsNorm && window.fixedReplacementTargetsNorm.has(plnNormalizeReplacementKey(newFrom)))
                    ) {
                        alert("Este origen está reservado por la lista de Wazeopedia Colombia (como origen o destino) y no puede usarse como 'Texto Original'.");
                        return;
                    }
                }
                catch (_) { }
                // Bloqueo por conflicto con regla de hoja (reversa)
                try {
                    const nFromLC = plnNormalizeReplacementKey(newFrom);
                    const nToLC = plnNormalizeReplacementKey(newTo);
                    const keys = Object.keys(replacementWords || {});
                    for (const k of keys) {
                        if (window.replacementSources && window.replacementSources[k] === 'sheet') {
                            const sFromLC = plnNormalizeReplacementKey(k);
                            const sToLC = plnNormalizeReplacementKey(replacementWords[k] || '');
                            if (sFromLC === nToLC && sToLC === nFromLC) {
                                alert(`Esta edición contradice una regla bloqueada en Wazeopedia Colombia: "${k}" → "${replacementWords[k]}".`);
                                return;
                            }
                        }
                    }
                } catch (_) { }

                // Duplicado case-insensitive de la clave
                try {
                    const nLc = plnNormalizeReplacementKey(newFrom);
                    const existingCI = Object.keys(replacementWords || {}).find(k => plnNormalizeReplacementKey(k) === nLc && k !== from);
                    if (existingCI) {
                        if (window.replacementSources && window.replacementSources[existingCI] === 'sheet') {
                            alert(`Ya existe una regla en Wazeopedia Colombia para "${existingCI}". No se puede sobrescribir.`);
                            return;
                        }
                        if (!confirm(`Ya existe una regla para "${existingCI}". ¿Deseas sobrescribirla con "${newFrom}" → "${newTo}"?`))
                            return;
                        delete replacementWords[existingCI];
                        if (window.replacementSources) delete window.replacementSources[existingCI];
                    }
                } catch (_) { }
                // Si cambia la clave, elimina la anterior
                if (newFrom !== from) delete replacementWords[from];
                replacementWords[newFrom] = newTo;
                if (!window.replacementSources) window.replacementSources = {};
                window.replacementSources[newFrom] = 'user';
                delete window.replacementSources[from];
                renderReplacementsList(ulElement);
                saveReplacementWordsToStorage();
            });

            // Botón Eliminar
            const deleteBtn = document.createElement("button");
            deleteBtn.innerHTML = "🗑️";
            deleteBtn.title = `Eliminar este reemplazo`;
            deleteBtn.style.border = "none";
            deleteBtn.style.background = "transparent";
            deleteBtn.style.cursor = "pointer";
            deleteBtn.style.padding = "2px 4px";
            deleteBtn.style.fontSize = "14px";
            deleteBtn.style.marginLeft = "4px";
            if (source === 'sheet') {
                deleteBtn.disabled = true;
                deleteBtn.style.opacity = '0.4';
                deleteBtn.style.cursor = 'not-allowed';
                deleteBtn.title = 'Reemplazo bloqueado desde Wazeopedia Colombia';
            }
            deleteBtn.addEventListener("click", () => {
                if ((window.replacementSources && window.replacementSources[from] === 'sheet')) {
                    alert('Este reemplazo proviene de Wazeopedia Colombia y no puede eliminarse.');
                    return;
                }
                if (confirm(`¿Estás seguro de eliminar el reemplazo:\n"${from}" → "${to}"?`)) {
                    delete replacementWords[from];
                    if (window.replacementSources) delete window.replacementSources[from];
                    renderReplacementsList(ulElement);
                    saveReplacementWordsToStorage();
                }
            });
            // Contenedor para los botones de acción
            const btnContainer = document.createElement("span");
            btnContainer.style.display = "flex";
            btnContainer.style.gap = "4px";
            btnContainer.appendChild(editBtn);
            btnContainer.appendChild(deleteBtn);
            // Añadir el contenedor de botones al li
            li.appendChild(btnContainer);
            ulElement.appendChild(li);
        });
        if (typeof window.updateReplacementsCountLabel === 'function') window.updateReplacementsCountLabel();
    }


    // Exporta las palabras especiales y reemplazos a un archivo XML
    function exportSharedDataToXml() {
        let xmlParts = [];
        const rootTagName = "WME_PLN_Backup";
        const fileName = "wme_pln_backup.xml";

        if (excludedWords.size === 0 && excludedPlaces.size === 0 && Object.keys(replacementWords).length === 0 &&
            (!window.swapWords || window.swapWords.length === 0) && Object.keys(editorStats).length === 0) {
            alert("No hay datos (palabras especiales, lugares excluidos, reemplazos, swap, estadísticas) para exportar.");
            return;
        }

        // Exportar palabras especiales (excludedWords)
        if (excludedWords.size > 0) {
            xmlParts.push("    <words>");
            Array.from(excludedWords)
                .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
                .forEach(w => xmlParts.push(`        <word>${xmlEscape(w)}</word>`));
            xmlParts.push("    </words>");
        }

        // Exportar reemplazos (replacementWords)
        if (Object.keys(replacementWords).length > 0) {
            xmlParts.push("    <replacements>");
            Object.entries(replacementWords)
                .sort((a, b) => a[0].toLowerCase().localeCompare(b[0].toLowerCase()))
                .forEach(([from, to]) => {
                    xmlParts.push(`        <replacement from="${xmlEscape(from)}">${xmlEscape(to)}</replacement>`);
                });
            xmlParts.push("    </replacements>");
        }

        // Exportar palabras de intercambio (swapWords)
        if (window.swapWords && window.swapWords.length > 0) {
            xmlParts.push("    <swapWords>");
            window.swapWords.forEach(item => {
                if (typeof item === 'object' && item.word && item.direction) {
                    xmlParts.push(`        <swap word="${xmlEscape(item.word)}" direction="${xmlEscape(item.direction)}"></swap>`);
                }
            });
            xmlParts.push("    </swapWords>");
        }

        // Exportar estadísticas (editorStats)
        if (Object.keys(editorStats).length > 0) {
            xmlParts.push("    <statistics>");
            Object.entries(editorStats).forEach(([userId, data]) => {
                xmlParts.push(`        <editor id="${userId}"
                    name="${xmlEscape(data.userName || '')}"
                    total_count="${data.total_count || 0}"
                    monthly_count="${data.monthly_count || 0}"
                    monthly_period="${data.monthly_period || ''}"
                    weekly_count="${data.weekly_count || 0}"
                    weekly_period="${data.weekly_period || ''}"
                    daily_count="${data.daily_count || 0}"
                    daily_period="${data.daily_period || ''}"
                    last_update="${data.last_update || 0}" />`);
            });
            xmlParts.push("    </statistics>");
        }

        // Exportar lugares excluidos (excludedPlaces)
        if (excludedPlaces.size > 0) {
            xmlParts.push("    <placeIds>");
            Array.from(excludedPlaces.entries())
                .sort((a, b) => (a[1] || '').toLowerCase().localeCompare(b[1] || ''))
                .forEach(([id, name]) => {
                    xmlParts.push(`        <placeId id="${xmlEscape(id)}" name="${xmlEscape(name || '')}"></placeId>`);
                });
            xmlParts.push("    </placeIds>");
        }

        // Construir el contenido XML completo
        const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>\n<${rootTagName}>\n${xmlParts.join("\n")}\n</${rootTagName}>`;

        const blob = new Blob([xmlContent], { type: "application/xml;charset=utf-8" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = fileName;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }

    // [PLN] Canonicalizar por hoja: drop si 'to' ∈ sheetTo; reencaminar si 'to' ∈ sheetFrom
    function plnCanonicalizeReplacementsBySheet() {
        try {
            const map = window.replacementWords || {};
            const src = window.replacementSources || {};

            // Map y conjuntos de hoja (from -> to canónico)
            const sheetFrom = [];
            const sheetMap = {};
            for (const f in map) {
                if (src[f] === 'sheet') {
                    sheetFrom.push(f);
                    sheetMap[f] = String(map[f] || '').trim();
                }
            }
            const sheetFromSet = new Set(sheetFrom);
            const sheetToSet = new Set(sheetFrom.map(f => sheetMap[f]));
            const newMap = {};
            const newSrc = {};
            for (const from in map) {
                const to = String(map[from] || '').trim();
                const isSheet = src[from] === 'sheet';
                // (A) Si el FROM local existe en hoja, imponer el B canónico de hoja
                if (!isSheet && sheetFromSet.has(from)) {
                    const canonicalTo = sheetMap[from];
                    if (canonicalTo) {
                        newMap[from] = canonicalTo;
                        if (src[from]) newSrc[from] = src[from];
                    }
                    continue;
                }
                // (B) Si el FROM local es de hoja, conservar tal cual (ya es canónico)
                if (isSheet) {
                    newMap[from] = to;
                    newSrc[from] = 'sheet';
                    continue;
                }
                // (C) Si el FROM local no es de hoja, aplicar reglas:
                if (sheetFromSet.has(to)) {
                    const canonical = sheetMap[to]; // B de la hoja para ese A
                    if (canonical) {
                        newMap[from] = canonical;
                        if (src[from]) newSrc[from] = src[from];
                    }
                    continue;
                }

                // 2) Si el 'to' local coincide con un 'from' de hoja → reencaminar al B canónico
                if (sheetFromSet.has(to)) {
                    const canonical = sheetMap[to]; // B de la hoja para ese A
                    if (canonical) {
                        newMap[from] = canonical;
                        if (src[from]) newSrc[from] = src[from];
                    }
                    continue;
                }

                // 3) Caso normal: conservar
                newMap[from] = to;
                if (src[from]) newSrc[from] = src[from];
            }
            window.replacementWords = newMap;
            window.replacementSources = newSrc;
        }
        catch (e) {
            try {
                console.error('[WME PLN] plnCanonicalizeReplacementsBySheet error', e);
            }
            catch (_) { }
        }
    }//plnCanonicalizeReplacementsBySheet

    // Función para manejar el archivo XML arrastrado
    function handleXmlFileDrop(file, type = "words") {
        if (file && (file.type === "text/xml" || file.name.endsWith(".xml"))) {
            const reader = new FileReader();
            reader.onload = function (evt) {
                try {
                    let newExcludedAdded = 0;
                    let newReplacementsAdded = 0;
                    let replacementsOverwritten = 0;
                    let newPlacesAdded = 0;

                    const parser = new DOMParser();
                    const xmlDoc = parser.parseFromString(evt.target.result, "application/xml");
                    const parserError = xmlDoc.querySelector("parsererror");
                    if (parserError) {
                        alert("Error al parsear el archivo XML: " + parserError.textContent);
                        return;
                    }
                    const rootTag = xmlDoc.documentElement.tagName.toLowerCase();

                    if (type === "words") {
                        if (rootTag !== "excludedwords") {
                            alert("El archivo XML no es válido para Palabras Especiales. Debe tener <ExcludedWords> como raíz.");
                            return;
                        }

                        const words = xmlDoc.getElementsByTagName("word");
                        for (let i = 0; i < words.length; i++) {
                            const val = words[i].textContent.trim();

                            // --- CORRECCIÓN CLAVE: Omitimos la validación al importar desde XML ---
                            // La validación es útil para la entrada manual, pero demasiado restrictiva para la importación,
                            // ya que rechaza palabras que ya existen en el diccionario.
                            if (val && !excludedWords.has(val)) {
                                excludedWords.add(val);
                                const firstChar = val.charAt(0).toLowerCase();
                                if (!excludedWordsMap.has(firstChar)) excludedWordsMap.set(firstChar, new Set());
                                excludedWordsMap.get(firstChar).add(val);
                                newExcludedAdded++;
                            }
                        }

                        const replacementNodes = xmlDoc.getElementsByTagName("replacement");
                        for (let i = 0; i < replacementNodes.length; i++) {
                            const from = replacementNodes[i].getAttribute("from")?.trim();
                            const to = replacementNodes[i].textContent.trim();
                            if (from && to) {
                                // Saltar si el origen pertenece a la lista fija de Sheets
                                try {
                                    if ((window.fixedReplacementFroms && window.fixedReplacementFroms.has(from)) ||
                                        (window.fixedReplacementFromsNorm && window.fixedReplacementFromsNorm.has(plnNormalizeReplacementKey(from))) ||
                                        (window.fixedReplacementTargets && window.fixedReplacementTargets.has(from)) ||
                                        (window.fixedReplacementTargetsNorm && window.fixedReplacementTargetsNorm.has(plnNormalizeReplacementKey(from)))) {
                                        continue;
                                    }
                                }
                                catch (_) { }
                                // Permitir que el destino contenga al origen y viceversa (ej. "Av" → "Av.", "CED." → "CED").
                                // Pero evitar introducir pares cuyo DESTINO coincide con un ORIGEN de la hoja (normalizado),
                                // para no colisionar con reglas canónicas: en ese caso, saltar la importación.
                                try {
                                    if (window.fixedReplacementFromsNorm && window.fixedReplacementFromsNorm.has(plnNormalizeReplacementKey(to))) {
                                        continue;
                                    }
                                } catch (_) { }
                                if (window.replacementSources && window.replacementSources[from] === 'sheet') {
                                    // No permitir sobreescribir los que vienen de hoja
                                    continue;
                                }
                                if (replacementWords.hasOwnProperty(from) && replacementWords[from] !== to) {
                                    replacementsOverwritten++;
                                }
                                else if (!replacementWords.hasOwnProperty(from)) {
                                    newReplacementsAdded++;
                                }
                                replacementWords[from] = to;
                                if (!window.replacementSources) window.replacementSources = {};
                                window.replacementSources[from] = 'user';
                            }
                        }
                    }
                    else if (type === "places") {
                        if (rootTag !== "placeIds" && rootTag !== "excludedwords") {
                            alert("El archivo XML no es válido para Lugares Excluidos. Debe tener <placeIds> o <ExcludedWords> como raíz.");
                            return;
                        }

                        const placesNodes = xmlDoc.getElementsByTagName("placeId");
                        let placesUpdated = 0;
                        for (let i = 0; i < placesNodes.length; i++) {
                            const placeId = placesNodes[i].getAttribute("id")?.trim();
                            const placeName = placesNodes[i].getAttribute("name")?.trim() || `ID: ${placeId}`;

                            if (placeId) {
                                if (!excludedPlaces.has(placeId)) {
                                    newPlacesAdded++;
                                }
                                else {
                                    placesUpdated++;
                                }
                                // Guardar solo el nombre
                                excludedPlaces.set(placeId, placeName);
                            }
                        }
                        alert(`Importación completada.\n- Lugares nuevos añadidos: ${newPlacesAdded}\n- Lugares existentes actualizados: ${placesUpdated}`);
                        saveExcludedPlacesToLocalStorage();
                    }
                    else {
                        alert("Tipo de importación XML desconocido.");
                        return;
                    }

                    // En handleXmlFileDrop():
                    const swapWordsNode = xmlDoc.querySelector("swapWords");
                    if (swapWordsNode) {
                        if (!window.swapWords) window.swapWords = [];

                        swapWordsNode.querySelectorAll("swap").forEach(swapNode => {
                            const word = swapNode.getAttribute("value");
                            const direction = swapNode.getAttribute("direction") || "start"; // Default a "start"

                            // Saltar si está protegido por hoja (Swap)
                            try {
                                const norm = (word || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/\s+/g, ' ').trim().toLowerCase();
                                if (window.fixedSwapNormSet && window.fixedSwapNormSet.has(norm)) return;
                            } catch (_) { }
                            if (word && !window.swapWords.some(item => (typeof item === 'object' ? item.word : item) === word)) {
                                window.swapWords.push({ word: word, direction: direction });
                            }
                        });
                        saveSwapWordsToStorage();
                    }

                    const statsNode = xmlDoc.querySelector("statistics");
                    if (statsNode) {
                        const editorNode = statsNode.querySelector("editor");
                        if (editorNode) {
                            if (editorNode.hasAttribute("total_count")) {
                                if (!currentGlobalUserInfo.id || currentGlobalUserInfo.name === 'No detectado') {
                                    showTemporaryMessage("Espera a que tu usuario se cargue para importar estadísticas.", 5000, "error");
                                }
                                else if (editorNode.getAttribute("id") === String(currentGlobalUserInfo.id)) {
                                    editorStats[currentGlobalUserInfo.id] = {
                                        userName: editorNode.getAttribute("name") || currentGlobalUserInfo.name,
                                        total_count: parseInt(editorNode.getAttribute("total_count"), 10) || 0,
                                        monthly_count: parseInt(editorNode.getAttribute("monthly_count"), 10) || 0,
                                        monthly_period: editorNode.getAttribute("monthly_period") || '',
                                        weekly_count: parseInt(editorNode.getAttribute("weekly_count"), 10) || 0,
                                        weekly_period: editorNode.getAttribute("weekly_period") || '',
                                        daily_count: parseInt(editorNode.getAttribute("daily_count"), 10) || 0,
                                        daily_period: editorNode.getAttribute("daily_period") || '',
                                        last_update: parseInt(editorNode.getAttribute("last_update"), 10) || 0
                                    };
                                }
                            }
                            else {
                                showTemporaryMessage("XML con formato de estadísticas antiguo detectado. Se omitió la importación.", 6000, "warning");
                            }
                        }
                    }

                    saveExcludedWordsToLocalStorage();
                    saveReplacementWordsToStorage();
                    saveEditorStats();
                    plnCanonicalizeReplacementsBySheet();
                    renderExcludedWordsList(document.getElementById("excludedWordsList"));
                    renderExcludedPlacesList(document.getElementById("excludedPlacesListUL"));
                    // [PLN] Re-render según modo actual (replacements vs swap) tras canonicalizar
                    {
                        const _el = document.getElementById("replacementsListUL") || document.querySelector("#replacementsContainer ul");
                        const _sel = document.getElementById("replacementModeSelector");
                        if (_el) {
                            if (_sel && _sel.value === "swapStart" && typeof renderSwapList === "function") {
                                renderSwapList(_el);
                            }
                            else {
                                renderReplacementsList(_el);
                            }
                        }
                    }
                    updateStatsDisplay();

                    alert(`Importación completada. \n - Palabras Especiales nuevas: ${newExcludedAdded} \n - Reemplazos nuevos: ${newReplacementsAdded} \n - Reemplazos sobrescritos: ${replacementsOverwritten} \n - Nuevos lugares: ${newPlacesAdded}`);
                }
                catch (err) {
                    console.error("[WME PLN] Error procesando el archivo XML importado:", err);
                    alert("Ocurrió un error procesando el archivo XML.");
                }
            };
            reader.readAsText(file);
        }
        else {
            alert("Por favor, arrastra un archivo XML válido.");
        }
    }//handleXmlFileDrop

    // Bloquea todos los controles de la pestaña principal durante el escaneo
    function disableScanControls() {
        const idsToDisable = [
            'pln-start-scan-btn', 'maxPlacesInput', 'chk-recommend-categories',
            'chk-avoid-my-edits', 'dateFilterSelect', 'chk-enable-stats'
        ];
        // Deshabilitar los controles principales
        idsToDisable.forEach(id => {
            const el = document.getElementById(id);
            if (el) {
                el.disabled = true;
                el.style.opacity = '0.6';
                el.style.cursor = 'not-allowed';
            }
        });
        // Deshabilitar los botones de presets
        document.querySelectorAll('.pln-preset-btn').forEach(btn => {
            btn.disabled = true;
            btn.style.opacity = '0.6';
            btn.style.cursor = 'not-allowed';
        });
    }// disableScanControls

    // Reactiva todos los controles de la pestaña principal al finalizar el escaneo
    function enableScanControls() {
        const idsToEnable = [
            'pln-start-scan-btn', 'maxPlacesInput', 'chk-recommend-categories',
            'chk-avoid-my-edits', 'dateFilterSelect', 'chk-enable-stats'
        ];
        // Reactivar los controles principales
        idsToEnable.forEach(id => {
            const el = document.getElementById(id);
            if (el) {
                el.disabled = false;
                el.style.opacity = '1';
                el.style.cursor = 'pointer';
            }
        });
        
        // Resetear el texto del botón de escaneo
        const scanBtn = document.getElementById('pln-start-scan-btn');
        if (scanBtn) {
            scanBtn.textContent = "Start Scan...";
        }
        // Reactivar los botones de presets
        document.querySelectorAll('.pln-preset-btn').forEach(btn => {
            btn.disabled = false;
            btn.style.opacity = '1';
            btn.style.cursor = 'pointer';
        });
        // Restaurar el estado del dropdown de fecha según el checkbox
        const avoidMyEditsCheckbox = document.getElementById("chk-avoid-my-edits");
        if (avoidMyEditsCheckbox) {
            const dateFilterRow = document.getElementById("dateFilterSelect").parentElement;
            dateFilterRow.style.opacity = avoidMyEditsCheckbox.checked ? "1" : "0.5";
            dateFilterRow.style.pointerEvents = avoidMyEditsCheckbox.checked ? "auto" : "none";
        }
    }// enableScanControls

    // Carga las palabras swap desde localStorage
    // Función para cargar las palabras swap desde localStorage con migración automática
    function loadSwapWordsFromStorage() {
        const stored = localStorage.getItem("swapWords");

        // Si hay datos en localStorage, intentar parsearlos
        if (stored) {
            try {
                const parsed = JSON.parse(stored);

                // MIGRACIÓN AUTOMÁTICA: Verificar el formato de los datos
                if (Array.isArray(parsed) && parsed.length > 0) {
                    // Verificar si es formato antiguo (array de strings)
                    if (typeof parsed[0] === "string") {
                        //console.log("[WME PLN] Detectado formato antiguo de swapWords. Migrando automáticamente...");

                        // Migrar formato antiguo a nuevo formato
                        window.swapWords = parsed.map(word => ({
                            word: word,
                            direction: "start" // Todas las palabras existentes se configuran como "start" por defecto
                        }));

                        // Guardar el nuevo formato inmediatamente
                        saveSwapWordsToStorage();
                        //console.log(`[WME PLN] Migración completada: ${window.swapWords.length} palabras migradas a formato 'start'.`);
                    }
                    else if (typeof parsed[0] === "object" && parsed[0].hasOwnProperty('word')) {
                        // Ya está en formato nuevo
                        window.swapWords = parsed;
                        //console.log("[WME PLN] Formato nuevo de swapWords detectado. No se requiere migración.");
                    }
                    else {
                        // Formato desconocido, inicializar vacío
                        console.warn("[WME PLN] Formato desconocido en swapWords. Inicializando lista vacía.");
                        window.swapWords = [];
                    }
                }
                else {
                    // Array vacío o null
                    window.swapWords = [];
                }
            }
            catch (e) {
                console.error("[WME PLN] Error al parsear swapWords desde localStorage:", e);
                window.swapWords = [];
            }
        }
        else {
            // No hay datos guardados
            window.swapWords = [];
        }
        if (typeof window.updateSwapWordsCountLabel === 'function') {
            window.updateSwapWordsCountLabel();
        }
    }// loadSwapWordsFromStorage

    // === Cargar Swap por defecto (hoja "Swap"): protege las entradas de hoja ===
    async function loadSwapWordsFromSheet(forceReload = false) {
        const SPREADSHEET_ID = "10HSyonrHd__sC0wO0hmOQyBbNRlWF8UFr6kkOuSru00";
        const API_KEY = "AIzaSyAQbvIQwSPNWfj6CcVEz5BmwfNkao533i8";
        const RANGE = "Swap!A2:B"; // A=token, B=direction

        const normDir = v => {
            const s = String(v || '').trim().toLowerCase();
            if (s === 'start') return 'start';
            if (s === 'end') return 'end';
            return null;
        };
        const plnNormalizeSwapKey = s => {
            try {
                return String(s || '')
                    .normalize('NFD')
                    .replace(/[\u0300-\u036f]/g, '')
                    .replace(/\s+/g, ' ')
                    .trim()
                    .toLowerCase();
            } catch (_) { return String(s || '').toLowerCase().trim(); }
        };

        if (SPREADSHEET_ID === "TU_SPREADSHEET_ID" || API_KEY === "TU_API_KEY") return;

        // Cache 24h
        const cacheKey = 'wme_pln_swap_cache';
        if (!forceReload) {
            try {
                const cached = JSON.parse(localStorage.getItem(cacheKey) || 'null');
                if (cached && cached.timestamp && (Date.now() - cached.timestamp < 24 * 60 * 60 * 1000)) {
                    applySwapSheetRows(cached.data || []);
                    return;
                }
            } catch (_) { }
        }

        return new Promise(resolve => {
            const url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${RANGE}?key=${API_KEY}`;
            makeRequest({
                method: 'GET', url, timeout: 10000,
                onload: (res) => {
                    try {
                        const payload = JSON.parse(res.responseText || '{}');
                        const rows = Array.isArray(payload.values) ? payload.values : [];
                        const data = rows.map(r => ({ word: (r[0] || '').toString().trim(), direction: normDir(r[1]) }))
                            .filter(x => x.word && x.direction);
                        try { localStorage.setItem(cacheKey, JSON.stringify({ data, timestamp: Date.now() })); } catch (_) { }
                        applySwapSheetRows(data);
                    } catch (e) { /* noop */ }
                    resolve();
                },
                onerror: () => resolve(),
                ontimeout: () => resolve()
            });
        });

        function applySwapSheetRows(rows) {
            try {
                // Crear set de palabras protegidas por hoja (normalizadas)
                window.fixedSwapNormSet = new Set(rows.map(r => plnNormalizeSwapKey(r.word)));
                if (!Array.isArray(window.swapWords)) window.swapWords = [];
                // Quitar de memoria local cualquier palabra de usuario que choque con la hoja
                window.swapWords = window.swapWords.filter(item => !window.fixedSwapNormSet.has(plnNormalizeSwapKey(item.word || item)));
                // Añadir las de hoja (canónicas)
                rows.forEach(r => {
                    const direction = r.direction === 'end' ? 'end' : 'start';
                    window.swapWords.push({ word: r.word, direction });
                });
                try { saveSwapWordsToStorage(); } catch (_) { }
                if (typeof window.updateSwapWordsCountLabel === 'function') {
                    window.updateSwapWordsCountLabel();
                }
            } catch (_) { }
        }
    }

    // === Recolector unificado de reglas de swap ===
    function plnCollectSwapRules() {
        try {
            const normDir = v => {
                v = String(v || '').toLowerCase();
                if (v === 'start') return 'before';
                if (v === 'end') return 'after';
                return null;
            };
            const key = s => String(s || '')
                .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
                .toLowerCase().trim();

            // Precedencia: 1) window.swapWords  2) UI lists  3) localStorage  -> luego OVERRIDES
            const map = new Map(); // k -> { word, position, _pri }
            const setRule = (w, d, pri) => {
                w = String(w || '').trim();
                d = normDir(d);
                if (!w || !d) return;
                const k = key(w);
                const prev = map.get(k);
                if (!prev || (prev._pri || 0) <= pri) {
                    map.set(k, { word: w, position: d, _pri: pri });
                }
            };

            // 1) Base: window.swapWords
            (Array.isArray(window.swapWords) ? window.swapWords : []).forEach(x => {
                if (!x) return;
                if (typeof x === 'string') { setRule(x, 'before', 1); return; }
                const w = x.word || x.text || x.token || x.value || x.name;
                const d = x.position || x.where || x.dir || x.direction;
                setRule(w, d, 1);
            });

            // 2) Listas de la UI si existen
            const toArr = v => Array.isArray(v) ? v : (typeof v === 'string' ? (() => { try { return JSON.parse(v); } catch { return [] } })() : []);
            const sStart = window.swapWordsStart || window.wordsStart || window.startWords || null;
            const sEnd = window.swapWordsEnd || window.wordsEnd || window.endWords || null;
            [
                { source: sStart, dir: 'before' },
                { source: sEnd, dir: 'after' }
            ].forEach(({ source, dir }) => {
                if (source) {
                    toArr(source).forEach(w => setRule(w, dir, 2));
                }
            });

            // 3) Escaneo de localStorage por claves relacionadas
            try {
                for (let i = 0; i < localStorage.length; i++) {
                    const k = localStorage.key(i);
                    const low = k.toLowerCase();
                    if (!/(^|:|_)swap(words)?|\bwords\b|palabra/i.test(low)) continue;
                    let val = null; try { val = JSON.parse(localStorage.getItem(k)); } catch { }
                    if (!val) continue;
                    if (Array.isArray(val)) {
                        const allStr = val.every(x => typeof x === 'string');
                        const allObj = val.every(x => x && typeof x === 'object');
                        if (allStr) {
                            if (/inicio|start|before/.test(low)) val.forEach(w => setRule(w, 'before', 3));
                            else if (/final|end|after/.test(low)) val.forEach(w => setRule(w, 'after', 3));
                        } else if (allObj) {
                            val.forEach(x => {
                                const w = x.word || x.text || x.token || x.value || x.name;
                                let d = normDir(x.position || x.where || x.dir || x.direction);
                                if (!d) {
                                    if (/inicio|start|before/.test(low)) d = 'before';
                                    else if (/final|end|after/.test(low)) d = 'after';
                                }
                                setRule(w, d, 3);
                            });
                        }
                    }
                }
            } catch { }


            return Array.from(map.values());
        } catch (e) {
            try { console.error('[PLN Swap] plnCollectSwapRules error', e); } catch { }
            // Fallback mínimo a window.swapWords normalizado + override
            const key = s => String(s || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim();
            const base = [];
            (Array.isArray(window.swapWords) ? window.swapWords : []).forEach(x => {
                if (!x) return;
                if (typeof x === 'string') base.push({ word: String(x).trim(), position: 'before' });
                else {
                    const w = x.word || x.text || x.token || x.value || x.name;
                    const d = String(x.position || x.where || x.dir || x.direction || 'before').toLowerCase();
                    base.push({ word: String(w || '').trim(), position: (d === 'after' || d === 'end' || d === 'despues' || d === 'después' || d === 'post') ? 'after' : 'before' });
                }
            });

            base.sort((a, b) => b.word.length - a.word.length);
            return base;
        }
    }
    // Función para calcular el área en metros cuadrados de una geometría de polígono
    // NOTA: Turf.js ha sido deshabilitado temporalmente, usando función alternativa
    function calculateAreaMeters(shape) {
        if (!shape || !shape.geometry) {
            return null; // Return null instead of Infinity
        }

        try {
            // Check if we have a valid polygon geometry
            if (shape.geometry.type === 'Polygon') {
                // Extract coordinates from the polygon
                const coordinates = shape.geometry.coordinates[0]; // First ring of coordinates

                if (!coordinates || !Array.isArray(coordinates) || coordinates.length < 3) {
                    return null; // Not enough points for a valid polygon
                }

                // Use the Shoelace formula (Gauss's area formula) to calculate polygon area
                let area = 0;
                for (let i = 0; i < coordinates.length - 1; i++) {
                    if (!Array.isArray(coordinates[i]) || !Array.isArray(coordinates[i + 1]) ||
                        coordinates[i].length < 2 || coordinates[i + 1].length < 2) {
                        return null; // Invalid coordinate pair
                    }
                    area += coordinates[i][0] * coordinates[i + 1][1];
                    area -= coordinates[i][1] * coordinates[i + 1][0];
                }

                area = Math.abs(area) / 2;

                // Convert to square meters based on projection
                // This assumes the coordinates are in a geographic coordinate system
                const metersPerDegree = 111319.9; // Approximate meters per degree at the equator
                return area * Math.pow(metersPerDegree, 2);
            }
        } catch (error) {
            console.warn("[WME PLN] Error calculating area:", error);
            return null; // Return null on error
        }

        return null; // Default return for non-polygon shapes
    }// calculateAreaMeters

    // Crea el gestor de reemplazos
    function createReplacementsManager(parentContainer) {
        loadSwapWordsFromStorage();
        parentContainer.innerHTML = ''; // Limpiar por si acaso

        // ===================================================================
        // INICIO: NUEVA FUNCIÓN HELPER PARA EL MODAL DE EDICIÓN
        // ===================================================================
        function openSwapWordEditor(item, index) {
            // Crear el fondo del modal
            const modalOverlay = document.createElement("div");
            modalOverlay.style.position = "fixed";
            modalOverlay.style.top = "0";
            modalOverlay.style.left = "0";
            modalOverlay.style.width = "100%";
            modalOverlay.style.height = "100%";
            modalOverlay.style.background = "rgba(0,0,0,0.5)";
            modalOverlay.style.zIndex = "20000";
            modalOverlay.style.display = "flex";
            modalOverlay.style.justifyContent = "center";
            modalOverlay.style.alignItems = "center";

            // Crear el contenido del modal
            const modalContent = document.createElement("div");
            modalContent.style.background = "#fff";
            modalContent.style.padding = "25px";
            modalContent.style.borderRadius = "8px";
            modalContent.style.boxShadow = "0 5px 15px rgba(0,0,0,0.3)";
            modalContent.style.width = "400px";
            modalContent.style.fontFamily = "sans-serif";

            // Título del modal
            const title = document.createElement("h4");
            title.textContent = "Editar Palabra Swap";
            title.style.marginTop = "0";
            title.style.marginBottom = "20px";
            title.style.textAlign = "center";
            modalContent.appendChild(title);

            // Input para la palabra
            const wordLabel = document.createElement("label");
            wordLabel.textContent = "Palabra o Frase:";
            wordLabel.style.display = "block";
            wordLabel.style.marginBottom = "5px";
            modalContent.appendChild(wordLabel);

            const wordInput = document.createElement("input");
            wordInput.type = "text";
            wordInput.value = item.word;
            wordInput.style.width = "calc(100% - 12px)";
            wordInput.style.padding = "8px";
            wordInput.style.marginBottom = "15px";
            wordInput.setAttribute('spellcheck', 'false');
            modalContent.appendChild(wordInput);

            // Radio buttons para la dirección
            const directionFieldset = document.createElement("fieldset");
            directionFieldset.style.border = "1px solid #ccc";
            directionFieldset.style.borderRadius = "5px";
            directionFieldset.style.padding = "10px";
            const legend = document.createElement("legend");
            legend.textContent = "Mover a:";
            directionFieldset.appendChild(legend);

            ['start', 'end'].forEach(dir => {
                const container = document.createElement("div");
                container.style.marginBottom = "5px";
                const radio = document.createElement("input");
                radio.type = "radio";
                radio.name = "editSwapDirection";
                radio.value = dir;
                radio.id = `editSwap_${dir}`;
                if (item.direction === dir) radio.checked = true;

                const label = document.createElement("label");
                label.htmlFor = `editSwap_${dir}`;
                label.textContent = ` ${dir === 'start' ? 'Al Inicio (Start ←A)' : 'Al Final (A→End)'}`;
                label.style.cursor = "pointer";

                container.appendChild(radio);
                container.appendChild(label);
                directionFieldset.appendChild(container);
            });
            modalContent.appendChild(directionFieldset);

            // Contenedor para los botones de acción
            const buttonContainer = document.createElement("div");
            buttonContainer.style.display = "flex";
            buttonContainer.style.justifyContent = "flex-end";
            buttonContainer.style.gap = "10px";
            buttonContainer.style.marginTop = "20px";

            // Botón Cancelar
            const cancelBtn = document.createElement("button");
            cancelBtn.textContent = "Cancelar";
            cancelBtn.style.padding = "8px 15px";
            cancelBtn.addEventListener("click", () => modalOverlay.remove());
            buttonContainer.appendChild(cancelBtn);

            // Botón Guardar
            const saveBtn = document.createElement("button");
            saveBtn.textContent = "Guardar Cambios";
            saveBtn.style.padding = "8px 15px";
            saveBtn.style.background = "#007bff";
            saveBtn.style.color = "white";
            saveBtn.style.border = "none";
            saveBtn.style.borderRadius = "4px";
            saveBtn.addEventListener("click", () => {
                const newWord = wordInput.value.trim();
                const newDirection = document.querySelector('input[name="editSwapDirection"]:checked').value;

                if (!newWord) {
                    alert("La palabra no puede estar vacía.");
                    return;
                }

                // Verificar si el nuevo nombre ya existe (excluyendo el item actual)
                if (newWord !== item.word && window.swapWords.some((sw, i) => i !== index && sw.word === newWord)) {
                    alert("Esa palabra ya existe en la lista.");
                    return;
                }

                // Actualizar el item en el array global
                window.swapWords[index].word = newWord;
                window.swapWords[index].direction = newDirection;

                saveSwapWordsToStorage();
                renderSwapList(document.getElementById("swapListUL"));
                if (typeof window.updateSwapWordsCountLabel === 'function') window.updateSwapWordsCountLabel();
                modalOverlay.remove();
            });
            buttonContainer.appendChild(saveBtn);

            modalContent.appendChild(buttonContainer);
            modalOverlay.appendChild(modalContent);
            document.body.appendChild(modalOverlay);
        }
        // ===================================================================
        // FIN: NUEVA FUNCIÓN HELPER
        // ===================================================================


        // --- Contenedor principal ---
        const title = document.createElement("h4");
        title.textContent = "Gestión de Reemplazos";
        title.style.fontSize = "15px";
        title.style.marginBottom = "10px";
        parentContainer.appendChild(title);
        // --- Dropdown de modo de reemplazo ---
        const modeSelector = document.createElement("select");
        modeSelector.id = "replacementModeSelector";
        modeSelector.style.marginBottom = "10px";
        modeSelector.style.marginTop = "5px";
        // Añadir opciones al selector
        const optionWords = document.createElement("option");
        optionWords.value = "words";
        optionWords.textContent = "Reemplazos de palabras";
        modeSelector.appendChild(optionWords);
        // Añadir opción para swap
        const optionSwap = document.createElement("option");
        optionSwap.value = "swapStart";
        optionSwap.textContent = "Palabras al inicio/final (swap)"; // Texto actualizado
        modeSelector.appendChild(optionSwap);
        parentContainer.appendChild(modeSelector);
        //Contenedor para reemplazos y controles
        const replacementsContainer = document.createElement("div");
        replacementsContainer.id = "replacementsContainer";
        // Sección para añadir nuevos reemplazos
        const addSection = document.createElement("div");
        addSection.style.display = "flex";
        addSection.style.gap = "8px";
        addSection.style.marginBottom = "12px";
        addSection.style.alignItems = "flex-end"; // Alinear inputs y botón
        // Contenedores para inputs de texto
        const fromInputContainer = document.createElement("div");
        fromInputContainer.style.flexGrow = "1";
        const fromLabel = document.createElement("label");
        fromLabel.textContent = "Texto Original:";
        fromLabel.style.display = "block";
        fromLabel.style.fontSize = "12px";
        fromLabel.style.marginBottom = "2px";
        // Input para el texto original
        const fromInput = document.createElement("input");
        fromInput.type = "text";
        fromInput.placeholder = "Ej: Urb.";
        fromInput.style.width = "95%"; // Para que quepa bien
        fromInput.style.padding = "6px";
        fromInput.style.border = "1px solid #ccc";
        // Añadir label e input al contenedor
        fromInputContainer.appendChild(fromLabel);
        fromInputContainer.appendChild(fromInput);
        addSection.appendChild(fromInputContainer);
        // Contenedor para el texto de reemplazo
        const toInputContainer = document.createElement("div");
        toInputContainer.style.flexGrow = "1";
        const toLabel = document.createElement("label");
        toLabel.textContent = "Texto de Reemplazo:";
        toLabel.style.display = "block";
        toLabel.style.fontSize = "12px";
        toLabel.style.marginBottom = "2px";
        // Input para el texto de reemplazo
        const toInput = document.createElement("input");
        toInput.type = "text";
        toInput.placeholder = "Ej: Urbanización";
        toInput.style.width = "95%";
        toInput.style.padding = "6px";
        toInput.style.border = "1px solid #ccc";
        toInputContainer.appendChild(toLabel);
        toInputContainer.appendChild(toInput);
        addSection.appendChild(toInputContainer);
        // Atributos para evitar corrección ortográfica
        fromInput.setAttribute('spellcheck', 'false');
        toInput.setAttribute('spellcheck', 'false');
        // Botón para añadir el reemplazo
        const addReplacementBtn = document.createElement("button");
        addReplacementBtn.textContent = "Añadir";
        addReplacementBtn.style.padding = "6px 10px";
        addReplacementBtn.style.cursor = "pointer";
        addReplacementBtn.style.height = "30px"; // Para alinear con los inputs
        addSection.appendChild(addReplacementBtn);
        // Elemento UL para la lista de reemplazos
        const listElement = document.createElement("ul");
        listElement.id = "replacementsListElementID"; // ID ÚNICO para esta lista
        listElement.style.maxHeight = "150px";
        listElement.style.overflowY = "auto";
        listElement.style.border = "1px solid #ddd";
        listElement.style.padding = "8px";
        listElement.style.margin = "0 0 10px 0";
        listElement.style.background = "#fff";
        listElement.style.listStyle = "none";
        // Event listener para el botón "Añadir"
        addReplacementBtn.addEventListener("click", () => {
            const fromValue = fromInput.value.trim();
            const toValue = toInput.value.trim();
            if (!fromValue) {
                alert("El campo 'Texto Original' es requerido.");
                return;
            }
            if (fromValue === toValue) {
                alert("El texto original y el de reemplazo no pueden ser iguales.");
                return;
            }
            // Permitir que el destino contenga al origen y viceversa (ej. "Av" → "Av.", "CED." → "CED").
            // La prevención de bucles y conflictos se maneja con las reglas de hoja (reversa)
            // y el motor de reemplazo.
            // Regla: no permitir crear reglas de usuario con orígenes fijos por defecto
            try {
                if ((window.fixedReplacementFroms && window.fixedReplacementFroms.has(fromValue)) ||
                    (window.fixedReplacementFromsNorm && window.fixedReplacementFromsNorm.has(plnNormalizeReplacementKey(fromValue))) ||
                    (window.fixedReplacementTargets && window.fixedReplacementTargets.has(fromValue)) ||
                    (window.fixedReplacementTargetsNorm && window.fixedReplacementTargetsNorm.has(plnNormalizeReplacementKey(fromValue)))) {
                    alert("Este origen está reservado por la lista de Wazeopedia(como origen o destino) y no puede ser agregado por el usuario.");
                    return;
                }
            } catch (_) { }
            if ((window.replacementSources && window.replacementSources[fromValue] === 'sheet')) {
                alert("No puedes modificar un reemplazo definido por la comunidad.");
                return;
            }
            // Bloqueo: no permitir reglas que contradicen una regla de hoja (reversa)
            try {
                const userFromLC = plnNormalizeReplacementKey(fromValue);
                const userToLC = plnNormalizeReplacementKey(toValue);
                const keys = Object.keys(replacementWords || {});
                for (const k of keys) {
                    if (window.replacementSources && window.replacementSources[k] === 'sheet') {
                        const sheetFromLC = plnNormalizeReplacementKey(k);
                        const sheetToLC = plnNormalizeReplacementKey(replacementWords[k] || '');
                        if (sheetFromLC === userToLC && sheetToLC === userFromLC) {
                            alert(`Esta regla contradice una regla bloqueada por la Comundiad de Editores: "${k}" → "${replacementWords[k]}".\nNo se puede crear "${fromValue}" → "${toValue}".`);
                            return;
                        }
                    }
                }
            } catch (_) { }

            // Tratar duplicados case-insensitive de la clave 'from'
            let existingKeySameCI = null;
            try {
                const lc = plnNormalizeReplacementKey(fromValue);
                existingKeySameCI = Object.keys(replacementWords || {}).find(k => plnNormalizeReplacementKey(k) === lc) || null;
                if (existingKeySameCI && existingKeySameCI !== fromValue) {
                    if (window.replacementSources && window.replacementSources[existingKeySameCI] === 'sheet') {
                        alert(`Ya existe una regla definida por la comunidad para "${existingKeySameCI}". No se puede crear otra con diferente capitalización.`);
                        return;
                    }
                    if (!confirm(`Ya existe una regla para "${existingKeySameCI}". ¿Deseas sobrescribirla con "${fromValue}" → "${toValue}"?`))
                        return;
                }
            } catch (_) { }

            if (replacementWords.hasOwnProperty(fromValue) && replacementWords[fromValue] !== toValue) {
                if (!confirm(`El reemplazo para "${fromValue}" ya existe ('${replacementWords[fromValue]}'). ¿Deseas sobrescribirlo con '${toValue}'?`))
                    return;
            }

            const keyToStore = existingKeySameCI || fromValue;
            if (existingKeySameCI && existingKeySameCI !== fromValue) {
                if (existingKeySameCI in replacementWords && existingKeySameCI !== keyToStore) {
                    delete replacementWords[existingKeySameCI];
                    if (window.replacementSources) delete window.replacementSources[existingKeySameCI];
                }
            }
            replacementWords[keyToStore] = toValue;
            if (!window.replacementSources) window.replacementSources = {};
            window.replacementSources[keyToStore] = 'user';
            fromInput.value = "";
            toInput.value = "";
            renderReplacementsList(listElement);
            saveReplacementWordsToStorage();
        });

        // Contador de Reemplazos (ARRIBA de los botones)
        const replacementsCountLabel = document.createElement("div");
        replacementsCountLabel.id = "replacementsCountLabel";
        replacementsCountLabel.style.marginBottom = "8px";
        replacementsCountLabel.style.marginTop = "10px";
        replacementsCountLabel.style.fontSize = "13px";
        replacementsCountLabel.style.color = "#666";
        replacementsCountLabel.style.fontWeight = "500";
        replacementsCountLabelElement = replacementsCountLabel;
        window.updateReplacementsCountLabel?.();
        replacementsContainer.appendChild(replacementsCountLabel);

        // Botones de Acción y Drop Area (usarán la lógica compartida)
        const actionButtonsContainer = document.createElement("div");
        actionButtonsContainer.style.display = "flex";
        actionButtonsContainer.style.gap = "8px";
        actionButtonsContainer.style.alignItems = "center"; // Alineación vertical
        actionButtonsContainer.style.flexWrap = "wrap"; // Permitir wrap
        actionButtonsContainer.style.marginBottom = "10px";

        // Botón para exportar solo reemplazos
        const clearButton = document.createElement("button");
        clearButton.textContent = "Limpiar Reemplazos";
        clearButton.title = "Limpiar solo la lista de reemplazos";
        clearButton.style.padding = "6px 10px";
        clearButton.addEventListener("click", async () => {
            if (!confirm("¿Estás seguro de que deseas eliminar TODOS los reemplazos definidos?")) {
                return;
            }

            const originalText = clearButton.textContent;
            clearButton.disabled = true;
            clearButton.textContent = "Limpiando...";

            try {
                replacementWords = {};
                window.replacementSources = {};
                saveReplacementWordsToStorage();
                renderReplacementsList(listElement);

                await loadReplacementsFromSheet(true);
                renderReplacementsList(listElement);
                showTemporaryMessage("Reemplazos por defecto recargados.", 3500, 'info');
            }
            catch (e) {
                console.error('[WME PLN] Error recargando reemplazos por defecto tras limpiar:', e);
                showTemporaryMessage("No se pudieron recargar los reemplazos por defecto.", 4000, 'error');
            }
            finally {
                clearButton.disabled = false;
                clearButton.textContent = originalText;
            }
        });
        actionButtonsContainer.appendChild(clearButton);
        // Botón para importar desde XML
        const dropArea = document.createElement("div");
        dropArea.textContent = "Arrastra aquí el archivo XML (contiene Excluidas y Reemplazos)";
        dropArea.style.border = "2px dashed #ccc";
        dropArea.style.borderRadius = "4px";
        dropArea.style.padding = "15px";
        dropArea.style.marginTop = "10px";
        dropArea.style.textAlign = "center";
        dropArea.style.background = "#f9f9f9";
        dropArea.style.color = "#555";
        // Añadir estilos para el drop area
        dropArea.addEventListener("dragover", (e) => {
            e.preventDefault();
            dropArea.style.background = "#e9e9e9";
        });
        // Cambiar el fondo al salir del área de arrastre
        dropArea.addEventListener("dragleave", () => { dropArea.style.background = "#f9f9f9"; });
        // Manejar el evento de drop
        dropArea.addEventListener("drop", (e) => {
            e.preventDefault();
            dropArea.style.background = "#f9f9f9";
            handleXmlFileDrop(e.dataTransfer.files[0]);
        });
        // --- Ensamblar en replacementsContainer ---
        replacementsContainer.appendChild(addSection);
        replacementsContainer.appendChild(listElement);
        replacementsContainer.appendChild(actionButtonsContainer);
        replacementsContainer.appendChild(dropArea);
        parentContainer.appendChild(replacementsContainer);


        // --- Contenedor para swapStart/frases al inicio ---
        const swapContainer = document.createElement("div");
        swapContainer.id = "swapContainer";
        swapContainer.style.display = "none";

        // === TÍTULO DINÁMICO CON CONTADOR ===
        const swapTitle = document.createElement("h4");
        swapTitle.id = "swapWordsCountLabel";
        swapTitle.style.fontSize = "14px";
        swapTitle.style.marginBottom = "8px";
        swapWordsCountLabelElement = swapTitle;
        if (typeof window.updateSwapWordsCountLabel === 'function') window.updateSwapWordsCountLabel();
        swapContainer.appendChild(swapTitle);

        const swapExplanationBox = document.createElement("div");
        swapExplanationBox.style.background = "#f4f8ff";
        swapExplanationBox.style.borderLeft = "4px solid #2d6df6";
        swapExplanationBox.style.padding = "10px";
        swapExplanationBox.style.margin = "10px 0";
        swapExplanationBox.style.fontSize = "13px";
        swapExplanationBox.style.lineHeight = "1.4";
        swapExplanationBox.innerHTML =
            "<strong>🔄 ¿Qué hace esta lista?</strong><br>" +
            "Las palabras aquí se moverán al inicio o al final del nombre.<br>" +
            "<em>Ej:</em> \"Las Palmas <b>Urbanización</b>\" → \"<b>Urbanización</b> Las Palmas\" (si se configura 'Al Inicio').";
        swapContainer.appendChild(swapExplanationBox);

        // =======================================================
        // INICIO DE LA MODIFICACIÓN DEL LAYOUT
        // =======================================================

        // Contenedor principal para los controles, ahora apilado verticalmente
        const swapInputContainer = document.createElement("div");
        swapInputContainer.style.display = "flex";
        swapInputContainer.style.flexDirection = "column"; // Apilado vertical
        swapInputContainer.style.gap = "8px";
        swapInputContainer.style.marginBottom = "8px";

        // Fila 1: Input de la palabra
        const swapInputDiv = document.createElement("div");
        const swapInputLabel = document.createElement("label");
        swapInputLabel.textContent = "Palabra a agregar:";
        swapInputLabel.style.fontSize = "12px";
        swapInputLabel.style.display = "block";
        swapInputLabel.style.marginBottom = "2px";
        const swapInput = document.createElement("input");
        swapInput.type = "text";
        swapInput.placeholder = "Ej: Urbanización";
        swapInput.style.width = "calc(100% - 12px)"; // Ancho completo
        swapInput.style.padding = "6px";
        swapInput.setAttribute('spellcheck', 'false');
        swapInputDiv.appendChild(swapInputLabel);
        swapInputDiv.appendChild(swapInput);

        // Fila 2: Controles de dirección y botón de añadir
        const controlsRow = document.createElement("div");
        controlsRow.style.display = "flex";
        controlsRow.style.alignItems = "center";
        controlsRow.style.gap = "10px";

        // Contenedor para los radio buttons
        const directionContainer = document.createElement("div");
        directionContainer.style.display = "flex";
        directionContainer.style.gap = "15px";
        directionContainer.style.padding = "5px 10px";
        directionContainer.style.border = "1px solid #ccc";
        directionContainer.style.borderRadius = "4px";

        ['start', 'end'].forEach(dir => {
            const optionContainer = document.createElement('div');
            optionContainer.style.display = 'flex';
            optionContainer.style.alignItems = 'center';

            const radio = document.createElement("input");
            radio.type = "radio";
            radio.name = "swapDirection";
            radio.value = dir;
            radio.id = `swap_${dir}`;
            if (dir === 'start') radio.checked = true;
            radio.style.marginRight = "4px";

            const label = document.createElement("label");
            label.htmlFor = `swap_${dir}`;
            label.textContent = dir === 'start' ? 'Mover a Inicio' : 'Mover al Final'; // ETIQUETAS ACTUALIZADAS
            label.style.fontSize = "13px";
            label.style.cursor = "pointer";

            optionContainer.appendChild(radio);
            optionContainer.appendChild(label);
            directionContainer.appendChild(optionContainer);
        });

        // Botón para añadir
        const swapBtn = document.createElement("button");
        swapBtn.textContent = "Añadir";
        swapBtn.style.padding = "6px 12px";
        swapBtn.style.height = "32px";

        // Ensamblar la fila 2
        controlsRow.appendChild(directionContainer);
        controlsRow.appendChild(swapBtn);

        // Ensamblar el contenedor principal
        swapInputContainer.appendChild(swapInputDiv);
        swapInputContainer.appendChild(controlsRow);

        swapContainer.appendChild(swapInputContainer); // Añadir el contenedor principal al panel

        // =======================================================
        // FIN DE LA MODIFICACIÓN DEL LAYOUT
        // =======================================================

        // === EVENT LISTENER PARA EL BOTÓN AÑADIR (con protección de hoja) ===
        swapBtn.addEventListener("click", () => {
            const val = swapInput.value.trim();
            const direction = document.querySelector('input[name="swapDirection"]:checked').value;

            if (!val || /^[^a-zA-Z0-9]+$/.test(val)) {
                alert("No se permiten caracteres especiales solos o palabras vacías.");
                return;
            }
            // Bloquear si la palabra está protegida por la hoja Swap
            try {
                const norm = (val || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/\s+/g, ' ').trim().toLowerCase();
                if (window.fixedSwapNormSet && window.fixedSwapNormSet.has(norm)) {
                    alert('Esta palabra está protegida por la lista de la Comundiad de Editores (Swap) y no puede agregarse.');
                    return;
                }
            } catch (_) { }

            if (window.swapWords.some(item => item.word === val)) {
                alert("Esa palabra ya existe en la lista.");
                return;
            }

            window.swapWords.push({ word: val, direction: direction });
            saveSwapWordsToStorage();
            if (typeof window.updateSwapWordsCountLabel === 'function') window.updateSwapWordsCountLabel();
            swapInput.value = "";
            renderSwapList(document.getElementById("swapListUL"));
        });

        // === CAMPO DE BÚSQUEDA ===
        const searchSwapInput = document.createElement("input");
        searchSwapInput.type = "text";
        searchSwapInput.placeholder = "Buscar palabra...";
        searchSwapInput.id = "searchSwapInput";
        searchSwapInput.style.width = "calc(100% - 12px)";
        searchSwapInput.style.padding = "6px";
        searchSwapInput.style.marginBottom = "8px";
        searchSwapInput.style.border = "1px solid #ccc";

        searchSwapInput.addEventListener("input", () => renderSwapList());
        swapContainer.appendChild(searchSwapInput);

        // === LISTA UL ===
        const swapList = document.createElement("ul");
        swapList.id = "swapListUL";
        swapList.style.maxHeight = "150px";
        swapList.style.overflowY = "auto";
        swapList.style.border = "1px solid #ddd";
        swapList.style.padding = "5px";
        swapList.style.margin = "0";
        swapList.style.background = "#fff";
        swapList.style.listStyle = "none";
        swapContainer.appendChild(swapList);

        // === BOTONES DE ACCIÓN ===
        const swapActionButtonsContainer = document.createElement("div");
        swapActionButtonsContainer.style.display = "flex";
        swapActionButtonsContainer.style.gap = "8px";
        swapActionButtonsContainer.style.alignItems = "center";
        swapActionButtonsContainer.style.flexWrap = "wrap";
        swapActionButtonsContainer.style.marginTop = "10px";

        const clearSwapBtn = document.createElement("button");
        clearSwapBtn.textContent = "Limpiar Swap";
        clearSwapBtn.title = "Limpiar lista de palabras de intercambio";
        clearSwapBtn.style.padding = "6px 10px";
        clearSwapBtn.style.cursor = "pointer";
        clearSwapBtn.addEventListener("click", () => {
            if (confirm("¿Estás seguro de que deseas eliminar TODAS las palabras de intercambio?")) {
                window.swapWords = [];
                renderSwapList();
                saveSwapWordsToStorage();
                if (typeof window.updateSwapWordsCountLabel === 'function') window.updateSwapWordsCountLabel();
            }
        });
        swapActionButtonsContainer.appendChild(clearSwapBtn);

        swapContainer.appendChild(swapActionButtonsContainer);

        // Render inicial
        renderSwapList();

        parentContainer.appendChild(swapContainer);

        // === LÓGICA DE RENDERIZADO DE LA LISTA (ACTUALIZADA) ===
        function renderSwapList() {
            const searchInput = document.getElementById("searchSwapInput");
            const swapList = swapContainer.querySelector("ul") || (() => {
                const ul = document.createElement("ul");
                ul.id = "swapList";
                ul.style.maxHeight = "120px";
                ul.style.overflowY = "auto";
                ul.style.border = "1px solid #ddd";
                ul.style.padding = "8px";
                ul.style.margin = "0";
                ul.style.background = "#fff";
                ul.style.listStyle = "none";
                swapContainer.appendChild(ul);
                return ul;
            })();

            swapList.innerHTML = "";

            if (!window.swapWords || window.swapWords.length === 0) {
                const li = document.createElement("li");
                li.textContent = "No hay palabras de intercambio definidas.";
                li.style.textAlign = "center";
                li.style.color = "#777";
                swapList.appendChild(li);
                return;
            }

            const searchTerm = searchInput ? searchInput.value.trim().toLowerCase() : "";
            const filteredSwapWords = window.swapWords.filter(item => typeof item.word === 'string' && item.word.toLowerCase().includes(searchTerm));

            filteredSwapWords.forEach((item, index) => {
                const li = document.createElement("li");
                li.style.display = "flex";
                li.style.justifyContent = "space-between";
                li.style.alignItems = "center";
                li.style.padding = "4px 2px";
                li.style.borderBottom = "1px solid #f0f0f0";

                const wordSpan = document.createElement("span");
                const directionIcon = item.direction === "start" ? "←" : "→";
                const directionText = item.direction === "start" ? "Al Inicio" : "Al Final";
                let isLocked = false;
                try {
                    const norm = (item.word || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/\s+/g, ' ').trim().toLowerCase();
                    isLocked = !!(window.fixedSwapNormSet && window.fixedSwapNormSet.has(norm));
                } catch (_) { isLocked = false; }
                wordSpan.innerHTML = `<b>${item.word}</b> <small style="color: #666;">(${directionIcon} ${directionText})</small>${isLocked ? ' 🔒' : ''}`;

                const btnContainer = document.createElement("span");
                btnContainer.style.display = "flex";
                btnContainer.style.gap = "4px";

                // Botón Editar
                const editBtn = document.createElement("button");
                editBtn.innerHTML = "✏️";
                editBtn.title = "Editar";
                editBtn.style.border = "none";
                editBtn.style.background = "transparent";
                editBtn.style.cursor = "pointer";
                editBtn.addEventListener("click", () => {
                    const originalIndex = window.swapWords.findIndex(sw => sw.word === item.word);
                    if (originalIndex > -1) {
                        openSwapWordEditor(window.swapWords[originalIndex], originalIndex);
                    }
                });
                if (isLocked) { editBtn.disabled = true; editBtn.style.opacity = '0.4'; editBtn.style.cursor = 'not-allowed'; }

                // Botón Eliminar
                const deleteBtn = document.createElement("button");
                deleteBtn.innerHTML = "🗑️";
                deleteBtn.title = "Eliminar";
                deleteBtn.style.border = "none";
                deleteBtn.style.background = "transparent";
                deleteBtn.style.cursor = "pointer";
                deleteBtn.addEventListener("click", () => {
                    if (confirm(`¿Eliminar la palabra swap '${item.word}'?`)) {
                        const indexToDelete = window.swapWords.findIndex(sw => sw.word === item.word);
                        if (indexToDelete > -1) {
                            window.swapWords.splice(indexToDelete, 1);
                            saveSwapWordsToStorage();
                            renderSwapList();
                        }
                    }
                });
                if (isLocked) { deleteBtn.disabled = true; deleteBtn.style.opacity = '0.4'; deleteBtn.style.cursor = 'not-allowed'; }

                btnContainer.appendChild(editBtn);
                btnContainer.appendChild(deleteBtn);

                li.appendChild(wordSpan);
                li.appendChild(btnContainer);
                swapList.appendChild(li);
            });
        }
        // Render inicial y listener del selector
        plnCanonicalizeReplacementsBySheet();
        renderReplacementsList(listElement);
        renderSwapList();
        modeSelector.addEventListener("change", () => {
            replacementsContainer.style.display = modeSelector.value === "words" ? "block" : "none";
            swapContainer.style.display = modeSelector.value === "swapStart" ? "block" : "none";
        });
    }
    // Crea el gestor de reemplazos


    // Renderiza la lista de palabras excluidas
    function renderExcludedWordsList(ulElement, filter = "") {
        if (!ulElement) {
            return;
        }
        // Asegurarse de que ulElement es válido
        const currentFilter = filter.toLowerCase();
        ulElement.innerHTML = "";
        // Asegurarse de que excludedWords es un Set
        const wordsToRender = Array.from(excludedWords).filter(word => word.toLowerCase().includes(currentFilter))
            .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
        // Si no hay palabras para renderizar, mostrar mensaje
        if (wordsToRender.length === 0) {
            const li = document.createElement("li");
            li.textContent = "No hay palabras excluidas.";
            li.style.textAlign = "center";
            li.style.color = "#777";
            ulElement.appendChild(li);
            return;
        }

        // ✨ PAGINACIÓN: Calcular índices de página
        const totalWords = wordsToRender.length;
        const totalPages = Math.ceil(totalWords / WORDS_PER_PAGE);

        // Asegurar que la página actual está en rango válido
        if (excludedWordsCurrentPage > totalPages) {
            excludedWordsCurrentPage = totalPages || 1;
        }

        const startIdx = (excludedWordsCurrentPage - 1) * WORDS_PER_PAGE;
        const endIdx = Math.min(startIdx + WORDS_PER_PAGE, totalWords);
        const pageWords = wordsToRender.slice(startIdx, endIdx);

        // Agregar indicador de resultados
        const infoLi = document.createElement("li");
        infoLi.style.textAlign = "center";
        infoLi.style.padding = "8px";
        infoLi.style.backgroundColor = "#f5f5f5";
        infoLi.style.fontWeight = "bold";
        infoLi.style.borderBottom = "2px solid #007bff";
        infoLi.textContent = `Mostrando ${startIdx + 1}-${endIdx} de ${totalWords} palabra${totalWords !== 1 ? 's' : ''}`;
        ulElement.appendChild(infoLi);

        // Renderizar solo palabras de la página actual
        pageWords.forEach(word => {
            const li = document.createElement("li");
            li.style.display = "flex"; // Agregado para alinear texto y botones
            li.style.justifyContent = "space-between"; // Agregado para espacio entre texto y botones
            li.style.alignItems = "center"; // Agregado para centrado vertical
            li.style.padding = "5px";
            li.style.borderBottom = "1px solid #ddd";
            // Span para el texto de la palabra
            const wordSpan = document.createElement("span");
            wordSpan.textContent = word;
            wordSpan.style.flexGrow = "1"; // Permite que el texto ocupe el espacio disponible
            wordSpan.style.marginRight = "10px"; // Espacio entre el texto y los botones
            li.appendChild(wordSpan);
            //Bloque para los botones de edición y eliminación ---
            const btnContainer = document.createElement("span");
            btnContainer.style.display = "flex";
            btnContainer.style.gap = "8px"; // Espacio entre los botones

            // Botón de edición
            const editBtn = document.createElement("button");
            editBtn.innerHTML = "✏️"; // Icono de lápiz
            editBtn.title = "Editar";
            editBtn.style.border = "none";
            editBtn.style.background = "transparent";
            editBtn.style.cursor = "pointer";
            editBtn.style.padding = "2px";
            editBtn.style.fontSize = "14px";
            editBtn.addEventListener("click", async () => {
                const newWord = await showCustomPrompt("Editar palabra:", "✏️ Editar Palabra Excluida", word, "📝");
                if (newWord !== null && newWord.trim() !== word) {
                    const validation = isValidExcludedWord(newWord.trim());
                    if (!validation.valid) {
                        alert(validation.msg);
                        return;
                    }

                    // Eliminar la palabra antigua del Set y Map
                    excludedWords.delete(word);
                    const oldFirstChar = word.charAt(0).toLowerCase();
                    if (excludedWordsMap.has(oldFirstChar)) {
                        excludedWordsMap.get(oldFirstChar).delete(word);
                        if (excludedWordsMap.get(oldFirstChar).size === 0) {
                            excludedWordsMap.delete(oldFirstChar);
                        }
                    }

                    // Añadir la nueva palabra al Set y Map
                    const trimmedNewWord = newWord.trim();
                    excludedWords.add(trimmedNewWord);
                    const newFirstChar = trimmedNewWord.charAt(0).toLowerCase();
                    if (!excludedWordsMap.has(newFirstChar)) {
                        excludedWordsMap.set(newFirstChar, new Set());
                    }
                    excludedWordsMap.get(newFirstChar).add(trimmedNewWord);

                    // ✨ Limpiar cache porque las palabras excluidas cambiaron
                    clearWordLookupCache();

                    renderExcludedWordsList(ulElement, currentFilter);
                    saveExcludedWordsToLocalStorage();
                }
            });
            btnContainer.appendChild(editBtn);

            // Botón de eliminación
            const deleteBtn = document.createElement("button");
            deleteBtn.innerHTML = "🗑️"; // Icono de bote de basura
            deleteBtn.title = "Eliminar";
            deleteBtn.style.border = "none";
            deleteBtn.style.background = "transparent";
            deleteBtn.style.cursor = "pointer";
            deleteBtn.style.padding = "2px";
            deleteBtn.style.fontSize = "14px";
            deleteBtn.addEventListener("click", () => {
                if (confirm(`¿Eliminar la palabra '${word}' de la lista de especiales?`)) {
                    excludedWords.delete(word);
                    const firstChar = word.charAt(0).toLowerCase();
                    if (excludedWordsMap.has(firstChar)) {
                        excludedWordsMap.get(firstChar).delete(word);
                        if (excludedWordsMap.get(firstChar).size === 0) {
                            excludedWordsMap.delete(firstChar);
                        }
                    }

                    // ✨ Limpiar cache porque las palabras excluidas cambiaron
                    clearWordLookupCache();

                    renderExcludedWordsList(ulElement, currentFilter);
                    saveExcludedWordsToLocalStorage();
                }
            });
            btnContainer.appendChild(deleteBtn);
            //--- Fin del contenedor de botones

            li.appendChild(btnContainer);
            ulElement.appendChild(li);
        });

        // ✨ PAGINACIÓN: Agregar controles de paginación
        if (totalPages > 1) {
            const paginationLi = document.createElement("li");
            paginationLi.style.textAlign = "center";
            paginationLi.style.padding = "10px";
            paginationLi.style.backgroundColor = "#f9f9f9";
            paginationLi.style.borderTop = "2px solid #007bff";
            paginationLi.style.display = "flex";
            paginationLi.style.justifyContent = "center";
            paginationLi.style.alignItems = "center";
            paginationLi.style.gap = "10px";

            // Botón Anterior
            const prevBtn = document.createElement("button");
            prevBtn.textContent = "◀ Anterior";
            prevBtn.disabled = excludedWordsCurrentPage === 1;
            prevBtn.style.padding = "6px 12px";
            prevBtn.style.border = "1px solid #007bff";
            prevBtn.style.borderRadius = "4px";
            prevBtn.style.backgroundColor = prevBtn.disabled ? "#e0e0e0" : "#007bff";
            prevBtn.style.color = prevBtn.disabled ? "#999" : "white";
            prevBtn.style.cursor = prevBtn.disabled ? "not-allowed" : "pointer";
            prevBtn.style.fontWeight = "bold";
            prevBtn.addEventListener("click", () => {
                if (excludedWordsCurrentPage > 1) {
                    excludedWordsCurrentPage--;
                    renderExcludedWordsList(ulElement, currentFilter);
                }
            });

            // Indicador de página
            const pageInfo = document.createElement("span");
            pageInfo.textContent = `Página ${excludedWordsCurrentPage} de ${totalPages}`;
            pageInfo.style.fontWeight = "bold";
            pageInfo.style.padding = "0 10px";

            // Botón Siguiente
            const nextBtn = document.createElement("button");
            nextBtn.textContent = "Siguiente ▶";
            nextBtn.disabled = excludedWordsCurrentPage === totalPages;
            nextBtn.style.padding = "6px 12px";
            nextBtn.style.border = "1px solid #007bff";
            nextBtn.style.borderRadius = "4px";
            nextBtn.style.backgroundColor = nextBtn.disabled ? "#e0e0e0" : "#007bff";
            nextBtn.style.color = nextBtn.disabled ? "#999" : "white";
            nextBtn.style.cursor = nextBtn.disabled ? "not-allowed" : "pointer";
            nextBtn.style.fontWeight = "bold";
            nextBtn.addEventListener("click", () => {
                if (excludedWordsCurrentPage < totalPages) {
                    excludedWordsCurrentPage++;
                    renderExcludedWordsList(ulElement, currentFilter);
                }
            });

            paginationLi.appendChild(prevBtn);
            paginationLi.appendChild(pageInfo);
            paginationLi.appendChild(nextBtn);
            ulElement.appendChild(paginationLi);
        }
    }// renderExcludedWordsList



    // Función para renderizar la lista de lugares excluidos
    function renderExcludedPlacesList(ulElement, filter = "") {
        if (!ulElement) return;
        ulElement.innerHTML = "";
        const lowerFilter = filter.toLowerCase();

        // Ahora excludedPlaces es un Map<ID, Name>.
        const placesToRender = Array.from(excludedPlaces.entries()).filter(([placeId, placeName]) =>
            // Filtra por ID o por el nombre guardado
            placeId.toLowerCase().includes(lowerFilter) ||
            (placeName && placeName.toLowerCase().includes(lowerFilter))
        ).sort(([idA, nameA], [idB, nameB]) => {
            // Ordena alfabéticamente por el nombre guardado
            const safeNameA = nameA || '';
            const safeNameB = nameB || '';
            return safeNameA.toLowerCase().localeCompare(safeNameB.toLowerCase());
        });

        if (placesToRender.length === 0) {
            const li = document.createElement("li");
            li.textContent = "No hay lugares excluidos.";
            li.style.textAlign = "center";
            li.style.color = "#777";
            li.style.padding = "5px";
            ulElement.appendChild(li);
            return;
        }

        placesToRender.forEach(([placeId, placeNameSaved]) => { // Ahora recibimos [ID, NombreGuardado]
            const li = document.createElement("li");
            li.style.display = "flex";
            li.style.justifyContent = "space-between";
            li.style.alignItems = "center";
            li.style.padding = "4px 2px";
            li.style.borderBottom = "1px solid #f0f0f0";

            // Muestra el nombre guardado, con un fallback si el nombre guardado está vacío.
            const displayName = placeNameSaved || `ID: ${placeId}`;
            const linkSpan = document.createElement("span");
            linkSpan.style.flexGrow = "1";
            linkSpan.style.marginRight = "10px";
            const link = document.createElement("a");
            link.href = "#";
            link.textContent = displayName; // Muestra el nombre guardado
            link.title = `Abrir lugar en WME (ID: ${placeId})`; // El tooltip sigue mostrando el ID
            link.addEventListener("click", (e) => {
                e.preventDefault();
                // Intenta obtener el lugar del modelo para seleccionarlo y centrarlo
                // Usamos W.model como fallback si wmeSDK.DataModel.Venues.getById no es eficiente aquí o no está diseñado para esta interacción
                const venueObj = W.model.venues.getObjectById(placeId); // <---
                const venueSDKForUse = venueSDKForRender; // Objeto del SDK que pasamos desde processNextPlace

                if (venueObj) {
                    if (W.map && typeof W.map.setCenter === 'function' && venueObj.getOLGeometry && venueObj.getOLGeometry().getCentroid) {
                        W.map.setCenter(venueObj.getOLGeometry().getCentroid(), null, false, 0); // <--- REINTRODUCIMOS W.map.setCenter
                    }
                    if (W.selectionManager && typeof W.selectionManager.select === 'function') {
                        W.selectionManager.select(venueObj); // <--- REINTRODUCIMOS W.selectionManager.select
                    } else if (W.selectionManager && typeof W.selectionManager.setSelectedModels === 'function') {
                        W.selectionManager.setSelectedModels([venueObj]); // Fallback para versiones antiguas
                    }
                }
                else {
                    // Si el lugar no está en el modelo (fuera de vista), avisa y ofrece abrir en nueva pestaña.
                    const confirmOpen = confirm(`Lugar '${displayName}' (ID: ${placeId}) no encontrado en el modelo actual. ¿Deseas abrirlo en una nueva pestaña del editor?`);
                    if (confirmOpen) {
                        const wmeUrl = `https://www.waze.com/editor?env=row&venueId=${placeId}`;
                        window.open(wmeUrl, '_blank');
                    }
                }
            });
            linkSpan.appendChild(link);
            li.appendChild(linkSpan);

            // Botón para eliminar el lugar de la lista de excluidos.
            const deleteBtn = document.createElement("button");
            deleteBtn.innerHTML = "🗑️";
            deleteBtn.title = "Eliminar lugar de la lista de excluidos";
            deleteBtn.style.border = "none";
            deleteBtn.style.background = "transparent";
            deleteBtn.style.cursor = "pointer";
            deleteBtn.style.padding = "2px";
            deleteBtn.style.fontSize = "14px";
            deleteBtn.addEventListener("click", () => {
                // ************************************************************
                // INICIO DE LA MODIFICACIÓN: Modal de confirmación "bonito"
                // ************************************************************
                const confirmModal = document.createElement("div");
                confirmModal.style.position = "fixed";
                confirmModal.style.top = "50%";
                confirmModal.style.left = "50%";
                confirmModal.style.transform = "translate(-50%, -50%)";
                confirmModal.style.background = "#fff";
                confirmModal.style.border = "1px solid #aad";
                confirmModal.style.padding = "28px 32px 20px 32px";
                confirmModal.style.zIndex = "20000"; // Z-INDEX ALTO
                confirmModal.style.boxShadow = "0 4px 24px rgba(0,0,0,0.18)";
                confirmModal.style.fontFamily = "sans-serif";
                confirmModal.style.borderRadius = "10px";
                confirmModal.style.textAlign = "center";
                confirmModal.style.minWidth = "340px";

                // Ícono visual
                const iconElement = document.createElement("div");
                iconElement.innerHTML = "⚠️"; // Ícono de advertencia
                iconElement.style.fontSize = "38px";
                iconElement.style.marginBottom = "10px";
                confirmModal.appendChild(iconElement);

                // Mensaje principal
                const messageTitle = document.createElement("div");
                messageTitle.innerHTML = `<b>¿Eliminar de excluidos "${placeNameSaved}"?</b>`;
                messageTitle.style.fontSize = "20px";
                messageTitle.style.marginBottom = "8px";
                confirmModal.appendChild(messageTitle);

                // Mensaje explicativo
                const explanationDiv = document.createElement("div");
                explanationDiv.textContent = `Este lugar volverá a aparecer en futuras búsquedas del normalizador.`;
                explanationDiv.style.fontSize = "15px";
                explanationDiv.style.color = "#555";
                explanationDiv.style.marginBottom = "18px";
                confirmModal.appendChild(explanationDiv);

                // Botones de confirmación
                const buttonWrapper = document.createElement("div");
                buttonWrapper.style.display = "flex";
                buttonWrapper.style.justifyContent = "center";
                buttonWrapper.style.gap = "18px";

                // Botón Cancelar
                const cancelBtn = document.createElement("button");
                cancelBtn.textContent = "Cancelar";
                cancelBtn.style.padding = "7px 18px";
                cancelBtn.style.background = "#eee";
                cancelBtn.style.border = "none";
                cancelBtn.style.borderRadius = "4px";
                cancelBtn.style.cursor = "pointer";
                cancelBtn.addEventListener("click", () => confirmModal.remove());

                // Botón Confirmar Eliminación
                const confirmDeleteBtn = document.createElement("button");
                confirmDeleteBtn.textContent = "Eliminar";
                confirmDeleteBtn.style.padding = "7px 18px";
                confirmDeleteBtn.style.background = "#d9534f"; // Rojo
                confirmDeleteBtn.style.color = "#fff";
                confirmDeleteBtn.style.border = "none";
                confirmDeleteBtn.style.borderRadius = "4px";
                confirmDeleteBtn.style.cursor = "pointer";
                confirmDeleteBtn.style.fontWeight = "bold";

                confirmDeleteBtn.addEventListener("click", () => {
                    // Aquí va la lógica que antes estaba directamente en el if(confirm)
                    excludedPlaces.delete(placeId); // Sigue eliminando por ID
                    renderExcludedPlacesList(ulElement, filter); // Vuelve a renderizar la lista después de eliminar.
                    saveExcludedPlacesToLocalStorage(); // Guarda los cambios en localStorage.
                    showTemporaryMessage("Lugar eliminado de la lista de excluidos.", 3000, 'success');
                    confirmModal.remove(); // Cerrar el modal después de la acción
                });

                buttonWrapper.appendChild(cancelBtn);
                buttonWrapper.appendChild(confirmDeleteBtn);
                confirmModal.appendChild(buttonWrapper);
                document.body.appendChild(confirmModal); // Añadir el modal al body
                // --- ✅ Lógica de posicionamiento dinámico para el popup de eliminar ✅ ---
                const triggerButton = event.currentTarget;
                if (triggerButton) {
                    const modalRect = confirmModal.getBoundingClientRect();
                    const triggerRect = triggerButton.getBoundingClientRect();
                    let newLeft = triggerRect.right + 5; // 5px a la derecha del botón
                    let newTop = triggerRect.top;

                    // Ajustar si se sale de la pantalla
                    if ((newLeft + modalRect.width) > window.innerWidth) {
                        newLeft = triggerRect.left - modalRect.width - 5;
                    }
                    if (newTop < 0) newTop = 5;

                    confirmModal.style.left = `${newLeft}px`;
                    confirmModal.style.top = `${newTop}px`;
                    confirmModal.style.transform = 'none'; // Anular el centrado
                }
                else {
                    // Fallback al centrado original
                    confirmModal.style.left = "50%";
                    confirmModal.style.top = "50%";
                    confirmModal.style.transform = "translate(-50%, -50%)";
                }
            });
            li.appendChild(deleteBtn);
            ulElement.appendChild(li);
        });
    }// renderExcludedPlacesList

    // Crea un dropdown para seleccionar categorías recomendadas
    function createRecommendedCategoryDropdown(placeId, currentCategoryKey, dynamicCategorySuggestions) {
        const wrapperDiv = document.createElement("div");
        wrapperDiv.style.position = "relative";
        wrapperDiv.style.width = "100%";
        wrapperDiv.style.minWidth = "150px";
        wrapperDiv.style.display = "flex";
        wrapperDiv.style.flexDirection = "column";
        // Parte de sugerencias dinámicas existentes
        const suggestionsWrapper = document.createElement("div"); // Contenedor para sugerencias
        suggestionsWrapper.style.display = "flex";
        suggestionsWrapper.style.flexDirection = "column";
        suggestionsWrapper.style.alignItems = "flex-start";
        suggestionsWrapper.style.gap = "4px";
        // Filtrar y ordenar las sugerencias dinámicas para la presentación
        const filteredSuggestions = dynamicCategorySuggestions.filter(suggestion => suggestion.categoryKey.toUpperCase() !== currentCategoryKey.toUpperCase());
        if (filteredSuggestions.length > 0) { // Solo si hay sugerencias diferentes a la actual
            filteredSuggestions.forEach(suggestion => {
                const suggestionEntry = document.createElement("div");
                suggestionEntry.style.display = "flex";
                suggestionEntry.style.alignItems = "center";
                suggestionEntry.style.gap = "4px";
                suggestionEntry.style.padding = "2px 4px";
                suggestionEntry.style.border = "1px solid #dcdcdc";
                suggestionEntry.style.borderRadius = "3px";
                suggestionEntry.style.backgroundColor = "#eaf7ff"; // Un color distinto para sugerencias
                suggestionEntry.style.cursor = "pointer";
                suggestionEntry.title = `Sugerencia: ${getCategoryDetails(suggestion.categoryKey).description}`;
                //Añadir icono y descripción de la categoría
                const suggestedIconSpan = document.createElement("span");// Icono de la sugerencia
                suggestedIconSpan.textContent = suggestion.icon;
                suggestedIconSpan.style.fontSize = "16px";
                suggestionEntry.appendChild(suggestedIconSpan);
                // Añadir descripción de la categoría
                const suggestedDescSpan = document.createElement("span");
                suggestedDescSpan.textContent = getCategoryDetails(suggestion.categoryKey).description;
                suggestionEntry.appendChild(suggestedDescSpan);
                suggestionEntry.addEventListener("click", async function handler() { // Cambiado a función con nombre 'handler'
                    const placeToUpdate = W.model.venues.getObjectById(placeId);
                    if (!placeToUpdate) {
                        console.error("[WME_PLN] Lugar no encontrado para actualizar categoría.");
                        return;
                    }
                    try {
                        const UpdateObject = require("Waze/Action/UpdateObject");
                        const action = new UpdateObject(placeToUpdate, { categories: [suggestion.categoryKey] });
                        W.model.actionManager.add(action);
                        // Obtener la celda de la categoría original y aplicar un estilo de opacidad
                        const row = document.querySelector(`tr[data-place-id="${placeId}"]`); // Obtener la fila
                        row.dataset.categoryChanged = 'true'; // Marcar fila como modificada
                        // Habilitar el botón de aplicar sugerencia
                        const applyButton = row.querySelector('button[title="Aplicar sugerencia"]');
                        if (applyButton) {
                            applyButton.disabled = false;
                            applyButton.style.opacity = "1";
                        }
                        //Actualizar visualmente la celda de Categoría Actual en la tabla
                        updateCategoryDisplayInTable(placeId, suggestion.categoryKey);

                        // ✨ REGENERAR BOTONES DE PREFIJOS (28-nov-2024)
                        // Cuando cambia la categoría, actualizar los botones de palabras clave
                        if (row) {
                            // Buscar la celda de sugerencias que tiene la función guardada
                            const suggestionCells = row.querySelectorAll('td');
                            for (const cell of suggestionCells) {
                                if (cell._generatePrefixButtons && typeof cell._generatePrefixButtons === 'function') {
                                    cell._generatePrefixButtons(suggestion.categoryKey);
                                    break;
                                }
                            }
                        }

                        // Asegurarse de que la fila existe antes de intentar acceder a sus celdas
                        if (row) {
                            const originalCategoryCell = row.querySelector('td:nth-child(10)'); // La décima columna es "Categoría"
                            if (originalCategoryCell) {
                                originalCategoryCell.style.opacity = '0.5'; // Atenuar la celda completa
                                originalCategoryCell.title += ' (Modificada)'; // Opcional, añadir un tooltip
                            }
                        }
                        // : Mostrar chulito verde en la sugerencia misma
                        const successIcon = document.createElement("span");
                        successIcon.textContent = " ✅";
                        successIcon.style.marginLeft = "5px";
                        suggestionEntry.appendChild(successIcon); // Añadir el chulito a la entrada de la sugerencia
                        suggestionEntry.style.cursor = "default"; // Deshabilitar clic posterior

                        suggestionEntry.removeEventListener("click", handler); // Deshabilita el listener una vez que se ha hecho clic
                        suggestionEntry.style.opacity = "0.7"; // Opcional: Atenúa la sugerencia para indicar que ya se usó

                        optionsListDiv.style.display = "none"; // Ocultar lista
                        searchInput.blur(); // Quitar el foco
                        // : Eliminar la selección temporal para la categoría, ya se guardó
                        tempSelectedCategories.delete(placeId); // Si esta categoría se guardó directamente
                    }
                    catch (e) {
                        //console.error("[WME_PLN] Error al actualizar la categoría desde sugerencia:", e);
                        alert("Error al actualizar la categoría: " + e.message); // Mantener alerta para errores
                    }
                });
                suggestionsWrapper.appendChild(suggestionEntry);
            });
            wrapperDiv.appendChild(suggestionsWrapper); // Añadir contenedor de sugerencias
        }// createRecommendedCategoryDropdown
        //Fin de parte de sugerencias dinámicas
        // Input para buscar
        const searchInput = document.createElement("input");
        searchInput.type = "text";
        searchInput.placeholder = "Buscar o Seleccionar Categoría";// Placeholder más descriptivo
        searchInput.style.width = "calc(100% - 10px)";
        searchInput.style.padding = "5px";
        searchInput.style.marginTop = "5px"; //  Espacio después de sugerencias
        searchInput.style.marginBottom = "5px";
        searchInput.style.border = "1px solid #ccc";
        searchInput.style.borderRadius = "3px";
        searchInput.setAttribute('spellcheck', 'false');// Evitar corrección ortográfica
        searchInput.readOnly = false;// Permitir escribir pero no editar directamente
        searchInput.style.cursor = 'auto';// Permitir escribir pero no editar directamente
        searchInput.style.opacity = '1.0'; // Opacidad normal para el input
        wrapperDiv.appendChild(searchInput); // Añadir el input al wrapper
        // Div que actuará como la lista desplegable de opciones
        const optionsListDiv = document.createElement("div");
        optionsListDiv.style.position = "absolute";
        // Ajuste de top para que aparezca debajo del input, incluso con sugerencias
        optionsListDiv.style.top = "calc(100% + 5px)"; // Se ajusta dinámicamente o se puede hacer con position: relative dentro de un contenedor fijo.
        optionsListDiv.style.left = "0";
        optionsListDiv.style.width = "calc(100% - 2px)";
        optionsListDiv.style.maxHeight = "200px";
        optionsListDiv.style.overflowY = "auto";
        optionsListDiv.style.border = "1px solid #ddd";
        optionsListDiv.style.backgroundColor = "#fff";
        optionsListDiv.style.zIndex = "1001";
        optionsListDiv.style.display = "none";
        optionsListDiv.style.borderRadius = "3px";
        optionsListDiv.style.boxShadow = "0 2px 5px rgba(0,0,0,0.2)";
        wrapperDiv.appendChild(optionsListDiv);

        // --- Populate options list ---
        function populateOptions(filterText = "") {
            optionsListDiv.innerHTML = ""; // Clear existing options
            const lowerFilterText = filterText.toLowerCase(); // Normalize filter text for case-insensitive search
            // Sort rules alphabetically by their Spanish description for display
            const sortedRules = [...window.dynamicCategoryRules].sort((a, b) => {
                const descA = (getWazeLanguage() === 'es' && a.desc_es) ? a.desc_es : a.desc_en;
                const descB = (getWazeLanguage() === 'es' && b.desc_es) ? b.desc_es : b.desc_en;
                return descA.localeCompare(descB);
            });
            sortedRules.forEach(rule => {// Iterate through each rule
                const displayDesc = (getWazeLanguage() === 'es' && rule.desc_es) ? rule.desc_es : rule.desc_en;
                if (filterText === "" || displayDesc.toLowerCase().includes(lowerFilterText) ||
                    rule.categoryKey.toLowerCase().includes(lowerFilterText)) {// Check if displayDesc or categoryKey contains the filter text
                    const optionDiv = document.createElement("div");
                    optionDiv.style.padding = "5px";
                    optionDiv.style.cursor = "pointer";
                    optionDiv.style.borderBottom = "1px solid #eee";
                    optionDiv.style.display = "flex";
                    optionDiv.style.alignItems = "center";
                    optionDiv.style.gap = "5px";
                    optionDiv.title = `Seleccionar: ${displayDesc} (${rule.categoryKey})`;
                    // Resaltar si es la categoría actual o la temporalmente seleccionada
                    const tempSelectedKey = tempSelectedCategories.get(placeId); // Obtener selección temporal
                    if (rule.categoryKey.toUpperCase() === currentCategoryKey.toUpperCase()) {// Resaltar la categoría actual
                        optionDiv.style.backgroundColor = "#e0f7fa"; // Azul claro para la actual
                        optionDiv.style.fontWeight = "bold";
                    }
                    else if (tempSelectedKey && rule.categoryKey.toUpperCase() === tempSelectedKey.toUpperCase())  // Resaltar selección temporal
                        optionDiv.style.backgroundColor = "#fffacd"; // Amarillo claro para la seleccionada temporalmente
                    else if (dynamicCategorySuggestions.some(s => s.categoryKey.toUpperCase() === rule.categoryKey.toUpperCase()))
                        optionDiv.style.backgroundColor = "#e6ffe6"; // Verde claro para sugerida por el sistema
                    const iconSpan = document.createElement("span");// Icono de la categoría
                    iconSpan.textContent = rule.icon;
                    iconSpan.style.fontSize = "16px";
                    optionDiv.appendChild(iconSpan);
                    const textSpan = document.createElement("span");// Descripción de la categoría
                    textSpan.textContent = displayDesc;
                    optionDiv.appendChild(textSpan);// Añadir descripción de la categoría
                    optionDiv.addEventListener("mouseenter", () => optionDiv.style.backgroundColor = "#f0f0f0");
                    optionDiv.addEventListener("mouseleave", () => {
                        if (tempSelectedKey && rule.categoryKey.toUpperCase() === tempSelectedKey.toUpperCase()) {
                            optionDiv.style.backgroundColor = "#fffacd";
                        }
                        else if (rule.categoryKey.toUpperCase() === currentCategoryKey.toUpperCase()) {
                            optionDiv.style.backgroundColor = "#e0f7fa";
                        }
                        else if (dynamicCategorySuggestions.some(s => s.categoryKey.toUpperCase() === rule.categoryKey.toUpperCase())) {
                            optionDiv.style.backgroundColor = "#e6ffe6";
                        }
                        else {
                            optionDiv.style.backgroundColor = "#fff";
                        }
                    });
                    // Añadir evento click para seleccionar la categoría
                    optionDiv.addEventListener("click", async () => {
                        const placeToUpdate = W.model.venues.getObjectById(placeId);
                        if (!placeToUpdate) {
                            //console.error("[WME_PLN] Lugar no encontrado para actualizar categoría.");
                            return;
                        }

                        try {
                            const UpdateObject = require("Waze/Action/UpdateObject");
                            const action = new UpdateObject(placeToUpdate, { categories: [rule.categoryKey] });
                            W.model.actionManager.add(action);

                            // ✅ CORRECCIÓN: Se declara 'row' aquí, ANTES de su primer uso.
                            const row = document.querySelector(`tr[data-place-id="${placeId}"]`);

                            // Ahora es seguro usar la variable 'row'.
                            if (row) {
                                row.dataset.categoryChanged = 'true'; // Marcar fila como modificada
                                const applyButton = row.querySelector('button[title="Aplicar sugerencia"]');
                                // Habilitar el botón de aplicar sugerencia
                                if (applyButton) {
                                    applyButton.disabled = false;
                                    applyButton.style.opacity = "1";
                                }
                            }

                            // Actualizar visualmente la celda de Categoría Actual en la tabla
                            updateCategoryDisplayInTable(placeId, rule.categoryKey);

                            // ✨ REGENERAR BOTONES DE PREFIJOS (28-nov-2024)
                            // Cuando cambia la categoría, actualizar los botones de palabras clave
                            if (row) {
                                const suggestionCells = row.querySelectorAll('td');
                                for (const cell of suggestionCells) {
                                    if (cell._generatePrefixButtons && typeof cell._generatePrefixButtons === 'function') {
                                        cell._generatePrefixButtons(rule.categoryKey);
                                        break;
                                    }
                                }
                            }

                            // Atenuar la celda de la categoría original
                            if (row) {
                                const categoryCell = row.querySelector('td:nth-child(10)');
                                if (categoryCell) {
                                    const currentCategoryDiv = categoryCell.querySelector('div');
                                    if (currentCategoryDiv) {
                                        currentCategoryDiv.style.opacity = '0.5';
                                        currentCategoryDiv.title += ' (Modificada)';
                                    }
                                }
                            }

                            // Actualizar el valor del input con icono y descripción de la selección
                            searchInput.value = `${rule.icon} ${displayDesc}`;
                            searchInput.style.setProperty('opacity', '1.0', 'important'); // Usar setProperty para asegurar visibilidad

                            // Ocultar la lista de opciones
                            optionsListDiv.style.display = "none";
                            searchInput.blur();

                        } catch (e) {
                            console.error("[WME_PLN] Error al actualizar la categoría desde dropdown:", e);
                            alert("Error al actualizar la categoría: " + e.message);
                        }
                    });
                    optionsListDiv.appendChild(optionDiv);
                }
            });
            if (optionsListDiv.childElementCount === 0) {// Si no hay opciones que coincidan con el filtro, mostrar mensaje
                const noResults = document.createElement("div");
                noResults.style.padding = "5px";
                noResults.style.color = "#777";
                noResults.textContent = "No hay resultados.";
                optionsListDiv.appendChild(noResults);
            }
        }// populateOptions

        // Limpiamos los listeners anteriores y los reescribimos de forma más robusta.

        let debounceTimer;
        searchInput.addEventListener("input", () => {
            clearTimeout(debounceTimer);
            // Muestra la lista y filtra mientras el usuario escribe.
            debounceTimer = setTimeout(() => {
                populateOptions(searchInput.value);
                optionsListDiv.style.display = "block";
            }, 200);
        });

        searchInput.addEventListener("focus", () => {
            // Al hacer foco, muestra la lista completa.
            populateOptions(searchInput.value);
            optionsListDiv.style.display = "block";
        });

        // Usamos 'mousedown' en lugar de 'click' para cerrar el menú.
        // Esto evita conflictos con el evento 'click' de las opciones.
        document.addEventListener("mousedown", (e) => {
            if (!wrapperDiv.contains(e.target)) {
                optionsListDiv.style.display = "none";
            }
        });
        populateOptions(""); // Cargar las opciones inicialmente (sin filtro)
        return wrapperDiv;
    }// createRecommendedCategoryDropdown

    // Función auxiliar para actualizar el display de la categoría actual en la tabla
    function updateCategoryDisplayInTable(placeId, newCategoryKey) {
        const row = document.querySelector(`tr[data-place-id="${placeId}"]`); // Asume que cada fila tiene un data-place-id
        if (!row) return;
        const categoryCell = row.querySelector('td:nth-child(8)'); // La décima columna es "Categoría"

        if (!categoryCell) return;// Asegurarse de que la celda existe
        const categoryDetails = getCategoryDetails(newCategoryKey); // Obtener detalles de la categoría
        const currentCategoryDiv = categoryCell.querySelector('div'); // Contenedor del texto y el ícono
        if (currentCategoryDiv) {// Actualizar el contenido del div existente
            const firstSpan = currentCategoryDiv.querySelector('span:first-child');
            const lastSpan = currentCategoryDiv.querySelector('span:last-child');

            // Verificar que los spans existan antes de actualizar
            if (firstSpan) {
                firstSpan.textContent = categoryDetails.description; // Actualiza el texto
                firstSpan.title = `Categoría Actual: ${categoryDetails.description}`; // Actualiza el título
            }
            if (lastSpan) {
                lastSpan.textContent = categoryDetails.icon; // Actualiza el ícono
            }
        }
    }

    // Renderizar lista de palabras del diccionario
    function renderDictionaryList(ulElement, filter = "") {
        updateDictionaryWordsCountLabel();

        // Asegurarse de que ulElement es válido
        if (!ulElement || !window.dictionaryWords)
            return;
        // Asegurarse de que ulElement es válido
        const currentFilter = filter.toLowerCase();
        ulElement.innerHTML = "";
        // Asegurarse de que dictionaryWords es un Set
        const wordsToRender =
            Array.from(window.dictionaryWords)
                .filter(word => word.toLowerCase().startsWith(currentFilter))
                .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
        // Si no hay palabras que renderizar, mostrar mensaje
        if (wordsToRender.length === 0) {
            const li = document.createElement("li");
            li.textContent = window.dictionaryWords.size === 0
                ? "El diccionario está vacío."
                : "No hay coincidencias.";
            li.style.textAlign = "center";
            li.style.color = "#777";
            ulElement.appendChild(li);
            // Guardar diccionario también cuando está vacío
            try {
                localStorage.setItem(
                    "dictionaryWordsList",
                    JSON.stringify(Array.from(window.dictionaryWords)));
            }
            catch (e) {
                console.error("[WME PLN] Error guardando el diccionario en localStorage:", e);
            }
            return;
        }

        // ✨ PAGINACIÓN: Calcular índices de página
        const totalWords = wordsToRender.length;
        const totalPages = Math.ceil(totalWords / WORDS_PER_PAGE);

        // Asegurar que la página actual está en rango válido
        if (dictionaryCurrentPage > totalPages) {
            dictionaryCurrentPage = totalPages || 1;
        }

        const startIdx = (dictionaryCurrentPage - 1) * WORDS_PER_PAGE;
        const endIdx = Math.min(startIdx + WORDS_PER_PAGE, totalWords);
        const pageWords = wordsToRender.slice(startIdx, endIdx);

        // Agregar indicador de resultados
        const infoLi = document.createElement("li");
        infoLi.style.textAlign = "center";
        infoLi.style.padding = "8px";
        infoLi.style.backgroundColor = "#f5f5f5";
        infoLi.style.fontWeight = "bold";
        infoLi.style.borderBottom = "2px solid #007bff";
        infoLi.textContent = `Mostrando ${startIdx + 1}-${endIdx} de ${totalWords} palabra${totalWords !== 1 ? 's' : ''}`;
        ulElement.appendChild(infoLi);

        // Renderizar solo palabras de la página actual
        pageWords.forEach(word => {
            const li = document.createElement("li");
            li.style.display = "flex";
            li.style.justifyContent = "space-between";
            li.style.alignItems = "center";
            li.style.padding = "4px 2px";
            li.style.borderBottom = "1px solid #f0f0f0";
            // Span para la palabra
            const wordSpan = document.createElement("span");
            wordSpan.textContent = word;
            wordSpan.style.maxWidth = "calc(100% - 60px)";
            wordSpan.style.overflow = "hidden";
            wordSpan.style.textOverflow = "ellipsis";
            wordSpan.style.whiteSpace = "nowrap";
            wordSpan.title = word;
            li.appendChild(wordSpan);
            // Contenedor para los iconos de acción
            const iconContainer = document.createElement("span");
            iconContainer.style.display = "flex";
            iconContainer.style.gap = "8px";
            //Botón de edición y eliminación
            const editBtn = document.createElement("button");
            editBtn.innerHTML = "✏️";
            editBtn.title = "Editar";
            editBtn.style.border = "none";
            editBtn.style.background = "transparent";
            editBtn.style.cursor = "pointer";
            editBtn.style.padding = "2px";
            editBtn.style.fontSize = "14px";
            editBtn.addEventListener("click", async () => {
                const newWord = await showCustomPrompt("Editar palabra:", "✏️ Editar Palabra del Diccionario", word, "📝");
                if (newWord !== null && newWord.trim() !== word) {
                    window.dictionaryWords.delete(word);
                    window.dictionaryWords.add(newWord.trim());

                    // ✨ MARCAR la nueva palabra como añadida por el usuario (28-nov-2024)
                    if (!window.userAddedDictionaryWords) window.userAddedDictionaryWords = new Set();
                    window.userAddedDictionaryWords.add(newWord.trim());

                    // Guardar en localStorage
                    try {
                        localStorage.setItem("dictionaryWordsList", JSON.stringify(Array.from(window.dictionaryWords)));
                        localStorage.setItem('userAddedDictionaryWords', JSON.stringify(Array.from(window.userAddedDictionaryWords)));
                    } catch (e) {
                        console.error("[WME PLN] Error guardando diccionario después de editar:", e);
                    }

                    renderDictionaryList(ulElement, currentFilter);
                }
            });
            // Botón de eliminación
            const deleteBtn = document.createElement("button");
            deleteBtn.innerHTML = "🗑️";
            deleteBtn.title = "Eliminar";
            deleteBtn.style.border = "none";
            deleteBtn.style.background = "transparent";
            deleteBtn.style.cursor = "pointer";
            deleteBtn.style.padding = "2px";
            deleteBtn.style.fontSize = "14px";
            deleteBtn.addEventListener("click", () => {
                // Confirmación antes de eliminar
                if (confirm(`¿Eliminar la palabra '${word}' del diccionario?`)) {
                    window.dictionaryWords.delete(word);
                    renderDictionaryList(ulElement, currentFilter);
                }
            });
            iconContainer.appendChild(editBtn);
            iconContainer.appendChild(deleteBtn);
            li.appendChild(iconContainer);
            ulElement.appendChild(li);
        });

        // ✨ PAGINACIÓN: Agregar controles de paginación
        if (totalPages > 1) {
            const paginationLi = document.createElement("li");
            paginationLi.style.textAlign = "center";
            paginationLi.style.padding = "10px";
            paginationLi.style.backgroundColor = "#f9f9f9";
            paginationLi.style.borderTop = "2px solid #007bff";
            paginationLi.style.display = "flex";
            paginationLi.style.justifyContent = "center";
            paginationLi.style.alignItems = "center";
            paginationLi.style.gap = "10px";

            // Botón Anterior
            const prevBtn = document.createElement("button");
            prevBtn.textContent = "◀ Anterior";
            prevBtn.disabled = dictionaryCurrentPage === 1;
            prevBtn.style.padding = "6px 12px";
            prevBtn.style.border = "1px solid #007bff";
            prevBtn.style.borderRadius = "4px";
            prevBtn.style.backgroundColor = prevBtn.disabled ? "#e0e0e0" : "#007bff";
            prevBtn.style.color = prevBtn.disabled ? "#999" : "white";
            prevBtn.style.cursor = prevBtn.disabled ? "not-allowed" : "pointer";
            prevBtn.style.fontWeight = "bold";
            prevBtn.addEventListener("click", () => {
                if (dictionaryCurrentPage > 1) {
                    dictionaryCurrentPage--;
                    renderDictionaryList(ulElement, currentFilter);
                }
            });

            // Indicador de página
            const pageInfo = document.createElement("span");
            pageInfo.textContent = `Página ${dictionaryCurrentPage} de ${totalPages}`;
            pageInfo.style.fontWeight = "bold";
            pageInfo.style.padding = "0 10px";

            // Botón Siguiente
            const nextBtn = document.createElement("button");
            nextBtn.textContent = "Siguiente ▶";
            nextBtn.disabled = dictionaryCurrentPage === totalPages;
            nextBtn.style.padding = "6px 12px";
            nextBtn.style.border = "1px solid #007bff";
            nextBtn.style.borderRadius = "4px";
            nextBtn.style.backgroundColor = nextBtn.disabled ? "#e0e0e0" : "#007bff";
            nextBtn.style.color = nextBtn.disabled ? "#999" : "white";
            nextBtn.style.cursor = nextBtn.disabled ? "not-allowed" : "pointer";
            nextBtn.style.fontWeight = "bold";
            nextBtn.addEventListener("click", () => {
                if (dictionaryCurrentPage < totalPages) {
                    dictionaryCurrentPage++;
                    renderDictionaryList(ulElement, currentFilter);
                }
            });

            paginationLi.appendChild(prevBtn);
            paginationLi.appendChild(pageInfo);
            paginationLi.appendChild(nextBtn);
            ulElement.appendChild(paginationLi);
        }
        // Guardar el diccionario actualizado en localStorage después de cada render
        try {
            localStorage.setItem("dictionaryWordsList", JSON.stringify(Array.from(window.dictionaryWords)));
        }
        catch (e) {
            console.error("[WME PLN] Error guardando el diccionario en localStorage:", e);
        }
    }// renderDictionaryList


    // Función para obtener el ícono de categoría (acepta nombre o clave)
    function getCategoryIcon(categoryName) {


        // Mapa de categorías a íconos con soporte bilingüe
        const categoryIcons = {
            // Comida y Restaurantes / Food & Restaurants
            "FOOD_AND_DRINK": { icon: "🦞🍷", es: "Comida y Bebidas", en: "Food and Drinks" },
            "RESTAURANT": { icon: "🍽️", es: "Restaurante", en: "Restaurant" },
            "FAST_FOOD": { icon: "🍔", es: "Comida rápida", en: "Fast Food" },
            "CAFE": { icon: "☕", es: "Cafetería", en: "Cafe" },
            "BAR": { icon: "🍺", es: "Bar", en: "Bar" },
            "BAKERY": { icon: "🥖", es: "Panadería", en: "Bakery" },
            "ICE_CREAM": { icon: "🍦", es: "Heladería", en: "Ice Cream Shop" },
            "DEPARTMENT_STORE": { icon: "🏬", es: "Tienda por departamentos", en: "Department Store" },
            "PARK": { icon: "🌳", es: "Parque", en: "Park" },
            // Compras y Servicios / Shopping & Services
            "FASHION_AND_CLOTHING": { icon: "👗", es: "Moda y Ropa", en: "Fashion and Clothing" },
            "SHOPPING_AND_SERVICES": { icon: "👜👝", es: "Mercado o Tienda", en: "Shopping and Services" },
            "SHOPPING_CENTER": { icon: "🛍️", es: "Centro comercial", en: "Shopping Center" },
            "SUPERMARKET_GROCERY": { icon: "🛒", es: "Supermercado", en: "Supermarket" },
            "MARKET": { icon: "🛒", es: "Mercado", en: "Market" },
            "CONVENIENCE_STORE": { icon: "🏪", es: "Tienda", en: "Convenience Store" },
            "PHARMACY": { icon: "💊", es: "Farmacia", en: "Pharmacy" },
            "BANK": { icon: "🏦", es: "Banco", en: "Bank" },
            "ATM": { icon: "💳", es: "Cajero automático", en: "ATM" },
            "HARDWARE_STORE": { icon: "🔧", es: "Ferretería", en: "Hardware Store" },
            "COURTHOUSE": { icon: "⚖️", es: "Corte", en: "Courthouse" },
            "FURNITURE_HOME_STORE": { icon: "🛋️", es: "Tienda de muebles", en: "Furniture Store" },
            "TOURIST_ATTRACTION_HISTORIC_SITE": { icon: "🗿", es: "Atracción turística o Sitio histórico", en: "Tourist Attraction or Historic Site" },
            "PET_STORE_VETERINARIAN_SERVICES": { icon: "🦮🐈", es: "Tienda de mascotas o Veterinaria", en: "Pet Store or Veterinary Services" },
            "CEMETERY": { icon: "🪦", es: "Cementerio", en: "Cemetery" },
            "KINDERGARDEN": { icon: "🍼", es: "Jardín Infantil", en: "Kindergarten" },
            "JUNCTION_INTERCHANGE": { icon: "🔀", es: "Cruce o Intercambio", en: "Junction or Interchange" },
            "OUTDOORS": { icon: "🏞️", es: "Aire libre", en: "Outdoors" },
            "ORGANIZATION_OR_ASSOCIATION": { icon: "👔", es: "Organización o Asociación", en: "Organization or Association" },
            "TRAVEL_AGENCY": { icon: "🧳", es: "Agencia de viajes", en: "Travel Agency" },
            "BANK_FINANCIAL": { icon: "💰", es: "Banco o Financiera", en: "Bank or Financial Institution" },
            "SPORTING_GOODS": { icon: "🛼🏀🏐", es: "Artículos deportivos", en: "Sporting Goods" },
            "TOY_STORE": { icon: "🧸", es: "Tienda de juguetes", en: "Toy Store" },
            "CURRENCY_EXCHANGE": { icon: "💶💱", es: "Casa de cambio", en: "Currency Exchange" },
            "PHOTOGRAPHY": { icon: "📸", es: "Fotografía", en: "Photography" },
            "DESSERT": { icon: "🍰", es: "Postre", en: "Dessert" },
            "FOOD_COURT": { icon: "🥗", es: "Comedor o Patio de comidas", en: "Food Court" },
            "CANAL": { icon: "〰", es: "Canal", en: "Canal" },
            "JEWELRY": { icon: "💍", es: "Joyería", en: "Jewelry" },
            // Transporte / Transportation
            "TRAIN_STATION": { icon: "🚂", es: "Estación de tren", en: "Train Station" },
            "GAS_STATION": { icon: "⛽", es: "Estación de servicio", en: "Gas Station" },
            "PARKING_LOT": { icon: "🅿️", es: "Estacionamiento", en: "Parking Lot" },
            "BUS_STATION": { icon: "🚍", es: "Terminal de bus", en: "Bus Station" },
            "AIRPORT": { icon: "✈️", es: "Aeropuerto", en: "Airport" },
            "CAR_WASH": { icon: "🚗💦", es: "Lavado de autos", en: "Car Wash" },
            "CAR_RENTAL": { icon: "🚘🛺🛻🚙", es: "Alquiler de Vehículos", en: "Car Rental" },
            "TAXI_STATION": { icon: "🚕", es: "Estación de taxis", en: "Taxi Station" },
            "FOREST_GROVE": { icon: "🌳", es: "Bosque", en: "Forest Grove" },
            "GARAGE_AUTOMOTIVE_SHOP": { icon: "🔧🚗", es: "Taller mecánico", en: "Automotive Garage" },
            "GIFTS": { icon: "🎁", es: "Tienda de regalos", en: "Gift Shop" },
            "TOLL_BOOTH": { icon: "🚧", es: "Peaje", en: "Toll Booth" },
            "CHARGING_STATION": { icon: "🔋", es: "Estación de carga", en: "Charging Station" },
            "CAR_SERVICES": { icon: "🚗🔧", es: "Servicios de automóviles", en: "Car Services" },
            "STADIUM_ARENA": { icon: "🏟️", es: "Estadio o Arena", en: "Stadium or Arena" },
            "CAR_DEALERSHIP": { icon: "🚘🏢", es: "Concesionario de autos", en: "Car Dealership" },
            "FERRY_PIER": { icon: "⛴️", es: "Muelle de ferry", en: "Ferry Pier" },
            "INFORMATION_POINT": { icon: "ℹ️", es: "Punto de información", en: "Information Point" },
            "REST_AREAS": { icon: "🏜", es: "Áreas de descanso", en: "Rest Areas" },
            "MUSIC_VENUE": { icon: "🎶", es: "Lugar de música", en: "Music Venue" },
            "CASINO": { icon: "🎰", es: "Casino", en: "Casino" },
            "CITY_HALL": { icon: "🎩", es: "Ayuntamiento", en: "City Hall" },
            "PERFORMING_ARTS_VENUE": { icon: "🎭", es: "Lugar de artes escénicas", en: "Performing Arts Venue" },
            "TUNNEL": { icon: "🔳", es: "Túnel", en: "Tunnel" },
            "SEAPORT_MARINA_HARBOR": { icon: "⚓", es: "Puerto o Marina", en: "Seaport or Marina" },
            // Alojamiento / Lodging
            "HOTEL": { icon: "🏨", es: "Hotel", en: "Hotel" },
            "HOSTEL": { icon: "🛏️", es: "Hostal", en: "Hostel" },
            "LODGING": { icon: "⛺", es: "Alojamiento", en: "Lodging" },
            "MOTEL": { icon: "🛕", es: "Motel", en: "Motel" },
            "SWIMMING_POOL": { icon: "🏊", es: "Piscina", en: "Swimming Pool" },
            "RIVER_STREAM": { icon: "🌊", es: "Río o Arroyo", en: "River or Stream" },
            "CAMPING_TRAILER_PARK": { icon: "🏕️", es: "Camping o Parque de Trailers", en: "Camping or Trailer Park" },
            "SEA_LAKE_POOL": { icon: "🏖️", es: "Mar, Lago o Piscina", en: "Sea, Lake or Pool" },
            "FARM": { icon: "🚜", es: "Granja", en: "Farm" },
            "NATURAL_FEATURES": { icon: "🌲", es: "Características naturales", en: "Natural Features" },
            // Salud / Healthcare
            "HOSPITAL": { icon: "🏥", es: "Hospital", en: "Hospital" },
            "HOSPITAL_URGENT_CARE": { icon: "🏥🚑", es: "Urgencias", en: "Urgent Care" },
            "DOCTOR_CLINIC": { icon: "🏥⚕️", es: "Clínica", en: "Clinic" },
            "DOCTOR": { icon: "👨‍⚕️", es: "Consultorio médico", en: "Doctor's Office" },
            "VETERINARY": { icon: "🐾", es: "Veterinaria", en: "Veterinary" },
            "PERSONAL_CARE": { icon: "💅💇🦷", es: "Cuidado personal", en: "Personal Care" },
            "FACTORY_INDUSTRIAL": { icon: "🏭", es: "Fábrica o Industrial", en: "Factory or Industrial" },
            "MILITARY": { icon: "🪖", es: "Militar", en: "Military" },
            "LAUNDRY_DRY_CLEAN": { icon: "🧺", es: "Lavandería o Tintorería", en: "Laundry or Dry Clean" },
            "PLAYGROUND": { icon: "🛝", es: "Parque infantil", en: "Playground" },
            "TRASH_AND_RECYCLING_FACILITIES": { icon: "🗑️♻️", es: "Instalaciones de basura y reciclaje", en: "Trash and Recycling Facilities" },
            // Educación / Education
            "UNIVERSITY": { icon: "🎓", es: "Universidad", en: "University" },
            "COLLEGE_UNIVERSITY": { icon: "🏫", es: "Colegio", en: "College" },
            "SCHOOL": { icon: "🎒", es: "Escuela", en: "School" },
            "LIBRARY": { icon: "📖", es: "Biblioteca", en: "Library" },
            "FLOWERS": { icon: "💐", es: "Floristería", en: "Flower Shop" },
            "CONVENTIONS_EVENT_CENTER": { icon: "🎤🥂", es: "Centro de convenciones o eventos", en: "Convention or Event Center" },
            "CLUB": { icon: "♣", es: "Club", en: "Club" },
            "ART_GALLERY": { icon: "🖼️", es: "Galería de arte", en: "Art Gallery" },
            "NATURAL_FEATURES": { icon: "🌄", es: "Características naturales", en: "Natural Features" },
            // Entretenimiento / Entertainment
            "CINEMA": { icon: "🎬", es: "Cine", en: "Cinema" },
            "THEATER": { icon: "🎭", es: "Teatro", en: "Theater" },
            "MUSEUM": { icon: "🖼", es: "Museo", en: "Museum" },
            "CULTURE_AND_ENTERTAINEMENT": { icon: "🎨", es: "Cultura y Entretenimiento", en: "Culture and Entertainment" },
            "STADIUM": { icon: "🏟️", es: "Estadio", en: "Stadium" },
            "GYM": { icon: "💪", es: "Gimnasio", en: "Gym" },
            "GYM_FITNESS": { icon: "🏋️", es: "Gimnasio o Fitness", en: "Gym or Fitness" },
            "GAME_CLUB": { icon: "⚽🏓", es: "Club de juegos", en: "Game Club" },
            "BOOKSTORE": { icon: "📖📚", es: "Librería", en: "Bookstore" },
            "ELECTRONICS": { icon: "📱💻", es: "Electrónica", en: "Electronics" },
            "SPORTS_COURT": { icon: "⚽🏀", es: "Cancha deportiva", en: "Sports Court" },
            "GOLF_COURSE": { icon: "⛳", es: "Campo de golf", en: "Golf Course" },
            "SKI_AREA": { icon: "⛷️", es: "Área de esquí", en: "Ski Area" },
            "RACING_TRACK": { icon: "🛷⛸🏎️", es: "Pista de carreras", en: "Racing Track" },
            // Gobierno y Servicios Públicos / Government & Public Services
            "GOVERNMENT": { icon: "🏛️", es: "Oficina gubernamental", en: "Government Office" },
            "POLICE_STATION": { icon: "👮", es: "Estación de policía", en: "Police Station" },
            "FIRE_STATION": { icon: "🚒", es: "Estación de bomberos", en: "Fire Station" },
            "FIRE_DEPARTMENT": { icon: "🚒", es: "Departamento de bomberos", en: "Fire Department" },
            "POST_OFFICE": { icon: "📫", es: "Correo", en: "Post Office" },
            "TRANSPORTATION": { icon: "🚌", es: "Transporte", en: "Transportation" },
            "PRISON_CORRECTIONAL_FACILITY": { icon: "👁️‍🗨️", es: "Prisión o Centro Correccional", en: "Prison or Correctional Facility" },
            // Religión / Religion
            "RELIGIOUS_CENTER": { icon: "⛪", es: "Iglesia", en: "Church" },
            // Otros / Others
            "RESIDENTIAL": { icon: "🏘️", es: "Residencial", en: "Residential" },
            "RESIDENCE_HOME": { icon: "🏠", es: "Residencia o Hogar", en: "Residence or Home" },
            "OFFICES": { icon: "🏢", es: "Oficina", en: "Office" },
            "FACTORY": { icon: "🏭", es: "Fábrica", en: "Factory" },
            "CONSTRUCTION_SITE": { icon: "🏗️", es: "Construcción", en: "Construction" },
            "MONUMENT": { icon: "🗽", es: "Monumento", en: "Monument" },
            "BRIDGE": { icon: "🌉", es: "Puente", en: "Bridge" },
            "PROFESSIONAL_AND_PUBLIC": { icon: "🗄💼", es: "Profesional y Público", en: "Professional and Public" },
            "OTHER": { icon: "🚪", es: "Otro", en: "Other" },
            "ARTS_AND_CRAFTS": { icon: "🎨", es: "Artes y Manualidades", en: "Arts and Crafts" },
            "COTTAGE_CABIN": { icon: "🏡", es: "Cabaña", en: "Cottage Cabin" },
            "TELECOM": { icon: "📡", es: "Telecomunicaciones", en: "Telecommunications" }
        };
        // Si no hay categoría, devolver ícono por defecto
        if (!categoryName) {
            return { icon: "❓", title: "Sin categoría / No category" };
        }
        // Normalizar el nombre de la categoría
        const normalizedInput = String(categoryName).toLowerCase()
            .normalize("NFD")
            .replace(/[\u0300-\u036f]/g, "")
            .trim();
        //console.log("[WME_PLN][DEBUG] Buscando ícono para categoría:", categoryName);
        //console.log("[WME_PLN][DEBUG] Nombre normalizado:", normalizedInput);
        // 1. Buscar coincidencia exacta por clave interna (ej: "PARK")
        for (const [key, data] of Object.entries(categoryIcons)) {
            if (key.toLowerCase() === normalizedInput) {
                return { icon: data.icon, title: `${data.es} / ${data.en}` };
            }
        }
        // Buscar coincidencia en el mapa de categorías
        for (const [key, data] of Object.entries(categoryIcons)) {
            // Normalizar los nombres en español e inglés para la comparación
            const normalizedES = data.es.toLowerCase()
                .normalize("NFD")
                .replace(/[\u0300-\u036f]/g, "")
                .trim();
            const normalizedEN = data.en.toLowerCase()
                .normalize("NFD")
                .replace(/[\u0300-\u036f]/g, "")
                .trim();
            if (normalizedInput === normalizedES || normalizedInput === normalizedEN) {
                return { icon: data.icon, title: `${data.es} / ${data.en}` };
            }
        }
        // Si no se encuentra coincidencia, devolver ícono por defecto
        //console.log("[WME_PLN][DEBUG] No se encontró coincidencia, usando ícono por defecto");
        return {
            icon: "⚪",
            title: `${categoryName} (Sin coincidencia / No match)`
        };
    }// getCategoryIcon

    // Función para manejar el archivo XML arrastrado
    function exportExcludedWordsList() {
        // Verificar si hay palabras excluidas
        if (excludedWords.size === 0 && Object.keys(replacementWords).length === 0) {
            alert("No hay palabras especiales ni reemplazos para exportar.");
            return;
        }
        // Crear el contenido XML
        let xmlContent = `<?xml version="1.0" encoding="UTF-8"?>\n<ExcludedWords>\n`;
        xmlContent +=
            Array.from(excludedWords)
                .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
                .map(w => `    <word>${xmlEscape(w)}</word>`)
                .join("\n");
        // Añadir reemplazos si existen
        if (Object.keys(replacementWords).length > 0) {
            xmlContent += "\n";
            xmlContent +=
                Object.entries(replacementWords)
                    .map(([from, to]) => `    <replacement from="${xmlEscape(from)}">${xmlEscape(to)}</replacement>`)
                    .join("\n");
        }
        xmlContent += "\n</ExcludedWords>";
        // Crear el Blob y descargarlo
        const blob = new Blob([xmlContent], { type: "application/xml;charset=utf-8" });
        // Crear un enlace temporal para descargar el archivo
        const url = URL.createObjectURL(blob);
        // Crear un elemento <a> para descargar el archivo
        const a = document.createElement("a");
        a.href = url;
        a.download = "wme_excluded_words_export.xml";
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }// exportExcludedWordsList

    // Función para exportar palabras del diccionario a XML
    function exportDictionaryWordsList() {
        // Verificar si hay palabras en el diccionario
        if (window.dictionaryWords.size === 0) {
            alert(
                "La lista de palabras del diccionario está vacía. Nada que exportar.");
            return;
        }
        // Crear el contenido XML
        const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>\n<diccionario>\n${Array.from(window.dictionaryWords)
            .sort((a, b) => a.toLowerCase().localeCompare(
                b.toLowerCase()))                     // Exportar ordenado
            .map(w => `    <word>${xmlEscape(w)}</word>`) // Indentación y escape
            .join("\n")}\n</diccionario>`;
        // Crear el Blob y descargarlo
        const blob = new Blob([xmlContent], { type: "application/xml;charset=utf-8" }); // Añadir charset
        // Crear un enlace temporal para descargar el archivo
        const url = URL.createObjectURL(blob);
        // Crear un elemento <a> para descargar el archivo
        const a = document.createElement("a");
        a.href = url;
        a.download = "wme_dictionary_words_export.xml"; // Nombre más descriptivo
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }// exportDictionaryWordsList

    // Función para exportar datos compartidos a XML
    function xmlEscape(str) {
        return str.replace(/[<>&"']/g, function (match) {
            switch (match) {
                case '<':
                    return '&lt;';
                case '>':
                    return '&gt;';
                case '&':
                    return '&amp;';
                case '"':
                    return '&quot;';
                case "'":
                    return '&apos;';
                default:
                    return match;
            }
        });
    }// xmlEscape
    // Add this near the end of your script init
    window.addEventListener('beforeunload', function () {
        // Cancel any pending requests or cleanup tasks
        pendingRequests = [];

        // Save any unsaved data to localStorage
        if (window.dynamicCategoryRules && window.dynamicCategoryRules.length) {
            try {
                localStorage.setItem("wme_pln_categories_cache", JSON.stringify({
                    data: window.dynamicCategoryRules,
                    timestamp: Date.now()
                }));
            } catch (e) {
                console.warn('[WME PLN] Error saving categories on unload:', e);
            }
        }

        if (window.dictionaryWords && window.dictionaryWords.size) {
            try {
                localStorage.setItem("dictionaryWordsList", JSON.stringify(Array.from(window.dictionaryWords)));
            } catch (e) {
                console.warn('[WME PLN] Error saving dictionary on unload:', e);
            }
        }
    });
    // Función para manejar el archivo XML arrastrado
    waitForSidebarAPI();
    // Iniciar el bucle de procesamiento para el efecto de titilado
    requestAnimationFrame(processingLoop);
    //Llamar a la función para mostrar el changelog
    showChangelogOnUpdate();
    
    // NOTE: showDisclaimerPopup ya se maneja dentro de plnValidateAccess (línea ~9999)
    // No duplicar la llamada aquí para evitar múltiples popups

    // Agregar un observador para detectar cuándo se cierra el panel de resultados
    // Esto ayudará a restablecer los estados correctamente cuando el usuario cierra el panel manualmente
    function setupResultsPanelObserver() {
        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
                    const panel = document.getElementById('wme-place-inspector-panel');
                    if (panel && panel.style.display === 'none') {
                        // El panel se ha cerrado, restablecer los estados
                        isResultsPanelOpen = false;
                        isProcessingActive = false;
                        isNormalizationActive = true;
                        console.log("[WME PLN] Panel cerrado, estados restablecidos");
                    }
                }
            }
        });

        // Observar el panel principal para detectar cuando se cierra
        const panel = document.getElementById('wme-place-inspector-panel');
        if (panel) {
            observer.observe(panel, { attributes: true });
        } else {
            // Si el panel aún no existe, configurar un temporizador para intentar nuevamente
            setTimeout(setupResultsPanelObserver, 1000);
        }
    }

    // Llamar a esta función al inicio
    setTimeout(setupResultsPanelObserver, 1000);

})();



// Función reutilizable para mostrar el spinner de carga
function showLoadingSpinner() {
    const scanSpinner = document.createElement("div");
    scanSpinner.id = "scanSpinnerOverlay";
    scanSpinner.style.position = "fixed";
    scanSpinner.style.top = "0";
    scanSpinner.style.left = "0";
    scanSpinner.style.width = "100%";
    scanSpinner.style.height = "100%";
    scanSpinner.style.background = "rgba(0, 0, 0, 0.5)";
    scanSpinner.style.zIndex = "10000";
    scanSpinner.style.display = "flex";
    scanSpinner.style.justifyContent = "center";
    scanSpinner.style.alignItems = "center";
    // Estilos para centrar el contenido
    const scanContent = document.createElement("div");
    scanContent.style.background = "#fff";
    scanContent.style.padding = "20px";
    scanContent.style.borderRadius = "8px";
    scanContent.style.textAlign = "center";
    // Spinner de carga
    const spinner = document.createElement("div");
    spinner.classList.add("spinner");
    spinner.style.width = "40px";
    spinner.style.height = "40px";
    spinner.style.margin = "0 auto 10px auto";
    // Texto de progreso
    const progressText = document.createElement("div");
    progressText.id = "scanProgressText";
    progressText.textContent = "Analizando lugares: 0%";
    progressText.style.fontSize = "14px";
    progressText.style.color = "#333";
    // Añadir spinner y texto al contenido
    scanContent.appendChild(spinner);
    scanContent.appendChild(progressText);
    scanSpinner.appendChild(scanContent);
    document.body.appendChild(scanSpinner);
    // Añadir estilos de animación al documento si no existen
    if (!document.getElementById('wme-pln-animations')) {
        const style = document.createElement("style");
        style.id = 'wme-pln-animations';
        style.textContent = `
                @keyframes spin {
                    0% { transform: rotate(0deg); }
                    100% { transform: rotate(360deg); }
                }
                @keyframes area-blink {
                    0% { opacity: 1; }
                    50% { opacity: 0.3; }
                    100% { opacity: 1; }
                }
                .area-blink {
                    animation: area-blink 1s infinite;
                }
                .spinner {
                    border: 6px solid #f3f3f3;
                    border-top: 6px solid #3498db;
                    border-radius: 50%;
                    animation: spin 1s linear infinite;
                }
            `;
        document.head.appendChild(style);
    }

    // Añadir estilos para lugares sin nombre
    const emptyNameStyles = document.createElement('style');
    emptyNameStyles.textContent = `
            .pln-empty-name-row {
                background-color: #fff3e0;
            }
            .pln-empty-name-row:hover {
                background-color: #ffe0b2;
            }

            /* Estilos para el placeholder de "Requiere corrección manual" */
            .replacement-input::placeholder {
                color: var(--placeholder-color, #999);
                font-weight: bold;
            }
            .replacement-input::-webkit-input-placeholder {
                color: var(--placeholder-color, #999);
                font-weight: bold;
            }
            .replacement-input::-moz-placeholder {
                color: var(--placeholder-color, #999);
                font-weight: bold;
            }
            .replacement-input:-ms-input-placeholder {
                color: var(--placeholder-color, #999);
                font-weight: bold;
            }
        `;
    document.head.appendChild(emptyNameStyles);
}// showLoadingSpinner



// Función para agregar una palabra al diccionario
function addWordToDictionary(input) {
    const newWord = input.value.trim().toLowerCase();

    if (!newWord) {
        if (window.plnToast) window.plnToast("⚠️ La palabra no puede estar vacía.", 3000);
        return;
    }
    // Validaciones básicas antes de añadir
    if (newWord.length === 1 && !newWord.match(/[a-zA-Z0-9]/)) {
        if (window.plnToast) window.plnToast("⚠️ No se permite agregar un solo carácter que no sea alfanumérico.", 3000);
        return;
    }
    if (window.commonWords && window.commonWords.has(newWord)) {
        if (window.plnToast) window.plnToast("⚠️ Esa palabra es muy común y no debe agregarse al diccionario.", 3000);
        return;
    }
    if (window.excludedWords && window.excludedWords.has(newWord)) {
        if (window.plnToast) window.plnToast("⚠️ Esa palabra ya existe en la lista de especiales (excluidas).", 3000);
        return;
    }
    if (window.dictionaryWords.has(newWord)) {
        if (window.plnToast) window.plnToast("⚠️ La palabra ya existe en el diccionario.", 3000);
        return;
    }
    if (!window.dictionaryWords) window.dictionaryWords = new Set();
    if (!window.dictionaryIndex) window.dictionaryIndex = {};
    window.dictionaryWords.add(newWord); // Añadir al Set

    // ✨ MARCAR como palabra añadida por el usuario (28-nov-2024)
    if (!window.userAddedDictionaryWords) window.userAddedDictionaryWords = new Set();
    window.userAddedDictionaryWords.add(newWord);

    // === AÑADIR AL ÍNDICE ===
    const firstChar = newWord.charAt(0).toLowerCase();
    if (!window.dictionaryIndex[firstChar]) {
        window.dictionaryIndex[firstChar] = [];
    }
    window.dictionaryIndex[firstChar].push(newWord); // Añadir al índice
    input.value = ""; // Limpiar el input

    // ✨ Limpiar cache porque el diccionario cambió
    if (window.clearWordLookupCache) {
        window.clearWordLookupCache();
    }

    // Actualizar UI antigua (si existe)
    const oldList = document.getElementById("dictionaryWordsList");
    if (oldList) renderDictionaryList(oldList);

    // ✨ Actualizar UI Nueva (Diccionario Dinámico)
    const listContainer = document.getElementById("dictionaryContainer");
    if (listContainer && typeof inicializarDiccionarioDinamico === 'function' && typeof prepararDatosDiccionario === 'function') {
        listContainer.innerHTML = "";
        inicializarDiccionarioDinamico("dictionaryContainer", prepararDatosDiccionario());
    }

    if (window.updateDictionaryWordsCountLabel) {
        window.updateDictionaryWordsCountLabel();
    }
    // Guardar en localStorage después de añadir
    try {
        localStorage.setItem("dictionaryWordsList", JSON.stringify(Array.from(window.dictionaryWords)));
        // Guardar también las palabras de usuario
        localStorage.setItem('userAddedDictionaryWords', JSON.stringify(Array.from(window.userAddedDictionaryWords)));
        if (window.plnToast) window.plnToast(`✅ Palabra "${newWord}" agregada al diccionario.`, 2500);
    } catch (e) {
        console.error("Error guardando en localStorage:", e);
        if (window.plnToast) window.plnToast("❌ Error al guardar la palabra.", 3000);
    }

}// addWordToDictionary

// === [PLN] Export city register helper globally ===
try {
    window.plnRegisterWorkCity = plnAddWorkCity;
    if (typeof unsafeWindow !== 'undefined') unsafeWindow.plnRegisterWorkCity = plnAddWorkCity;
} catch (_) { }


// =============================================================================
// IIFE for EasyShortCuts Module
// =============================================================================
(function () {

    // Lógica de restricción de rango e inicialización
    async function checkRankAndInitEasyShortCuts() {
        console.log('[NrmliZer-EasyShortCuts] 🕵️ checkRankAndInitEasyShortCuts iniciado');
        
        // Intentar obtener el rango desde varias fuentes posibles
        let rank = -1;
        let userName = '';

        if (typeof W !== 'undefined' && W.loginManager) {
            userName = W.loginManager.userName || '';
            // Intento 1: W.loginManager.user.rank
            if (W.loginManager.user && W.loginManager.user.rank != null) {
                rank = Number(W.loginManager.user.rank);
            }
            // Intento 2: W.loginManager.user.attributes.rank
            else if (W.loginManager.user && W.loginManager.user.attributes && W.loginManager.user.attributes.rank != null) {
                rank = Number(W.loginManager.user.attributes.rank);
            }
            // Intento 3: W.loginManager.getUser()
            else if (W.loginManager.getUser) {
                const u = W.loginManager.getUser();
                if (u && u.rank != null) rank = Number(u.rank);
                else if (u && u.attributes && u.attributes.rank != null) rank = Number(u.attributes.rank);
            }
        }

        if (rank >= 0) {
            // 🔐 PASO 1: Verificar lista blanca para EasyShortCuts
            let whitelisted = false;
            const accessCtrl = window.PLN_ACCESS_CONTROL || (typeof PLN_ACCESS_CONTROL !== 'undefined' ? PLN_ACCESS_CONTROL : {});
            
            // 🔧 DEBUG: Mostrar config actual
            console.log('[NrmliZer-EasyShortCuts] 📋 Config actual:', {
                easyShortcutsMinLevel: accessCtrl.easyShortcutsMinLevel,
                defaultMinLevel: accessCtrl.defaultMinLevel,
                userRank: rank,
                userLevel: rank + 1,
                userName: userName
            });

            if (userName && accessCtrl.enabled) {
                try {
                    const whitelistResult = await checkWhitelistAccess(userName, 'easyshortcuts');
                    if (whitelistResult.success && whitelistResult.hasAccess && whitelistResult.isWhitelisted) {
                        console.log(`[NrmliZer-EasyShortCuts] ✅ Usuario ${userName} en LISTA BLANCA para EasyShortCuts. Acceso permitido.`);
                        whitelisted = true;
                    }
                } catch (e) {
                    console.warn('[NrmliZer-EasyShortCuts] Error verificando whitelist:', e);
                }
            }

            // 🔐 PASO 2: Verificación de nivel local (si no está en whitelist)
            // Nivel 4 corresponde a rank index 3 (0-based)
            const minRank = accessCtrl.easyShortcutsMinLevel ? (accessCtrl.easyShortcutsMinLevel - 1) : 3;
            
            // 🔧 DEBUG: Mostrar comparación
            console.log(`[NrmliZer-EasyShortCuts] 🔍 Comparación: userRank=${rank} (Nivel ${rank+1}) >= minRank=${minRank} (Nivel ${minRank+1})? ${rank >= minRank}`);

            if (whitelisted || rank >= minRank) {
                if (!whitelisted) console.log(`[NrmliZer-EasyShortCuts] ✅ Usuario Nivel ${rank + 1} detectado. Habilitando EasyShortCuts.`);
                initEasyShortCutsModule();
            } else {
                console.log(`[NrmliZer-EasyShortCuts] ⛔ Usuario Nivel ${rank + 1}. EasyShortCuts requiere Nivel ${minRank + 1}+. Módulo desactivado.`);
            }
        } else {
            // Reintentar si no está listo
            console.log('[NrmliZer-EasyShortCuts] ⏳ Esperando a Waze/User...');
            setTimeout(checkRankAndInitEasyShortCuts, 1000);
        }
    }

    // =====================================================================
    //  MÓDULO EASY SHORTCUTS (Integrado)
    //  Restringido a Nivel 4+
    // =====================================================================

    function initEasyShortCutsModule() {
        console.log('[NrmliZer] Inicializando módulo EasyShortCuts...');
        'use strict';
        // Capa para mostrar el análisis de curvas
        let curveAnalysisLayer = null;

        const MAIN_TAB_ICON_BASE64 = "data:image/png;base64,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";
        /**************************************************************************
        //Nombre: initEasyShortCuts
        //Fecha modificación: 2025-11-15
        //Hora: 11:30
        //Autor: mincho77
        //Entradas: Ninguna
        //Salidas: Ninguna
        //Prerrequisitos si existen: WazeWrap cargado y editor inicializado
        //Descripción: Punto de arranque del script. Inicializa el registro de ShortCuts,
        //             configura el modo híbrido y arranca observadores de menú.
        **************************************************************************/
        function initEasyShortCuts(sdkInitializer)
        { // <-- Ahora acepta el inicializador del SDK
            ShortcutRegistry.init();
            HazardAndPlaceActions.registerAll();
            KeyboardListener.attach();
            MenuDecorator.start();
            ScriptLauncher.start();

            let wmeSDK = null;
            if (sdkInitializer)
            {
                try
                {
                    //
                    wmeSDK = sdkInitializer({ scriptId: 'WMEEasyShortCuts', scriptName: 'WME Easy ShortCuts' });
                    // logInfo('WME SDK inicializado correctamente.');
                }
                catch (e)
                {
                    logWarn('Falló la llamada a getWmeSdk: ' + e.message);
                }
            }
            else
            {
                // Esto no debería pasar con el nuevo bootstrap, pero es una buena comprobación
                logWarn('WME SDK no se pasó a init. El analizador de curvas estará desactivado.');
            }
            // Pasar el SDK al analizador
            CurveAnalyzer.setSDK(wmeSDK); //
            SchoolZoneVisualizer.setSDK(wmeSDK);
            // logInfo('WME Easy ShortCuts inicializado');
        }

        /**************************************************************************
        //Nombre: logInfo
        //Fecha modificación: 2025-11-15
        //Hora: 11:30
        //Autor: mincho77
        //Entradas: msg (string)
        //Salidas: Ninguna
        //Prerrequisitos si existen: Consola disponible
        //Descripción: Wrapper simple para logs informativos del script.
        **************************************************************************/
        function logInfo(msg)
        {
            // Prefijo para facilitar filtrado en la consola
            console.info('[EasyShortCuts] ' + msg);
        }

        /**************************************************************************
        //Nombre: logWarn
        //Fecha modificación: 2025-11-15
        //Hora: 11:30
        //Autor: mincho77
        //Entradas: msg (string)
        //Salidas: Ninguna
        //Prerrequisitos si existen: Consola disponible
        //Descripción: Wrapper simple para logs de advertencia del script.
        **************************************************************************/
        function logWarn(msg)
        {
            console.warn('[EasyShortCuts] ' + msg);
        }//logWarn

        function safeGetElementById(id)
        {
            if (typeof id !== 'string' || !id.trim()) {
                return null;
            }
            return document.getElementById(id);
        }

        function getMenuLabelCandidates(action)
        {
            if (!action)
            {
                return [];
            }
            const values = [];
            function pushCandidate(val)
            {
                if (!val) {
                    return;
                }
                const normalized = ('' + val).trim();
                if (!normalized) {
                    return;
                }
                if (values.indexOf(normalized) === -1) {
                    values.push(normalized);
                }
            }
            if (action.menuLabel) {
                pushCandidate(action.menuLabel);
            }
            if (Array.isArray(action.menuLabels)) {
                action.menuLabels.forEach(pushCandidate);
            }
            if (Array.isArray(action.menuAltLabels)) {
                action.menuAltLabels.forEach(pushCandidate);
            }
            pushCandidate(action.label);
            return values;
        }

        // ---------------------------------------------------------------------
        //  Módulo: Estilos compartidos (modal + launcher propio)
        // ---------------------------------------------------------------------

        const UIStyles = (function () {
            const STYLE_ID = 'esc-shared-styles';

            function ensure() {
                if (safeGetElementById(STYLE_ID)) {
                    return;
                }

                const style = document.createElement('style');
                style.id = STYLE_ID;
                style.textContent = '\
    #esc-shortcuts-modal {\
        position: fixed;\
        top: 0;\
        left: 0;\
        width: 100%;\
        height: 100%;\
        background: rgba(0, 0, 0, 0.4);\
        z-index: 99999;\
        display: flex;\
        align-items: center;\
        justify-content: center;\
    }\
    #esc-shortcuts-modal .esc-modal {\
        background: #fff;\
        border-radius: 10px;\
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);\
        width: 950px;\
        max-height: 85vh;\
        display: flex;\
        flex-direction: column;\
        padding: 18px 24px;\
        font-family: "Rubik", "Open Sans", sans-serif;\
        animation: esc-modal-in 0.2s ease-out;\
    }\
    #esc-shortcuts-modal .esc-modal__header {\
        display: flex;\
        align-items: center;\
        justify-content: space-between;\
        margin-bottom: 8px;\
    }\
    #esc-shortcuts-modal .esc-modal__header h2 {\
        margin: 0;\
        font-size: 20px;\
    }\
    #esc-shortcuts-modal .esc-close-btn {\
        background: transparent;\
        border: none;\
        font-size: 22px;\
        cursor: pointer;\
        color: #666;\
    }\
    #esc-shortcuts-modal .esc-close-btn:hover {\
        color: #000;\
    }\
    #esc-shortcuts-modal .esc-modal__body {\
        overflow: auto;\
        flex: 1;\
        margin-top: 8px;\
    }\
    #esc-shortcuts-modal table {\
        width: 100%;\
        border-collapse: collapse;\
        font-size: 13px;\
    }\
    #esc-shortcuts-modal thead th {\
        position: sticky;\
        top: 0;\
        background: #f7f7f7;\
        padding: 6px;\
        border-bottom: 1px solid #ddd;\
        text-align: left;\
    }\
    #esc-shortcuts-modal tbody td {\
        padding: 6px;\
        border-bottom: 1px solid #f0f0f0;\
        vertical-align: top;\
    }\
    #esc-shortcuts-modal .esc-combo-control {\
        display: flex;\
        gap: 6px;\
        align-items: center;\
    }\
    #esc-shortcuts-modal .esc-state-toggle-btn {\
        border: 1px solid #d1d5db;\
        background: #fff;\
        color: #111;\
        padding: 4px 10px;\
        border-radius: 4px;\
        cursor: pointer;\
        display: inline-flex;\
        align-items: center;\
        gap: 4px;\
        font-size: 12px;\
    }\
    #esc-shortcuts-modal .esc-state-toggle-btn:focus {\
        outline: 2px solid #2563eb;\
        outline-offset: 2px;\
    }\
    #esc-shortcuts-modal .esc-state-toggle-icon {\
        font-size: 18px;\
        line-height: 1;\
    }\
    #esc-shortcuts-modal .esc-state-toggle-label {\
        font-size: 12px;\
        font-weight: 500;\
    }\
    #esc-shortcuts-modal .esc-state-cell-na {\
        color: #9ca3af;\
        font-style: italic;\
        font-size: 12px;\
    }\
    #esc-shortcuts-modal .esc-combo-input {\
        width: 120px; \
        padding: 5px 8px; \
        border-radius: 4px;\
        border: 1px solid #c5c5c5;\
        font-size: 13px;\
    }\
    #esc-shortcuts-modal .esc-combo-input:disabled {\
        background: #f0f0f0;\
        color: #555;\
    }\
    #esc-shortcuts-modal .esc-icon-hint {\
        font-size: 12px;\
        color: #777;\
    }\
    #esc-shortcuts-modal .esc-btn {\
        border: none;\
        border-radius: 4px;\
        padding: 6px 12px;\
        font-size: 13px;\
        cursor: pointer;\
    }\
    #esc-shortcuts-modal .esc-btn-primary {\
        background: #4c89ff;\
        color: #fff;\
    }\
    #esc-shortcuts-modal .esc-btn-secondary {\
        background: #ececec;\
        color: #333;\
    }\
    #esc-shortcuts-modal .esc-btn-ghost {\
        background: transparent;\
        color: #555;\
        border: 1px dashed #bbb;\
    }\
    #esc-shortcuts-modal .esc-btn:disabled {\
        opacity: 0.5;\
        cursor: default;\
    }\
    #esc-shortcuts-modal .esc-modal__footer {\
        margin-top: 12px;\
        display: flex;\
        align-items: center;\
        justify-content: space-between;\
        gap: 12px;\
    }\
    #esc-shortcuts-modal .esc-footer-buttons {\
        display: flex;\
        gap: 8px;\
    }\
    #esc-shortcuts-modal .esc-status {\
        font-size: 12px;\
        color: #666;\
        min-height: 18px;\
    }\
    #esc-shortcuts-modal .esc-status--success {\
        color: #0a7f44;\
    }\
    #esc-shortcuts-modal .esc-status--error {\
        color: #c0392b;\
    }\
    #esc-shortcuts-modal .esc-input-error {\
        border-color: #c0392b !important;\
    }\
    @keyframes esc-modal-in {\
        from {\
            opacity: 0;\
            transform: translateY(10px);\
        }\
        to {\
            opacity: 1;\
            transform: translateY(0);\
        }\
    }\
    #esc-launcher-btn {\
        position: fixed;\
        bottom: 60px;\
        left: 19%;\
        top: auto;\
        width: 44px;\
        height: 44px;\
        z-index: 99990;\
        background: #ffffff;\
        border: 1px solid rgba(60, 64, 67, 0.2);\
        border-radius: 50%;\
        padding: 0;\
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\
        display: flex;\
        align-items: center;\
        justify-content: center;\
        cursor: pointer;\
        font-family: "Rubik", "Open Sans", sans-serif;\
        transition: transform 0.15s ease, box-shadow 0.15s ease;\
    }\
    #esc-launcher-btn:hover {\
        transform: translateY(-1px);\
        box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);\
    }\
    #esc-launcher-btn .esc-launcher__icon {\
        width: 28px;\
        height: 28px;\
        border-radius: 50%;\
        background: #f3f4f6;\
        display: flex;\
        align-items: center;\
        justify-content: center;\
        overflow: hidden;\
    }\
    #esc-launcher-btn .esc-launcher__icon img {\
        width: 20px;\
        height: 20px;\
    }\
    #esc-launcher-btn .esc-launcher__label {\
        display: none;\
    }\
    #esc-launcher-btn::after {\
        content: attr(data-tooltip);\
        position: absolute;\
        top: 50%;\
        transform: translateY(-50%);\
        left: 52px;\
        background: rgba(17, 24, 39, 0.9);\
        color: #fff;\
        padding: 3px 8px;\
        border-radius: 4px;\
        font-size: 11px;\
        opacity: 0;\
        pointer-events: none;\
        transition: opacity 0.15s ease;\
        white-space: nowrap;\
        font-family: "Rubik", "Open Sans", sans-serif;\
    }\
    #esc-launcher-btn:hover::after {\
        opacity: 1;\
    }\
    ';
                document.head.appendChild(style);
            }

            return {
                ensure
            };
        })();


        // ---------------------------------------------------------------------
        //  Módulo: Configuración de Curvas (Datos + Interfaz + Control)
        // ---------------------------------------------------------------------
        const CurveSettings = (function () {
            const STORAGE_KEY = 'esc_curve_settings';

            // Lista COMPLETA de tipos de vía conducibles en Waze
            const roadTypes = {
                3: 'Autopista (Freeway)',
                6: 'Carretera Mayor (Major)',
                7: 'Carretera Menor (Minor)',
                4: 'Rampa',
                2: 'Calle Primaria (Primary)',
                1: 'Calle (Street)',
                8: 'Off-road / No mantenida',
                20: 'Vía de Estacionamiento', // <--- NUEVO
                22: 'Calle Estrecha (Narrow)' // <--- NUEVO
            };

            // Configuración por defecto para todos los tipos
            const defaultSettings = {
                3: { angle: 115, sampleDist: 45 },  // Autopista: mayor distancia por alta velocidad
                6: { angle: 115, sampleDist: 35 },  // Carretera Mayor: distancia estándar
                7: { angle: 115, sampleDist: 35 },  // Carretera Menor: distancia estándar
                4: { angle: 100, sampleDist: 30 },  // Rampa: reducida, curvas más cerradas
                2: { angle: 90, sampleDist: 35 },   // Calle Primaria: distancia estándar
                1: { angle: 90, sampleDist: 30 },   // Calle: reducida, velocidades más bajas
                8: { angle: 120, sampleDist: 20 },  // Off-road: muy reducida, curvas cerradas
                20: { angle: 90, sampleDist: 20 },  // Parking: muy reducida, velocidad muy baja
                22: { angle: 90, sampleDist: 25 }   // Narrow: reducida moderadamente
            };

            let currentSettings = loadSettings();

            function loadSettings() {
                try {
                    const saved = localStorage.getItem(STORAGE_KEY);
                    if (!saved) return JSON.parse(JSON.stringify(defaultSettings));
                    const parsed = JSON.parse(saved);
                    const merged = JSON.parse(JSON.stringify(defaultSettings));
                    // Fusionar guardados con defaults para asegurar que existan todas las llaves
                    Object.keys(merged).forEach(k => {
                        if(parsed[k]) merged[k].angle = parsed[k].angle;
                    });
                    return merged;
                } catch (e) {
                    return JSON.parse(JSON.stringify(defaultSettings));
                }
            }

            function saveSettings(newSettings) {
                currentSettings = newSettings;
                localStorage.setItem(STORAGE_KEY, JSON.stringify(currentSettings));
                logInfo('Configuración de curvas guardada.');
                if (typeof CurveAnalyzer !== 'undefined' && CurveAnalyzer.isActive()) {
                    CurveAnalyzer.refresh();
                }
            }

            function getSettingForType(roadTypeId) {
                // Si el tipo no está en la lista, devolvemos un valor muy alto para que se marque siempre (o nunca, según prefieras)
                // Usamos 180 para que sea evidente si se nos escapó algún tipo raro
                return currentSettings[roadTypeId] || { angle: 180 };
            }

            function showPopup() {
                UIStyles.ensure();

                const overlay = document.createElement('div');
                overlay.className = 'esc-modal-backdrop';
                overlay.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:99999;display:flex;justify-content:center;align-items:center;";
                overlay.addEventListener('click', (e) => { if(e.target === overlay) document.body.removeChild(overlay); });

                const modal = document.createElement('div');
                modal.className = 'esc-modal';
                modal.style.cssText = "background:#fff;padding:20px;border-radius:8px;width:480px;max-width:95%;box-shadow:0 4px 20px rgba(0,0,0,0.3);font-family:'Rubik',sans-serif;display:flex;flex-direction:column;gap:15px;";

                const header = document.createElement('div');
                header.style.cssText = "display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #eee;padding-bottom:15px;";

                const title = document.createElement('h2');
                title.textContent = 'Panel de Curvas';
                title.style.margin = '0';
                title.style.fontSize = '24px';
                title.style.fontWeight = 'bold';

                const btnToggle = document.createElement('button');
                function updateBtnState() {
                    const active = (typeof CurveAnalyzer !== 'undefined' && CurveAnalyzer.isActive());
                    btnToggle.textContent = active ? '🟢 Análisis: ACTIVADO' : '🔴 Análisis: DESACTIVADO';
                    btnToggle.style.cssText = active
                        ? "background:#fff; color:#28a745; border:1px solid #28a745; padding: 5px 15px; font-weight:bold; border-radius:4px; cursor:pointer;"
                        : "background:#fff; color:#dc3545; border:1px solid #dc3545; padding: 5px 15px; font-weight:bold; border-radius:4px; cursor:pointer;";
                }
                btnToggle.onclick = () => {
                    if (typeof CurveAnalyzer !== 'undefined') {
                        CurveAnalyzer.toggleCurveAnalysis();
                        updateBtnState();
                    }
                };
                updateBtnState();

                header.appendChild(title);
                header.appendChild(btnToggle);
                modal.appendChild(header);

                const tableContainer = document.createElement('div');
                tableContainer.style.cssText = "max-height:60vh;overflow-y:auto;";

                const table = document.createElement('table');
                table.style.width = '100%';
                table.style.borderCollapse = 'collapse';

                const thead = document.createElement('thead');
                thead.innerHTML = `
                    <tr style="background:#f8f9fa;text-align:left;font-size:13px;color:#555;">
                        <th style="padding:10px;">Tipo de Vía</th>
                        <th style="padding:10px;">Ángulo Máximo (°)</th>
                    </tr>`;
                table.appendChild(thead);

                const tbody = document.createElement('tbody');
                // Ordenamos para que aparezcan en orden de importancia (Freeway primero)
                const order = [3, 6, 7, 4, 2, 1, 8, 20, 22];

                order.forEach(typeId => {
                    const config = currentSettings[typeId] || { angle: 135 };
                    const label = roadTypes[typeId] || 'Desconocido (' + typeId + ')';

                    const row = document.createElement('tr');
                    row.style.borderBottom = '1px solid #f0f0f0';
                    row.innerHTML = `
                        <td style="padding:10px;">${label}</td>
                        <td style="padding:10px;">
                            <input type="number" id="angle-${typeId}" value="${config.angle}" min="1" max="180" style="width:80px;padding:5px;border:1px solid #ccc;border-radius:4px;"> °
                        </td>
                    `;
                    tbody.appendChild(row);
                });
                table.appendChild(tbody);
                tableContainer.appendChild(table);
                modal.appendChild(tableContainer);

                const footer = document.createElement('div');
                footer.style.cssText = "display:flex;justify-content:flex-end;gap:10px;margin-top:10px;padding-top:10px;border-top:1px solid #eee;";

                const btnSave = document.createElement('button');
                btnSave.textContent = 'Guardar y Aplicar';
                btnSave.className = 'esc-btn esc-btn-primary';
                btnSave.onclick = () => {
                    const newConf = JSON.parse(JSON.stringify(currentSettings));
                    Object.keys(roadTypes).forEach(typeId => {
                        const el = safeGetElementById(`angle-${typeId}`);
                        if (el) {
                             const a = parseInt(el.value) || 180;
                             newConf[typeId] = { angle: a };
                        }
                    });
                    saveSettings(newConf);
                    document.body.removeChild(overlay);
                };

                footer.appendChild(btnSave);
                modal.appendChild(footer);

                overlay.appendChild(modal);
                document.body.appendChild(overlay);
            }

            return {
                get: getSettingForType,
                showConfig: showPopup
            };
        })();//curvesettings

            //**************************************************************************
            //Nombre: getCurveAnalyzerStyle
            //Fecha modificación: 2025-11-19
            //Hora: 17:00
            //Autor: mincho77
            //Entradas: feature (opcional)
            //Salidas: Objeto de estilo para la capa de curvas
            //Prerrequisitos si existen: Librería de mapa cargada
            //Descripción: Define el estilo visual de la capa del CurveAnalyzer
            //**************************************************************************
        function getCurveAnalyzerStyle(feature)
        {
            return {
                color: '#ff0000',
                weight: 4,
                opacity: 0.9
            };
        }

     // ---------------------------------------------------------------------
        //  Módulo: Analizador de Curvas (Rumbo Acumulado - Método Profesional)
        // ---------------------------------------------------------------------
        const CurveAnalyzer = (function () {
            let wmeSDK = null;
            let isAnalyerActive = false;
            const LAYER_NAME = 'ESC_Curve_Analysis_Layer';
            let layerExists = false;

            function projectLatLonToMercator(lon, lat) {
                const rMajor = 6378137;
                const x = rMajor * (lon * Math.PI / 180);
                const y = rMajor * Math.log(Math.tan((Math.PI / 4) + (lat * Math.PI / 360)));
                return { x: x, y: y };
            }

            // Calcula el rumbo (azimuth) en grados (0-360) entre dos puntos
            function getBearing(p1, p2) {
                const dx = p2.x - p1.x;
                const dy = p2.y - p1.y;
                let theta = Math.atan2(dx, dy); // Usamos dx, dy para que 0 sea Norte
                let bearing = theta * (180 / Math.PI);
                if (bearing < 0) bearing += 360;
                return bearing;
            }

            // Calcula la diferencia angular absoluta entre dos rumbos (0-180)
            function getAngleDiff(b1, b2) {
                let diff = Math.abs(b1 - b2);
                if (diff > 180) diff = 360 - diff;
                return diff;
            }

            function getDistance(p1, p2) {
                return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
            }

            // Encuentra un punto a cierta distancia (aprox) hacia atrás o adelante en la línea
            function findPointAtDistance(points, startIndex, targetDist, direction) {
                let currentDist = 0;
                let i = startIndex;

                while (currentDist < targetDist) {
                    let nextI = i + direction;
                    if (nextI < 0 || nextI >= points.length) return points[i]; // Fin de línea

                    currentDist += getDistance(points[i], points[nextI]);
                    i = nextI;
                }
                return points[i];
            }

         //**************************************************************************
    //Nombre: ensureLayer
    //Fecha modificación: 2025-11-19
    //Hora: 17:10
    //Autor: mincho77
    //Entradas: Ninguna
    //Salidas: Ninguna
    //Prerrequisitos si existen: wmeSDK.Map disponible
    //Descripción: Crea (una sola vez) la capa de análisis de curvas en el mapa.
    //**************************************************************************
    async function ensureLayer() {
        // Si ya se creó antes, no hacemos nada
        if (layerExists) {
            return;
        }

        // Sin SDK no hay capa
        if (!wmeSDK || !wmeSDK.Map || !wmeSDK.Map.addLayer) {
            logWarn('CurveAnalyzer: SDK de mapa no disponible; no se puede crear la capa.');
            return;
        }

            try {
            const layerConfig = {
                layerName: LAYER_NAME,
                title: 'Curve Analyzer',
                type: 'feature',
                visible: isAnalyerActive, // o true si quieres que siempre se vea
                zIndex: 310
            };

            await wmeSDK.Map.addLayer(layerConfig);
            layerExists = true;
            logInfo('CurveAnalyzer: capa creada: ' + LAYER_NAME);
        } catch (e) {
            logWarn('CurveAnalyzer: error creando capa: ' + e.message);
        }
    }
           //**************************************************************************
    //Nombre: analyzeAndDrawCurves
    //Fecha modificación: 2025-11-19
    //Hora: 17:25
    //Autor: mincho77
    //Entradas: Ninguna
    //Salidas: Dibuja features de puntos peligrosos en la capa de curvas
    //Prerrequisitos si existen: wmeSDK inicializado, capa creada por ensureLayer
    //Descripción: Analiza todos los segmentos visibles y marca puntos con
    //             ángulo interno menor al umbral configurado por tipo de vía.
    //**************************************************************************
    async function analyzeAndDrawCurves() {
        if (!wmeSDK) return;

        // Asegura que la capa exista (usa wmeSDK.Map.addLayer)
        await ensureLayer();
        if (!layerExists) return;

        // Limpia features anteriores
        try {
            await wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: LAYER_NAME });
        } catch (e) {
            logWarn('CurveAnalyzer: error limpiando capa: ' + e.message);
        }

        logInfo('--- ANÁLISIS DE RUMBO (HEADING) ---');

        let segments = [];
        try {
            segments = await wmeSDK.DataModel.Segments.getAll();
        } catch (e) {
            logWarn('CurveAnalyzer: error obteniendo segmentos: ' + e.message);
            return;
        }

        let segmentsFound = 0;
        const featuresToDraw = [];

        for (const segment of segments) {
            // 1) Geometría
            let coordinates = null;
            if (typeof segment.getOLGeometry === 'function') {
                const olGeometry = segment.getOLGeometry();
                if (!olGeometry || olGeometry.getType() !== 'LineString') continue;
                coordinates = olGeometry.getCoordinates();
            } else if (!segment.getOLGeometry && segment.geometry && segment.geometry.type === 'LineString') {
                coordinates = segment.geometry.coordinates;
            } else {
                continue; // sin línea, sin fiesta
            }

            if (!coordinates || coordinates.length < 3) continue;

            const points = coordinates.map(c => projectLatLonToMercator(c[0], c[1]));

            // 2) Tipo de vía
            let roadType = null;
            if (segment.attributes && typeof segment.attributes.roadType !== 'undefined') {
                roadType = segment.attributes.roadType;
            } else if (typeof segment.roadType !== 'undefined') {
                roadType = segment.roadType;
            }
            if (roadType === null) roadType = 99;
            if (roadType === 17) continue; // tipo 17: ignorado
            if (roadType === 18) continue; // tipo 18: vía privada (ignorar giros peligrosos)
            if (roadType === 20) continue; // tipo 20: parking (ignorar giros peligrosos)

            // 3) Config de curvas
            const config = CurveSettings.get(roadType);
            const SAMPLEDIST = config.sampleDist || 35;
            const thresholdAngle = config.angle || 140;

            // 3.5) Verificar si el segmento es de un solo sentido y su dirección
            let isReverseOnly = false;

            let fwd = null;
            let rev = null;

            // Intentar leer de attributes
            if (segment.attributes) {
                if (segment.attributes.fwdDirection !== undefined) fwd = segment.attributes.fwdDirection;
                if (segment.attributes.revDirection !== undefined) rev = segment.attributes.revDirection;
            }

            // Fallback: leer directo del objeto
            if (fwd === null && segment.fwdDirection !== undefined) fwd = segment.fwdDirection;
            if (rev === null && segment.revDirection !== undefined) rev = segment.revDirection;

            if (fwd !== null && rev !== null) {
                // Si solo una dirección está habilitada, es one-way
                isOneWay = (fwd && !rev) || (!fwd && rev);
                // Determinar si el tráfico va solo en dirección reversa
                isReverseOnly = (!fwd && rev);
            }

            // 4) Barrido de puntos intermedios - coleccionamos primero todos los puntos peligrosos
            const dangerousPoints = [];
            for (let i = 1; i < points.length - 1; i++) {
                const current = points[i];
                const prev = findPointAtDistance(points, i, SAMPLEDIST, -1);
                const next = findPointAtDistance(points, i, SAMPLEDIST, 1);
                if (!prev || !next) continue;

                const bearingIn = getBearing(prev, current);
                const bearingOut = getBearing(current, next);
                const turnAngle = getAngleDiff(bearingIn, bearingOut);
                const internalAngle = 180 - turnAngle;

                // Rectas casi perfectas
                if (internalAngle > 175) continue;

                // Marca cada punto "peligroso" según umbral
                if (internalAngle < thresholdAngle) {
                    dangerousPoints.push({
                        index: i,
                        angle: internalAngle,
                        coordinate: coordinates[i]
                    });
                }
            }

            // 5) Agrupar puntos peligrosos en curvas consecutivas
            // Un segmento largo puede tener varias curvas separadas por tramos rectos
            const curves = [];
            let currentCurve = [];

            for (let i = 0; i < dangerousPoints.length; i++) {
                if (currentCurve.length === 0) {
                    // Empezar nueva curva
                    currentCurve.push(dangerousPoints[i]);
                } else {
                    // Verificar si este punto es consecutivo al anterior
                    const lastPoint = currentCurve[currentCurve.length - 1];
                    const gap = dangerousPoints[i].index - lastPoint.index;

                    if (gap <= 5) {  // Si están cerca (máximo 5 puntos de separación), misma curva
                        currentCurve.push(dangerousPoints[i]);
                    } else {
                        // Hay un gap grande, terminar curva actual y empezar nueva
                        curves.push([...currentCurve]);
                        currentCurve = [dangerousPoints[i]];
                    }
                }
            }

            // No olvidar la última curva
            if (currentCurve.length > 0) {
                curves.push(currentCurve);
            }

            // 6) Para cada curva individual, seleccionar puntos a mostrar
            for (const curve of curves) {
                if (curve.length === 0) continue;

                const indicesToShow = [];

                if (isOneWay) {
                    // Si es de un solo sentido, mostramos solo el punto de inicio de ESTA curva
                    // Si es reverse, el "inicio" para el conductor es el último punto de la curva
                    if (isReverseOnly) {
                        indicesToShow.push(curve.length - 1); // Último punto de la curva (inicio en reverse)
                    } else {
                        indicesToShow.push(0); // Primer punto de la curva (inicio en forward)
                    }
                } else if (curve.length <= 3) {
                    // Si es doble sentido y la curva tiene 3 o menos puntos, mostramos todos
                    indicesToShow.push(...curve.map((_, idx) => idx));
                } else {
                    // Si es doble sentido y la curva tiene más de 3 puntos
                    indicesToShow.push(0); // Primer punto (inicio en forward)
                    const middleIdx = Math.floor(curve.length / 2);
                    indicesToShow.push(middleIdx); // Punto del medio
                    indicesToShow.push(curve.length - 1); // Último punto (inicio en reverse)
                }

                // Creamos las features solo para los puntos seleccionados de ESTA curva
                for (const idx of indicesToShow) {
                    const point = curve[idx];
                    const angleText = point.angle.toFixed(0);

                    const pointFeature = {
                        type: 'Feature',
                        id: 'curve_' + segment.id + '_' + point.index,
                        geometry: {
                            type: 'Point',
                            coordinates: point.coordinate
                        },
                        properties: {
                            angle: angleText
                        },
                        style: {
                            pointRadius: 6,
                            fillColor: '#FF0000',
                            fillOpacity: 0.8,
                            strokeColor: '#FFFFFF',
                            strokeWidth: 2,
                            label: angleText + '\u00B0',
                            fontColor: 'black',
                            fontSize: '14px',
                            fontWeight: 'bold',
                            fontFamily: 'Arial, sans-serif',
                            labelOutlineColor: 'white',
                            labelOutlineWidth: 3,
                            labelAlign: 'cm',
                            labelYOffset: -20
                        }
                    };

                    // Debug fino por si acaso
                    console.log('PUSHING curve feature:', pointFeature);
                    featuresToDraw.push(pointFeature);
                    segmentsFound++;
                }
            }
        }

        // 5) Dibujar en la capa
        if (featuresToDraw.length > 0) {
            //console.log('ADDING features to layer', LAYER_NAME, featuresToDraw);
            await wmeSDK.Map.addFeaturesToLayer({
                layerName: LAYER_NAME,
                features: featuresToDraw
            });
        }

        logInfo('Análisis (Rumbo) completado. ' + segmentsFound + ' puntos.');
    }
            async function clearAnalysis() {
                if (layerExists && wmeSDK) {
                    try { await wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: LAYER_NAME }); } catch (e) {}
                }
            }

            function toggleCurveAnalysis() {
                if (!wmeSDK) return;
                isAnalyerActive = !isAnalyerActive;
                if (isAnalyerActive) {
                    logInfo('Análisis: ACTIVADO');
                    analyzeAndDrawCurves();
                } else {
                    logInfo('Análisis: DESACTIVADO');
                    clearAnalysis();
                }
            }

            function isActive() { return isAnalyerActive; }
            function refresh() { if (isAnalyerActive) analyzeAndDrawCurves(); }
            function setSDK(sdk) { wmeSDK = sdk; }

            return { toggleCurveAnalysis, setSDK, isActive, refresh };
        })();//curveanalyzer

        // ---------------------------------------------------------------------
        //  Módulo: Visualizador de Zonas Escolares (Places)
        // ---------------------------------------------------------------------
        const SchoolZoneVisualizer = (function () {
            let wmeSDK = null;
            let isActive = false;
            const LAYER_GREEN = 'ESC_School_WithZone';
            const LAYER_RED = 'ESC_School_NoZone';
            let layerGreenExists = false;
            let layerRedExists = false;

            // Categorías de Waze que indican zona escolar
            const SCHOOL_CATEGORIES = [
                'SCHOOL', 'COLLEGE_UNIVERSITY', 'KINDERGARDEN', 'PRESCHOOL',
                'HIGH_SCHOOL', 'MIDDLE_SCHOOL', 'ELEMENTARY_SCHOOL'
            ];

            // Función para verificar si hay una zona escolar cerca (permanentHazards)
            function hasSchoolZoneNearby(venueGeometry) {
                try {
                    if (!venueGeometry || typeof W === 'undefined' || !W.model) {
                        return false;
                    }

                    let centerX, centerY;
                    if (venueGeometry.getCentroid) {
                        const centroid = venueGeometry.getCentroid();
                        // Las coordenadas del venue están en EPSG:4326 (grados), necesitamos convertir a EPSG:3857 (metros)
                        const transformed = new OpenLayers.LonLat(centroid.x, centroid.y).transform('EPSG:4326', 'EPSG:3857');
                        centerX = transformed.lon;
                        centerY = transformed.lat;
                    } else if (venueGeometry.coordinates) {
                        if (venueGeometry.type === 'Point') {
                            const transformed = new OpenLayers.LonLat(venueGeometry.coordinates[0], venueGeometry.coordinates[1]).transform('EPSG:4326', 'EPSG:3857');
                            centerX = transformed.lon;
                            centerY = transformed.lat;
                        } else if (venueGeometry.type === 'Polygon') {
                            const coords = venueGeometry.coordinates[0];
                            let sumX = 0, sumY = 0;
                            for (const c of coords) {
                                sumX += c[0];
                                sumY += c[1];
                            }
                            const avgX = sumX / coords.length;
                            const avgY = sumY / coords.length;
                            const transformed = new OpenLayers.LonLat(avgX, avgY).transform('EPSG:4326', 'EPSG:3857');
                            centerX = transformed.lon;
                            centerY = transformed.lat;
                        }
                    }

                    if (!centerX || !centerY) return false;

                    const searchRadius = 100; // 100 metros
                    
                    // DEBUG: Mostrar que hay en W.model una vez
                    if (window.__PLN_DECISION_DEBUG_ON && !window._schoolZoneModelDebug) {
                        window._schoolZoneModelDebug = true;
                        console.log('[SchoolZone DEBUG] Keys de W.model:', Object.keys(W.model));
                        if (W.model.permanentHazards) {
                            console.log('[SchoolZone DEBUG] permanentHazards existe:', W.model.permanentHazards);
                            const hazards = Object.values(W.model.permanentHazards.objects || {});
                            console.log('[SchoolZone DEBUG] Total permanentHazards:', hazards.length);
                            
                            // Agrupar por tipo para ver qué tipos existen
                            const typeGroups = {};
                            for (const h of hazards) {
                                const t = h.attributes?.type;
                                if (!typeGroups[t]) {
                                    typeGroups[t] = [];
                                }
                                typeGroups[t].push(h);
                            }
                            
                            console.log('[SchoolZone DEBUG] Tipos de hazards encontrados:');
                            for (const [type, items] of Object.entries(typeGroups)) {
                                console.log(`  Type ${type}: ${items.length} hazards`);
                                // Mostrar ejemplo del primero
                                if (items[0]) {
                                    console.log(`    Ejemplo:`, items[0].attributes);
                                }
                            }
                        }
                    }

                    // Intentar buscar en permanentHazards
                    if (W.model.permanentHazards && W.model.permanentHazards.objects) {
                        const hazards = Object.values(W.model.permanentHazards.objects);
                        const schoolZoneHazards = hazards.filter(h => h.attributes?.type === 9);
                        
                        if (window.__PLN_DECISION_DEBUG_ON) {
                            console.log(`[SchoolZone DEBUG] Total hazards: ${hazards.length}, Type 9: ${schoolZoneHazards.length}`);
                            
                            if (schoolZoneHazards.length > 0) {
                                console.log('[SchoolZone DEBUG] Primer hazard type 9:', schoolZoneHazards[0]);
                            }
                        }
                        
                        for (const hazard of hazards) {
                            const attrs = hazard.attributes;
                            if (!attrs) continue;
                            
                            // Verificar si es una zona escolar (type = 9 en WME)
                            const hazardType = attrs.type;
                            const isSchoolZone = hazardType === 9;
                            
                            if (!isSchoolZone) continue;

                            const hazGeometry = hazard.getOLGeometry ? hazard.getOLGeometry() : null;
                            if (!hazGeometry) {
                                if (window.__PLN_DECISION_DEBUG_ON) console.log('[SchoolZone DEBUG] Hazard sin geometría:', hazard);
                                continue;
                            }

                            let hazCenterX, hazCenterY;
                            if (hazGeometry.getCentroid) {
                                const hazCenter = hazGeometry.getCentroid();
                                hazCenterX = hazCenter.x;
                                hazCenterY = hazCenter.y;
                                if (window.__PLN_DECISION_DEBUG_ON) console.log('[SchoolZone DEBUG] Hazard centroid:', hazCenterX, hazCenterY);
                            } else {
                                if (window.__PLN_DECISION_DEBUG_ON) console.log('[SchoolZone DEBUG] Hazard geometry sin getCentroid:', hazGeometry);
                                continue;
                            }

                            const distance = Math.sqrt(
                                Math.pow(centerX - hazCenterX, 2) + 
                                Math.pow(centerY - hazCenterY, 2)
                            );
                            
                            if (window.__PLN_DECISION_DEBUG_ON) console.log(`[SchoolZone DEBUG] Distancia al hazard: ${Math.round(distance)}m (limit: ${searchRadius}m)`);

                            if (distance <= searchRadius) {
                                if (window.__PLN_DECISION_DEBUG_ON) console.log('[SchoolZone] ✅ Encontre permanentHazard escolar a', Math.round(distance), 'm');
                                return true;
                            }
                        }
                    }
                    
                    // FALLBACK: Buscar en segmentos con flags
                    if (W.model.segments && W.model.segments.objects) {
                        const segments = Object.values(W.model.segments.objects);
                        
                        for (const segment of segments) {
                            const attrs = segment.attributes;
                            if (!attrs) continue;

                            let hasSchoolZoneFlag = false;
                            
                            // Verificar flags como objetos
                            if (attrs.flags && typeof attrs.flags === 'object') {
                                hasSchoolZoneFlag = attrs.flags.nearSchool === true || attrs.flags.schoolZone === true;
                            }
                            
                            // Verificar fwdFlags/revFlags como objetos
                            if (!hasSchoolZoneFlag && attrs.fwdFlags && typeof attrs.fwdFlags === 'object') {
                                hasSchoolZoneFlag = attrs.fwdFlags.nearSchool === true;
                            }
                            if (!hasSchoolZoneFlag && attrs.revFlags && typeof attrs.revFlags === 'object') {
                                hasSchoolZoneFlag = attrs.revFlags.nearSchool === true;
                            }
                            
                            // Verificar fwdFlags/revFlags como bitmask (número)
                            // Bit 5 (valor 32) = nearSchool
                            if (!hasSchoolZoneFlag && typeof attrs.fwdFlags === 'number') {
                                hasSchoolZoneFlag = (attrs.fwdFlags & 32) !== 0;
                            }
                            if (!hasSchoolZoneFlag && typeof attrs.revFlags === 'number') {
                                hasSchoolZoneFlag = (attrs.revFlags & 32) !== 0;
                            }
                            
                            if (!hasSchoolZoneFlag) continue;

                            const segGeometry = segment.getOLGeometry ? segment.getOLGeometry() : null;
                            if (!segGeometry) continue;

                            let segCenterX, segCenterY;
                            if (segGeometry.getCentroid) {
                                const segCenter = segGeometry.getCentroid();
                                segCenterX = segCenter.x;
                                segCenterY = segCenter.y;
                            } else {
                                continue;
                            }

                            const distance = Math.sqrt(
                                Math.pow(centerX - segCenterX, 2) + 
                                Math.pow(centerY - segCenterY, 2)
                            );

                            if (distance <= searchRadius) {
                                if (window.__PLN_DECISION_DEBUG_ON) console.log('[SchoolZone] ✅ Encontre segmento escolar a', Math.round(distance), 'm');
                                return true;
                            }
                        }
                    }

                    return false;
                } catch (e) {
                    console.warn('[SchoolZone] Error checking nearby school zone:', e);
                    return false;
                }
            }

            async function ensureLayers() {
                if (!wmeSDK) return;
                
                // Habilitar la capa de zonas escolares de WME (para que se vea el highlight naranja)
                try {
                    if (typeof W !== 'undefined' && W.map && W.map.getLayerByName) {
                        const schoolZoneLayer = W.map.getLayerByName('school_zone');
                        if (schoolZoneLayer) {
                            schoolZoneLayer.setVisibility(true);
                            if (window.__PLN_DECISION_DEBUG_ON) console.log('[SchoolZone] Capa WME school_zone habilitada');
                        }
                    }
                } catch (e) {
                    console.warn('[SchoolZone] No se pudo habilitar capa WME:', e.message);
                }
                
                // Capa VERDE para escuelas CON zona escolar
                if (!layerGreenExists) {
                    try {
                        await wmeSDK.Map.addLayer({
                            layerName: LAYER_GREEN,
                            title: 'Escuelas CON Zona',
                            type: 'feature',
                            visible: isActive,
                            zIndex: 321,
                            styleRules: [{
                                style: {
                                    strokeColor: '#00FF00',  // Verde brillante (lime)
                                    strokeOpacity: 1.0,
                                    strokeWidth: 6,
                                    fill: true,
                                    fillColor: '#4CAF50',
                                    fillOpacity: 0.5,
                                    pointRadius: 15,
                                    label: '✅ CON Zona Escolar',
                                    fontColor: '#1B5E20',
                                    labelOutlineColor: '#FFFFFF',
                                    labelOutlineWidth: 3,
                                    fontSize: '12px',
                                    fontWeight: 'bold',
                                    labelYOffset: -22
                                }
                            }]
                        });
                        layerGreenExists = true;
                        if (window.__PLN_DECISION_DEBUG_ON) console.log('[SchoolZone] Capa VERDE creada');
                    } catch (e) {
                        console.warn('[SchoolZone] Error creando capa verde:', e.message);
                    }
                }

                // Capa ROJA para escuelas SIN zona escolar
                if (!layerRedExists) {
                    try {
                        await wmeSDK.Map.addLayer({
                            layerName: LAYER_RED,
                            title: 'Escuelas SIN Zona',
                            type: 'feature',
                            visible: isActive,
                            zIndex: 322,
                            styleRules: [{
                                style: {
                                    strokeColor: '#D32F2F',
                                    strokeOpacity: 1.0,
                                    strokeWidth: 8,
                                    fill: true,
                                    fillColor: '#FFCDD2',
                                    fillOpacity: 0.6,
                                    pointRadius: 15,
                                    label: '⚠️ SIN Zona Escolar',
                                    fontColor: '#B71C1C',
                                    labelOutlineColor: '#FFFFFF',
                                    labelOutlineWidth: 4,
                                    fontSize: '13px',
                                    fontWeight: 'bold',
                                    labelYOffset: -22
                                }
                            }]
                        });
                        layerRedExists = true;
                        if (window.__PLN_DECISION_DEBUG_ON) console.log('[SchoolZone] Capa ROJA creada');
                    } catch (e) {
                        console.warn('[SchoolZone] Error creando capa roja:', e.message);
                    }
                }
            }

            async function highlightSchools() {
                console.log('[DEBUG SchoolZone] 1. Iniciando highlightSchools');
                if (!wmeSDK) {
                    console.error('[DEBUG SchoolZone] ERROR: wmeSDK no esta disponible');
                    return;
                }
                
                await ensureLayers();
                
                // Limpiar marcas anteriores
                try { await wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: LAYER_GREEN }); } catch (e) {}
                try { await wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: LAYER_RED }); } catch (e) {}

                logInfo('--- BUSCANDO INSTITUCIONES EDUCATIVAS ---');

                let venues = [];
                if (wmeSDK.DataModel.Venues && typeof wmeSDK.DataModel.Venues.getAll === 'function') {
                    venues = await wmeSDK.DataModel.Venues.getAll();
                } else if (typeof W !== 'undefined' && W.model && W.model.venues) {
                    venues = W.model.venues.getObjectArray();
                }
                console.log('[DEBUG SchoolZone] Total venues:', venues.length);

                const featuresGreen = [];
                const featuresRed = [];
                let schoolsWithZone = 0;
                let schoolsWithoutZone = 0;

                for (const venue of venues) {
                    const cats = venue.attributes ? venue.attributes.categories : venue.categories;
                    if (!cats || cats.length === 0) continue;

                    const isSchool = cats.some(c => SCHOOL_CATEGORIES.includes((c || '').toUpperCase()));
                    if (!isSchool) continue;

                    const venueName = venue.attributes?.name || venue.name || 'sin nombre';

                    let geometry = null;
                    if (typeof venue.getOLGeometry === 'function') {
                        geometry = venue.getOLGeometry();
                    } else if (venue.geometry) {
                        geometry = venue.geometry;
                    }

                    if (!geometry) continue;

                    const hasZone = hasSchoolZoneNearby(geometry);
                    
                    let featureGeo = null;
                    if (geometry.getType && typeof geometry.getType === 'function') {
                        const type = geometry.getType();
                        const coords = geometry.getCoordinates();
                        if (type === 'Point') {
                            // Para puntos, crear un cuadro de 100x100 metros (50m en cada dirección)
                            const [lon, lat] = coords;
                            // Convertir a Web Mercator para trabajar en metros
                            const transformed = new OpenLayers.LonLat(lon, lat).transform('EPSG:4326', 'EPSG:3857');
                            const centerX = transformed.lon;
                            const centerY = transformed.lat;
                            
                            // Crear cuadro de 100x100m (50m de radio)
                            const radius = 50; // 50 metros
                            const box3857 = [
                                [centerX - radius, centerY - radius],
                                [centerX + radius, centerY - radius],
                                [centerX + radius, centerY + radius],
                                [centerX - radius, centerY + radius],
                                [centerX - radius, centerY - radius] // Cerrar el polígono
                            ];
                            
                            // Convertir de vuelta a EPSG:4326 para la capa
                            const box4326 = box3857.map(coord => {
                                const pt = new OpenLayers.LonLat(coord[0], coord[1]).transform('EPSG:3857', 'EPSG:4326');
                                return [pt.lon, pt.lat];
                            });
                            
                            featureGeo = { type: 'Polygon', coordinates: [box4326] };
                        } else if (type === 'Polygon') {
                            featureGeo = { type: 'Polygon', coordinates: coords };
                        } else if (type === 'MultiPolygon') {
                            featureGeo = { type: 'MultiPolygon', coordinates: coords };
                        }
                    } else {
                        featureGeo = geometry;
                    }

                    if (!featureGeo) continue;

                    const feature = {
                        type: 'Feature',
                        id: 'school_' + (venue.attributes ? venue.attributes.id : venue.id),
                        geometry: featureGeo,
                        properties: { name: venueName }
                    };

                    if (hasZone) {
                        schoolsWithZone++;
                        featuresGreen.push(feature);
                        if (window.__PLN_DECISION_DEBUG_ON) console.log('[SchoolZone] ✅ CON zona:', venueName);
                    } else {
                        schoolsWithoutZone++;
                        featuresRed.push(feature);
                        if (window.__PLN_DECISION_DEBUG_ON) console.log('[SchoolZone] ⚠️ SIN zona:', venueName);
                    }
                }

                console.log(`[SchoolZone] Resumen: ✅ ${schoolsWithZone} con zona | ⚠️ ${schoolsWithoutZone} sin zona`);

                if (featuresGreen.length > 0) {
                    await wmeSDK.Map.addFeaturesToLayer({
                        layerName: LAYER_GREEN,
                        features: featuresGreen
                    });
                }

                if (featuresRed.length > 0) {
                    await wmeSDK.Map.addFeaturesToLayer({
                        layerName: LAYER_RED,
                        features: featuresRed
                    });
                }

                logInfo(`Marcadas ${featuresGreen.length + featuresRed.length} instituciones: ✅ ${schoolsWithZone} con zona | ⚠️ ${schoolsWithoutZone} sin zona`);
            }

            async function clearHighlights() {
                if (wmeSDK) {
                    try { await wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: LAYER_GREEN }); } catch (e) {}
                    try { await wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: LAYER_RED }); } catch (e) {}
                }
            }

            function toggle() {
                console.log('[DEBUG SchoolZone] Toggle. Estado actual:', isActive);
                if (!wmeSDK) {
                    logWarn('Visualizador: SDK no listo.');
                    return;
                }
                isActive = !isActive;
                if (isActive) {
                    logInfo('Visualizador Escuelas: ACTIVADO');
                    highlightSchools();
                } else {
                    logInfo('Visualizador Escuelas: DESACTIVADO');
                    clearHighlights();
                }
            }

            function setSDK(sdk) { wmeSDK = sdk; }

            return {
                toggle,
                setSDK,
                isActive: function() { return isActive; }
            };
        })();



        // ---------------------------------------------------------------------
        //  Módulo: Normalización de teclas y combos
        // ---------------------------------------------------------------------
        const KeyUtils = (function () {
            const ARROW_MAP = {
                ArrowUp: 'UP',
                ArrowDown: 'DOWN',
                ArrowLeft: 'LEFT',
                ArrowRight: 'RIGHT'
            };
            const ARROW_NORMALIZED_MAP = {
                ARROWUP: 'UP',
                ARROWDOWN: 'DOWN',
                ARROWLEFT: 'LEFT',
                ARROWRIGHT: 'RIGHT',
                UP: 'UP',
                DOWN: 'DOWN',
                LEFT: 'LEFT',
                RIGHT: 'RIGHT'
            };
            const SHIFTED_DIGIT_MAP = {
                '!': '1',
                '@': '2',
                '#': '3',
                '$': '4',
                '%': '5',
                '^': '6',
                '&': '7',
                '*': '8',
                '(': '9',
                ')': '0'
            };

            /**************************************************************************
            //Nombre: normalizeComboFromEvent
            //Fecha modificación: 2025-11-15
            //Hora: 11:30
            //Autor: mincho77
            //Entradas: evt (KeyboardEvent)
            //Salidas: string con el combo normalizado o null si no aplica
            //Prerrequisitos si existen: Evento de teclado válido
            //Descripción: Convierte un KeyboardEvent en una cadena tipo
            //             "ALT+SHIFT+Z" o "UP". Ignora eventos repetidos.
            **************************************************************************/
            function normalizeComboFromEvent(evt, options) {
                if (!evt || evt.repeat) {
                    return null;
                }

                // Evitar capturar ShortCuts cuando el usuario está escribiendo
                const opts = options || {};
                const allowEditableTargets = !!opts.allowEditableTargets;
                if (!allowEditableTargets) {
                    const target = evt.target;
                    if (target) {
                        const tagName = (target.tagName || '').toLowerCase();
                        const isEditable = target.isContentEditable;
                        if (tagName === 'input' || tagName === 'textarea' || isEditable) {
                            return null;
                        }
                    }
                }

                const parts = [];
                if (evt.ctrlKey) {
                    parts.push('CTRL');
                }
                if (evt.altKey) {
                    parts.push('ALT');
                }
                if (evt.shiftKey) {
                    parts.push('SHIFT');
                }
                if (evt.metaKey) {
                    parts.push('CMD');
                }

                let key = evt.key;
                if (!key) {
                    return null;
                }

                if (ARROW_MAP[key]) {
                    key = ARROW_MAP[key];
                } else if (key.length === 1) {
                    // Si es un símbolo de número con SHIFT (por ejemplo !, @, #),
                    // lo mapeamos al dígito correspondiente para combos tipo CTRL+SHIFT+1
                    if (evt.shiftKey && SHIFTED_DIGIT_MAP[key]) {
                        key = SHIFTED_DIGIT_MAP[key];
                    } else {
                        key = key.toUpperCase();
                    }
                } else {
                    // Normalizar algunos casos especiales si fueran necesarios
                    key = key.toUpperCase();
                }

                parts.push(key);
                return parts.join('+');
            }

            function normalizeComboString(comboText) {
                if (!comboText) {
                    return '';
                }

                const rawParts = comboText.split('+');
                const modifierFlags = {
                    CTRL: false,
                    ALT: false,
                    SHIFT: false,
                    CMD: false
                };
                let keyPart = '';

                rawParts.forEach(function (part) {
                    const trimmed = part.trim();
                    if (!trimmed) {
                        return;
                    }
                    const upper = trimmed.toUpperCase();
                    if (upper === 'CTRL' || upper === 'CONTROL' || upper === 'STRG') {
                        modifierFlags.CTRL = true;
                        return;
                    }
                    if (upper === 'ALT' || upper === 'OPTION') {
                        modifierFlags.ALT = true;
                        return;
                    }
                    if (upper === 'SHIFT') {
                        modifierFlags.SHIFT = true;
                        return;
                    }
                    if (upper === 'CMD' || upper === 'COMMAND' || upper === 'META') {
                        modifierFlags.CMD = true;
                        return;
                    }

                    if (ARROW_NORMALIZED_MAP[upper]) {
                        keyPart = ARROW_NORMALIZED_MAP[upper];
                        return;
                    }

                    keyPart = upper.length === 1 ? upper : upper;
                });

                const ordered = [];
                if (modifierFlags.CTRL) {
                    ordered.push('CTRL');
                }
                if (modifierFlags.ALT) {
                    ordered.push('ALT');
                }
                if (modifierFlags.SHIFT) {
                    ordered.push('SHIFT');
                }
                if (modifierFlags.CMD) {
                    ordered.push('CMD');
                }
                if (keyPart) {
                    ordered.push(keyPart);
                }

                return ordered.join('+');
            }

            return {
                normalizeComboFromEvent,
                normalizeComboString
            };
        })();

        // ---------------------------------------------------------------------
        //  Módulo: Persistencia de overrides (localStorage simple)
        // ---------------------------------------------------------------------

        const ShortcutsStorage = (function () {
            const STORAGE_KEY = 'wme_easy_shortcuts_user_combos';
            let comboCache = null;

            function ensureLoaded() {
                if (comboCache !== null) {
                    return;
                }

                comboCache = {};
                try {
                    const raw = window.localStorage.getItem(STORAGE_KEY);
                    if (!raw) {
                        return;
                    }
                    const parsed = JSON.parse(raw);
                    if (parsed && typeof parsed === 'object') {
                        comboCache = parsed;
                    }
                } catch (err) {
                    logWarn('ShortcutsStorage: error leyendo overrides -> ' + err.message);
                    comboCache = {};
                }
            }

            function persist() {
                if (!comboCache || Object.keys(comboCache).length === 0) {
                    window.localStorage.removeItem(STORAGE_KEY);
                    return;
                }

                try {
                    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(comboCache));
                } catch (err) {
                    logWarn('ShortcutsStorage: error guardando overrides -> ' + err.message);
                }
            }

            function getOverride(actionId) {
                if (!actionId) {
                    return undefined;
                }
                ensureLoaded();
                if (Object.prototype.hasOwnProperty.call(comboCache, actionId)) {
                    return comboCache[actionId];
                }
                return undefined;
            }

            function setOverride(actionId, combo) {
                if (!actionId) {
                    return;
                }
                ensureLoaded();
                comboCache[actionId] = combo;
                persist();
            }

            function clearOverride(actionId) {
                if (!actionId) {
                    return;
                }
                ensureLoaded();
                if (Object.prototype.hasOwnProperty.call(comboCache, actionId)) {
                    delete comboCache[actionId];
                    persist();
                }
            }

            function resetAll() {
                comboCache = {};
                window.localStorage.removeItem(STORAGE_KEY);
            }

            return {
                getOverride,
                setOverride,
                clearOverride,
                resetAll
            };
        })();

        // ---------------------------------------------------------------------
        //  Módulo: Registro híbrido de ShortCuts
        // ---------------------------------------------------------------------

        const ShortcutRegistry = (function () {
            // Combos nativos reservados (tomados de la ayuda de ShortCuts de WME)
            // Formato: igual que devuelve KeyUtils.normalizeComboFromEvent
            const BUILTIN_RESERVED_COMBOS = new Set([
                // General
                'Z',              // Undo
                'SHIFT+Z',        // Redo
                'S',              // Save
                'F',              // Focus search bar
                'ALT+SHIFT+R',    // Refresh map data
                ']',              // Issue tracker next
                '[',              // Issue tracker previous

                // Drawing
                'I',              // New road
                'O',              // New roundabout
                'J',              // Junction box
                'P',              // Pedestrian path
                'N',              // Map note
                'B',              // Restricted area
                'G',              // Residential place
                'X',              // Speed camera

                // Editing
                'M',              // Toggle multiple selection
                'W',              // Allow all turns
                'Q',              // Disallow all turns
                'R',              // Toggle direction
                'E',              // Edit address
                'H',              // House numbers
                'C',              // Closures
                'T',              // Restrictions
                'A',              // Select entire street
                'ALT+P',          // Copy permalink
                'ALT+SHIFT+P',    // Set permalink to URL
                'UP',             // Increase elevation
                'DOWN',           // Decrease elevation
                'D',              // Delete vertex

                // Layers
                'L',              // Toggle layer switcher
                'SHIFT+D',        // Highlight segments with no name
                'SHIFT+W',        // Close Street View
                'SHIFT+I',        // Toggle satellite imagery
                'SHIFT+G',        // Toggle GPS points
                'SHIFT+R',        // Toggle roads
                'SHIFT+M',        // Toggle map notes
                'SHIFT+A',        // Toggle area managers
                'SHIFT+B',        // Toggle junction boxes
                'SHIFT+H',        // Toggle house numbers
                'SHIFT+P',        // Toggle map problems
                'SHIFT+U',        // Toggle update requests
                'SHIFT+E',        // Toggle editable areas
                'SHIFT+C',        // Toggle road closures
                'SHIFT+V',        // Toggle online editors
                'SHIFT+Z',        // Show disallowed turns
                'SHIFT+L',        // Toggle places

                // Map navigation
                'SHIFT+UP',       // Zoom in
                'SHIFT+DOWN',     // Zoom out
                'LEFT',           // Pan left
                'RIGHT'           // Pan right
            ]);

            // Acciones registradas por el script
            const actionsById = new Map();
            const actionsByCombo = new Map();

            function init() {
                // En modo híbrido, partimos de la lista reservada estática.
                // Si en el futuro usamos el SDK para leer ShortCuts reales, se puede
                // expandir esta función sin tocar el resto del módulo.
                logInfo('ShortcutRegistry iniciado con modo híbrido estático.');
            }

            function isComboReserved(combo, actionId) {
                if (!combo) {
                    return false;
                }
                const owner = actionsByCombo.get(combo);
                if (owner && owner.id !== actionId) {
                    return true;
                }
                return BUILTIN_RESERVED_COMBOS.has(combo);
            }

            function registerAction(def) {
                if (!def || !def.id) {
                    return;
                }

                const existing = actionsById.get(def.id);
                if (existing) {
                    logWarn('Acción ya registrada: ' + def.id);
                    return;
                }

                const action = Object.assign({}, def);
                action.defaultCombo = def.combo || null;

                actionsById.set(action.id, action);

                const overrideCombo = ShortcutsStorage.getOverride(action.id);
                if (typeof overrideCombo !== 'undefined') {
                    const result = setActionCombo(action.id, overrideCombo, { skipStorage: true });
                    if (!result.ok) {
                        logWarn('Override inválido para "' + action.label + '": ' + result.reason);
                        action.combo = action.defaultCombo || null;
                        registerKeyboardCombo(action);
                    }
                    return;
                }

                registerKeyboardCombo(action);
            }

            function registerKeyboardCombo(action) {
                if (!action || !action.handler || typeof action.handler !== 'function') {
                    return;
                }
                if (!action.combo) {
                    return;
                }

                if (isComboReserved(action.combo, action.id)) {
                    logWarn('Combo reservado o en uso, se omite registro de teclado para: ' + action.label + ' [' + action.combo + ']');
                    return;
                }

                actionsByCombo.set(action.combo, action);
                logInfo('Registrado ShortCut personalizado: ' + action.label + ' [' + action.combo + ']');
            }

            function unregisterKeyboardCombo(action) {
                if (!action || !action.combo) {
                    return;
                }
                const owner = actionsByCombo.get(action.combo);
                if (owner && owner.id === action.id) {
                    actionsByCombo.delete(action.combo);
                }
            }

            function setActionCombo(actionId, requestedCombo, options) {
                const action = actionsById.get(actionId);
                if (!action) {
                    return { ok: false, reason: 'Acción no encontrada' };
                }

                const normalized = KeyUtils.normalizeComboString(requestedCombo || '');
                const newCombo = normalized || null;
                const previousCombo = action.combo || null;

                if (previousCombo === newCombo) {
                    return { ok: true, changed: false, combo: newCombo };
                }

                if (action.handler && newCombo && isComboReserved(newCombo, action.id)) {
                    return {
                        ok: false,
                        reason: 'ShortCut ya usado por otro script o reservado por WME.'
                    };
                }

                unregisterKeyboardCombo(action);
                action.combo = newCombo;
                registerKeyboardCombo(action);

                const skipStorage = options && options.skipStorage;
                if (!skipStorage) {
                    if (action.defaultCombo === newCombo) {
                        ShortcutsStorage.clearOverride(action.id);
                    } else {
                        ShortcutsStorage.setOverride(action.id, newCombo);
                    }
                }

                return { ok: true, changed: true, combo: newCombo };
            }

            function resetAllCombos() {
                actionsById.forEach(function (action) {
                    unregisterKeyboardCombo(action);
                    action.combo = action.defaultCombo || null;
                    registerKeyboardCombo(action);
                });
                ShortcutsStorage.resetAll();
            }

            function getActionById(actionId) {
                return actionsById.get(actionId) || null;
            }

            function findActionByCombo(combo) {
                return actionsByCombo.get(combo) || null;
            }

            function getAllActions() {
                return Array.from(actionsById.values());
            }

            return {
                init,
                registerAction,
                findActionByCombo,
                getAllActions,
                isComboReserved,
                setActionCombo,
                resetAllCombos,
                getActionById
            };
        })();

        // ---------------------------------------------------------------------
        //  Módulo: Listener global de teclado (solo para combos personalizados)
        // ---------------------------------------------------------------------

        const KeyboardListener = (function () {
            function onKeyDown(evt) {
                const combo = KeyUtils.normalizeComboFromEvent(evt);
                if (!combo) {
                    return;
                }

                const action = ShortcutRegistry.findActionByCombo(combo);
                if (!action || !action.handler) {
                    return;
                }

                // LOGS: Combo detectado y acción a ejecutar
                logInfo('Combo detectado: ' + combo);
                logInfo('Ejecutando acción: ' + action.id + ' [' + action.label + ']');

                // Si el ShortCut es nuestro, evitamos que llegue al editor
                evt.preventDefault();
                evt.stopPropagation();

                try {
                    action.handler();
                } catch (e) {
                    logWarn('Error ejecutando acción "' + action.label + '": ' + e.message);
                }
            }

            function attach() {
                document.addEventListener('keydown', onKeyDown, true);
                logInfo('KeyboardListener adjuntado.');
            }

            return {
                attach
            };
        })();

        // ---------------------------------------------------------------------
        //  Módulo: Acciones de Hazards y Places (DOM puro)
        // ---------------------------------------------------------------------

        const HazardAndPlaceActions = (function () {
            // Definición de acciones: algunas son nativas (solo para mostrar hint),
            // otras son personalizadas (ejecutadas por este script).

            const ACTION_DEFS = [
                // --- Hazards ---
                {
                    id: 'hazard-speed-bump-native',
                    group: 'Peligros',
                    label: 'Reductor de velocidad (nativo)',
                    icon: 'Speed bump',
                    menuLabel: 'Speed bump',
                    menuAltLabels: ['Reductor de velocidad', 'Resalto'],
                    combo: 'Z',        // ShortCut nativo de WME
                    type: 'native',
                    handler: null
                },
                {
                    id: 'hazard-sharp-curve',
                    group: 'Peligros',
                    label: 'Curva pronunciada',
                    icon: 'Sharp curve',
                    menuLabel: 'Sharp curve',
                    menuAltLabels: ['Curva pronunciada', 'Curva peligrosa'],
                    combo: 'CTRL+SHIFT+1',    // ShortCut personalizado
                    type: 'custom',
                    handler: function () {
                        triggerHazardByLabels(['Sharp curve', 'Curva pronunciada', 'Curva peligrosa']);
                    }
                },
                {
                    id: 'hazard-analyze-curves-unified', // ID único
                    group: 'Peligros',
                    label: 'Analizar Curvas (Config)',   // Este texto saldrá en azul
                    icon: 'Sharp curve',
                    menuLabel: 'Analizar Curvas',
                    combo: 'CTRL+SHIFT+A',              // El atajo que activa/desactiva
                    type: 'custom',
                    stateControl: {
                        label: 'Curvas',
                        tooltip: 'Análisis de curvas',
                        toggle: function () {
                            CurveAnalyzer.toggleCurveAnalysis();
                        },
                        getState: function () {
                            return typeof CurveAnalyzer !== 'undefined' && CurveAnalyzer.isActive();
                        }
                    },

                    // 1. Esto pasa cuando presionas las TECLAS (On/Off)
                    handler: function () {
                        CurveAnalyzer.toggleCurveAnalysis();
                    },

                    // 2. Esto pasa cuando haces CLICK en el texto de la lista (Abre Panel)
                    configHandler: function () {
                        CurveSettings.showConfig();
                    }
                },
                {
                    id: 'hazard-complex-intersection',
                    group: 'Peligros',
                    label: 'Intersección compleja',
                    icon: 'Complex intersection',
                    menuLabel: 'Complex intersection',
                    menuAltLabels: ['Intersección compleja'],
                    combo: 'CTRL+SHIFT+2',
                    type: 'custom',
                    handler: function () {
                        triggerHazardByLabels(['Complex intersection', 'Intersección compleja']);
                    }
                },
                {
                    id: 'hazard-multiple-lanes',
                    group: 'Peligros',
                    label: 'Convergencia de carriles',
                    icon: 'Multiple lanes merging',
                    menuLabel: 'Multiple lanes merging',
                    menuAltLabels: ['Convergencia de carriles', 'Carriles se unen'],
                    combo: 'CTRL+SHIFT+3',
                    type: 'custom',
                    handler: function () {
                        triggerHazardByLabels(['Multiple lanes merging', 'Convergencia de carriles', 'Carriles se unen']);
                    }
                },
                {
                    id: 'hazard-tollbooth',
                    group: 'Peligros',
                    label: 'Peaje',
                    icon: 'Tollbooth',
                    menuLabel: 'Tollbooth',
                    menuAltLabels: ['Peaje'],
                    combo: 'CTRL+SHIFT+4',
                    type: 'custom',
                    handler: function () {
                        triggerHazardByLabels(['Tollbooth', 'Peaje']);
                    }
                },
                {
                    id: 'hazard-school-zone',
                    group: 'Peligros',
                    label: 'Zona escolar',
                    icon: 'School zone',
                    menuLabel: 'School zone',
                    menuAltLabels: ['Zona escolar', 'School zone'],
                    combo: 'CTRL+SHIFT+5',
                    type: 'custom',
                    // Botón para togglear la visualización de zonas escolares
                    stateControl: {
                        label: 'Ver Zona Escolar',
                        tooltip: 'Activar/Desactivar visualización de zonas escolares',
                        toggle: function () {
                            SchoolZoneVisualizer.toggle();
                        },
                        getState: function () {
                            return SchoolZoneVisualizer.isActive();
                        }
                    },
                    handler: function () {
                        return triggerHazardByLabels(['School zone', 'Zona escolar']);
                    }
                },
                // --- Peligros Faltantes ---
                {
                    id: 'hazard-ground-level-crossing',
                    group: 'Peligros',
                    label: 'Cruce Ferroviario',
                    icon: 'Ground level crossing',
                    menuLabel: 'Ground level crossing',
                    menuAltLabels: ['Paso a nivel', 'Cruce ferroviario', 'Railroad crossing'],
                    combo: 'CTRL+SHIFT+7', // ShortCut personalizado
                    type: 'custom',
                    handler: function () {
                        // Esta función busca el item directamente en el menú de Peligros
                        return triggerHazardByLabels(['Ground level crossing', 'Paso a nivel', 'Cruce ferroviario', 'Railroad crossing']);
                    }
                },
             /*  {
                    id: 'hazard-narrow-bridge',
                    group: 'Peligros',
                    label: 'Puente angosto',
                    icon: 'Narrow bridge',
                    menuLabel: 'Narrow bridge',
                    menuAltLabels: ['Puente angosto', 'Puente estrecho'],
                    combo: 'CTRL+SHIFT+8', // ShortCut personalizado
                    type: 'custom',
                    handler: function () {
                        // Esta función busca el item directamente en el menú de Peligros
                        return triggerHazardByLabels(['Narrow bridge', 'Puente angosto', 'Puente estrecho']);
                    }
                },*/
                // --- Cámaras (Grupo Nuevo) ---
                {
                    id: 'hazard-speed-camera-native',
                    group: 'Camaras',
                    label: 'Cámara de velocidad (nativo)',
                    icon: 'Speed camera',
                    menuLabel: 'Speed camera',
                    menuAltLabels: ['Cámara de velocidad', 'Control de velocidad'],
                    combo: 'X', // ShortCut nativo de WME
                    type: 'native',
                    handler: null
                },
                {
                    id: 'hazard-redlight-camera',
                    group: 'Camaras',
                    label: 'Cámara de semáforo',
                    icon: 'Red light camera',
                    menuLabel: 'Red light camera',
                    menuAltLabels: ['Cámara de semáforo', 'Cámara de luz roja'],
                    combo: 'CTRL+SHIFT+9', // ShortCut personalizado
                    type: 'custom',
                    handler: function () {
                        // Llama a la nueva función: triggerHazardSubMenuByLabels(['SubMenuIngles', 'SubMenuEsp'], ['ItemIngles', 'ItemEsp'])
                        return triggerHazardSubMenuByLabels(
                            ['Camera', 'Cámara'],
                            ['Red light camera', 'Cámara de semáforo', 'Cámara de luz roja']
                        );
                    }
                },
                {
                    id: 'hazard-fake-camera',
                    group: 'Camaras',
                    label: 'Cámara falsa',
                    icon: 'Fake camera',
                    menuLabel: 'Fake camera',
                    menuAltLabels: ['Cámara falsa', 'Cámara disuasoria'],
                    combo: 'CTRL+SHIFT+6', // ShortCut personalizado
                    type: 'custom',
                    handler: function () {
                        return triggerHazardSubMenuByLabels(
                            ['Camera', 'Cámara'],
                            ['Fake camera', 'Cámara falsa', 'Cámara disuasoria']
                        );
                    }
                },
            ];

            function registerAll() {
                ACTION_DEFS.forEach(function (def) {
                    ShortcutRegistry.registerAction(def);
                });
            }

            // --- Helpers DOM ---

            function normalizeLabelInput(labels) {
                const normalized = [];
                function push(val) {
                    if (!val) {
                        return;
                    }
                    const text = ('' + val).trim();
                    if (!text) {
                        return;
                    }
                    if (normalized.indexOf(text) === -1) {
                        normalized.push(text);
                    }
                }

                if (Array.isArray(labels)) {
                    labels.forEach(push);
                } else {
                    push(labels);
                }

                return normalized;
            }

            function findFirstMatchingMenuItem(labels, scope) {
                const normalizedLabels = normalizeLabelInput(labels);
                for (let i = 0; i < normalizedLabels.length; i += 1) {
                    const candidate = findMenuItemByText(normalizedLabels[i], scope);
                    if (candidate) {
                        return candidate;
                    }
                }
                return null;
            }

            function ensureAddMenuOpen() {
                // Nuevo enfoque: por ahora asumimos que el usuario abre el menú Add manualmente.
                // Este helper solo valida que exista un <wz-menu> en el DOM.
                const wzMenu = document.querySelector('wz-menu');
                if (wzMenu) {
                    logInfo('ensureAddMenuOpen: wz-menu ya presente, usando menú actual.');
                    return true;
                }

                logWarn('ensureAddMenuOpen: no hay wz-menu abierto. Abre el menú Add manualmente.');
                return false;
            }

            function findMenuItemByText(labelText, root) {
                if (!labelText) {
                    return null;
                }

                const scope = root || document;

                // Primer intento: elementos clásicos con role="menuitem"
                const menuItems = scope.querySelectorAll('div[role="menuitem"], button[role="menuitem"], li[role="menuitem"]');
                for (let i = 0; i < menuItems.length; i += 1) {
                    const item = menuItems[i];
                    const txt = (item.textContent || '').trim();
                    if (!txt) {
                        continue;
                    }

                    if (txt === labelText || txt.startsWith(labelText + '\n')) {
                        return item;
                    }
                }

                // Segundo intento: estructura nueva de WME con <wz-menu-item> y .itemLabel--kXZjU
                const labelNodes = scope.querySelectorAll('wz-menu-item .itemLabel--kXZjU');
                for (let j = 0; j < labelNodes.length; j += 1) {
                    const labelNode = labelNodes[j];
                    const txt = (labelNode.textContent || '').trim();
                    if (!txt) {
                        continue;
                    }

                    if (txt === labelText || txt.startsWith(labelText + '\n')) {
                        // Intentamos devolver el contenedor clicable; si no existe, devolvemos el propio labelNode
                        const hostItem = labelNode.closest('wz-menu-item');
                        return hostItem || labelNode;
                    }
                }

                return null;
            }

            function triggerHazardByLabels(labels) {
                const labelList = normalizeLabelInput(labels);
                logInfo('triggerHazardByLabel: inicio para labels="' + labelList.join(', ') + '"');
                if (!ensureAddMenuOpen()) {
                    logWarn('No se encontró un menú Add abierto para Hazards (wz-menu ausente).');
                    return;
                }

                const menu = document.querySelector('wz-menu');
                if (!menu) {
                    logWarn('triggerHazardByLabel: no se encontró el contenedor wz-menu.');
                    return;
                }

                const hazardSubMenu = menu.querySelector('wz-menu-sub-menu[sub-menu-title="Hazard"]');
                logInfo('triggerHazardByLabel: submenú Hazard -> ' + (hazardSubMenu ? 'encontrado' : 'NO encontrado'));
                if (!hazardSubMenu) {
                    logWarn('No se encontró el submenú Hazard dentro de wz-menu.');
                    return;
                }

                const target = findFirstMatchingMenuItem(labelList, hazardSubMenu);
                logInfo('triggerHazardByLabel: resultado búsqueda hazard -> ' + (target ? 'encontrado' : 'NO encontrado'));
                if (!target) {
                    logWarn('No se encontró el hazard con etiquetas: ' + labelList.join(', '));
                    return;
                }

                logInfo('triggerHazardByLabel: haciendo click en hazard (etiquetas evaluadas: ' + labelList.join(', ') + ').');
                target.click();
            }

            function triggerPlaceCategoryByLabels(labels) {
                const labelList = normalizeLabelInput(labels);
                logInfo('triggerPlaceCategoryByLabel: inicio para labels="' + labelList.join(', ') + '"');
                if (!ensureAddMenuOpen()) {
                    logWarn('No se encontró un menú Add abierto para Places (wz-menu ausente).');
                    return;
                }

                const menu = document.querySelector('wz-menu');
                if (!menu) {
                    logWarn('triggerPlaceCategoryByLabel: no se encontró el contenedor wz-menu.');
                    return;
                }

                const placeSubMenu = menu.querySelector('wz-menu-sub-menu[sub-menu-title="Place"]');
                logInfo('triggerPlaceCategoryByLabel: submenú Place -> ' + (placeSubMenu ? 'encontrado' : 'NO encontrado'));
                if (!placeSubMenu) {
                    logWarn('No se encontró el submenú Place dentro de wz-menu.');
                    return;
                }

                // 1) Intentar primero si es un submenú (por ejemplo "Car Services")
                let target = null;
                labelList.some(function (label) {
                    target = placeSubMenu.querySelector('wz-menu-sub-menu[sub-menu-title="' + label + '"]');
                    return !!target;
                });
                if (target) {
                    logInfo('triggerPlaceCategoryByLabel: haciendo click en submenú (label: ' + (labelList[0] || '?') + ').');
                    target.click();
                    return;
                }

                // 2) Si no es submenú, buscarlo como ítem normal dentro de Place
                target = findFirstMatchingMenuItem(labelList, placeSubMenu);
                logInfo('triggerPlaceCategoryByLabel: resultado búsqueda categoría -> ' + (target ? 'encontrado' : 'NO encontrado'));
                if (!target) {
                    logWarn('No se encontró la categoría Place con etiquetas: ' + labelList.join(', '));
                    return;
                }

                logInfo('triggerPlaceCategoryByLabel: haciendo click en categoría (etiquetas evaluadas: ' + labelList.join(', ') + ').');
                target.click();
            }

            function triggerHazardSubMenuByLabels(subMenuLabels, itemLabels) {
                const subMenuLabelList = normalizeLabelInput(subMenuLabels);
                const itemLabelList = normalizeLabelInput(itemLabels);
                logInfo('triggerHazardSubMenuByLabels: inicio para subMenú="' + subMenuLabelList.join(', ') + '", items="' + itemLabelList.join(', ') + '"');

                if (!ensureAddMenuOpen()) {
                    logWarn('triggerHazardSubMenuByLabels: no se encontró un menú Add abierto.');
                    return;
                }

                const menu = document.querySelector('wz-menu');
                if (!menu) {
                    logWarn('triggerHazardSubMenuByLabels: no se encontró el contenedor wz-menu.');
                    return;
                }

                const hazardSubMenu = menu.querySelector('wz-menu-sub-menu[sub-menu-title="Hazard"]');
                if (!hazardSubMenu) {
                    logWarn('triggerHazardSubMenuByLabels: no se encontró el submenú Hazard.');
                    return;
                }

                let targetSubMenu = null;
                for (let i = 0; i < subMenuLabelList.length; i++) {
                    targetSubMenu = hazardSubMenu.querySelector('wz-menu-sub-menu[sub-menu-title="' + subMenuLabelList[i] + '"]');
                    if (targetSubMenu) {
                        if (typeof targetSubMenu.click === 'function') {
                            targetSubMenu.click();
                        }
                        break;
                    }
                }

                if (!targetSubMenu) {
                    logWarn('triggerHazardSubMenuByLabels: submenú no encontrado (' + subMenuLabelList.join(', ') + ').');
                    return;
                }

                const targetItem = findFirstMatchingMenuItem(itemLabelList, targetSubMenu);
                if (!targetItem) {
                    logWarn('triggerHazardSubMenuByLabels: item no encontrado (' + itemLabelList.join(', ') + ').');
                    return;
                }

                logInfo('triggerHazardSubMenuByLabels: haciendo click en item.');
                targetItem.click();
            }

            return {
                registerAll,
                triggerHazardByLabels,
                triggerPlaceCategoryByLabels,
                triggerHazardSubMenuByLabels
            };
        })();

        // ---------------------------------------------------------------------
        //  Módulo: Decorador de menús (añade [shortcut] o ND al texto)
        // ---------------------------------------------------------------------

        const MenuDecorator = (function () {
            let intervalId = null;

            function decorateOnce() {
                const allActions = ShortcutRegistry.getAllActions();
                if (!allActions || allActions.length === 0) {
                    return;
                }

                allActions.forEach(function (act) {
                    const comboText = act.combo || 'ND';
                    const labelCandidates = getMenuLabelCandidates(act);
                    if (!labelCandidates || labelCandidates.length === 0) {
                        return;
                    }

                    const menuItems = document.querySelectorAll('div[role="menuitem"], button[role="menuitem"], li[role="menuitem"]');
                    for (let i = 0; i < menuItems.length; i += 1) {
                        const item = menuItems[i];
                        const txt = (item.textContent || '').trim();
                        if (!txt) {
                            continue;
                        }

                        const matches = labelCandidates.some(function (candidate) {
                            return txt === candidate || txt.startsWith(candidate + '\n');
                        });

                        if (matches) {
                            let span = item.querySelector('.pln-shortcut-hint');
                            if (!span) {
                                span = document.createElement('span');
                                span.className = 'pln-shortcut-hint';
                                item.appendChild(span);
                            }
                            span.textContent = ' [' + comboText + ']';
                        }
                    }
                });
            }

            function start() {
                if (intervalId !== null) {
                    return;
                }

                // Decoramos periódicamente porque los menús aparecen y desaparecen.
                intervalId = window.setInterval(decorateOnce, 1000);
                logInfo('MenuDecorator iniciado.');
            }

            return {
                start
            };
        })();

        // ---------------------------------------------------------------------
        //  Módulo: UI de configuración de ShortCuts (entrada en lista de scripts)
        // ---------------------------------------------------------------------

        const ShortcutsConfigUI = (function () {
            const MODAL_ID = 'esc-shortcuts-modal';
            let escHandler = null;
            let statusTimer = null;
            let stateWatcherTimer = null;
            let stateWatchers = [];

            function attachEscListener() {
                if (escHandler) {
                    return;
                }
                escHandler = function (evt) {
                    if (evt.key === 'Escape') {
                        closeManager();
                    }
                };
                document.addEventListener('keydown', escHandler, true);
            }

            function closeManager() {
                const overlay = safeGetElementById(MODAL_ID);
                if (overlay && overlay.parentElement) {
                    overlay.parentElement.removeChild(overlay);
                }
                if (escHandler) {
                    document.removeEventListener('keydown', escHandler, true);
                    escHandler = null;
                }
                stopStateWatcher();
                stateWatchers.length = 0;
                if (statusTimer) {
                    window.clearTimeout(statusTimer);
                    statusTimer = null;
                }
            }

            function handleComboInputKeydown(evt) {
                if (evt.key === 'Tab') {
                    return;
                }

                evt.preventDefault();
                evt.stopPropagation();

                if (evt.key === 'Escape') {
                    evt.target.blur();
                    return;
                }

                if (evt.key === 'Backspace' || evt.key === 'Delete') {
                    evt.target.value = '';
                    return;
                }

                const combo = KeyUtils.normalizeComboFromEvent(evt, { allowEditableTargets: true });
                if (combo) {
                    evt.target.value = combo;
                }
            }

            function renderTableRows(tbody, actions) {
                tbody.innerHTML = '';
                stateWatchers.length = 0;
                const sorted = actions.slice().sort(function (a, b) {
                    const groupA = (a.group || 'General').toLowerCase();
                    const groupB = (b.group || 'General').toLowerCase();
                    if (groupA !== groupB) {
                        return groupA.localeCompare(groupB);
                    }
                    return (a.label || a.id).localeCompare(b.label || b.id);
                });

                sorted.forEach(function (action) {
                    const row = document.createElement('tr');
                    row.setAttribute('data-action-id', action.id);

                    const stateCell = document.createElement('td');
                    const stateInfo = action.stateControl;
                    if (stateInfo && typeof stateInfo.toggle === 'function' && typeof stateInfo.getState === 'function') {
                        const toggleBtn = document.createElement('button');
                        toggleBtn.type = 'button';
                        toggleBtn.className = 'esc-state-toggle-btn';
                        const icon = document.createElement('span');
                        icon.className = 'esc-state-toggle-icon';
                        icon.textContent = '✔';
                        toggleBtn.appendChild(icon);
                        const labelSpan = document.createElement('span');
                        labelSpan.className = 'esc-state-toggle-label';
                        labelSpan.textContent = stateInfo.label || action.label || 'Estado';
                        toggleBtn.appendChild(labelSpan);

                        function updateStateIcon() {
                            let active = false;
                            try {
                                active = !!stateInfo.getState();
                            } catch (err) {
                                active = false;
                                logWarn('esc: no se pudo leer el estado de ' + action.id + ' -> ' + err.message);
                            }
                            icon.style.color = active ? '#16a34a' : '#dc2626';
                            toggleBtn.dataset.state = active ? 'active' : 'inactive';
                            const targetLabel = stateInfo.tooltip || stateInfo.label || action.label || 'estado';
                            toggleBtn.title = (active ? 'Desactivar ' : 'Activar ') + targetLabel;
                        }

                        toggleBtn.addEventListener('click', function () {
                            try {
                                stateInfo.toggle();
                            } catch (err) {
                                logWarn('esc: error alternando ' + action.id + ' -> ' + err.message);
                            }
                            updateStateIcon();
                        });

                        stateWatchers.push({
                            refresh: updateStateIcon
                        });

                        updateStateIcon();
                        stateCell.appendChild(toggleBtn);
                    } else {
                        stateCell.textContent = 'N/A';
                        stateCell.className = 'esc-state-cell-na';
                    }
                    row.appendChild(stateCell);

                    const groupCell = document.createElement('td');
                    groupCell.textContent = action.group || 'General';
                    row.appendChild(groupCell);

                    const labelCell = document.createElement('td');
                    // --- INICIO MODIFICACIÓN: Texto Cliqueable ---
                    if (action.configHandler && typeof action.configHandler === 'function') {
                        // Si tiene configuración, creamos un enlace
                        const link = document.createElement('span'); // Usamos span con estilo de link
                        link.textContent = action.label || action.id;
                        link.style.cssText = "color:#007bff; text-decoration:underline; cursor:pointer; font-weight:500;";
                        link.title = "Click para abrir configuración";
                        link.onclick = function(e) {
                            e.preventDefault(); // Evitar comportamientos extraños
                            action.configHandler(); // Ejecutar la función de abrir panel
                        };
                        labelCell.appendChild(link);
                    } else {
                        // Comportamiento normal
                        labelCell.textContent = action.label || action.id;
                    }
                    // --- FIN MODIFICACIÓN ---
                    row.appendChild(labelCell);

                    const comboCell = document.createElement('td');
                    const comboControl = document.createElement('div');
                    comboControl.className = 'esc-combo-control';
                    const input = document.createElement('input');
                    input.type = 'text';
                    input.className = 'esc-combo-input';
                    input.dataset.actionId = action.id;
                    input.value = action.combo || '';
                    input.placeholder = action.defaultCombo ? action.defaultCombo : 'Sin ShortCut';

                    if (!action.handler || typeof action.handler !== 'function') {
                        input.disabled = true;
                        input.title = 'ShortCut nativo / informativo (no editable).';
                    } else {
                        input.title = 'Haz click y presiona el nuevo ShortCut (Backspace para limpiar).';
                        input.addEventListener('keydown', handleComboInputKeydown);
                    }

                    comboControl.appendChild(input);

                    const restoreBtn = document.createElement('button');
                    restoreBtn.type = 'button';
                    restoreBtn.className = 'esc-btn esc-btn-ghost';
                    restoreBtn.textContent = 'Predeterminado';
                    restoreBtn.title = 'Restaurar ShortCut original';
                    if (!action.defaultCombo || !action.handler || typeof action.handler !== 'function') {
                        restoreBtn.disabled = true;
                    }
                    restoreBtn.addEventListener('click', function () {
                        if (restoreBtn.disabled) {
                            return;
                        }
                        input.value = action.defaultCombo || '';
                    });
                    comboControl.appendChild(restoreBtn);

                    comboCell.appendChild(comboControl);
                    row.appendChild(comboCell);

                    const defaultCell = document.createElement('td');
                    defaultCell.textContent = action.defaultCombo || 'ND';
                    row.appendChild(defaultCell);

                    const typeCell = document.createElement('td');
                    typeCell.textContent = action.type === 'native' ? 'Nativo' : 'Custom';
                    row.appendChild(typeCell);

                    tbody.appendChild(row);
                });
            }

            function refreshStateIndicators() {
                if (!stateWatchers.length) {
                    return;
                }
                stateWatchers.forEach(function (watcher) {
                    try {
                        watcher.refresh();
                    } catch (err) {
                        logWarn('esc: error actualizando estado -> ' + err.message);
                    }
                });
            }

            function startStateWatcher() {
                if (!stateWatchers.length) {
                    stopStateWatcher();
                    return;
                }
                stopStateWatcher();
                refreshStateIndicators();
                stateWatcherTimer = window.setInterval(refreshStateIndicators, 1200);
            }

            function stopStateWatcher() {
                if (stateWatcherTimer) {
                    window.clearInterval(stateWatcherTimer);
                    stateWatcherTimer = null;
                }
            }

            function showStatus(modal, message, type) {
                if (!modal) {
                    return;
                }
                const statusNode = modal.querySelector('.esc-status');
                if (!statusNode) {
                    return;
                }

                statusNode.textContent = message || '';
                statusNode.className = 'esc-status';
                if (type === 'success') {
                    statusNode.classList.add('esc-status--success');
                } else if (type === 'error') {
                    statusNode.classList.add('esc-status--error');
                }

                if (statusTimer) {
                    window.clearTimeout(statusTimer);
                }
                if (message) {
                    statusTimer = window.setTimeout(function () {
                        statusNode.textContent = '';
                        statusNode.className = 'esc-status';
                    }, 4500);
                }
            }

            function applyChanges(modal) {
                const inputs = modal.querySelectorAll('.esc-combo-input');
                let changed = 0;
                const errors = [];

                inputs.forEach(function (input) {
                    input.classList.remove('esc-input-error');
                    if (input.disabled) {
                        return;
                    }

                    const actionId = input.dataset.actionId;
                    const action = ShortcutRegistry.getActionById(actionId);
                    if (!action) {
                        return;
                    }

                    const result = ShortcutRegistry.setActionCombo(actionId, input.value || '');
                    if (!result.ok) {
                        errors.push(action.label + ': ' + result.reason);
                        input.classList.add('esc-input-error');
                        return;
                    }
                    if (result.changed) {
                        changed += 1;
                    }
                    input.value = result.combo || '';
                });

                if (errors.length > 0) {
                    showStatus(modal, 'No se aplicaron algunos cambios:\n- ' + errors.join('\n- '), 'error');
                    return;
                }

                if (changed === 0) {
                    showStatus(modal, 'Sin cambios por guardar.', 'success');
                } else {
                    showStatus(modal, 'Se guardaron ' + changed + ' cambio(s).', 'success');
                }
            }

            function handleResetAll(modal) {
                const confirmed = window.confirm('¿Restablecer todos los ShortCuts a sus valores originales?');
                if (!confirmed) {
                    return;
                }

                ShortcutRegistry.resetAllCombos();
                const inputs = modal.querySelectorAll('.esc-combo-input');
                inputs.forEach(function (input) {
                    const action = ShortcutRegistry.getActionById(input.dataset.actionId);
                    input.classList.remove('esc-input-error');
                    if (action) {
                        input.value = action.combo || '';
                    }
                });
                showStatus(modal, 'Se restablecieron los ShortCuts originales.', 'success');
            }

            function openManager() {
                const actions = ShortcutRegistry.getAllActions();
                if (!actions || actions.length === 0) {
                    window.alert('WME Easy ShortCuts\n\nNo hay acciones registradas todavía.');
                    return;
                }

                UIStyles.ensure();
                closeManager();

                const overlay = document.createElement('div');
                overlay.id = MODAL_ID;
                overlay.className = 'esc-modal-backdrop';
                overlay.addEventListener('click', function (evt) {
                    if (evt.target === overlay) {
                        closeManager();
                    }
                });

                const modal = document.createElement('div');
                modal.className = 'esc-modal';
                overlay.appendChild(modal);

                const header = document.createElement('div');
                header.className = 'esc-modal__header';
                const title = document.createElement('h2');
                title.textContent = 'WME Easy ShortCuts';
                header.appendChild(title);
                const closeBtn = document.createElement('button');
                closeBtn.type = 'button';
                closeBtn.className = 'esc-close-btn';
                closeBtn.textContent = '×';
                closeBtn.addEventListener('click', closeManager);
                header.appendChild(closeBtn);
                modal.appendChild(header);

                const intro = document.createElement('p');
                intro.className = 'esc-icon-hint';
                intro.textContent = 'Captura un ShortCut haciendo click en el campo y presionando el combo deseado. Backspace lo limpia. Los ShortCuts nativos solo se muestran como referencia.';
                modal.appendChild(intro);

                const body = document.createElement('div');
                body.className = 'esc-modal__body';
                const table = document.createElement('table');
                const thead = document.createElement('thead');
                const headRow = document.createElement('tr');
                ['Estado', 'Grupo', 'Acción', 'ShortCut actual', 'Predeterminado', 'Tipo'].forEach(function (label) {
                    const th = document.createElement('th');
                    th.textContent = label;
                    headRow.appendChild(th);
                });
                thead.appendChild(headRow);
                table.appendChild(thead);
                const tbody = document.createElement('tbody');
                renderTableRows(tbody, actions);
                table.appendChild(tbody);
                body.appendChild(table);
                modal.appendChild(body);

                const footer = document.createElement('div');
                footer.className = 'esc-modal__footer';
                const status = document.createElement('div');
                status.className = 'esc-status';
                footer.appendChild(status);
                const buttons = document.createElement('div');
                buttons.className = 'esc-footer-buttons';

                const resetBtn = document.createElement('button');
                resetBtn.type = 'button';
                resetBtn.className = 'esc-btn esc-btn-secondary';
                resetBtn.textContent = 'Restaurar todo';
                resetBtn.addEventListener('click', function () {
                    handleResetAll(modal);
                });
                buttons.appendChild(resetBtn);

                const saveBtn = document.createElement('button');
                saveBtn.type = 'button';
                saveBtn.className = 'esc-btn esc-btn-primary';
                saveBtn.textContent = 'Guardar cambios';
                saveBtn.addEventListener('click', function () {
                    applyChanges(modal);
                });
                buttons.appendChild(saveBtn);

                footer.appendChild(buttons);
                modal.appendChild(footer);

                document.body.appendChild(overlay);
                startStateWatcher();
                attachEscListener();
            }

            return {
                openManager
            };
        })();

        // ---------------------------------------------------------------------
        //  Módulo: Lanzador flotante independiente
        // ---------------------------------------------------------------------

        const ScriptLauncher = (function () {
            const BUTTON_ID = 'esc-launcher-btn';
            let attached = false;
            let repositionTimerId = null;

            function findOnlineEditorsBadge() {
                const candidates = Array.from(document.querySelectorAll('div, span, button, wz-button, wz-tooltip'));
                for (let i = 0; i < candidates.length; i += 1) {
                    const node = candidates[i];
                    const txt = (node.textContent || '').trim().toLowerCase();
                    if (!txt) {
                        continue;
                    }
                    if (txt.includes('online editors')) {
                        return node;
                    }
                }
                return null;
            }

            function findNrmlizerBadge() {
                const candidates = Array.from(document.querySelectorAll('div, span, button'));
                for (let i = 0; i < candidates.length; i += 1) {
                    const node = candidates[i];
                    const txt = (node.textContent || '').trim();
                    if (!txt) {
                        continue;
                    }
                    if (txt.includes('Places NrmliZer') || txt.includes('Places NrmlIZer') || txt.includes('Places Nrmlizer')) {
                        return node;
                    }
                }
                return null;
            }

            function getAnchorElement() {
                // 1) Intentar anclar al badge de "online editors" (nuevo comportamiento principal)
                const onlineEditorsBadge = findOnlineEditorsBadge();
                if (onlineEditorsBadge) {
                    return onlineEditorsBadge;
                }

                // 2) Si no existe (por ejemplo en pantallas muy pequeñas), usar Places NrmliZer como respaldo
                const nrmlizerBadge = findNrmlizerBadge();
                if (nrmlizerBadge) {
                    return nrmlizerBadge;
                }

                // 3) Último recurso: el contenedor del mapa o el panel izquierdo
                const map = safeGetElementById('map') ||
                    document.querySelector('#map') ||
                    document.querySelector('.WazeMap') ||
                    document.querySelector('[data-testid="map-container"]');
                if (map) {
                    return map;
                }

                return safeGetElementById('edit-panel') ||
                    document.querySelector('#left-panel') ||
                    document.querySelector('[data-testid="left-panel"]') ||
                    document.querySelector('.left-panel, .side-panel');
            }

            function positionButton() {
                const btn = safeGetElementById(BUTTON_ID);
                if (!btn) {
                    return;
                }

                // Posición fija similar al panel de Places NrmliZer
                btn.style.position = 'fixed';
                btn.style.bottom = '60px';
                btn.style.left = '19%';
                btn.style.top = 'auto';
            }

            function createButtonIfNeeded() {
                if (!document.body) {
                    return false;
                }

                let container = safeGetElementById(BUTTON_ID);
                if (container) {
                    attached = true;
                    positionButton();
                    return true;
                }

                UIStyles.ensure();

                container = document.createElement('div');
                container.id = BUTTON_ID;
                container.setAttribute('data-tooltip', 'EasyShortCuts');

                const wzBtn = document.createElement('wz-button');
                wzBtn.setAttribute('color', 'clear-icon');
                wzBtn.setAttribute('size', 'md');
                wzBtn.setAttribute('type', 'button');
                wzBtn.setAttribute('aria-label', 'Abrir gestor Easy ShortCuts');
                wzBtn.title = 'Abrir gestor de ShortCuts de WME Easy ShortCuts';

                const iconSpan = document.createElement('span');
                iconSpan.className = 'esc-launcher__icon';
                const img = document.createElement('img');
                img.src = MAIN_TAB_ICON_BASE64;
                img.alt = 'EasyShortCuts icon';
                iconSpan.appendChild(img);

                wzBtn.appendChild(iconSpan);

                wzBtn.addEventListener('click', function (evt) {
                    evt.preventDefault();
                    evt.stopPropagation();
                    ShortcutsConfigUI.openManager();
                });

                container.appendChild(wzBtn);
                document.body.appendChild(container);

                attached = true;
                positionButton();

                window.addEventListener('resize', positionButton);

                logInfo('ScriptLauncher: acceso rápido creado en la interfaz (SDK button, anclado al mapa).');
                return true;
            }

            function start() {
                if (attached && safeGetElementById(BUTTON_ID)) {
                    return;
                }

                createButtonIfNeeded();

                if (!repositionTimerId) {
                    repositionTimerId = window.setInterval(function () {
                        if (!safeGetElementById(BUTTON_ID)) {
                            createButtonIfNeeded();
                        }
                        positionButton();
                    }, 1200);
                }
            }

            return {
                start
            };
        })();

        // ---------------------------------------------------------------------
        //  Bootstrap: esperar a que WME/WazeWrap Y EL SDK estén listos
        // ---------------------------------------------------------------------
        let sdkReady = false;
        let wazeWrapReady = false;
        let sdkInitializerFunc = null;

        // Función que se llamará cuando AMBOS estén listos
        function tryInitializeScript()
        {
            if (sdkReady && wazeWrapReady)
            {
                logInfo('WazeWrap y WME SDK detectados. Inicializando EasyShortCuts...');
                initEasyShortCuts(sdkInitializerFunc);
            }
        }

        // Vigilante 1: Espera por WazeWrap
        function bootstrapWazeWrap()
        {
            // Simplificamos la comprobación. Si WazeWrap dice que está listo, confiamos en él.
            // Eliminamos la dependencia estricta de W.map.projection para evitar bloqueos.
            if (typeof WazeWrap === 'undefined' || !WazeWrap.Ready)
            {
                logInfo('Esperando inicialización de WazeWrap...');
                window.setTimeout(bootstrapWazeWrap, 500);
                return;
            }

            logInfo('WazeWrap está listo.');
            wazeWrapReady = true;
            tryInitializeScript();
        }

        // Vigilante 2: Espera por el WME SDK
        function bootstrapSDK()
        {
            if (typeof unsafeWindow === 'undefined' || typeof unsafeWindow.SDK_INITIALIZED === 'undefined')
            {
                // Log menos agresivo (debug en lugar de warn) para no llenar la consola mientras carga
                // console.debug('[EasyShortCuts] Esperando WME SDK...');
                window.setTimeout(bootstrapSDK, 500);
                return;
            }

            unsafeWindow.SDK_INITIALIZED.then(function() {
                logInfo('WME SDK inicializado correctamente.');
                sdkInitializerFunc = unsafeWindow.getWmeSdk;
                sdkReady = true;
                tryInitializeScript();
            }).catch(function(err) {
                logWarn('Error crítico al obtener WME SDK: ' + err.message);
                // Aún si el SDK falla, podríamos querer arrancar el resto del script
                // sdkReady = true; tryInitializeScript();
            });
        }

        // Iniciar secuencia de arranque
        bootstrapWazeWrap();
        bootstrapSDK();

    };


    console.log('[WME PLN] 🏁 Script loaded. Starting initialization check...');

    // Iniciar verificación de EasyShortCuts con espera inteligente
    // Espera hasta que la configuración del servidor esté lista o pase un tiempo máximo (15s)
    let configAttempts = 0;
    const maxConfigAttempts = 15;
    
    const waitForConfigAndInit = function() {
        configAttempts++;
        const accessCtrl = window.PLN_ACCESS_CONTROL || (typeof PLN_ACCESS_CONTROL !== 'undefined' ? PLN_ACCESS_CONTROL : {});
        
        // Criterio de éxito: easyShortcutsMinLevel está definido en la config
        const configLoaded = accessCtrl && typeof accessCtrl.easyShortcutsMinLevel !== 'undefined';
        
        if (configLoaded || configAttempts >= maxConfigAttempts) {
            
            if (configLoaded) {
                 console.log(`[NrmliZer-EasyShortCuts] ✅ Configuración de servidor detectada en intento ${configAttempts}. loading...`);
            } else {
                 console.log('[NrmliZer-EasyShortCuts] ⚠️ Timeout esperando config del servidor (15s). Usando valores por defecto/locales.');
            }
            
            checkRankAndInitEasyShortCuts();
        } else {
            // Aún no está la config, esperar 1s más
            // console.log(`[NrmliZer-EasyShortCuts] ⏳ Esperando config del servidor (Intento ${configAttempts}/${maxConfigAttempts})...`);
            setTimeout(waitForConfigAndInit, 1000);
        }
    };
    
    // Iniciar el ciclo de espera (primer chequeo en 1s)
    setTimeout(waitForConfigAndInit, 1000);

    // [PLN] Force user tracking execution
    setTimeout(function() {
        if (typeof plnForceTrackUser === 'function') {
            plnForceTrackUser();
        }
    }, 2000);

})();