WME PLN Module - Lists Handler

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

ეს სკრიპტი არ უნდა იყოს პირდაპირ დაინსტალირებული. ეს ბიბლიოთეკაა, სხვა სკრიპტებისთვის უნდა ჩართეთ მეტა-დირექტივაში // @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。