WME - UR Manager

Ultimate UR Management Toolkit with zoom refresh and panel update

当前为 2025-05-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name WME - UR Manager
  3. // @namespace http://waze.com/
  4. // @version 2025.05.08.01
  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. const actualizarContenidoPanel = () => {
  349. estado.URsActuales = obtenerURsSinAtender();
  350.  
  351. if (estado.URsActuales.length === 0) {
  352. panelContent.html('<div style="padding:15px;text-align:center;"><b>No hay URs sin atender visibles</b></div>');
  353. } else {
  354. let tablaHTML = `
  355. <h3 style="margin-top:0;">URs Activas: ${estado.URsActuales.length}</h3>
  356. <table>
  357. <thead>
  358. <tr>
  359. <th>ID</th>
  360. <th>Fecha Creación</th>
  361. <th>Estado</th>
  362. <th>UC (Días)</th>
  363. <th>Acción</th>
  364. </tr>
  365. </thead>
  366. <tbody>`;
  367.  
  368. estado.URsActuales.forEach(ur => {
  369. const id = ur.attributes.id;
  370. const fecha = obtenerFechaCreacionExacta(ur);
  371. const clasificacion = clasificarUR(fecha);
  372. const diferenciaDias = formatearDiferenciaDias(ur);
  373.  
  374. let fechaStr = 'No disponible';
  375. if (fecha) {
  376. fechaStr = fecha.toLocaleDateString('es-ES', {
  377. year: 'numeric',
  378. month: '2-digit',
  379. day: '2-digit',
  380. hour: '2-digit',
  381. minute: '2-digit'
  382. });
  383. }
  384.  
  385. const esVisitada = estado.urVisitadas.includes(id) ? 'ur-visitada' : '';
  386. const fueCentrada = estado.urCentradas.includes(id);
  387.  
  388. tablaHTML += `
  389. <tr id="fila-ur-${id}" class="${esVisitada}">
  390. <td>${id}</td>
  391. <td>${fechaStr}</td>
  392. <td class="${clasificacion.clase}">${clasificacion.estado}</td>
  393. <td>${diferenciaDias}</td>
  394. <td><button class="btn-centrar" data-id="${id}" ${fueCentrada ? 'data-centered="true"' : ''}>🗺️ Centrar</button></td>
  395. </tr>`;
  396. });
  397.  
  398. panelContent.html(`
  399. ${tablaHTML}
  400. </tbody>
  401. </table>
  402. `);
  403.  
  404. panelContent.on('click', '.btn-centrar', function() {
  405. if (estado.accionEnProgreso || estado.bloqueado) {
  406. debugLog('Acción de centrar bloqueada temporalmente');
  407. return;
  408. }
  409.  
  410. const id = $(this).data('id');
  411. const $btn = $(this);
  412.  
  413. if ($btn.attr('data-centered') === 'true') {
  414. const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
  415. if (ur) {
  416. try {
  417. if (W.control?.MapUpdateRequest?.show) {
  418. W.control.MapUpdateRequest.show(ur);
  419. } else if (W.control?.MapProblem?.show) {
  420. W.control.MapProblem.show(ur);
  421. } else if (W.control?.UR?.show) {
  422. W.control.UR.show(ur);
  423. }
  424. } catch (e) {
  425. debugLog(`Error al mostrar UR ${id}: ${e}`);
  426. }
  427. }
  428. } else {
  429. centrarYMostrarUR(id);
  430. $btn.attr('data-centered', 'true');
  431. }
  432.  
  433. $(`#fila-ur-${id}`).addClass('ur-visitada');
  434. if (!estado.urVisitadas.includes(id)) {
  435. estado.urVisitadas.push(id);
  436. }
  437. });
  438. }
  439. };
  440.  
  441. panelFooter.on('click', '#actualizar-lista', function() {
  442. if (estado.accionEnProgreso) return;
  443.  
  444. W.map.getOLMap().zoomTo(CONFIG.ZOOM_ACTUALIZACION);
  445.  
  446. agregarTimeout(() => {
  447. actualizarContenidoPanel();
  448. debugLog(`Panel actualizado después de ajustar zoom a ${CONFIG.ZOOM_ACTUALIZACION}`);
  449. }, 1000);
  450. });
  451.  
  452. panelFooter.on('click', '#responder-todas', function() {
  453. if (estado.accionEnProgreso || estado.bloqueado) return;
  454. estado.URsActuales.forEach((ur, index) => {
  455. agregarTimeout(() => responderUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES);
  456. });
  457. });
  458.  
  459. panelFooter.on('click', '#resolver-todas', function() {
  460. if (estado.accionEnProgreso || estado.bloqueado) return;
  461. estado.URsActuales.forEach((ur, index) => {
  462. agregarTimeout(() => resolverUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES);
  463. });
  464. });
  465.  
  466. panelFooter.on('click', '#cerrar-todas', function() {
  467. if (estado.accionEnProgreso || estado.bloqueado) return;
  468. estado.URsActuales.forEach((ur, index) => {
  469. agregarTimeout(() => cerrarUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES);
  470. });
  471. });
  472.  
  473. actualizarContenidoPanel();
  474.  
  475. panel.append(panelContent);
  476. panel.append(panelFooter);
  477. panel.appendTo('body').fadeIn(300);
  478. }
  479.  
  480. function centrarYMostrarUR(id) {
  481. if (estado.accionEnProgreso || estado.bloqueado) {
  482. debugLog(`Acción bloqueada - accionEnProgreso: ${estado.accionEnProgreso}, bloqueado: ${estado.bloqueado}`);
  483. return;
  484. }
  485.  
  486. estado.accionEnProgreso = true;
  487. limpiarTimeouts();
  488.  
  489. const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
  490. if (!ur) {
  491. debugLog(`UR ${id} no encontrada - probablemente fue cerrada`);
  492. estado.urCentradas = estado.urCentradas.filter(urId => urId !== id);
  493. estado.urVisitadas = estado.urVisitadas.filter(urId => urId !== id);
  494. $(`#fila-ur-${id}`).remove();
  495. estado.accionEnProgreso = false;
  496. return;
  497. }
  498.  
  499. if (!estado.urCentradas.includes(id)) {
  500. estado.urCentradas.push(id);
  501. }
  502.  
  503. const geom = ur.getOLGeometry?.();
  504. if (geom) {
  505. const center = geom.getBounds().getCenterLonLat();
  506. W.map.setCenter(center, 17);
  507.  
  508. agregarTimeout(() => {
  509. try {
  510. let shown = false;
  511. if (W.control?.MapUpdateRequest?.show) {
  512. W.control.MapUpdateRequest.show(ur);
  513. shown = true;
  514. }
  515. if (!shown && W.control?.MapProblem?.show) {
  516. W.control.MapProblem.show(ur);
  517. shown = true;
  518. }
  519. if (!shown && W.control?.UR?.show) {
  520. W.control.UR.show(ur);
  521. shown = true;
  522. }
  523. if (!shown && W.selectionManager?.select) {
  524. W.selectionManager.select([ur]);
  525. shown = true;
  526. }
  527.  
  528. if (!shown) {
  529. throw new Error('No se pudo encontrar método para mostrar la UR');
  530. }
  531.  
  532. $(`#fila-ur-${id}`).addClass('ur-visitada');
  533. if (!estado.urVisitadas.includes(id)) {
  534. estado.urVisitadas.push(id);
  535. }
  536. } catch (e) {
  537. debugLog(`Error al mostrar UR ${id}: ${e}`);
  538. } finally {
  539. estado.accionEnProgreso = false;
  540. }
  541. }, 300);
  542. } else {
  543. debugLog(`No se pudo obtener geometría para UR ${id}`);
  544. estado.accionEnProgreso = false;
  545. }
  546. }
  547.  
  548. function responderUR(id) {
  549. if (estado.accionEnProgreso || estado.bloqueado) return;
  550. estado.accionEnProgreso = true;
  551.  
  552. limpiarTimeouts();
  553. const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
  554. if (!ur) {
  555. resetearEstado();
  556. return;
  557. }
  558.  
  559. centrarYMostrarUR(id);
  560.  
  561. agregarTimeout(() => {
  562. try {
  563. const commentField = $('.new-comment-text');
  564. if (!commentField.length) {
  565. throw new Error('Campo de comentario no encontrado');
  566. }
  567.  
  568. commentField.val(CONFIG.MENSAJE_RESPUESTA);
  569. commentField.trigger('input').trigger('change');
  570.  
  571. agregarTimeout(() => {
  572. const sendButton = $('.send-button:not(:disabled)');
  573. if (!sendButton.length) {
  574. throw new Error('Botón enviar no encontrado o deshabilitado');
  575. }
  576.  
  577. sendButton[0].click();
  578. resetearEstado();
  579. }, 500);
  580. } catch (error) {
  581. debugLog(`Error en responderUR: ${error.message}`);
  582. resetearEstado();
  583. }
  584. }, CONFIG.RETRASO_ESPERA_UI);
  585. }
  586.  
  587. function resolverUR(id) {
  588. if (estado.accionEnProgreso || estado.bloqueado) return;
  589.  
  590. estado.accionEnProgreso = true;
  591. estado.bloqueado = true;
  592. limpiarTimeouts();
  593.  
  594. const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
  595. if (!ur) {
  596. resetearEstado();
  597. return;
  598. }
  599.  
  600. agregarTimeout(() => {
  601. try {
  602. const commentField = $('.new-comment-text');
  603. if (!commentField.length) {
  604. throw new Error('Campo de comentario no encontrado');
  605. }
  606.  
  607. commentField.val(CONFIG.MENSAJE_RESUELTA);
  608. commentField.trigger('input').trigger('change');
  609.  
  610. agregarTimeout(() => {
  611. const sendButton = $('.send-button:not(:disabled)');
  612. if (!sendButton.length) {
  613. throw new Error('Botón enviar no encontrado o deshabilitado');
  614. }
  615.  
  616. sendButton[0].click();
  617.  
  618. agregarTimeout(() => {
  619. const solvedButton = document.querySelector('[data-status="SOLVED"], label[for="state-solved"], [data-testid="solved-button"]');
  620. if (!solvedButton) {
  621. throw new Error('Botón "Resuelta" no encontrado');
  622. }
  623.  
  624. solvedButton.click();
  625.  
  626. agregarTimeout(() => {
  627. const confirmButton = document.querySelector('.buttons .button-primary, .dialog-footer .button-primary');
  628. if (confirmButton) {
  629. confirmButton.click();
  630. }
  631.  
  632. agregarTimeout(() => {
  633. $(`#fila-ur-${id}`).remove();
  634. estado.URsActuales = estado.URsActuales.filter(u => u.attributes.id !== id);
  635. estado.urCentradas = estado.urCentradas.filter(urId => urId !== id);
  636. estado.urVisitadas = estado.urVisitadas.filter(urId => urId !== id);
  637.  
  638. const contador = $('h3').first();
  639. if (contador.length) {
  640. contador.text(`URs Activas: ${estado.URsActuales.length}`);
  641. }
  642.  
  643. resetearEstado();
  644. debugLog('Estado desbloqueado después de resolver UR');
  645. }, 500);
  646. }, 500);
  647. }, 500);
  648. }, 500);
  649. } catch (error) {
  650. debugLog(`Error en resolverUR: ${error.message}`);
  651. resetearEstado();
  652. }
  653. }, CONFIG.RETRASO_ESPERA_UI);
  654. }
  655.  
  656. function cerrarUR(id) {
  657. if (estado.accionEnProgreso || estado.bloqueado) return;
  658.  
  659. estado.accionEnProgreso = true;
  660. estado.bloqueado = true;
  661. limpiarTimeouts();
  662.  
  663. const ur = W.model.mapUpdateRequests.getObjectById(Number(id));
  664. if (!ur) {
  665. resetearEstado();
  666. return;
  667. }
  668.  
  669. centrarYMostrarUR(id);
  670.  
  671. agregarTimeout(() => {
  672. try {
  673. const commentField = $('.new-comment-text');
  674. if (!commentField.length) {
  675. throw new Error('Campo de comentario no encontrado');
  676. }
  677.  
  678. commentField.val(CONFIG.MENSAJE_CIERRE);
  679. commentField.trigger('input').trigger('change');
  680.  
  681. agregarTimeout(() => {
  682. const sendButton = $('.send-button:not(:disabled)');
  683. if (!sendButton.length) {
  684. throw new Error('Botón enviar no encontrado o deshabilitado');
  685. }
  686.  
  687. sendButton[0].click();
  688.  
  689. agregarTimeout(() => {
  690. const notIdentifiedButton = document.querySelector('[data-status="NOT_IDENTIFIED"], label[for="state-not-identified"], [data-testid="not-identified-button"]');
  691. if (!notIdentifiedButton) {
  692. throw new Error('Botón "No Identificado" no encontrado');
  693. }
  694.  
  695. notIdentifiedButton.click();
  696.  
  697. agregarTimeout(() => {
  698. const confirmButton = document.querySelector('.buttons .button-primary, .dialog-footer .button-primary');
  699. if (confirmButton) {
  700. confirmButton.click();
  701. }
  702.  
  703. agregarTimeout(() => {
  704. $(`#fila-ur-${id}`).remove();
  705. estado.URsActuales = estado.URsActuales.filter(u => u.attributes.id !== id);
  706. estado.urCentradas = estado.urCentradas.filter(urId => urId !== id);
  707. estado.urVisitadas = estado.urVisitadas.filter(urId => urId !== id);
  708.  
  709. const contador = $('h3').first();
  710. if (contador.length) {
  711. contador.text(`URs Activas: ${estado.URsActuales.length}`);
  712. }
  713.  
  714. resetearEstado();
  715. debugLog('Estado desbloqueado después de cerrar UR');
  716. }, 500);
  717. }, 500);
  718. }, 500);
  719. }, 500);
  720. } catch (error) {
  721. debugLog(`Error en cerrarUR: ${error.message}`);
  722.  
  723. if (estado.reintentos < CONFIG.MAX_REINTENTOS) {
  724. estado.reintentos++;
  725. debugLog(`Reintentando (${estado.reintentos}/${CONFIG.MAX_REINTENTOS})...`);
  726. agregarTimeout(() => cerrarUR(id), 1000);
  727. } else {
  728. resetearEstado();
  729. }
  730. }
  731. }, CONFIG.RETRASO_ESPERA_UI);
  732. }
  733.  
  734. function inicializarScript() {
  735. debugLog('Inicializando script...');
  736. window.togglePanelURs = togglePanelURs;
  737. crearBoton();
  738.  
  739. estado.intervaloVerificacion = setInterval(() => {
  740. if ($(`#${CONFIG.BOTON_ID}`).length === 0) {
  741. debugLog('Botón no encontrado, recreando...');
  742. crearBoton();
  743. }
  744. }, CONFIG.INTERVALO_VERIFICACION);
  745.  
  746. debugLog('Script inicializado correctamente');
  747. }
  748.  
  749. function esperarWME() {
  750. if (typeof W === 'undefined' || !W.loginManager || !W.model || !W.map) {
  751. debugLog('WME no está completamente cargado, reintentando...');
  752. setTimeout(esperarWME, 1000);
  753. return;
  754. }
  755.  
  756. if (!W.model.mapUpdateRequests) {
  757. debugLog('Módulo mapUpdateRequests no está disponible, reintentando...');
  758. setTimeout(esperarWME, 1000);
  759. return;
  760. }
  761.  
  762. setTimeout(inicializarScript, 2000);
  763. }
  764.  
  765. debugLog('Script cargado, esperando WME...');
  766. esperarWME();
  767.  
  768. })();