您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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(); })();