WME - UR Manager

Ultimate UR Management Toolkit

当前为 2025-05-21 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         WME - UR Manager
// @namespace    http://waze.com/
// @version      2025.05.20.05
// @description  Ultimate UR Management Toolkit 
// @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';

// Configuración constante
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!",
    ZOOM_INICIAL: 13,
    ZOOM_DETALLE: 17,
    PANEL_ID: 'urna-panel-fecha-exacta',
    BOTON_ID: 'urna-btn-fecha-exacta',
    INTERVALO_VERIFICACION: 4000,
    RETRASO_ESPERA_UI: 1500,
    UMBRAL_VIEJO: 7,
    UMBRAL_RECIENTE: 3
};

// Estado persistente
let estado = {
    URsIniciales: [],
    panelVisible: false,
    botonUR: null,
    intervaloVerificacion: null,
    urVisitadas: [],
    accionEnProgreso: false
};

// Estilos CSS
GM_addStyle(`
    #${CONFIG.BOTON_ID} {
        position: fixed;
        bottom: 20px;
        left: 20px;
        z-index: 99999;
        padding: 10px 15px;
        background: #3498db;
        color: white;
        font-weight: bold;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        font-family: Arial, sans-serif;
        box-shadow: 0 2px 5px rgba(0,0,0,0.2);
    }

    #${CONFIG.PANEL_ID} {
        position: fixed;
        top: 80px;
        right: 20px;
        width: 500px;
        max-height: 70vh;
        min-height: 200px;
        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 {
        padding: 15px;
        overflow-y: auto;
        max-height: calc(70vh - 60px);
    }

    #${CONFIG.PANEL_ID} table {
        width: 100%;
        border-collapse: collapse;
        margin-top: 10px;
    }

    #${CONFIG.PANEL_ID} th, #${CONFIG.PANEL_ID} td {
        border: 1px solid #ddd;
        padding: 6px;
        text-align: left;
    }

    #${CONFIG.PANEL_ID} th {
        position: sticky;
        top: 0;
        background-color: #f2f2f2;
    }

    .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;
    }

    .btn-global {
        padding: 8px 15px;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        font-weight: bold;
    }

    .btn-responder {
        background: #f0ad4e;
        color: white;
    }

    .btn-resuelta {
        background: #5cb85c;
        color: white;
    }

    .btn-cerrar {
        background: #d9534f;
        color: white;
    }

    .btn-actualizar {
        background: #5bc0de;
        color: white;
    }

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

// Funciones de utilidad
function debugLog(message) {
    console.log('[UR Manager]', message);
}

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;
        }

        if (ur.attributes?.createdOn) {
            const fecha = parsearFecha(ur.attributes.createdOn);
            if (fecha) return fecha;
        }

        if (ur.attributes?.comments?.[0]?.createdOn) {
            const fecha = parsearFecha(ur.attributes.comments[0].createdOn);
            if (fecha) return fecha;
        }

        return null;
    } catch (e) {
        debugLog(`Error obteniendo fecha para UR ${ur.attributes?.id}: ${e}`);
        return null;
    }
}

function obtenerFechaUC(ur) {
    try {
        if (ur.attributes?.updatedOn) {
            const fecha = parsearFecha(ur.attributes.updatedOn);
            if (fecha) return fecha;
        }

        if (ur.attributes?.comments?.length > 0) {
            const ultimoComentario = ur.attributes.comments[ur.attributes.comments.length - 1];
            if (ultimoComentario?.createdOn) {
                return parsearFecha(ultimoComentario.createdOn);
            }
        }

        // Si no hay comentarios, devolver null
        return null;
    } catch (e) {
        debugLog(`Error obteniendo fecha UC para UR ${ur.attributes?.id}: ${e}`);
        return null;
    }
}


function calcularDiferenciaDias(fecha) {
    if (!fecha) return null;
    const hoy = new Date();
    const diffTiempo = hoy.getTime() - fecha.getTime();
    return Math.floor(diffTiempo / (1000 * 60 * 60 * 24));
}

function clasificarUR(fecha) {
    if (!fecha) return { estado: "Sin fecha", clase: "ur-no-fecha" };
    const dias = calcularDiferenciaDias(fecha);
    if (dias === null) return { estado: "Sin fecha", clase: "ur-no-fecha" };
    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 formatearFecha(fecha) {
    if (!fecha) return 'N/A';
    return fecha.toLocaleDateString('es-ES', {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit'
    });
}

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

    const bounds = W.map.getExtent();
    return Object.values(W.model.mapUpdateRequests.objects)
        .filter(ur => {
            if (ur.attributes?.open === false || ur.attributes?.resolved) return false;

            const geom = ur.getOLGeometry?.();
            if (!geom) return false;

            const center = geom.getBounds().getCenterLonLat();
            return bounds.containsLonLat(center);
        });
}

// Funciones de interfaz
function crearBotonPrincipal() {
    if ($(`#${CONFIG.BOTON_ID}`).length) return;

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

function togglePanel() {
    if (estado.panelVisible) {
        cerrarPanel();
    } else {
        abrirPanel();
    }
}

function cerrarPanel() {
    $(`#${CONFIG.PANEL_ID}`).fadeOut(300);
    estado.panelVisible = false;
}

function abrirPanel() {
    if ($(`#${CONFIG.PANEL_ID}`).length) {
        $(`#${CONFIG.PANEL_ID}`).fadeIn(300);
        estado.panelVisible = true;
        return;
    }

    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>
            <button class="btn-global btn-actualizar" id="actualizar-lista">Actualizar Lista</button>
        </div>
    `);

    actualizarContenidoPanel(panelContent);

    panelFooter.on('click', '#actualizar-lista', () => {
        W.map.getOLMap().zoomTo(CONFIG.ZOOM_INICIAL);
        setTimeout(() => actualizarContenidoPanel(panelContent), 1000);
    });

    panelFooter.on('click', '#responder-todas', () => gestionarURs('responder'));
    panelFooter.on('click', '#resolver-todas', () => gestionarURs('resolver'));
    panelFooter.on('click', '#cerrar-todas', () => gestionarURs('cerrar'));

    panel.append(panelContent);
    panel.append(panelFooter);
    panel.appendTo('body').fadeIn(300);
    estado.panelVisible = true;
}

function actualizarContenidoPanel(panelContent) {
    W.map.getOLMap().zoomTo(CONFIG.ZOOM_INICIAL);
     debugLog('[UR Manager] se activo zoom 13');

    setTimeout(() => {
        estado.URsIniciales = obtenerURsVisibles();
        estado.urVisitadas = estado.urVisitadas.filter(id =>
            estado.URsIniciales.some(u => u.attributes?.id == id)
        );

        if (estado.URsIniciales.length === 0) {
            panelContent.html('<p>No se encontraron URs visibles en el área actual</p>');
            return;
        }

        let tablaHTML = `
            <h3>URs Visibles: ${estado.URsIniciales.length}</h3>
            <table>
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Fecha Creación</th>
                        <th>Estado</th>
                        <th>Días desde UC</th>
                        <th>Acción</th>
                    </tr>
                </thead>
                <tbody>`;

        estado.URsIniciales.forEach(ur => {
            const id = ur.attributes?.id;
            const fechaCreacion = obtenerFechaCreacionExacta(ur);
            const fechaUC = obtenerFechaUC(ur);
            const clasificacion = clasificarUR(fechaCreacion);
            const diasDesdeUC = calcularDiferenciaDias(fechaUC);
            const esVisitada = estado.urVisitadas.includes(id) ? 'ur-visitada' : '';

            tablaHTML += `
                <tr id="ur-row-${id}" class="${esVisitada}">
                    <td>${id}</td>
                    <td>${formatearFecha(fechaCreacion)}</td>
                    <td class="${clasificacion.clase}">${clasificacion.estado}</td>
                    <td>${diasDesdeUC !== null ? diasDesdeUC + ' días' : 'Sin comentarios'}</td>
                    <td><button class="btn-centrar" data-id="${id}">Centrar</button></td>
                </tr>`;
        });

        panelContent.html(tablaHTML + '</tbody></table>');

        panelContent.on('click', '.btn-centrar', function() {
            const id = $(this).data('id');
            centrarUR(id);
        });
    }, 1000);
}

function centrarUR(id) {
    if (estado.accionEnProgreso) return;

    const ur = estado.URsIniciales.find(u => u.attributes?.id == id);
    if (!ur) return;

    const geom = ur.getOLGeometry();
    if (!geom) return;

    const center = geom.getBounds().getCenterLonLat();
    W.map.setCenter(center);
    W.map.getOLMap().zoomTo(CONFIG.ZOOM_DETALLE);

    setTimeout(() => {
        if (W.control?.UR?.show) {
            W.control.UR.show(ur);
        } else if (W.control?.MapUpdateRequest?.show) {
            W.control.MapUpdateRequest.show(ur);
        }

        if (!estado.urVisitadas.includes(id)) {
            estado.urVisitadas.push(id);
            $(`#ur-row-${id}`).addClass('ur-visitada');
        }
    }, 500);
}

