1window.renderForm = function(data, config) { 2 3 const target = document.querySelector(config.targetSelector); 4 if (!target) return; 5 6 const mensajePerfil = data.mensajePerfil || ""; 7 8 const isLoggedIn = data.usuario && data.usuario.logueado; 9 10 const isSubscriber = data.usuario && 11 data.usuario.datos && 12 data.usuario.datos.suscriptor && 13 data.usuario.datos.suscriptor.suscripcion === true; 14 15 if (data.usuario) { 16 if (data.usuario.nuevo_token) { 17 setCrossDomainCookie('bx_token', data.usuario.nuevo_token); 18 } 19 if (data.usuario.logueado === false) { 20 setCrossDomainCookie('bx_token', ""); 21 } 22 } 23 24 if (data.usuario.logueado == false) { 25 window.location.href = "/acceso"; 26 27 return; 28 } 29 30 function inicialesDeNombre(nombre) { 31 if (!nombre) return ''; 32 return nombre 33 .trim() 34 .split(/\s+/) 35 .slice(0, 2) 36 .map(p => p.charAt(0).toUpperCase()) 37 .join(''); 38 } 39 40 function incrustarCodigo(html, target) { 41 // Resolver el contenedor destino 42 var el = target || document.body; 43 if (typeof target === 'string') { 44 el = document.querySelector(target) || document.body; // si no existe, cae en <body> 45 } 46 47 // Incrustar el HTML 48 var wrapper = document.createElement('div'); 49 wrapper.innerHTML = html; 50 el.appendChild(wrapper); 51 52 // Ejecutar <script> embebidos (misma técnica que ya usas) 53 var scripts = wrapper.querySelectorAll('script'); 54 scripts.forEach(function(oldScript) { 55 var newScript = document.createElement('script'); 56 57 // Copiar atributos (src, type, etc.) 58 for (var i = 0; i < oldScript.attributes.length; i++) { 59 var attr = oldScript.attributes[i]; 60 newScript.setAttribute(attr.name, attr.value); 61 } 62 63 // Copiar contenido inline 64 newScript.text = oldScript.text || oldScript.textContent || ''; 65 66 // Reemplazar para disparar la ejecución 67 oldScript.parentNode.replaceChild(newScript, oldScript); 68 }); 69 } 70 71 incrustarCodigo(mensajePerfil, target); 72 73 function ponerDatos(data) { 74 75 usu = data.usuario.datos; 76 77 const ctx = { 78 nombre: usu.nombre || '', 79 correo: usu.correo || '', 80 iniciales: inicialesDeNombre(usu.nombre || '') 81 }; 82 83 target.querySelectorAll('[data-n-texto]').forEach(function(el) { 84 const key = el.getAttribute('data-n-texto'); 85 if (key && (key in ctx)) el.textContent = ctx[key] || ''; 86 }); 87 88 // Values 89 target.querySelectorAll('[data-n-valor]').forEach(function(el) { 90 const key = el.getAttribute('data-n-valor'); 91 if (key && (key in ctx)) el.value = ctx[key] || ''; 92 }); 93 94 } 95 96 ponerDatos(data); 97 98 function ponerRedes(redes){ 99 // mapa por nombre de red 100 var map = {}; 101 (redes || []).forEach(function(r){ map[r.red] = r; }); 102 103 document.querySelectorAll('[data-n-social]').forEach(function(tile){ 104 var red = tile.getAttribute('data-n-social'); // p.ej. "google" 105 var info = map[red] || null; 106 107 var status = tile.querySelector('[data-n-status]'); 108 var badge = tile.querySelector('[data-n-connected-badge]'); 109 var btnCon = tile.querySelector('[data-n-connect]'); 110 var btnDis = tile.querySelector('[data-n-disconnect]'); 111 112 if (info) { 113 // conectado 114 if (status) status.textContent = info.correo ? ('Conectado como ' + info.correo) : 'Conectado'; 115 if (badge) badge.hidden = false; 116 if (btnCon) btnCon.hidden = true; 117 if (btnDis) btnDis.hidden = false; 118 tile.setAttribute('data-n-state', 'connected'); 119 } else { 120 // no conectado 121 if (status) status.textContent = 'No conectado'; 122 if (badge) badge.hidden = true; 123 if (btnCon) btnCon.hidden = false; 124 if (btnDis) btnDis.hidden = true; 125 tile.setAttribute('data-n-state', 'disconnected'); 126 } 127 }); 128 } 129
130 ponerRedes(data.usuario.redes); 131 132 function formatearFechaEs(isoStr) { 133 if (!isoStr) return ''; 134 const [y, m, d] = isoStr.split('-').map(Number); 135 const meses = ['ene','feb','mar','abr','may','jun','jul','ago','sep','oct','nov','dic']; 136 const dt = new Date(y, (m - 1), d); 137 if (isNaN(dt)) return ''; 138 const dd = String(dt.getDate()).padStart(2, '0'); 139 const mmm = meses[dt.getMonth()]; 140 const yyyy = dt.getFullYear(); 141 142 return `${dd}/${mmm}/${yyyy}`; 143 } 144 145 function renderTipo(tipo) { 146 const t = String(tipo || '').toLowerCase(); 147 if (t === 'paypal') { 148 // Logo PayPal (puedes cambiar la URL luego por tu asset local) 149 const src = 'https://meteoro.noroeste.com.mx/public/imgs/paypal-svgrepo-com.svg'; 150 return `<img src="${src}" alt="PayPal" style="height:58px; vertical-align:middle;">`; 151 } 152 if (t === 'openpay') { 153 const src = 'https://meteoro.noroeste.com.mx/public/imgs/credit-card.png'; 154 return `<img src="${src}" alt="PayPal" style="height:42px; vertical-align:middle;">`; 155 } 156 return tipo || ''; 157 } 158 159 function renderEstadoBadge(status) { 160 const s = String(status || '').toLowerCase(); 161 let cls = 'text-bg-secondary'; 162 if (s === 'activa') cls = 'text-bg-success'; 163 else if (s === 'vencida') cls = 'text-bg-warning'; 164 else if (s === 'cancelada') cls = 'text-bg-danger'; 165 const label = s ? s.charAt(0).toUpperCase() + s.slice(1) : ''; 166 return `<span class="badge ${cls} badge-rounded">${label}</span>`; 167 } 168 169 function renderRenovacion(renovar) { 170 return (String(renovar).toUpperCase() === 'S') ? 'Automática' : 'No'; 171 } 172 173 function renderAcciones(item) { 174 const t = String(item.tipo || '').toLowerCase(); 175 const statusActiva = String(item.status || '').toLowerCase() === 'activa'; 176 const renovarS = String(item.renovar || '').toUpperCase() === 'S'; 177 178 const btns = []; 179 180 // Siempre incluimos data-id para que luego enlaces tus callbacks 181 const dataId = `data-n-id="${item.id}"`; 182 183 if (t === 'paypal' || t === 'openpay') { 184 // btns.push(`<a href="#" ${dataId} data-n-action="details" class="btn btn-outline-primary btn-sm text-nowrap">Ver detalles</a><br>`); 185 186 if (statusActiva && renovarS) { 187 btns.push(`<a href="#" ${dataId} data-n-action="cancel_renewal" class="btn btn-outline-danger btn-sm text-nowrap">Cancelar renovación</a>`); 188 } else if (statusActiva && !renovarS) { 189 btns.push(`<a href="#" ${dataId} data-n-action="cancel" class="btn btn-outline-danger btn-sm">Cancelar</a>`); 190 } 191 } else { 192 if (statusActiva) { 193 btns.push(`<a href="#" ${dataId} data-n-action="cancel" data-n-tipo="${t}" class="btn btn-outline-danger btn-sm">Cancelar</a>`); 194 } 195 } 196 197 // Si no hay botones, devolvemos un guion para no dejar la celda vacÃa 198 return btns.length ? btns.join(' ') : ' '; 199 } 200 201 function formatearFechaEsHM(dtStr) { 202 // dtStr: 'YYYY-MM-DD HH:mm:ss' 203 if (!dtStr) return ''; 204 const [dPart, tPart='00:00:00'] = dtStr.split(' '); 205 const [y, m, d] = dPart.split('-').map(Number); 206 const [hh, mm, ss] = tPart.split(':').map(Number); 207 const meses = ['ene','feb','mar','abr','may','jun','jul','ago','sep','oct','nov','dic']; 208 const dd = String(d).padStart(2, '0'); 209 const mmm = meses[(m-1) || 0]; 210 const H = String(hh||0).padStart(2,'0'); 211 const M = String(mm||0).padStart(2,'0'); 212 return `${dd}/${mmm}/${y} ${H}:${M}`; 213 } 214 215 function epochUTC(dtStr){ 216 // para ordenar: convierte 'YYYY-MM-DD HH:mm:ss' a epoch ms (UTC) 217 if (!dtStr) return 0; 218 const [dPart, tPart='00:00:00'] = dtStr.split(' '); 219 const [y, m, d] = dPart.split('-').map(Number); 220 const [hh, mm, ss] = tPart.split(':').map(Number); 221 return Date.UTC(y, (m-1), d, hh||0, mm||0, ss||0); 222 } 223 224 function formatMXN(v){
225 const n = Number(v); 226 if (isNaN(n)) return v ?? ''; 227 const num = new Intl.NumberFormat('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(n); 228 return `$${num} MXN`; 229 } 230 231 function renderEstadoTx(status){ 232 const s = String(status||'').toLowerCase(); 233 const map = { 234 exitoso: ['Pagado', 'text-bg-success'], 235 pendiente: ['Pendiente', 'text-bg-warning'], 236 fallido: ['Fallido', 'text-bg-secondary'], // puedes cambiar a 'text-bg-danger' si prefieres rojo 237 reembolsado: ['Reembolsado', 'text-bg-info'] 238 }; 239 const [txt, cls] = map[s] || [s, 'text-bg-secondary']; 240 return `<span class="badge ${cls} badge-rounded">${txt}</span>`; 241 } 242 243 document.addEventListener('change', function (ev) { 244 if (!ev.target.matches('#tab-newsletters input[type="checkbox"][data-n-news]')) return; 245 246 const ck = ev.target; 247 const id = ck.dataset.nNews; 248 const activo = ck.checked; 249 250 token = getCookie("bx_token"); 251 252 formUtils.waitModal.show(); 253 254 fetch('https://meteoro.noroeste.com.mx/public/api/user/news', { 255 method: 'POST', 256 headers: { 257 'Content-Type': 'application/json' 258 }, 259 body: JSON.stringify({ id: id, bx_siteId: config.siteId, token: token, activo: activo}) 260 }) 261 .then(r => r.json()) 262 .then(function(data) { 263 if (data.ok == true) { 264 265 bxToast('Dato guardado correctamente', { type:'success', duration: 3000 }); 266 267 } else { 268 269 bxToast('Hubo un error al guardar el dato. Favor de intentar nuevamente mas tarde', { type:'danger', duration: 3000 }); 270 271 if (data.error) { 272 dlog("Error cURL:", data.error); 273 } 274 if (data.raw) { 275 dlog("Respuesta sin JSON válido:", data.raw); 276 } 277 278 } 279 }) 280 .catch(err => console.error('error:', err)) 281 .finally(function(){ 282 283 formUtils.waitModal.hide(); 284 285 }); 286 }); 287 288 var susBtn = document.querySelector('button[data-n-action="suscribirme"]'); 289 290 if (susBtn) { 291 susBtn.addEventListener('click', function (e) { 292 e.preventDefault(); 293 e.stopPropagation(); 294 295 window.location.href = "/suscripciones"; 296 }); 297 } 298 299 document.addEventListener("click", async function (ev) { 300 const b = ev.target.closest('button[data-n-metodo]'); 301 if (b) { 302 ev.preventDefault(); 303 304 cambiar_metodoPago(b); 305 306 return; 307 } 308 309 const c = ev.target.closest('button[data-n-metodo-eliminar]'); 310 if (c) { 311 ev.preventDefault(); 312 313 eliminar_metodoPago(c); 314 315 return; 316 } 317 318 const a = ev.target.closest('a[data-n-action]'); 319 if (!a) return; 320 321 ev.preventDefault(); 322 323 const action = a.getAttribute("data-n-action"); // details | cancel_renewal | cancel 324 const id = a.getAttribute("data-n-id") || ""; 325 const tipo = a.getAttribute("data-n-tipo") || ""; 326 327 if (action == "cancel") { 328 329 tit = "Cancelar Suscripción"; 330 331 } else if (action == "cancel_renewal") { 332 333 tit = "Cancelar Renovación de Suscripción"; 334 335 } else if (action == "details") { 336 337 // mostrar detalles 338 efectuarAccionSusc(action, id); 339 340 return; 341 342 } else if (action == "delete-account") { 343 344 eliminarCuenta(); 345 346 return; 347 348 } else { 349 350 return; 351 352 } 353 354 msg = "¿Seguro que deseas efectuar esta acción?"; 355 356 if (action == "cancel" && tipo == "ios") { 357 msg = msg + "<br><br>Esta suscripción es de tipo iOS. La renovación automática se tiene que cancelar desde su cuenta de Apple"; 358 } 359 360 formUtils.confirmModal.show({ 361 title: tit, 362 message: msg, 363 onConfirm: function() { 364 365 efectuarAccionSusc(action, id); 366 367 }, 368 onCancel: function() { 369 370 // console.log("El usuario canceló la acción"); 371 372 } 373 }); 374 375 });
376 377 function eliminarCuenta() { 378 379 existeActiva = Array.from( 380 document.querySelectorAll('#tabla-suscripciones .badge') 381 ).some(badge => badge.textContent.trim().toLowerCase().includes('activa')); 382 383 existeIOSActiva = Array.from( 384 document.querySelectorAll('#tabla-suscripciones [data-n-tipo="ios"]') 385 ).some(btn => { 386 const estado = btn.closest('tr').querySelector('td:nth-child(5) .badge'); 387 return estado && estado.textContent.trim().toLowerCase().includes("activa"); 388 }); 389 390 tit = "Eliminar cuenta"; 391 msg = "¿Seguro que deseas efectuar esta acción? Esta operación no se puede deshacer."; 392 393 if (existeActiva) { 394 msg = msg + "<br><br><div style='font-size: 90%; color: #F00; background-color: #FF0; padding: 4px;'>Tiene una suscripción activa y será cancelada</div><br>"; 395 } 396 397 if (existeIOSActiva) { 398 msg = msg + "<br><div style='font-size: 90%'>La renovación automática se tiene que cancelar desde su cuenta de Apple</div>"; 399 } 400 401 formUtils.confirmModal.show({ 402 title: tit, 403 message: msg, 404 onConfirm: function() { 405 406 token = getCookie("bx_token"); 407 408 formUtils.waitModal.show(); 409 410 fetch('https://meteoro.noroeste.com.mx/public/api/user/delete', { 411 method: 'POST', 412 headers: { 413 'Content-Type': 'application/json' 414 }, 415 body: JSON.stringify({ bx_siteId: config.siteId, token: token}) 416 }) 417 .then(r => r.json()) 418 .then(function(data) { 419 if (data.res == "ok") { 420 421 setCrossDomainCookie("bx_token", ""); 422 423 window.location.href = "https://cuenta.noroeste.com.mx/acceso"; 424 425 } else { 426 427 formUtils.alertModal.show({ 428 title: "Error", 429 message: "Hubo un error al intentar guardar el dato. " + data.error, 430 type: "error" 431 }); 432 433 } 434 }) 435 .catch(err => console.error('error:', err)) 436 .finally(function(){ 437 438 formUtils.waitModal.hide(); 439 440 }); 441 442 } 443 }); 444 445 } 446 447 function efectuarAccionSusc(action, id) { 448 449 token = getCookie("bx_token"); 450 451 formUtils.waitModal.show(); 452 453 fetch('https://meteoro.noroeste.com.mx/public/api/subscriptions/' + action, { 454 method: 'POST', 455 headers: { 456 'Content-Type': 'application/json' 457 }, 458 body: JSON.stringify({ action: action, id: id, bx_siteId: config.siteId, token: token}) 459 }) 460 .then(r => r.json()) 461 .then(function(data) { 462 if (data.ok == true) { 463 464 if (action == "cancel" || action == "cancel_renewal") { 465 466 ponerSuscripciones(data.suscripciones); 467 468 } else if (action == "details") { 469 470 471 472 } 473 474 } else { 475 476 formUtils.alertModal.show({ 477 title: "Error", 478 message: "Hubo un error al intentar generar la acción", 479 type: "error" 480 }); 481 482 } 483 }) 484 .catch(err => console.error('error:', err)) 485 .finally(function(){ 486 487 formUtils.waitModal.hide(); 488 489 }); 490 491 } 492 493 function ponerSuscripciones(suscripciones) { 494 const data = Array.isArray(suscripciones) ? suscripciones : []; 495 496 // ¿Paginamos? Solo si 8 o más 497 const enablePaging = data.length >= 8; 498 499 // Si ya existe una DataTable previa, destrúyela 500 if ($.fn.dataTable && $.fn.dataTable.isDataTable('#tabla-suscripciones')) { 501 $('#tabla-suscripciones').DataTable().clear().destroy(); 502 $('#tabla-suscripciones tbody').empty(); 503 } 504 505 // Mapear data a filas (incluyendo columna oculta para ordenar por id) 506 const rows = data.map(it => ([ 507 it.nombre_publico || '', // Plan 508 formatearFechaEs(it.fec_ini), // Inicio 509 formatearFechaEs(it.fec_fin), // Vence 510 renderTipo(it.tipo), // Tipo 511 renderEstadoBadge(it.status), // Estado 512 renderRenovacion(it.renovar), // Renovación 513 renderAcciones(it), // Acciones 514 Number(it.id) || 0 // _id_hidden (oculta, para ordenar desc) 515 ])); 516 517 $('#tabla-suscripciones').DataTable({ 518 data: rows, 519 searching: false, // sin buscador 520 lengthChange: false, // sin selector "cuántos por página" 521 info: false, // ocultar "Mostrando X de Y" 522 paging: enablePaging, // solo si hay 8 o más 523 pageLength: 8, // tamaño de página si hay paginación 524 ordering: true, 525 order: [[7, 'desc']], // ordenar por _id_hidden (id) desc 526 language: { 527 emptyTable: "No hay suscripciones para mostrar. <a href='https://cuenta.noroeste.com.mx/suscripciones'>Ir pantalla de opciones</a>", 528 paginate: { previous: "Anterior", next: "Siguiente" } 529 }, 530 autoWidth: false, 531 columnDefs: [ 532 { targets: 0, className:'text-nowrap' }, 533 { targets: 1, width: '90px', className:'text-nowrap' }, // Inicio 534 { targets: 2, width: '90px', className:'text-nowrap' }, // Vence 535 { targets: 3, createdCell: function(td){ td.style.minWidth = '70px'; } }, 536 { targets: 4, createdCell: function(td){ td.style.minWidth = '70px'; } }, 537 { targets: 7, visible: false, searchable: false } 538 ], 539 }); 540 } 541 542 function ponerTransacciones(transacciones) { 543 544 const data = Array.isArray(transacciones) ? transacciones : []; 545 const enablePaging = data.length >= 8; 546 547 if ($.fn.dataTable && $.fn.dataTable.isDataTable('#tabla-transacciones')) { 548 $('#tabla-transacciones').DataTable().clear().destroy(); 549 $('#tabla-transacciones tbody').empty(); 550 } 551 552 const rows = data.map(it => ([ 553 formatearFechaEsHM(it.fecha_movimiento), // Fecha (visible) 554 it.nombre_publico || '', // Concepto 555 formatMXN(it.monto), // Monto 556 renderEstadoTx(it.status), // Estado 557 it.orden_id || '', // Referencia 558 epochUTC(it.fecha_movimiento) // _fecha_hidden (para ordenar) 559 ])); 560 561 $('#tabla-transacciones').DataTable({ 562 data: rows, 563 autoWidth: false, 564 searching: false, 565 lengthChange: false, 566 info: false, 567 paging: enablePaging, 568 pageLength: 8, 569 order: [[5, 'desc']], // ordenar por fecha oculta (más reciente primero) 570 columnDefs: [ 571 { targets: 0, className:'text-nowrap' }, // Fecha 572 { targets: 2, className:'text-end text-nowrap' }, // Monto 573 { targets: 3, orderable:false }, // Estado 574 { targets: 5, visible:false, searchable:false } // _fecha_hidden 575 ], 576 language: { 577 emptyTable: "No hay transacciones para mostrar", 578 paginate: { previous: "Anterior", next: "Siguiente" } 579 } 580 }); 581 582 } 583 584 function ponerMetodosPago_(metodos) { 585 var cont = document.getElementById('div-metodos'); 586 if (!cont) return; 587 cont.innerHTML = 'Aún no tienes métodos de pago guardados. <br>Puedes agregarlos ahora para usarlos en compras o renovaciones automáticas.'; 588 589 var PP_ICON = 'https://meteoro.noroeste.com.mx/public/imgs/paypal-svgrepo-com.svg'; 590 var CC_ICON = 'https://meteoro.noroeste.com.mx/public/imgs/credit-card.png'; 591 592 function two(v){ v=String(v||''); return v.length>2 ? v.slice(-2) : v.padStart(2,'0'); } 593 function brand(v){ return String(v||'').toUpperCase(); } 594 function vigencia(m,y){ 595 m = parseInt(m,10)||0; y = String(y||''); 596 if (!m || !y || y==='0') return ''; 597 return two(m) + '/' + two(y); 598 } 599 600 if (metodos.length > 0) { 601 cont.innerHTML = ''; 602 } 603 604 metodos.forEach(function(m, idx){ 605 var pas = String(m.pasarela||'').toLowerCase(); 606 var isPaypal = pas === 'paypal'; 607 var isPred = String(m.es_predeterminado||'') === '1'; 608 609 // contenedor de fila 610 var row = document.createElement('div'); 611 row.className = 'd-flex align-items-center justify-content-between gap-3 border rounded-3 p-3 mb-2'; 612 row.setAttribute('data-method-index', idx); 613 row.setAttribute('data-pasarela', pas); 614 615 // izquierda: icono + textos 616 var left = document.createElement('div'); 617 left.className = 'd-flex align-items-center gap-3'; 618 619 var iconWrap = document.createElement('div'); 620 iconWrap.className = 'd-inline-flex align-items-center justify-content-center bg-light rounded-2'; 621 iconWrap.style.width = '40px'; 622 iconWrap.style.height = '40px'; 623 624 var img = document.createElement('img'); 625 img.src = isPaypal ? PP_ICON : CC_ICON; 626 img.alt = isPaypal ? 'PayPal' : 'Tarjeta'; 627 img.style.width = '24px'; 628 img.style.height = '24px'; 629 img.style.objectFit = 'contain'; 630 iconWrap.appendChild(img); 631 632 var textBox = document.createElement('div'); 633 634 var title = document.createElement('div'); 635 title.className = 'fw-semibold'; 636 637 if (isPaypal) { 638 title.textContent = 'PayPal'; 639 } else { 640 // Tarjeta: "Tarjeta terminación 5100" 641 var last4 = String(m.ultimos_4_digitos || '').replace(/\s+/g,''); 642 title.textContent = 'Tarjeta terminación ' + (last4 || 'â'); 643 } 644 645 var small = document.createElement('div'); 646 small.className = 'text-muted small'; 647 648 if (isPaypal) { 649 small.textContent = m.payer_email ? m.payer_email : 'â'; 650 } else { 651 var vig = vigencia(m.mes_expiracion, m.ano_expiracion); 652 var mk = brand(m.marca_tarjeta); 653 small.textContent = (vig ? ('Vigencia ' + vig) : '') + (vig && mk ? ' â ' : '') + (mk || ''); 654 } 655 656 textBox.appendChild(title); 657 textBox.appendChild(small); 658 659 left.appendChild(iconWrap); 660 left.appendChild(textBox); 661 662 // derecha: badge o botón 663 var right = document.createElement('div'); 664 right.className = 'd-flex align-items-center gap-2'; 665 666 if (isPred) { 667 var badge = document.createElement('span'); 668 badge.className = 'badge rounded-pill text-bg-primary'; 669 badge.textContent = 'Predeterminado'; 670 right.appendChild(badge); 671 } else { 672 var btn = document.createElement('button'); 673 btn.type = 'button'; 674 btn.className = 'btn btn-outline-primary btn-sm'; 675 btn.textContent = 'Hacer predeterminado'; 676 // data-* útiles para que luego enlaces tu lógica 677 btn.setAttribute('data-n-metodo', m.id); 678 btn.setAttribute('data-method-index', idx); 679 btn.setAttribute('data-pasarela', pas); 680 if (isPaypal) btn.setAttribute('data-email', m.payer_email || ''); 681 if (!isPaypal) btn.setAttribute('data-last4', m.ultimos_4_digitos || ''); 682 right.appendChild(btn); 683 } 684 685 row.appendChild(left); 686 row.appendChild(right); 687 cont.appendChild(row);
688 }); 689 } 690 691 function ponerMetodosPago(metodos) { 692 var cont = document.getElementById('div-metodos'); 693 if (!cont) return; 694 695 cont.innerHTML = 'Aún no tienes métodos de pago guardados. <br>Puedes agregarlos ahora para usarlos en compras o renovaciones automáticas.'; 696 697 var PP_ICON = 'https://meteoro.noroeste.com.mx/public/imgs/paypal-svgrepo-com.svg'; 698 var CC_ICON = 'https://meteoro.noroeste.com.mx/public/imgs/credit-card.png'; 699 700 function two(v){ v=String(v||''); return v.length>2 ? v.slice(-2) : v.padStart(2,'0'); } 701 function brand(v){ return String(v||'').toUpperCase(); } 702 function vigencia(m,y){ 703 m = parseInt(m,10)||0; y = String(y||''); 704 if (!m || !y || y==='0') return ''; 705 return two(m) + '/' + two(y); 706 } 707 708 if (metodos.length > 0) cont.innerHTML = ''; 709 710 metodos.forEach(function(m, idx){ 711 var pas = String(m.pasarela||'').toLowerCase(); 712 var isPaypal = pas === 'paypal'; 713 var isPred = String(m.es_predeterminado||'') === '1'; 714 715 // contenedor (mobile: columna, sm+: fila) 716 var row = document.createElement('div'); 717 row.className = 'd-flex flex-column flex-sm-row align-items-start align-items-sm-center gap-3 border rounded-3 p-3 mb-2'; 718 row.setAttribute('data-method-index', idx); 719 row.setAttribute('data-pasarela', pas); 720 721 // izquierda: icono + textos 722 var left = document.createElement('div'); 723 left.className = 'd-flex align-items-center gap-3'; 724 725 var iconWrap = document.createElement('div'); 726 iconWrap.className = 'd-inline-flex align-items-center justify-content-center bg-light rounded-2'; 727 iconWrap.style.width = '40px'; 728 iconWrap.style.height = '40px'; 729 730 var img = document.createElement('img'); 731 img.src = isPaypal ? PP_ICON : CC_ICON; 732 img.alt = isPaypal ? 'PayPal' : 'Tarjeta'; 733 img.style.width = '24px'; 734 img.style.height = '24px'; 735 img.style.objectFit = 'contain'; 736 iconWrap.appendChild(img); 737 738 var textBox = document.createElement('div'); 739 740 var title = document.createElement('div'); 741 title.className = 'fw-semibold'; 742 743 if (isPaypal) { 744 title.textContent = 'PayPal'; 745 } else { 746 var last4 = String(m.ultimos_4_digitos || '').replace(/\s+/g,''); 747 title.textContent = 'Tarjeta terminación ' + (last4 || 'â'); 748 } 749 750 var small = document.createElement('div'); 751 small.className = 'text-muted small'; 752 753 if (isPaypal) { 754 small.textContent = m.payer_email ? m.payer_email : 'â'; 755 } else { 756 var vig = vigencia(m.mes_expiracion, m.ano_expiracion); 757 var mk = brand(m.marca_tarjeta); 758 small.textContent = (vig ? ('Vigencia ' + vig) : '') + (vig && mk ? ' â ' : '') + (mk || ''); 759 } 760 761 textBox.appendChild(title); 762 textBox.appendChild(small); 763 764 left.appendChild(iconWrap); 765 left.appendChild(textBox); 766 767 // derecha: acciones (mobile: abajo ocupando ancho, sm+: a la derecha) 768 var right = document.createElement('div'); 769 right.className = 'ms-sm-auto w-100-'; 770 771 var actions = document.createElement('div'); 772 actions.className = 'd-flex flex-wrap gap-2 justify-content-start justify-content-sm-end'; 773 774 if (isPred) { 775 var badge = document.createElement('span'); 776 badge.className = 'badge rounded-pill text-bg-primary'; 777 badge.textContent = 'Predeterminado'; 778 actions.appendChild(badge); 779 } else { 780 var btn = document.createElement('button'); 781 btn.type = 'button'; 782 btn.className = 'btn btn-outline-primary btn-sm'; 783 btn.textContent = 'Hacer predeterminado'; 784 btn.setAttribute('data-n-metodo', m.id); 785 btn.setAttribute('data-method-index', idx); 786 btn.setAttribute('data-pasarela', pas); 787 if (isPaypal) btn.setAttribute('data-email', m.payer_email || ''); 788 if (!isPaypal) btn.setAttribute('data-last4', m.ultimos_4_digitos || ''); 789 actions.appendChild(btn); 790 791 var btnDel = document.createElement('button'); 792 btnDel.type = 'button'; 793 btnDel.className = 'btn btn-outline-danger btn-sm'; 794 btnDel.textContent = 'Eliminar'; 795 btnDel.setAttribute('data-n-metodo-eliminar', m.id); 796 actions.appendChild(btnDel); 797 } 798 799 right.appendChild(actions); 800 801 row.appendChild(left); 802 row.appendChild(right); 803 cont.appendChild(row);
804 }); 805 } 806 807 function cambiar_metodoPago(btn) { 808 809 id = btn.getAttribute("data-n-metodo") || ""; 810 811 if (id) { 812 813 token = getCookie("bx_token"); 814 815 formUtils.waitModal.show(); 816 817 fetch('https://meteoro.noroeste.com.mx/public/api/subscriptions/change_pm', { 818 method: 'POST', 819 headers: { 820 'Content-Type': 'application/json' 821 }, 822 body: JSON.stringify({ id: id, bx_siteId: config.siteId, token: token}) 823 }) 824 .then(r => r.json()) 825 .then(function(data) { 826 if (data.ok == true) { 827 828 ponerMetodosPago(data.metodos_pago); 829 830 } else { 831 832 formUtils.alertModal.show({ 833 title: "Error", 834 message: "Hubo un error al intentar generar la acción", 835 type: "error" 836 }); 837 838 } 839 }) 840 .catch(err => console.error('error:', err)) 841 .finally(function(){ 842 843 formUtils.waitModal.hide(); 844 845 }); 846 847 } 848 849 } 850 851 idBtnEliminar = 0; 852 853 function eliminar_metodoPago(btn) { 854 855 id = btn.getAttribute("data-n-metodo-eliminar") || ""; 856 857 if (id) { 858 859 idBtnEliminar = id; 860 861 formUtils.confirmModal.show({ 862 title: "Eliminar método de pago", 863 message: "¿Seguro de eliminar este método de pago?", 864 onConfirm: function() { 865 866 eliminar_metodoPago_2(); 867 868 }, 869 onCancel: function() { 870 871 // console.log("El usuario canceló la acción"); 872 873 } 874 }); 875 876 } 877 878 } 879 880 function eliminar_metodoPago_2() { 881 882 token = getCookie("bx_token"); 883 884 formUtils.waitModal.show(); 885 886 fetch('https://meteoro.noroeste.com.mx/public/api/subscriptions/delete_pm', { 887 method: 'POST', 888 headers: { 889 'Content-Type': 'application/json' 890 }, 891 body: JSON.stringify({ id: idBtnEliminar, bx_siteId: config.siteId, token: token }) 892 }) 893 .then(r => r.json()) 894 .then(function(data) { 895 if (data.ok == true) { 896 897 ponerMetodosPago(data.metodos_pago); 898 899 } else { 900 901 formUtils.alertModal.show({ 902 title: "Error", 903 message: "Hubo un error al intentar generar la acción", 904 type: "error" 905 }); 906 907 } 908 }) 909 .catch(err => console.error('error:', err)) 910 .finally(function(){ 911 912 formUtils.waitModal.hide(); 913 914 }); 915 916 } 917 918 function accionRedes(btn) { 919 920 const tile = btn.closest('[data-n-social]'); 921 const red = tile && tile.getAttribute('data-n-social'); 922 923 if (btn.matches('[data-n-connect]')) { 924 925 iniciarLoginSocial(red, config.siteId, data.usuario.token_redes); 926 927 } else if (btn.matches('[data-n-disconnect]')) { 928 929 accionRedesFetch(red, "disconnect", tile); 930 931 } 932 933 } 934 935 function accionRedesFetch(red, accion, tile) { 936 937 formUtils.waitModal.show({ 938 message: "Espere un momento.." 939 }); 940 941 token = getCookie("bx_token"); 942 943 fetch('https://meteoro.noroeste.com.mx/public/api/user/social', { 944 method: 'POST', 945 headers: { 946 'Content-Type': 'application/json', 947 'Authorization': 'Bearer ' + token 948 }, 949 body: JSON.stringify({ action: accion, network: red, bx_siteId: config.siteId }) 950 }) 951 .then(r => r.json()) 952 .then(function(data) { 953 if (data.res == "OK") { 954 955 formUtils.alertModal.show({ 956 title: "Red social", 957 message: "Dato guardado correctamente.", 958 type: "success" 959 }); 960 961 if (accion == "disconnect") { 962 setDisconnected(tile); 963 } 964 965 } else { 966 967 formUtils.alertModal.show({ 968 title: "Error", 969 message: "Hubo un error al intentar guardar el dato", 970 type: "error" 971 }); 972 973 } 974 }) 975 .catch(err => console.error('error:', err)) 976 .finally(function(){ 977 formUtils.waitModal.hide(); 978 }); 979 980 } 981 982 function setDisconnected(tile){ 983 const badge = tile.querySelector('[data-n-connected-badge]'); 984 const btnCon = tile.querySelector('[data-n-connect]'); 985 const btnDis = tile.querySelector('[data-n-disconnect]'); 986 const status = tile.querySelector('[data-n-status]'); 987 988 if (badge) badge.hidden = true; 989 if (btnDis) btnDis.hidden = true; 990 if (btnCon) btnCon.hidden = false; 991 if (status) status.textContent = 'No conectado'; 992 } 993 994 function setConnected(provider){ 995 const badge = tile.querySelector('[data-n-connected-badge]'); 996 const btnCon = tile.querySelector('[data-n-connect]'); 997 const btnDis = tile.querySelector('[data-n-disconnect]'); 998 const status = tile.querySelector('[data-n-status]'); 999 1000 if (badge) badge.hidden = true; 1001 if (btnDis) btnDis.hidden = true; 1002 if (btnCon) btnCon.hidden = false; 1003 if (status) status.textContent = 'No conectado'; 1004 } 1005 1006 function activarRed(provider, correo){ 1007 const tile = document.querySelector('.tile[data-n-social="'+provider+'"]'); 1008 if (!tile) return; 1009 1010 const badge = tile.querySelector('[data-n-connected-badge]'); 1011 const btnCon = tile.querySelector('[data-n-connect]'); 1012 const btnDis = tile.querySelector('[data-n-disconnect]'); 1013 const status = tile.querySelector('[data-n-status]'); 1014 1015 if (badge) badge.hidden = false; // mostrar badge "Conectado" 1016 if (btnCon) btnCon.hidden = true; // ocultar "Conectar" 1017 if (btnDis) btnDis.hidden = false; // mostrar "Desconectar" 1018 if (status) status.textContent = correo 1019 ? ('Conectado como ' + correo) 1020 : 'Conectado'; 1021 1022 tile.setAttribute('data-n-state', 'connected'); // (opcional) marca de estado 1023 } 1024 1025 function renderNewsletterChecks(datos) { 1026 // Normaliza claves a string y valores a boolean 1027 var state = {}; 1028 for (var k in datos) if (datos.hasOwnProperty(k)) state[String(k)] = !!datos[k]; 1029 1030 var nodes = document.querySelectorAll('#tab-newsletters input[type="checkbox"][data-n-news]'); 1031 for (var i = 0; i < nodes.length; i++) { 1032 var el = nodes[i];
1033 var id = el.getAttribute('data-n-news'); // "1", "2", "3" 1034 var checked = !!state[id]; // default: false si no viene 1035 if (el.checked !== checked) el.checked = checked; 1036 } 1037 } 1038 1039 function leer_news() { 1040 1041 token = getCookie("bx_token"); 1042 1043 fetch('https://meteoro.noroeste.com.mx/public/api/user/news_list', { 1044 method: 'POST', 1045 headers: { 1046 'Content-Type': 'application/json' 1047 }, 1048 body: JSON.stringify({ bx_siteId: config.siteId, token: token}) 1049 }) 1050 .then(r => r.json()) 1051 .then(function(data) { 1052 if (data.ok == true) { 1053 1054 renderNewsletterChecks(data.datos); 1055 1056 } else { 1057 1058 dlog('Hubo un error al guardar el dato'); 1059 1060 if (data.error) { 1061 dlog("Error cURL:", data.error); 1062 } 1063 if (data.raw) { 1064 dlog("Respuesta sin JSON válido:", data.raw); 1065 } 1066 1067 } 1068 }) 1069 .catch(err => console.error('error:', err)) 1070 .finally(function(){ 1071 1072 formUtils.waitModal.hide(); 1073 1074 }); 1075 1076 } 1077 1078 const script = document.createElement("script"); 1079 script.src = "https://meteoro.noroeste.com.mx/public/renderer/social_profile.js"; 1080 script.onload = () => dlog("social_profile.js cargado"); 1081 document.head.appendChild(script); 1082 1083 const script2 = document.createElement("script"); 1084 script2.src = "https://kit.fontawesome.com/a331efe121.js"; 1085 script2.onload = () => dlog("font awesome.js cargado"); 1086 document.head.appendChild(script2); 1087 1088 document.addEventListener('click', function (e) { 1089 const btn1 = e.target.closest('[data-n-action="logout"]'); 1090 if (btn1) { 1091 cerrar_sesion(); 1092 return; 1093 } 1094 1095 const btn3 = e.target.closest('[data-n-connect],[data-n-disconnect]'); 1096 if (btn3) { 1097 e.preventDefault(); 1098 accionRedes(btn3); 1099 return; 1100 } 1101 1102 const btn2 = e.target.closest('[data-n-action="password"]'); 1103 if (btn2) { 1104 cambiar_password(); 1105 return; 1106 } 1107 1108 const btn4 = e.target.closest('[data-n-action="nombre"]'); 1109 if (btn4) { 1110 cambiar_nombre(); 1111 return; 1112 } 1113 1114 const btn5 = e.target.closest('[data-n-action="add-payment-method"]'); 1115 if (btn5) { 1116 agregar_metodo(); 1117 return; 1118 } 1119 }); 1120 1121 function cambiar_password() { 1122 1123 const input = document.querySelector('[data-n-valor="password"]'); 1124 const pwd = input ? input.value : ''; 1125 1126 if (!pwd || pwd.length < 4) { 1127 alert('La nueva contraseña debe tener al menos 4 caracteres.'); 1128 1129 if (input) { input.focus(); } 1130 1131 return; 1132 } 1133 1134 token = getCookie("bx_token"); 1135 1136 fetch('https://meteoro.noroeste.com.mx/public/api/user/change_password', { 1137 method: 'POST', 1138 headers: { 1139 'Content-Type': 'application/json', 1140 'Authorization': 'Bearer ' + token 1141 }, 1142 body: JSON.stringify({ new_password: pwd, bx_siteId: config.siteId }), 1143 credentials: 'include' 1144 }) 1145 .then(r => r.json()) 1146 .then(function(data) { 1147 if (data.res == "OK") { 1148 1149 formUtils.alertModal.show({ 1150 title: "Correcto", 1151 message: "Dato guardado correctamente.", 1152 type: "success" 1153 }); 1154 1155 } else { 1156 1157 formUtils.alertModal.show({ 1158 title: "Error", 1159 message: "Hubo un error al intentar guardar el dato", 1160 type: "error" 1161 }); 1162 1163 } 1164 }) 1165 .catch(err => console.error('error:', err)); 1166 1167 } 1168 1169 function cambiar_nombre() { 1170 1171 const input = document.querySelector('[data-n-valor="nombre"]'); 1172 const nom = input ? input.value : ''; 1173 1174 if (!nom || nom.length < 4) { 1175 alert('El nombre debe tener al menos 4 caracteres.'); 1176 1177 if (input) { input.focus(); } 1178 1179 return; 1180 } 1181 1182 token = getCookie("bx_token"); 1183 1184 fetch('https://meteoro.noroeste.com.mx/public/api/user/change_name', { 1185 method: 'POST', 1186 headers: { 1187 'Content-Type': 'application/json', 1188 'Authorization': 'Bearer ' + token 1189 }, 1190 body: JSON.stringify({ new_name: nom, bx_siteId: config.siteId }), 1191 credentials: 'include' 1192 }) 1193 .then(r => r.json()) 1194 .then(function(data) { 1195 if (data.res == "OK") { 1196 1197 formUtils.alertModal.show({ 1198 title: "Correcto", 1199 message: "Dato guardado correctamente.", 1200 type: "success" 1201 }); 1202 1203 } else { 1204 1205 formUtils.alertModal.show({ 1206 title: "Error", 1207 message: "Hubo un error al intentar guardar el dato", 1208 type: "error" 1209 }); 1210 1211 } 1212 }) 1213 .catch(err => console.error('error:', err));
1214 1215 } 1216 1217 function cerrar_sesion() { 1218 1219 deleteCrossDomainCookie("bx_token"); 1220 1221 formUtils.alertModal.show({ 1222 title: "Sesión cerrada", 1223 message: "Has cerrado sesión correctamente.", 1224 type: "success", 1225 onClose: function() { 1226 const loadingModal = window.formUtils.loadingModal; 1227 loadingModal.show(); 1228 1229 window.location.href = "/acceso"; 1230 } 1231 }); 1232 1233 } 1234 1235 function agregar_metodo() { 1236 1237 } 1238 1239 window.onBxLoginSuccess = function(datos) { 1240 1241 if (datos.res == "OK") { 1242 1243 activarRed(datos.provider, datos.correo); 1244 1245 } else { 1246 1247 formUtils.alertModal.show({ 1248 title: "Red social", 1249 message: datos.msg, 1250 type: "error" 1251 }); 1252 1253 } 1254 1255 }; 1256 1257 (function() { 1258 // Crear script de jQuery 1259 var jq = document.createElement("script"); 1260 jq.src = "https://code.jquery.com/jquery-3.7.1.min.js"; 1261 jq.crossOrigin = "anonymous"; 1262 1263 jq.onload = function() { 1264 dlog("jQuery cargado"); 1265 1266 // Ahora sà cargamos DataTables 1267 var dt = document.createElement("script"); 1268 dt.src = "https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"; 1269 1270 dt.onload = function() { 1271 dlog("dataTables cargado"); 1272 }; 1273 1274 document.head.appendChild(dt); 1275 }; 1276 1277 document.head.appendChild(jq); 1278 })(); 1279 1280 (function waitDT(){ 1281 1282 if (window.jQuery && $.fn && $.fn.dataTable) { 1283 dlog("jQuery y dataTables cargados"); 1284 1285 ponerSuscripciones(data.usuario.suscripciones); 1286 ponerTransacciones(data.usuario.transacciones); 1287 ponerMetodosPago(data.usuario.metodos_pago); 1288 1289 const tieneActiva = data.usuario.suscripciones.some(s => s.status === 'activa') 1290 1291 if (tieneActiva) { 1292 var btn = document.querySelector('[data-n-action="suscribirme"]'); 1293 btn.style.display = 'none'; 1294 } 1295 1296 leer_news(); 1297 } else { 1298 dlog("jQuery y dataTables NO cargados"); 1299 1300 setTimeout(waitDT, 200); 1301 } 1302 1303 })(); 1304 1305};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.