您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ultimate UR Management Toolkit with zoom refresh and panel update
当前为
- // ==UserScript==
- // @name WME - UR Manager
- // @namespace http://waze.com/
- // @version 2025.05.08.01
- // @description Ultimate UR Management Toolkit with zoom refresh and panel update
- // @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,
- RETRASO_ESPERA_UI: 1000,
- MAX_REINTENTOS: 3,
- ZOOM_ACTUALIZACION: 13
- };
- 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; }
- .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;
- }
- .btn-reiniciar {
- background: #d9534f;
- color: white;
- }
- `);
- let estado = {
- URsActuales: [],
- panelVisible: false,
- botonUR: null,
- intervaloVerificacion: null,
- timeouts: [],
- accionEnProgreso: false,
- reintentos: 0,
- urVisitadas: [],
- urCentradas: [],
- bloqueado: 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 resetearEstado() {
- estado.accionEnProgreso = false;
- estado.bloqueado = false;
- estado.reintentos = 0;
- limpiarTimeouts();
- debugLog('Estado del script reiniciado');
- }
- 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 obtenerFechaUC(ur) {
- try {
- if (ur.attributes.createdOn) {
- const fecha = parsearFecha(ur.attributes.createdOn);
- if (fecha) return fecha;
- }
- if (ur.attributes.updatedOn) {
- const fecha = parsearFecha(ur.attributes.updatedOn);
- if (fecha) return fecha;
- }
- if (ur.attributes.comments && ur.attributes.comments.length > 0) {
- const primerComentario = ur.attributes.comments[0];
- if (primerComentario.createdOn) {
- const fecha = parsearFecha(primerComentario.createdOn);
- if (fecha) return fecha;
- }
- }
- return null;
- } catch (e) {
- debugLog(`Error obteniendo fecha UC para UR ${ur.id}: ${e}`);
- return null;
- }
- }
- function calcularDiferenciaDias(fecha) {
- if (!fecha) return null;
- const hoy = new Date();
- const diffTiempo = hoy.getTime() - fecha.getTime();
- const diffDias = Math.floor(diffTiempo / (1000 * 60 * 60 * 24));
- return diffDias;
- }
- function formatearDiferenciaDias(ur) {
- const fechaUC = obtenerFechaUC(ur);
- if (!fechaUC) return "No disponible";
- const dias = calcularDiferenciaDias(fechaUC);
- if (dias === null) return "Error cálculo";
- return `${dias} días`;
- }
- 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 obtenerURsSinAtender() {
- try {
- if (!W.model?.mapUpdateRequests?.objects) return [];
- const bounds = W.map.getExtent();
- return Object.values(W.model.mapUpdateRequests.objects)
- .filter(ur => {
- // Filtrar URs cerradas (open: false o resolved: true)
- if (ur.attributes.open === false || ur.attributes.resolved) {
- return false;
- }
- const geom = ur.getOLGeometry?.();
- if (!geom) return false;
- const center = geom.getBounds().getCenterLonLat();
- if (!bounds.containsLonLat(center)) 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 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>
- <button class="btn-global btn-reiniciar" id="actualizar-lista">Actualizar Lista</button>
- </div>
- `);
- const actualizarContenidoPanel = () => {
- 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>Fecha Creación</th>
- <th>Estado</th>
- <th>UC (Días)</th>
- <th>Acción</th>
- </tr>
- </thead>
- <tbody>`;
- estado.URsActuales.forEach(ur => {
- const id = ur.attributes.id;
- const fecha = obtenerFechaCreacionExacta(ur);
- const clasificacion = clasificarUR(fecha);
- const diferenciaDias = formatearDiferenciaDias(ur);
- 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'
- });
- }
- const esVisitada = estado.urVisitadas.includes(id) ? 'ur-visitada' : '';
- const fueCentrada = estado.urCentradas.includes(id);
- tablaHTML += `
- <tr id="fila-ur-${id}" class="${esVisitada}">
- <td>${id}</td>
- <td>${fechaStr}</td>
- <td class="${clasificacion.clase}">${clasificacion.estado}</td>
- <td>${diferenciaDias}</td>
- <td><button class="btn-centrar" data-id="${id}" ${fueCentrada ? 'data-centered="true"' : ''}>🗺️ Centrar</button></td>
- </tr>`;
- });
- panelContent.html(`
- ${tablaHTML}
- </tbody>
- </table>
- `);
- panelContent.on('click', '.btn-centrar', function() {
- if (estado.accionEnProgreso || estado.bloqueado) {
- debugLog('Acción de centrar bloqueada temporalmente');
- return;
- }
- const id = $(this).data('id');
- const $btn = $(this);
- if ($btn.attr('data-centered') === 'true') {
- const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
- if (ur) {
- 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);
- }
- } catch (e) {
- debugLog(`Error al mostrar UR ${id}: ${e}`);
- }
- }
- } else {
- centrarYMostrarUR(id);
- $btn.attr('data-centered', 'true');
- }
- $(`#fila-ur-${id}`).addClass('ur-visitada');
- if (!estado.urVisitadas.includes(id)) {
- estado.urVisitadas.push(id);
- }
- });
- }
- };
- panelFooter.on('click', '#actualizar-lista', function() {
- if (estado.accionEnProgreso) return;
- W.map.getOLMap().zoomTo(CONFIG.ZOOM_ACTUALIZACION);
- agregarTimeout(() => {
- actualizarContenidoPanel();
- debugLog(`Panel actualizado después de ajustar zoom a ${CONFIG.ZOOM_ACTUALIZACION}`);
- }, 1000);
- });
- panelFooter.on('click', '#responder-todas', function() {
- if (estado.accionEnProgreso || estado.bloqueado) 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 || estado.bloqueado) 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 || estado.bloqueado) return;
- estado.URsActuales.forEach((ur, index) => {
- agregarTimeout(() => cerrarUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES);
- });
- });
- actualizarContenidoPanel();
- panel.append(panelContent);
- panel.append(panelFooter);
- panel.appendTo('body').fadeIn(300);
- }
- function centrarYMostrarUR(id) {
- if (estado.accionEnProgreso || estado.bloqueado) {
- debugLog(`Acción bloqueada - accionEnProgreso: ${estado.accionEnProgreso}, bloqueado: ${estado.bloqueado}`);
- return;
- }
- estado.accionEnProgreso = true;
- limpiarTimeouts();
- const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
- if (!ur) {
- debugLog(`UR ${id} no encontrada - probablemente fue cerrada`);
- estado.urCentradas = estado.urCentradas.filter(urId => urId !== id);
- estado.urVisitadas = estado.urVisitadas.filter(urId => urId !== id);
- $(`#fila-ur-${id}`).remove();
- estado.accionEnProgreso = false;
- return;
- }
- if (!estado.urCentradas.includes(id)) {
- estado.urCentradas.push(id);
- }
- const geom = ur.getOLGeometry?.();
- if (geom) {
- const center = geom.getBounds().getCenterLonLat();
- W.map.setCenter(center, 17);
- agregarTimeout(() => {
- try {
- let shown = false;
- if (W.control?.MapUpdateRequest?.show) {
- W.control.MapUpdateRequest.show(ur);
- shown = true;
- }
- if (!shown && W.control?.MapProblem?.show) {
- W.control.MapProblem.show(ur);
- shown = true;
- }
- if (!shown && W.control?.UR?.show) {
- W.control.UR.show(ur);
- shown = true;
- }
- if (!shown && W.selectionManager?.select) {
- W.selectionManager.select([ur]);
- shown = true;
- }
- if (!shown) {
- throw new Error('No se pudo encontrar método para mostrar la UR');
- }
- $(`#fila-ur-${id}`).addClass('ur-visitada');
- if (!estado.urVisitadas.includes(id)) {
- estado.urVisitadas.push(id);
- }
- } 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 || estado.bloqueado) return;
- estado.accionEnProgreso = true;
- limpiarTimeouts();
- const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
- if (!ur) {
- resetearEstado();
- return;
- }
- centrarYMostrarUR(id);
- agregarTimeout(() => {
- try {
- const commentField = $('.new-comment-text');
- if (!commentField.length) {
- throw new Error('Campo de comentario no encontrado');
- }
- commentField.val(CONFIG.MENSAJE_RESPUESTA);
- commentField.trigger('input').trigger('change');
- agregarTimeout(() => {
- const sendButton = $('.send-button:not(:disabled)');
- if (!sendButton.length) {
- throw new Error('Botón enviar no encontrado o deshabilitado');
- }
- sendButton[0].click();
- resetearEstado();
- }, 500);
- } catch (error) {
- debugLog(`Error en responderUR: ${error.message}`);
- resetearEstado();
- }
- }, CONFIG.RETRASO_ESPERA_UI);
- }
- function resolverUR(id) {
- if (estado.accionEnProgreso || estado.bloqueado) return;
- estado.accionEnProgreso = true;
- estado.bloqueado = true;
- limpiarTimeouts();
- const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
- if (!ur) {
- resetearEstado();
- return;
- }
- agregarTimeout(() => {
- try {
- const commentField = $('.new-comment-text');
- if (!commentField.length) {
- throw new Error('Campo de comentario no encontrado');
- }
- commentField.val(CONFIG.MENSAJE_RESUELTA);
- commentField.trigger('input').trigger('change');
- agregarTimeout(() => {
- const sendButton = $('.send-button:not(:disabled)');
- if (!sendButton.length) {
- throw new Error('Botón enviar no encontrado o deshabilitado');
- }
- sendButton[0].click();
- agregarTimeout(() => {
- const solvedButton = document.querySelector('[data-status="SOLVED"], label[for="state-solved"], [data-testid="solved-button"]');
- if (!solvedButton) {
- throw new Error('Botón "Resuelta" no encontrado');
- }
- solvedButton.click();
- agregarTimeout(() => {
- const confirmButton = document.querySelector('.buttons .button-primary, .dialog-footer .button-primary');
- if (confirmButton) {
- confirmButton.click();
- }
- agregarTimeout(() => {
- $(`#fila-ur-${id}`).remove();
- estado.URsActuales = estado.URsActuales.filter(u => u.attributes.id !== id);
- estado.urCentradas = estado.urCentradas.filter(urId => urId !== id);
- estado.urVisitadas = estado.urVisitadas.filter(urId => urId !== id);
- const contador = $('h3').first();
- if (contador.length) {
- contador.text(`URs Activas: ${estado.URsActuales.length}`);
- }
- resetearEstado();
- debugLog('Estado desbloqueado después de resolver UR');
- }, 500);
- }, 500);
- }, 500);
- }, 500);
- } catch (error) {
- debugLog(`Error en resolverUR: ${error.message}`);
- resetearEstado();
- }
- }, CONFIG.RETRASO_ESPERA_UI);
- }
- function cerrarUR(id) {
- if (estado.accionEnProgreso || estado.bloqueado) return;
- estado.accionEnProgreso = true;
- estado.bloqueado = true;
- limpiarTimeouts();
- const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
- if (!ur) {
- resetearEstado();
- return;
- }
- centrarYMostrarUR(id);
- agregarTimeout(() => {
- try {
- const commentField = $('.new-comment-text');
- if (!commentField.length) {
- throw new Error('Campo de comentario no encontrado');
- }
- commentField.val(CONFIG.MENSAJE_CIERRE);
- commentField.trigger('input').trigger('change');
- agregarTimeout(() => {
- const sendButton = $('.send-button:not(:disabled)');
- if (!sendButton.length) {
- throw new Error('Botón enviar no encontrado o deshabilitado');
- }
- sendButton[0].click();
- agregarTimeout(() => {
- const notIdentifiedButton = document.querySelector('[data-status="NOT_IDENTIFIED"], label[for="state-not-identified"], [data-testid="not-identified-button"]');
- if (!notIdentifiedButton) {
- throw new Error('Botón "No Identificado" no encontrado');
- }
- notIdentifiedButton.click();
- agregarTimeout(() => {
- const confirmButton = document.querySelector('.buttons .button-primary, .dialog-footer .button-primary');
- if (confirmButton) {
- confirmButton.click();
- }
- agregarTimeout(() => {
- $(`#fila-ur-${id}`).remove();
- estado.URsActuales = estado.URsActuales.filter(u => u.attributes.id !== id);
- estado.urCentradas = estado.urCentradas.filter(urId => urId !== id);
- estado.urVisitadas = estado.urVisitadas.filter(urId => urId !== id);
- const contador = $('h3').first();
- if (contador.length) {
- contador.text(`URs Activas: ${estado.URsActuales.length}`);
- }
- resetearEstado();
- debugLog('Estado desbloqueado después de cerrar UR');
- }, 500);
- }, 500);
- }, 500);
- }, 500);
- } catch (error) {
- debugLog(`Error en cerrarUR: ${error.message}`);
- if (estado.reintentos < CONFIG.MAX_REINTENTOS) {
- estado.reintentos++;
- debugLog(`Reintentando (${estado.reintentos}/${CONFIG.MAX_REINTENTOS})...`);
- agregarTimeout(() => cerrarUR(id), 1000);
- } else {
- resetearEstado();
- }
- }
- }, CONFIG.RETRASO_ESPERA_UI);
- }
- 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();
- })();