function gestionarURs(accion) {
    // Verificar si ya hay una acción en progreso o no hay URs
    if (estado.accionEnProgreso) {
        debugLog('[UR Manager] Acción bloqueada - ya hay una operación en progreso');
        return;
    }

    if (!estado.URsIniciales.length) {
        debugLog('[UR Manager] No hay URs para procesar');
        return;
    }

    // Configurar timeout de seguridad para evitar bloqueos permanentes
    const timeoutSeguridad = setTimeout(() => {
        if (estado.accionEnProgreso) {
            debugLog('[UR Manager] Timeout de seguridad - liberando bloqueo');
            estado.accionEnProgreso = false;
           // if ($(`#${CONFIG.PANEL_ID} .panel-content`).length) {
             //   actualizarContenidoPanel($(`#${CONFIG.PANEL_ID} .panel-content`));
            //}
        }
    }, 1000); // 30 segundos

    try {
        estado.accionEnProgreso = true;
        debugLog(`[UR Manager] Iniciando acción: ${accion}`);

        const ur = estado.URsIniciales[0];
        if (!ur || !ur.attributes?.id) {
            throw new Error('UR no válida');
        }

        // Centrar la UR en el mapa
        centrarUR(ur.attributes.id);

        setTimeout(() => {
            try {
                const commentField = $('.new-comment-text');
                if (!commentField.length) {
                    throw new Error('No se encontró el campo de comentarios');
                }

                let mensaje = '';
                let estadoUR = '';

                switch(accion) {
                    case 'responder':
                        mensaje = CONFIG.MENSAJE_RESPUESTA;
                        break;
                    case 'resolver':
                        mensaje = CONFIG.MENSAJE_RESUELTA;
                        estadoUR = 'SOLVED';
                        break;
                    case 'cerrar':
                        mensaje = CONFIG.MENSAJE_CIERRE;
                        estadoUR = 'NOT_IDENTIFIED';
                        break;
                    default:
                        throw new Error(`Acción no reconocida: ${accion}`);
                }

                // Enviar comentario
                commentField.val(mensaje);
                commentField.trigger('input').trigger('change');

                setTimeout(() => {
                    try {
                        const sendButton = $('.send-button:not(:disabled)');
                        if (!sendButton.length) {
                            throw new Error('Botón de enviar no disponible');
                        }
                        sendButton.click();

                        if (estadoUR) {
                            setTimeout(() => {
                                try {
                                    // Cambiar estado de la UR
                                    const statusButton = $(`
                                        [data-status="${estadoUR}"],
                                        [data-testid="${estadoUR.toLowerCase().replace('_', '-')}-button"],
                                        label[for="state-${estadoUR.toLowerCase().replace('_', '-')}"]
                                    `).first();

                                    if (!statusButton.length) {
                                        throw new Error(`Botón de estado ${estadoUR} no encontrado`);
                                    }
                                    statusButton.click();

                                    setTimeout(() => {
                                        try {
                                            // Guardar cambios
                                            guardarURviaFetch(ur);

                                            // Mover a la siguiente UR después de guardar
                                           // estado.URsIniciales.shift();
                                            //estado.urVisitadas.push(ur.attributes.id);

                                            // Si quedan URs, procesar la siguiente
                                            //if (estado.URsIniciales.length > 0) {
                                              //  setTimeout(() => gestionarURs(accion), 1500);
                                            //} else {
                                              //  estado.accionEnProgreso = false;
                                                //clearTimeout(timeoutSeguridad);
                                                //actualizarContenidoPanel($(`#${CONFIG.PANEL_ID} .panel-content`));
                                          //  }
                                        } catch (e) {
                                            manejarError(e);
                                        }
                                    }, 1000);
                                } catch (e) {
                                    manejarError(e);
                                }
                            }, 1000);
                        } else {
                            // Solo comentario, no cambio de estado
                            estado.URsIniciales.shift();
                            estado.urVisitadas.push(ur.attributes.id);

                            if (estado.URsIniciales.length > 0) {
                                setTimeout(() => gestionarURs(accion), 1500);
                            } else {
                                estado.accionEnProgreso = false;
                                clearTimeout(timeoutSeguridad);
                                //actualizarContenidoPanel($(`#${CONFIG.PANEL_ID} .panel-content`));
                            }
                        }
                    } catch (e) {
                       // manejarError(e);
                    }
                }, 500);
            } catch (e) {
               // manejarError(e);
            }
        }, CONFIG.RETRASO_ESPERA_UI);

        function manejarError(error) {
            debugLog(`[UR Manager] Error: ${error.message}`);
            estado.accionEnProgreso = false;
            clearTimeout(timeoutSeguridad);
           // actualizarContenidoPanel($(`#${CONFIG.PANEL_ID} .panel-content`));

            // Mostrar alerta al usuario
            alert(`Error en UR Manager: ${error.message}`);
        }
    } catch (e) {
        manejarError(e);
    }
}

