WME PLN Module - Lists Handler

Módulo para gestionar listas de usuario en WME Place Normalizer. No funciona por sí solo.

Dit script moet niet direct worden geïnstalleerd - het is een bibliotheek voor andere scripts om op te nemen met de meta-richtlijn // @require https://update.greasyforks.org/scripts/548862/1657865/WME%20PLN%20Module%20-%20Lists%20Handler.js

// ==UserScript==
// @name         WME PLN Module - Lists Handler
// @version      9.0.0
// @description  Módulo para gestionar listas de usuario en WME Place Normalizer. No funciona por sí solo.
// @author       mincho77
// @license      MIT
// @grant        none
// ==/UserScript==
// Función para crear el gestor de palabras excluidas y lugares excluidos
function createSpecialItemsManager(parentContainer)
{
    // Evitar crear múltiples instancias
    const mainSection = document.createElement("div"); 
    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
    // 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)
        {
            plnToast(validation.msg, 3000);
            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", () => plnUiExportDataToXml("words")); // UI adapter
    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";
        plnUiHandleXmlFileDrop(e.dataTransfer.files[0], "words"); // UI adapter
    });
    wordsView.appendChild(wordsDropArea); // AÑADIDO A wordsView
    // 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", () => plnUiExportDataToXml("places")); // UI adapter
    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";
        plnUiHandleXmlFileDrop(e.dataTransfer.files[0], "places"); // UI adapter
    });
    placesView.appendChild(placesDropArea);
    // --- 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, ""); 
    renderExcludedPlacesList(placesListUL, ""); 

    parentContainer.appendChild(mainSection); 
}// createSpecialItemsManager


