您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ultimate UR Management Toolkit with UI refinements and restored logic.
// ==UserScript== // @name WME - UR Manager // @namespace http://waze.com/ // @version 2025.07.02.6 // @description Ultimate UR Management Toolkit with UI refinements and restored logic. // @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!", ZOOM_INICIAL: 13, ZOOM_DETALLE: 18, PANEL_ID: 'urna-manager-sidebar-panel', RETRASO_ESPERA_UI: 1500, UMBRAL_VIEJO: 7, UMBRAL_RECIENTE: 3 }; let estado = { URsIniciales: [], urVisitadas: [], accionEnProgreso: false, guardadoAutomatico: true }; GM_addStyle(` #sidebar .nav-tabs a[href="#${CONFIG.PANEL_ID}"] { padding: 15px 4px; font-size: 10px; text-align: center; line-height: 1.2; } #${CONFIG.PANEL_ID} { padding: 0 5px; font-family: Arial, sans-serif; font-size: 10px; } #${CONFIG.PANEL_ID} .panel-header, #${CONFIG.PANEL_ID} .panel-footer { padding: 10px 15px; background: #f8f8f8; } #${CONFIG.PANEL_ID} .panel-header { border-bottom: 1px solid #eee; } #${CONFIG.PANEL_ID} .panel-footer { border-top: 1px solid #eee; } #${CONFIG.PANEL_ID} .panel-header label { font-weight: bold; margin: 0; } #${CONFIG.PANEL_ID} .panel-content { padding: 15px; overflow-y: auto; max-height: calc(100vh - 380px); } /* --- ESTILOS DE TABLA CORREGIDOS --- */ #${CONFIG.PANEL_ID} table { width: 100%; border-collapse: collapse; margin-top: 10px; table-layout: fixed; /* CLAVE: Forza los anchos definidos */ } #${CONFIG.PANEL_ID} th, #${CONFIG.PANEL_ID} td { border: 1px solid #ddd; padding: 5px; text-align: left; overflow: hidden; /* Evita que el texto largo rompa el diseño */ text-overflow: ellipsis; /* Añade "..." si el texto no cabe */ white-space: nowrap; /* Mantiene el texto en una sola línea */ } /* -- Definición de anchos para cada columna -- */ #${CONFIG.PANEL_ID} th:nth-child(1) { width: 25%; } /* Columna Actualizado: */ #${CONFIG.PANEL_ID} th:nth-child(2) { width: 30%; } /* Columna Fecha Creación */ #${CONFIG.PANEL_ID} th:nth-child(3) { width: 20%; } /* Columna Estado */ #${CONFIG.PANEL_ID} th:nth-child(4) { width: 14%; text-align: center; } #${CONFIG.PANEL_ID} th:nth-child(5) { width: 13%; text-align: center; } #${CONFIG.PANEL_ID} th { position: sticky; top: -15px; background-color: #f2f2f2; z-index: 1; } .btn-centrar { padding: 4px 8px; background: #3498db; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 14px; } .panel-footer { display: flex; justify-content: space-around; flex-wrap: wrap; } .btn-global { padding: 8px; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; text-align: center; flex: 1 1 45%; margin: 4px; } .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; } `); 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); } return null; } catch (e) { debugLog(`Error obteniendo fecha UC para UR ${ur.attributes?.id}: ${e}`); return null; } } function obtenerActualizadoPor(ur) { try { const updatedById = String(ur.attributes?.updatedBy || ur.attributes?.metaData?.updatedBy || ur.updatedBy || 'N/A'); if (updatedById === 'N/A') return 'N/A'; if (updatedById === "-1") return "Wazer"; if (updatedById === "0") return "System"; const userIdNum = parseInt(updatedById, 10); if (W.model.users) { const user = W.model.users.getObjectById(userIdNum); if (user && user.attributes && user.attributes.userName) return user.attributes.userName; } return updatedById; } catch (e) { debugLog(`Error en obtenerActualizadoPor para UR ${ur.attributes?.id}: ${e}`); return (ur.attributes?.updatedBy || 'N/A').toString(); } } function calcularDiferenciaDias(fecha) { if (!fecha) return null; const hoy = new Date(); const diffTiempo = (hoy.getTime() + 3600000) - 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); }); } function crearSidebarTab() { if ($(`#${CONFIG.PANEL_ID}`).length) return; const tab = $(`<li><a href="#${CONFIG.PANEL_ID}" data-toggle="tab" title="UR Manager">UR Manager</a></li>`); $('#sidebar .nav-tabs').append(tab); const tabContent = $(`<div class="tab-pane" id="${CONFIG.PANEL_ID}"></div>`); $('#sidebar .tab-content').append(tabContent); renderizarPanel(); } function renderizarPanel() { const panel = $(`#${CONFIG.PANEL_ID}`); panel.empty(); const panelHeader = $(`<div class="panel-header"><label for="auto-save-checkbox"><input type="checkbox" id="auto-save-checkbox" ${estado.guardadoAutomatico ? 'checked' : ''}> Guardado Automático</label></div>`); 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">Resolver</button><button class="btn-global btn-cerrar" id="cerrar-todas">Cerrar UR</button><button class="btn-global btn-actualizar" id="actualizar-lista">Actualizar</button></div>`); panelHeader.on('change', '#auto-save-checkbox', function() { estado.guardadoAutomatico = $(this).is(':checked'); debugLog(`Guardado automático: ${estado.guardadoAutomatico ? 'activado' : 'desactivado'}`); }); actualizarContenidoPanel(panelContent); panelFooter.on('click', '#actualizar-lista', () => { W.map.getOLMap().zoomTo(CONFIG.ZOOM_INICIAL); setTimeout(() => actualizarContenidoPanel($(`#${CONFIG.PANEL_ID} .panel-content`)), 1000); }); panelFooter.on('click', '#responder-todas', () => gestionarURs('responder')); panelFooter.on('click', '#resolver-todas', () => gestionarURs('resolver')); panelFooter.on('click', '#cerrar-todas', () => gestionarURs('cerrar')); panel.append(panelHeader, panelContent, panelFooter); } function actualizarContenidoPanel(panelContent) { 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>Actualizado:</th><th>Fecha Creación</th><th>Estado</th><th>UC</th><th>Acción</th></tr></thead><tbody>`; estado.URsIniciales.forEach(ur => { const id = ur.attributes?.id, fechaCreacion = obtenerFechaCreacionExacta(ur), fechaUC = obtenerFechaUC(ur), clasificacion = clasificarUR(fechaCreacion), diasDesdeUC = calcularDiferenciaDias(fechaUC), esVisitada = estado.urVisitadas.includes(id) ? 'ur-visitada' : '', actualizadoPor = obtenerActualizadoPor(ur); tablaHTML += `<tr id="ur-row-${id}" class="${esVisitada}"><td>${actualizadoPor}</td><td>${formatearFecha(fechaCreacion)}</td><td class="${clasificacion.clase}">${clasificacion.estado}</td><td>${diasDesdeUC !== null ? diasDesdeUC + ' días' : 'S/C'}</td><td><button class="btn-centrar" data-id="${id}">📍</button></td></tr>`; }); panelContent.html(tablaHTML + '</tbody></table>'); panelContent.off('click', '.btn-centrar').on('click', '.btn-centrar', function() { const id = $(this).data('id'); centrarUR(id); }); }, 1500); } function centrarUR(id) { if (estado.accionEnProgreso) return; const ur = estado.URsIniciales.find(u => u.attributes?.id == id); if (!ur) { debugLog(`UR con ID ${id} no encontrada.`); return; } const geom = ur.getOLGeometry(); if (!geom) { debugLog(`Geometría no encontrada para UR ${id}.`); 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); } if (!estado.urVisitadas.includes(id)) { estado.urVisitadas.push(id); $(`#ur-row-${id}`).addClass('ur-visitada'); } }, 500); } function gestionarURs(accion) { if (estado.accionEnProgreso || !estado.URsIniciales.length) return; estado.accionEnProgreso = true; const ur = estado.URsIniciales[0]; centrarUR(ur.attributes.id); setTimeout(() => { const commentField = $('.new-comment-text'); if (!commentField.length) { estado.accionEnProgreso = false; debugLog("Campo de comentario no encontrado."); return; } let mensaje = '', 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; } commentField.val(mensaje).trigger('input').trigger('change'); setTimeout(() => { $('.send-button:not(:disabled)').click(); if (estadoUR) { setTimeout(() => { const statusButton = $(`[data-status="${estadoUR}"], [data-testid="${estadoUR.toLowerCase().replace('_', '-')}-button"], label[for="state-${estadoUR.toLowerCase().replace('_', '-')}"]`).first(); if (statusButton.length) { statusButton.click(); setTimeout(() => { $('.button-primary, .wz-button.primary.save-button').click(); setTimeout(() => { if (estado.guardadoAutomatico) { guardarCambios(); } else { debugLog('[UR Manager] Guardado automático desactivado.'); estado.accionEnProgreso = false; } }, 1000); }, 500); } else { estado.accionEnProgreso = false; debugLog("Botón de estado no encontrado."); } }, 1000); } else { estado.accionEnProgreso = false; } }, 500); }, CONFIG.RETRASO_ESPERA_UI); } function guardarCambios() { try { if (W.controller?.save) { W.controller.save(); debugLog('[UR Manager] Cambios guardados con W.controller.save()'); setTimeout(() => { estado.accionEnProgreso = false; debugLog('[UR Manager] Operación completada'); }, 2000); } 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; } } function inicializarScript() { crearSidebarTab(); } function esperarWME() { if (typeof W === 'undefined' || !W.loginManager || !W.model || !W.map || !W.controller || !$('#sidebar .nav-tabs').length) { setTimeout(esperarWME, 1000); return; } if (!W.model.actionManager || !W.model.mapUpdateRequests) { setTimeout(esperarWME, 1000); return; } setTimeout(inicializarScript, 2000); } esperarWME(); })();