function guardarURviaFetch(ur) {
    try {
        if (W.controller?.save) {
            W.controller.save();
            debugLog('[UR Manager] Cambios guardados con W.controller.save()');

            // Esperar a que se complete el guardado
            setTimeout(() => {
                estado.accionEnProgreso = false;
                debugLog('[UR Manager] Operación completada');

                // Actualizar el panel después de guardar
                //if ($(`#${CONFIG.PANEL_ID} .panel-content`).length) {
                  //  actualizarContenidoPanel($(`#${CONFIG.PANEL_ID} .panel-content`));
                //}
            }, 2000); // Aumentar tiempo de espera para guardado
        } else {
            debugLog('[UR Manager] No se pudo guardar automáticamente: W.controller.save() no disponible');
            estado.accionEnProgreso = false;
        }
    } catch (e) {
        debugLog('[UR Manager] Error al guardar automáticamente: ' + e.message);
        estado.accionEnProgreso = false;
    }
}
// Inicialización
function inicializarScript() {
    crearBotonPrincipal();

    estado.intervaloVerificacion = setInterval(() => {
        if ($(`#${CONFIG.BOTON_ID}`).length === 0) {
            crearBotonPrincipal();
        }
    }, CONFIG.INTERVALO_VERIFICACION);
}

function esperarWME() {
    if (typeof W === 'undefined' || !W.loginManager || !W.model || !W.map) {
        setTimeout(esperarWME, 1000);
        return;
    }

    if (!W.model.mapUpdateRequests) {
        setTimeout(esperarWME, 1000);
        return;
    }

    setTimeout(inicializarScript, 2000);
}

// Iniciar el script
esperarWME();

})();