// === Diccionario ===
// Función para crear el gestor de diccionario personalizado
function createDictionaryManager(parentContainer)
{
    // Evitar crear múltiples instancias
    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();
        const validation = isValidExcludedWord(newWord);
        if (!validation.valid) 
        {
            plnToast(validation.msg,3000);
            return;
        }
        if (newWord)
        {
            const lowerNewWord = newWord.toLowerCase();
            const alreadyExists = Array.from(window.dictionaryWords).some(w => w.toLowerCase() === lowerNewWord);

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

            if (alreadyExists)
            {
                plnToast("La palabra ya está en la lista.", 3000);
                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)
                    {
                        plnLog('error',"[WME PLN] Error parseando XML:", parserError.textContent);
                        plnToast("Error al parsear el archivo XML del diccionario.", 3000);
                        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)
                        plnLog('swap', `[WME PLN] ${newWordsAddedCount} nuevas palabras añadidas desde XML.`);
                    // Renderizar la lista en el panel
                    renderDictionaryList(listContainerElement);
                }
                catch (err)
                {
                    plnToast("Error procesando el diccionario XML.", 3000);
                }
            };
            reader.readAsText(file);
        }
        else
        {
            plnToast("Por favor, arrastra un archivo XML válido.", 3000);
        }
    });
    section.appendChild(dropArea);
    parentContainer.appendChild(section);
    renderDictionaryList(listContainerElement);
}// createDictionaryManager
// Crea el gestor de reemplazos
function createReplacementsManager(parentContainer)
{
    loadSwapWordsFromStorage();
    parentContainer.innerHTML = ''; // Limpiar por si acaso
    function openSwapWordEditor(item, index) 
    {
        // Crear el fondo del modal
        const modalOverlay = document.createElement("div");
        modalOverlay.style.position = "fixed";
        modalOverlay.style.top = "0";
        modalOverlay.style.left = "0";
        modalOverlay.style.width = "100%";
        modalOverlay.style.height = "100%";
        modalOverlay.style.background = "rgba(0,0,0,0.5)";
        modalOverlay.style.zIndex = "20000";
        modalOverlay.style.display = "flex";
        modalOverlay.style.justifyContent = "center";
        modalOverlay.style.alignItems = "center";
        // Crear el contenido del modal
        const modalContent = document.createElement("div");
        modalContent.style.background = "#fff";
        modalContent.style.padding = "25px";
        modalContent.style.borderRadius = "8px";
        modalContent.style.boxShadow = "0 5px 15px rgba(0,0,0,0.3)";
        modalContent.style.width = "400px";
        modalContent.style.fontFamily = "sans-serif";
        // Título del modal
        const title = document.createElement("h4");
        title.textContent = "Editar Palabra Swap";
        title.style.marginTop = "0";
        title.style.marginBottom = "20px";
        title.style.textAlign = "center";
        modalContent.appendChild(title);
        // Input para la palabra
        const wordLabel = document.createElement("label");
        wordLabel.textContent = "Palabra o Frase:";
        wordLabel.style.display = "block";
        wordLabel.style.marginBottom = "5px";
        modalContent.appendChild(wordLabel);
        // Input de texto
        const wordInput = document.createElement("input");
        wordInput.type = "text";
        wordInput.value = item.word;
        wordInput.style.width = "calc(100% - 12px)";
        wordInput.style.padding = "8px";
        wordInput.style.marginBottom = "15px";
        wordInput.setAttribute('spellcheck', 'false');
        modalContent.appendChild(wordInput);
        // Radio buttons para la dirección
        const directionFieldset = document.createElement("fieldset");
        directionFieldset.style.border = "1px solid #ccc";
        directionFieldset.style.borderRadius = "5px";
        directionFieldset.style.padding = "10px";
        const legend = document.createElement("legend");
        legend.textContent = "Mover a:";
        directionFieldset.appendChild(legend);
        // Crear radio buttons
        ['start', 'end'].forEach(dir => 
        {
            const container = document.createElement("div");
            container.style.marginBottom = "5px";
            const radio = document.createElement("input");
            radio.type = "radio";
            radio.name = "editSwapDirection";
            radio.value = dir;
            radio.id = `editSwap_${dir}`;
            if (item.direction === dir) radio.checked = true;
            // Asociar label al radio
            const label = document.createElement("label");
            label.htmlFor = `editSwap_${dir}`;
            label.textContent = ` ${dir === 'start' ? 'Al Inicio (Start ←A)' : 'Al Final (A→End)'}`;
            label.style.cursor = "pointer";
            // Añadir al contenedor
            container.appendChild(radio);
            container.appendChild(label);
            directionFieldset.appendChild(container);
        });
        modalContent.appendChild(directionFieldset);
        // Contenedor para los botones de acción
        const buttonContainer = document.createElement("div");
        buttonContainer.style.display = "flex";
        buttonContainer.style.justifyContent = "flex-end";
        buttonContainer.style.gap = "10px";
        buttonContainer.style.marginTop = "20px";
        // Botón Cancelar
        const cancelBtn = document.createElement("button");
        cancelBtn.textContent = "Cancelar";
        cancelBtn.style.padding = "8px 15px";
        cancelBtn.addEventListener("click", () => modalOverlay.remove());
        buttonContainer.appendChild(cancelBtn);
        // Botón Guardar
        const saveBtn = document.createElement("button");
        saveBtn.textContent = "Guardar Cambios";
        saveBtn.style.padding = "8px 15px";
        saveBtn.style.background = "#007bff";
        saveBtn.style.color = "white";
        saveBtn.style.border = "none";
        saveBtn.style.borderRadius = "4px";
        saveBtn.addEventListener("click", () => 
        {
            const newWord = wordInput.value.trim();
            const newDirection = document.querySelector('input[name="editSwapDirection"]:checked').value;
            // Validar que la palabra no esté vacía
            if (!newWord) 
            {
                plnToast("La palabra no puede estar vacía.");
                return;
            }
            // Verificar si el nuevo nombre ya existe (excluyendo el item actual)
            if (newWord !== item.word && window.swapWords.some((sw, i) => i !== index && sw.word === newWord)) 
            {
                plnToast("Esa palabra ya existe en la lista.");
                return;
            }
            // Actualizar el item en el array global
            window.swapWords[index].word = newWord;
            window.swapWords[index].direction = newDirection;            
            saveSwapWordsToStorage();
            renderSwapList();
            modalOverlay.remove();
        });
        buttonContainer.appendChild(saveBtn);
        modalContent.appendChild(buttonContainer);
        modalOverlay.appendChild(modalContent);
        document.body.appendChild(modalOverlay);
    }
    // --- Contenedor principal ---
    const title = document.createElement("h4");
    title.textContent = "Gestión de Reemplazos";
    title.style.fontSize = "15px";
    title.style.marginBottom = "10px";
    parentContainer.appendChild(title);
    // --- Dropdown de modo de reemplazo ---
    const modeSelector = document.createElement("select");
    modeSelector.id = "replacementModeSelector";
    modeSelector.style.marginBottom = "10px";
    modeSelector.style.marginTop = "5px";
    // Añadir opciones al selector
    const optionWords = document.createElement("option");
    optionWords.value = "words";
    optionWords.textContent = "Reemplazos de palabras";
    modeSelector.appendChild(optionWords);
    // Añadir opción para swap
    const optionSwap = document.createElement("option");
    optionSwap.value = "swapStart";
    optionSwap.textContent = "Palabras al inicio/final (swap)"; // Texto actualizado
    modeSelector.appendChild(optionSwap);
    parentContainer.appendChild(modeSelector);
    //Contenedor para reemplazos y controles
    const replacementsContainer = document.createElement("div");
    replacementsContainer.id = "replacementsContainer";
    // Sección para añadir nuevos reemplazos
    const addSection = document.createElement("div");
    addSection.style.display = "flex";
    addSection.style.gap = "8px";
    addSection.style.marginBottom = "12px";
    addSection.style.alignItems = "flex-end"; // Alinear inputs y botón
    // Contenedores para inputs de texto
    const fromInputContainer = document.createElement("div");
    fromInputContainer.style.flexGrow = "1";
    const fromLabel = document.createElement("label");
    fromLabel.textContent = "Texto Original:";
    fromLabel.style.display = "block";
    fromLabel.style.fontSize = "12px";
    fromLabel.style.marginBottom = "2px";
    // Input para el texto original
    const fromInput = document.createElement("input");
    fromInput.type = "text";
    fromInput.placeholder = "Ej: Urb.";
    fromInput.style.width = "95%"; // Para que quepa bien
    fromInput.style.padding = "6px";
    fromInput.style.border = "1px solid #ccc";
    // Añadir label e input al contenedor
    fromInputContainer.appendChild(fromLabel);
    fromInputContainer.appendChild(fromInput);
    addSection.appendChild(fromInputContainer);
    // Contenedor para el texto de reemplazo
    const toInputContainer = document.createElement("div");
    toInputContainer.style.flexGrow = "1";
    const toLabel = document.createElement("label");
    toLabel.textContent = "Texto de Reemplazo:";
    toLabel.style.display = "block";
    toLabel.style.fontSize = "12px";
    toLabel.style.marginBottom = "2px";
    // Input para el texto de reemplazo
    const toInput = document.createElement("input");
    toInput.type = "text";
    toInput.placeholder = "Ej: Urbanización";
    toInput.style.width = "95%";
    toInput.style.padding = "6px";
    toInput.style.border = "1px solid #ccc";
    toInputContainer.appendChild(toLabel);
    toInputContainer.appendChild(toInput);
    addSection.appendChild(toInputContainer);
    // Atributos para evitar corrección ortográfica
    fromInput.setAttribute('spellcheck', 'false');
    toInput.setAttribute('spellcheck', 'false');
    // Botón para añadir el reemplazo
    const addReplacementBtn = document.createElement("button");
    addReplacementBtn.textContent = "Añadir";
    addReplacementBtn.style.padding = "6px 10px";
    addReplacementBtn.style.cursor = "pointer";
    addReplacementBtn.style.height = "30px"; // Para alinear con los inputs
    addSection.appendChild(addReplacementBtn);
    // Elemento UL para la lista de reemplazos
    const listElement = document.createElement("ul");
    listElement.id = "replacementsListElementID"; // ID ÚNICO para esta lista
    listElement.style.maxHeight = "150px";
    listElement.style.overflowY = "auto";
    listElement.style.border = "1px solid #ddd";
    listElement.style.padding = "8px";
    listElement.style.margin = "0 0 10px 0";
    listElement.style.background = "#fff";
    listElement.style.listStyle = "none";
    // Event listener para el botón "Añadir"
    addReplacementBtn.addEventListener("click", () =>
    {
        const fromValue = fromInput.value.trim();
        const toValue = toInput.value.trim();
        if (!fromValue)
        {
            plnToast("El campo 'Texto Original' es requerido.", 3000);
            return;
        }
        // Validar que no sea solo caracteres especiales
        if (fromValue === toValue)
        {
            plnToast("El texto original y el de reemplazo no pueden ser iguales.", 3000);
            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", () => plnUiExportDataToXml("words")); // Exporta Excluidas/Reemplazos/Swap
    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";
        plnUiHandleXmlFileDrop(e.dataTransfer.files[0]); // defaults to "words"
    });
    // --- 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 EXPLICACIONES ===
    const swapTitle = document.createElement("h4");
    swapTitle.textContent = "Palabras de Intercambio (Swap)";
    swapTitle.style.fontSize = "14px";
    swapTitle.style.marginBottom = "8px";
    swapContainer.appendChild(swapTitle);
    // Caja de explicación
    const swapExplanationBox = document.createElement("div");
    swapExplanationBox.style.background = "#f4f8ff";
    swapExplanationBox.style.borderLeft = "4px solid #2d6df6";
    swapExplanationBox.style.padding = "10px";
    swapExplanationBox.style.margin = "10px 0";
    swapExplanationBox.style.fontSize = "13px";
    swapExplanationBox.style.lineHeight = "1.4";
    swapExplanationBox.innerHTML =
        "<strong>🔄 ¿Qué hace esta lista?</strong><br>" +
        "Las palabras aquí se moverán al inicio o al final del nombre.<br>" +
        "<em>Ej:</em> \"Las Palmas <b>Urbanización</b>\" → \"<b>Urbanización</b> Las Palmas\" (si se configura 'Al Inicio').";
    swapContainer.appendChild(swapExplanationBox);
    // Contenedor principal para los controles, ahora apilado verticalmente
    const swapInputContainer = document.createElement("div");
    swapInputContainer.style.display = "flex";
    swapInputContainer.style.flexDirection = "column"; // Apilado vertical
    swapInputContainer.style.gap = "8px";
    swapInputContainer.style.marginBottom = "8px";
    // Fila 1: Input de la palabra
    const swapInputDiv = document.createElement("div");
    const swapInputLabel = document.createElement("label");
    swapInputLabel.textContent = "Palabra a agregar:";
    swapInputLabel.style.fontSize = "12px";
    swapInputLabel.style.display = "block";
    swapInputLabel.style.marginBottom = "2px";
    const swapInput = document.createElement("input");
    swapInput.type = "text";
    swapInput.placeholder = "Ej: Urbanización";
    swapInput.style.width = "calc(100% - 12px)"; // Ancho completo
    swapInput.style.padding = "6px";
    swapInput.setAttribute('spellcheck', 'false');
    swapInputDiv.appendChild(swapInputLabel);
    swapInputDiv.appendChild(swapInput);
    // Fila 2: Controles de dirección y botón de añadir
    const controlsRow = document.createElement("div");
    controlsRow.style.display = "flex";
    controlsRow.style.alignItems = "center";
    controlsRow.style.gap = "10px";
    // Contenedor para los radio buttons
    const directionContainer = document.createElement("div");
    directionContainer.style.display = "flex";
    directionContainer.style.gap = "15px";
    directionContainer.style.padding = "5px 10px";
    directionContainer.style.border = "1px solid #ccc";
    directionContainer.style.borderRadius = "4px";
    ['start', 'end'].forEach(dir => {
        const optionContainer = document.createElement('div');
        optionContainer.style.display = 'flex';
        optionContainer.style.alignItems = 'center';
        const radio = document.createElement("input");
        radio.type = "radio";
        radio.name = "swapDirection";
        radio.value = dir;
        radio.id = `swap_${dir}`;
        if (dir === 'start') radio.checked = true;
        radio.style.marginRight = "4px";
        //Permitir seleccionar el radio al hacer clic en la etiqueta
        const label = document.createElement("label");
        label.htmlFor = `swap_${dir}`;
        label.textContent = dir === 'start' ? 'Mover a Inicio' : 'Mover al Final'; // ETIQUETAS ACTUALIZADAS
        label.style.fontSize = "13px";
        label.style.cursor = "pointer";
        optionContainer.appendChild(radio);
        optionContainer.appendChild(label);
        directionContainer.appendChild(optionContainer);
    });
    // Botón para añadir
    const swapBtn = document.createElement("button");
    swapBtn.textContent = "Añadir";
    swapBtn.style.padding = "6px 12px";
    swapBtn.style.height = "32px";
    // Ensamblar la fila 2
    controlsRow.appendChild(directionContainer);
    controlsRow.appendChild(swapBtn);
    // Ensamblar el contenedor principal
    swapInputContainer.appendChild(swapInputDiv);
    swapInputContainer.appendChild(controlsRow);
    swapContainer.appendChild(swapInputContainer); // Añadir el contenedor principal al panel
    // === EVENT LISTENER PARA EL BOTÓN AÑADIR (sin cambios en la lógica) ===
    swapBtn.addEventListener("click", () => 
    {
        const val = swapInput.value.trim();
        const direction = document.querySelector('input[name="swapDirection"]:checked').value;
        if (!val || /^[^a-zA-Z0-9]+$/.test(val)) 
        {
            plnToast("No se permiten caracteres especiales solos o palabras vacías.", 3000);
            return;
        }

        if (window.swapWords.some(item => item.word === val)) {
            plnToast("Esa palabra ya existe en la lista.", 3000);
            return;
        }
        window.swapWords.push({ word: val, direction: direction });
        saveSwapWordsToStorage();
        swapInput.value = "";
        renderSwapList();
    });
    // === CAMPO DE BÚSQUEDA ===
    const searchSwapInput = document.createElement("input");
    searchSwapInput.type = "text";
    searchSwapInput.placeholder = "Buscar palabra...";
    searchSwapInput.id = "searchSwapInput";
    searchSwapInput.style.width = "calc(100% - 12px)";
    searchSwapInput.style.padding = "6px";
    searchSwapInput.style.marginBottom = "8px";
    searchSwapInput.style.border = "1px solid #ccc";
    searchSwapInput.addEventListener("input", () => renderSwapList());
    swapContainer.appendChild(searchSwapInput);
    parentContainer.appendChild(swapContainer);
    // === LÓGICA DE RENDERIZADO DE LA LISTA (ACTUALIZADA) ===
    function renderSwapList() 
    {
        const searchInput = document.getElementById("searchSwapInput");
        const swapList = swapContainer.querySelector("ul") || (() => 
        {
            const ul = document.createElement("ul");
            ul.id = "swapList";
            ul.style.maxHeight = "120px";
            ul.style.overflowY = "auto";
            ul.style.border = "1px solid #ddd";
            ul.style.padding = "8px";
            ul.style.margin = "0";
            ul.style.background = "#fff";
            ul.style.listStyle = "none";
            swapContainer.appendChild(ul);
            return ul;
        })();
        swapList.innerHTML = "";
        if (!window.swapWords || window.swapWords.length === 0) 
        {
            const li = document.createElement("li");
            li.textContent = "No hay palabras de intercambio definidas.";
            li.style.textAlign = "center";
            li.style.color = "#777";
            swapList.appendChild(li);
            return;
        }
        const searchTerm = searchInput ? searchInput.value.trim().toLowerCase() : "";
        const filteredSwapWords = window.swapWords.filter(item => item.word.toLowerCase().includes(searchTerm));
        filteredSwapWords.forEach((item, index) => 
        {
            const li = document.createElement("li");
            li.style.display = "flex";
            li.style.justifyContent = "space-between";
            li.style.alignItems = "center";
            li.style.padding = "4px 2px";
            li.style.borderBottom = "1px solid #f0f0f0";
            // Mostrar la palabra con su dirección
            const wordSpan = document.createElement("span");
            const directionIcon = item.direction === "start" ? "←" : "→";
            const directionText = item.direction === "start" ? "Al Inicio" : "Al Final";
            wordSpan.innerHTML = `<b>${item.word}</b> <small style="color: #666;">(${directionIcon} ${directionText})</small>`;
            // Contenedor para los botones
            const btnContainer = document.createElement("span");
            btnContainer.style.display = "flex";
            btnContainer.style.gap = "4px";
            // Botón Editar
            const editBtn = document.createElement("button");
            editBtn.innerHTML = "✏️";
            editBtn.title = "Editar";
            editBtn.style.border = "none";
            editBtn.style.background = "transparent";
            editBtn.style.cursor = "pointer";
            editBtn.addEventListener("click", () => 
            {
                const originalIndex = window.swapWords.findIndex(sw => sw.word === item.word);
                if (originalIndex > -1) {
                    openSwapWordEditor(window.swapWords[originalIndex], originalIndex);
                }
            });
            // Botón Eliminar
            const deleteBtn = document.createElement("button");
            deleteBtn.innerHTML = "🗑️";
            deleteBtn.title = "Eliminar";
            deleteBtn.style.border = "none";
            deleteBtn.style.background = "transparent";
            deleteBtn.style.cursor = "pointer";
            deleteBtn.addEventListener("click", () => 
            {
                if (confirm(`¿Eliminar la palabra swap '${item.word}'?`)) 
                {
                    const indexToDelete = window.swapWords.findIndex(sw => sw.word === item.word);
                    if (indexToDelete > -1) 
                    {
                        window.swapWords.splice(indexToDelete, 1);
                        saveSwapWordsToStorage();
                        renderSwapList();
                    }
                }
            });
            btnContainer.appendChild(editBtn);
            btnContainer.appendChild(deleteBtn);
            li.appendChild(wordSpan);
            li.appendChild(btnContainer);
            swapList.appendChild(li);
        });
    }
    // Render inicial y listener del selector
    renderReplacementsList(listElement);
    renderSwapList();
    modeSelector.addEventListener("change", () => 
    {
        replacementsContainer.style.display = modeSelector.value === "words" ? "block" : "none";
        swapContainer.style.display = modeSelector.value === "swapStart" ? "block" : "none";
    });
}// Crea el gestor de diccionario
//Permite crear la lista de palabras swap
function renderSwapList() 
{
    const searchInput = document.getElementById("searchSwapInput");
    const swapList = swapContainer.querySelector("ul") || (() => 
    {
        const ul = document.createElement("ul");
        ul.id = "swapList";
        ul.style.maxHeight = "120px";
        ul.style.overflowY = "auto";
        ul.style.border = "1px solid #ddd";
        ul.style.padding = "8px";
        ul.style.margin = "0";
        ul.style.background = "#fff";
        ul.style.listStyle = "none";
        swapContainer.appendChild(ul);
        return ul;
    })();

    swapList.innerHTML = "";

    if (!window.swapWords || window.swapWords.length === 0) {
        const li = document.createElement("li");
        li.textContent = "No hay palabras de intercambio definidas.";
        li.style.textAlign = "center";
        li.style.color = "#777";
        swapList.appendChild(li);
        return;
    }

    const searchTerm = searchInput ? searchInput.value.trim().toLowerCase() : "";
    const filteredSwapWords = window.swapWords.filter(item => item.word.toLowerCase().includes(searchTerm));

    filteredSwapWords.forEach((item, index) => {
        const li = document.createElement("li");
        li.style.display = "flex";
        li.style.justifyContent = "space-between";
        li.style.alignItems = "center";
        li.style.padding = "4px 2px";
        li.style.borderBottom = "1px solid #f0f0f0";

        const wordSpan = document.createElement("span");
        const directionIcon = item.direction === "start" ? "←" : "→";
        const directionText = item.direction === "start" ? "Al Inicio" : "Al Final";
        wordSpan.innerHTML = `<b>${item.word}</b> <small style="color: #666;">(${directionIcon} ${directionText})</small>`;

        const btnContainer = document.createElement("span");
        btnContainer.style.display = "flex";
        btnContainer.style.gap = "4px";

        // Botón Editar
        const editBtn = document.createElement("button");
        editBtn.innerHTML = "✏️";
        editBtn.title = "Editar";
        editBtn.style.border = "none";
        editBtn.style.background = "transparent";
        editBtn.style.cursor = "pointer";
        editBtn.addEventListener("click", () => {
            const originalIndex = window.swapWords.findIndex(sw => sw.word === item.word);
            if (originalIndex > -1) {
                openSwapWordEditor(window.swapWords[originalIndex], originalIndex);
            }
        });

        // Botón Eliminar
        const deleteBtn = document.createElement("button");
        deleteBtn.innerHTML = "🗑️";
        deleteBtn.title = "Eliminar";
        deleteBtn.style.border = "none";
        deleteBtn.style.background = "transparent";
        deleteBtn.style.cursor = "pointer";
        deleteBtn.addEventListener("click", () => {
            if (confirm(`¿Eliminar la palabra swap '${item.word}'?`)) 
            {
                const indexToDelete = window.swapWords.findIndex(sw => sw.word === item.word);
                if (indexToDelete > -1)
                {
                    window.swapWords.splice(indexToDelete, 1);
                    saveSwapWordsToStorage();
                    renderSwapList();
                }
            }
        });

        btnContainer.appendChild(editBtn);
        btnContainer.appendChild(deleteBtn);

        li.appendChild(wordSpan);
        li.appendChild(btnContainer);
        swapList.appendChild(li);
    });
}//renderSwapList
// Renderiza la lista de palabras excluidas
function renderExcludedWordsList(ulElement, filter = "")
{
    // Asegurarse de que ulElement es un elemento UL válido
    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) 
                {
                    plnToast(validation.msg,3000);
                    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);
        ulElement.appendChild(li);
    });//
}// renderExcludedWordsList

