WME Places Name Normalizer

Normaliza nombres de lugares y gestiona categorías dinámicamente en WME.

// ==UserScript==
// @name         WME Places Name Normalizer
// @namespace    https://greasyfork.org/en/users/mincho77
// @version      8.0.1
// @author       Mincho77
// @description  Normaliza nombres de lugares y gestiona categorías dinámicamente en WME.
// @license      MIT
// @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
// @run-at       document-end
// @require      https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @require     https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js
// ==/UserScript==
(function ()
{

    //window.normalizeWordInternal = normalizeWordInternal;
    // Variables globales básicas
    const SCRIPT_NAME = GM_info.script.name;
    const VERSION = GM_info.script.version.toString();
    // 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) : [];
    }
    // 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

    const processingPanelDimensions = { width: '400px', height: '200px' };  // Panel pequeño para procesamiento
    const resultsPanelDimensions = { width: '1400px', height: '700px' };    // Panel grande para resultados
    const commonWords = [//Palabras comunes en español que no deberían ser consideradas para normalización
        'de',  'del', 'el',    'la',   'los',  'las',  'y', 'e',
        'o',   'u',   'un',    'una',  'unos', 'unas', 'a', 'en',
    'con', 'tras', 'por',  'al',   'lo'
    ];
    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

  
    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' }; 
    
    //Novedades de cada version del script esto permitirá una pantalla la primera vez que se abra el script
const myChangelog = {
   [VERSION]: {
       "Novedades": [
            // --- NUEVAS NOVEDADES ---
            "Ahora puedes excluir lugares completos por su ID desde el panel de resultados, para que no vuelvan a aparecer en futuras búsquedas.",
            "La gestión de Palabras Especiales ahora incluye una sección para 'Lugares Excluidos', con importación/exportación.",
            "El panel de resultados muestra el área en m² para lugares de tipo polígono (con alerta si es < 400m²).",
            "Los lugares corregidos, eliminados o excluidos ahora se marcan visualmente en la tabla (tachado y atenuado) en lugar de desaparecer inmediatamente, y el contador se actualiza dinámicamente."
        ],
        "Correcciones": [
            // Correcciones existentes
            "No identificaba palabras con emojis para habilitar el botón Aplicar", 
            "No se estaban tomando correctamente las palabras Especiales al normalizar",
            "Se corrige inconveniente cuando se tiene más de una palabra exlcuída en un nombre",
            // --- NUEVAS CORRECCIONES ---
            "Se asegura que los artículos y preposiciones (ej. 'El', 'La', 'De', 'Del') en nombres propios siempre comiencen con mayúscula, incluso si no son la primera palabra del nombre.",
            "Corregido el problema de duplicación de guiones (ej. ' -- ') en nombres.",
            "Se asegura que el guion final (' -') se elimine si no hay una palabra después de un reemplazo.",
            "Se corrige el reemplazo contextual de palabras (ej. 'D1' a 'D1 -') para que el guion se inserte correctamente si hay una palabra siguiente.",
            "La lista de lugares excluidos ahora muestra el nombre del lugar (si está disponible) en lugar del ID, para una mejor identificación.",
            "Se presentan problemas con la función de calculo de área al pasar a producción el script, se bloquea la opción mientras se encuentra una solución"
        ]
    }               
};// myChangelog

    // ========================================================================    
    // --- 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);
        }
    }

    // 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();
    }
    // 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

    // 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) => {
            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["Novedades"] && versionData["Novedades"].length > 0) 
        {
            html += `<h6>Novedades:</h6><ul style="margin-bottom: 10px; list-style-type: disc; margin-left: 20px;">`;
            versionData["Novedades"].forEach(item => {
                html += `<li>${item}</li>`;
            });
            html += `</ul>`;
        }
        if (versionData["Correcciones"] && versionData["Correcciones"].length > 0) {
            html += `<h6>Correcciones:</h6><ul style="margin-bottom: 10px; list-style-type: disc; margin-left: 20px;">`;
            versionData["Correcciones"].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
    //Permite inicializar el SDK de WME
    function tryInitializeSDK(finalCallback)
    {
        let attempts = 0;
        const maxAttempts = 60; // Intentos máximos (60 * 500ms = 30 segundos)
        const intervalTime = 500;
        let sdkAttemptInterval = null;

        // Función interna para intentar obtener el SDK de WME
        function attempt()
        {
            if (typeof getWmeSdk === 'function')
            {
                if (sdkAttemptInterval)
                    clearInterval(sdkAttemptInterval);
                try
                {
                    wmeSDK = getWmeSdk({scriptId : 'WMEPlacesNameInspector', scriptName : SCRIPT_NAME, });
                    if (wmeSDK)
                         console.log("[WME_PLN][SDK INIT SUCCESS] SDK obtenido exitosamente:", wmeSDK);
                    else
                        console.warn("[WME_PLN][SDK INIT WARNING] getWmeSdk() fue llamada pero devolvió null/undefined.");
                }
                catch (e)
                {
                    console.error("[WME_PLN][SDK INIT ERROR] Error al llamar a getWmeSdk():", e);
                    wmeSDK = null;
                }
                finalCallback();
                return;
            }
            attempts++;
            if (attempts >= maxAttempts)
            {
                if (sdkAttemptInterval) clearInterval(sdkAttemptInterval);
// console.error(`[WME_PLN][SDK INIT FAILURE] No se pudo encontrar getWmeSdk() después de ${maxAttempts} intentos.`);
                wmeSDK = null;
                finalCallback();
            }
        }
        sdkAttemptInterval = setInterval(attempt, intervalTime);
        attempt();
    }//tryInitializeSDK

    //-----------------------------------------------------------------------------------------------------------
    // 1) Con el WME SDK
    // Funciones de obtención de usuario (revisa y asegúrate de que tengan estos logs)
    async function getCurrentEditorViaSdk()
    {
        if (!wmeSDK) {
            //console.log('[WME PLN][DEBUG]  SDK: wmeSDK es null.');
            return null;
        }
        if (!wmeSDK.DataModel || !wmeSDK.DataModel.User || typeof wmeSDK.DataModel.User.getCurrentUser !== 'function') {
            //console.log('[WME PLN][DEBUG]  SDK: DataModel.User o getCurrentUser no disponible. DataModel:', wmeSDK.DataModel); // Más detalles
            return null;
        }
        try {
            const user = await wmeSDK.DataModel.User.getCurrentUser();
            if (user && user.id && user.name) {
                //console.log(`[WME PLN][DEBUG] SDK SUCCESS: Usuario obtenido: ${user.name} (ID: ${user.id})`);
                return { id: user.id, name: user.name, privilege: user.privilege };
            } 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') {
            //console.log('[WME PLN][DEBUG]  WazeWrap: WazeWrap no definido.');
            return null;
        }
        if (!WazeWrap.Login || typeof WazeWrap.Login.getLoggedInUser !== 'function') {
            //console.log('[WME PLN][DEBUG]  WazeWrap: Login.getLoggedInUser() no disponible. WazeWrap.Login:', WazeWrap.Login); // Más detalles
            return null;
        }
        const wrapUser = WazeWrap.Login.getLoggedInUser();
        if (wrapUser && wrapUser.userId && wrapUser.username) {
            //console.log(`[WME PLN][DEBUG] WazeWrap SUCCESS: Usuario obtenido: ${wrapUser.username} (ID: ${wrapUser.userId})`);
            return { id: wrapUser.userId, name: wrapUser.username, privilege: wrapUser.privilege };
        } else {
            //console.log('[WME PLN][DEBUG]  WazeWrap: getLoggedInUser() devolvió datos incompletos o null:', wrapUser);
            return null;
        }
    }// getCurrentEditorViaWazeWrap

    // 3) Fallback a la API nativa de WME
    function getCurrentEditorViaWmeInternal()
    {
        if (typeof W === 'undefined' || !W.loginManager)
        {
            //console.log('[WME PLN][DEBUG]  WME Internal: W o W.loginManager no definido.');
            return null;
        }
        if (W.loginManager.userId && W.loginManager.userName)
        {
            //console.log(`[WME PLN][DEBUG] WME Internal SUCCESS: Usuario obtenido: ${W.loginManager.userName} (ID: ${W.loginManager.userId})`);
            return { id: W.loginManager.userId, name: W.loginManager.userName, privilege: W.loginManager.userPrivilege };
        }
        else
        {
            //console.log('[WME PLN][DEBUG]  WME Internal: W.loginManager devolvió datos incompletos o null:', W.loginManager); // Más detalles
            // Puedes incluso logear todo el objeto W.loginManager para ver su contenido
            // console.log('[WME_PLN][DEBUG] W.loginManager content:', W.loginManager);
            return null;
        }
    }// getCurrentEditorViaWmeInternal
    //-----------------------------------------------------------------------------------------------------------
    // 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 de Google Sheets si no se han cargado aún
                {
                    try
                    {
                        await loadDynamicCategoriesFromSheet();
                        dynamicCategoriesLoaded = true; // <-- Marcar como cargado
                    }
                    catch (error)
                    {
                        console.error("No se pudieron cargar las categorías dinámicas:", error);
                    }
                }
                // : Esperar a que tryInitializeSDK se complete
                tryInitializeSDK(() => { // 
                    // 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(); // : Mueve el callback aquí
                }); // 
            }
            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
    function getPlaceCoordinates(venueOldModel, venueSDK)
    {
        let lat = null;
        let lon = null;
        const placeId = venueOldModel ? venueOldModel.getID() : 'N/A';
        // Priorizar SDK si tiene 'geometry.coordinates' (lat/lon como array [lon, lat])
        if (venueSDK && venueSDK.geometry && Array.isArray(venueSDK.geometry.coordinates) && venueSDK.geometry.coordinates.length === 2) 
        {
            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) 
            {
                // console.log(`[WME_PLN][COORD] Coords from SDK (geometry.coordinates) for ID ${placeId}: ${lat}, ${lon}`);
                return { lat, lon };
            } 
            else 
            {
                console.log(`[WME PLN][COORD]  SDK geometry.coordinates tiene valores no geográficos o inválidos para ID ${placeId}. Coords: [${lon}, ${lat}]`);
                lat = null;
                lon = null;
            }
        }
        // Si no se obtuvieron del SDK o eran inválidas, intentar con 'geometry.centroid' del SDK
        if (venueSDK && venueSDK.geometry && venueSDK.geometry.centroid) 
        {
            if (typeof venueSDK.geometry.centroid.lat === 'number' && typeof venueSDK.geometry.centroid.lon === 'number') 
            {
                lat = venueSDK.geometry.centroid.lat;
                lon = venueSDK.geometry.centroid.lon;
                // console.log(`[WME_PLN][COORD] Coords from SDK (geometry.centroid) for ID ${placeId}: ${lat}, ${lon}`);
                return { lat, lon };
            } 
            else 
            {
                console.log(`[WME PLN][COORD]  SDK geometry.centroid lacks numerical lat/lon for ID ${placeId}. Centroid:`, venueSDK.geometry.centroid);
                lat = null;
                lon = null;
            }
        }
        // Fallback a OpenLayers Geometry (modelo antiguo) si SDK no proporcionó o fue inválido
        //              Añadiendo conversión de coordenadas de proyección.
        if ((lat === null || lon === null) && 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') 
                    {
                        // Convertir de la proyección de Waze (Spherical Mercator) a WGS84 (Lat/Lon)
                        // Waze usa EPSG:3857 (Spherical Mercator) para sus coordenadas de mapa interno.
                        // La función OL.Projection.transform es la clave aquí.
                        // OpenLayers.Projection debe estar  disponible globalmente.
                        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"), // Fuente: Spherical Mercator
                                new OpenLayers.Projection("EPSG:4326")  // Destino: WGS84 Lat/Lon
                            );
                            lat = wgs84Point.y;
                            lon = wgs84Point.x;
                            if (typeof lat === 'number' && typeof lon === 'number' && Math.abs(lat) <= 90 && Math.abs(lon) <= 180) 
                            {
                                // console.log(`[WME_PLN][COORD] Coords from Old Model (Converted OLGeometry) for ID ${placeId}: ${lat}, ${lon}`);
                                return { lat, lon };
                            } 
                            else 
                            {
                                //console.log(`[WME PLN][COORD]  OLGeometry().getCentroid() (after conversion) resulted in invalid lat/lon for ID ${placeId}. Converted: ${lat}, ${lon}`);
                                lat = null;
                                lon = null;
                            }
                        } 
                        else 
                        {
                            console.log(`[WME PLN][COORD]  OpenLayers.Projection no está disponible para la conversión de coordenadas para ID ${placeId}.`);
                            lat = centroid.y; // Si no hay conversión, se usan las originales (que fallarán la validación)
                            lon = centroid.x;
                        }
                    } 
                    else 
                    {
                        console.log(`[WME PLN][COORD]  getOLGeometry().getCentroid() returned invalid/null centroid or missing x/y for ID ${placeId}. Centroid:`, centroid);
                    }
                } 
                else 
                {
                    console.log(`[WME PLN][COORD]  getOLGeometry() or getCentroid() method not available for ID ${placeId}. Geometry:`, geometry);
                }
            } 
            catch (e) 
            {
                console.error(`[WME_PLN][COORD] Error calling getOLGeometry() or transforming for ID ${placeId}:`, e);
            }
        }
        // Log final si las coordenadas siguen siendo inválidas o indefinidas.
        if (lat === null || lon === null || isNaN(lat) || isNaN(lon))
        {
            console.log(`[WME PLN][COORD]  No se pudieron obtener coordenadas válidas FINALES para el lugar ID: ${placeId}. Lat: ${lat}, Lon: ${lon}`);
            return { lat: null, lon: null };
        }
        return { lat, lon };
    }//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 || p1.normalized !== p2.normalized) {
                    return false;
                }
                const distance = calculateDistance(p1.lat, p1.lon, p2.lat, p2.lon);
                return distance <= DISTANCE_THRESHOLD_METERS;
            });

            if (nearbyMatches.length > 0) {
                // Si encontramos duplicados cercanos para p1, agruparlos
                const groupKey = p1.normalized.toLowerCase();
                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` para el `⚠️` visual
                    uniquePlacesInGroup.forEach(p => {
                        const originalPlaceInInconsistents = allScannedPlacesData.find(item => item.id === p.id);
                        if (originalPlaceInInconsistents) {
                            originalPlaceInInconsistents.isDuplicate = true;
                        }
                    });
                    // 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++++++++++++++
    // 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

// Función auxiliar para actualizar el contador de inconsistencias en la cabecera
    function updateInconsistenciesCount(delta) {
        const resultsCounterDiv = document.querySelector("#wme-place-inspector-panel .results-counter-display"); // Nuevo ID/Clase
        if (resultsCounterDiv) {
            let currentCount = parseInt(resultsCounterDiv.dataset.currentCount || resultsCounterDiv.textContent.match(/\d+/)?.[0] || '0', 10);
            currentCount = Math.max(0, currentCount + delta); // Asegura que no sea negativo
            resultsCounterDiv.dataset.currentCount = currentCount; // Guardar el valor real en un data attribute

            const totalOriginal = parseInt(resultsCounterDiv.dataset.totalOriginal || '0', 10);
            const maxRenderLimit = parseInt(resultsCounterDiv.dataset.maxRenderLimit || '0', 10);

            if (currentCount === 0) {
                resultsCounterDiv.innerHTML = `<span style="color: green;">✔</span> Todos los lugares visibles están ${totalOriginal > 0 ? "procesados o excluidos" : "correctamente normalizados"}.`;
                // Si todo está procesado, podrías querer ocultar la tabla o mostrar un mensaje de éxito grande.
                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 if (totalOriginal > maxRenderLimit && maxRenderLimit > 0) {
                // Si se aplicó un límite, actualiza el mensaje mostrando el conteo actual de visibles.
                resultsCounterDiv.innerHTML = `<span style="color: #ff0000;"><b>${totalOriginal}</b> inconsistencias encontradas</span>. Mostrando <span style="color: #ff0000;"><b>${currentCount}</b></span> (límite de ${maxRenderLimit} aplicado).`;
            } else {
                // Mensaje normal sin límite aplicado
                resultsCounterDiv.innerHTML = `<span style="color: #ff0000;"><b>${currentCount}</b> inconsistencias encontradas</span>. Mostrando <span style="color: #ff0000;"><b>${currentCount}</b></span>.`;
            }
        }
    }//updateInconsistenciesCount


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

    // Función para cargar palabras del diccionario desde Google Sheets (Hoja "Dictionary")
    async function loadDictionaryWordsFromSheet()
    {
        const SPREADSHEET_ID = "1kJDEOn8pKLdqEyhIZ9DdcrHTb_GsoeXgIN4GisrpW2Y";
        const API_KEY = "AIzaSyAQbvIQwSPNWfj6CcVEz5BmwfNkao533i8";
        const RANGE = "Dictionary!A2:B";
        // Asegurarse de que window.dictionaryWords y window.dictionaryIndex estén inicializados
        if (!window.dictionaryWords) window.dictionaryWords = new Set(); // Usar Set para evitar duplicados
        if (!window.dictionaryIndex) window.dictionaryIndex = {}; // Índice para palabras por primera letra
        const url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${RANGE}?key=${API_KEY}`; // URL para acceder a la hoja de Google Sheets
        return new Promise((resolve) => { // Promise para manejar la carga asíncrona
            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. Se omitirá la carga desde Google Sheets.');
                resolve();
                return;
            }
            console.log('[WME PLN] Cargando palabras del diccionario desde Google Sheets...');
            GM_xmlhttpRequest(
            {
                method: "GET",
                url: url,
                onload: function (response)
                {
                    if (response.status >= 200 && response.status < 300)
                    {
                        const data = JSON.parse(response.responseText);
                        let newWordsAdded = 0;
                        if (data.values)
                        {
                            data.values.forEach(row => { // Procesar cada fila de la hoja
                                const word = (row[0] || '').trim(); // Columna A es WORD
                                if (word && !window.dictionaryWords.has(word.toLowerCase()))// Verificar si la palabra ya está en el Set
                                {
                                    window.dictionaryWords.add(word.toLowerCase()); // Añadir a Set en minúsculas
                                    const firstChar = word.charAt(0).toLowerCase();
                                    if (!window.dictionaryIndex[firstChar])
                                        window.dictionaryIndex[firstChar] = [];
                                    // Asegurarse de que el índice existe para la primera letra
                                    window.dictionaryIndex[firstChar].push(word.toLowerCase()); // Añadir al índice
                                    newWordsAdded++;
                                }
                            });
//console.log(`[WME PLN] ¡Éxito! Diccionario cargado desde Google Sheets. ${newWordsAdded} palabras nuevas añadidas.`);
                            try// Guardar el diccionario actualizado en localStorage
                            {
                                localStorage.setItem("dictionaryWordsList", JSON.stringify(Array.from(window.dictionaryWords)));
                            }
                            catch (e)
                            {
                                console.error("[WME PLN] Error guardando diccionario actualizado en localStorage:", e);
                            }
                        }
                        else
                            console.warn('[WME PLN] No se encontraron valores en la hoja "Dictionary".');
                    }
                    else
                    {
                        const error = JSON.parse(response.responseText);
                        console.error(`[WME PLN] Error al cargar la hoja "Dictionary" de Google: ${error.error.message}.`);
                    }
                    resolve();
                },
                onerror: function ()
                {
                    console.error('[WME PLN][ERROR] Error de red al intentar conectar con Google Sheets para el diccionario.');
                    resolve();
                }
            });
        });
    }//loadDictionaryWordsFromSheet

    //Función Para Cargar Categorías Desde Google Sheets
    async function loadDynamicCategoriesFromSheet()
    {
        const SPREADSHEET_ID = "1kJDEOn8pKLdqEyhIZ9DdcrHTb_GsoeXgIN4GisrpW2Y";
        const API_KEY = "AIzaSyAQbvIQwSPNWfj6CcVEz5BmwfNkao533i8";
        const RANGE = "Categories!A2:E";
        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) => { // Verificamos si la variable global ya está definida
            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(); // Permite que el script continúe sin las reglas.
                return;
            }
