1(function(){ 2 // No ejecutar en moviles / pantallas < lg (Bootstrap: 992px) 3 if (window.matchMedia && window.matchMedia('(max-width: 991.98px)').matches) return; 4 5 const FEED_URL = window.location.origin + "/ajax/live_feed.php"; 6 7 const bar = document.getElementById('ceLiveBar'); 8 const text = document.getElementById('ceLiveText'); 9 if (!bar || !text) return; 10 11 let items = []; 12 let idx = 0; 13 14 function esc(str){ 15 return String(str).replace(/[&<>"']/g, m => ({ 16 '&':'&','<':'<','>':'>','"':'"',"'":''' 17 }[m])); 18 } 19 20 function timeAgo(ts){ 21 // ts viene en segundos (unix). Forzamos número por si llega string. 22 const t = Number(ts || 0); 23 const s = Math.max(0, Math.floor(Date.now()/1000 - t)); 24 25 // Evita "hace 0 segundos" 26 if (s === 0) return 'hace un momento'; 27 28 if (s < 60) return `hace ${s} segundo${s === 1 ? '' : 's'}`; 29 const m = Math.floor(s / 60); 30 if (m < 60) return `hace ${m} minuto${m === 1 ? '' : 's'}`; 31 const h = Math.floor(m / 60); 32 return `hace ${h} hora${h === 1 ? '' : 's'}`; 33 } 34 35 function renderOne(){ 36 if (!items.length) { bar.hidden = true; return; } 37 bar.hidden = false; 38 39 const it = items[idx % items.length]; 40 idx++; 41 42 const ago = timeAgo(it.ts); 43 const city = esc(it.place_label || 'Perú'); 44 const name = esc(it.anchor || it.company_name || 'Empresa'); 45 const url = (it.url && typeof it.url === 'string') ? it.url : ''; 46 47 if (url) { 48 text.innerHTML = `<a class="meta-live__link" href="${esc(url)}">${name}</a> visto <span class="meta_live__time_ago">${ago}</span> en <b>${city}</b> <img src="${window.location.origin}/images/flags/pe.png" alt="PE" width="20" height="14" loading="lazy" class="meta_live__flag">`; 49 } else { 50 text.innerHTML = `<b>${name}</b> visto <span class="meta_live__time_ago">${ago}</span> en <b>${city}</b>`; 51 } 52 } 53 54 async function fetchFeed(){ 55 try{ 56 const r = await fetch(FEED_URL, { credentials:'same-origin', cache:'no-store' }); 57 if (!r.ok) throw new Error('bad_response'); 58 const data = await r.json(); 59 if (Array.isArray(data)) items = data; 60 renderOne(); 61 } catch(e){ 62 // falla silencioso 63 } 64 } 65 66 // Rotación suave (cada 4s) 67 setInterval(renderOne, 4000); 68 69 // Refresh del feed (cada 15s) 70 fetchFeed(); 71 setInterval(fetchFeed, 15000); 72})(); 73 74 75 76(function(){ 77 const btn = document.getElementById('btnFichaUpdate'); 78 if(!btn) return; 79 80 const endpoint = "../ajax/fichaUpdate.php"; 81 const ruc = (btn.getAttribute('data-ruc') || '').trim(); 82 if(!ruc) { btn.style.display = 'none'; return; } 83 84 // ---------- Cookie helpers ---------- 85 function setCookie(name, value, days){ 86 try{ 87 const d = new Date(); 88 d.setTime(d.getTime() + (days*24*60*60*1000)); 89 document.cookie = name + "=" + encodeURIComponent(value) + "; expires=" + d.toUTCString() + "; path=/; SameSite=Lax"; 90 }catch(e){} 91 } 92 function getCookie(name){ 93 try{ 94 const key = name + "="; 95 const parts = document.cookie.split(';'); 96 for(let i=0;i<parts.length;i++){ 97 let c = parts[i].trim(); 98 if(c.indexOf(key) === 0) return decodeURIComponent(c.substring(key.length)); 99 } 100 }catch(e){} 101 return ""; 102 } 103 104 // cookie por empresa (RUC) 105 const cookieKey = "ce_ficha_update_" + ruc; 106 107 // Si ya pidió actualización antes (cookie), ocultar el botón aunque PHP lo muestre 108 if (getCookie(cookieKey) === "1") { 109 btn.style.display = "none"; 110 return; 111 } 112 113 // ---------- Toast Bootstrap ---------- 114 const toastEl = document.getElementById('ceToast'); 115 const toastBody = document.getElementById('ceToastBody'); 116 117 function showToast(type, msg){ 118 // type: success | danger | warning | info 119 if(!toastEl || !toastBody) return; 120 121 // background 122 toastEl.classList.remove('text-bg-success','text-bg-danger','text-bg-warning','text-bg-info','text-bg-dark'); 123 if(type === 'success') toastEl.classList.add('text-bg-success'); 124 else if(type === 'danger') toastEl.classList.add('text-bg-danger'); 125 else if(type === 'warning') toastEl.classList.add('text-bg-warning'); 126 else if(type === 'info') toastEl.classList.add('text-bg-info'); 127 else toastEl.classList.add('text-bg-dark'); 128 129 toastBody.textContent = msg || 'â'; 130 131 try{ 132 const t = bootstrap.Toast.getOrCreateInstance(toastEl, { delay: 5500 }); 133 t.show(); 134 }catch(e){ 135 // fallback silencioso 136 } 137 } 138 139 function setBtnLoading(on){ 140 if(on){ 141 btn.disabled = true; 142 btn.dataset._old = btn.innerHTML; 143 btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span>Enviando...'; 144 }else{ 145 btn.disabled = false; 146 if(btn.dataset._old) btn.innerHTML = btn.dataset._old; 147 } 148 } 149 150 btn.addEventListener('click', async function(){ 151 setBtnLoading(true); 152 153 try{ 154 const body = new URLSearchParams({ type: 'revision', ruc: ruc }); 155 156 const res = await fetch(endpoint, { 157 method: 'POST', 158 credentials: 'same-origin', 159 headers: { 160 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 161 'X-Requested-With': 'XMLHttpRequest', 162 'Accept': 'application/json' 163 }, 164 body: body.toString() 165 }); 166 167 const ct = (res.headers.get('content-type') || '').toLowerCase(); 168 const data = ct.includes('application/json') ? await res.json().catch(()=>null) : null; 169 170 if(res.ok && data && data.ok){ 171 // Persistir (30 dÃas) para que no reaparezca al refrescar
172 setCookie(cookieKey, "1", 30); 173 174 showToast('success', data.msg || 'Solicitud enviada. Revisaremos la ficha pronto.'); 175 176 // UX: cambiar a âenviadoâ y ocultar 177 btn.classList.remove('btn-outline-secondary'); 178 btn.classList.add('btn-outline-success'); 179 btn.innerHTML = '<i class="bi bi-check2"></i> Solicitud enviada'; 180 setTimeout(()=>{ btn.style.display = 'none'; }, 900); 181 return; 182 } 183 184 const msg = (data && data.msg) ? data.msg : 'No se pudo enviar la solicitud. Intenta nuevamente.'; 185 showToast('danger', msg); 186 187 }catch(e){ 188 showToast('danger', 'Error de red. Intenta nuevamente.'); 189 }finally{ 190 if(btn.style.display !== 'none') setBtnLoading(false); 191 } 192 }); 193})();
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.