WME - UR Manager

Gestión de URs

// ==UserScript==
// @name         WME - UR Manager
// @namespace    http://waze.com/
// @version      2025.04.17.15
// @description  Gestión de URs 
// @author       Crotalo
// @match        https://www.waze.com/*/editor*
// @match        https://beta.waze.com/*/editor*
// @grant        GM_addStyle
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function() {
    'use strict';

    const CONFIG = {
        MENSAJE_RESPUESTA: "¡Hola, Wazer! Gracias por tu reporte. Para resolverlo de forma efectiva, necesitamos un poco más de detalle sobre lo sucedido. Quedamos atentos a tu respuesta.",
        MENSAJE_CIERRE: "¡¡Hola Wazer! Buen día, Lamentablemente no pudimos solucionar el error en esta ocasión. Por favor, déjanos más datos la próxima vez. Gracias por reportar. ",
        MENSAJE_RESUELTA: "¡Hola Wazer! Buen día, el problema fue solucionado y se verá reflejado en la aplicación en la próxima actualización del mapa, esta tomará entre 3 y 5 días. ¡Gracias por reportar!!",
        DEBUG: true,
        BOTON_ID: 'urna-btn-fecha-exacta',
        PANEL_ID: 'urna-panel-fecha-exacta',
        INTERVALO_VERIFICACION: 5000,
        UMBRAL_VIEJO: 7,
        UMBRAL_RECIENTE: 3,
        RETRASO_ENTRE_ACCIONES: 800
    };

    GM_addStyle(`
        #${CONFIG.BOTON_ID} {
            position: fixed !important;
            bottom: 20px !important;
            left: 20px !important;
            z-index: 99999 !important;
            padding: 10px 15px !important;
            background: #3498db !important;
            color: white !important;
            font-weight: bold !important;
            border: none !important;
            border-radius: 5px !important;
            cursor: pointer !important;
            font-family: Arial, sans-serif !important;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important;
        }
        #${CONFIG.PANEL_ID} {
            position: fixed;
            top: 80px;
            right: 20px;
            width: 500px;
            max-height: 70vh;
            min-height: 200px;
            display: flex;
            flex-direction: column;
            background: white;
            border: 2px solid #999;
            z-index: 99998;
            font-family: Arial, sans-serif;
            font-size: 13px;
            box-shadow: 2px 2px 15px rgba(0,0,0,0.3);
            border-radius: 5px;
            display: none;
        }
        #${CONFIG.PANEL_ID} .panel-content {
            flex: 1;
            overflow-y: auto;
            padding: 15px;
            max-height: calc(70vh - 60px);
        }
        #${CONFIG.PANEL_ID} table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 10px;
        }
        #${CONFIG.PANEL_ID} th {
            position: sticky;
            top: 0;
            background-color: #f2f2f2;
            z-index: 10;
        }
        #${CONFIG.PANEL_ID} th, #${CONFIG.PANEL_ID} td {
            border: 1px solid #ddd;
            padding: 6px;
            text-align: left;
        }
        .ur-old { color: #d9534f; font-weight: bold; }
        .ur-recent { color: #5bc0de; }
        .ur-new { color: #5cb85c; }
        .ur-visitada { background-color: #fdf5d4 !important; }
        .ur-no-fecha { color: #777; font-style: italic; }
        .ur-cerrada { text-decoration: line-through; opacity: 0.6; }
        .btn-centrar {
            padding: 4px 8px;
            background: #3498db;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
        }
        .panel-footer {
            padding: 10px 15px;
            background: #f8f8f8;
            border-top: 1px solid #eee;
            display: flex;
            justify-content: center;
            gap: 10px;
            position: sticky;
            bottom: 0;
            z-index: 20;
            height: 60px;
        }
        .btn-global {
            padding: 8px 15px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-weight: bold;
            white-space: nowrap;
        }
        .btn-responder {
            background: #f0ad4e;
            color: white;
        }
        .btn-cerrar {
            background: #5cb85c;
            color: white;
        }
        .btn-resuelta {
            background: #5bc0de;
            color: white;
        }
    `);

    let estado = {
        URsActuales: [],
        panelVisible: false,
        botonUR: null,
        intervaloVerificacion: null,
        timeouts: [],
        accionEnProgreso: false
    };

    function debugLog(message) {
        if (CONFIG.DEBUG) console.log('[UR Script] ' + message);
    }

    function limpiarTimeouts() {
        estado.timeouts.forEach(timeout => clearTimeout(timeout));
        estado.timeouts = [];
    }

    function agregarTimeout(callback, delay) {
        const timeoutId = setTimeout(() => {
            callback();
            estado.timeouts = estado.timeouts.filter(id => id !== timeoutId);
        }, delay);
        estado.timeouts.push(timeoutId);
        return timeoutId;
    }

    function togglePanelURs() {
        if (estado.panelVisible) {
            $(`#${CONFIG.PANEL_ID}`).fadeOut(300, function() {
                $(this).remove();
            });
            estado.panelVisible = false;
            limpiarTimeouts();
        } else {
            mostrarPanelURs();
        }
    }

    function crearBoton() {
        if ($(`#${CONFIG.BOTON_ID}`).length > 0) return;

        debugLog('Creando botón...');
        estado.botonUR = $(`<button id="${CONFIG.BOTON_ID}">📝 UR Manager</button>`)
            .appendTo('body')
            .on('click', togglePanelURs);

        debugLog('Botón creado exitosamente');
    }

    function parsearFecha(valor) {
        if (!valor) return null;

        if (typeof valor === 'object' && '_seconds' in valor) {
            try {
                return new Date(valor._seconds * 1000 + (valor._nanoseconds / 1000000));
            } catch (e) {
                debugLog(`Error parseando Firebase Timestamp: ${JSON.stringify(valor)}`);
            }
        }

        if (typeof valor === 'string' && valor.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) {
            try {
                return new Date(valor);
            } catch (e) {
                debugLog(`Error parseando fecha ISO: ${valor}`);
            }
        }

        if (/^\d+$/.test(valor)) {
            try {
                const num = parseInt(valor);
                return new Date(num > 1000000000000 ? num : num * 1000);
            } catch (e) {
                debugLog(`Error parseando timestamp numérico: ${valor}`);
            }
        }

        return null;
    }

    function obtenerFechaCreacionExacta(ur) {
        try {
            if (ur.attributes.driveDate) {
                const fecha = parsearFecha(ur.attributes.driveDate);
                if (fecha) return fecha;
            }
            return null;
        } catch (e) {
            debugLog(`Error obteniendo fecha: ${e}`);
            return null;
        }
    }

    function clasificarUR(fecha) {
        if (!fecha) return { estado: "Sin fecha", clase: "ur-no-fecha" };

        const hoy = new Date();
        const diff = hoy - fecha;
        const dias = Math.floor(diff / (1000 * 60 * 60 * 24));

        if (dias > CONFIG.UMBRAL_VIEJO) return { estado: `Antigua (${dias}d)`, clase: "ur-old" };
        if (dias > CONFIG.UMBRAL_RECIENTE) return { estado: `Reciente (${dias}d)`, clase: "ur-recent" };
        return { estado: `Nueva (${dias}d)`, clase: "ur-new" };
    }

    function mostrarPanelURs() {
        estado.panelVisible = true;
        limpiarTimeouts();
        $(`#${CONFIG.PANEL_ID}`).remove();

        const panel = $(`<div id="${CONFIG.PANEL_ID}">`);
        const panelContent = $('<div class="panel-content">');
        const panelFooter = $(`
            <div class="panel-footer">
                <button class="btn-global btn-responder" id="responder-todas">Preguntar</button>
                <button class="btn-global btn-resuelta" id="resolver-todas">Resuelta</button>
                <button class="btn-global btn-cerrar" id="cerrar-todas">No Identificada</button>
            </div>
        `);

        estado.URsActuales = obtenerURsSinAtender();

        if (estado.URsActuales.length === 0) {
            panelContent.html('<div style="padding:15px;text-align:center;"><b>No hay URs sin atender visibles</b></div>');
        } else {
            let tablaHTML = `
                <h3 style="margin-top:0;">URs Activas: ${estado.URsActuales.length}</h3>
                <table>
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Tipo</th>
                            <th>Fecha Creación</th>
                            <th>Estado</th>
                            <th>Acción</th>
                        </tr>
                    </thead>
                    <tbody>`;

            estado.URsActuales.forEach(ur => {
                const id = ur.attributes.id;
                const tipo = ur.attributes.type || 'Desconocido';
                const fecha = obtenerFechaCreacionExacta(ur);
                const clasificacion = clasificarUR(fecha);

                let fechaStr = 'No disponible';
                if (fecha) {
                    fechaStr = fecha.toLocaleDateString('es-ES', {
                        year: 'numeric',
                        month: '2-digit',
                        day: '2-digit',
                        hour: '2-digit',
                        minute: '2-digit'
                    });
                }

                tablaHTML += `
                    <tr id="fila-ur-${id}">
                        <td>${id}</td>
                        <td>${tipo}</td>
                        <td>${fechaStr}</td>
                        <td class="${clasificacion.clase}">${clasificacion.estado}</td>
                        <td><button class="btn-centrar" data-id="${id}">🗺️ Centrar</button></td>
                    </tr>`;
            });

            panelContent.html(`
                ${tablaHTML}
                    </tbody>
                </table>
            `);

            panelContent.on('click', '.btn-centrar', function() {
                if (estado.accionEnProgreso) return;
                const id = $(this).data('id');
                centrarYMostrarUR(id);
                $(`#fila-ur-${id}`).addClass('ur-visitada');
            });
        }

        panelFooter.on('click', '#responder-todas', function() {
            if (estado.accionEnProgreso) return;
            estado.URsActuales.forEach((ur, index) => {
                agregarTimeout(() => responderUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES);
            });
        });

        panelFooter.on('click', '#resolver-todas', function() {
            if (estado.accionEnProgreso) return;
            estado.URsActuales.forEach((ur, index) => {
                agregarTimeout(() => resolverUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES);
            });
        });

        panelFooter.on('click', '#cerrar-todas', function() {
            if (estado.accionEnProgreso) return;
            estado.URsActuales.forEach((ur, index) => {
                agregarTimeout(() => cerrarUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES);
            });
        });

        panel.append(panelContent);
        panel.append(panelFooter);
        panel.appendTo('body').fadeIn(300);
    }

    function obtenerURsSinAtender() {
        try {
            if (!W.model?.mapUpdateRequests?.objects) return [];

            const bounds = W.map.getExtent();
            return Object.values(W.model.mapUpdateRequests.objects)
                .filter(ur => {
                    const geom = ur.getOLGeometry?.();
                    if (!geom) return false;

                    const center = geom.getBounds().getCenterLonLat();
                    if (!bounds.containsLonLat(center)) return false;

                    if (ur.attributes.resolved) return false;

                    const comentarios = ur.attributes.comments || [];
                    return !comentarios.some(c => c.type === 'user' && c.text?.trim().length > 0);
                });
        } catch (e) {
            debugLog('Error obteniendo URs: ' + e);
            return [];
        }
    }

    function centrarYMostrarUR(id) {
        if (estado.accionEnProgreso) return;
        estado.accionEnProgreso = true;

        limpiarTimeouts();

        const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
        if (!ur) {
            debugLog(`UR ${id} no encontrada`);
            estado.accionEnProgreso = false;
            return;
        }

        if (ur.attributes.resolved) {
            debugLog(`UR ${id} ya está resuelta`);
            $(`#fila-ur-${id}`).addClass('ur-cerrada').find('.btn-centrar').prop('disabled', true);
            estado.accionEnProgreso = false;
            return;
        }

        const geom = ur.getOLGeometry?.();
        if (geom) {
            const center = geom.getBounds().getCenterLonLat();
            W.map.setCenter(center, 17);

            agregarTimeout(() => {
                try {
                    if (W.control?.MapUpdateRequest?.show) {
                        W.control.MapUpdateRequest.show(ur);
                    } else if (W.control?.MapProblem?.show) {
                        W.control.MapProblem.show(ur);
                    } else if (W.control?.UR?.show) {
                        W.control.UR.show(ur);
                    } else if (W.selectionManager) {
                        W.selectionManager.select([ur]);
                    }
                    $(`#fila-ur-${id}`).addClass('ur-visitada');
                } catch (e) {
                    debugLog(`Error al mostrar UR ${id}: ${e}`);
                } finally {
                    estado.accionEnProgreso = false;
                }
            }, 300);
        } else {
            debugLog(`No se pudo obtener geometría para UR ${id}`);
            estado.accionEnProgreso = false;
        }
    }

    function responderUR(id) {
        if (estado.accionEnProgreso) return;
        estado.accionEnProgreso = true;

        limpiarTimeouts();
        const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
        if (!ur) {
            estado.accionEnProgreso = false;
            return;
        }

        centrarYMostrarUR(id);

        agregarTimeout(() => {
            const commentField = $('.new-comment-text');
            if (commentField.length) {
                commentField.val(CONFIG.MENSAJE_RESPUESTA);
                const sendButton = $('.send-button');
                if (sendButton.length) {
                    sendButton.click();
                }
            }
            estado.accionEnProgreso = false;
        }, 1500);
    }

    function resolverUR(id) {
        if (estado.accionEnProgreso) return;
        estado.accionEnProgreso = true;

        limpiarTimeouts();
        const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
        if (!ur) {
            estado.accionEnProgreso = false;
            return;
        }

        centrarYMostrarUR(id);

        agregarTimeout(() => {
            const commentField = $('.new-comment-text');
            if (commentField.length) {
                commentField.val(CONFIG.MENSAJE_RESUELTA);

                const resueltaButton = $('label[for="state-solved"]');
                if (resueltaButton.length) {
                    resueltaButton.click();

                    agregarTimeout(() => {
                        const sendButton = $('.send-button');
                        if (sendButton.length) {
                            sendButton.click();
                        }
                        const noIssueButton = document.querySelector('[data-status="NO_ISSUE"]');
                        if (noIssueButton) noIssueButton.click();

                        const okButton = Array.from(document.querySelectorAll('button')).find(btn =>
                            btn.textContent.trim().includes('OK') || btn.textContent.trim().includes('Aplicar')
                        );
                        if (okButton) okButton.click();

                        agregarTimeout(() => {
                            $(`#fila-ur-${id}`).addClass('ur-cerrada').find('.btn-centrar').prop('disabled', true);
                            estado.accionEnProgreso = false;
                        }, 500);
                    }, 300);
                } else {
                    estado.accionEnProgreso = false;
                }
            } else {
                estado.accionEnProgreso = false;
            }
        }, 1500);
    }

    function cerrarUR(id) {
        if (estado.accionEnProgreso) return;
        estado.accionEnProgreso = true;

        limpiarTimeouts();
        const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
        if (!ur) {
            estado.accionEnProgreso = false;
            return;
        }

        centrarYMostrarUR(id);

        agregarTimeout(() => {
            const commentField = $('.new-comment-text');
            if (commentField.length) {
                commentField.val(CONFIG.MENSAJE_CIERRE);
                const sendButton = $('.send-button');
                if (sendButton.length) {
                    sendButton.click();

                    agregarTimeout(() => {
                        const niButton = $('label[for="state-not-identified"]');
                        if (niButton.length) {
                            niButton.click();

                            agregarTimeout(() => {
                                $(`#fila-ur-${id}`).addClass('ur-cerrada').find('.btn-centrar').prop('disabled', true);
                                estado.accionEnProgreso = false;
                            }, 500);
                        } else {
                            estado.accionEnProgreso = false;
                        }
                    }, 300);
                } else {
                    estado.accionEnProgreso = false;
                }
            } else {
                estado.accionEnProgreso = false;
            }
        }, 1500);
        estado.accionEnProgreso = false;
    }

    function inicializarScript() {
        debugLog('Inicializando script...');
        window.togglePanelURs = togglePanelURs;
        crearBoton();

        estado.intervaloVerificacion = setInterval(() => {
            if ($(`#${CONFIG.BOTON_ID}`).length === 0) {
                debugLog('Botón no encontrado, recreando...');
                crearBoton();
            }
        }, CONFIG.INTERVALO_VERIFICACION);

        debugLog('Script inicializado correctamente');
    }

    function esperarWME() {
        if (typeof W === 'undefined' || !W.loginManager || !W.model || !W.map) {
            debugLog('WME no está completamente cargado, reintentando...');
            setTimeout(esperarWME, 1000);
            return;
        }

        if (!W.model.mapUpdateRequests) {
            debugLog('Módulo mapUpdateRequests no está disponible, reintentando...');
            setTimeout(esperarWME, 1000);
            return;
        }

        setTimeout(inicializarScript, 2000);
    }

    debugLog('Script cargado, esperando WME...');
    esperarWME();
})();