//console.log('[WME PLN] Cargando reglas de categoría dinámicas desde Google Sheets...');
            GM_xmlhttpRequest({method: "GET", url: url, onload: function (response)
            {
                if (response.status >= 200 && response.status < 300) // Verifica si la respuesta fue exitosa
                {
                    const data = JSON.parse(response.responseText);
                    if (data.values) // Verifica si hay valores en la hoja
                    {
                        window.dynamicCategoryRules = data.values.map(row => { // Procesa los datos y los guarda en la variable global
                            const keyword = (row[0] || '').toLowerCase().trim();
                            const keywords = keyword.split(';').map(k => k.trim()).filter(k => k.length > 0);
                            const regexParts = keywords.map(k => `\\b${escapeRegExp(k)}\\b`);
                            const combinedRegex = new RegExp(`(${regexParts.join('|')})`, 'i');
                            return {
                                keyword: keyword, // Mantener original si es necesario
                                categoryKey: row[1] || '',
                                icon: row[2] || '⚪',
                                desc_es: row[3] || 'Sin descripción',
                                desc_en: row[4] || 'No description',
                                compiledRegex: combinedRegex // Guarda la regex pre-compilada
                            };
                        });
                        window.dynamicCategoryRules.sort((a, b) => b.keyword.length - a.keyword.length);  // Una vez cargadas, ordena las reglas UNA SOLA VEZ
                        console.log('[WME PLN] ¡Éxito! Reglas de categoría dinámicas cargadas y ordenadas:', window.dynamicCategoryRules);
                    }
                    else
                    {
                        console.warn('[WME PLN] No se encontraron valores en la hoja de categorías.');
                    }
                }
                else
                {
                    const error = JSON.parse(response.responseText);
                    alert(`Error al cargar la hoja de Google: ${error.error.message}.`);
                }
                resolve();
                }, onerror: function ()
                {
                    alert('Error de red al intentar conectar con Google Sheets.');
                    resolve();
                }
            });// GM_xmlhttpRequest
        });
    }//loadDynamicCategoriesFromSheet

    // 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
    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.includes(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.
        return cleanedText.trim().replace(/\s{2,}/g, ' ');
    }// 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)
            {
                // Si la palabra excluida termina en "-" y NO hay más caracteres después en el nombre original, elimínale el guion
                if (excl.endsWith('-') && excl.replace(/-+$/, '').length > 0)
                {
                    return excl.replace(/-+$/, ''); // Devuelve solo la parte antes del guion
                }
                return excl;
            }
            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)
        {
            processedName = aplicarReemplazosDefinidos(processedName, replacementWords);
        }
        //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 al inicio (swap words)
        processedName = applyWordsToStartMovement(processedName);
        //console.log(`[WME PLN] [Paso 7] Después de applyWordsToStartMovement: "${processedName}"`);

        let finalName = processedName.replace(/\s{2,}/g, ' ').trim();
         // Regla para eliminar un guion (-) si está al final del nombre, posiblemente con espacios.
        // Ejemplo: "Tiendas D1 - "  -> "Tiendas D1"
        // Ejemplo: "Tiendas D1-"   -> "Tiendas D1"
        // Ejemplo: "Tiendas D1-  " -> "Tiendas D1"
        finalName = finalName.replace(/\s*-\s*$/, ''); // Elimina '-' con espacios a su alrededor si está al final

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


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

        return finalName;
    }// processPlaceName

    //Permite normalizar una palabra individual
    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';
        if (nameIsDifferent || categoryWasChanged)
        {
            // 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; // La cadena que se está modificando

        if (typeof replacementRules !== 'object' || replacementRules === null || Object.keys(replacementRules).length === 0)
        {
            return newText;
        }
        // Ordenar las claves de reemplazo por longitud descendente para procesar primero las frases más largas.
        const sortedFromKeys = Object.keys(replacementRules).sort((a, b) => b.length - a.length);
        for (const fromKey of sortedFromKeys)
        {
            const toValue = replacementRules[fromKey];
            const escapedFromKey = escapeRegExp(fromKey);

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

            if (toValue.endsWith(' -'))
            {
                // Esta regex tiene 4 grupos de captura: (delimitadorPrevio), (matchedFromKey), (capturedSpaces), (nextWordIfCaptured)
                regex = new RegExp(`(^|[^\\p{L}\\p{N}_\\-])(${escapedFromKey})(\\s+)(${wordCharSet}+)?(?=$|[^\\p{L}\\p{N}_-])`, 'giu');
            }
            else
            {
                // Esta regex tiene 2 grupos de captura: (delimitadorPrevio), (matchedFromKey)
                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
                    delimitadorPrevio = args[0]; // p1
                    matchedFromKey = args[1];    // p2
                    capturedSpaces = args[2];    // p3
                    nextWordIfCaptured = args[3]; // p4
                } else {
                    // Para la regex con 2 grupos de captura
                    delimitadorPrevio = args[0]; // p1
                    matchedFromKey = args[1];    // p2
                    // Los demás serán undefined si se intenta acceder a ellos, lo cual es correcto.
                }
                
                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;
                    }
                }
                
                // --- Lógica para evitar auto-reemplazo infinito (ej: Terpel -> Terpel -) ---
                if (toValue.toLowerCase().startsWith(fromKey.toLowerCase()) && toValue.length > fromKey.length)
                {
                    const suffix = toValue.substring(fromKey.length);
                    const textAfterMatchRaw = stringBeingProcessedActual.substring(offsetOfMatchInCurrentText + match.length);
                    const textAfterMatchTrimmed = textAfterMatchRaw.trim();
                    if (textAfterMatchTrimmed.startsWith(suffix.trim()))
                    {
                        return match;
                    }
                }

                // --- Lógica específica para el reemplazo que termina en ' -' ---
                if (toValue.endsWith(' -'))
                {
                    return delimitadorPrevio + toValue + (nextWordIfCaptured || '');
                }
                // --- Para otros reemplazos que no terminan en ' -' ---
                return delimitadorPrevio + toValue;
            });
        }
        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

    //Permite renderizar los lugares en el panel flotante
    function renderPlacesInFloatingPanel(places)
    {
        // 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
       
       let initialVisiblePlacesCount = places.length; // Guardar el conteo original de visibles
        // Filtrar los lugares visibles por los Places Excluidos.
        // Si excludedPlaces es un Map, .has() funciona con la clave (ID).
        places = places.filter(place => !excludedPlaces.has(place.getID()));
        const excludedByInitialFilterCount = initialVisiblePlacesCount - places.length;
        console.log(`[WME PLN] ${excludedByInitialFilterCount} lugares excluidos por filtro inicial.`);
        
        if (places.length > maxPlacesToScan) // Limitar el número de lugares a escanear
            places = places.slice(0, maxPlacesToScan); // Limitar el número de places a escanear
       
        if (places.length > maxPlacesToScan) // Limitar el número de lugares a escanear
            places = places.slice(0, maxPlacesToScan); // Limitar el número de places a escanear
        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
// console.log(`[WME_PLN][CATEGORÍA] Usando nombre de categoría de ${source}: ${categoryName} ${categoryId ? `(ID: ${categoryId})` : ''}`); // Comentario de depuración eliminado
                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
// console.log(`[WME_PLN][CATEGORÍA] Usando nombre de categoría de W.model.categories (para ID ${categoryId} de ${source}): ${categoryObjWModel.attributes.name}`); // Comentario de depuración eliminado
                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
// console.log(`[WME_PLN][CATEGORÍA] No se pudo resolver el nombre para ID de categoría ${categoryId} (obtenido de ${source}). Devolviendo ID.`); // Comentario de depuración eliminado
                return `${categoryId}`; // Devuelve el ID si no se encuentra el nombre.
            }
            return "Sin categoría";
        }//getPlaceCategoryName

         //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(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)
            {
                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)";
                }
                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)";
                }
            }
            if (!hasExplicitCity && venueFromOldModel && venueFromOldModel.attributes)
            {// Old Model (if no explicit city from SDK)
                const cityID = venueFromOldModel.attributes.cityID;
                if (cityID && typeof W !== 'undefined' && W.model && W.model.cities && W.model.cities.getObjectById)
                {// Si hay un cityID en el modelo antiguo
                    const cityObject = W.model.cities.getObjectById(cityID); // Obtener el objeto de ciudad del modelo Waze
                    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)";
                    }
                }
            }
            // 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).
//console.log(`[WME_PLN] Calculated flags: hasExplicitCity=${hasExplicitCity} (Name: ${explicitCityName}), hasStreetInfo=${hasStreetInfo}, cityAssociatedWithStreet=${cityAssociatedWithStreet}`);
//console.log(`[WME_PLN] Calculated: hasAnyAddressInfo=${hasAnyAddressInfo}`);
            if (hasAnyAddressInfo)
            {// Si hay información de dirección (ciudad explícita o calle)
                if (hasExplicitCity)
                {// Tiene ciudad explícita
                    icon = "🏙️"; // Icono para ciudad asignada
                    title = `Ciudad: ${explicitCityName}`;
                }
                else
                { // No tiene ciudad explícita, pero sí información de calle
                    if (cityAssociatedWithStreet)
                    { // Tiene calle y ciudad asociada a la calle
                        icon = "🇻🇦";
                        title = `Tiene ciudad asociada a la calle: ${cityAssociatedWithStreet}`;
                    }
                    else
                    {
                        icon = "🚫";
                        title = "Tiene calle, sin ciudad explícita";
                    }
                }
            }
            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"
            }
// console.log(`[WME_PLN] Place ID ${venueFromOldModel ? venueFromOldModel.getID() : 'N/A'}: Icon=${icon}, Title='${title}', HasExplicitCity=${hasExplicitCity}, HasStreet=${hasStreetInfo}, CityViaStreet='${cityAssociatedWithStreet}', ReturnedHasCity=${returnedHasCityBoolean}`);
            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
        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");
        // 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();
            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 || "81") / 100;//  Umbral de similitud por defecto (convertido a porcentaje)           
    //console.log(`[WME_PLN] Configuraciones leídas.`);
            // 2. Condición de salida principal (todos los lugares procesados)
            if (index >= places.length)
            {
// console.log("[WME_PLN] Todos los lugares procesados. Finalizando render...");
                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;
            if (!places[index] || typeof places[index] !== 'object' || !venueFromOldModel || typeof venueFromOldModel !== 'object' || !venueFromOldModel.attributes || typeof nameValue !== 'string' || nameValue.trim() === '')
            {
                console.warn(`[WME_PLN] Lugar inválido o sin nombre en el índice ${index}:`, venueFromOldModel);
                updateScanProgressBar(index, places.length);
                index++;
                setTimeout(() => processNextPlace(), 0);
                return;
            }
       // console.log(`[WME_PLN] Venue Old Model obtenido: ID ${venueFromOldModel.getID()}`); // Esto es un log opcional.
            // 3. Salto temprano si el venue es inválido o no tiene nombre
            if (!venueFromOldModel || typeof venueFromOldModel !== 'object' || !venueFromOldModel.attributes || typeof nameValue !== 'string' || nameValue.trim() === '')
            {
                // console.warn(`[WME_PLN] Lugar inválido o sin nombre en el índice ${index}:`, venueFromOldModel);
                updateScanProgressBar(index, places.length); // Actualizar barra de progreso antes de saltar al siguiente lugar
                index++;
                // console.log(`[WME_PLN] Saltando al siguiente place (sin nombre/inválido). Próximo índice: ${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
            // ************************************************************************************
            // INICIO DE LA MODIFICACIÓN: Uso consistente de currentVenueId y cálculo de isNameEffectivelyNormalized
            // ************************************************************************************
            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 tu código original.

            // ************************************************************************************
            // FIN DE LA MODIFICACIÓN
            // ************************************************************************************

            // 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] Iniciando pipeline de normalización para: "${originalName}"`); // Comentario opcional.
            // Paso 1: Usar la función principal para la normalización base (capitalización, reglas especiales, etc.).
            let processedName = processPlaceName(originalName); //
            //  console.log(`[WME_PLN] Después de processPlaceName: "${processedName}"`); // Comentario opcional.
            // Paso 2: Aplicar reemplazos generales (como | por - , etc.).
            processedName = aplicarReemplazosGenerales(processedName); //
            //  console.log(`[WME_PLN] Después de aplicarReemplazosGenerales: "${processedName}"`); // Comentario opcional.
            // Paso 3: Aplicar el movimiento de palabras al inicio (función de SWAP).
            processedName = applyWordsToStartMovement(processedName); //
            //   console.log(`[WME_PLN] Después de applyWordsToStartMovement: "${processedName}"`); // Comentario opcional.
            // Paso 4: Limpieza final para crear el nombre sugerido definitivo.
            let suggestedName = processedName.replace(/\s{2,}/g, ' ').trim(); //
            // Paso 5: Quitar el punto final si existe.
            if (suggestedName.endsWith('.'))
            {
                suggestedName = suggestedName.slice(0, -1); //
            }
          //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 = {};
            const suggestedNameWords = suggestedName.split(/\s+/).filter(word => word.length > 0);

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

                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
                if (window.dictionaryWords && window.dictionaryWords.size > 0) {
                    console.log(`[WME PLN - processNextPlace] Iniciando búsqueda de corrección de tildes para: "${originalWord}"`);
                    const firstChar = lowerOriginalWord.charAt(0);
                    const candidatesForTildeCheck = window.dictionaryIndex[firstChar] ? Array.from(window.dictionaryIndex[firstChar]) : [];
                    console.log(`[WME PLN - processNextPlace] Candidatos del diccionario (letra '${firstChar}'):`, candidatesForTildeCheck);

                    for (const dictWord of candidatesForTildeCheck) {
                        const lowerDictWord = dictWord.toLowerCase();
                        const cleanedDictWordNoDiacritics = removeDiacritics(lowerDictWord);
                        console.log(`[WME PLN - processNextPlace]   Comparando con dictWord: "${dictWord}", lowerDictWord: "${lowerDictWord}", cleanedDictWordNoDiacritics: "${cleanedDictWordNoDiacritics}"`);

                        const conditionMet = cleanedDictWordNoDiacritics === cleanedLowerNoDiacritics && // Mismo sin tilde
                                            lowerDictWord !== lowerCurrentSuggestedWord && // Diferente (uno tiene tilde, el otro no)
                                            !currentSuggestedWordHasDiacritics && // La palabra en el SUGERIDO no tiene tilde
                                            /[áéíóúÁÉÍÓÚüÜñÑ]/.test(lowerDictWord); // La palabra del diccionario SÍ tiene tilde
                        console.log(`[WME PLN - processNextPlace]   Condición de tilde cumplida: ${conditionMet}`);

                        if (conditionMet) {
                            let suggestedTildeWord = normalizeWordInternal(dictWord, true, false);
                            console.log(`[WME PLN - processNextPlace]   ✅ ¡Tilde sugerida! Original: "${originalWord}" -> Sugerencia: "${suggestedTildeWord}"`);

                            if (!sugerenciasLugar[originalWord]) sugerenciasLugar[originalWord] = [];
                            sugerenciasLugar[originalWord].push({
                                word: suggestedTildeWord,
                                similarity: 0.999,
                                fuente: 'dictionary_tilde'
                            });
                            tildeCorrectionSuggested = true;
                            break;
                        }
                    }
                    if (!tildeCorrectionSuggested)
                    {
                        console.log(`[WME PLN - processNextPlace] No se encontró sugerencia de tilde para "${originalWord}" en el diccionario.`);
                    }
                }
                // *******************************************************************

                // *******************************************************************
                // 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;
            // Condición 1: Si el nombre ya está normalizado y no hay cambios significativos
            let isNameAlreadyNormalized = false;
            // Comparamos la versión de procesamiento (sin emojis) con el nombre final sugerido.
            const cleanedOriginalName = nameForProcessing.trim().replace(/\s{2,}/g, ' ');
            let isNameEffectivelyNormalized = (cleanedOriginalName === suggestedName.trim());
            console.log(`[WME PLN - processNextPlace] isNameEffectivelyNormalized (Original vs Sugerido): ${isNameEffectivelyNormalized}`);

            // 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) {
                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);
            }
            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;
