{"id":2784,"date":"2026-03-14T23:00:21","date_gmt":"2026-03-15T04:00:21","guid":{"rendered":"https:\/\/fast-net.com.co\/?page_id=2784"},"modified":"2026-03-14T23:17:32","modified_gmt":"2026-03-15T04:17:32","slug":"panel-interno","status":"publish","type":"page","link":"https:\/\/fast-net.com.co\/index.php\/panel-interno\/","title":{"rendered":"Panel Interno"},"content":{"rendered":"<p><!DOCTYPE html><br \/>\n<html lang=\"es\"><br \/>\n<head><br \/>\n<meta charset=\"UTF-8\"><br \/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><br \/>\n<title>FastNet \u2014 Panel Administrativo<\/title><\/p>\n<style>\n@import url('https:\/\/fonts.googleapis.com\/css2?family=Syne:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap');\n*{box-sizing:border-box;margin:0;padding:0}\n:root{\n  --bg:#07101f;--surf:#0d1a2d;--card:#112236;\n  --acc:#00d4ff;--acc2:#0055ff;--green:#00e676;--amber:#ffb300;--red:#ff5252;\n  --text:#dff0ff;--muted:#4a7099;--border:#1a3456;\n}\nbody{font-family:'Syne',sans-serif;background:var(--bg);color:var(--text);min-height:100vh}\n#screen-login{min-height:100vh;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}\n#screen-login::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse at 65% 35%,rgba(0,212,255,.08) 0%,transparent 60%),radial-gradient(ellipse at 20% 75%,rgba(0,85,255,.06) 0%,transparent 50%)}\n.grid-bg{position:absolute;inset:0;background-image:linear-gradient(rgba(0,212,255,.03) 1px,transparent 1px),linear-gradient(90deg,rgba(0,212,255,.03) 1px,transparent 1px);background-size:36px 36px;pointer-events:none}\n.lcard{background:var(--surf);border:1px solid var(--border);border-radius:16px;padding:2.5rem;width:100%;max-width:390px;position:relative;z-index:1}\n.llogo{text-align:center;margin-bottom:2rem}\n.licon{width:56px;height:56px;background:linear-gradient(135deg,var(--acc2),var(--acc));border-radius:14px;display:inline-flex;align-items:center;justify-content:center;font-size:28px;margin-bottom:10px}\n.ltitle{font-size:26px;font-weight:700}.ltitle span{color:var(--acc)}\n.lsub{font-size:11px;color:var(--muted);font-family:'IBM Plex Mono',monospace;margin-top:3px}\n.lf{margin-bottom:1.1rem}\n.lf label{display:block;font-size:10px;color:var(--muted);margin-bottom:5px;font-family:'IBM Plex Mono',monospace;letter-spacing:1px}\n.lf input{width:100%;background:var(--card);border:1px solid var(--border);color:var(--text);padding:.7rem 1rem;border-radius:8px;font-family:'Syne',sans-serif;font-size:14px;outline:none;transition:border-color .2s}\n.lf input:focus{border-color:var(--acc)}\n.lbtn{width:100%;background:linear-gradient(135deg,var(--acc2),var(--acc));border:none;color:#040d1a;padding:.85rem;border-radius:8px;font-family:'Syne',sans-serif;font-size:15px;font-weight:700;cursor:pointer;transition:opacity .2s;margin-top:.5rem}\n.lbtn:hover{opacity:.88}.lbtn:disabled{opacity:.5;cursor:not-allowed}\n.lerr{color:#ff6b6b;font-size:11px;text-align:center;margin-top:.7rem;display:none;font-family:'IBM Plex Mono',monospace}\n.lspinner{display:none;text-align:center;margin-top:.7rem;font-size:11px;color:var(--acc);font-family:'IBM Plex Mono',monospace}\n#screen-dash{display:none;padding:1.25rem 1.5rem;background:var(--bg);min-height:100vh}\n.topbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.25rem;padding-bottom:.9rem;border-bottom:1px solid var(--border)}\n.tlogo{font-size:20px;font-weight:700}.tlogo span{color:var(--acc)}\n.tsub{font-size:11px;color:var(--muted);font-family:'IBM Plex Mono',monospace;margin-left:8px}\n.tright{display:flex;align-items:center;gap:8px}\n.ubadge{background:var(--card);border:1px solid var(--border);border-radius:7px;padding:3px 11px;font-size:11px;color:var(--muted);font-family:'IBM Plex Mono',monospace}\n.logoutbtn,.refresh-btn{background:none;border:1px solid var(--border);color:var(--muted);padding:3px 11px;border-radius:7px;cursor:pointer;font-size:11px;font-family:'IBM Plex Mono',monospace;transition:all .2s}\n.logoutbtn:hover,.refresh-btn:hover{border-color:var(--acc);color:var(--acc)}\n.last-update{font-size:10px;color:var(--muted);font-family:'IBM Plex Mono',monospace}\n.loading-overlay{display:none;position:fixed;inset:0;background:rgba(7,16,31,.85);z-index:200;align-items:center;justify-content:center;flex-direction:column;gap:12px}\n.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--acc);border-radius:50%;animation:spin .7s linear infinite}\n@keyframes spin{to{transform:rotate(360deg)}}\n.loading-msg{font-size:12px;color:var(--acc);font-family:'IBM Plex Mono',monospace}\n.sec{font-size:10px;color:var(--muted);font-family:'IBM Plex Mono',monospace;letter-spacing:1.2px;text-transform:uppercase;margin:1.25rem 0 .7rem}\n.kpi-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px}\n.kpi{background:var(--card);border:1px solid var(--border);border-radius:11px;padding:.85rem 1rem}\n.kpi.hi{border-color:rgba(0,212,255,.35);background:rgba(0,212,255,.04)}\n.kpi.ok{border-color:rgba(0,230,118,.25);background:rgba(0,230,118,.03)}\n.kpi.warn{border-color:rgba(255,179,0,.25);background:rgba(255,179,0,.03)}\n.kpi.danger{border-color:rgba(255,82,82,.25);background:rgba(255,82,82,.03)}\n.kpi-lbl{font-size:10px;color:var(--muted);font-family:'IBM Plex Mono',monospace;margin-bottom:5px}\n.kpi-val{font-size:24px;font-weight:700;line-height:1}\n.kpi-sub{font-size:10px;margin-top:4px;font-family:'IBM Plex Mono',monospace;color:var(--muted)}\n.g2{display:grid;grid-template-columns:1fr 1fr;gap:10px}\n@media(max-width:620px){.g2{grid-template-columns:1fr}}\n.cc{background:var(--card);border:1px solid var(--border);border-radius:11px;padding:1rem}\n.cc-title{font-size:10px;color:var(--muted);font-family:'IBM Plex Mono',monospace;letter-spacing:.8px;margin-bottom:.85rem}\n.bar-area{display:flex;align-items:flex-end;gap:5px;height:90px;margin-bottom:20px}\n.bcol{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%}\n.bbar{width:80%;border-radius:3px 3px 0 0;min-height:3px;cursor:pointer;transition:opacity .15s}\n.bbar:hover{opacity:.7}\n.bval{font-size:9px;color:var(--text);font-family:'IBM Plex Mono',monospace;margin-bottom:2px}\n.blbl{font-size:9px;color:var(--muted);font-family:'IBM Plex Mono',monospace;margin-top:4px;white-space:nowrap}\n.dual-bar{width:42%;border-radius:3px 3px 0 0;min-height:3px;cursor:pointer;transition:opacity .15s}\n.dual-bar:hover{opacity:.7}\n.prog{margin-bottom:.55rem}\n.prog-head{display:flex;justify-content:space-between;font-size:11px;margin-bottom:3px}\n.prog-bg{background:var(--surf);border-radius:4px;height:7px;overflow:hidden}\n.prog-fill{height:100%;border-radius:4px;transition:width .5s}\n.donut-row{display:flex;align-items:center;justify-content:center;gap:1.25rem}\n.dleg{display:flex;flex-direction:column;gap:6px}\n.dli{display:flex;align-items:center;gap:6px;font-size:11px}\n.ddot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n.tk-row{display:flex;gap:8px;margin-bottom:1rem}\n.tk-box{flex:1;background:var(--surf);border-radius:9px;padding:.75rem;text-align:center}\n.tk-num{font-size:28px;font-weight:700;line-height:1}\n.tk-lbl{font-size:10px;color:var(--muted);font-family:'IBM Plex Mono',monospace;margin-top:4px}\n.bill-row{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:1rem}\n.bill-box{background:var(--surf);border-radius:9px;padding:.85rem}\n.bill-lbl{font-size:10px;color:var(--muted);font-family:'IBM Plex Mono',monospace;margin-bottom:5px}\n.bill-val{font-size:19px;font-weight:700}\n.bill-bar-bg{background:var(--border);border-radius:4px;height:5px;margin-top:7px;overflow:hidden}\n.bill-bar-fill{height:100%;border-radius:4px}\n.leg-row{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:.5rem}\n.leg-item{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--muted);font-family:'IBM Plex Mono',monospace}\n.leg-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}\n.tbl-wrap{overflow-x:auto}\ntable.dtbl{width:100%;border-collapse:collapse;font-size:12px}\n.dtbl th{color:var(--muted);font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:.5px;padding:6px 8px;text-align:left;border-bottom:1px solid var(--border)}\n.dtbl td{padding:7px 8px;border-bottom:1px solid rgba(26,52,86,.4)}\n.dtbl tr:last-child td{border-bottom:none}\n.badge{display:inline-block;padding:2px 8px;border-radius:20px;font-size:10px;font-family:'IBM Plex Mono',monospace}\n.b-active{background:rgba(0,230,118,.12);color:var(--green)}\n.b-suspend{background:rgba(255,179,0,.12);color:var(--amber)}\n.b-cancel{background:rgba(255,82,82,.12);color:var(--red)}\n.b-open{background:rgba(255,179,0,.12);color:var(--amber)}\n.b-closed{background:rgba(0,230,118,.12);color:var(--green)}\n.sheets-note{background:rgba(0,212,255,.05);border:1px solid rgba(0,212,255,.2);border-radius:9px;padding:.85rem 1rem;margin-bottom:.75rem;font-size:11px;color:var(--muted);font-family:'IBM Plex Mono',monospace}\n.sheets-input{display:flex;gap:8px;margin-bottom:.75rem;flex-wrap:wrap}\n.sheets-input input{flex:1;min-width:200px;background:var(--card);border:1px solid var(--border);color:var(--text);padding:.5rem .75rem;border-radius:7px;font-family:'IBM Plex Mono',monospace;font-size:11px;outline:none;transition:border-color .2s}\n.sheets-input input:focus{border-color:var(--acc)}\n.sheets-input button{background:var(--acc2);border:none;color:#fff;padding:.5rem 1rem;border-radius:7px;cursor:pointer;font-family:'IBM Plex Mono',monospace;font-size:11px;white-space:nowrap}\n<\/style>\n<p><\/head><br \/>\n<body><\/p>\n<div class=\"loading-overlay\" id=\"overlay\">\n<div class=\"spinner\"><\/div>\n<div class=\"loading-msg\" id=\"overlay-msg\">Cargando datos\u2026<\/div>\n<\/div>\n<div id=\"screen-login\">\n<div class=\"grid-bg\"><\/div>\n<div class=\"lcard\">\n<div class=\"llogo\">\n<div class=\"licon\">\u26a1<\/div>\n<div class=\"ltitle\">Fast<span>Net<\/span><\/div>\n<div class=\"lsub\">\/\/ Panel Administrativo Interno<\/div>\n<\/p><\/div>\n<div class=\"lf\"><label>USUARIO<\/label><input type=\"text\" id=\"l-user\" placeholder=\"usuario\" autocomplete=\"username\"\/><\/div>\n<div class=\"lf\"><label>CONTRASE\u00d1A<\/label><input type=\"password\" id=\"l-pass\" placeholder=\"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\" autocomplete=\"current-password\"\/><\/div>\n<p>    <button class=\"lbtn\" id=\"l-btn\" onclick=\"doLogin()\">INGRESAR AL PANEL<\/button><\/p>\n<div class=\"lerr\" id=\"l-err\">Usuario o contrase\u00f1a incorrectos<\/div>\n<div class=\"lspinner\" id=\"l-spin\">Verificando\u2026<\/div>\n<\/p><\/div>\n<\/div>\n<div id=\"screen-dash\">\n<div class=\"topbar\">\n<div><span class=\"tlogo\">Fast<span>Net<\/span><\/span><span class=\"tsub\">\/\/ Panel Interno<\/span><\/div>\n<div class=\"tright\">\n      <span class=\"last-update\" id=\"last-update\"><\/span><br \/>\n      <button class=\"refresh-btn\" onclick=\"loadAll()\">\u21bb actualizar<\/button><br \/>\n      <span class=\"ubadge\" id=\"u-label\">admin<\/span><br \/>\n      <button class=\"logoutbtn\" onclick=\"doLogout()\">\u21a9 salir<\/button>\n    <\/div>\n<\/p><\/div>\n<div class=\"sec\">Resumen en tiempo real<\/div>\n<div class=\"kpi-row\">\n<div class=\"kpi hi\">\n<div class=\"kpi-lbl\">CLIENTES ACTIVOS<\/div>\n<div class=\"kpi-val\" style=\"color:var(--acc)\" id=\"k-activos\">\u2014<\/div>\n<div class=\"kpi-sub\" id=\"k-activos-sub\">cargando\u2026<\/div>\n<\/div>\n<div class=\"kpi warn\">\n<div class=\"kpi-lbl\">SUSPENDIDOS<\/div>\n<div class=\"kpi-val\" style=\"color:var(--amber)\" id=\"k-suspend\">\u2014<\/div>\n<div class=\"kpi-sub ne\">este mes<\/div>\n<\/div>\n<div class=\"kpi danger\">\n<div class=\"kpi-lbl\">CANCELADOS<\/div>\n<div class=\"kpi-val\" style=\"color:var(--red)\" id=\"k-cancel\">\u2014<\/div>\n<div class=\"kpi-sub ne\">acumulado<\/div>\n<\/div>\n<div class=\"kpi ok\">\n<div class=\"kpi-lbl\">FACTURADO MES<\/div>\n<div class=\"kpi-val\" style=\"color:var(--green)\" id=\"k-fact\">\u2014<\/div>\n<div class=\"kpi-sub\" id=\"k-fact-sub\">cargando\u2026<\/div>\n<\/div>\n<div class=\"kpi ok\">\n<div class=\"kpi-lbl\">RECAUDADO MES<\/div>\n<div class=\"kpi-val\" style=\"color:var(--green)\" id=\"k-rec\">\u2014<\/div>\n<div class=\"kpi-sub\" id=\"k-rec-pct\">\u2014<\/div>\n<\/div>\n<div class=\"kpi warn\">\n<div class=\"kpi-lbl\">TICKETS ABIERTOS<\/div>\n<div class=\"kpi-val\" style=\"color:var(--amber)\" id=\"k-tks-open\">\u2014<\/div>\n<div class=\"kpi-sub\" id=\"k-tks-closed-sub\">cerrados: \u2014<\/div>\n<\/div><\/div>\n<div class=\"sec\">Clientes instalados \u2014 2026<\/div>\n<div class=\"g2\">\n<div class=\"cc\">\n<div class=\"cc-title\">CLIENTES ACTIVOS POR MES<\/div>\n<div class=\"bar-area\" id=\"bar-clientes\"><\/div>\n<\/p><\/div>\n<div class=\"cc\">\n<div class=\"cc-title\">ESTADO ACTUAL DE CLIENTES<\/div>\n<div class=\"donut-row\">\n        <svg width=\"90\" height=\"90\" viewBox=\"0 0 90 90\" id=\"svg-clientes\"><circle cx=\"45\" cy=\"45\" r=\"33\" fill=\"none\" stroke=\"#1a2235\" stroke-width=\"13\"\/><\/svg><\/p>\n<div class=\"dleg\" id=\"leg-clientes\"><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div class=\"sec\">Facturaci\u00f3n y recaudo \u2014 2026<\/div>\n<div class=\"g2\">\n<div class=\"cc\">\n<div class=\"cc-title\">FACTURADO VS RECAUDADO POR MES<\/div>\n<div class=\"leg-row\">\n<div class=\"leg-item\">\n<div class=\"leg-dot\" style=\"background:#0055ff\"><\/div>\n<p>Facturado<\/p><\/div>\n<div class=\"leg-item\">\n<div class=\"leg-dot\" style=\"background:#00e676\"><\/div>\n<p>Recaudado<\/p><\/div>\n<\/p><\/div>\n<div class=\"bar-area\" id=\"bar-billing\"><\/div>\n<\/p><\/div>\n<div class=\"cc\">\n<div class=\"cc-title\">DETALLE MES ACTUAL<\/div>\n<div class=\"bill-row\">\n<div class=\"bill-box\">\n<div class=\"bill-lbl\">FACTURADO<\/div>\n<div class=\"bill-val\" style=\"color:var(--acc)\" id=\"b-fact\">\u2014<\/div>\n<div class=\"bill-bar-bg\">\n<div class=\"bill-bar-fill\" style=\"width:100%;background:var(--acc)\"><\/div>\n<\/div>\n<\/div>\n<div class=\"bill-box\">\n<div class=\"bill-lbl\">RECAUDADO<\/div>\n<div class=\"bill-val\" style=\"color:var(--green)\" id=\"b-rec\">\u2014<\/div>\n<div class=\"bill-bar-bg\">\n<div class=\"bill-bar-fill\" id=\"b-rec-bar\" style=\"width:0%;background:var(--green)\"><\/div>\n<\/div>\n<\/div><\/div>\n<div class=\"prog\">\n<div class=\"prog-head\"><span style=\"font-size:11px\">% Recaudo<\/span><span style=\"font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--muted)\" id=\"b-pct-lbl\">0%<\/span><\/div>\n<div class=\"prog-bg\" style=\"height:9px\">\n<div class=\"prog-fill\" id=\"b-pct-bar\" style=\"width:0%;background:linear-gradient(90deg,var(--green),#00bcd4)\"><\/div>\n<\/div>\n<\/div>\n<div class=\"prog\">\n<div class=\"prog-head\"><span style=\"font-size:11px;color:var(--red)\">Cartera pendiente<\/span><span style=\"font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--red)\" id=\"b-pend-lbl\">\u2014<\/span><\/div>\n<div class=\"prog-bg\" style=\"height:9px\">\n<div class=\"prog-fill\" id=\"b-pend-bar\" style=\"width:0%;background:var(--red)\"><\/div>\n<\/div>\n<\/div><\/div>\n<\/p><\/div>\n<div class=\"sec\">Tickets de soporte \u2014 2026<\/div>\n<div class=\"g2\">\n<div class=\"cc\">\n<div class=\"cc-title\">TICKETS CREADOS VS CERRADOS POR MES<\/div>\n<div class=\"leg-row\">\n<div class=\"leg-item\">\n<div class=\"leg-dot\" style=\"background:var(--amber)\"><\/div>\n<p>Creados<\/p><\/div>\n<div class=\"leg-item\">\n<div class=\"leg-dot\" style=\"background:var(--green)\"><\/div>\n<p>Cerrados<\/p><\/div>\n<\/p><\/div>\n<div class=\"bar-area\" id=\"bar-tickets\"><\/div>\n<\/p><\/div>\n<div class=\"cc\">\n<div class=\"cc-title\">ESTADO MES ACTUAL<\/div>\n<div class=\"tk-row\">\n<div class=\"tk-box\">\n<div class=\"tk-num\" style=\"color:var(--amber)\" id=\"tk-open\">\u2014<\/div>\n<div class=\"tk-lbl\">ABIERTOS<\/div>\n<\/div>\n<div class=\"tk-box\">\n<div class=\"tk-num\" style=\"color:var(--green)\" id=\"tk-closed\">\u2014<\/div>\n<div class=\"tk-lbl\">CERRADOS<\/div>\n<\/div><\/div>\n<div class=\"prog\">\n<div class=\"prog-head\"><span style=\"font-size:11px\">Tasa de cierre<\/span><span style=\"font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--muted)\" id=\"tk-rate\">0%<\/span><\/div>\n<div class=\"prog-bg\" style=\"height:8px\">\n<div class=\"prog-fill\" id=\"tk-rate-bar\" style=\"width:0%;background:var(--green)\"><\/div>\n<\/div>\n<\/div><\/div>\n<\/p><\/div>\n<div class=\"sec\">\u00daltimos tickets<\/div>\n<div class=\"cc\" style=\"margin-bottom:1rem\">\n<div class=\"tbl-wrap\">\n<table class=\"dtbl\">\n<thead>\n<tr>\n<th>#<\/th>\n<th>CLIENTE<\/th>\n<th>ASUNTO<\/th>\n<th>FECHA<\/th>\n<th>ESTADO<\/th>\n<\/tr>\n<\/thead>\n<tbody id=\"tabla-tickets\">\n<tr>\n<td colspan=\"5\" style=\"text-align:center;color:var(--muted);padding:1rem\">Cargando\u2026<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/div>\n<\/p><\/div>\n<div class=\"sec\">Equipos recogidos (Google Sheets)<\/div>\n<div class=\"sheets-note\">\n    \ud83d\udccb Publica tu Google Sheets como CSV: Archivo \u2192 Compartir \u2192 Publicar en la web \u2192 CSV \u2192 Copiar enlace y p\u00e9galo aqu\u00ed.\n  <\/div>\n<div class=\"sheets-input\">\n    <input type=\"text\" id=\"sheets-url\" placeholder=\"https:\/\/docs.google.com\/spreadsheets\/d\/...\"\/><br \/>\n    <button onclick=\"loadSheets()\">Cargar<\/button>\n  <\/div>\n<div class=\"g2\" id=\"equipos-section\" style=\"display:none\">\n<div class=\"cc\">\n<div class=\"cc-title\">EQUIPOS RECOGIDOS POR MES<\/div>\n<div class=\"bar-area\" id=\"bar-equipos\"><\/div>\n<\/p><\/div>\n<div class=\"cc\">\n<div class=\"cc-title\">POR TECNOLOG\u00cdA \u2014 MES ACTUAL<\/div>\n<div class=\"donut-row\">\n        <svg width=\"90\" height=\"90\" viewBox=\"0 0 90 90\" id=\"svg-equipos\"><circle cx=\"45\" cy=\"45\" r=\"33\" fill=\"none\" stroke=\"#1a2235\" stroke-width=\"13\"\/><\/svg><\/p>\n<div class=\"dleg\" id=\"leg-equipos\"><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/div>\n<p><script>\nconst WP_BASE = 'https:\/\/fast-net.com.co\/wp-json\/fastnet\/v1';\nconst MESES = ['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic'];\nlet SESSION_TOKEN = localStorage.getItem('fn_token') || '';\nlet SESSION_USER  = localStorage.getItem('fn_user')  || '';<\/p>\n<p>function showOverlay(msg='Cargando...'){\n  document.getElementById('overlay').style.display='flex';\n  document.getElementById('overlay-msg').textContent=msg;\n}\nfunction hideOverlay(){ document.getElementById('overlay').style.display='none' }\nfunction fmtK(n){ return n>=1e6?'$'+(n\/1e6).toFixed(1)+'M':n>=1000?'$'+(n\/1000).toFixed(0)+'K':'$'+n }<\/p>\n<p>async function doLogin(){\n  const u=document.getElementById('l-user').value.trim();\n  const p=document.getElementById('l-pass').value;\n  const btn=document.getElementById('l-btn');\n  const err=document.getElementById('l-err');\n  err.style.display='none'; btn.disabled=true;\n  document.getElementById('l-spin').style.display='block';\n  try{\n    const r=await fetch(WP_BASE+'\/login',{method:'POST',headers:{'Content-Type':'application\/json'},body:JSON.stringify({usuario:u,password:p})});\n    const d=await r.json();\n    if(r.ok && d.token){\n      SESSION_TOKEN=d.token; SESSION_USER=d.usuario;\n      localStorage.setItem('fn_token',d.token);\n      localStorage.setItem('fn_user',d.usuario);\n      document.getElementById('screen-login').style.display='none';\n      document.getElementById('screen-dash').style.display='block';\n      document.getElementById('u-label').textContent=d.usuario;\n      await loadAll();\n    }else{ err.textContent=d.message||'Error de autenticaci\u00f3n'; err.style.display='block'; }\n  }catch(e){ err.textContent='No se pudo conectar con el servidor'; err.style.display='block'; }\n  finally{ btn.disabled=false; document.getElementById('l-spin').style.display='none'; }\n}<\/p>\n<p>function doLogout(){\n  SESSION_TOKEN=''; SESSION_USER='';\n  localStorage.removeItem('fn_token'); localStorage.removeItem('fn_user');\n  document.getElementById('screen-dash').style.display='none';\n  document.getElementById('screen-login').style.display='flex';\n  document.getElementById('l-user').value=''; document.getElementById('l-pass').value='';\n}<\/p>\n<p>document.addEventListener('keydown',e=>{ if(e.key==='Enter') doLogin() });<\/p>\n<p>async function apiCall(endpoint,body={}){\n  const r=await fetch(WP_BASE+'\/mikro\/'+endpoint,{\n    method:'POST',\n    headers:{'Content-Type':'application\/json','X-Fastnet-Token':SESSION_TOKEN},\n    body:JSON.stringify(body)\n  });\n  if(r.status===401){ doLogout(); return null; }\n  return r.json();\n}<\/p>\n<p>async function loadAll(){\n  showOverlay('Conectando con Mikrosystem...');\n  try{\n    const now=new Date();\n    const [clientes,facturas,pagos,tickets]=await Promise.all([\n      apiCall('clientes'),\n      apiCall('facturas',{mes:now.getMonth()+1,anio:now.getFullYear()}),\n      apiCall('pagos',{mes:now.getMonth()+1,anio:now.getFullYear()}),\n      apiCall('tickets'),\n    ]);\n    if(clientes) renderClientes(clientes);\n    if(facturas&&pagos) renderBilling(facturas,pagos);\n    if(tickets) renderTickets(tickets);\n    document.getElementById('last-update').textContent='Actualizado: '+now.toLocaleTimeString('es-CO',{hour:'2-digit',minute:'2-digit'});\n  }catch(e){ console.error(e); alert('Error cargando datos. Revisa la consola (F12).'); }\n  finally{ hideOverlay(); }\n}<\/p>\n<p>function renderClientes(data){\n  const activos=data.activos??data.total_activos??0;\n  const suspendidos=data.suspendidos??data.total_suspendidos??0;\n  const cancelados=data.cancelados??data.total_cancelados??0;\n  const total=activos+suspendidos+cancelados||1;\n  document.getElementById('k-activos').textContent=activos.toLocaleString('es-CO');\n  document.getElementById('k-activos-sub').textContent='de '+total.toLocaleString('es-CO')+' totales';\n  document.getElementById('k-suspend').textContent=suspendidos.toLocaleString('es-CO');\n  document.getElementById('k-cancel').textContent=cancelados.toLocaleString('es-CO');\n  const porMes=data.por_mes||new Array(12).fill(0);\n  renderBarSimple('bar-clientes',porMes,'linear-gradient(to top,#0055ff,#00d4ff)');\n  renderDonut('svg-clientes','leg-clientes',[\n    {label:'Activos',val:activos,color:'#00e676'},\n    {label:'Suspendidos',val:suspendidos,color:'#ffb300'},\n    {label:'Cancelados',val:cancelados,color:'#ff5252'},\n  ],total);\n}<\/p>\n<p>function renderBilling(facturas,pagos){\n  const fact=facturas.total??facturas.monto_total??0;\n  const rec=pagos.total??pagos.monto_total??0;\n  const pct=fact>0?Math.round((rec\/fact)*100):0;\n  const pend=fact-rec;\n  document.getElementById('k-fact').textContent=fmtK(fact);\n  document.getElementById('k-fact-sub').textContent='mes '+MESES[new Date().getMonth()];\n  document.getElementById('k-rec').textContent=fmtK(rec);\n  document.getElementById('k-rec-pct').textContent='recaudo: '+pct+'%';\n  document.getElementById('b-fact').textContent=fmtK(fact);\n  document.getElementById('b-rec').textContent=fmtK(rec);\n  document.getElementById('b-rec-bar').style.width=pct+'%';\n  document.getElementById('b-pct-lbl').textContent=pct+'%';\n  document.getElementById('b-pct-bar').style.width=pct+'%';\n  document.getElementById('b-pend-lbl').textContent=fmtK(pend);\n  document.getElementById('b-pend-bar').style.width=(100-pct)+'%';\n  renderBarDual('bar-billing',facturas.por_mes||new Array(12).fill(0),pagos.por_mes||new Array(12).fill(0),'#0055ff','#00e676');\n}<\/p>\n<p>function renderTickets(data){\n  const abiertos=data.abiertos??0;\n  const cerrados=data.cerrados??0;\n  const total=abiertos+cerrados||1;\n  const rate=Math.round((cerrados\/total)*100);\n  document.getElementById('k-tks-open').textContent=abiertos;\n  document.getElementById('k-tks-closed-sub').textContent='cerrados: '+cerrados;\n  document.getElementById('tk-open').textContent=abiertos;\n  document.getElementById('tk-closed').textContent=cerrados;\n  document.getElementById('tk-rate').textContent=rate+'%';\n  document.getElementById('tk-rate-bar').style.width=rate+'%';\n  renderBarDual('bar-tickets',data.creados_mes||new Array(12).fill(0),data.cerrados_mes||new Array(12).fill(0),'#ffb300','#00e676');\n  const lista=data.tickets||data.lista||[];\n  const tbody=document.getElementById('tabla-tickets');\n  if(!lista.length){ tbody.innerHTML='<\/p>\n<tr>\n<td colspan=\"5\" style=\"text-align:center;color:var(--muted);padding:1rem\">Sin tickets<\/td>\n<\/tr>\n<p>'; return; }\n  tbody.innerHTML=lista.slice(0,10).map(t=>`<\/p>\n<tr>\n<td style=\"color:var(--muted);font-family:'IBM Plex Mono',monospace\">#${t.id||t.codigo||'\u2014'}<\/td>\n<td>${t.cliente||t.nombre||'\u2014'}<\/td>\n<td>${t.asunto||t.titulo||'\u2014'}<\/td>\n<td style=\"color:var(--muted);font-family:'IBM Plex Mono',monospace;white-space:nowrap\">${t.fecha||'\u2014'}<\/td>\n<td><span class=\"badge ${t.estado==='Abierto'||t.estado==='abierto'?'b-open':'b-closed'}\">${t.estado||'\u2014'}<\/span><\/td>\n<\/tr>\n<p>`).join('');\n}<\/p>\n<p>async function loadSheets(){\n  const url=document.getElementById('sheets-url').value.trim();\n  if(!url){ alert('Ingresa la URL del Google Sheets'); return; }\n  showOverlay('Cargando Google Sheets...');\n  try{\n    const r=await fetch(url);\n    const text=await r.text();\n    const rows=text.trim().split('\\n').map(l=>l.split(','));\n    const headers=rows[0];\n    const mesesData=rows.slice(1).filter(r=>r.length>1);\n    const techs=headers.slice(1,headers.length-1);\n    const colors=['#00d4ff','#0055ff','#00e676','#ffb300','#7c3aed','#ff5252'];\n    renderBarSimple('bar-equipos',mesesData.map(r=>parseInt(r[r.length-1])||0),'linear-gradient(to top,#7c3aed,#00d4ff)');\n    const lastRow=mesesData[mesesData.length-1];\n    const lastTotal=parseInt(lastRow[lastRow.length-1])||1;\n    renderDonut('svg-equipos','leg-equipos',techs.map((t,i)=>({label:t,val:parseInt(lastRow[i+1])||0,color:colors[i]||'#888'})),lastTotal);\n    document.getElementById('equipos-section').style.display='grid';\n  }catch(e){ alert('Error cargando Sheets. Verifica que la URL sea CSV p\u00fablica.'); }\n  finally{ hideOverlay(); }\n}<\/p>\n<p>function renderBarSimple(id,values,color){\n  const el=document.getElementById(id); if(!el)return;\n  const active=values.map((v,i)=>({v,i})).filter(x=>x.v>0);\n  const max=Math.max(...active.map(x=>x.v),1);\n  el.innerHTML='';\n  active.forEach(({v,i})=>{\n    const col=document.createElement('div'); col.className='bcol';\n    col.innerHTML=`<\/p>\n<div class=\"bval\">${v}<\/div>\n<div class=\"bbar\" style=\"height:${Math.round(v\/max*100)}%;background:${color}\" title=\"${MESES[i]}: ${v}\"><\/div>\n<div class=\"blbl\">${MESES[i]}<\/div>\n<p>`;\n    el.appendChild(col);\n  });\n}<\/p>\n<p>function renderBarDual(id,vals1,vals2,c1,c2){\n  const el=document.getElementById(id); if(!el)return;\n  const max=Math.max(...[...vals1,...vals2].filter(v=>v>0),1);\n  el.innerHTML='';\n  vals1.forEach((v1,i)=>{\n    if(!v1&&!vals2[i])return;\n    const col=document.createElement('div');\n    col.style.cssText='flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%';\n    col.innerHTML=`<\/p>\n<div style=\"display:flex;gap:2px;align-items:flex-end;width:100%;justify-content:center;flex:1\">\n<div class=\"dual-bar\" style=\"height:${Math.round(v1\/max*100)}%;background:${c1}\"><\/div>\n<div class=\"dual-bar\" style=\"height:${Math.round((vals2[i]||0)\/max*100)}%;background:${c2}\"><\/div>\n<\/p><\/div>\n<div class=\"blbl\">${MESES[i]}<\/div>\n<p>`;\n    el.appendChild(col);\n  });\n}<\/p>\n<p>function renderDonut(svgId,legId,items,total){\n  const svg=document.getElementById(svgId); if(!svg)return;\n  const C=2*Math.PI*33; let off=0;\n  const circles=items.filter(x=>x.val>0).map(item=>{\n    const d=(item.val\/total)*C; const g=C-d;\n    const c=`<circle cx=\"45\" cy=\"45\" r=\"33\" fill=\"none\" stroke=\"${item.color}\" stroke-width=\"13\" stroke-dasharray=\"${d.toFixed(1)} ${g.toFixed(1)}\" stroke-dashoffset=\"${(-off).toFixed(1)}\"\/>`;\n    off+=d; return c;\n  }).join('');\n  svg.innerHTML=`<circle cx=\"45\" cy=\"45\" r=\"33\" fill=\"none\" stroke=\"#1a2235\" stroke-width=\"13\"\/>${circles}<text x=\"45\" y=\"49\" text-anchor=\"middle\" font-size=\"13\" font-weight=\"700\" fill=\"#dff0ff\" font-family=\"Syne,sans-serif\">${total}<\/text>`;\n  document.getElementById(legId).innerHTML=items.map(x=>`<\/p>\n<div class=\"dli\">\n<div class=\"ddot\" style=\"background:${x.color}\"><\/div>\n<p>${x.label} <span style=\"color:var(--muted);font-family:'IBM Plex Mono',monospace;font-size:10px;margin-left:3px\">${x.val}<\/span><\/div>\n<p>`).join('');\n}<\/p>\n<p>if(SESSION_TOKEN){\n  document.getElementById('screen-login').style.display='none';\n  document.getElementById('screen-dash').style.display='block';\n  document.getElementById('u-label').textContent=SESSION_USER;\n  loadAll();\n}\n<\/script><br \/>\n<\/body><br \/>\n<\/html><\/p>\n","protected":false},"excerpt":{"rendered":"<p>FastNet \u2014 Panel Administrativo Cargando datos\u2026 \u26a1 FastNet \/\/ Panel Administrativo Interno USUARIO CONTRASE\u00d1A INGRESAR AL PANEL Usuario o contrase\u00f1a incorrectos Verificando\u2026 FastNet\/\/ Panel Interno \u21bb actualizar admin \u21a9 salir Resumen en tiempo real CLIENTES ACTIVOS \u2014 cargando\u2026 SUSPENDIDOS \u2014 este mes CANCELADOS \u2014 acumulado FACTURADO MES \u2014 cargando\u2026 RECAUDADO MES \u2014 \u2014 TICKETS ABIERTOS [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_canvas","meta":{"footnotes":""},"class_list":["post-2784","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/fast-net.com.co\/index.php\/wp-json\/wp\/v2\/pages\/2784"}],"collection":[{"href":"https:\/\/fast-net.com.co\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/fast-net.com.co\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/fast-net.com.co\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fast-net.com.co\/index.php\/wp-json\/wp\/v2\/comments?post=2784"}],"version-history":[{"count":1,"href":"https:\/\/fast-net.com.co\/index.php\/wp-json\/wp\/v2\/pages\/2784\/revisions"}],"predecessor-version":[{"id":2785,"href":"https:\/\/fast-net.com.co\/index.php\/wp-json\/wp\/v2\/pages\/2784\/revisions\/2785"}],"wp:attachment":[{"href":"https:\/\/fast-net.com.co\/index.php\/wp-json\/wp\/v2\/media?parent=2784"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}