// Función para renderizar la lista de lugares excluidos
function renderExcludedPlacesList(ulElement, filter = "")
{
    // Asegurarse de que ulElement es un elemento UL válido
    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)
        });
        // Si no hay lugares para renderizar, mostrar mensaje
        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;
        }
        // Renderizar cada lugar
        placesToRender.forEach(([placeId, placeNameSaved]) =>
        { // Ahora recibimos [ID, NombreGuardado]
            const li = document.createElement("li");
            li.style.display = "flex";
            li.style.justifyContent = "space-between";
            li.style.alignItems = "center";
            li.style.padding = "4px 2px";
            li.style.borderBottom = "1px solid #f0f0f0";
            // Muestra el nombre guardado, con un fallback si el nombre guardado está vacío.
            const displayName = placeNameSaved || `ID: ${placeId}`;
            const linkSpan = document.createElement("span");
            linkSpan.style.flexGrow = "1";
            linkSpan.style.marginRight = "10px";
            const link = document.createElement("a");
            link.href = "#";
            link.textContent = displayName; // Muestra el nombre guardado
            link.title = `Abrir lugar en WME (ID: ${placeId})`; // El tooltip sigue mostrando el ID
            link.addEventListener("click", (e) =>
            {
                e.preventDefault();
                // Intenta obtener el lugar del modelo para seleccionarlo y centrarlo
                // Usamos W.model como fallback si wmeSDK.DataModel.Venues.getById no es eficiente aquí o no está diseñado para esta interacción
                const venueObj = W.model.venues.getObjectById(placeId); // <---
                const venueSDKForUse = venueSDKForRender; // Objeto del SDK que pasamos desde processNextPlace
                if (venueObj)
                {
                    if (W.map && typeof W.map.setCenter === 'function' && venueObj.getOLGeometry && venueObj.getOLGeometry().getCentroid)
                    {
                        W.map.setCenter(venueObj.getOLGeometry().getCentroid(), null, false, 0); 
                    }
                    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
        });
        li.appendChild(deleteBtn);
        ulElement.appendChild(li);
    });
}// renderExcludedPlacesList