//console.log(`[WME_PLN][DEBUG] Place ID: ${currentVenueId}, Raw LockRank: ${lockRank}`);
            let lockRankEmoji;
            // Lógica corregida: 1 al 6 muestra su respectivo emoji; 0 (desbloqueado) o cualquier otro valor muestra 0️⃣
            if (lockRank >= 0 && lockRank <= 5)
                lockRankEmoji = lockRankEmojis[lockRank+1]; // Usa el emoji para el nivel exacto (1 al 6)
            else
                lockRankEmoji = lockRankEmojis[0]; // Para 0 (desbloqueado), Auto (si no fue 1-6), o cualquier otro caso
    //console.log(`[WME_PLN][DEBUG] Assigned LockRankEmoji: ${lockRankEmoji}`);     
            // Agregar a la lista de inconsistencias
            inconsistents.push({
                lockRankEmoji: lockRankEmoji,
                id: currentVenueId,
                original: originalNameRaw,
                normalized: suggestedName,
                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
            });
        // 9. Agregar datos del lugar para la verificación de duplicados      
            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
            //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
            if (totalInconsistentsOriginal > maxRenderLimit)
            {
                inconsistents = inconsistents.slice(0, maxRenderLimit);
                isLimited = true; // Establecer isLimited a true si se aplica el límite
                // Mostrar mensaje de advertencia si se aplica el límite
                if (!sessionStorage.getItem("popupShown"))
                {
                    const modalLimit = document.createElement("div"); // Renombrado a modalLimit para claridad
                    modalLimit.style.position = "fixed";
                    modalLimit.style.top = "50%";
                    modalLimit.style.left = "50%";
                    modalLimit.style.transform = "translate(-50%, -50%)";
                    modalLimit.style.background = "#fff";
                    modalLimit.style.border = "1px solid #ccc";
                    modalLimit.style.padding = "20px";
                    modalLimit.style.zIndex = "10007"; // <<<<<<< Z-INDEX AUMENTADO
                    modalLimit.style.width = "400px";
                    modalLimit.style.boxShadow = "0 0 15px rgba(0,0,0,0.3)";
                    modalLimit.style.borderRadius = "8px";
                    modalLimit.style.fontFamily = "sans-serif";
                    // Fondo suave azul y mejor presentación
                    modalLimit.style.backgroundColor = "#f0f8ff";
                    modalLimit.style.border = "1px solid #aad";
                    modalLimit.style.boxShadow = "0 0 10px rgba(0, 123, 255, 0.2)";
                    // --- Insertar ícono visual de información arriba del mensaje ---
                    const iconInfo = document.createElement("div"); // Renombrado
                    iconInfo.innerHTML = "ℹ️";
                    iconInfo.style.fontSize = "24px";
                    iconInfo.style.marginBottom = "10px";
                    modalLimit.appendChild(iconInfo);
                    // Contenedor del mensaje
                    const message = document.createElement("p");
                    message.innerHTML = `Se encontraron <strong>${
                    totalInconsistentsOriginal}</strong> lugares con nombres no normalizados.<br><br>Solo se mostrarán los primeros <strong>${
                    maxRenderLimit}</strong>.<br><br>Una vez corrijas estos, presiona nuevamente <strong>'Start Scan...'</strong> para continuar con el análisis del resto.`;
                    message.style.marginBottom = "20px";
                    modalLimit.appendChild(message);
                    // Botón de aceptar
                    const acceptBtn = document.createElement("button");
                    acceptBtn.textContent = "Aceptar";
                    acceptBtn.style.padding = "6px 12px";
                    acceptBtn.style.cursor = "pointer";
                    acceptBtn.style.backgroundColor = "#007bff";
                    acceptBtn.style.color = "#fff";
                    acceptBtn.style.border = "none";
                    acceptBtn.style.borderRadius = "4px";
                    acceptBtn.addEventListener("click", () => {sessionStorage.setItem("popupShown", "true");
                        modalLimit.remove();
                    });
                    modalLimit.appendChild(acceptBtn);
                    document.body.appendChild(modalLimit); // Se añade al body, así que el z-index debería funcionar globalmente
                }
            }
            // Llamar a la función para detectar y alertar nombres duplicados
            detectAndAlertDuplicateNames(inconsistents);
            
            // 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
            
            const resultsCounter = document.createElement("div");
            resultsCounter.className = "results-counter-display"; 
            resultsCounter.style.fontSize = "13px";
            resultsCounter.style.color = "#555"; // Color base para el texto normal
            resultsCounter.style.marginBottom = "8px";
            resultsCounter.style.textAlign = "left";

            // Almacenar los conteos originales en data attributes para la función updateInconsistenciesCount
            resultsCounter.dataset.currentCount = inconsistents.length;
            resultsCounter.dataset.totalOriginal = totalInconsistentsOriginal;
            resultsCounter.dataset.maxRenderLimit = maxRenderLimit;

            // Mostrar el número total de inconsistencias encontradas
            if (totalInconsistentsOriginal > 0)
            {
                if (isLimited)
                {
                    resultsCounter.innerHTML = `<span style="color: #ff0000;"><b>${totalInconsistentsOriginal}</b> inconsistencias encontradas</span>. Mostrando las primeras <span style="color: #ff0000;"><b>${inconsistents.length}</b></span> (límite de ${maxRenderLimit} aplicado).`;
                }
                else
                {
                    resultsCounter.innerHTML = `<span style="color: #ff0000;"><b>${totalInconsistentsOriginal}</b> inconsistencias encontradas</span>. Mostrando <span style="color: #ff0000;"><b>${inconsistents.length}</b></span>.`;
                }
                fixedHeaderContainer.appendChild(resultsCounter); // Añadir resultsCounter al fixedHeaderContainer
            }
            else
            {
                // Si no hay inconsistencias para mostrar en la tabla, mostrar el mensaje de éxito y salir
                // Esto se manejará en el bloque de 'if (inconsistents.length === 0)' más abajo.
                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>`;
                }
            }
            // Si no hay inconsistencias para renderizar la tabla, el mensaje de éxito ya se mostró arriba.
            // Aquí se asegura que fixedHeaderContainer se añada al output antes de la tabla,
            // o si no hay inconsistencias, se muestre el mensaje de "Todo normalizado".
            if (inconsistents.length === 0)
            { // Si no hay inconsistencias para mostrar en la tabla
                if (totalInconsistentsOriginal === 0)
                { // Si realmente no había nada, muestra el mensaje de éxito
                    //output.appendChild(document.createTextNode("Todos los nombres de lugares visibles están correctamente normalizados."));
                    const checkIcon = document.createElement("div");
                    checkIcon.innerHTML = "✔ Análisis finalizado sin inconsistencias.";
                    checkIcon.style.marginTop = "10px";
                    checkIcon.style.fontSize = "14px";
                    checkIcon.style.color = "green";
                    output.appendChild(checkIcon);
                    const successMsg = document.createElement("div");
                    successMsg.textContent = "Todos los nombres están correctamente normalizados.";
                    successMsg.style.marginTop = "10px";
                    successMsg.style.fontSize = "14px";
                    successMsg.style.color = "green";
                    successMsg.style.fontWeight = "bold";
                    output.appendChild(successMsg);
                }
                // Si inconsistents.length === 0 PERO totalInconsistentsOriginal > 0,
                // significa que se aplicó un límite tan bajo que no se muestra nada,
                // o se filtraron todos los inconsistentes. El mensaje adecuado ya se mostró en resultsCounter si aplica.
                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; // Salir de la función si no hay inconsistencias para renderizar tabla
            }
            // Asegurarse de que el outputDiv tenga estilos para manejar el sticky header
            // Este bloque se ejecuta SIEMPRE que se va a renderizar la tabla (es decir, inconsistents.length > 0)
            if (output)
            { // Asegurarse de que 'output' existe
                output.style.display = 'flex'; //
                output.style.flexDirection = 'column'; //
                output.style.position = 'relative'; // Necesario para que sticky funcione bien
                output.appendChild(fixedHeaderContainer); // Añadir el fixedHeaderContainer antes de la tabla
            }
            //Permite renderizar la tabla de resultados
            const table = document.createElement("table"); // Declaración única de table
            table.style.width = "100%";
            table.style.borderCollapse = "collapse";
            table.style.fontSize = "12px";
            // Añadir clase para estilo de tabla (No es necesaria si no tienes CSS externo para esa clase)
            const thead = document.createElement("thead"); // Declaración única de thead
            // Añadir cabecera de la tabla           
            const headerRow = document.createElement("tr"); // Declaración única de headerRow
            [
                "N°",
                "Perma",
                "Tipo/Ciudad",
                "LL",
                "Editor",
                "Nombre Actual",
                "⚠️", // ("Alerta", "Advertencia")
                "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"; // Un ancho pequeño
                }
                else if (header === "Categoría")
                {
                    th.style.width = "130px";
                }
                else if (header === "Categoría<br>Recomendada" || header === "Sugerencias<br>de reemplazo")
                {
                    th.style.width = "180px";
                }
                else if (header === "Editor")
                { // <-- Ajustar ancho si es necesario
                    th.style.width = "100px"; // Reducido de 120px a 100px
                }
                else if (header === "Acción")
                { // <-- Ajustar ancho si es necesario
                    th.style.width = "100px"; // Reducido de 120px a 100px
                }
                else if (header === "Nombre Actual" || header === "Nombre Sugerido")
                {
                    th.style.width = "270px";
                }
                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"; // z-index de la cabecera de la tabla
            headerRow.style.backgroundColor = "#003366";
            headerRow.style.color = "#ffffff";
            // Declarar tbody aquí, ANTES de llenarlo con el forEach
            const tbody = document.createElement("tbody"); // Declaración ÚNICA de tbody
            // En el render de cada fila:
            inconsistents.forEach(({ lockRankEmoji, id, original, normalized, editor, cityIcon, cityTitle, hasCity, currentCategoryName, currentCategoryIcon, currentCategoryTitle, currentCategoryKey, dynamicCategorySuggestions, venueSDKForRender, isDuplicate = false, duplicatePartners = [], typeInfo, areaMeters }, index) =>
            {
                //const typeInfo = venueSDKForRender?.typeInfo || {};
// Añadir un console.log para depurar el estado de isDuplicate y duplicatePartners
//console.log([WME_PLN][DEBUG] Procesando lugar ID: ${id}, isDuplicate: ${isDuplicate}, duplicatePartners:`, duplicatePartners);
                // Actualizar barra de progreso visual EN EL TAB PRINCIPAL
                const progressPercent =  Math.floor(((index + 1) / inconsistents.length) * 100);
                // Actualiza barra de progreso en el tab principal
                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");   
                row.querySelectorAll("td").forEach(td => td.style.verticalAlign = "top");
                row.dataset.placeId = id; //Añadir data-place-id a la fila para fácil referencia
                //Celda para el número de línea (N°)
                const numberCell = document.createElement("td");
                numberCell.textContent = index + 1; // +1 porque el índice es base 0
                numberCell.style.textAlign = "center";
                numberCell.style.padding = "4px";
                row.appendChild(numberCell);
               // Columna de enlace permanente (Perma)
                const permalinkCell = document.createElement("td");
                const link = document.createElement("a");
                link.href = "#";
                link.addEventListener("click", (e) =>
                {
                    e.preventDefault();
                    const venueObj = W.model.venues.getObjectById(id); // Obtiene el objeto del lugar (modelo antiguo)
                    const venueSDKForUse = venueSDKForRender; // Objeto del SDK que pasamos desde processNextPlace

                    let targetLat = null;
                    let targetLon = null;

                    // PRIORIDAD 1: Usar coordenadas del venueSDK (más robusto y directo)
                    if (venueSDKForUse && venueSDKForUse.geometry && Array.isArray(venueSDKForUse.geometry.coordinates) && venueSDKForUse.geometry.coordinates.length >= 2) {
                        targetLon = venueSDKForUse.geometry.coordinates[0];
                        targetLat = venueSDKForUse.geometry.coordinates[1];
                        // Nota: Si es un polígono, las coords pueden ser [lon, lat] de su primer punto,
                        // pero W.map.setCenter necesita el centroide.
                        // Para polígonos, es mejor obtener el centroide de la geometría OL.
                    }
                    
                    // PRIORIDAD 2: Obtener coordenadas del modelo antiguo (OLGeometry) si venueSDK no dio un punto claro
                    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();
                                // Transformar de EPSG:3857 (Waze) a EPSG:4326 (Lat/Lon)
                                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 {
                                    // Fallback si OpenLayers.Projection no está disponible, usar las coordenadas crudas (menos precisas)
                                    targetLat = centroidOL.y;
                                    targetLon = centroidOL.x;
                                }
                            }
                        } catch (e) {
                            console.error("[WME PLN] Error al obtener/transformar geometría OL para navegación:", e);
                        }
                    }

                    // *** LÓGICA DE NAVEGACIÓN Y SELECCIÓN ***
                    let navigated = false;
                   /* if (venueObj && W.map && typeof W.map.setCenter === "function" && targetLat !== null && targetLon !== null)
                    {
                        // Centrar mapa si tenemos coordenadas válidas
                        W.map.setCenter(new OpenLayers.LonLat(targetLon, targetLat), null, false, 0); // OpenLayers.LonLat necesita (lon, lat)
                        navigated = true;
                    }*/
                    if (venueObj && W.selectionManager && typeof W.selectionManager.select === "function")
                    {
                        // Seleccionar lugar si el objeto está cargado
                        W.selectionManager.select(venueObj);
                        navigated = true;
                    }
                    else if (venueObj && W.selectionManager && typeof W.selectionManager.setSelectedModels === "function")
                    {
                        W.selectionManager.setSelectedModels([venueObj]);
                        navigated = true;
                    }

                    // Fallback: Abrir en nueva pestaña si no se pudo navegar/seleccionar
                    if (!navigated) {
                        const confirmOpen = confirm(`El lugar "${original}" (ID: ${id}) no se pudo seleccionar o centrar directamente. ¿Deseas abrirlo en una nueva pestaña del editor?`);
                        if (confirmOpen) {
                            const wmeUrl = `https://www.waze.com/editor?env=row&venueId=${id}`;
                            window.open(wmeUrl, '_blank');
                        } else {
                            showTemporaryMessage("El lugar podría estar fuera de vista o no cargado.", 4000, 'warning');
                        }
                    } else {
                        showTemporaryMessage("Presentando detalles del lugar...", 2000, 'info');
                    }
                });
                link.title = "Seleccionar lugar en el mapa"; // Título más descriptivo
                link.textContent = "🔗";
                permalinkCell.appendChild(link);
                permalinkCell.style.padding = "4px";
                permalinkCell.style.fontSize = "18px"; // Tamaño del ícono
                permalinkCell.style.textAlign = "center"; // Centrar el ícono
                permalinkCell.style.width = "65px";
                row.appendChild(permalinkCell);
   
   
   
 // Combinada: Tipo / Ciudad
                const typeCityCell = document.createElement("td");
                typeCityCell.style.padding = "4px";
                typeCityCell.style.width = "65px"; // Ancho de la columna
                typeCityCell.style.verticalAlign = "middle"; // Centrado vertical

                // Contenedor principal para alinear el tipo/área a la izquierda y la ciudad a la derecha
                const cellContentWrapper = document.createElement("div");
                cellContentWrapper.style.display = "flex";
                cellContentWrapper.style.justifyContent = "space-between"; // Espacio entre elementos
                cellContentWrapper.style.alignItems = "center";          // Centrado verticalmente

                // --- Contenedor del Icono de Tipo y Área (lado izquierdo) ---
                const typeAreaWrapper = document.createElement("div");
                typeAreaWrapper.style.display = "flex";
                typeAreaWrapper.style.flexDirection = "column"; // Contenido apilado verticalmente
                typeAreaWrapper.style.alignItems = "center";    // Centrados horizontalmente

                // Icono de tipo de lugar (Polígono o Punto) - SIEMPRE debe estar
                const typeIconSpan = document.createElement("span");
                typeIconSpan.textContent = typeInfo.icon;
                typeIconSpan.style.fontSize = "20px"; // Tamaño del icono
                typeIconSpan.title = `Tipo: ${typeInfo.title}`;
                typeAreaWrapper.appendChild(typeIconSpan); // Añadir el icono base (ARRIBA)

                // Mostrar área solo si es un polígono y se calculó el área
                if (typeInfo.isArea && areaMeters !== null) {
                    const areaSpan = document.createElement("span");
                    const formattedArea = areaMeters.toLocaleString(getWazeLanguage(), { maximumFractionDigits: 0 });
                    areaSpan.innerHTML = `${formattedArea}m<sup>2</sup>`; // Formato: 329m² (con superíndice)
                    areaSpan.style.fontSize = "9px"; // Números pequeños
                    areaSpan.style.fontWeight = "bold";
                    areaSpan.style.whiteSpace = "nowrap";
                    areaSpan.style.backgroundColor = "rgba(255,255,255,0.8)";
                    areaSpan.style.padding = "0 3px";
                    areaSpan.style.borderRadius = "3px";
                    areaSpan.style.marginTop = "2px"; // <-- AJUSTE CLAVE: Empujar hacia abajo, más espacio
                    areaSpan.style.marginBottom = "0"; // Asegurarse de que no haya margen inferior extra

                    // Color rojo si es menor de 400m²
                    if (areaMeters < 400) {
                        areaSpan.style.color = "red";
                        areaSpan.title = `Área: ${formattedArea} m²\n(Menor a 400m² para ser visible en la app)`;
                    } else {
                        areaSpan.style.color = "blue";
                        areaSpan.title = `Área: ${formattedArea} m²`;
                    }
                    typeAreaWrapper.appendChild(areaSpan); // Añadir el área al wrapper de tipo (ABAJO)
                }
                
                cellContentWrapper.appendChild(typeAreaWrapper); // Añadir el wrapper de tipo/área a la celda principal

                // --- Contenedor del Icono de Ciudad (lado derecho) ---
                if (!hasCity) { // Si NO tiene ciudad, muestra el icono de ciudad en rojo
                    const cityIconSpan = document.createElement("span");
                    cityIconSpan.innerHTML = cityIcon;
                    cityIconSpan.style.color = "red";
                    cityIconSpan.style.fontSize = "18px";
                    cityIconSpan.title = cityTitle;
                    cellContentWrapper.appendChild(cityIconSpan); // Añadir al contenedor principal
                }

                typeCityCell.appendChild(cellContentWrapper); // Añadir el contenedor principal a la celda

                typeCityCell.style.textAlign = "center";
                typeCityCell.style.fontSize = "20px";
                row.appendChild(typeCityCell); // Añadir la celda a la fila
                //Columna Bloqueo (LL)
                const lockCell = document.createElement("td");
                lockCell.textContent = lockRankEmoji; // Usa el emoji obtenido
                lockCell.style.textAlign = "center"; //
                lockCell.style.padding = "4px"; //
                lockCell.style.width = "40px"; // Mismo ancho que en el thead
                lockCell.style.fontSize = "18px";
                row.appendChild(lockCell); //
                // Columna Editor (username)
                const editorCell = document.createElement("td");
                editorCell.textContent =  editor || "Desconocido"; // Use the stored editor name
                editorCell.title = "Último editor";
                editorCell.style.padding = "4px";
                editorCell.style.width = "140px";
                editorCell.style.textAlign = "center";
                row.appendChild(editorCell);
                const originalCell = document.createElement("td");
                const inputOriginal = document.createElement("input");
                inputOriginal.type = "text";
