WME - UR Manager

Ultimate UR Management Toolkit

// ==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();

})();