WME - UR Manager

Ultimate UR Management Toolkit with zoom refresh and panel update

当前为 2025-04-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name WME - UR Manager
  3. // @namespace http://waze.com/
  4. // @version 2025.04.28.13
  5. // @description Ultimate UR Management Toolkit with zoom refresh and panel update
  6. // @author Crotalo
  7. // @match https://www.waze.com/*/editor*
  8. // @match https://beta.waze.com/*/editor*
  9. // @grant GM_addStyle
  10. // @require https://code.jquery.com/jquery-3.6.0.min.js
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const CONFIG = {
  17. 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.",
  18. 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.",
  19. 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!!",
  20. DEBUG: true,
  21. BOTON_ID: 'urna-btn-fecha-exacta',
  22. PANEL_ID: 'urna-panel-fecha-exacta',
  23. INTERVALO_VERIFICACION: 5000,
  24. UMBRAL_VIEJO: 7,
  25. UMBRAL_RECIENTE: 3,
  26. RETRASO_ENTRE_ACCIONES: 800,
  27. RETRASO_ESPERA_UI: 1000,
  28. MAX_REINTENTOS: 3,
  29. ZOOM_ACTUALIZACION: 13
  30. };
  31.  
  32. GM_addStyle(`
  33. #${CONFIG.BOTON_ID} {
  34. position: fixed !important;
  35. bottom: 20px !important;
  36. left: 20px !important;
  37. z-index: 99999 !important;
  38. padding: 10px 15px !important;
  39. background: #3498db !important;
  40. color: white !important;
  41. font-weight: bold !important;
  42. border: none !important;
  43. border-radius: 5px !important;
  44. cursor: pointer !important;
  45. font-family: Arial, sans-serif !important;
  46. box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important;
  47. }
  48. #${CONFIG.PANEL_ID} {
  49. position: fixed;
  50. top: 80px;
  51. right: 20px;
  52. width: 500px;
  53. max-height: 70vh;
  54. min-height: 200px;
  55. display: flex;
  56. flex-direction: column;
  57. background: white;
  58. border: 2px solid #999;
  59. z-index: 99998;
  60. font-family: Arial, sans-serif;
  61. font-size: 13px;
  62. box-shadow: 2px 2px 15px rgba(0,0,0,0.3);
  63. border-radius: 5px;
  64. display: none;
  65. }
  66. #${CONFIG.PANEL_ID} .panel-content {
  67. flex: 1;
  68. overflow-y: auto;
  69. padding: 15px;
  70. max-height: calc(70vh - 60px);
  71. }
  72. #${CONFIG.PANEL_ID} table {
  73. width: 100%;
  74. border-collapse: collapse;
  75. margin-top: 10px;
  76. }
  77. #${CONFIG.PANEL_ID} th {
  78. position: sticky;
  79. top: 0;
  80. background-color: #f2f2f2;
  81. z-index: 10;
  82. }
  83. #${CONFIG.PANEL_ID} th, #${CONFIG.PANEL_ID} td {
  84. border: 1px solid #ddd;
  85. padding: 6px;
  86. text-align: left;
  87. }
  88. .ur-old { color: #d9534f; font-weight: bold; }
  89. .ur-recent { color: #5bc0de; }
  90. .ur-new { color: #5cb85c; }
  91. .ur-visitada { background-color: #fdf5d4 !important; }
  92. .ur-no-fecha { color: #777; font-style: italic; }
  93. .btn-centrar {
  94. padding: 4px 8px;
  95. background: #3498db;
  96. color: white;
  97. border: none;
  98. border-radius: 3px;
  99. cursor: pointer;
  100. }
  101. .panel-footer {
  102. padding: 10px 15px;
  103. background: #f8f8f8;
  104. border-top: 1px solid #eee;
  105. display: flex;
  106. justify-content: center;
  107. gap: 10px;
  108. position: sticky;
  109. bottom: 0;
  110. z-index: 20;
  111. height: 60px;
  112. }
  113. .btn-global {
  114. padding: 8px 15px;
  115. border: none;
  116. border-radius: 5px;
  117. cursor: pointer;
  118. font-weight: bold;
  119. white-space: nowrap;
  120. }
  121. .btn-responder {
  122. background: #f0ad4e;
  123. color: white;
  124. }
  125. .btn-cerrar {
  126. background: #5cb85c;
  127. color: white;
  128. }
  129. .btn-resuelta {
  130. background: #5bc0de;
  131. color: white;
  132. }
  133. .btn-reiniciar {
  134. background: #d9534f;
  135. color: white;
  136. }
  137. `);
  138.  
  139. let estado = {
  140. URsActuales: [],
  141. panelVisible: false,
  142. botonUR: null,
  143. intervaloVerificacion: null,
  144. timeouts: [],
  145. accionEnProgreso: false,
  146. reintentos: 0,
  147. urVisitadas: [],
  148. urCentradas: [],
  149. bloqueado: false
  150. };
  151.  
  152. function debugLog(message) {
  153. if (CONFIG.DEBUG) console.log('[UR Script] ' + message);
  154. }
  155.  
  156. function limpiarTimeouts() {
  157. estado.timeouts.forEach(timeout => clearTimeout(timeout));
  158. estado.timeouts = [];
  159. }
  160.  
  161. function agregarTimeout(callback, delay) {
  162. const timeoutId = setTimeout(() => {
  163. callback();
  164. estado.timeouts = estado.timeouts.filter(id => id !== timeoutId);
  165. }, delay);
  166. estado.timeouts.push(timeoutId);
  167. return timeoutId;
  168. }
  169.  
  170. function resetearEstado() {
  171. estado.accionEnProgreso = false;
  172. estado.bloqueado = false;
  173. estado.reintentos = 0;
  174. limpiarTimeouts();
  175. debugLog('Estado del script reiniciado');
  176. }
  177.  
  178. function togglePanelURs() {
  179. if (estado.panelVisible) {
  180. $(`#${CONFIG.PANEL_ID}`).fadeOut(300, function() {
  181. $(this).remove();
  182. });
  183. estado.panelVisible = false;
  184. limpiarTimeouts();
  185. } else {
  186. mostrarPanelURs();
  187. }
  188. }
  189.  
  190. function crearBoton() {
  191. if ($(`#${CONFIG.BOTON_ID}`).length > 0) return;
  192.  
  193. debugLog('Creando botón...');
  194. estado.botonUR = $(`<button id="${CONFIG.BOTON_ID}">📝 UR Manager</button>`)
  195. .appendTo('body')
  196. .on('click', togglePanelURs);
  197.  
  198. debugLog('Botón creado exitosamente');
  199. }
  200.  
  201. function parsearFecha(valor) {
  202. if (!valor) return null;
  203.  
  204. if (typeof valor === 'object' && '_seconds' in valor) {
  205. try {
  206. return new Date(valor._seconds * 1000 + (valor._nanoseconds / 1000000));
  207. } catch (e) {
  208. debugLog(`Error parseando Firebase Timestamp: ${JSON.stringify(valor)}`);
  209. }
  210. }
  211.  
  212. if (typeof valor === 'string' && valor.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) {
  213. try {
  214. return new Date(valor);
  215. } catch (e) {
  216. debugLog(`Error parseando fecha ISO: ${valor}`);
  217. }
  218. }
  219.  
  220. if (/^\d+$/.test(valor)) {
  221. try {
  222. const num = parseInt(valor);
  223. return new Date(num > 1000000000000 ? num : num * 1000);
  224. } catch (e) {
  225. debugLog(`Error parseando timestamp numérico: ${valor}`);
  226. }
  227. }
  228.  
  229. return null;
  230. }
  231.  
  232. function obtenerFechaCreacionExacta(ur) {
  233. try {
  234. if (ur.attributes.driveDate) {
  235. const fecha = parsearFecha(ur.attributes.driveDate);
  236. if (fecha) return fecha;
  237. }
  238. return null;
  239. } catch (e) {
  240. debugLog(`Error obteniendo fecha: ${e}`);
  241. return null;
  242. }
  243. }
  244.  
  245. function obtenerFechaUC(ur) {
  246. try {
  247. if (ur.attributes.createdOn) {
  248. const fecha = parsearFecha(ur.attributes.createdOn);
  249. if (fecha) return fecha;
  250. }
  251.  
  252. if (ur.attributes.updatedOn) {
  253. const fecha = parsearFecha(ur.attributes.updatedOn);
  254. if (fecha) return fecha;
  255. }
  256.  
  257. if (ur.attributes.comments && ur.attributes.comments.length > 0) {
  258. const primerComentario = ur.attributes.comments[0];
  259. if (primerComentario.createdOn) {
  260. const fecha = parsearFecha(primerComentario.createdOn);
  261. if (fecha) return fecha;
  262. }
  263. }
  264.  
  265. return null;
  266. } catch (e) {
  267. debugLog(`Error obteniendo fecha UC para UR ${ur.id}: ${e}`);
  268. return null;
  269. }
  270. }
  271.  
  272. function calcularDiferenciaDias(fecha) {
  273. if (!fecha) return null;
  274.  
  275. const hoy = new Date();
  276. const diffTiempo = hoy.getTime() - fecha.getTime();
  277. const diffDias = Math.floor(diffTiempo / (1000 * 60 * 60 * 24));
  278.  
  279. return diffDias;
  280. }
  281.  
  282. function formatearDiferenciaDias(ur) {
  283. const fechaUC = obtenerFechaUC(ur);
  284. if (!fechaUC) return "No disponible";
  285.  
  286. const dias = calcularDiferenciaDias(fechaUC);
  287. if (dias === null) return "Error cálculo";
  288.  
  289. return `${dias} días`;
  290. }
  291.  
  292. function clasificarUR(fecha) {
  293. if (!fecha) return { estado: "Sin fecha", clase: "ur-no-fecha" };
  294.  
  295. const hoy = new Date();
  296. const diff = hoy - fecha;
  297. const dias = Math.floor(diff / (1000 * 60 * 60 * 24));
  298.  
  299. if (dias > CONFIG.UMBRAL_VIEJO) return { estado: `Antigua (${dias}d)`, clase: "ur-old" };
  300. if (dias > CONFIG.UMBRAL_RECIENTE) return { estado: `Reciente (${dias}d)`, clase: "ur-recent" };
  301. return { estado: `Nueva (${dias}d)`, clase: "ur-new" };
  302. }
  303.  
  304. function obtenerURsSinAtender() {
  305. try {
  306. if (!W.model?.mapUpdateRequests?.objects) return [];
  307.  
  308. const bounds = W.map.getExtent();
  309. return Object.values(W.model.mapUpdateRequests.objects)
  310. .filter(ur => {
  311. // Filtrar URs cerradas (open: false o resolved: true)
  312. if (ur.attributes.open === false || ur.attributes.resolved) {
  313. return false;
  314. }
  315.  
  316. const geom = ur.getOLGeometry?.();
  317. if (!geom) return false;
  318.  
  319. const center = geom.getBounds().getCenterLonLat();
  320. if (!bounds.containsLonLat(center)) return false;
  321.  
  322. const comentarios = ur.attributes.comments || [];
  323. return !comentarios.some(c => c.type === 'user' && c.text?.trim().length > 0);
  324. });
  325. } catch (e) {
  326. debugLog('Error obteniendo URs: ' + e);
  327. return [];
  328. }
  329. }
  330.  
  331. function mostrarPanelURs() {
  332. estado.panelVisible = true;
  333. limpiarTimeouts();
  334.  
  335. $(`#${CONFIG.PANEL_ID}`).remove();
  336.  
  337. const panel = $(`<div id="${CONFIG.PANEL_ID}">`);
  338. const panelContent = $('<div class="panel-content">');
  339. const panelFooter = $(`
  340. <div class="panel-footer">
  341. <button class="btn-global btn-responder" id="responder-todas">Preguntar</button>
  342. <button class="btn-global btn-resuelta" id="resolver-todas">Resuelta</button>
  343. <button class="btn-global btn-cerrar" id="cerrar-todas">No Identificada</button>
  344. <button class="btn-global btn-reiniciar" id="actualizar-lista">Actualizar Lista</button>
  345. </div>
  346. `);
  347.  
  348. // Función para actualizar el contenido del panel
  349. const actualizarContenidoPanel = () => {
  350. estado.URsActuales = obtenerURsSinAtender();
  351.  
  352. if (estado.URsActuales.length === 0) {
  353. panelContent.html('<div style="padding:15px;text-align:center;"><b>No hay URs sin atender visibles</b></div>');
  354. } else {
  355. let tablaHTML = `
  356. <h3 style="margin-top:0;">URs Activas: ${estado.URsActuales.length}</h3>
  357. <table>
  358. <thead>
  359. <tr>
  360. <th>ID</th>
  361. <th>Fecha Creación</th>
  362. <th>Estado</th>
  363. <th>UC (Días)</th>
  364. <th>Acción</th>
  365. </tr>
  366. </thead>
  367. <tbody>`;
  368.  
  369. estado.URsActuales.forEach(ur => {
  370. const id = ur.attributes.id;
  371. const fecha = obtenerFechaCreacionExacta(ur);
  372. const clasificacion = clasificarUR(fecha);
  373. const diferenciaDias = formatearDiferenciaDias(ur);
  374.  
  375. let fechaStr = 'No disponible';
  376. if (fecha) {
  377. fechaStr = fecha.toLocaleDateString('es-ES', {
  378. year: 'numeric',
  379. month: '2-digit',
  380. day: '2-digit',
  381. hour: '2-digit',
  382. minute: '2-digit'
  383. });
  384. }
  385.  
  386. const esVisitada = estado.urVisitadas.includes(id) ? 'ur-visitada' : '';
  387. const fueCentrada = estado.urCentradas.includes(id);
  388.  
  389. tablaHTML += `
  390. <tr id="fila-ur-${id}" class="${esVisitada}">
  391. <td>${id}</td>
  392. <td>${fechaStr}</td>
  393. <td class="${clasificacion.clase}">${clasificacion.estado}</td>
  394. <td>${diferenciaDias}</td>
  395. <td><button class="btn-centrar" data-id="${id}" ${fueCentrada ? 'data-centered="true"' : ''}>🗺️ Centrar</button></td>
  396. </tr>`;
  397. });
  398.  
  399. panelContent.html(`
  400. ${tablaHTML}
  401. </tbody>
  402. </table>
  403. `);
  404.  
  405. panelContent.on('click', '.btn-centrar', function() {
  406. if (estado.accionEnProgreso || estado.bloqueado) {
  407. debugLog('Acción de centrar bloqueada temporalmente');
  408. return;
  409. }
  410.  
  411. const id = $(this).data('id');
  412. const $btn = $(this);
  413.  
  414. if ($btn.attr('data-centered') === 'true') {
  415. const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
  416. if (ur) {
  417. try {
  418. if (W.control?.MapUpdateRequest?.show) {
  419. W.control.MapUpdateRequest.show(ur);
  420. } else if (W.control?.MapProblem?.show) {
  421. W.control.MapProblem.show(ur);
  422. } else if (W.control?.UR?.show) {
  423. W.control.UR.show(ur);
  424. }
  425. } catch (e) {
  426. debugLog(`Error al mostrar UR ${id}: ${e}`);
  427. }
  428. }
  429. } else {
  430. centrarYMostrarUR(id);
  431. $btn.attr('data-centered', 'true');
  432. }
  433.  
  434. $(`#fila-ur-${id}`).addClass('ur-visitada');
  435. if (!estado.urVisitadas.includes(id)) {
  436. estado.urVisitadas.push(id);
  437. }
  438. });
  439. }
  440. };
  441.  
  442. // Configurar el evento de actualización
  443. panelFooter.on('click', '#actualizar-lista', function() {
  444. if (estado.accionEnProgreso) return;
  445.  
  446. // Ajustar el zoom a 13
  447. W.map.getOLMap().zoomTo(CONFIG.ZOOM_ACTUALIZACION);
  448.  
  449.  
  450. // Pequeña espera antes de actualizar para que el mapa se estabilice
  451. agregarTimeout(() => {
  452. actualizarContenidoPanel();
  453. debugLog(`Panel actualizado después de ajustar zoom a ${CONFIG.ZOOM_ACTUALIZACION}`);
  454. }, 1000);
  455. });
  456.  
  457. // Configurar otros eventos
  458. panelFooter.on('click', '#responder-todas', function() {
  459. if (estado.accionEnProgreso || estado.bloqueado) return;
  460. estado.URsActuales.forEach((ur, index) => {
  461. agregarTimeout(() => responderUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES);
  462. });
  463. });
  464.  
  465. panelFooter.on('click', '#resolver-todas', function() {
  466. if (estado.accionEnProgreso || estado.bloqueado) return;
  467. estado.URsActuales.forEach((ur, index) => {
  468. agregarTimeout(() => resolverUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES);
  469. });
  470. });
  471.  
  472. panelFooter.on('click', '#cerrar-todas', function() {
  473. if (estado.accionEnProgreso || estado.bloqueado) return;
  474. estado.URsActuales.forEach((ur, index) => {
  475. agregarTimeout(() => cerrarUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES);
  476. });
  477. });
  478.  
  479. // Cargar contenido inicial
  480. actualizarContenidoPanel();
  481.  
  482. panel.append(panelContent);
  483. panel.append(panelFooter);
  484. panel.appendTo('body').fadeIn(300);
  485. }
  486.  
  487. function centrarYMostrarUR(id) {
  488. if (estado.accionEnProgreso || estado.bloqueado) {
  489. debugLog(`Acción bloqueada - accionEnProgreso: ${estado.accionEnProgreso}, bloqueado: ${estado.bloqueado}`);
  490. return;
  491. }
  492.  
  493. estado.accionEnProgreso = true;
  494. limpiarTimeouts();
  495.  
  496. const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
  497. if (!ur) {
  498. debugLog(`UR ${id} no encontrada`);
  499. estado.accionEnProgreso = false;
  500. return;
  501. }
  502.  
  503. if (!estado.urCentradas.includes(id)) {
  504. estado.urCentradas.push(id);
  505. }
  506.  
  507. const geom = ur.getOLGeometry?.();
  508. if (geom) {
  509. const center = geom.getBounds().getCenterLonLat();
  510. W.map.setCenter(center, 17);
  511.  
  512. agregarTimeout(() => {
  513. try {
  514. if (W.control?.MapUpdateRequest?.show) {
  515. W.control.MapUpdateRequest.show(ur);
  516. } else if (W.control?.MapProblem?.show) {
  517. W.control.MapProblem.show(ur);
  518. } else if (W.control?.UR?.show) {
  519. W.control.UR.show(ur);
  520. } else if (W.selectionManager) {
  521. W.selectionManager.select([ur]);
  522. }
  523.  
  524. $(`#fila-ur-${id}`).addClass('ur-visitada');
  525. if (!estado.urVisitadas.includes(id)) {
  526. estado.urVisitadas.push(id);
  527. }
  528. } catch (e) {
  529. debugLog(`Error al mostrar UR ${id}: ${e}`);
  530. } finally {
  531. estado.accionEnProgreso = false;
  532. }
  533. }, 300);
  534. } else {
  535. debugLog(`No se pudo obtener geometría para UR ${id}`);
  536. estado.accionEnProgreso = false;
  537. }
  538. }
  539.  
  540. function responderUR(id) {
  541. if (estado.accionEnProgreso || estado.bloqueado) return;
  542. estado.accionEnProgreso = true;
  543.  
  544. limpiarTimeouts();
  545. const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
  546. if (!ur) {
  547. resetearEstado();
  548. return;
  549. }
  550.  
  551. centrarYMostrarUR(id);
  552.  
  553. agregarTimeout(() => {
  554. try {
  555. const commentField = $('.new-comment-text');
  556. if (!commentField.length) {
  557. throw new Error('Campo de comentario no encontrado');
  558. }
  559.  
  560. commentField.val(CONFIG.MENSAJE_RESPUESTA);
  561. commentField.trigger('input').trigger('change');
  562.  
  563. agregarTimeout(() => {
  564. const sendButton = $('.send-button:not(:disabled)');
  565. if (!sendButton.length) {
  566. throw new Error('Botón enviar no encontrado o deshabilitado');
  567. }
  568.  
  569. sendButton[0].click();
  570. resetearEstado();
  571. }, 500);
  572. } catch (error) {
  573. debugLog(`Error en responderUR: ${error.message}`);
  574. resetearEstado();
  575. }
  576. }, CONFIG.RETRASO_ESPERA_UI);
  577. }
  578.  
  579. function resolverUR(id) {
  580. if (estado.accionEnProgreso || estado.bloqueado) return;
  581.  
  582. estado.accionEnProgreso = true;
  583. estado.bloqueado = true;
  584. limpiarTimeouts();
  585.  
  586. const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
  587. if (!ur) {
  588. resetearEstado();
  589. return;
  590. }
  591.  
  592. //centrarYMostrarUR(id);
  593.  
  594. agregarTimeout(() => {
  595. try {
  596. const commentField = $('.new-comment-text');
  597. if (!commentField.length) {
  598. throw new Error('Campo de comentario no encontrado');
  599. }
  600.  
  601. commentField.val(CONFIG.MENSAJE_RESUELTA);
  602. commentField.trigger('input').trigger('change');
  603.  
  604. agregarTimeout(() => {
  605. const sendButton = $('.send-button:not(:disabled)');
  606. if (!sendButton.length) {
  607. throw new Error('Botón enviar no encontrado o deshabilitado');
  608. }
  609.  
  610. sendButton[0].click();
  611.  
  612. agregarTimeout(() => {
  613. const solvedButton = document.querySelector('[data-status="SOLVED"], label[for="state-solved"], [data-testid="solved-button"]');
  614. if (!solvedButton) {
  615. throw new Error('Botón "Resuelta" no encontrado');
  616. }
  617.  
  618. solvedButton.click();
  619.  
  620. agregarTimeout(() => {
  621. const confirmButton = document.querySelector('.buttons .button-primary, .dialog-footer .button-primary');
  622. if (confirmButton) {
  623. confirmButton.click();
  624. }
  625.  
  626. agregarTimeout(() => {
  627. $(`#fila-ur-${id}`).remove();
  628. estado.URsActuales = estado.URsActuales.filter(u => u.attributes.id !== id);
  629.  
  630. const contador = $('h3').first();
  631. if (contador.length) {
  632. contador.text(`URs Activas: ${estado.URsActuales.length}`);
  633. }
  634.  
  635. resetearEstado();
  636. debugLog('Estado desbloqueado después de resolver UR');
  637. }, 500);
  638. }, 500);
  639. }, 500);
  640. }, 500);
  641. } catch (error) {
  642. debugLog(`Error en resolverUR: ${error.message}`);
  643. resetearEstado();
  644. }
  645. }, CONFIG.RETRASO_ESPERA_UI);
  646. if (estado.accionEnProgreso) return;
  647. }
  648.  
  649. function cerrarUR(id) {
  650. if (estado.accionEnProgreso || estado.bloqueado) return;
  651.  
  652. estado.accionEnProgreso = true;
  653. estado.bloqueado = true;
  654. limpiarTimeouts();
  655.  
  656. const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
  657. if (!ur) {
  658. resetearEstado();
  659. return;
  660. }
  661.  
  662. centrarYMostrarUR(id);
  663.  
  664. agregarTimeout(() => {
  665. try {
  666. const commentField = $('.new-comment-text');
  667. if (!commentField.length) {
  668. throw new Error('Campo de comentario no encontrado');
  669. }
  670.  
  671. commentField.val(CONFIG.MENSAJE_CIERRE);
  672. commentField.trigger('input').trigger('change');
  673.  
  674. agregarTimeout(() => {
  675. const sendButton = $('.send-button:not(:disabled)');
  676. if (!sendButton.length) {
  677. throw new Error('Botón enviar no encontrado o deshabilitado');
  678. }
  679.  
  680. sendButton[0].click();
  681.  
  682. agregarTimeout(() => {
  683. const notIdentifiedButton = document.querySelector('[data-status="NOT_IDENTIFIED"], label[for="state-not-identified"], [data-testid="not-identified-button"]');
  684. if (!notIdentifiedButton) {
  685. throw new Error('Botón "No Identificado" no encontrado');
  686. }
  687.  
  688. notIdentifiedButton.click();
  689.  
  690. agregarTimeout(() => {
  691. const confirmButton = document.querySelector('.buttons .button-primary, .dialog-footer .button-primary');
  692. if (confirmButton) {
  693. confirmButton.click();
  694. }
  695.  
  696. agregarTimeout(() => {
  697. $(`#fila-ur-${id}`).remove();
  698. estado.URsActuales = estado.URsActuales.filter(u => u.attributes.id !== id);
  699.  
  700. const contador = $('h3').first();
  701. if (contador.length) {
  702. contador.text(`URs Activas: ${estado.URsActuales.length}`);
  703. }
  704.  
  705. resetearEstado();
  706. debugLog('Estado desbloqueado después de cerrar UR');
  707. }, 500);
  708. }, 500);
  709. }, 500);
  710. }, 500);
  711. } catch (error) {
  712. debugLog(`Error en cerrarUR: ${error.message}`);
  713.  
  714. if (estado.reintentos < CONFIG.MAX_REINTENTOS) {
  715. estado.reintentos++;
  716. debugLog(`Reintentando (${estado.reintentos}/${CONFIG.MAX_REINTENTOS})...`);
  717. agregarTimeout(() => cerrarUR(id), 1000);
  718. } else {
  719. resetearEstado();
  720. }
  721. }
  722. }, CONFIG.RETRASO_ESPERA_UI);
  723. }
  724.  
  725. function inicializarScript() {
  726. debugLog('Inicializando script...');
  727. window.togglePanelURs = togglePanelURs;
  728. crearBoton();
  729.  
  730. estado.intervaloVerificacion = setInterval(() => {
  731. if ($(`#${CONFIG.BOTON_ID}`).length === 0) {
  732. debugLog('Botón no encontrado, recreando...');
  733. crearBoton();
  734. }
  735. }, CONFIG.INTERVALO_VERIFICACION);
  736.  
  737. debugLog('Script inicializado correctamente');
  738. }
  739.  
  740. function esperarWME() {
  741. if (typeof W === 'undefined' || !W.loginManager || !W.model || !W.map) {
  742. debugLog('WME no está completamente cargado, reintentando...');
  743. setTimeout(esperarWME, 1000);
  744. return;
  745. }
  746.  
  747. if (!W.model.mapUpdateRequests) {
  748. debugLog('Módulo mapUpdateRequests no está disponible, reintentando...');
  749. setTimeout(esperarWME, 1000);
  750. return;
  751. }
  752.  
  753. setTimeout(inicializarScript, 2000);
  754. }
  755.  
  756. debugLog('Script cargado, esperando WME...');
  757. esperarWME();
  758. })();