// Añadir logs para depurar el problema del nombre vacío
// console.log(`[WME_PLN][DEBUG] Procesando fila para ID: ${id}`);
//  console.log(`[WME_PLN][DEBUG] Nombre 'original' del inconsistents (passed in): "${original}"`); // Este es el que debería ir
                const venueLive = W.model.venues.getObjectById(id);
                // Añadir logs para ver el objeto venueLive y su nombre
                //  console.log(`[WME_PLN][DEBUG] Objeto venueLive para ID ${id}:`, venueLive);
                // console.log(`[WME_PLN][DEBUG] venueLive?.attributes?.name:`, venueLive?.attributes?.name);
                const currentLiveName = venueLive?.attributes?.name?.value || venueLive?.attributes?.name || "";
//  console.log(`[WME_PLN][DEBUG] Nombre actual del W.model.venues (para inputOriginal): "${currentLiveName}"`);
                // Usar 'original' (del objeto inconsistents) como fallback si currentLiveName está vacío.
                inputOriginal.value = currentLiveName || original; //    
            // Si el nombre actual es distinto del sugerido, resalta en rojo.
                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"; // Asegura el ancho del input
                inputOriginal.style.backgroundColor = "#eee";                
                // convierte la celda en flex y centra verticalmente           
                originalCell.style.padding = "4px";
                originalCell.style.width = "270px"; // Asegura el ancho de la celda
                originalCell.style.display      = "flex";
                originalCell.style.alignItems   = "flex-start";
                originalCell.style.verticalAlign = "middle";
                // haces que el input llene el alto
                inputOriginal.style.flex       = "1";
                inputOriginal.style.height     = "100%";
                inputOriginal.style.boxSizing  = "border-box";
                originalCell.appendChild(inputOriginal); // input al originalCell
                row.appendChild(originalCell); // celda "Nombre Actual" a la fila
                // Alertas
                const alertCell = document.createElement("td");
                alertCell.style.width = "30px"; // Ancho de la columna de alerta
                alertCell.style.textAlign = "center";
                alertCell.style.verticalAlign = "middle"; // Centra el contenido verticalmente
                alertCell.style.padding = "4px";
                if (isDuplicate)
                { // Si el lugar es un duplicado, añade el icono de advertencia
                    const warningIcon = document.createElement("span");
                    warningIcon.textContent = " ⚠️"; // El símbolo de advertencia
                    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); //  icono a la nueva celda de alerta
                }                
                row.appendChild(alertCell); // nueva celda de alerta a la fila            
                const suggestionCell = document.createElement("td");
                // : Asegurar estilos flex para alineación vertical
                suggestionCell.style.display = "flex";
                suggestionCell.style.alignItems = "flex-start";
                suggestionCell.style.justifyContent = "flex-start";
                suggestionCell.style.padding = "4px";            
                // Según la cabecera: "Nombre Sugerido" tiene width "270px".
                suggestionCell.style.width = "270px"; // Mantener este ancho para la celda
                // --- Renderizar input principal de sugerencia ---
                const inputReplacement = document.createElement("input");
                inputReplacement.className = 'replacement-input'; // Clase para poder seleccionarlo fácilmente
                inputReplacement.type = "text";
                inputReplacement.value = normalized;
                inputReplacement.style.width = "100%";
                inputReplacement.style.height = "100%";
                inputReplacement.style.boxSizing = "border-box";
                suggestionCell.appendChild(inputReplacement); // Añadir el input a la celda
                
                //Función debounce (necesaria para el listener)
                function debounce(func, delay)
                {
                    let timeout;
                    return function (...args)
                    {
                        clearTimeout(timeout);
                        timeout = setTimeout(() => func.apply(this, args), delay);
                    };
                }
                // Habilitar/deshabilitar el botón Aplicar ---
                const checkAndUpdateApplyButton = () =>
                {
                    // Compara el valor actual del campo con el nombre original del lugar
                    const nameIsDifferent = inputReplacement.value.trim() !== original.trim();
                    // Comprueba si la categoría ha sido cambiada
                    const categoryWasChanged = row.dataset.categoryChanged === 'true';
                    if (nameIsDifferent || categoryWasChanged)
                    {
                        // Si el nombre es diferente O la categoría cambió -> Habilitar botón
                        applyButton.disabled = false;
                        applyButton.style.opacity = "1";
                        const successIcon = applyButtonWrapper.querySelector('span');
                        if (successIcon) successIcon.remove();
                    }
                    else
                    {
                        // Si no hay cambios -> Deshabilitar botón
                        applyButton.disabled = true;
                        applyButton.style.opacity = "0.5";
                    }
                };           
                // 2. LISTENER ÚNICO: Se añade el listener para cuando el usuario escribe manualmente.
                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"; // verde claro
                    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"; // amarillo claro
                    inputReplacement.title = "Contiene palabra especial reemplazada";
                }
                // --- Función debounce 
                function debounce(func, delay)
                {
                    let timeout;
                    return function(...args) {
                        clearTimeout(timeout);
                        timeout = setTimeout(() => func.apply(this, args), delay);
                    };
                }
                // --- Activar/desactivar el botón Aplicar 
                inputReplacement.addEventListener('input', debounce(() =>
                {
                    if (inputReplacement.value.trim() !== original)
                    {
                        applyButton.disabled = false;
                        applyButton.style.color = "";
                    }
                    else
                    {
                        applyButton.disabled = true;
                        applyButton.style.color = "#bbb";
                    }
                }, 300));
                // --- Listener para inputOriginal 
                inputOriginal.addEventListener('input', debounce(() =>
                {
                    // Opcional: alguna lógica si se desea manejar cambios en inputOriginal
                }, 300));
                // --- Lógica Unificada Para Renderizar Todas Las Sugerencias ---            
                const suggestionListCell = document.createElement("td");
                suggestionListCell.style.padding = "4px";
                suggestionListCell.style.width = "180px";
                const suggestionContainer = document.createElement('div');
                const palabrasYaProcesadas = new Set();
                // Asegurarse de que allSuggestions[id] existe
                const currentPlaceSuggestions = allSuggestions[id];
                // Asegurarse de que currentPlaceSuggestions es un objeto
                if (currentPlaceSuggestions)
                {
                    // Aquí usamos `suggestionsArray` para evitar el conflicto con `suggestions` que podría venir de un nombre de variable anterior
                    Object.entries(currentPlaceSuggestions).forEach(([originalWordForThisPlace, suggestionsArray]) =>
                    {
                        // Verificar si 'suggestionsArray' es realmente un array antes de intentar 'forEach'.
                        if (Array.isArray(suggestionsArray))
                        {
                            suggestionsArray.forEach(s =>
                            { // Aquí ya NO deberías tener error de "not defined"
                                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': // ESTE ES EL CASE ORIGINAL PARA DICCIONARIO
                                        esSugerenciaValida = true; //
                                        icono = '📘'; //
                                        colorFondo = '#e6ffe6'; // Color de fondo para sugerencias del diccionario
                                        // 1. Normalizamos la palabra sugerida para que tenga la capitalización correcta.
                                        // Por ejemplo, convierte "clínica" (del diccionario) en "Clínica".
                                        // Usamos normalizeWordInternal que es más robusta para esto.
                                        const normalizedSuggestedWordForDisplay = normalizeWordInternal(s.word, true, false); //

                                        // 2. Usamos esa palabra normalizada para mostrarla en el texto de la sugerencia.
                                        textoSugerencia = `¿"${originalWordForThisPlace}" x "${normalizedSuggestedWordForDisplay}"? (sim. ${(s.similarity * 100).toFixed(0)}%)`; //
                                        // 3. Asignamos las palabras correctas para la acción de reemplazo.
                                        palabraAReemplazar = originalWordForThisPlace; //
                                        // Usamos la versión normalizada para que se inserte con la capitalización correcta.
                                        palabraAInsertar = normalizedSuggestedWordForDisplay; //
                                        break; //

                                    // ************************************************************
                                    // NUEVO CASO PARA SUGERENCIAS DE TILDES ESPECÍFICAS
                                    case 'dictionary_tilde': //
                                        esSugerenciaValida = true; //
                                        icono = '✍️'; // Un ícono que sugiera "escritura" o "corrección"
                                        colorFondo = '#ffe6e6'; // Un color rojizo claro o rosado para resaltar corrección de tilde
                                        // El s.word ya viene con la capitalización deseada desde la lógica anterior
                                        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);
                                        // Actualizar el inputReplacement con el nuevo valor sugerido
                                        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);
                // Se añaden las celdas a la fila
                row.appendChild(suggestionCell);
                row.appendChild(suggestionListCell);                
                // Columna Categoría (nombre y luego ícono abajo)
                const categoryCell = document.createElement("td");
                categoryCell.style.padding = "4px";
                categoryCell.style.width = "130px";
                categoryCell.style.textAlign = "center"; // Centra el contenido en la celda
                // Columna Categoría (nombre y luego ícono abajo)
                const currentCategoryDiv = document.createElement("div"); // Contenedor para el nombre y el ícono
                currentCategoryDiv.style.display = "flex";
                currentCategoryDiv.style.flexDirection = "column"; // Elementos apilados verticalmente
                currentCategoryDiv.style.alignItems = "center";     // Centrar horizontalmente
                currentCategoryDiv.style.gap = "2px"; // Pequeño espacio entre el nombre y el ícono
                // Crear el texto y el ícono de la categoría actual
                const currentCategoryText = document.createElement("span");
                currentCategoryText.textContent = currentCategoryTitle;
                currentCategoryText.title = `Categoría Actual: ${currentCategoryTitle}`; // Tooltip para la categoría actual
                currentCategoryDiv.appendChild(currentCategoryText);
                // Crear el ícono de la categoría actual
                const currentCategoryIconDisplay = document.createElement("span"); // Contenedor para el ícono de la categoría actual
                currentCategoryIconDisplay.textContent = currentCategoryIcon; // Usar el ícono de la categoría actual
                currentCategoryIconDisplay.style.fontSize = "20px"; // Tamaño del ícono
                currentCategoryDiv.appendChild(currentCategoryIconDisplay);// Añadir el ícono al contenedor
                // Añadir el contenedor de categoría al cell
                categoryCell.appendChild(currentCategoryDiv);
                row.appendChild(categoryCell);
                // Columna Categoría Recomendada (Ahora con un dropdown de búsqueda)
                const recommendedCategoryCell = document.createElement("td"); //  Línea existente
                recommendedCategoryCell.style.padding = "4px"; 
                recommendedCategoryCell.style.width = "180px"; 
                recommendedCategoryCell.style.textAlign = "left"; // Alinear a la izquierda para el dropdown
                // Crear y adjuntar el nuevo dropdown de categoría con búsqueda
                const categoryDropdown = createRecommendedCategoryDropdown( 
                    id, // ID del lugar para actualizar la categoría 
                    currentCategoryKey, // La categoría actual del lugar 
                    dynamicCategorySuggestions // Las sugerencias dinámicas 
                ); 
                recommendedCategoryCell.appendChild(categoryDropdown); 
                row.appendChild(recommendedCategoryCell); 
                // --- Columna Acción ---
                const actionCell = document.createElement("td");
                actionCell.style.padding = "4px";
                actionCell.style.width = "120px";
                // Crear botones de acción
                const buttonGroup = document.createElement("div"); // Contenedor principal de los botones de acción
                buttonGroup.style.display = "flex";
                buttonGroup.style.flexDirection = "column"; // VERTICAL: Apilar los elementos (wrappers de botones)
                buttonGroup.style.gap = "4px"; // Espacio entre los grupos de botones
                buttonGroup.style.alignItems = "flex-start"; // Alinear los grupos de botones a la izquierda
                // Estilos Comunes Para Todos Los Botones De Acción Para Homogeneidad
                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"
                };
                // 1. Botón de aplicar sugerencia (EL BOTÓN GRIS CON EL CHULITO)
                const applyButton = document.createElement("button");
                Object.assign(applyButton.style, commonButtonStyle);
                applyButton.textContent = "✔"; // Texto o icono para el botón de aplicar
                applyButton.title = "Aplicar sugerencia";
                applyButton.disabled = true;// Deshabilitado inicialmente
                applyButton.style.opacity = "0.5"; // Botón deshabilitado inicialmente
                // Contenedor para el botón Aplicar y el chulito VERDE de confirmación
                const applyButtonWrapper = document.createElement("div");
                applyButtonWrapper.style.display = "flex"; // Horizontal: Botón y chulito uno al lado del otro
                applyButtonWrapper.style.alignItems = "center";
                applyButtonWrapper.style.gap = "5px";
                applyButtonWrapper.appendChild(applyButton);
                buttonGroup.appendChild(applyButtonWrapper); // Añadir el wrapper al grupo principal de botones
                // 2. Botón de eliminar lugar (EL BOTÓN GRIS CON EL BOTE DE BASURA)
                let deleteButton = document.createElement("button"); 
                Object.assign(deleteButton.style, commonButtonStyle);
                deleteButton.textContent = "🗑️";
                deleteButton.title = "Eliminar lugar";                      
                // Contenedor para el botón Eliminar y el icono de bote de basura de confirmación
                const deleteButtonWrapper = document.createElement("div");
                Object.assign(deleteButtonWrapper.style, { // Asegurar estilos para el wrapper del deleteButton también
                    display: "flex",
                    alignItems: "center",
                    gap: "5px"
                });
                deleteButtonWrapper.appendChild(deleteButton);
                buttonGroup.appendChild(deleteButtonWrapper); // Añadir el wrapper al grupo principal de botones
                // 3. Botón para añadir a la lista de exclusión (EL BOTÓN GRIS CON LA ETIQUETA)
                const addToExclusionBtn = document.createElement("button");
                Object.assign(addToExclusionBtn.style, commonButtonStyle); // Aplicar estilos comunes
                addToExclusionBtn.textContent = "🏷️";
                addToExclusionBtn.title = "Marcar palabra como especial (no se modifica)";
                buttonGroup.appendChild(addToExclusionBtn); // Este botón se añade directamente al buttonGroup (no necesita un wrapper flex si siempre va solo)
                actionCell.appendChild(buttonGroup); // Añadir el grupo de botones a la celda de acción
                row.appendChild(actionCell);
                
                 // 4. Botón para añadir lugar completo a la lista de excluidos
                const excludePlaceBtn = document.createElement("button");
                Object.assign(excludePlaceBtn.style, commonButtonStyle);
                excludePlaceBtn.textContent = "📵"; // Ícono de "más"
                excludePlaceBtn.title = "Excluir este lugar (no aparecerá en futuras búsquedas)";
                buttonGroup.appendChild(excludePlaceBtn);

                actionCell.appendChild(buttonGroup);
                row.appendChild(actionCell);
                
                // --- LISTENERS  ---
                // Listener para el botón "APLICAR" (Versión Mejorada)
                applyButton.addEventListener("click", async () =>
                {
                    const venueObj = W.model.venues.getObjectById(id);
                    if (!venueObj)
                    {
                        console.error("[WME_PLN] Error: El lugar no está disponible o ya fue eliminado.");
                        return;
                    }
                    const newName = inputReplacement.value.trim();
                    const currentLiveNameInWaze = venueObj?.attributes?.name?.value || venueObj?.attributes?.name || "";
                    // Obtenemos la fila y revisamos si la categoría fue cambiada
                    const row = applyButton.closest('tr');
                    const nameWasChanged = (newName !== currentLiveNameInWaze);
                    const categoryWasChanged = row.dataset.categoryChanged === 'true';
                    try
                    {
                        if (nameWasChanged)
                        {
                            // CASO 1: El nombre ha cambiado (con o sin cambio de categoría)
                            const UpdateObject = require("Waze/Action/UpdateObject");
                            const action = new UpdateObject(venueObj, { name: newName });
                            W.model.actionManager.add(action);
                            // Actualizar el nombre en el venue
                            showTemporaryMessage("Lugar normalizado. Presione 'Guardar' para finalizar.", 4000, 'success');
                            recordNormalizationEvent(); // Registrar el evento de normalización
                            // Feedback visual de éxito
                            applyButton.disabled = true;
                            applyButton.style.opacity = "0.5";
                            const successIcon = document.createElement("span");
                            successIcon.textContent = " ✅";
                            successIcon.style.fontSize = "20px";
                            applyButtonWrapper.appendChild(successIcon);
                            row.dataset.categoryChanged = 'false'; // Reseteamos la bandera
                        }
                        else if (categoryWasChanged)
                        {
                            // CASO 2: El nombre NO ha cambiado, PERO la categoría SÍ
                            showTemporaryMessage("Cambios de categoría aplicados correctamente.", 3000, 'success');
                            recordNormalizationEvent();// Registrar el evento de normalización
                            // Feedback visual de éxito
                            applyButton.disabled = true;
                            applyButton.style.opacity = "0.5";
                            const successIcon = document.createElement("span");
                            successIcon.textContent = " ✅";
                            successIcon.style.fontSize = "20px";
                            applyButtonWrapper.appendChild(successIcon);                        
                            row.dataset.categoryChanged = 'false'; // Reseteamos la bandera
                        }
                        else
                        {
                            // CASO 3: No ha cambiado ni el nombre ni la categoría
                            showTemporaryMessage("No hay cambios para aplicar.", 3000, 'warning');
                        }
                        if (nameWasChanged || categoryWasChanged)
                        {
                            markRowAsProcessed(row, 'applied'); // Marcar la fila como aplicada
                            // Aquí necesitas actualizar el contador de inconsistencias en la cabecera
                            updateInconsistenciesCount(-1); // Función auxiliar que crearemos
                        }
                    }
                    catch (e)
                    {
                        alert("Error al actualizar: " + e.message);
                        console.error("[WME_PLN] Error al actualizar lugar:", e);
                    }
                });
                // Listener para el botón de "ELIMINAR"
                deleteButton.addEventListener("click", () =>
                {
                    // Modal bonito de confirmación
                    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 AUMENTADO
                    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 = "⚠️";
                    iconElement.style.fontSize = "38px";
                    iconElement.style.marginBottom = "10px";
                    confirmModal.appendChild(iconElement);
                    // Mensaje principal
                    const message = document.createElement("div");
                    const venue = W.model.venues.getObjectById(id);
                    // Asegurarse de que placeName siempre tenga un valor para mostrar en el modal
                    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);
                    // Nombre del lugar
                    const nameDiv = document.createElement("div");
                    nameDiv.textContent = `"${placeName}"`;
                    nameDiv.style.fontSize = "15px";
                    nameDiv.style.color = "#007bff";
                    nameDiv.style.marginBottom = "18px";
                    confirmModal.appendChild(nameDiv);
                    // Botones de confirmación
                    const buttonWrapper = document.createElement("div");
                    buttonWrapper.style.display = "flex";
                    buttonWrapper.style.justifyContent = "center";
                    buttonWrapper.style.gap = "18px";
                    // Botones de confirmación
                    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 de confirmación
                    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";
                    // Listener para el botón de confirmación
                    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(); // Asegurarse de cerrar el modal
                            return;
                        }
                        try 
                        {
                            const DeleteObject = require("Waze/Action/DeleteObject");
                            const action = new DeleteObject(venue);
                            W.model.actionManager.add(action); // Ejecutar la acción de eliminación en Waze
                            recordNormalizationEvent(); // Registrar el evento de normalización
                           const row = deleteButton.closest('tr');
                            markRowAsProcessed(row, 'deleted'); // Marcar la fila como eliminada
                            updateInconsistenciesCount(-1); // Función auxiliar
                           
                            // Deshabilitar el botón de eliminar actual
                            deleteButton.disabled = true;
                            deleteButton.style.color = "#bbb";
                            deleteButton.style.opacity = "0.5";
                            // Deshabilitar el botón de aplicar de esta fila
                            applyButton.disabled = true; 
                            applyButton.style.color = "#bbb";
                            applyButton.style.opacity = "0.5";
                            // Añadir el icono de éxito visual al lado del botón de eliminar
                            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(); // Cerrar el modal después de la acción (éxito o fallo)
                    });
                    buttonWrapper.appendChild(cancelBtn);
                    buttonWrapper.appendChild(confirmBtn);
                    confirmModal.appendChild(buttonWrapper);
                    document.body.appendChild(confirmModal); // Añadir el modal al body para que sea visible
                });
                // Listener para el botón de añadir a exclusión
                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 #ccc";
                    modal.style.padding = "10px";
                    modal.style.zIndex = "20000";
                    modal.style.maxWidth = "300px";
                    // Añadir un borde redondeado
                    const title = document.createElement("h4");
                    title.textContent = "Agregar palabra a especiales";
                    modal.appendChild(title);
                    // Añadir un mensaje de instrucciones
                    const instructions = document.createElement("p");
                    const list = document.createElement("ul");
                    list.style.listStyle = "none";
                    list.style.padding = "0";
                    words.forEach(w =>
                    {
                        if (w.trim() === '') return;
                        const lowerW = w.trim().toLowerCase();
                        if (!/[a-zA-ZáéíóúÁÉÍÓÚñÑüÜ0-9]/.test(lowerW) || /^[^a-zA-Z0-9]+$/.test(lowerW)) return;
                        const alreadyExists = Array.from(excludedWords).some(existing => existing.toLowerCase() === lowerW);
                        if (commonWords.includes(lowerW) || alreadyExists) return;
                        // Crear un checkbox para cada palabra
                        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);
                    });
                    modal.appendChild(list);
                    // Botones de confirmación
                    const confirmBtn = document.createElement("button");
                    confirmBtn.textContent = "Añadir Seleccionadas";
                    confirmBtn.addEventListener("click", () =>
                    {
                        const checked = modal.querySelectorAll("input[type=checkbox]:checked");
                        let wordsActuallyAdded = false;
                        // Añadir las palabras seleccionadas al Set de palabras excluidas
                        checked.forEach(c =>
                        {
                            if (!excludedWords.has(c.value))
                            {
                                excludedWords.add(c.value);
                                wordsActuallyAdded = true;
                            }
                        });
                        // Actualizar el mapa de palabras excluidas
                        if (wordsActuallyAdded)
                        {
                            if (typeof renderExcludedWordsList === 'function')
                            {
                                const excludedListElement = document.getElementById("excludedWordsList");
                                if (excludedListElement)
                                {
                                    renderExcludedWordsList(excludedListElement);
                                }
                                else
                                {
                                    renderExcludedWordsList();
                                }
                            }
                        }
                        modal.remove();
                        if (wordsActuallyAdded)
                        { // Solo guardar si realmente se añadió algo
                           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);
                    // Botón de cancelar
                    const cancelBtn = document.createElement("button");
                    cancelBtn.textContent = "Cancelar";
                    cancelBtn.style.marginLeft = "8px";
                    cancelBtn.addEventListener("click", () => modal.remove());
                    modal.appendChild(cancelBtn);
                    document.body.appendChild(modal);
                });
                buttonGroup.appendChild(addToExclusionBtn);      
                
                // Listener para el botón "Excluir Lugar" (el botón con el ícono de prohibido 📵)
                excludePlaceBtn.addEventListener("click", () =>
                {
                    const placeName = original || `ID: ${id}`; 
                    // ************************************************************
                    // 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 "prohibido" o "no permitido"
                    iconElement.style.fontSize = "38px";
                    iconElement.style.marginBottom = "10px";
                    confirmModal.appendChild(iconElement);

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

                    // Mensaje explicativo
                    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);

                    // 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 Exclusión
                    const confirmExcludeBtn = document.createElement("button");
                    confirmExcludeBtn.textContent = "Excluir";
                    confirmExcludeBtn.style.padding = "7px 18px";
                    confirmExcludeBtn.style.background = "#d9534f"; // Rojo
                    confirmExcludeBtn.style.color = "#fff";
                    confirmExcludeBtn.style.border = "none";
                    confirmExcludeBtn.style.borderRadius = "4px";
                    confirmExcludeBtn.style.cursor = "pointer";
                    confirmExcludeBtn.style.fontWeight = "bold";

                    confirmExcludeBtn.addEventListener("click", () => {
                        // Aquí va la lógica que antes estaba directamente en el if(confirm)
                        excludedPlaces.set(id, placeName); // Guardar ID y Nombre
                        saveExcludedPlacesToLocalStorage(); 
                        showTemporaryMessage("Lugar excluido de futuras búsquedas.", 3000, 'success');

                        const row = excludePlaceBtn.closest('tr');
                        if (row) {
                            markRowAsProcessed(row, 'excluded'); // Marcar la fila como excluida
                            updateInconsistenciesCount(-1); // Función auxiliar
                        }
                        confirmModal.remove(); // Cerrar el modal después de la acción
                    });

                    buttonWrapper.appendChild(cancelBtn);
                    buttonWrapper.appendChild(confirmExcludeBtn);
                    confirmModal.appendChild(buttonWrapper);
                    document.body.appendChild(confirmModal); // Añadir el modal al body
                    // ************************************************************
                    // FIN DE LA MODIFICACIÓN
                    // ************************************************************
                });
               

                actionCell.appendChild(buttonGroup);
                row.appendChild(actionCell);
                // Añadir borde inferior visible entre cada lugar
                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();//
                // Actualizar progreso al final del ciclo usando setTimeout para evitar bloqueo de UI            
                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);
            });// forEach
            // Adjuntar tbody a la tabla (fuera del forEach)
            table.appendChild(tbody);
            // Adjuntar la tabla completa al contenedor de salida (output)
            output.appendChild(table); 
            // Quitar overlay spinner justo antes de mostrar la tabla
            const existingOverlay = document.getElementById("scanSpinnerOverlay");
            if (existingOverlay)
            {
                existingOverlay.remove();
            }
            // Al finalizar, actualizar el texto final en el tab principal (progreso 100%)
            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})`;
            }

            // Función para reactivar todos los botones de acción en el panel flotante
            function reactivateAllActionButtons()
            {
                document.querySelectorAll("#wme-place-inspector-output button")
                    .forEach(btn =>
                    {
                        btn.disabled = false;
                        btn.style.color = "";
                        btn.style.opacity = "";
                    });
            }// reactivateAllActionButtons

            W.model.actionManager.events.register("afterundoaction", null, () =>
            {
                // Verificar si el panel flotante está visible
                if (floatingPanelElement && floatingPanelElement.style.display !== 'none')
                {
                    waitForWazeAPI(() =>
                    {
                        const places = getVisiblePlaces();
                        renderPlacesInFloatingPanel(places); // Esto mostrará el panel de "procesando" y luego resultados
                        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, () =>
            {
                // Verificar si el panel flotante está visible
                if (floatingPanelElement && floatingPanelElement.style.display !== 'none')
                {
                    waitForWazeAPI(() =>
                    {
                        const places = getVisiblePlaces();
                        renderPlacesInFloatingPanel(places); // Esto mostrará el panel de "procesando" y luego resultados
                        setTimeout(reactivateAllActionButtons, 250);
                    });
                }
                else
                {
                    console.log("[WME PLN] Undo/Redo: Panel de resultados no visible, no se re-escanea.");
                }
            });           
        }
    }// 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();
        // Si las palabras son diferentes, pero al quitarles las tildes son idénticas,
        // dales una similitud muy alta (99%) para priorizarlas siempre.
        if (w1_lower !== w2_lower && removeDiacritics(w1_lower) === removeDiacritics(w2_lower))
        {
            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);
        if (maxLen === 0) return 1;
        return 1 - distance / maxLen;
    }// 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[firstChar])
        {
            candidates = Array.from(indexedListOrArray[firstChar] || []); // Esto es lo correcto para tu window.dictionaryIndex
        }
        // Si es un Set o Array (menos óptimo, pero fallback)
        else if (indexedListOrArray instanceof Set || Array.isArray(indexedListOrArray))
        {
            candidates = Array.from(indexedListOrArray).filter(candidate => candidate.charAt(0).toLowerCase() === firstChar);
        }
        else
        {
            return [];
        }

        return candidates
            .map(candidate =>
            {
                const similarity = calculateSimilarity(lowerWord, candidate.toLowerCase());
                return { word: candidate, 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 =>
        {
            const similar = findSimilarWords(word, Array.from(excludedWords), 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
    function markRowAsProcessed(rowElement, actionType) {
        if (!rowElement) return;

        // 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

        // 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 de Waze';
            } else if (actionType === 'excluded') {
                icon = '🚫';
                tooltip = 'Lugar excluido de futuras búsquedas';
            }
            if (icon) {
                const statusIcon = document.createElement('span');
                statusIcon.textContent = icon;
                statusIcon.style.marginLeft = '5px';
                statusIcon.style.fontSize = '1.2em';
                statusIcon.title = tooltip;
                numberCell.appendChild(statusIcon);
            }
        }
    }// markRowAsProcessed

    // Muestra un mensaje temporal en la parte superior de la pantalla
    function showTemporaryMessage(message, duration = 3000, type = 'info')
    {
        // Crear el elemento del popup
        const popup = document.createElement('div');
        popup.textContent = message;
        // Estilos base para el popup
        popup.style.position = 'fixed';
        popup.style.top = '70px';
        popup.style.left = '50%';
        popup.style.transform = 'translateX(-50%)';
        popup.style.padding = '12px 25px';
        popup.style.borderRadius = '6px';
        popup.style.color = 'white';
        popup.style.fontWeight = 'bold';
        popup.style.zIndex = '25000'; // Un z-index muy alto para que esté por encima de todo
        popup.style.boxShadow = '0 4px 10px rgba(0,0,0,0.15)';
        popup.style.opacity = '0';
        popup.style.transition = 'opacity 0.4s ease, top 0.4s ease'; // Animación suave
        // Estilos según el tipo de mensaje
        switch (type)
        {
            case 'success':
                popup.style.backgroundColor = '#28a745'; // Verde
                break;
            case 'warning':
                popup.style.backgroundColor = '#ffc107'; // Amarillo/Naranja
                popup.style.color = '#212529'; // Texto oscuro para mejor contraste sobre amarillo
                break;
            case 'error':
                popup.style.backgroundColor = '#dc3545'; // Rojo
                break;
            default: // 'info'
                popup.style.backgroundColor = '#17a2b8'; // Azul
                break;
        }
        // Añadir el popup al body y animar su entrada
        document.body.appendChild(popup);
        setTimeout(() =>
        {
            popup.style.opacity = '1';
            popup.style.top = '90px'; // Se desliza hacia abajo
        }, 50);
        // Configurar la animación de salida y eliminación del DOM
        setTimeout(() =>
        {
            popup.style.opacity = '0';
            popup.style.top = '70px'; // Se desliza hacia arriba
            // Eliminar el elemento del DOM después de que termine la transición
            popup.addEventListener('transitionend', () => popup.remove());
            // Agregamos un fallback por si el evento transitionend 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";
            // Dimensiones del panel
            const closeBtn = document.createElement("span");
            closeBtn.textContent = "×";
            closeBtn.style.position = "absolute";
            closeBtn.style.top = "8px";
            closeBtn.style.right = "12px";
            closeBtn.style.cursor = "pointer";
            closeBtn.style.fontSize = "22px";
            closeBtn.style.color = "#555";
            closeBtn.title = "Cerrar panel";
            closeBtn.addEventListener("click", () =>
            {
                if (floatingPanelElement) floatingPanelElement.style.display = 'none';
                     resetInspectorState();
            });
            floatingPanelElement.appendChild(closeBtn);
            // Dimensiones del panel de procesamiento
            const titleElement = document.createElement("h4");
            titleElement.id = "wme-pln-panel-title";
            titleElement.style.marginTop = "0";
            titleElement.style.marginBottom = "10px";
            titleElement.style.fontSize = "20px";
            titleElement.style.color = "#333";
            titleElement.style.textAlign = "center";
            titleElement.style.fontWeight = "bold";
            floatingPanelElement.appendChild(titleElement);
            // Dimensiones del panel de resultados
            const outputDivLocal = document.createElement("div");
            outputDivLocal.id = "wme-place-inspector-output";
            outputDivLocal.style.fontSize = "18px";
            outputDivLocal.style.backgroundColor = "#fdfdfd";
            outputDivLocal.style.overflowY = "auto";
            floatingPanelElement.appendChild(outputDivLocal);
            document.body.appendChild(floatingPanelElement);
        }
        // Dimensiones del panel de procesamiento
        const titleElement = floatingPanelElement.querySelector("#wme-pln-panel-title");
        // Dimensiones del panel de resultados
        const outputDiv = floatingPanelElement.querySelector("#wme-place-inspector-output");
        // Dimensiones del panel de procesamiento
        if(outputDiv) outputDiv.innerHTML = "";
        // Dimensiones del panel de procesamiento
        if (status === "processing")
        {
            floatingPanelElement.style.width = processingPanelDimensions.width;
            floatingPanelElement.style.height = processingPanelDimensions.height;
            if(outputDiv) outputDiv.style.height = "150px";
            if(titleElement) titleElement.textContent = "Buscando...";
            if (outputDiv)
            {
                // Añadir un overlay spinner al panel de procesamiento
                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>";          
                const processingStepLabel = document.getElementById("processingStep");
            }
            // Centrar el panel de procesamiento
            floatingPanelElement.style.top = "50%";
            floatingPanelElement.style.left = "50%";
            floatingPanelElement.style.transform = "translate(-50%, -50%)";
        }
        else
        { // status === "results"
            floatingPanelElement.style.width = resultsPanelDimensions.width;
            floatingPanelElement.style.height = resultsPanelDimensions.height;
            if(outputDiv) outputDiv.style.height = "660px";
            if(titleElement) titleElement.textContent = "Resultado de la búsqueda";
            // Mover el panel de resultados más a la derecha
            floatingPanelElement.style.top = "50%";
            floatingPanelElement.style.left = "60%";
            floatingPanelElement.style.transform = "translate(-50%, -50%)";
        }
        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", () => 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;
                }
                //console.error("[WME PLN] Error registrando el sidebar tab:", e);
                throw e; // Relanzar otros errores para que se vean en consola
            }
            const { tabLabel, tabPane } = registration;
            if (!tabLabel || !tabPane)
            {
                //console.error("[WME PLN] Falló el registro del Tab: 'tabLabel' o 'tabPane' no fueron retornados.");
                return;
            }
            // Configurar el ícono y nombre de la pestaña principal del script
            tabLabel.innerHTML = `
            <img src="data:image/jpeg;base64,/9j/4QDKRXhpZgAATU0AKgAAAAgABgESAAMAAAABAAEAAAEaAAUAAAABAAAAVgEbAAUAAAABAAAAXgEoAAMAAAABAAIAAAITAAMAAAABAAEAAIdpAAQAAAABAAAAZgAAAAAAAABIAAAAAQAAAEgAAAABAAeQAAAHAAAABDAyMjGRAQAHAAAABAECAwCgAAAHAAAABDAxMDCgAQADAAAAAQABAACgAgAEAAAAAQAAAoCgAwAEAAAAAQAAAqmkBgADAAAAAQAAAAAAAAAAAAD/2wCEAAEBAQEBAQIBAQIDAgICAwQDAwMDBAUEBAQEBAUGBQUFBQUFBgYGBgYGBgYHBwcHBwcICAgICAkJCQkJCQkJCQkBAQEBAgICBAICBAkGBQYJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCf/dAAQABv/AABEIAGUAXwMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AP7+KKKKACiivxO/4Kff8FePDv7Gt23wK+B9jbeKvird26zNBcMf7O0SGUfu7nUTGVd3Ycw2kbK8g5ZokIevQyvKq+MrLD4eN2/6+47MBl9XE1VRoq7P2rlnht4zNOwRFGSxOAB7noK5i18e+CL67+wWWsWMs/Ty0uImb/vkNmv86P42fHz9o/8Aaf1eTW/2lPiBrXitpWZvsAuXsdIiD/8ALOLTbQx2/lr0XzVlkx952PNfNsHwh+FdlOlzY+HNOtpoyGSWG3jjkUjoVdAGBHYg1+uYXwbk4fvq9n5RuvzX5H6Rh/C+q4/vKqT8l/wx/qNZFLX+ej+zZ+3h+2f+x/qMNx8G/Heoato0LBpPDnii4m1bTJlG3KI9w7XVqSowrQTKik7jG/Q/2Mf8E7v+Cjvwo/4KAfD25vdDtz4c8a6AI08QeGriUST2bSZCTwSAL9pspip8mdVXoUkWORWRfiuJ+A8Xlkfav3qfddPVdPyPlc/4QxOAXPLWHdf5dD9FqKK5bxp4v0bwH4Zu/FWvPstrOPccfeY9FRR3ZjgKPWvgMXi6WHpSr1pKMYq7b2SX+R81QoTqTVOmrt6JHU0V8kfAr9pTUfix4uufC+q6SlliB7iF4ZDIAqMqlZMgc/MMEcdsdK+t68DhHjHL88wax+WT5qd2tmtV5NJno51keJy6v9WxUbSt5fof/9D+/iiiigD5K/bq/acsP2Ov2TfG/wC0Tcwpd3Ph7TmbT7R2CC61Gdlt7G2z2865kjT8a/z2ftfifWtUv/GPj7UJNZ8Sa9cyajrGpTf6y7vZzullb0GeEQfKiBUXCqoH9d3/AAcVXl2n7C+haPGStrqHjnQo7nHdYGluYwfbzYkr+RYnnmv6H8I8vpwwM8T9qTt8klY/bPDPBQWGnX6t2+Ssdp8K/hT8Yf2gviVZ/Bn9n7w7N4p8U3qGYW0brDBbW6kK1zeXD/Jb26EjLHLH7qK74U/qF4j/AOCDH/BRrw74N/4SvTr7wTr+oLGHfQrS8vIJxgfMkN5cQLDK/ZQ6QqT1Ze36Wf8ABuH8PPBVn+zz8Rvi9DGj+J9c8YT6bfSHaZYbPTLeFbK3B6rFiV7hV6bpmPev6NK8Xi3xIxmGx0sNhUlGGmq3/wCB6Hj8R8dYuji5UcPZKOm39fgf5lFzba3o+t6l4R8XabdaHruiXL2Wp6Xfx+VdWdzHjdFKnY4IKkZV1KshKMpPoXwU/aD8ZfsgfGzw7+1R8PyxvPCU2/ULZP8Al/0aQqNRsmAxu8yFd8Q6CeOJv4cV+t3/AAcK+BPCXhT9tz4eeOvDsaQar4w8JajHrAT/AJbDR7u1Sxmcf3lW7mj3dWVVByEXH4i3j20dpLJesqQqjGRmIChAPmJJ4AA6+1fqmT42GZ5dCtUhpNar8H+Wnkfo2V4uGY4CNSpHSS2/A/0wfCnifRfGnhfTvGPhyYXOnaraw3lrKvSSGdBJGw9ipFfmh+1J8XX8feKx4K8PuZNM0qXZ+75+0XX3SQB1CfcT1OfavHv2Ovil43+FH/BKf4HeCfFENxp3i+/8GabbtDcqUuLa2SEIsrq3zK5i2BAQCCeR8pFfRv7I/wAGf7Y1Bfih4gi/0SyYpp8bdHlXgy/7sfRf9rn+EV/k/wCO+bYnOc5jwDkstW/30ltGC6fdaT7+7Hq0fHcG5PQymjVzzG6qF4013e11+S7avoj6Z/Zy+DqfC3wiLrVox/bOpBZLo9fLA+5CD6J/Fjq2e2K+iqTGOBS1/QXDHDmFyjAUsuwUbU6asv8AN+b3fmfk+bZpWxuIniq7vKX9W9Fsj//R/v4ooooA/K7/AILRfATWv2gf+CdfjzRfCls95rXhpLXxTp1vEPnmm0OdL0wr/tSxRvGOP4q/hs07UbPV9Pg1XTpFmt7mNZYnXlWRxlSMdiOlf6b80MVxC0E6h0cbWVhkEHggj0r/AD7f+Cgv7Hd3+wd+1dqvwhsIDF4K8Sm413wbLgBFsZJAbnThjgNp00gjVQABbvBjJ3Y/cPCPPIpTy+e/xR+7X8l+J+s+GmbxjzYKXXVfr+SOi/4J7/8ABQLx/wD8E7vinq/iHTtHl8V+BvFvlNr+hWzxxXaXMC+XFqFg0pSIziMCKWKRlWWNUw6GMbv6DPEP/BxT+wTZ+F5NR8JWHjLW9a8smLSE8P3VnK0gHCNc3Yis09NxmK+meK/kNZgME9K+ifgT+yJ+07+0zqMVh8EvBOp6tbybc6jNC1lpcasMiR764VIWTjnyfNf0Q19txFwVlWKqfXMX7vd3SXzv+lj6zO+EMvxFT6zXfL31SXz/AKRX/aH+Pnxo/bq/aTu/jT47sS2ua59m0fQ9A09muVsrQORbWNvkIZZZJZC0km1TJI38KKoT9f8A4F/8EdvDfw28a+H/ABj+2J460eWPS9uq6l4KsbeWWWby0EkNlNfecI5A0oHnRrABKmYwShZm/Qf9gn/gmd8PP2MJ4vif49vLfxj8SyjiK7jjI07SFkG1ksUf5pJinyPcvhiMhFiRih774r/BzxpoOq3nivSXn12xupHnm3fPdws5ydw/5aoOxHIHGMDNfyN9IH6SOOyPBrB8F0ozUdJSteytb3Vvp3WqOzJq2GxFT6lRqeypJWTSV5el17qt10b6W63/AAtoXib9pT4uST3uYYZSJLlk+7a2ifKkadgcfIn+1luxr9ddH0jTtA0u30XSIVgtbWNYoo0GAqKMACvgb9hPXri9i8QaTbQxNaRGCUzhcSea25fLY9wFXIH8PPrX6F1+DfRs4foRyZ55NudfEuTnJrXSTVl5XTfm35JL4DxVzKo8csviuWnSSUUttl+mnoFFFFf0Yflp/9L+/iiiigAr4P8A+CiX7Dfg79vb9na9+FGsTJpfiCwkGpeG9ZKb207VIVIikIGGaCRSYbiMEb4XYZBwR94UV04PF1MPVjWou0o7G2GxE6M1UpuzWx/mfXOleM/hR8ULrwL8V9DWy8U+B9Zt49Z0S5bMTy2U0VwYGcKd1reRBdsgXD28oYLztr+8H4G/tO/DX9rD4NWvxm+EF+8mkKFgv9LbC3Gk3SKN1rcQx8KUBBVhlHQq6EoytXyF/wAFf/8Agl8v7X3hJPj58B7WG2+Lnhe18uFeI49e0+MlzplyxwokUlms5m/1UhKE+XI9fy1/sJ/Gn9qzwH+09oOkfsXWdxcfETWp30u68OXqSQ2s8NpIVvIddhYBre3sWLebMyia1f5Y8ySeRN+t8U5dheL8l/ieyq00/Radf7rtofskcyoZphFiW1GdPo9v+Be2j6H9yWn6hJqLebBHstxwGbqx9h0AFaAuYt7qD/qsbvb2/KvSNc+H2qz6RHc6MILa+Ma+bChJhD4+byiQCAD93IHHYV51b+FNUluYvCsMMiPKf30jKRtT+JyenPav4PzDh3HYSqqMoXvs1s+yR5WGzPD1oc8Xa3TsepfCDQLLTdBn1uG3SGbVZjPIyqFLgfIhbAGeBXrVVrO1gsbSOytl2xxKEUegUYFWa/ecmy2OEwtPDR+yvx6/ifm2PxTr1pVX1/pfgFFFFemcZ//T/v4oopOlAH5M/wDBZL/gpvaf8Esf2Urf43aZo9t4k8Ta5rVpomiaTdSywQzyyB57l5ZII5HjjgtIZpSwU5YKnVgK+Q/+CHX/AAXNuP8AgrD4j8d/Dvx/4X0vwj4j8KWljqllBpV9Lew3mn3Mk1vK4aeOFw8E0ShwE27ZY8HOQPxT/wCC4WvXf/BUb/guB8IP+CXnhWc3PhzwhPbafraLJIiibUlj1PXZMrxuttGt44UdRlZLopuQk1i/tR29p/wRy/4OU/CHx60GIaR8Nfiy1m9zHBHHFbJY655Oi6pFnhQlnfwWF8wG0hWON3SgD+rb/grf+3b4u/4Jx/sX6r+1F4I8PWfii/0/VdJ05NPv55LaBxqV5HaFjLCkjrs8zdwh6YxX8vPwi/4Lm/t1XUWtft1/Bv8AYL0u7sPGEKjV/HGgx6pM2pQac5gPnXVrpUk8q27IUZmQqmz5uF4/Zj/g6IYH/gkb4jYc/wDFT+FP/Txb1/P7/wAEvv8Agu78V/2Iv+CYvhX9nr4ffsy+N/HcvhmHV3t/FUVtdDw5MbnULq7LvNa2dy/lweb5cgQH50YZXqKjNpNJjUmtj+on/gj/AP8ABZn4Pf8ABWXwPrn9g6DceDfGvhKO1l1jRJ51vIDbXm9YLyxvEVBcW0jxSJ80cUqMvzxqGQt82/8ABW3/AIOFvg1/wTn8eH9nX4T+HD8TPikiQte2CXX2bT9JNwEa3ivZoo553upkdWitLeF5MNGZDEJYt/5c/wDBo58KPh/s+MP7Wknjjw/qvjLxHDb20/hLR2K3ekWbXV1qBuLyI4VBdzylbZIPMijhiUedI7MqfEP/AAbR+ENH/bp/4K1fEr9sT47QjU9b0Gz1HxbZW96DJJFq2u6rNAk7q5I8ywtka3i4/dbtq42JiRH163/BzH/wVV+Bk1p8QP2v/wBk/wDsLwLfXEaQ3bW2u6K0iSNgLFdahaPb+a3SKO48jzGwBgHNf1nfsJ/tyfAj/god+znpP7Sn7Pt5LLpN+8lrd2d0qpeadf2523Fldxozqs0TY5RmjdSskbNGysfefjH8H/h58ffhV4g+CnxY0yHWPDfiiwn03UbO4UPHLBOhRhhgQCM5U9VYAjkV8r/sC/8ABN79l7/gm18PtU+Hf7MllqUFvr1zFeapc6rqV1qNxeXUMK26zOZ3McbeWiqRCka4AG3AGAD70ooooA//1P7+K8j+Pnxn8F/s6fBLxZ8efiLcraaF4O0m71i+lY4xBZwtKwHudu0DuSAK9crG8QeHfD/izR5/Dvimxt9S0+5AWa2uokmhkAIIDxuCrDIBwR2oA/zJ/wDgl3/wSl+Lv/Bd/wCI/wAYv2wPGvxOvfh0P7eaebVtJiW+lu9X1gvqF3ZxzC5jKxWNtJbQ5U8rsTaqpivYP+Cr3/Btd40/YP8A2SNS/at0/wCNGtfFC28P3VpZ6rZarZeW1ppmpzLaTXUM5uJ/LETyRtLldmwFmxsBH+jR4S8D+C/AOnPo/gXSLLRbSSQytBYW8dtG0hABcpEqqWIAGcZwB6Vp67oGheKNIuPD/iWyg1Cwu08ue2uY1lhkQ/wvG4KsPYjFAH8SP7a/7a1t+3D/AMGsPh742a/qMNxr+l614X8P+JJfNi2jVNG1m3tbiZmRigS4VFukJI/dSq2BXjv/AASH/wCDkL/gnv8A8E//APgm54H/AGUvi1H4j1Xxt4VGrNNb6Va2z2crXmpXV7Akd5NdRQDMcyBixVVbIPSv7lovgj8GIPDs3hCDwjoqaTczLcy2S6fbC3kmQALI0Qj2M6gABiMjAx0rGh/Zu/Z4t5Vnt/Afh2N0OVZdLswQR6ERcUAfxH/8GyvwY+M3x1/4KPfE3/gpFpnhR/CXwy1i38UJbj5vsj3HiLWIL+HTrGTYqXMVmkDGeSImJHKKh5KR+AfG3wT+0b/wbZ/8FXtV/a28M+F5PEPwZ8bXmpJBKhMFldaRq9wL2bTJ7zaYbTUbC6H+ieftjkjChM+bMYf9E6ysrPTrWOysIkghiAVI41CqoHQBRgAewqlrmgaH4m0ubQ/EdnBf2VwuyW3uY1lidfRkcFSPYigD+Mv9pj/g8H/Zo8Q/BDU/DX7GXhHxBN8StVs3tbJ9dWyhstNuph5ayEW13PLfyRkkxQ2qssrhVMkYYGv2Z/4IT/8ADyjWv2R5viH/AMFJfEF9qeq+ILxJ/DVhrFrbW2q2ejrCio+ofZ7a1YTXMu+RY5k82OHy/M2yM6J+lngf9kf9lb4ZeIT4t+HHw18LaBqpbd9s07R7K2nz6+ZFErfrX0KBigBaKKKAP//V/v4ooooAKKKKACiiigAooooAKKKKACiiigD/2Q=="
            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")
                            {
                            const ulReemplazos = document.getElementById("replacementsListElementID");
                                if (ulReemplazos && typeof renderReplacementsList === 'function') renderReplacementsList(ulReemplazos);
                            }
                        }
                    });
                });
                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 () =>
                    {
                        //console.log(`[WME PLN][DEBUG] Polling intento ${pollingAttempts + 1}...`);
                        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}"`; //
                                    //console.log(`[WME PLN][DEBUG] SDK.State SUCCESS: Usuario obtenido: ${currentUserInfoLocal.name} (ID: ${displayId})`); //
                                }
                                else
                                {
                                   // console.warn(`[WME_PLN][DEBUG] SDK.State: getUserInfo() devolvió datos incompletos o null:`, sdkUserInfo);
                                }
                            }
                            catch (e)
                            {
                               // console.error(`[WME_PLN][DEBUG] SDK.State ERROR al obtener usuario:`, 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);
                 //   console.log('[WME_PLN][DEBUG] Iniciando polling para información del usuario...');
                };
                // 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
                    // console.error("[WME_PLN] Div de salida (wme-normalization-tab-output) no encontrado en el tab.");
                        return;
                    }
                    if (places.length === 0)
                    {
                        outputDiv.textContent = "No se encontraron lugares visibles para analizar.";
                        return;
                    }
                    const maxPlacesInput =  document.getElementById("maxPlacesInput");
                    const maxPlacesToScan = parseInt(maxPlacesInput?.value || "100", 10);
                    const scannedCount = Math.min(places.length, maxPlacesToScan);
                    outputDiv.textContent = `Escaneando ${scannedCount} lugares...`;
                    setTimeout(() => {renderPlacesInFloatingPanel(places.slice(0, maxPlacesToScan));}, 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);
                // 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
                // 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();
                // --- 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(); // Reinicializa el Set
                    excludedWordsMap = new Map(); // Reinicializa el Map
                    parsed.forEach(word =>
                    { // parsed es el array del JSON
                        excludedWords.add(word);
                        const firstChar = word.charAt(0).toLowerCase();
                        if (!excludedWordsMap.has(firstChar))
                        {
                            excludedWordsMap.set(firstChar, new Set());
                        }
                        excludedWordsMap.get(firstChar).add(word);
                    });
                    //   console.log("[WME PLN] Palabras especiales restauradas desde localStorage:", Array.from(excludedWords));
                }
                catch (e)
                {
                    //console.error("[WME PLN] Error al cargar excludedWordsList del localStorage:", e);
                    excludedWords = new Set();
                }
            }
            else
            {
                excludedWords = new Set();
                //  console.log("[WME PLN] No se encontraron palabras especiales en localStorage.");
            }

            // --- Cargar Lugares Excluidos desde localStorage ---
            const savedExcludedPlaces = localStorage.getItem("excludedPlacesList");
            if (savedExcludedPlaces)
            {
                try
                {
                    const parsed = JSON.parse(savedExcludedPlaces);
                    // Restaurar el Map desde el array de arrays
                    excludedPlaces = new Map(parsed); // Antes era new Set(parsed)
                    console.log("[WME PLN] Lugares excluidos restaurados desde localStorage:", Array.from(excludedPlaces.entries())); // Logear entries
                }
                catch (e)
                {
                    console.error("[WME PLN] Error al cargar excludedPlacesList del localStorage:", e);
                    excludedPlaces = new Map(); // Asegurar que sea un Map vacío en caso de error
                }
            }
            else
            {
                excludedPlaces = new Map(); // Asegurar que sea un Map vacío si no hay datos
                console.log("[WME PLN] No se encontraron lugares excluidos en localStorage.");
            }


            // --- Cargar diccionario desde localStorage ---
            const savedDictionary = localStorage.getItem("dictionaryWordsList");
            if (savedDictionary)
            {
                try
                {
                    const parsed = JSON.parse(savedDictionary);
                    window.dictionaryWords = new Set(parsed);
                    // Crear el índice de palabras por letra
                    window.dictionaryIndex = {};
                    // Iterar sobre las palabras y agregarlas al índice
                    parsed.forEach(word =>
                    {
                        const letter = word.charAt(0).toLowerCase();
                        if (!window.dictionaryIndex[letter])
                            window.dictionaryIndex[letter] = [];
                        window.dictionaryIndex[letter].push(word);
                    });
                }
                catch (e)
                {
                    console.error("[WME PLN] Error al cargar dictionaryWordsList del localStorage:", e);
                    window.dictionaryWords = new Set();
                    window.dictionaryIndex = {};
                }
            }
            else
            {
                window.dictionaryWords = new Set();
                window.dictionaryIndex = {};
                //  console.log("[WME PLN] No se encontró diccionario en
                //  localStorage.");
            }
            // 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().then(() =>
            {
                console.log('[WME PLN] Carga del diccionario desde Google Sheets finalizada.');
            }).catch(err =>
            {
                console.error('[WME PLN] Fallo en la carga del diccionario desde Google Sheets:', err);
            });
            // Cargar estadísticas del editor
            loadEditorStats();
            // --- Cargar palabras de reemplazo desde localStorage ---
            loadReplacementWordsFromStorage();            
            // La llamada a waitForWazeAPI ya se encarga de la lógica de dynamicCategoriesLoaded.
            waitForWazeAPI(() =>
            {
                createSidebarTab();
                createStatsPanel(); //Crea el panel de estadísticas
               
            });
        }
        else
        {
            // console.log("[WME PLN] Esperando W.userscripts API...");
            setTimeout(waitForSidebarAPI, 1000);
        }
    }// Fin de waitForSidebarAPI

    // 1. normalizePlaceName
    function normalizePlaceName(word)
    {
        //console.log("[WME_PLN][DEBUG] Analizando nombre:", word);
        if (!word || typeof word !== "string")
            return "";
        // Manejar palabras con "/" recursivamente
        if (word.includes("/"))
        {
            if (word === "/") return "/";
            return word.split("/").map(part => normalizePlaceName(part.trim())).join("/");
        }
        // Regla 1: Si la palabra es SOLO números, mantenerla tal cual. (Prioridad alta)
        if (/^[0-9]+$/.test(word))
            return word;
        // Regla 2: Números seguidos de letras (sin espacio)
        word = word.replace(/(\d)([a-zA-Z])/g, (_, num, letter) => `${num}${letter.toUpperCase()}`);
        // Regla 3: Números romanos
        const romanRegexStrict = /^(C{0,3}(XC|XL|L?X{0,3})?(IX|IV|V?I{0,3})?)$/i;
        
        if (romanRegexStrict.test(word))
            return word.toUpperCase();
        // Regla 4: Acrónimos/Palabras con puntos/letras mayúsculas que deben mantenerse. Esto es para "St." o "U.S.A." o "EPM", "SURA"
        // NOTA: originalNameFull ya no tiene emoticones gracias a `processNextPlace`
        if (/^[A-ZÁÉÍÓÚÑ0-9.]+$/.test(word) && word.length > 1 && (word.includes('.') || /^[A-ZÁÉÍÓÚÑ]+$/.test(word)))
        {
            // Asegurarse de que no sea  "DI", "SI" si están en mayúsculas accidentales
            if (word.toUpperCase() === "DI" || word.toUpperCase() === "SI")
                return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
            return word; // Mantener como está
        }
        // Regla 5: Capitalización estándar para el resto de las palabras.
        // Esta será la regla para la mayoría de las palabras que no caen en las anteriores.
        let normalizedWord = word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
        return normalizedWord;
    }// Fin de normalizePlaceName

    // Función para escapar caracteres especiales en una cadena para usar en regex
    function applyWordsToStartMovement(name)
    {
        let newName = name;
        // Asegurarse de que window.swapWords exista y no esté vacío
        if (!window.swapWords || window.swapWords.length === 0)
        {
            return newName; // No hay palabras para mover
        }
        // Ordenar las palabras swap por longitud descendente para procesar primero las frases más largas.
        // Esto es crucial para evitar que una palabra corta (ej. "Club") se mueva antes que una frase más larga que la contiene
        // (ej. "Club Campestre"), si ambas estuvieran en la lista.
        const sortedSwapWords = [...window.swapWords].sort((a, b) => b.length - a.length);
        for (const swapWord of sortedSwapWords)
        {
            // Regex para encontrar la palabra swap al final del nombre.
            // \s* : Cero o más espacios antes de la palabra.
            // (${escapeRegExp(swapWord)}) : Captura la palabra swap (escapando caracteres especiales de regex).
            // \s*$ : Cero o más espacios al final del nombre, seguido del fin de la cadena.
            // 'i' : Para búsqueda insensible a mayúsculas/minúsculas.
            const regex = new RegExp(`\\s*(${escapeRegExp(swapWord)})\\s*$`, 'i');

            if (regex.test(newName))
            {
                // Captura la parte del nombre que coincide con la palabra swap al final.
                const match = newName.match(regex);
                const matchedSwapWord = match[1]; // La palabra/frase real que coincidió (ej. "apartamentos", "Urbanización")

                // Elimina la palabra swap del final del nombre para obtener el resto.
                const remainingName = newName.replace(regex, '').trim();

                // Capitaliza la palabra movida para que aparezca correctamente al inicio.
                // Si ya está correctamente capitalizada (ej. "Urbanización"), se mantiene así.
                //const capitalizedSwapWord = matchedSwapWord.charAt(0).toUpperCase() + matchedSwapWord.slice(1).toLowerCase();
                const capitalizedSwapWord = capitalizeEachWord(matchedSwapWord); // Usar la nueva función aquí

                // Reconstruye el nombre: Palabra movida + espacio + resto del nombre.
                newName = `${capitalizedSwapWord} ${remainingName}`.trim();

                // Una vez que se mueve una palabra, asumimos que no hay más movimientos
                // que hacer con otras palabras swap para esta misma entrada,
                // a menos que quieras permitir múltiples movimientos, lo cual
                // complicaría la lógica. Por ahora, nos detenemos en la primera coincidencia.
                break;
            }
        }
        return newName;
    }//applyWordsToStartMovement

   

