WME Residencial Navigator

Herramienta completa para gestionar lugares residenciales en WME con nombre visible

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         WME Residencial Navigator
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  Herramienta completa para gestionar lugares residenciales en WME con nombre visible
// @author       TuNombre
// @match        https://www.waze.com/*/editor*
// @match        https://beta.waze.com/*/editor*
// @match        https://www.waze.com/editor/*
// @grant        GM_addStyle
// @require      https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// ==/UserScript==

(function() {
    'use strict';

    // Configuración
    const TAB_NAME = "Residenciales";
    const DEBUG_MODE = true;

    // Estilos mejorados
    GM_addStyle(`
        #residentialPanel {
            background: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            width: 100%;
            max-height: 80vh;
            overflow-y: auto;
            margin-top: 10px;
            border: 1px solid #ddd;
        }

        #residentialList {
            width: 100%;
            margin: 15px 0;
            padding: 5px;
            max-height: 60vh;
            overflow-y: auto;
        }

        .residential-item {
            display: flex;
            align-items: center;
            padding: 8px 0;
            border-bottom: 1px solid #eee;
        }

        .residential-checkbox {
            margin-right: 12px;
            cursor: pointer;
        }

        .residential-name {
            flex-grow: 1;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            font-size: 14px;
        }

        .go-to-btn {
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 5px 10px;
            cursor: pointer;
            margin-left: 10px;
            flex-shrink: 0;
            font-size: 13px;
            transition: background 0.2s;
        }

        .go-to-btn:hover {
            background: #3e8e41;
        }

        .action-buttons {
            display: flex;
            gap: 10px;
            margin-top: 15px;
        }

        .refresh-btn, .delete-btn {
            color: white;
            border: none;
            border-radius: 4px;
            padding: 8px 12px;
            cursor: pointer;
            flex-grow: 1;
            font-weight: bold;
            transition: opacity 0.2s;
        }

        .refresh-btn {
            background: #2196F3;
        }

        .refresh-btn:hover {
            opacity: 0.9;
        }

        .delete-btn {
            background: #f44336;
        }

        .delete-btn:hover {
            opacity: 0.9;
        }

        .select-all-container {
            margin: 10px 0;
            font-size: 14px;
            display: flex;
            align-items: center;
        }

        .status-message {
            margin-top: 15px;
            padding: 10px;
            border-radius: 4px;
            font-size: 13px;
            text-align: center;
            display: none;
        }

        .success {
            background-color: #dff0d8;
            color: #3c763d;
            border: 1px solid #d6e9c6;
        }

        .warning {
            background-color: #fcf8e3;
            color: #8a6d3b;
            border: 1px solid #faebcc;
        }

        .error {
            background-color: #f2dede;
            color: #a94442;
            border: 1px solid #ebccd1;
        }

        /* Estilo para la pestaña personalizada */
        #user-tabs .residential-tab {
            background-color: #f8f9fa;
        }

        #user-tabs .residential-tab.active {
            background-color: #e9ecef;
        }

        /* Asegurar que el texto de la pestaña sea visible */
        #user-tabs .residential-tab a {
            color: #333 !important;
            font-weight: bold;
            padding: 8px 12px;
            display: block;
        }

        /* Botón flotante de respaldo */
        #residentialFloatingBtn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 9999;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 50%;
            width: 50px;
            height: 50px;
            font-size: 20px;
            cursor: pointer;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            display: none;
        }
    `);

    let residentialPlaces = [];
    let panelInitialized = false;

    // Función para debug
    function debugLog(message) {
        if (DEBUG_MODE) {
            console.log(`[Residencial Navigator] ${message}`);
        }
    }

    // Función principal para registrar la pestaña
    function registerSidebarTab() {
        debugLog(`Intentando registrar pestaña: ${TAB_NAME}`);

        // Método 1: API oficial de WME
        if (typeof W !== 'undefined' && W.userscripts && typeof W.userscripts.registerSidebarTab === 'function') {
            try {
                const { tabLabel, tabPane } = W.userscripts.registerSidebarTab(TAB_NAME);

                if (tabPane) {
                    debugLog("Pestaña registrada con API oficial");

                    // Asegurar que el nombre sea visible
                    tabLabel.textContent = TAB_NAME;
                    tabLabel.title = TAB_NAME;
                    tabLabel.style.color = "#333";
                    tabLabel.style.fontWeight = "bold";

                    const contentPanel = document.createElement("div");
                    contentPanel.id = "residential-content";
                    tabPane.appendChild(contentPanel);

                    tabLabel.addEventListener("click", function(e) {
                        e.preventDefault();
                        toggleResidentialPanel(contentPanel);
                    });

                    initResidentialPanel(contentPanel);
                    return;
                }
            } catch (e) {
                debugLog(`Error con API oficial: ${e}`);
            }
        }

        // Método 2: Alternativo si falla la API
        setupAlternativeTab();
    }

    // Método alternativo para crear pestaña
    function setupAlternativeTab() {
        debugLog("Usando método alternativo para crear pestaña");

        const userTabs = document.querySelector('#user-tabs ul');
        if (!userTabs) {
            debugLog("No se encontró #user-tabs, creando botón flotante");
            createFloatingButton();
            return;
        }

        // Verificar si la pestaña ya existe
        if (document.querySelector(`#user-tabs a[title="${TAB_NAME}"]`)) {
            debugLog("La pestaña ya existe");
            return;
        }

        // Crear nueva pestaña
        const tabItem = document.createElement('li');
        tabItem.className = 'residential-tab';

        const tabLink = document.createElement('a');
        tabLink.href = '#';
        tabLink.title = TAB_NAME;
        tabLink.textContent = TAB_NAME;

        tabItem.appendChild(tabLink);
        userTabs.appendChild(tabItem);

        // Crear panel de contenido
        const userPanel = document.querySelector('#user-panel');
        if (!userPanel) {
            debugLog("No se encontró #user-panel, creando botón flotante");
            createFloatingButton();
            return;
        }

        const contentPanel = document.createElement('div');
        contentPanel.id = 'residential-content';
        contentPanel.style.display = 'none';
        userPanel.appendChild(contentPanel);

        // Manejar clics en la pestaña
        tabLink.addEventListener('click', function(e) {
            e.preventDefault();
            toggleResidentialPanel(contentPanel);

            // Actualizar estado activo
            document.querySelectorAll('#user-tabs li').forEach(tab => {
                tab.classList.remove('active');
            });
            this.parentNode.classList.add('active');
        });

        initResidentialPanel(contentPanel);
    }

    // Crear botón flotante como respaldo
    function createFloatingButton() {
        debugLog("Creando botón flotante de respaldo");

        const floatingBtn = document.createElement('button');
        floatingBtn.id = 'residentialFloatingBtn';
        floatingBtn.textContent = '🏠';
        floatingBtn.title = TAB_NAME;
        floatingBtn.style.display = 'block';
        document.body.appendChild(floatingBtn);

        const panel = document.createElement('div');
        panel.id = 'residentialPanel';
        panel.style.position = 'fixed';
        panel.style.right = '20px';
        panel.style.bottom = '80px';
        panel.style.zIndex = '9998';
        panel.style.display = 'none';
        panel.style.width = '300px';
        document.body.appendChild(panel);

        initResidentialPanel(panel);

        floatingBtn.addEventListener('click', () => {
            panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
        });
    }

    // Inicializar el panel de contenido
    function initResidentialPanel(container) {
        if (panelInitialized) return;
        panelInitialized = true;

        debugLog("Inicializando panel residencial");

        const panel = document.createElement('div');
        panel.id = 'residentialPanel';

        panel.innerHTML = `
            <h3 style="margin-top: 0; color: #333;">Lugares Residenciales</h3>
            <div class="select-all-container">
                <input type="checkbox" id="selectAllResidentials" class="residential-checkbox">
                <label for="selectAllResidentials">Seleccionar todos</label>
            </div>
            <div id="residentialList"></div>
            <div class="action-buttons">
                <button class="refresh-btn">🔄 Actualizar</button>
                <button class="delete-btn">🗑️ Eliminar</button>
            </div>
            <div id="statusMessage" class="status-message" style="display: none;"></div>
        `;

        // Configurar eventos
        panel.querySelector('#selectAllResidentials').addEventListener('change', function() {
            const checkboxes = panel.querySelectorAll('.residential-checkbox');
            checkboxes.forEach(checkbox => {
                if (checkbox !== this) checkbox.checked = this.checked;
            });
        });

        panel.querySelector('.refresh-btn').addEventListener('click', updateResidentialList);
        panel.querySelector('.delete-btn').addEventListener('click', deleteSelectedPlaces);

        container.appendChild(panel);
        updateResidentialList();
    }

    // Mostrar/ocultar panel
    function toggleResidentialPanel(contentPanel) {
        if (!contentPanel) return;

        debugLog("Alternando visibilidad del panel");

        // Ocultar todos los paneles primero
        document.querySelectorAll("#user-panel > div").forEach(panel => {
            panel.style.display = "none";
        });

        // Mostrar/ocultar nuestro panel
        contentPanel.style.display = contentPanel.style.display === "block" ? "none" : "block";

        // Cargar datos si se muestra
        if (contentPanel.style.display === "block" && residentialPlaces.length === 0) {
            updateResidentialList();
        }
    }

    // Actualizar lista de lugares residenciales
    function updateResidentialList() {
        debugLog("Actualizando lista de residenciales");

        if (!W || !W.model || !W.model.venues) {
            showStatusMessage("WME no está completamente cargado", 'error');
            return;
        }

        residentialPlaces = [];
        const venues = W.model.venues.objects;
        let count = 0;

        for (let id in venues) {
            const venue = venues[id];
            if (venue.attributes.categories && venue.attributes.categories.includes('RESIDENCE_HOME')) {
                const name = venue.attributes.name || 'Sin nombre';
                const geom = venue.attributes.geoJSONGeometry;

                if (geom && geom.coordinates) {
                    let lat, lon;
                    if (geom.type === "Point") {
                        [lon, lat] = geom.coordinates;
                    } else if (geom.type === "Polygon") {
                        [lon, lat] = geom.coordinates[0][0];
                    } else {
                        continue;
                    }

                    residentialPlaces.push({
                        id: id,
                        name: name,
                        lat: lat,
                        lon: lon,
                        venueObject: venue
                    });
                    count++;
                }
            }
        }

        updateListDisplay();
        showStatusMessage(`Encontrados ${count} lugares residenciales`, count > 0 ? 'success' : 'warning');
        debugLog(`Encontrados ${count} lugares residenciales`);
    }

    // Actualizar visualización de la lista
    function updateListDisplay() {
        const listContainer = document.getElementById('residentialList');
        if (!listContainer) return;

        listContainer.innerHTML = '';

        if (residentialPlaces.length === 0) {
            listContainer.innerHTML = '<div style="padding: 10px; text-align: center;">No se encontraron lugares residenciales</div>';
            return;
        }

        residentialPlaces.forEach((place, index) => {
            const item = document.createElement('div');
            item.className = 'residential-item';
            item.innerHTML = `
                <input type="checkbox" class="residential-checkbox" data-id="${place.id}">
                <span class="residential-name" title="${place.name}">${place.name || `Lugar ${index + 1}`}</span>
                <button class="go-to-btn" data-id="${place.id}">Ir</button>
            `;

            item.querySelector('.go-to-btn').addEventListener('click', () => goToPlace(place));
            listContainer.appendChild(item);
        });
    }

    // Navegar a un lugar
    function goToPlace(place) {
        debugLog(`Navegando a lugar: ${place.name || place.id}`);

        if (!W || !W.map) return;

        try {
            const center = WazeWrap.LonLat.fromObject({lon: place.lon, lat: place.lat});
            W.map.setCenter(center, 18);

            if (place.venueObject) {
                W.model.venues.setSelectedVenue(place.venueObject);

                // Habilitar botón de guardar
                setTimeout(() => {
                    const saveBtn = document.querySelector('[title="Save"], [title="Guardar"]');
                    if (saveBtn) saveBtn.removeAttribute('disabled');
                }, 500);
            }
        } catch (e) {
            debugLog(`Error al navegar: ${e}`);
            showStatusMessage("Error al navegar al lugar", 'error');
        }
    }

    // Eliminar lugares seleccionados
    function deleteSelectedPlaces() {
        const checkboxes = document.querySelectorAll('.residential-checkbox:checked');
        if (checkboxes.length === 0) {
            showStatusMessage('Selecciona al menos un lugar', 'warning');
            return;
        }

        if (!confirm(`¿Eliminar ${checkboxes.length} lugar(es) seleccionado(s)?`)) return;

        let deletedCount = 0;
        let errors = 0;

        checkboxes.forEach(checkbox => {
            const id = checkbox.dataset.id;
            const place = W.model.venues.getObjectById(id);

            if (!place) {
                errors++;
                return;
            }

            try {
                const DeleteObject = require("Waze/Action/DeleteObject");
                const action = new DeleteObject(place);
                W.model.actionManager.add(action);
                deletedCount++;
                debugLog(`Eliminado lugar: ${id}`);
            } catch (e) {
                debugLog(`Error eliminando lugar ${id}: ${e}`);
                errors++;
            }
        });

        if (deletedCount > 0) {
            showStatusMessage(`Eliminados ${deletedCount} lugares (${errors} errores)`, 'success');
            updateResidentialList();

            // Habilitar botón de guardar
            setTimeout(() => {
                const saveBtn = document.querySelector('[title="Save"], [title="Guardar"]');
                if (saveBtn) saveBtn.removeAttribute('disabled');
            }, 500);
        } else {
            showStatusMessage("No se eliminaron lugares", 'warning');
        }
    }

    // Mostrar mensajes de estado
    function showStatusMessage(message, type) {
        debugLog(`Mensaje de estado [${type}]: ${message}`);

        const element = document.getElementById('statusMessage');
        if (!element) return;

        element.textContent = message;
        element.className = `status-message ${type}`;
        element.style.display = 'block';

        setTimeout(() => {
            element.style.display = 'none';
        }, 5000);
    }

    // Inicialización del script

        let initAttempts = 0;
    const MAX_INIT_ATTEMPTS = 5;

function initScript() {
    debugLog("Iniciando WME Residencial Navigator");
    initAttempts++;

    if (document.querySelector("#user-tabs")) {
        registerSidebarTab();
    } else if (initAttempts < MAX_INIT_ATTEMPTS) {
        setTimeout(initScript, 500);
    } else {
        debugLog("No se encontró #user-tabs después de múltiples intentos");
        createFloatingButton();
    }
}
    

    // Esperar a que cargue WME
    if (document.readyState === 'complete') {
        initScript();
    } else {
        window.addEventListener('load', initScript);
    }

    debugLog("Script cargado correctamente");
})();