// 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)
        {
            plnLog('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)
    {
        plnLog('error',"[WME PLN] Error guardando el diccionario en localStorage:", e);
    }
}// renderDictionaryList

// Renderiza la lista de reemplazos
function renderReplacementsList(ulElement)
{
    plnLog('swap', "[WME_PLN][DEBUG] renderReplacementsList llamada para:", ulElement ? ulElement.id : "Elemento UL nulo");
    if (!ulElement)
    {
        plnLog('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())
            {
                plnToast("El campo 'Texto Original' es requerido.", 3000);
                return;
            }
            if (newFrom === newTo)
            {
                plnToast("El texto original y el de reemplazo no pueden ser iguales.", 3000);
                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);
    });
}// renderReplacementsList
// Carga las palabras excluidas desde localStorage
function saveExcludedWordsToLocalStorage()
{
    try
    {
        localStorage.setItem("excludedWordsList", JSON.stringify(Array.from(excludedWords)));
        plnLog('swap', "[WME PLN] Lista de palabras especiales guardada en localStorage.");
    }
    catch (e)
    {
        plnLog('error',"[WME PLN] Error guardando palabras especiales en localStorage:", e);
    }
}// saveExcludedWordsToLocalStorage
// Carga las palabras excluidas desde localStorage
// Añadir esta función dentro de WME_PLN_module_lists.js
function loadExcludedWordsFromLocalStorage()
{
    if (!window.excludedWords)
    {
        const stored = localStorage.getItem('wme_excludedWords'); // Ojo: la clave era diferente
        if (stored)
        {
            try
            {
                const parsed = JSON.parse(stored);
                if (Array.isArray(parsed))
                {
                    window.excludedWords = new Set(parsed);
                }
            }
            catch (e)
            {
                plnLog('error','No se pudieron cargar las palabras excluidas:', e);
            }
        }
        // Asegurarse que window.excludedWords sea un Set si no existe
        if (!(window.excludedWords instanceof Set))
        {
            window.excludedWords = new Set();
        }
    }
}// loadExcludedWordsFromLocalStorage
// 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())));
        plnLog('swap', '[WME PLN] Lugares excluidos GUARDADOS EXITOSAMENTE.');
    }
    catch (e)
    {
        plnLog('error','[WME PLN] Error guardando lugares excluidos en localStorage:', e);
    }
}// saveExcludedPlacesToLocalStorage
// Función para cargar los IDs de lugares excluidos desde localStorage
function loadExcludedPlacesFromLocalStorage()
{
    if (!window.excludedPlaces)
    {
        const storedData = localStorage.getItem('wme_excluded_places');
        if (storedData)
        {
            try
            {
                // El formato guardado es un array de [id, nombre]
                const parsedArray = JSON.parse(storedData);
                if (Array.isArray(parsedArray))
                {
                    // Convertimos el array de nuevo a un Map
                    window.excludedPlaces = new Map(parsedArray);
                }
            }
            catch (e)
            {
                plnLog('error','No se pudieron cargar los lugares excluidos:', e);
            }
        }

        // Si después de todo no es un Map, lo inicializamos como uno vacío
        if (!(window.excludedPlaces instanceof Map))
        {
            window.excludedPlaces = new Map();
        }
    }
}// loadExcludedPlacesFromLocalStorage
// Función para cargar palabras del diccionario desde Google Sheets (Hoja "Dictionary")
async function loadDictionaryWordsFromSheet(forceReload = false)
{
    const SPREADSHEET_ID = "1kJDEOn8pKLdqEyhIZ9DdcrHTb_GsoeXgIN4GisrpW2Y";
    const API_KEY = "AIzaSyAQbvIQwSPNWfj6CcVEz5BmwfNkao533i8";
    const RANGE = "Dictionary!A2:B";

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

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

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

    // verifica si hay datos en caché
    // Si hay datos en caché y no se fuerza la recarga, los usa
    // Si no hay datos en caché o se fuerza la recarga, hace la solicitud
    const cachedData = localStorage.getItem("wme_pln_dictionary_cache");
    if (!forceReload && cachedData)
    {
        try
        {
            const { data, timestamp } = JSON.parse(cachedData);
            // usar caché si tiene menos de 24 horas
            if (data && timestamp && (Date.now() - timestamp < 24 * 60 * 60 * 1000))
            {
                plnLog('sdk', '[WME PLN] Usando datos en caché. Tiempo restante para expirar:', ((timestamp + 24 * 60 * 60 * 1000) - Date.now())/1000/60, 'minutos');
                plnLog('swap', '[WME PLN] Usando diccionario en caché');
                // restaura las palabras y el índice del diccionario desde la caché
                window.dictionaryWords = new Set(data.words);
                window.dictionaryIndex = data.index;
                resolve();
                return;
            }
        } catch (e) {
            plnLog('warn','[WME PLN] Error al leer caché del diccionario:', e);
        }
    }
    makeRequest(
    {
        method: "GET",
        url: url,
        timeout: 10000,
            onload: function (response)
            {
                if (response.status >= 200 && response.status < 300)
                {
                    try
                    {
                        const data = JSON.parse(response.responseText);
                        let newWordsAdded = 0;

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

                            // Cache the dictionary
                            try
                            {
                                localStorage.setItem("wme_pln_dictionary_cache", JSON.stringify(
                                {
                                    data:
                                    {
                                        words: Array.from(window.dictionaryWords),
                                        index: window.dictionaryIndex
                                    },
                                    timestamp: Date.now()
                                }));
                            }
                            catch (e)
                            {
                                plnLog('warn','[WME PLN] Error al guardar caché del diccionario:', e);
                            }
                            // también guarda en localStorage para uso rápido
                            try
                            {
                                localStorage.setItem("dictionaryWordsList", JSON.stringify(Array.from(window.dictionaryWords)));
                            }
                            catch (e)
                            {
                                plnLog('error',"[WME PLN] Error guardando diccionario en localStorage:", e);
                            }

                            plnLog('swap', `[WME PLN] Diccionario cargado: ${newWordsAdded} palabras nuevas añadidas.`);
                        }
                    }
                    catch (e)
                    {
                        plnLog('error','[WME PLN] Error al procesar datos del diccionario:', e);
                    }
                }
                resolve();
            },
            // Añade esto en ambas funciones, justo después del try/catch en onload:
            onerror: function (error)
            {
                plnLog('error','[WME PLN] Error de red al cargar datos desde Google Sheets:', error);
                plnLog('scan', '[WME PLN] URL que falló:', url);
                resolve(); // Resolver la promesa para no bloquear
            },
            ontimeout: function ()
            {
                plnLog('error','[WME PLN] Timeout al cargar diccionario');
                resolve();
            }
        });
    });
}//loadDictionaryWordsFromSheet
// Carga las palabras reemplazo
function saveReplacementWordsToStorage()
{
    try
    {
        localStorage.setItem("replacementWordsList", JSON.stringify(replacementWords));
        plnLog('swap', "[WME PLN] Lista de reemplazos guardada en localStorage.");
    }
    catch (e)
    {
        plnLog('error',"[WME PLN] Error guardando lista de reemplazos en localStorage:", e);
    }
}// saveReplacementWordsToStorage
// 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)
        {
            plnLog('error',"[WME PLN] Error cargando lista de reemplazos desde localStorage:", e);
            replacementWords = {};
        }
    }
    else
    {
        replacementWords = {}; // Inicializar si no hay nada guardado
    }
    plnLog('swap', "[WME PLN] Reemplazos cargados:",    Object.keys(replacementWords).length, "reglas.");
}// loadReplacementWordsFromStorage

 // Carga las palabras excluidas desde localStorage