// 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 /\p{L}-\p{L}/u.test(word) es crucial: asegura que el guion esté entre letras
    if (word.includes('-') && /\p{L}-\p{L}/u.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 = /^[A-ZÁÉÍÓÚÑ0-9.]+$/.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;
        });
        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 = /^[A-ZÁÉÍÓÚÑ0-9.&]+$/.test(word);
    if (isAcronymLike && word.length > 1)
    {
        console.log(`[WME PLN - NWI] Aplicando Prioridad 4 (Acrónimo): "${word}"`);
        return word; // Mantener como está
    }
    // Prioridad 5: Números Romanos
    const romanRegexInsensitive = /^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/i;
    if (romanRegexInsensitive.test(word))
    {
        console.log(`[WME PLN - NWI] Aplicando Prioridad 5 (Números Romanos): "${word}"`);
        return word.toUpperCase();
    }
   // Prioridad 6: Palabras Comunes
        const lowerWord = word.toLowerCase().replace('.', '');
        // `commonWords` es una constante global.
        if (commonWords.includes(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", "del", "y", "e", "o", "u", "al", "en", "con", "por"];

            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;

        // Normalizar contenido dentro de comillas dobles
        text = text.replace(/"([^"]*)"/g, (match, content) =>
        {
            const trimmedContent = content.trim();
            if (trimmedContent === "") return '""';
            const wordsInside = trimmedContent.split(/\s+/).filter(w => w.length > 0);
            const normalizedWordsInside = wordsInside.map((singleWord, index) =>
            {
                return normalizeWordInternal(singleWord, index === 0, true); // true para isInsideQuotesOrParentheses
            }).join(' ');
            return `"${normalizedWordsInside}"`; // Sin espacios extra
        });
        // Normalizar contenido dentro de paréntesis
        text = text.replace(/\(([^)]*)\)/g, (match, content) =>
        {
            const trimmedContent = content.trim();
            if (trimmedContent === "") return '()';
            const wordsInside = trimmedContent.split(/\s+/).filter(w => w.length > 0);
            const normalizedWordsInside = wordsInside.map((singleWord, index) =>
            {
                return normalizeWordInternal(singleWord, index === 0, true); // true para isInsideQuotesOrParentheses
            }).join(' ');
            return `(${normalizedWordsInside})`; // Sin espacios extra
        });
        return text.replace(/\s+/g, ' ').trim(); // Limpieza final general
    }// 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 = new Set();
    // === 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
        renderExcludedWordsList(document.getElementById("excludedWordsList"));
        saveExcludedWordsToLocalStorage();
    });
    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.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();
        }
    });
    wordsActionButtonsContainer.appendChild(clearWordsBtn); // AÑADIDO A wordsActionButtonsContainer
    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);

    // 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.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();
        }
    });
    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
}

    // === 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"; // Alinear verticalmente
        // 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"; // Mejor padding
        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"; // Mejor padding
        addBtn.style.cursor = "pointer";
        addBtn.addEventListener("click", function ()
        {
            const newWord = input.value.trim();
            if (newWord)
            {
                const lowerNewWord = newWord.toLowerCase();

                const alreadyExists =
                  Array.from(window.dictionaryWords).some(w => w.toLowerCase() === lowerNewWord);

                if (commonWords.includes(lowerNewWord))
                {
                    alert("La palabra es muy común y no debe agregarse a la lista.");
                    return;
                }

                if (alreadyExists)
                {
                    alert("La palabra ya está en la lista.");
                    return;
                }
                window.dictionaryWords.add(lowerNewWord);
                input.value = "";
                renderDictionaryList(document.getElementById("dictionaryWordsList"));
            }
        });
        // Añadir tooltip al botón
        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.marginBottom = "10px"; // Más espacio
        // Botón para importar desde XML
        const exportBtn = document.createElement("button");
        exportBtn.textContent = "Exportar"; // Más corto
        exportBtn.title = "Exportar Diccionario a XML";
        exportBtn.style.padding = "6px 10px";
        exportBtn.style.cursor = "pointer";
        exportBtn.addEventListener("click", exportDictionaryWordsList);
        actionButtonsContainer.appendChild(exportBtn);
        // Botón para importar desde XML
        const clearBtn = document.createElement("button");
        clearBtn.textContent = "Limpiar"; // Más corto
        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();
                renderDictionaryList(document.getElementById("dictionaryWordsList")); // Pasar el elemento UL
            }
        });
        actionButtonsContainer.appendChild(clearBtn);
        section.appendChild(actionButtonsContainer);
        // Diccionario: 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";
        // On search input, render filtered list
        search.addEventListener("input", () =>
        {
            renderDictionaryList(document.getElementById("dictionaryWordsList"),search.value.trim());
        });
        section.appendChild(search);
        // Lista UL para mostrar palabras del diccionario
        const listContainerElement = document.createElement("ul");
        listContainerElement.id = "dictionaryWordsList";
        listContainerElement.style.maxHeight = "150px";
        listContainerElement.style.overflowY = "auto";
        listContainerElement.style.border = "1px solid #ddd";
        listContainerElement.style.padding = "5px";
        listContainerElement.style.margin = "0";
        listContainerElement.style.background = "#fff";
        listContainerElement.style.listStyle = "none";
        section.appendChild(listContainerElement);
        // Renderizar la lista de palabras del diccionario
        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";
        // Añadir eventos de arrastrar y soltar
        dropArea.addEventListener("dragover", (e) =>
        {
            e.preventDefault();
            dropArea.style.background = "#e9e9e9";
            dropArea.style.borderColor = "#aaa";
        });
        // Evento para cuando el ratón sale del área de arrastre
        dropArea.addEventListener("dragleave", () =>
        {
            dropArea.style.background = "#f9f9f9";
            dropArea.style.borderColor = "#ccc";
        });
        // Evento para cuando se suelta el archivo
        dropArea.addEventListener("drop", (e) =>
        {
            e.preventDefault();
            dropArea.style.background = "#f9f9f9";
            dropArea.style.borderColor = "#ccc";
            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++;
                            }
                        }
                        if (newWordsAddedCount > 0)
                            console.log(`[WME PLN] ${newWordsAddedCount} nuevas palabras añadidas desde XML.`);
                        // Renderizar la lista en el panel
                        renderDictionaryList(listContainerElement);
                    }
                    catch (err)
                    {
                        alert("Error procesando el diccionario XML.");
                    }
                };
                reader.readAsText(file);
            }
            else
            {
                alert("Por favor, arrastra un archivo XML válido.");
            }
        });
        section.appendChild(dropArea);
        parentContainer.appendChild(section);
        renderDictionaryList(listContainerElement);
    }// createDictionaryManager
    
    // Carga las palabras excluidas desde localStorage
    function loadReplacementWordsFromStorage()
    {
        const savedReplacements = localStorage.getItem("replacementWordsList");
        if (savedReplacements)
        {
            try
            {
                replacementWords = JSON.parse(savedReplacements);
                if (typeof replacementWords !== 'object' || replacementWords === null)
                { // Asegurar que sea un objeto
                    replacementWords = {};
                }
            }
            catch (e)
            {
                console.error("[WME PLN] Error cargando lista de reemplazos desde localStorage:", e);
                replacementWords = {};
            }
        }
        else
        {
            replacementWords = {}; // Inicializar si no hay nada guardado
        }
        console.log("[WME PLN] Reemplazos cargados:",    Object.keys(replacementWords).length, "reglas.");
    }// loadReplacementWordsFromStorage

    // Carga las palabras excluidas desde localStorage
    function saveSwapWordsToStorage()
    {
        localStorage.setItem("swapWords", JSON.stringify(window.swapWords || []));
    }// saveSwapWordsToStorage

    // Carga las palabras reemplazo
    function saveReplacementWordsToStorage()
    {
        try
        {
            localStorage.setItem("replacementWordsList",
                                 JSON.stringify(replacementWords));
            // 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 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 EXITOSAMENTE.');
        } 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;
        }
        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";
            textContainer.title = `Reemplazar "${from}" con "${to}"`;
            // 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);
            // 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";
            editBtn.addEventListener("click", () =>
            {
                const newFrom = prompt("Editar texto original:", from);
                if (newFrom === null) return;
                const newTo = prompt("Editar texto de 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;
                }
                // Si cambia la clave, elimina la anterior
                if (newFrom !== from) delete replacementWords[from];
                replacementWords[newFrom] = newTo;
                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";
            deleteBtn.addEventListener("click", () =>
            {
                if (confirm(`¿Estás seguro de eliminar el reemplazo:\n"${from}" → "${to}"?`))
                {
                    delete replacementWords[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);
        });
    }

    // Exporta las palabras especiales y reemplazos a un archivo XML
    function exportSharedDataToXml(type = "words") 
    {
        let xmlParts = [];
        let rootTagName = "SharedData"; // Nombre de raíz por defecto, puede cambiar.
        let fileName = "wme_normalizer_data_export.xml";
        
        if (type === "words")
        {
            rootTagName = "ExcludedWords";
            fileName = "wme_excluded_words_export.xml";
            if (excludedWords.size === 0 && Object.keys(replacementWords).length === 0 &&
                (!window.swapWords || window.swapWords.length === 0) && Object.keys(editorStats).length === 0)
            {
                alert("No hay datos para Palabras Especiales (Excluidas, Reemplazos, SwapWords, Estadísticas) para exportar.");
                return;
            }    
            // Exportar palabras especiales
            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
            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
            if (window.swapWords && window.swapWords.length > 0)
            {
                xmlParts.push("    <swapWords>");
                window.swapWords.forEach(val =>
                {
                    xmlParts.push(`        <swap value="${xmlEscape(val)}"/>`);
                });
                xmlParts.push("    </swapWords>");
            }      
            // Exportar estadísticas con la nueva estructura de totales
            if (Object.keys(editorStats).length > 0)
            {
                xmlParts.push("    <statistics>");
                Object.entries(editorStats).forEach(([userId, data]) =>
                {
                    // Escribe todos los contadores y periodos como atributos del editor
                    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>");
            }
            else if (type === "places")
            {
                rootTagName = "ExcludedPlaces"; // Nuevo tag raíz para lugares
                fileName = "wme_excluded_places_export.xml";
                if (excludedPlaces.size === 0)
                {
                    alert("No hay Lugares Excluidos para exportar.");
                    return;
                }
               // Exportar lugares excluidos
                xmlParts.push("    <placeIds>");
                // Iterar sobre los entries del Map: [id, name]
                Array.from(excludedPlaces.entries())
                    .sort((a, b) => a[0].toLowerCase().localeCompare(b[0].toLowerCase())) // Ordenar por ID para consistencia en XML
                    .forEach(([id, name]) => xmlParts.push(`        <placeId id="${xmlEscape(id)}" name="${xmlEscape(name)}"></placeId>`)); // Guardar ID y nombre
                xmlParts.push("    </placeIds>");
            }
            else
            {
                alert("Tipo de exportación XML desconocido.");
                return;
            }
            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; // Usa el nombre de archivo dinámico
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }        
    }//exportSharedDataToXml

   // 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
            {
                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();

                // ************************************************************
                // INICIO MODIFICACIÓN DE LÓGICA DE IMPORTACIÓN
                // ************************************************************
                if (type === "words")
                {
                    if (rootTag !== "excludedwords")
                    {
                        alert("El archivo XML no es válido para Palabras Especiales. Debe tener <ExcludedWords> como raíz.");
                        return;
                    }
                    let newExcludedAdded = 0;
                    const words = xmlDoc.getElementsByTagName("word");
                    for (let i = 0; i < words.length; i++)
                    {
                        const val = words[i].textContent.trim();
                        if (val && !excludedWords.has(val))
                        {
                            const validation = isValidExcludedWord(val); // <-- Asumiendo que isValidExcludedWord existe y es robusta
                            if (validation.valid)
                            {
                                excludedWords.add(val);
                                // Sincronizar excludedWordsMap en importación
                                const firstChar = val.charAt(0).toLowerCase();
                                if (!excludedWordsMap.has(firstChar)) excludedWordsMap.set(firstChar, new Set());
                                excludedWordsMap.get(firstChar).add(val);
                                newExcludedAdded++;
                            }
                        }
                    }
                    alert(`Importación completada para Palabras Especiales. Palabras nuevas: ${newExcludedAdded}`);
                    saveExcludedWordsToLocalStorage();
                    const excludedListElement = document.getElementById("excludedWordsList");
                    if (excludedListElement) renderExcludedWordsList(excludedListElement);
                }
                else if (type === "places")
                {
                    if (rootTag !== "excludedplaces")
                    {
                        alert("El archivo XML no es válido para Lugares Excluidos. Debe tener <ExcludedPlaces> como raíz.");
                        return;
                    }
                    let newPlacesAdded = 0;
                    const placesNodes = xmlDoc.getElementsByTagName("placeId"); // Asume <placeId id="..." name="..."></placeId>
                    excludedPlaces = new Map(); // Reinicializa el Map para la importación
                    for (let i = 0; i < placesNodes.length; i++)
                    {
                        const placeId = placesNodes[i].getAttribute("id")?.trim();
                        const placeName = placesNodes[i].textContent.trim(); // Nombre del contenido del tag
                        if (placeId && !excludedPlaces.has(placeId))
                        {
                            excludedPlaces.set(placeId, placeName); // Guarda ID y Nombre
                            newPlacesAdded++;
                        }
                    }
                    alert(`Importación completada para Lugares Excluidos. Lugares nuevos: ${newPlacesAdded}`);
                    saveExcludedPlacesToLocalStorage();
                    const placesListElement = document.getElementById("excludedPlacesListUL");
                    if (placesListElement) renderExcludedPlacesList(placesListElement);
                }
                else
                {
                    alert("Tipo de importación XML desconocido.");
                    return;
                }
                // ************************************************************
                // FIN MODIFICACIÓN DE LÓGICA DE IMPORTACIÓN
                // ************************************************************

                // --- Mover lógica de reemplazos, swapWords, y estadísticas AQUI (si aún deben importarse desde este XML) ---
                // Tu código actual importa reemplazos, swapWords y estadísticas en CUALQUIER importación XML.
                // Necesitas decidir si estos también deben ser importados SÓLO si el rootTag es "excludedwords".
                // Por ahora, asumiré que los reemplazos, swapWords y estadísticas se importan sólo si el XML es de tipo "words".

                if (type === "words")
                { // Si este XML es para palabras, procesa también reemplazos y stats.
                    const replacements = xmlDoc.getElementsByTagName("replacement");
                    let newReplacementsAdded = 0;
                    let replacementsOverwritten = 0;
                    for (let i = 0; i < replacements.length; i++)
                    {
                        const from = replacements[i].getAttribute("from")?.trim();
                        const to = replacements[i].textContent.trim();
                        if (from && to)
                        {
                            if (replacements.hasOwnProperty(from) && replacements[from] !== to)
                            {
                                replacementsOverwritten++;
                            }
                            else if (!replacements.hasOwnProperty(from)) {
                                newReplacementsAdded++;
                            }
                            replacements[from] = to;
                        }
                    }
                    saveReplacementWordsToStorage(); // Guardar reemplazos
                    const replacementsListElement = document.getElementById("replacementsListElementID");
                    if (replacementsListElement) renderReplacementsList(replacementsListElement);

                    const swapWordsNode = xmlDoc.querySelector("swapWords");
                    if (swapWordsNode)
                    {
                        if (!window.swapWords) window.swapWords = [];
                        window.swapWords = []; // Limpiar antes de importar nuevos
                        swapWordsNode.querySelectorAll("swap").forEach(swapNode =>
                        {
                            const value = swapNode.getAttribute("value");
                            if (value && !window.swapWords.includes(value))
                            {
                                window.swapWords.push(value);
                            }
                        });
                        saveSwapWordsToStorage(); // Guardar swapWords
                    }

                    // Lógica de importación de estadísticas (tal como la tienes)
                    const statsNode = xmlDoc.querySelector("statistics");                    
                    if (statsNode)
                    {
                        const editorNode = statsNode.querySelector("editor");
                        if (editorNode)
                        {
                            // Detectar si el XML tiene el formato nuevo (con atributos de conteo) o el viejo (con eventos)
                            if (editorNode.hasAttribute("total_count"))
                            {
                                // --- Formato Nuevo: Importar los totales ---
                                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
                                    };
                                    // 2. Marcar como exitosa la importación de estadísticas
                                    statsImportedSuccessfully = true;
                                }
                            }
                            else
                            {
                                // --- Formato Viejo: Ignorar y advertir para no borrar datos ---
                                showTemporaryMessage("XML con formato de estadísticas antiguo detectado. Se omitió la importación de estadísticas para no borrar tus datos actuales.", 6000, "warning");
                            }
                        }
                    }
                    // Guardar y Re-renderizar todo
                    saveExcludedWordsToLocalStorage();
                    saveReplacementWordsToStorage();                   
                    saveEditorStats(); // Guardar estadísticas
                    updateStatsDisplay();
                }

                // Mensaje de resumen final.
                // Adapta este alert para reflejar lo que se importó realmente.
                // alert(`Importación completada para tipo ${type}. ...`);
            }
            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';
            }
        });
        // 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
    function loadSwapWordsFromStorage()
    {
        const stored = localStorage.getItem("swapWords");
        // Si hay datos en localStorage, intentar parsearlos
        if (stored)
        {
            try
            {
                window.swapWords = JSON.parse(stored);
            }
            catch (e)
            {
                window.swapWords = [];
            }
        }
        else
        {
            window.swapWords = [];
        }
    }// loadSwapWordsFromStorage

  // Función para calcular el área en metros cuadrados de una geometría de polígono usando Turf.js
    function calculateAreaMeters(venueSDK)
    {
    if (!venueSDK || !venueSDK.geometry || !venueSDK.geometry.coordinates || !venueSDK.geometry.type) {
        console.warn("[WME PLN] calculateAreaMeters: venueSDK inválido o sin datos de geometría.");
        return null;
    }

    // Asegurarse de que turf esté disponible
    if (typeof turf === 'undefined') {
        console.error("[WME PLN] calculateAreaMeters: La librería Turf.js no está cargada. No se puede calcular el área.");
        return null;
    }

    // La geometría del SDK ya viene en formato GeoJSON.
    // 'venueSDK.geometry.type' puede ser 'Point', 'LineString', 'Polygon', etc.
    if (venueSDK.geometry.type !== 'Polygon' && venueSDK.geometry.type !== 'MultiPolygon') {
        // Solo calculamos área para polígonos. Los puntos y líneas no tienen área significativa en este contexto.
        console.log("[WME PLN] calculateAreaMeters: Geometría no es Polygon/MultiPolygon (tipo: " + venueSDK.geometry.type + ").");
        return null;
    }

    try {
        // Crear un objeto Feature de GeoJSON para Turf.js
        const polygonFeature = {
            type: "Feature",
            geometry: venueSDK.geometry,
            properties: {}
        };

        // turf.area() calcula el área en metros cuadrados por defecto.
        const areaSquareMeters = turf.area(polygonFeature); //

        if (typeof areaSquareMeters === 'number' && !isNaN(areaSquareMeters)) {
            return areaSquareMeters;
        }
    } catch (e) {
        console.error("[WME PLN] calculateAreaMeters: Error al calcular el área del polígono con Turf.js:", e);
    }
    return null;
}


    // Crea el gestor de reemplazos
    function createReplacementsManager(parentContainer)
    {
        loadSwapWordsFromStorage();
        parentContainer.innerHTML = ''; // Limpiar por si acaso
        // --- 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 (swap)";
        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;
            }
            // Validar que no sea solo caracteres especiales
            if (fromValue === toValue)
            {
                alert("El texto original y el de reemplazo no pueden ser iguales.");
                return;
            }
            // Validar que no sea solo caracteres especiales
            if (replacementWords.hasOwnProperty(fromValue) && replacementWords[fromValue] !== toValue)
            {
                if (!confirm(`El reemplazo para "${fromValue}" ya existe ('${replacementWords[fromValue]}'). ¿Deseas sobrescribirlo con '${toValue}'?`))
                    return;
            }
            replacementWords[fromValue] = toValue;
            fromInput.value = "";
            toInput.value = "";
            // Renderiza toda la lista (más seguro y rápido en la práctica)
            renderReplacementsList(listElement);
            saveReplacementWordsToStorage();
        });
        // 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.marginBottom = "10px";
        // Botones de acción
        const exportButton = document.createElement("button");
        exportButton.textContent = "Exportar Todo";
        exportButton.title = "Exportar Excluidas y Reemplazos a XML";
        exportButton.style.padding = "6px 10px";
        exportButton.addEventListener("click", exportSharedDataToXml); // Llamar a la función compartida
        actionButtonsContainer.appendChild(exportButton);
        // 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", () =>
        {
            if (confirm("¿Estás seguro de que deseas eliminar TODOS los reemplazos definidos?"))
            {
                replacementWords = {};
                saveReplacementWordsToStorage();
                renderReplacementsList(listElement);
            }
        });
        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 y explicación del swap
        const swapTitle = document.createElement("h4");
        swapTitle.textContent = "Palabras al inicio";
        // Estilo del título
        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 ingresadas aquí se moverán del final al inicio del nombre del lugar si se encuentran al final.<br>" +
        "<em>Ej:</em> “Las Palmas <strong>Urbanización</strong>” → “<strong>Urbanización</strong> Las Palmas”<br>" +
        "<em>Ej:</em> “Tornillos <strong>Ferretería</strong>” → “<strong>Ferretería</strong> Tornillos”";
        // Añadir caja de explicación al contenedor
        swapContainer.appendChild(swapExplanationBox);
        const swapExplanation = document.createElement("p");
        swapExplanation.textContent = "El orden importa: las palabras se evalúan una a una desde el inicio. Si se ordenan alfabéticamente, una más corta podría bloquear otra más específica.";
        swapExplanation.style.fontSize = "12px";
        swapExplanation.style.fontStyle = "italic";
        swapExplanation.style.marginTop = "6px";
        swapExplanation.style.marginBottom = "10px";
        swapExplanation.style.color = "#555";
        // Inserta este nodo justo después del swapTitle, por ejemplo:
        swapContainer.appendChild(swapExplanation);
        swapTitle.style.fontSize = "14px";
        swapTitle.style.marginBottom = "8px";
        swapContainer.appendChild(swapTitle);
        // Contenedor para añadir nuevas palabras swap
        const swapInput = document.createElement("input");
        swapInput.type = "text";
        swapInput.placeholder = "Ej: Urbanización";
        swapInput.style.width = "70%";
        swapInput.style.padding = "6px";
        swapInput.style.marginRight = "8px";
        // Atributos para evitar corrección ortográfica
        const swapBtn = document.createElement("button");
        swapBtn.textContent = "Añadir";
        swapBtn.style.padding = "6px 10px";
        swapBtn.addEventListener("click", () =>
        {
            const val = swapInput.value.trim();
            if (!val || /^[^a-zA-Z0-9]+$/.test(val))
            {
                alert("No se permiten caracteres especiales solos");
                return;
            }
            if (window.swapWords.includes(val))
            {
                alert("Ya existe en la lista.");
                return;
            }
            window.swapWords.push(val); // mantiene orden
            localStorage.setItem("wme_swapWords", JSON.stringify(window.swapWords));
            saveSwapWordsToStorage();  // Guardar en localStorage
            swapInput.value = "";
            renderSwapList();
        });
        swapContainer.appendChild(swapInput);
        swapContainer.appendChild(swapBtn);
        // Añadir campo de búsqueda justo después de swapBtn
        let searchSwapInput = document.createElement("input");
        searchSwapInput.type = "text";
        searchSwapInput.placeholder = "Buscar palabra...";
        searchSwapInput.id = "searchSwapInput";
        searchSwapInput.style.width = "70%";
        searchSwapInput.style.padding = "6px";
        searchSwapInput.style.marginTop = "8px";
        searchSwapInput.style.marginBottom = "8px";
        searchSwapInput.style.border = "1px solid #ccc";
        // Escuchar el input para actualizar lista
        searchSwapInput.addEventListener("input", () =>
        {
            renderSwapList(searchSwapInput);
        });
        swapContainer.appendChild(searchSwapInput);
        // Renderiza la lista
        renderSwapList(searchSwapInput);
        parentContainer.appendChild(swapContainer);
        // --- Alternar visibilidad según modo seleccionado ---
        modeSelector.addEventListener("change", () =>
        {
            replacementsContainer.style.display = modeSelector.value === "words" ? "block" : "none";
            swapContainer.style.display = modeSelector.value === "swapStart" ? "block" : "none";
        });
        // --- Función para renderizar la lista de swapWords ---
        function renderSwapList(searchInput = null)
        {
            // Buscar automáticamente el campo si no se pasó como parámetro
            if (!searchInput)
                searchInput = document.getElementById("searchSwapInput");
            // Asegurarse de que swapContainer existe
            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 = "10px 0 0 0";
                ul.style.background = "#fff";
                ul.style.listStyle = "none";
                swapContainer.appendChild(ul);
                return ul;
            })();
            swapList.innerHTML = "";
            // Verificar si hay palabras swap definidas
            if (!window.swapWords || window.swapWords.length === 0)
            {
                const li = document.createElement("li");
                li.textContent = "No hay palabras al inicio definidas.";
                li.style.textAlign = "center";
                li.style.color = "#777";
                li.style.padding = "5px";
                swapList.appendChild(li);
                return;
            }
            // Filtrar palabras swap según el término de búsqueda
            const searchTerm = searchSwapInput && searchSwapInput.value ? searchSwapInput.value.trim().toLowerCase() : "";
            let filteredSwapWords = Array.from(window.swapWords);
            // Si hay un término de búsqueda, filtrar la lista
            if (searchTerm)
                filteredSwapWords = filteredSwapWords.filter(word => word.toLowerCase().includes(searchTerm));
            // Ordenar alfabéticamente
            filteredSwapWords.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.title = word;
                // Aplicar estilos para truncar texto largo
                if (searchTerm)
                {
                    const i = word.toLowerCase().indexOf(searchTerm);
                    if (i !== -1)
                    {
                        const before = word.substring(0, i);
                        const match = word.substring(i, i + searchTerm.length);
                        const after = word.substring(i + searchTerm.length);
                        wordSpan.innerHTML = `${before}<mark>${match}</mark>${after}`;
                    }
                    else
                    {
                        wordSpan.textContent = word;
                    }
                }
                else
                {
                    wordSpan.textContent = word;
                }
                // Estilos para el span de la palabra
                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.style.padding = "2px";
                editBtn.style.fontSize = "14px";
                editBtn.addEventListener("click", () =>
                {
                    const newWord = prompt("Editar palabra:", word);
                    if (newWord !== null && newWord.trim() !== word)
                    { // Permitir string vacío para borrar si se quisiera, pero
                        const trimmedNewWord = newWord.trim();
                        if (trimmedNewWord === "")
                        {
                            alert("La palabra no puede estar vacía.");
                            return;
                        }
                        if (window.swapWords.includes(trimmedNewWord) && trimmedNewWord !== word)
                        {
                            alert("Esa palabra ya existe en la lista.");
                            return;
                        }
                        window.swapWords = window.swapWords.filter(w => w !== word);
                        window.swapWords.push(trimmedNewWord);
                        saveSwapWordsToStorage();
                        renderSwapList(searchInput);
                    }
                });
                // 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.style.padding = "2px";
                deleteBtn.style.fontSize = "14px";
                deleteBtn.addEventListener("click", () =>
                {
                    if (confirm(`¿Eliminar la palabra '${word}' de la lista?`))
                    {
                        window.swapWords = window.swapWords.filter(w => w !== word);
                        renderSwapList(searchInput);
                        saveSwapWordsToStorage();
                    }
                });
                btnContainer.appendChild(editBtn);
                btnContainer.appendChild(deleteBtn);
                li.appendChild(wordSpan);
                li.appendChild(btnContainer);
                swapList.appendChild(li);
            });
        }
        // Render inicial
        renderReplacementsList(listElement);
        if (window.swapWords && window.swapWords.size > 0) renderSwapList();
        // Listener de búsqueda para swap
        searchSwapInput.addEventListener("input", renderSwapList);
    }// createReplacementsManager


    
    // 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;
        }
        // Renderizar cada palabra
        wordsToRender.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", () => {
                const newWord = prompt("Editar palabra:", 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);

                    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);
                        }
                    }
                    renderExcludedWordsList(ulElement, currentFilter);
                    saveExcludedWordsToLocalStorage();
                }
            });
            btnContainer.appendChild(deleteBtn);

            li.appendChild(btnContainer);
            // --- FIN DEL BLOQUE PARA LOS BOTONES ---

            ulElement.appendChild(li);
        });//
    }// 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, Nombre>. Iteramos sobre sus entries.
        const placesToRender = Array.from(excludedPlaces.entries()).filter(([placeId, placeNameSaved]) => 
            // Filtra por ID o por el nombre guardado
            placeId.toLowerCase().includes(lowerFilter) || 
            placeNameSaved.toLowerCase().includes(lowerFilter)
        ).sort((a, b) => {
            // Ordena alfabéticamente por el nombre guardado
            return a[1].toLowerCase().localeCompare(b[1].toLowerCase()); // Compara por el nombre (índice 1 del entry)
        });

        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); // <--- REINTRODUCIMOS W.model AQUÍ
            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
            // ************************************************************
            // FIN DE LA MODIFICACIÓN
            // ************************************************************
        });
        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);
      
                        // 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);

                            // 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)'); // Asume que la 8ª columna es la de Categoría Actual
        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
            currentCategoryDiv.querySelector('span:first-child').textContent = categoryDetails.description; // Actualiza el texto
            currentCategoryDiv.querySelector('span:last-child').textContent = categoryDetails.icon; // Actualiza el ícono
            currentCategoryDiv.querySelector('span:first-child').title = `Categoría Actual: ${categoryDetails.description}`; // Actualiza el título
        }
    }

    // Renderizar lista de palabras del diccionario
    function renderDictionaryList(ulElement, filter = "")
    {
        // 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;
        }
        // Renderizar cada palabra
        wordsToRender.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", () => {
                const newWord = prompt("Editar palabra:", word);
                if (newWord !== null && newWord.trim() !== word)
                {
                    window.dictionaryWords.delete(word);
                    window.dictionaryWords.add(newWord.trim());
                    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);
        });
        // 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 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
    // Función para manejar el archivo XML arrastrado
    waitForSidebarAPI();
    //Llamar a la función para mostrar el changelog
    showChangelogOnUpdate();

    
})();
    
   
    // 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.border = "6px solid #f3f3f3";
        spinner.style.borderTop = "6px solid #3498db";
        spinner.style.borderRadius = "50%";
        spinner.style.width = "40px";
        spinner.style.height = "40px";
        spinner.style.animation = "spin 1s linear infinite";
        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
        const style = document.createElement("style");
        style.textContent = `@keyframes spin {0%{ transform: rotate(0deg); } 100% { transform: rotate(360deg); }}`;
        document.head.appendChild(style);
    }// showLoadingSpinner  
    
    // Función para obtener el ícono de categoría
    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 = 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 agregar una palabra al diccionario
    function addWordToDictionary(input)
    {
        const newWord = input.value.trim().toLowerCase();

        if (!newWord)
        {
            alert("La palabra no puede estar vacía.");
            return;
        }
        // Validaciones básicas antes de añadir
        if (newWord.length === 1 && !newWord.match(/[a-zA-Z0-9]/))
        {
            alert("No se permite agregar un solo carácter que no sea alfanumérico.");
            return;
        }
        if (commonWords.includes(newWord))
        {
            alert("Esa palabra es muy común y no debe agregarse al diccionario.");
            return;
        }
        if (excludedWords.has(newWord))
        {
            alert("Esa palabra ya existe en la lista de especiales (excluidas).");
            return;
        }
        if (window.dictionaryWords.has(newWord))
        {
            alert("La palabra ya existe en el diccionario.");
            return;
        }
        if (!window.dictionaryWords) window.dictionaryWords = new Set();
        if (!window.dictionaryIndex) window.dictionaryIndex = {};
        window.dictionaryWords.add(newWord); // Añadir al Set
        // === 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
        renderDictionaryList(document.getElementById("dictionaryWordsList")); // Re-renderizar la lista
        // Guardar en localStorage después de añadir
        try
        {
            localStorage.setItem("dictionaryWordsList", JSON.stringify(Array.from(window.dictionaryWords)));
        }
        catch (e)
        {
            console.error("[WME PLN] Error guardando diccionario en localStorage después de añadir manualmente:", e);
        }
    }// addWordToDictionary