// Función para guardar las palabras swap en localStorage (formato nuevo)
function saveSwapWordsToStorage()
{
    try
    {
        localStorage.setItem("swapWords", JSON.stringify(window.swapWords || []));
        plnLog('swap', "[WME PLN] SwapWords guardadas en localStorage:", window.swapWords ? window.swapWords.length : 0, "palabras");
    }
    catch (e)
    {
        plnLog('error',"[WME PLN] Error guardando swapWords en localStorage:", e);
    }
}// saveSwapWordsToStorage

   // Función para cargar las palabras swap desde localStorage con migración automática
function loadSwapWordsFromStorage()
{
    const stored = localStorage.getItem("swapWords");

    // Si hay datos en localStorage, intentar parsearlos
    if (stored)
    {
        try
        {
            const parsed = JSON.parse(stored);

            // MIGRACIÓN AUTOMÁTICA: Verificar el formato de los datos
            if (Array.isArray(parsed) && parsed.length > 0)
            {
                // Verificar si es formato antiguo (array de strings)
                if (typeof parsed[0] === "string")
                {
                   plnLog('swap', "[WME PLN] Detectado formato antiguo de swapWords. Migrando automáticamente...");

                    // Migrar formato antiguo a nuevo formato
                    window.swapWords = parsed.map(word => ({
                        word: word,
                        direction: "start" // Todas las palabras existentes se configuran como "start" por defecto
                    }));

                    // Guardar el nuevo formato inmediatamente
                    saveSwapWordsToStorage();
                   plnLog('swap', `[WME PLN] Migración completada: ${window.swapWords.length} palabras migradas a formato 'start'.`);
                }
                else if (typeof parsed[0] === "object" && parsed[0].hasOwnProperty('word'))
                {
                    // Ya está en formato nuevo
                    window.swapWords = parsed;
                   plnLog('swap', "[WME PLN] Formato nuevo de swapWords detectado. No se requiere migración.");
                }
                else
                {
                    // Formato desconocido, inicializar vacío
                    plnLog('warn',"[WME PLN] Formato desconocido en swapWords. Inicializando lista vacía.");
                    window.swapWords = [];
                }
            }
            else
            {
                // Array vacío o null
                window.swapWords = [];
            }
        }
        catch (e)
        {
            plnLog('error',"[WME PLN] Error al parsear swapWords desde localStorage:", e);
            window.swapWords = [];
        }
    }
    else
    {
        // No hay datos guardados
        window.swapWords = [];
    }
}// loadSwapWordsFromStorage
长期地址
遇到问题?请前往 GitHub 提 Issues。