<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover"/>
<script>
  document.documentElement.dataset.panelMode =
    location.pathname.toLowerCase() === '/arkhon-engineering' ? 'engineering' : 'owner';
</script>
<title>Administración - ARKHON</title>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet"/>
<style>
:root{
  --bg:#f7f8fc;--surface:#fff;--border:#eaecf4;
  --text:#111827;--text-2:#4b5563;--text-3:#9ca3af;
  --orange:#f97316;--orange-dk:#ea6c0a;--orange-lt:rgba(249,115,22,.10);
  --green:#10b981;--green-lt:rgba(16,185,129,.12);
  --blue:#2563eb;--blue-lt:rgba(37,99,235,.10);
  --red:#ef4444;--red-lt:rgba(239,68,68,.10);
  --purple:#7c3aed;--purple-lt:rgba(124,58,237,.10);
  --amber:#d97706;--amber-lt:rgba(217,119,6,.10);
  --shadow-sm:0 1px 3px rgba(17,24,39,.06);
  --shadow:0 2px 8px rgba(17,24,39,.08);
  --r:12px;
}
*{margin:0;padding:0;box-sizing:border-box;}
html,body{font-family:"Plus Jakarta Sans",sans-serif;background:var(--bg);color:var(--text);min-height:100vh;font-size:13px;overflow-x:hidden;}
button,input,select,textarea{font-family:inherit;}
::-webkit-scrollbar{width:4px;height:4px;}
::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:8px;}
@keyframes spin{to{transform:rotate(360deg);}}
@keyframes fadein{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:translateY(0);}}

/* TOP BAR */
.topbar{
  background:var(--surface);border-bottom:1px solid var(--border);
  height:56px;padding:0 20px;display:flex;align-items:center;gap:12px;
  position:sticky;top:0;z-index:20;box-shadow:var(--shadow-sm);
}
.tb-logo{
  width:30px;height:30px;background:var(--orange);border-radius:8px;
  display:grid;place-items:center;flex-shrink:0;
}
.tb-logo svg{width:16px;height:16px;fill:none;stroke:#fff;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;}
.tb-title{font-weight:800;font-size:14px;}
.tb-sub{font-size:11px;color:var(--text-3);}
.tb-back{
  margin-left:auto;background:none;border:1px solid var(--border);border-radius:8px;
  padding:6px 14px;cursor:pointer;font-size:12px;font-weight:600;color:var(--text-2);
  display:flex;align-items:center;gap:6px;transition:all .15s;
}
.tb-back:hover{background:var(--bg);color:var(--text);}
.tb-back svg{width:13px;height:13px;flex-shrink:0;}

/* LAYOUT */
.layout{display:grid;grid-template-columns:210px minmax(0,1fr);height:calc(100vh - 56px);min-height:0;overflow:hidden;}

/* SIDEBAR */
.sidebar{
  background:var(--surface);border-right:1px solid var(--border);padding:12px 0;
  height:calc(100vh - 56px);min-height:0;overflow-y:auto;overflow-x:hidden;
  overscroll-behavior:contain;scrollbar-gutter:stable;
}
.sb-group{
  font-size:10px;font-weight:700;color:var(--text-3);letter-spacing:.8px;
  padding:14px 18px 6px;text-transform:uppercase;
}
.sb-item{
  display:flex;align-items:center;gap:10px;padding:9px 18px;cursor:pointer;
  font-size:12.5px;font-weight:500;color:var(--text-2);
  border-left:3px solid transparent;transition:all .15s;
}
.sb-item:hover{background:var(--bg);color:var(--text);}
.sb-item.active{background:var(--orange-lt);color:var(--orange);border-left-color:var(--orange);font-weight:700;}
.sb-item svg{width:15px;height:15px;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
.mode-hidden{display:none!important;}
html[data-panel-mode="owner"] [data-panel="engineering"],
html[data-panel-mode="engineering"] [data-panel="owner"]{display:none!important;}

/* MAIN */
.main{padding:24px 24px 96px;overflow-y:auto;overflow-x:hidden;max-width:none;width:100%;min-width:0;height:calc(100vh - 56px);min-height:0;}
.sec{display:none;}
.sec.active{display:block;animation:fadein .2s ease;max-width:1180px;}
.ph{margin-bottom:20px;}
.ph h2{font-size:17px;font-weight:800;}
.ph p{font-size:12px;color:var(--text-3);margin-top:3px;}

/* CARDS */
.card{
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r);padding:20px;margin-bottom:14px;box-shadow:var(--shadow-sm);
}
.ct{
  font-size:12.5px;font-weight:700;margin-bottom:14px;
  display:flex;align-items:center;gap:8px;color:var(--text);
}
.ct::before{content:'';width:4px;height:16px;background:var(--orange);border-radius:2px;flex-shrink:0;}

/* FORMS */
.fg{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
.fg.c1{grid-template-columns:1fr;}
.fg.c3{grid-template-columns:1fr 1fr 1fr;}
.billing-panel{max-width:960px;}
.billing-panel .card{border-radius:16px;box-shadow:0 10px 30px rgba(17,24,39,.06);}
.billing-panel .ph{
  background:linear-gradient(135deg,#fff 0%,#fff7ed 100%);
  border:1px solid rgba(249,115,22,.18);border-radius:16px;padding:16px 18px;
  box-shadow:0 10px 30px rgba(249,115,22,.06);
}
.billing-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;}
.billing-grid.customer{grid-template-columns:1fr 1fr;}
.billing-grid .full{grid-column:1/-1;}
.native-billing-fields{display:none!important;}
.billing-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:16px;}
.f{display:flex;flex-direction:column;gap:5px;}
.f label{font-size:11px;font-weight:700;color:var(--text-2);text-transform:uppercase;letter-spacing:.4px;}
.f input,.f select,.f textarea{
  border:1px solid var(--border);border-radius:8px;padding:8px 12px;
  font-size:12.5px;background:var(--bg);color:var(--text);outline:none;transition:border .15s;
}
.f textarea{resize:vertical;min-height:70px;}
.f input:focus,.f select:focus,.f textarea:focus{border-color:var(--orange);background:#fff;box-shadow:0 0 0 3px var(--orange-lt);}
.f input[readonly]{opacity:.6;cursor:default;}
.f input[readonly]:focus{border-color:var(--border);box-shadow:none;}
.f .hint{font-size:10.5px;color:var(--text-3);line-height:1.4;}

/* BUTTONS */
.btn{
  padding:8px 16px;border-radius:8px;font-size:12px;font-weight:700;
  cursor:pointer;border:none;transition:all .15s;
  display:inline-flex;align-items:center;gap:6px;
}
.btn svg{width:13px;height:13px;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.btn-primary{background:var(--orange);color:#fff;}
.btn-primary:hover{background:var(--orange-dk);}
.btn-primary:disabled{opacity:.5;cursor:not-allowed;}
.btn-ghost{background:var(--bg);color:var(--text-2);border:1px solid var(--border);}
.btn-ghost:hover{background:#e5e7eb;}
.btn-danger{background:var(--red-lt);color:var(--red);border:1px solid #fca5a5;}
.btn-danger:hover{background:#fee2e2;}
.btn-success{background:var(--green-lt);color:var(--green);border:1px solid #6ee7b7;}
.btn-success:hover{background:#d1fae5;}
.ab{display:flex;gap:10px;margin-top:16px;align-items:center;flex-wrap:wrap;}

/* BADGES */
.badge{display:inline-flex;align-items:center;padding:3px 9px;border-radius:20px;font-size:10.5px;font-weight:700;}
.bg{background:var(--green-lt);color:var(--green);}
.br{background:var(--red-lt);color:var(--red);}
.bo{background:var(--orange-lt);color:var(--orange);}
.bp{background:var(--purple-lt);color:var(--purple);}
.bam{background:var(--amber-lt);color:var(--amber);}
.bgr{background:#f3f4f6;color:var(--text-3);}

/* ALERTS */
.alert{padding:10px 14px;border-radius:8px;font-size:12px;margin-bottom:14px;display:none;font-weight:500;}
.alert.show{display:flex;align-items:center;gap:8px;}
.a-ok{background:var(--green-lt);border:1px solid #6ee7b7;color:#065f46;}
.a-err{background:var(--red-lt);border:1px solid #fca5a5;color:#991b1b;}
.a-info{background:var(--blue-lt);border:1px solid #93c5fd;color:#1e40af;}
.alert svg{width:15px;height:15px;flex-shrink:0;}

/* SYSTEM KPIs */
.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px;}
.kpi{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:14px 16px;text-align:center;}
.kpi .kv{font-size:22px;font-weight:800;color:var(--orange);}
.kpi .kl{font-size:11px;color:var(--text-3);margin-top:2px;}
.svc-row{display:flex;align-items:center;justify-content:space-between;padding:11px 0;border-bottom:1px solid var(--border);font-size:12.5px;}
.svc-row:last-child{border-bottom:none;}
.svc-name{font-weight:600;}
.svc-sub{font-size:11px;color:var(--text-3);}

/* LOGS */
.logbox{
  background:#0f172a;border-radius:8px;padding:14px;
  font-family:'Courier New',monospace;font-size:10.5px;color:#94a3b8;
  max-height:240px;overflow-y:auto;line-height:1.8;
}
.l-ok{color:#4ade80;}
.l-err{color:#f87171;}
.l-warn{color:#fbbf24;}
.l-info{color:#60a5fa;}

.spin{
  display:inline-block;width:12px;height:12px;
  border:2px solid var(--border);border-top-color:var(--orange);
  border-radius:50%;animation:spin .7s linear infinite;flex-shrink:0;
}
.sep{height:1px;background:var(--border);margin:4px 0 12px;}
.technical-gate{
  min-height:calc(100vh - 56px);
  display:flex;align-items:center;justify-content:center;
  padding:24px;background:var(--bg);
}
.technical-gate-card{
  width:min(92vw,420px);background:var(--surface);border:1px solid var(--border);
  border-radius:16px;padding:24px;box-shadow:var(--shadow);
}
.technical-gate-card h2{font-size:18px;font-weight:800;margin-bottom:6px;}
.technical-gate-card p{font-size:12px;color:var(--text-2);line-height:1.55;margin-bottom:16px;}
.technical-gate-card label{
  display:block;font-size:10px;font-weight:800;color:var(--text-3);
  letter-spacing:.08em;text-transform:uppercase;margin:12px 0 6px;
}
.technical-gate-card input{
  width:100%;border:1px solid var(--border);border-radius:10px;
  padding:10px 12px;font-size:14px;outline:none;background:var(--bg);margin-bottom:10px;
}
.technical-gate-card input:focus{border-color:var(--orange);background:#fff;box-shadow:0 0 0 3px var(--orange-lt);}
.technical-gate-error{min-height:16px;font-size:11px;color:var(--red);margin-bottom:10px;}

/* ─── USUARIOS ─────────────────────────────────────────────────── */
.usr-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:20px;}
.usr-card{
  border:1px solid var(--border);border-radius:var(--r);padding:16px;
  background:var(--surface);box-shadow:var(--shadow-sm);
  transition:box-shadow .15s;cursor:pointer;position:relative;overflow:hidden;
}
.usr-card:hover{box-shadow:var(--shadow);}
.usr-card.dueno   .usr-role-bar{background:var(--orange);}
.usr-card.gerente .usr-role-bar{background:var(--blue);}
.usr-card.aux     .usr-role-bar{background:var(--purple);}
.usr-role-bar{height:3px;position:absolute;top:0;left:0;right:0;}

.usr-avatar{
  width:40px;height:40px;border-radius:10px;
  display:grid;place-items:center;font-weight:800;font-size:16px;color:#fff;
  margin-bottom:10px;
}
.usr-card.dueno   .usr-avatar{background:var(--orange);}
.usr-card.gerente .usr-avatar{background:var(--blue);}
.usr-card.aux     .usr-avatar{background:var(--purple);}

.usr-name{font-size:13px;font-weight:700;margin-bottom:2px;}
.usr-rol{font-size:11px;color:var(--text-3);}
.usr-status{margin-top:10px;display:flex;align-items:center;justify-content:space-between;}
.usr-pin{font-size:10.5px;color:var(--text-3);font-family:monospace;}
.usr-edit-btn{
  background:none;border:1px solid var(--border);border-radius:6px;
  padding:3px 8px;font-size:10.5px;font-weight:600;cursor:pointer;
  color:var(--text-2);transition:all .15s;
}
.usr-edit-btn:hover{background:var(--bg);color:var(--text);}

/* permisos table */
.perm-table{width:100%;border-collapse:collapse;font-size:12px;}
.perm-table th{
  text-align:left;font-size:10.5px;font-weight:700;
  color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;
  padding:6px 10px;border-bottom:1px solid var(--border);
}
.perm-table td{padding:8px 10px;border-bottom:1px solid var(--border);vertical-align:middle;}
.perm-table tr:last-child td{border-bottom:none;}
.perm-table tr:hover td{background:var(--bg);}
.perm-check{width:16px;height:16px;accent-color:var(--orange);cursor:pointer;}
.perm-disabled{opacity:.35;pointer-events:none;}

/* modal overlay */
.modal-bg{
  display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);
  z-index:100;align-items:center;justify-content:center;
  backdrop-filter:blur(2px);
}
.modal-bg.open{display:flex;}
.modal{
  background:var(--surface);border-radius:16px;padding:24px;
  width:100%;max-width:480px;box-shadow:0 20px 60px rgba(0,0,0,.2);
  animation:fadein .2s ease;
}
.modal-title{font-size:15px;font-weight:800;margin-bottom:16px;display:flex;align-items:center;gap:8px;}
.modal-title svg{width:18px;height:18px;stroke:var(--orange);fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.modal-footer{display:flex;gap:10px;margin-top:20px;justify-content:flex-end;}

/* info chips */
.chip{
  display:inline-flex;align-items:center;gap:5px;
  background:var(--bg);border:1px solid var(--border);
  border-radius:6px;padding:4px 10px;font-size:11px;font-weight:600;color:var(--text-2);
}
.chip svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;}

/* ═══════════════════════════════════════════════════════════════
   RESPONSIVE — HAMBURGER / SIDEBAR OVERLAY / MOBILE LAYOUT
   ═══════════════════════════════════════════════════════════════ */

/* Botón hamburguesa — oculto en desktop */
.tb-burger{
  display:none;background:none;border:none;cursor:pointer;
  padding:6px;border-radius:8px;color:var(--text-2);margin-right:4px;
  transition:background .15s;flex-shrink:0;
}
.tb-burger:hover{background:var(--bg);}
.tb-burger svg{width:20px;height:20px;stroke:currentColor;fill:none;stroke-width:2.2;
  stroke-linecap:round;display:block;}

/* Overlay del sidebar en móvil */
.sb-overlay{
  display:none;position:fixed;inset:0;background:rgba(0,0,0,.4);
  z-index:29;backdrop-filter:blur(1px);
}
.sb-overlay.open{display:block;}

/* ── TABLET (≤900px) ────────────────────────────────────────── */
@media(max-width:900px){
  .layout{grid-template-columns:1fr;}

  .sidebar{
    position:fixed;top:0;left:0;bottom:0;width:240px;
    z-index:30;transform:translateX(-100%);
    transition:transform .25s ease;
    overflow-y:auto;padding-top:64px;
    box-shadow:4px 0 20px rgba(0,0,0,.12);
  }
  .sidebar.open{transform:translateX(0);}

  .tb-burger{display:flex;align-items:center;justify-content:center;}

  .tb-sub{display:none;}

  .main{padding:16px;max-width:100%;}

  /* Grids de formularios → 1 columna en tablet */
  .fg{grid-template-columns:1fr 1fr;}
  .fg.c3{grid-template-columns:1fr 1fr;}
  .billing-grid{grid-template-columns:1fr;}

  /* KPIs → 2 columnas */
  .kpis{grid-template-columns:1fr 1fr;}

  /* Usuarios → 2 columnas */
  .usr-grid{grid-template-columns:1fr 1fr;}

  /* Modal más ancho en tablet */
  .modal{max-width:92vw;}
}

/* ── MÓVIL (≤600px) ─────────────────────────────────────────── */
@media(max-width:600px){
  /* Topbar */
  .topbar{padding:0 12px;gap:8px;}
  .tb-title{font-size:13px;}
  .tb-back .tb-back-label{display:none;}
  .tb-back{padding:6px 10px;}

  /* Layout */
  .main{padding:12px 10px;}

  /* Formularios → 1 columna */
  .fg{grid-template-columns:1fr;}
  .fg.c3{grid-template-columns:1fr;}
  .billing-grid{grid-template-columns:1fr;}

  /* Cards */
  .card{padding:14px 12px;}

  /* KPIs → 2 columnas compactas */
  .kpis{grid-template-columns:1fr 1fr;gap:8px;}
  .kpi .kv{font-size:18px;}
  .kpi{padding:10px 12px;}

  /* Usuarios → 1 columna */
  .usr-grid{grid-template-columns:1fr;}

  /* Action buttons → wrap vertical */
  .ab{flex-direction:column;align-items:stretch;}
  .ab .btn{width:100%;justify-content:center;}
  .ab span{text-align:center;}

  /* Tabla de permisos — scroll horizontal */
  .perm-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
  .perm-table{min-width:420px;}
  .perm-table th:first-child,
  .perm-table td:first-child{min-width:130px;}

  /* Logbox */
  .logbox{font-size:9.5px;}

  /* Modal full-screen en móvil */
  .modal-bg{align-items:flex-end;}
  .modal{
    max-width:100%;width:100%;
    border-radius:18px 18px 0 0;
    padding:20px 16px;
    max-height:90vh;overflow-y:auto;
  }
  .modal-footer{flex-direction:column;}
  .modal-footer .btn{width:100%;justify-content:center;}

  /* SSH logbox */
  .logbox[style*="44px"]{font-size:10px;white-space:pre-wrap;word-break:break-all;max-height:64px;}

  /* Page headers */
  .ph h2{font-size:15px;}
  .ph p{font-size:11px;}

  /* Chips en topbar */
  .chip{font-size:10px;padding:3px 7px;}

  /* Badges de permisos */
  .badge{font-size:9.5px;padding:2px 7px;}
}

/* ── MÓVIL MUY PEQUEÑO (≤380px) ────────────────────────────── */
@media(max-width:380px){
  .tb-title{display:none;}
  .kpis{grid-template-columns:1fr 1fr;}
  .kpi .kv{font-size:16px;}
}

/* --- LOCAL SPECIFIC STYLES --- */

:root{
  --bg:#f7f8fc;--surface:#fff;--border:#eaecf4;
  --text:#111827;--text-2:#4b5563;--text-3:#9ca3af;
  --orange:#f97316;--orange-dk:#ea6c0a;--orange-lt:rgba(249,115,22,.10);
  --green:#10b981;--green-lt:rgba(16,185,129,.12);
  --blue:#2563eb;--blue-lt:rgba(37,99,235,.10);
  --red:#ef4444;--red-lt:rgba(239,68,68,.10);
  --purple:#7c3aed;--purple-lt:rgba(124,58,237,.10);
  --amber:#d97706;--amber-lt:rgba(217,119,6,.10);
  --shadow-sm:0 1px 3px rgba(17,24,39,.06);
  --shadow:0 2px 8px rgba(17,24,39,.08);
  --r:12px;
}
*{margin:0;padding:0;box-sizing:border-box;}
html,body{font-family:"Plus Jakarta Sans",sans-serif;background:var(--bg);color:var(--text);min-height:100vh;font-size:13px;overflow-x:hidden;}
button,input,select,textarea{font-family:inherit;}
::-webkit-scrollbar{width:4px;height:4px;}
::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:8px;}
@keyframes spin{to{transform:rotate(360deg);}}
@keyframes fadein{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:translateY(0);}}

/* TOP BAR */
.topbar{
  background:var(--surface);border-bottom:1px solid var(--border);
  height:56px;padding:0 20px;display:flex;align-items:center;gap:12px;
  position:sticky;top:0;z-index:20;box-shadow:var(--shadow-sm);
}
.tb-logo{
  width:30px;height:30px;background:var(--orange);border-radius:8px;
  display:grid;place-items:center;flex-shrink:0;
}
.tb-logo svg{width:16px;height:16px;fill:none;stroke:#fff;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;}
.tb-title{font-weight:800;font-size:14px;}
.tb-sub{font-size:11px;color:var(--text-3);}
.tb-back{
  margin-left:auto;background:none;border:1px solid var(--border);border-radius:8px;
  padding:6px 14px;cursor:pointer;font-size:12px;font-weight:600;color:var(--text-2);
  display:flex;align-items:center;gap:6px;transition:all .15s;
}
.tb-back:hover{background:var(--bg);color:var(--text);}
.tb-back svg{width:13px;height:13px;flex-shrink:0;}

/* LAYOUT */
.layout{display:grid;grid-template-columns:190px minmax(0,1fr);height:calc(100dvh - 56px);min-height:0;overflow:hidden;}

/* SIDEBAR */
.sidebar{
  background:var(--surface);border-right:1px solid var(--border);padding:12px 0;
  height:calc(100dvh - 56px);min-height:0;overflow-y:auto;overflow-x:hidden;
  overscroll-behavior:contain;scrollbar-gutter:stable;
}
.sb-group{
  font-size:10px;font-weight:700;color:var(--text-3);letter-spacing:.8px;
  padding:14px 18px 6px;text-transform:uppercase;
}
.sb-item{
  display:flex;align-items:center;gap:10px;padding:9px 18px;cursor:pointer;
  font-size:12.5px;font-weight:500;color:var(--text-2);
  border-left:3px solid transparent;transition:all .15s;
}
.sb-item:hover{background:var(--bg);color:var(--text);}
.sb-item.active{background:var(--orange-lt);color:var(--orange);border-left-color:var(--orange);font-weight:700;}
.sb-item svg{width:15px;height:15px;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
.mode-hidden{display:none!important;}

/* MAIN */
.main{padding:20px 24px 48px;overflow-y:auto;overflow-x:hidden;max-width:none;width:100%;min-width:0;height:calc(100dvh - 56px);min-height:0;}
.sec{display:none;}
.sec.active{display:block;animation:fadein .2s ease;max-width:1180px;}
.ph{margin-bottom:16px;}
.ph h2{font-size:17px;font-weight:800;}
.ph p{font-size:12px;color:var(--text-3);margin-top:3px;}

/* CARDS */
.card{
  background:var(--surface);border:1px solid var(--border);
  border-radius:8px;padding:18px;margin-bottom:14px;box-shadow:var(--shadow-sm);
}
.ct{
  font-size:12.5px;font-weight:700;margin-bottom:14px;
  display:flex;align-items:center;gap:8px;color:var(--text);
}
.ct::before{content:'';width:4px;height:16px;background:var(--orange);border-radius:2px;flex-shrink:0;}

/* FORMS */
.fg{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
.fg.c1{grid-template-columns:1fr;}
.fg.c3{grid-template-columns:1fr 1fr 1fr;}
.billing-panel{max-width:960px;}
.billing-panel .card{border-radius:16px;box-shadow:0 10px 30px rgba(17,24,39,.06);}
.billing-panel .ph{
  background:linear-gradient(135deg,#fff 0%,#fff7ed 100%);
  border:1px solid rgba(249,115,22,.18);border-radius:16px;padding:16px 18px;
  box-shadow:0 10px 30px rgba(249,115,22,.06);
}
.billing-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;}
.billing-grid.customer{grid-template-columns:1fr 1fr;}
.billing-grid .full{grid-column:1/-1;}
.native-billing-fields{display:none!important;}
.billing-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:16px;}
.f{display:flex;flex-direction:column;gap:5px;}
.f label{font-size:11px;font-weight:700;color:var(--text-2);text-transform:uppercase;letter-spacing:.4px;}
.f input,.f select,.f textarea{
  border:1px solid var(--border);border-radius:8px;padding:8px 12px;
  font-size:12.5px;background:var(--bg);color:var(--text);outline:none;transition:border .15s;
}
.f textarea{resize:vertical;min-height:70px;}
.f input:focus,.f select:focus,.f textarea:focus{border-color:var(--orange);background:#fff;box-shadow:0 0 0 3px var(--orange-lt);}
.f input[readonly]{opacity:.6;cursor:default;}
.f input[readonly]:focus{border-color:var(--border);box-shadow:none;}
.f .hint{font-size:10.5px;color:var(--text-3);line-height:1.4;}

/* BUTTONS */
.btn{
  padding:8px 16px;border-radius:8px;font-size:12px;font-weight:700;
  cursor:pointer;border:none;transition:all .15s;
  display:inline-flex;align-items:center;gap:6px;
}
.btn svg{width:13px;height:13px;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.btn-primary{background:var(--orange);color:#fff;}
.btn-primary:hover{background:var(--orange-dk);}
.btn-primary:disabled{opacity:.5;cursor:not-allowed;}
.btn-ghost{background:var(--bg);color:var(--text-2);border:1px solid var(--border);}
.btn-ghost:hover{background:#e5e7eb;}
.btn-danger{background:var(--red-lt);color:var(--red);border:1px solid #fca5a5;}
.btn-danger:hover{background:#fee2e2;}
.btn-success{background:var(--green-lt);color:var(--green);border:1px solid #6ee7b7;}
.btn-success:hover{background:#d1fae5;}
.ab{display:flex;gap:10px;margin-top:16px;align-items:center;flex-wrap:wrap;}

/* BADGES */
.badge{display:inline-flex;align-items:center;padding:3px 9px;border-radius:20px;font-size:10.5px;font-weight:700;}
.bg{background:var(--green-lt);color:var(--green);}
.br{background:var(--red-lt);color:var(--red);}
.bo{background:var(--orange-lt);color:var(--orange);}
.bp{background:var(--purple-lt);color:var(--purple);}
.bam{background:var(--amber-lt);color:var(--amber);}
.bgr{background:#f3f4f6;color:var(--text-3);}

/* ALERTS */
.alert{padding:10px 14px;border-radius:8px;font-size:12px;margin-bottom:14px;display:none;font-weight:500;}
.alert.show{display:flex;align-items:center;gap:8px;}
.a-ok{background:var(--green-lt);border:1px solid #6ee7b7;color:#065f46;}
.a-err{background:var(--red-lt);border:1px solid #fca5a5;color:#991b1b;}
.a-info{background:var(--blue-lt);border:1px solid #93c5fd;color:#1e40af;}
.alert svg{width:15px;height:15px;flex-shrink:0;}

/* SYSTEM KPIs */
.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px;}
.kpi{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:14px 16px;text-align:center;}
.kpi .kv{font-size:22px;font-weight:800;color:var(--orange);}
.kpi .kl{font-size:11px;color:var(--text-3);margin-top:2px;}
.svc-row{display:flex;align-items:center;justify-content:space-between;padding:11px 0;border-bottom:1px solid var(--border);font-size:12.5px;}
.svc-row:last-child{border-bottom:none;}
.svc-name{font-weight:600;}
.svc-sub{font-size:11px;color:var(--text-3);}
.mono-value{font-family:Consolas,"SFMono-Regular",monospace;color:var(--text-2);font-weight:600;letter-spacing:.02em;word-break:break-all;}

/* LOGS */
.logbox{
  background:#0f172a;border-radius:8px;padding:14px;
  font-family:'Courier New',monospace;font-size:10.5px;color:#94a3b8;
  max-height:240px;overflow-y:auto;line-height:1.8;
}
.l-ok{color:#4ade80;}
.l-err{color:#f87171;}
.l-warn{color:#fbbf24;}
.l-info{color:#60a5fa;}

.spin{
  display:inline-block;width:12px;height:12px;
  border:2px solid var(--border);border-top-color:var(--orange);
  border-radius:50%;animation:spin .7s linear infinite;flex-shrink:0;
}
.sep{height:1px;background:var(--border);margin:4px 0 12px;}

.security-section{max-width:1120px;height:calc(100dvh - 96px);overflow:hidden;flex-direction:column;min-height:0;}
.sec.security-section{display:none;}
.sec.security-section.active{display:flex;animation:fadein .2s ease;max-width:1120px;}
.security-section .ph h2{font-size:18px;}
.security-scroll{
  min-height:0;
  overflow-y:auto;
  padding-right:6px;
  scrollbar-gutter:stable;
}
.security-module-tabs{
  display:flex;
  gap:8px;
  margin-bottom:14px;
  position:sticky;
  top:0;
  z-index:2;
  background:var(--bg);
  padding:2px 0 10px;
}
.security-module-tab{
  border:1px solid var(--border);
  background:#fff;
  color:var(--text-2);
  border-radius:8px;
  padding:9px 14px;
  font-size:12px;
  font-weight:800;
  cursor:pointer;
}
.security-module-tab.active{background:var(--orange);border-color:var(--orange);color:#fff;}
.security-module{display:none;}
.security-module.active{display:block;}
.security-module-grid{display:grid;grid-template-columns:minmax(320px,.85fr) minmax(420px,1.15fr);gap:14px;align-items:start;}
.security-module .card{margin-bottom:14px;}
#seg-user-profiles{
  min-height:180px;
}
.security-top{display:grid;grid-template-columns:minmax(280px,1fr) minmax(360px,1fr);gap:14px;align-items:start;}
.security-create-card,.security-user-card{min-height:auto;}
.security-card-list{
  max-height:min(42vh,390px);
  overflow-y:auto;
  padding-right:4px;
  scrollbar-gutter:stable;
}
.security-profile{
  border:1px solid var(--border);
  border-radius:8px;
  margin-bottom:10px;
  padding:10px 12px;
  background:#fff;
}
.security-profile[open]{box-shadow:var(--shadow-sm);}
.security-profile summary{
  cursor:pointer;
  list-style:none;
  display:flex;
  align-items:center;
  gap:8px;
  min-height:28px;
}
.security-profile summary::-webkit-details-marker{display:none;}
.security-profile summary::before{
  content:'';
  width:0;height:0;
  border-top:4px solid transparent;
  border-bottom:4px solid transparent;
  border-left:6px solid var(--text-2);
  transition:transform .15s ease;
}
.security-profile[open] summary::before{transform:rotate(90deg);}
.security-profile-title{font-weight:800;line-height:1.25;}
.security-permissions{
  margin-top:8px;
  max-height:260px;
  overflow-y:auto;
  padding-right:4px;
}
.security-permission{
  display:flex;
  gap:10px;
  align-items:flex-start;
  padding:8px 0;
  border-bottom:1px solid var(--border);
}
.security-permission:last-child{border-bottom:0;}
.security-permission input{margin-top:2px;accent-color:var(--orange);flex-shrink:0;}
.security-permission-main{flex:1;min-width:0;}
.security-permission-title{font-weight:700;font-size:12px;}
.security-permission-help{display:block;font-size:10.5px;color:var(--text-3);line-height:1.35;margin-top:2px;}
.security-users{
  max-height:min(32vh,260px);
  overflow-y:auto;
  padding-right:4px;
}
.security-user-row{
  display:flex;
  gap:14px;
  align-items:center;
  justify-content:space-between;
  border-bottom:1px solid var(--border);
  padding:10px 0;
}
.security-user-row:last-child{border-bottom:0;}
.security-user-name{font-weight:800;}
.security-user-profiles{font-size:11px;color:var(--text-3);line-height:1.4;margin-top:2px;}
.technical-gate{
  min-height:calc(100vh - 56px);
  display:flex;align-items:center;justify-content:center;
  padding:24px;background:var(--bg);
}
.technical-gate-card{
  width:min(92vw,420px);background:var(--surface);border:1px solid var(--border);
  border-radius:16px;padding:24px;box-shadow:var(--shadow);
}
.technical-gate-card h2{font-size:18px;font-weight:800;margin-bottom:6px;}
.technical-gate-card p{font-size:12px;color:var(--text-2);line-height:1.55;margin-bottom:16px;}
.technical-gate-card label{
  display:block;font-size:10px;font-weight:800;color:var(--text-3);
  letter-spacing:.08em;text-transform:uppercase;margin:12px 0 6px;
}
.technical-gate-card input{
  width:100%;border:1px solid var(--border);border-radius:10px;
  padding:10px 12px;font-size:14px;outline:none;background:var(--bg);margin-bottom:10px;
}
.technical-gate-card input:focus{border-color:var(--orange);background:#fff;box-shadow:0 0 0 3px var(--orange-lt);}
.technical-gate-error{min-height:16px;font-size:11px;color:var(--red);margin-bottom:10px;}

/* ─── USUARIOS ─────────────────────────────────────────────────── */
.usr-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:20px;}
.usr-card{
  border:1px solid var(--border);border-radius:var(--r);padding:16px;
  background:var(--surface);box-shadow:var(--shadow-sm);
  transition:box-shadow .15s;cursor:pointer;position:relative;overflow:hidden;
}
.usr-card:hover{box-shadow:var(--shadow);}
.usr-card.dueno   .usr-role-bar{background:var(--orange);}
.usr-card.gerente .usr-role-bar{background:var(--blue);}
.usr-card.aux     .usr-role-bar{background:var(--purple);}
.usr-role-bar{height:3px;position:absolute;top:0;left:0;right:0;}

.usr-avatar{
  width:40px;height:40px;border-radius:10px;
  display:grid;place-items:center;font-weight:800;font-size:16px;color:#fff;
  margin-bottom:10px;
}
.usr-card.dueno   .usr-avatar{background:var(--orange);}
.usr-card.gerente .usr-avatar{background:var(--blue);}
.usr-card.aux     .usr-avatar{background:var(--purple);}

.usr-name{font-size:13px;font-weight:700;margin-bottom:2px;}
.usr-rol{font-size:11px;color:var(--text-3);}
.usr-status{margin-top:10px;display:flex;align-items:center;justify-content:space-between;}
.usr-pin{font-size:10.5px;color:var(--text-3);font-family:monospace;}
.usr-edit-btn{
  background:none;border:1px solid var(--border);border-radius:6px;
  padding:3px 8px;font-size:10.5px;font-weight:600;cursor:pointer;
  color:var(--text-2);transition:all .15s;
}
.usr-edit-btn:hover{background:var(--bg);color:var(--text);}

/* permisos table */
.perm-table{width:100%;border-collapse:collapse;font-size:12px;}
.perm-table th{
  text-align:left;font-size:10.5px;font-weight:700;
  color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;
  padding:6px 10px;border-bottom:1px solid var(--border);
}
.perm-table td{padding:8px 10px;border-bottom:1px solid var(--border);vertical-align:middle;}
.perm-table tr:last-child td{border-bottom:none;}
.perm-table tr:hover td{background:var(--bg);}
.perm-check{width:16px;height:16px;accent-color:var(--orange);cursor:pointer;}
.perm-disabled{opacity:.35;pointer-events:none;}

/* modal overlay */
.modal-bg{
  display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);
  z-index:100;align-items:center;justify-content:center;
  backdrop-filter:blur(2px);
}
.modal-bg.open{display:flex;}
.modal{
  background:var(--surface);border-radius:16px;padding:24px;
  width:100%;max-width:480px;box-shadow:0 20px 60px rgba(0,0,0,.2);
  animation:fadein .2s ease;
}
.modal-title{font-size:15px;font-weight:800;margin-bottom:16px;display:flex;align-items:center;gap:8px;}
.modal-title svg{width:18px;height:18px;stroke:var(--orange);fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.modal-footer{display:flex;gap:10px;margin-top:20px;justify-content:flex-end;}

/* info chips */
.chip{
  display:inline-flex;align-items:center;gap:5px;
  background:var(--bg);border:1px solid var(--border);
  border-radius:6px;padding:4px 10px;font-size:11px;font-weight:600;color:var(--text-2);
}
.chip svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;}

/* ═══════════════════════════════════════════════════════════════
   RESPONSIVE — HAMBURGER / SIDEBAR OVERLAY / MOBILE LAYOUT
   ═══════════════════════════════════════════════════════════════ */

/* Botón hamburguesa — oculto en desktop */
.tb-burger{
  display:none;background:none;border:none;cursor:pointer;
  padding:6px;border-radius:8px;color:var(--text-2);margin-right:4px;
  transition:background .15s;flex-shrink:0;
}
.tb-burger:hover{background:var(--bg);}
.tb-burger svg{width:20px;height:20px;stroke:currentColor;fill:none;stroke-width:2.2;
  stroke-linecap:round;display:block;}

/* Overlay del sidebar en móvil */
.sb-overlay{
  display:none;position:fixed;inset:0;background:rgba(0,0,0,.4);
  z-index:29;backdrop-filter:blur(1px);
}
.sb-overlay.open{display:block;}

/* ── TABLET (≤900px) ────────────────────────────────────────── */
@media(max-width:900px){
  .layout{grid-template-columns:1fr;}

  .sidebar{
    position:fixed;top:0;left:0;bottom:0;width:240px;
    z-index:30;transform:translateX(-100%);
    transition:transform .25s ease;
    overflow-y:auto;padding-top:64px;
    box-shadow:4px 0 20px rgba(0,0,0,.12);
  }
  .sidebar.open{transform:translateX(0);}

  .tb-burger{display:flex;align-items:center;justify-content:center;}

  .tb-sub{display:none;}

  .main{padding:16px;max-width:100%;}
  .security-top{grid-template-columns:1fr;}
  .security-section{height:auto;overflow:visible;}
  .security-scroll{overflow:visible;padding-right:0;}
  .security-card-list{max-height:none;}
  .security-permissions{max-height:320px;}

  /* Grids de formularios → 1 columna en tablet */
  .fg{grid-template-columns:1fr 1fr;}
  .fg.c3{grid-template-columns:1fr 1fr;}
  .billing-grid{grid-template-columns:1fr;}

  /* KPIs → 2 columnas */
  .kpis{grid-template-columns:1fr 1fr;}

  /* Usuarios → 2 columnas */
  .usr-grid{grid-template-columns:1fr 1fr;}

  /* Modal más ancho en tablet */
  .modal{max-width:92vw;}
}

/* ── MÓVIL (≤600px) ─────────────────────────────────────────── */
@media(max-width:600px){
  /* Topbar */
  .topbar{padding:0 12px;gap:8px;}
  .tb-title{font-size:13px;}
  .tb-back .tb-back-label{display:none;}
  .tb-back{padding:6px 10px;}

  /* Layout */
  .main{padding:12px 10px;}
  .security-users{max-height:none;}
  .security-user-row{align-items:flex-start;}

  /* Formularios → 1 columna */
  .fg{grid-template-columns:1fr;}
  .fg.c3{grid-template-columns:1fr;}
  .billing-grid{grid-template-columns:1fr;}

  /* Cards */
  .card{padding:14px 12px;}

  /* KPIs → 2 columnas compactas */
  .kpis{grid-template-columns:1fr 1fr;gap:8px;}
  .kpi .kv{font-size:18px;}
  .kpi{padding:10px 12px;}

  /* Usuarios → 1 columna */
  .usr-grid{grid-template-columns:1fr;}

  /* Action buttons → wrap vertical */
  .ab{flex-direction:column;align-items:stretch;}
  .ab .btn{width:100%;justify-content:center;}
  .ab span{text-align:center;}

  /* Tabla de permisos — scroll horizontal */
  .perm-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
  .perm-table{min-width:420px;}
  .perm-table th:first-child,
  .perm-table td:first-child{min-width:130px;}

  /* Logbox */
  .logbox{font-size:9.5px;}

  /* Modal full-screen en móvil */
  .modal-bg{align-items:flex-end;}
  .modal{
    max-width:100%;width:100%;
    border-radius:18px 18px 0 0;
    padding:20px 16px;
    max-height:90vh;overflow-y:auto;
  }
  .modal-footer{flex-direction:column;}
  .modal-footer .btn{width:100%;justify-content:center;}

  /* SSH logbox */
  .logbox[style*="44px"]{font-size:10px;white-space:pre-wrap;word-break:break-all;max-height:64px;}

  /* Page headers */
  .ph h2{font-size:15px;}
  .ph p{font-size:11px;}

  /* Chips en topbar */
  .chip{font-size:10px;padding:3px 7px;}

  /* Badges de permisos */
  .badge{font-size:9.5px;padding:2px 7px;}
}

/* ── MÓVIL MUY PEQUEÑO (≤380px) ────────────────────────────── */
@media(max-width:380px){
  .tb-title{display:none;}
  .kpis{grid-template-columns:1fr 1fr;}
  .kpi .kv{font-size:16px;}
}
</style>
<link rel="stylesheet" href="/css/responsive.css?v=407m"/>
</head>
<body>

<!-- TOP BAR -->
<div class="topbar">
  <button class="tb-burger" onclick="toggleAdminSidebar()" aria-label="Menú">
    <svg viewBox="0 0 24 24"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
  </button>
  <div class="tb-logo">
    <svg viewBox="0 0 24 24"><path d="M3 11l19-9-9 19-2-8-8-2z"/></svg>
  </div>
  <span class="tb-title">ARKHON</span>
  <span class="tb-sub" id="panel-subtitle">/ Panel de Administración</span>
  <button class="tb-back" type="button" data-panel="owner" onclick="showSec('mi-perfil')" title="Editar mi perfil">
    <svg viewBox="0 0 24 24" stroke-width="2.2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
    <span>Mi perfil</span>
  </button>
  <button class="tb-back" onclick="if(window.parent&&window.parent!==window){window.parent.postMessage({tipo:'nav_dashboard'},'*');}else{location.href='/';}">
    <svg viewBox="0 0 24 24" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
    <span class="tb-back-label">Volver al Monitor</span>
  </button>
</div>

<!-- Overlay para cerrar sidebar en móvil -->
<div class="sb-overlay" id="sb-overlay-admin" onclick="toggleAdminSidebar()"></div>

<div class="technical-gate mode-hidden" id="engineering-gate">
  <div class="technical-gate-card">
    <h2>Arkhon Engineering</h2>
    <p>Panel técnico interno. Ingresa con un correo autorizado por ARKHON y su contraseña cifrada de Engineering.</p>
    <label for="engineering-email">Correo autorizado</label>
    <input id="engineering-email" type="email" autocomplete="username" placeholder="noejacobovillar@gmail.com" value="noejacobovillar@gmail.com">
    <label for="engineering-key">Contraseña Engineering</label>
    <input id="engineering-key" type="password" autocomplete="current-password" placeholder="Contraseña">
    <div class="technical-gate-error" id="engineering-key-error"></div>
    <button class="btn btn-primary" style="width:100%;justify-content:center" onclick="submitEngineeringAccess()">Entrar</button>
  </div>
</div>

<div class="layout">

  <!-- SIDEBAR -->
  <nav class="sidebar" id="admin-sidebar">
    <div class="sb-group" data-panel="owner">Administración</div>

    <div class="sb-item" data-panel="owner" data-sec="mi-perfil">
      <svg viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
      Mi perfil
    </div>
    <div class="sb-item active" data-panel="owner" data-sec="seguridad">
      <svg viewBox="0 0 24 24"><path d="M12 2l7 4v6c0 5-3 9-7 10-4-1-7-5-7-10V6l7-4z"/><path d="M8 12h8"/><path d="M12 8v8"/></svg>
      Seguridad / Perfiles
    </div>
    <div class="sb-item" data-panel="owner" data-sec="licencia">
      <svg viewBox="0 0 24 24"><path d="M15 7a4 4 0 11-4-4 4 4 0 014 4z"/><path d="M14 14l7 7M18 18l3-3"/></svg>
      Licencia
    </div>
    <div class="sb-item" data-panel="owner" data-sec="actualizaciones">
      <svg viewBox="0 0 24 24"><path d="M21 12a9 9 0 11-2.64-6.36"/><polyline points="21 3 21 9 15 9"/></svg>
      Actualizaciones
    </div>

    <div class="sb-group" data-panel="engineering">Arkhon Engineering</div>

    <div class="sb-item active" data-panel="engineering" data-sec="general">
      <svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
      General
    </div>
    <div class="sb-item" data-panel="engineering" data-sec="db">
      <svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
      Base de Datos
    </div>
    <div class="sb-item" data-panel="engineering" data-sec="billing">
      <svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 8h10M7 12h10M7 16h5"/></svg>
      Facturacion
    </div>
    <div class="sb-item" data-panel="engineering" data-sec="acceso">
      <svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>
      Acceso y Claves
    </div>
    <div class="sb-item" data-panel="engineering" data-sec="tunel">
      <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 010 20M12 2a15.3 15.3 0 000 20"/></svg>
      Dominio / Tunel
    </div>
    <div class="sb-item" data-panel="engineering" data-sec="cache">
      <svg viewBox="0 0 24 24"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
      Cache
    </div>
    <div class="sb-item" data-panel="engineering" data-sec="ia">
      <svg viewBox="0 0 24 24"><path d="M12 2a2 2 0 012 2c0 .74-.4 1.38-1 1.72V7h1a7 7 0 017 7H3a7 7 0 017-7h1V5.72c-.6-.34-1-.98-1-1.72a2 2 0 012-2z"/><path d="M3 14v3a9 9 0 0018 0v-3"/></svg>
      Análisis avanzado
    </div>

    <div class="sb-group" data-panel="engineering">Sistema</div>

    <div class="sb-item" data-panel="engineering" data-sec="estado">
      <svg viewBox="0 0 24 24"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
      Estado
    </div>
    <div class="sb-item" data-panel="engineering" data-sec="logs">
      <svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
      Logs
    </div>
    <div class="sb-item" data-panel="engineering" data-sec="diagnostico">
      <svg viewBox="0 0 24 24"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
      Diagnostico
    </div>
    <div class="sb-item" data-panel="engineering" data-sec="licencia">
      <svg viewBox="0 0 24 24"><path d="M15 7a4 4 0 11-4-4 4 4 0 014 4z"/><path d="M14 14l7 7M18 18l3-3"/></svg>
      Licencia
    </div>
  </nav>

  <!-- MAIN CONTENT -->
  <main class="main">
    <div id="alert-g" class="alert"></div>

    <div class="sec" id="s-mi-perfil" data-panel="owner">
      <div class="ph">
        <h2>Mi perfil</h2>
        <p>Actualiza tu nombre visible y cambia tu contraseña de acceso.</p>
      </div>
      <div class="card" style="max-width:620px">
        <div class="ct">Cuenta de usuario</div>
        <div class="fg c1">
          <div class="f">
            <label>Nombre visible</label>
            <input id="me-display-name" autocomplete="name" placeholder="Tu nombre">
          </div>
          <div class="f">
            <label>Contraseña actual</label>
            <input id="me-current-password" type="password" autocomplete="current-password" placeholder="Requerida para cambiar contraseña">
            <span class="hint">Solo necesitas llenar este campo si vas a cambiar tu contraseña.</span>
          </div>
          <div class="f">
            <label>Nueva contraseña</label>
            <input id="me-new-password" type="password" autocomplete="new-password" placeholder="Letras y numeros, minimo 8">
          </div>
          <div class="f">
            <label>Confirmar contraseña</label>
            <input id="me-confirm-password" type="password" autocomplete="new-password" placeholder="Repite la nueva contraseña">
          </div>
        </div>
        <div class="ab">
          <button class="btn btn-primary" onclick="guardarMiPerfil()">Guardar cambios</button>
          <span id="me-profile-result" class="hint"></span>
        </div>
      </div>
    </div>

    <!-- ─── GENERAL ─────────────────────────────────────────── -->
    <div class="sec active" id="s-general" data-panel="engineering">
      <div class="ph"><h2>General tecnica</h2><p>Identidad tecnica, puerto y zona horaria de la sucursal</p></div>
      <div class="card">
        <div class="ct">Datos de la Sucursal</div>
        <div class="fg">
          <div class="f">
            <label>ID de Sucursal</label>
            <input id="g-id" placeholder="mi-restaurante">
            <span class="hint">Sin espacios ni acentos. Usado en URLs y logs.</span>
          </div>
          <div class="f">
            <label>Nombre para mostrar</label>
            <input id="g-nombre" placeholder="Mi Restaurante">
          </div>
          <div class="f">
            <label>Puerto del servidor</label>
            <input id="g-port" type="number" placeholder="3001">
            <span class="hint">Requiere reiniciar el servidor para aplicar.</span>
          </div>
          <div class="f">
            <label>Zona Horaria</label>
            <select id="g-tz">
              <option value="0">Sin ajuste manual — usar hora local de SQL/SoftRestaurant</option>
            </select>
          </div>
        </div>
        <div class="ab">
          <button class="btn btn-primary" onclick="guardar('general')">Guardar cambios</button>
        </div>
      </div>
    </div>

    <!-- ─── BASE DE DATOS ─────────────────────────────────────── -->
    <div class="sec" id="s-db" data-panel="engineering">
      <div class="ph"><h2>Base de Datos</h2><p>Conexion a SQL Server / SoftRestaurant</p></div>
      <div class="card">
        <div class="ct">SQL Server</div>
        <div class="fg">
          <div class="f">
            <label>Servidor \ Instancia</label>
            <input id="db-server" placeholder="localhost\NATIONALSOFT">
          </div>
          <div class="f">
            <label>Base de Datos</label>
            <input id="db-name" placeholder="softrestaurant11">
          </div>
          <div class="f">
            <label>Usuario</label>
            <input id="db-user" placeholder="sa">
          </div>
          <div class="f">
            <label>Contraseña</label>
            <input id="db-pass" type="password" placeholder="••••••••">
            <span class="hint">Deja vacio para no cambiar.</span>
          </div>
          <div class="f">
            <label>Puerto</label>
            <input id="db-port" type="number" placeholder="1433">
          </div>
          <div class="f">
            <label>Timeout conexión (ms)</label>
            <input id="db-timeout" type="number" placeholder="15000">
          </div>
        </div>
        <div class="ab">
          <button class="btn btn-primary" onclick="guardar('db')">Guardar cambios</button>
          <button class="btn btn-ghost" onclick="probarDB()">
            <span id="db-spin" style="display:none" class="spin"></span>
            Probar conexión
          </button>
          <span id="db-res"></span>
        </div>
      </div>
    </div>

    <!-- ─── ACCESO ─────────────────────────────────────────────── -->
    <div class="sec billing-panel" id="s-billing" data-panel="engineering">
      <div class="ph"><h2>Facturacion / Autofactura</h2><p>Conector tecnico al Portal de Autofactura. No visible para usuarios finales.</p></div>
      <div class="card">
        <div class="ct">Datos fiscales de la sucursal</div>
        <div class="billing-grid">
          <div class="f"><label>Proveedor</label><input value="Portal de Autofactura" readonly></div>
          <div class="f"><label>Ambiente</label><select id="bil-env"><option value="sandbox">Sandbox</option><option value="production">Produccion</option></select></div>
          <div class="f"><label>Activo</label><select id="bil-active"><option value="false">No</option><option value="true">Si</option></select></div>
          <div class="f full"><label>Base URL REST</label><input id="bil-url" placeholder="URL base entregada por el integrador"></div>
          <div class="f"><label>Modo autorizacion</label><select id="bil-auth-mode"><option value="basic">Usuario/contraseña HTTP Basic</option><option value="login">Login REST con token</option></select></div>
          <div class="f"><label>Usuario API</label><input id="bil-user" autocomplete="off"></div>
          <div class="f"><label>Contraseña API</label><input id="bil-pass" type="password" autocomplete="new-password" placeholder="Dejar vacio para conservar la actual"><span class="hint" id="bil-pass-mask">Sin contraseña guardada</span></div>
          <div class="f"><label>Ruta autorizacion</label><input id="bil-auth-path" placeholder="Solo si el integrador usa login REST"></div>
          <div class="f"><label>Campo token</label><input id="bil-token-field" placeholder="Opcional segun respuesta REST"></div>
        </div>
        <div class="ct" style="margin-top:18px">RFC emisor y sucursal</div>
        <div class="billing-grid">
          <div class="f"><label>RFC emisor</label><input id="bil-rfc"></div>
          <div class="f"><label>Razon social emisora</label><input id="bil-name"></div>
          <div class="f"><label>Regimen fiscal emisor</label><input id="bil-regime"></div>
          <div class="f"><label>CP emisor</label><input id="bil-zip"></div>
          <div class="f"><label>Certificado .cer / CDS</label><input id="bil-cer" type="file" accept=".cer,.cds"></div>
          <div class="f"><label>Llave privada .key</label><input id="bil-key" type="file" accept=".key"></div>
          <div class="f"><label>Contraseña llave</label><input id="bil-key-pass" type="password" autocomplete="new-password" placeholder="Dejar vacío para conservar"></div>
          <div class="f"><label>Sucursal externa</label><input id="bil-branch-ext"></div>
          <div class="f"><label>Nombre sucursal</label><input id="bil-branch-name"></div>
          <div class="f"><label>Codigo sucursal</label><input id="bil-branch-code"></div>
          <div class="f full"><label>Dominio Autofactura / QR</label><input id="bil-print-portal" placeholder="https://me-facturo.arkhonmonitor.com"></div>
          <div class="f full"><label>Logo portal Me-facturo</label><input id="bil-portal-logo" type="file" accept=".png,.jpg,.jpeg,.webp,.svg"><span class="hint" id="bil-portal-logo-hint">Sin logo personalizado</span></div>
        </div>
        <div class="billing-actions">
          <button class="btn btn-primary" onclick="guardarBilling()">Guardar conector</button>
          <button class="btn btn-ghost" onclick="probarBilling()">Probar conexión</button>
          <span id="bil-res"></span>
        </div>
      </div>
      <div class="card">
        <div class="ct">Estado Auto-Factura / QR</div>
        <div class="billing-grid">
          <div class="f"><label>Licencia</label><input id="bilqr-license" readonly></div>
          <div class="f"><label>Modulo facturacion</label><input id="bilqr-module" readonly></div>
          <div class="f"><label>QR Printing</label><input id="bilqr-status" readonly></div>
          <div class="f"><label>Carpeta PRN</label><input id="bilqr-folder" readonly></div>
          <div class="f"><label>Impresora salida POS</label><input id="bilqr-printer" readonly></div>
          <div class="f"><label>Seleccionar POS fisica</label><select id="bilqr-output-printer"><option value="">Cargando impresoras...</option></select></div>
          <div class="f"><label>Ticket Capture</label><input id="bilqr-capture-status" readonly></div>
          <div class="f"><label>Nombre captura</label><input id="bilqr-capture-printer" readonly></div>
          <div class="f"><label>Puerto captura</label><input id="bilqr-capture-port" readonly></div>
          <div class="f"><label>Driver captura</label><input id="bilqr-capture-driver" readonly></div>
          <div class="f full"><label>Archivo PRN esperado</label><input id="bilqr-capture-file" readonly></div>
          <div class="f"><label>Archivo esperado existe</label><input id="bilqr-capture-file-exists" readonly></div>
          <div class="f"><label>SoftRestaurant apunta a captura</label><input id="bilqr-softrestaurant-target" readonly></div>
          <div class="f full"><label>Ultimos PRN detectados</label><input id="bilqr-recent-prn" readonly></div>
          <div class="f"><label>Spooler Windows</label><input id="bilqr-spooler" readonly></div>
          <div class="f"><label>Ultimo ticket capturado</label><input id="bilqr-last-ticket" readonly></div>
          <div class="f"><label>Ultimo QR impreso</label><input id="bilqr-last-qr" readonly></div>
          <div class="f full"><label>Ultimo error</label><input id="bilqr-error" readonly></div>
        </div>
        <div class="billing-actions">
          <button class="btn btn-primary" onclick="guardarImpresoraBilling()">Guardar POS y activar</button>
          <button class="btn btn-ghost" onclick="cargarImpresorasBilling()">Refrescar impresoras</button>
          <button class="btn btn-ghost" onclick="repararTicketCapture()">Reparar Ticket Capture</button>
          <button class="btn btn-ghost" onclick="probarTicketCapture()">Probar captura</button>
          <button class="btn btn-ghost" onclick="reiniciarBillingQr()">Reiniciar QR Printing</button>
          <button class="btn btn-ghost" onclick="recargarLicenciaBilling()">Recargar licencia</button>
          <span id="bilqr-res"></span>
        </div>
      </div>
      <div class="card">
        <div class="ct">Validación SoftRestaurant</div>
        <p class="hint">En SoftRestaurant configure ARKHON Ticket Capture en: Cuentas (remisiones) y Notas de consumo. No configure la impresora física directamente si desea QR de autofactura.</p>
        <p class="hint">Después ejecute esta prueba e imprima una cuenta/remisión o nota de consumo real desde SoftRestaurant.</p>
        <div class="billing-grid">
          <div class="f"><label>Estado prueba real</label><input id="bilqr-sr-real-state" readonly></div>
          <div class="f full"><label>Acción recomendada</label><input id="bilqr-recommended" readonly></div>
        </div>
        <div class="billing-actions">
          <button class="btn btn-ghost" onclick="probarSoftRestaurantReal()">Iniciar prueba real SoftRestaurant</button>
          <span id="bilqr-sr-real-res"></span>
        </div>
      </div>
      <div class="card">
        <div class="ct">Sucursales configuradas</div>
        <div id="bil-branches" style="display:flex;gap:8px;flex-wrap:wrap"></div>
      </div>
    </div>

    <div class="sec" id="s-acceso" data-panel="engineering">
      <div class="ph"><h2>Acceso y Claves</h2><p>PIN de administrador, API Key y JWT Secret del sistema</p></div>
      <div class="card">
        <div class="ct">PIN de Administrador</div>
        <div class="fg c1">
          <div class="f">
            <label>PIN de acceso al panel de administración</label>
            <input id="sec-admin-pin" type="password" maxlength="8" placeholder="••••"
                   oninput="this.value=this.value.replace(/\D/g,'').slice(0,8)">
            <span class="hint" id="sec-admin-pin-hint">Mínimo 4 dígitos numéricos. Deja vacío para no cambiar.</span>
          </div>
          <div class="f">
            <label>Confirmar nuevo PIN</label>
            <input id="sec-admin-pin2" type="password" maxlength="8" placeholder="••••"
                   oninput="this.value=this.value.replace(/\D/g,'').slice(0,8)">
          </div>
        </div>
        <div class="ab">
          <button class="btn btn-primary" onclick="guardarAdminPin()">Cambiar PIN de admin</button>
          <span id="pin-res"></span>
        </div>
      </div>
      <div class="card">
        <div class="ct">Claves de Autenticacion</div>
        <div class="fg c1">
          <div class="f">
            <label>API Key Local (solo lectura)</label>
            <input id="sec-key" readonly>
            <span class="hint">Los HTMLs usan esta clave para comunicarse con el servidor. Se regenera con el boton de abajo.</span>
          </div>
          <div class="f">
            <label>JWT Secret (solo lectura)</label>
            <input id="sec-jwt" type="password" readonly>
            <span class="hint">Secreto de firma de tokens. Cambiar invalidará todas las sesiones activas.</span>
          </div>
        </div>
        <div class="ab">
          <button class="btn btn-danger" onclick="regenerarClaves()">Regenerar todas las claves</button>
          <span style="font-size:11px;color:var(--text-3)">Cerrará sesión en todos los dispositivos</span>
        </div>
      </div>
    </div>

    <!-- ─── TUNEL ──────────────────────────────────────────────── -->
    <div class="sec" id="s-tunel" data-panel="engineering">
      <div class="ph"><h2>Dominio / Tunel</h2><p>Acceso publico para uso desde celular o internet</p></div>
      <div class="card">
        <div class="ct">Dominio Publico</div>
        <div class="fg c1">
          <div class="f">
            <label>Dominio (sin https://)</label>
            <input id="tun-dom" placeholder="mi-negocio.arkhonmonitor.com">
            <span class="hint">Al guardar, CORS_ORIGINS se actualiza automaticamente. Deja vacio para solo acceso local.</span>
          </div>
          <div class="f">
            <label>CORS Permitidos (autocalculado)</label>
            <input id="tun-cors" readonly placeholder="http://localhost:3001,https://mi-dominio.com">
            <span class="hint">Se recalcula automaticamente al guardar el dominio.</span>
          </div>
        </div>
        <div class="ab">
          <button class="btn btn-primary" onclick="guardar('tunel')">Guardar cambios</button>
        </div>
      </div>
      <div class="card">
        <div class="ct">Servicio Cloudflare de Windows</div>
        <div id="cf-status" style="font-size:12px;color:var(--text-2);line-height:1.6">Detectando servicio...</div>
        <p style="font-size:11px;color:var(--text-3);margin-top:8px">
          ARKHON ya no solicita el token del tunel en pantalla. Si el servicio Cloudflare esta instalado en Windows,
          se usa como infraestructura existente; aqui solo se guarda la URL publica.
        </p>
      </div>
    </div>

    <!-- ─── CACHE ──────────────────────────────────────────────── -->
    <div class="sec" id="s-cache" data-panel="engineering">
      <div class="ph"><h2>Cache y Rendimiento</h2><p>Tiempo de vida de cada cache en milisegundos</p></div>
      <div class="card">
        <div class="ct">TTL por módulo</div>
        <div class="fg">
          <div class="f">
            <label>Snapshot (Dashboard principal)</label>
            <input id="c-snap" type="number" placeholder="4000">
            <span class="hint">Recomendado: 3000–8000 ms</span>
          </div>
          <div class="f">
            <label>Graficas</label>
            <input id="c-graf" type="number" placeholder="5000">
          </div>
          <div class="f">
            <label>Mesas</label>
            <input id="c-mesa" type="number" placeholder="1000">
          </div>
          <div class="f">
            <label>Cancelaciones</label>
            <input id="c-can" type="number" placeholder="1000">
          </div>
          <div class="f">
            <label>Productos</label>
            <input id="c-prod" type="number" placeholder="10000">
          </div>
          <div class="f">
            <label>Formas de Pago</label>
            <input id="c-pago" type="number" placeholder="10000">
          </div>
          <div class="f">
            <label>Metricas</label>
            <input id="c-met" type="number" placeholder="1000">
          </div>
          <div class="f">
            <label>Resumen del Mes</label>
            <input id="c-mes" type="number" placeholder="60000">
          </div>
        </div>
        <div class="ab">
          <button class="btn btn-primary" onclick="guardar('cache')">Guardar cambios</button>
          <button class="btn btn-ghost" onclick="limpiarCache()">Limpiar cache ahora</button>
        </div>
      </div>
    </div>

    <!-- ─── IA ──────────────────────────────────────────────────── -->
    <div class="sec" id="s-ia" data-panel="engineering">
      <div class="ph"><h2>Análisis avanzado</h2><p>Análisis de ventas y recomendaciones</p></div>
      <div class="card">
        <div class="ct">Proveedor de IA</div>
        <div class="fg">
          <div class="f">
            <label>Proveedor activo</label>
            <select id="ia-provider" onchange="actualizarProveedorIA()">
              <option value="openai">OpenAI · GPT-5.6 Terra</option>
              <option value="anthropic">Anthropic · Claude</option>
            </select>
            <span class="hint">El analisis avanzado usara el proveedor seleccionado.</span>
          </div>
          <div class="f" data-ai-provider="anthropic">
            <label>API Key de Anthropic</label>
            <input id="ia-key" placeholder="sk-ant-...">
            <span class="hint">Obtenla en console.anthropic.com/settings/keys — empieza con sk-ant-</span>
          </div>
          <div class="f" data-ai-provider="openai">
            <label>API Key de OpenAI</label>
            <input id="openai-key" type="password" autocomplete="new-password" placeholder="sk-..." oninput="seleccionarOpenAIAlCapturar()">
            <span class="hint">Llave de plataforma OpenAI. Se guarda solo en el servidor.</span>
          </div>
          <div class="f" data-ai-provider="anthropic">
            <label>Modelo Claude</label>
            <select id="ia-model">
              <option value="claude-sonnet-4-6">Claude Sonnet 4.6 (Recomendado — balance calidad/costo)</option>
              <option value="claude-haiku-4-5-20251001">Claude Haiku 4.5 (Economico — respuestas rapidas)</option>
              <option value="claude-opus-4-6">Claude Opus 4.6 (Premium — maximo detalle)</option>
            </select>
          </div>
          <div class="f" data-ai-provider="openai">
            <label>Modelo OpenAI</label>
            <select id="openai-model">
              <option value="gpt-5.6-terra">GPT-5.6 Terra (recomendado · inteligencia/costo)</option>
              <option value="gpt-5.6-sol">GPT-5.6 Sol (maxima capacidad)</option>
              <option value="gpt-5.6-luna">GPT-5.6 Luna (rapido y economico)</option>
            </select>
          </div>
        </div>
        <div class="ab">
          <button class="btn btn-primary" onclick="guardar('ia')">Guardar cambios</button>
          <button class="btn btn-ghost" onclick="probarIA()">
            <span id="ia-spin" style="display:none" class="spin"></span>
            Probar analisis
          </button>
          <span id="ia-res"></span>
        </div>
      </div>
    </div>

    <!-- ─── USUARIOS ───────────────────────────────────────────── -->
    <div class="sec" id="s-usuarios" data-panel="owner">
      <div class="ph">
        <h2>Administración del Dueño</h2>
        <p>Usuarios, roles, permisos y PIN operativos del negocio</p>
      </div>

      <!-- tarjetas de usuario -->
      <div class="usr-grid" id="usr-grid">
        <!-- se renderizan via JS -->
      </div>

      <!-- tabla de permisos -->
      <div class="card">
        <div class="ct">Permisos por Rol</div>
        <div class="perm-wrap">
        <table class="perm-table">
          <thead>
            <tr>
              <th style="width:40%">Modulo / Accion</th>
              <th style="text-align:center">
                <span class="badge bo">Dueño</span>
              </th>
              <th style="text-align:center">
                <span class="badge" style="background:var(--blue-lt);color:var(--blue)">Gerente</span>
              </th>
              <th style="text-align:center">
                <span class="badge bp">Aux. Contable</span>
              </th>
            </tr>
          </thead>
          <tbody id="perm-tbody">
            <!-- se renderizan via JS -->
          </tbody>
        </table>
        </div><!-- /perm-wrap -->
        <p style="font-size:10.5px;color:var(--text-3);margin-top:12px">
          Los permisos del Dueño son fijos y no pueden modificarse. Gerente y Aux. Contable son ajustables.
        </p>
      </div>
    </div>

    <!-- ─── ESTADO ─────────────────────────────────────────────── -->
    <div class="sec active security-section" id="s-seguridad" data-panel="owner">
      <div class="ph">
        <h2>Seguridad / Perfiles</h2>
        <p>Controla que puede ver y operar cada persona del restaurante</p>
      </div>

      <div class="security-scroll">
      <div class="security-module-tabs" role="tablist" aria-label="Seguridad">
        <button class="security-module-tab" type="button" data-security-tab="usuarios" onclick="showSecurityModule('usuarios')">Usuarios</button>
        <button class="security-module-tab" type="button" data-security-tab="perfiles" onclick="showSecurityModule('perfiles')">Perfiles</button>
      </div>
      <div class="security-top">
        <div class="card security-create-card">
          <div class="ct">Nuevo perfil personalizado</div>
          <div class="f"><label>Nombre</label><input id="seg-profile-name" placeholder="Ej: Auditor de facturacion"></div>
          <div class="f" style="margin-top:10px"><label>Descripción</label><input id="seg-profile-desc" placeholder="Alcance operativo del perfil"></div>
          <div class="ab"><button class="btn btn-primary" onclick="crearPerfilPersonalizado()">Crear perfil</button><span id="seg-profile-result"></span></div>
        </div>
        <div class="card security-user-card">
          <div class="ct">Usuario</div>
          <div class="fg c2">
            <div class="f"><label>Usuario</label><input id="seg-user-name" placeholder="usuario.local"></div>
            <div class="f"><label>Nombre visible</label><input id="seg-user-display" placeholder="Nombre del operador"></div>
            <div class="f"><label>Contraseña</label><input id="seg-user-password" type="password" autocomplete="new-password" placeholder="Letras y números"></div>
            <div class="f"><label>PIN operativo opcional</label><input id="seg-user-pin" type="password" maxlength="6" placeholder="4 a 6 dígitos"></div>
          </div>
          <div class="f" style="margin-top:10px"><label>Perfiles asignados</label><select id="seg-user-profiles" multiple size="8"></select></div>
          <div class="ab"><button class="btn btn-primary" onclick="guardarUsuarioSeguridad()">Guardar usuario</button><span id="seg-user-result"></span></div>
        </div>
      </div>

      <div class="card">
        <div class="ct">Perfiles</div>
        <div id="seg-profiles" class="security-card-list"></div>
      </div>

      <div class="card">
        <div class="ct">Usuarios</div>
        <div id="seg-users" class="security-users"></div>
      </div>
      </div>
    </div>

    <div class="sec" id="s-estado" data-panel="engineering">
      <div class="ph"><h2>Estado del Sistema</h2><p>Monitoreo en tiempo real</p></div>
      <div class="kpis">
        <div class="kpi"><div class="kv" id="k-uptime">—</div><div class="kl">Tiempo activo</div></div>
        <div class="kpi"><div class="kv" id="k-mem">—</div><div class="kl">RAM usada</div></div>
        <div class="kpi"><div class="kv" id="k-env">—</div><div class="kl">Entorno</div></div>
        <div class="kpi"><div class="kv" id="k-hora">—</div><div class="kl">Hora del servidor</div></div>
      </div>
      <div class="card">
        <div class="ct">Servicios</div>
        <div class="svc-row">
          <div><div class="svc-name">Servidor Node.js</div></div>
          <span class="badge bg">Activo</span>
        </div>
        <div class="svc-row">
          <div>
            <div class="svc-name">Base de Datos SQL Server</div>
            <div class="svc-sub" id="db-sub">Verificando...</div>
          </div>
          <span id="svc-db" class="badge bgr">...</span>
        </div>
        <div class="svc-row">
          <div><div class="svc-name">WebSocket (Socket.IO)</div></div>
          <span class="badge bg">Activo</span>
        </div>
        <div class="svc-row">
          <div><div class="svc-name">Cache en memoria</div></div>
          <span class="badge bg">Activo</span>
        </div>
        <div class="ab">
          <button class="btn btn-ghost" onclick="cargarEstado()">Actualizar</button>
          <button class="btn btn-danger" onclick="confirmarReinicio()">Reiniciar servidor</button>
        </div>
      </div>
    </div>

    <!-- ─── ACTUALIZAR VERSIÓN ──────────────────────────────── -->
    <div class="sec" id="s-actualizaciones" data-panel="owner">
    <div class="card" id="card-update" style="margin-bottom:16px">
      <div class="ph">
        <h2>Actualizar Versión</h2>
        <p>Descarga e instala la última versión de ARKHON automáticamente</p>
      </div>
      <div style="padding:16px 20px">
        <!-- Estado actual -->
        <div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;flex-wrap:wrap">
          <div class="kpi" style="min-width:140px">
            <div class="kv" id="u-version-actual" style="font-size:18px">—</div>
            <div class="kl">Versión instalada</div>
          </div>
          <div class="kpi" style="min-width:140px">
            <div class="kv" id="u-version-nueva" style="font-size:18px">—</div>
            <div class="kl">Versión disponible</div>
          </div>
          <div style="flex:1;min-width:200px">
            <div id="u-notas" style="font-size:12px;color:var(--text-2);line-height:1.5"></div>
          </div>
        </div>

        <!-- Progreso -->
        <div id="u-progreso" style="display:none;margin-bottom:12px">
          <div style="background:var(--bg);border-radius:8px;overflow:hidden;height:6px;margin-bottom:8px">
            <div id="u-barra" style="height:100%;background:var(--orange);width:0%;transition:width .5s"></div>
          </div>
          <div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:8px;font-size:11px;color:var(--text-2)">
            <span>Fase: <strong id="u-phase">-</strong></span>
            <span>Estado: <strong id="u-job-status">-</strong></span>
            <span>Job: <strong id="u-job-id">-</strong></span>
          </div>
          <div id="u-log" style="font-size:11px;color:var(--text-2);font-family:monospace;max-height:120px;overflow-y:auto;background:var(--bg);padding:8px;border-radius:6px"></div>
        </div>

        <!-- Botones -->
        <div class="ab">
          <button class="btn btn-ghost" onclick="verificarActualizacion()" id="u-btn-check">
            <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><line x1="16.5" y1="16.5" x2="21" y2="21"/></svg>
            Verificar actualizacion
          </button>
          <button class="btn btn-primary" onclick="aplicarActualizacion()" id="u-btn-apply" style="display:none">
            <svg viewBox="0 0 24 24"><path d="M12 3v12"/><polyline points="7 8 12 3 17 8"/><path d="M5 21h14"/></svg>
            Instalar actualizacion
          </button>
          <button class="btn btn-ghost" onclick="copiarDiagnosticoActualizador()" id="u-btn-diag" style="display:none">
            Copiar diagnostico
          </button>
        </div>
      </div>
    </div>
    </div>

    <!-- ─── LOGS ───────────────────────────────────────────────── -->
    <div class="sec" id="s-logs" data-panel="engineering">
      <div class="ph"><h2>Logs del Sistema</h2><p>Ultimas 50 entradas del servidor (mas reciente primero)</p></div>
      <div class="card">
        <div class="ct">Log en vivo</div>
        <div class="logbox" id="logbox">Cargando...</div>
        <div class="ab">
          <button class="btn btn-ghost" onclick="cargarLogs()">Actualizar logs</button>
        </div>
      </div>
    </div>

    <div class="sec" id="s-diagnostico" data-panel="engineering">
      <div class="ph"><h2>Diagnostico</h2><p>Revision tecnica rapida del servicio instalado</p></div>
      <div class="card">
        <div class="ct">Resumen tecnico</div>
        <div class="svc-row">
          <div><div class="svc-name">API ARKHON</div><div class="svc-sub">Usa /api/health para validar estado general</div></div>
          <button class="btn btn-ghost" onclick="cargarEstado()">Actualizar estado</button>
        </div>
        <div class="svc-row">
          <div><div class="svc-name">Base de Datos</div><div class="svc-sub" id="diag-db-sub">Usa la prueba de conexión existente</div></div>
          <button class="btn btn-ghost" onclick="probarDB()">Probar conexión</button>
        </div>
        <p style="font-size:11px;color:var(--text-3);margin-top:12px">Este espacio queda reservado para diagnósticos técnicos más completos sin exponerlos en la administración del dueño.</p>
      </div>
    </div>

    <div class="sec" id="s-licencia">
      <div class="ph"><h2>Licencia</h2><p>Activa ARKHON Monitor y consulta los módulos incluidos</p></div>
      <div class="card">
        <div class="ct">Estado de licencia</div>
        <div class="svc-row"><div class="svc-name">Estado</div><span class="badge bam" id="lic-estado">Cargando</span></div>
        <div class="svc-row"><div class="svc-name">Plan</div><span class="svc-sub" id="lic-plan">-</span></div>
        <div class="svc-row"><div class="svc-name">Negocio</div><span class="svc-sub" id="lic-negocio">-</span></div>
        <div class="svc-row"><div class="svc-name">Número de control</div><span class="svc-sub mono-value" id="lic-control">-</span></div>
        <div class="svc-row"><div class="svc-name">Sucursal</div><span class="svc-sub" id="lic-sucursal">-</span></div>
        <div class="svc-row"><div class="svc-name">Hardware ID de este equipo</div><span class="svc-sub mono-value" id="lic-hwid">-</span></div>
        <div class="svc-row"><div class="svc-name">Expira</div><span class="svc-sub" id="lic-expira">-</span></div>
        <div class="svc-row"><div class="svc-name">Validacion en linea</div><span class="svc-sub" id="lic-online">-</span></div>
        <div class="svc-row"><div class="svc-name">Módulos activos</div><span class="svc-sub" id="lic-modulos">-</span></div>
      </div>
      <div class="card">
        <div class="ct">Instalar licencia</div>
        <div class="fg c1">
          <div class="f">
            <label>Llave de licencia</label>
            <textarea id="lic-key" spellcheck="false" placeholder="Pega aqui tu licencia ARKHON5-..." style="min-height:120px;font-family:Consolas,monospace;font-size:11px"></textarea>
            <span class="hint">La licencia se guarda en este equipo. Si hay internet, ARKHON Cloud validara despues que siga activa.</span>
          </div>
        </div>
        <div class="ab">
          <button class="btn btn-primary" onclick="guardarLicencia()">Guardar licencia</button>
          <button class="btn btn-ghost" onclick="cargarLicencia()">Actualizar estado</button>
          <button class="btn btn-ghost" onclick="verificarLicenciaOnline()">Validar en linea</button>
        </div>
      </div>
      <div class="card">
        <div class="ct">Vincular con ARKHON Cloud</div>
        <p class="svc-sub" style="margin:-6px 0 14px">Usa las credenciales de control de tu licencia Cloud. No necesitas reinstalar el conector.</p>
        <div class="fg c2">
          <div class="f">
            <label for="cloud-control-number">Número de control</label>
            <input type="text" id="cloud-control-number" autocomplete="username" placeholder="Ej. ARK11...">
          </div>
          <div class="f">
            <label for="cloud-control-password">Contraseña de control</label>
            <input type="password" id="cloud-control-password" autocomplete="current-password" placeholder="Contraseña de control" onkeydown="if(event.key === 'Enter') vincularCloud()">
          </div>
        </div>
        <div class="ab">
          <button class="btn btn-primary" id="cloud-link-button" onclick="vincularCloud()">Vincular con ARKHON Cloud</button>
        </div>
      </div>
    </div>

  </main>
</div>

<!-- ─── MODAL EDITAR USUARIO ───────────────────────────────────── -->
<div class="modal-bg" id="modal-usr" onclick="cerrarModal(event)">
  <div class="modal" onclick="event.stopPropagation()">
    <div class="modal-title">
      <svg viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
      <span id="modal-usr-title">Editar Usuario</span>
    </div>
    <div class="fg c1" id="modal-usr-body">
      <div class="f">
        <label>Nombre completo</label>
        <input id="mu-nombre" placeholder="Ej: Carlos Gomez">
      </div>
      <div class="f" style="margin-top:10px">
        <label>PIN de acceso (4 dígitos)</label>
        <input id="mu-pin" type="password" maxlength="4" placeholder="••••"
               oninput="this.value=this.value.replace(/\D/g,'').slice(0,4)">
        <span class="hint">El PIN se usa para identificar al usuario al iniciar sesión. Mínimo 4 dígitos.</span>
      </div>
      <div class="f" style="margin-top:10px">
        <label>Rol asignado</label>
        <input id="mu-rol" readonly style="opacity:.6">
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn btn-ghost" onclick="document.getElementById('modal-usr').classList.remove('open')">Cancelar</button>
      <button class="btn btn-primary" onclick="guardarUsuario()">Guardar usuario</button>
    </div>
  </div>
</div>

<!-- ─── SCRIPTS ────────────────────────────────────────────────── -->
<script>
'use strict';

// ── Sidebar toggle (móvil/tablet) ─────────────────────────────
function toggleAdminSidebar() {
  const sb  = document.getElementById('admin-sidebar');
  const ov  = document.getElementById('sb-overlay-admin');
  const open = sb.classList.contains('open');
  sb.classList.toggle('open', !open);
  ov.classList.toggle('open', !open);
}
function closeAdminSidebar() {
  document.getElementById('admin-sidebar').classList.remove('open');
  document.getElementById('sb-overlay-admin').classList.remove('open');
}
let KEY = '';
let SECURITY_STATE = { permissions: [], profiles: [], users: [] };
let SECURITY_MODULE_READY = false;

function readEmbeddedAuth() {
  if (!location.hash || !location.hash.includes('arkhon_token=')) return;
  const params = new URLSearchParams(location.hash.slice(1));
  const token = params.get('arkhon_token');
  if (token) {
    KEY = token;
    try { sessionStorage.setItem('arkhon_token', token); } catch(_) {}
  }
  const nombre = params.get('arkhon_nombre');
  const rol = params.get('arkhon_rol');
  const permissions = params.get('arkhon_permissions');
  const permisos = params.get('arkhon_permisos');
  if (nombre) try { sessionStorage.setItem('arkhon_nombre', nombre); } catch(_) {}
  if (rol) try { sessionStorage.setItem('arkhon_rol', rol); } catch(_) {}
  if (permissions) try { sessionStorage.setItem('arkhon_permissions', permissions); } catch(_) {}
  if (permisos) try { sessionStorage.setItem('arkhon_permisos', permisos); } catch(_) {}
  try { history.replaceState(null, document.title, location.pathname + location.search); } catch(_) {}
}

function ensureSecurityModules() {
  if (SECURITY_MODULE_READY) return;
  const scroll = document.querySelector('#s-seguridad .security-scroll');
  const tabs = document.querySelector('.security-module-tabs');
  if (!scroll || !tabs) return;

  const usersModule = document.createElement('div');
  usersModule.id = 'seg-module-usuarios';
  usersModule.className = 'security-module';

  const profilesModule = document.createElement('div');
  profilesModule.id = 'seg-module-perfiles';
  profilesModule.className = 'security-module';

  const userCard = document.querySelector('.security-user-card');
  const usersCard = document.getElementById('seg-users')?.closest('.card');
  const createCard = document.querySelector('.security-create-card');
  const profilesCard = document.getElementById('seg-profiles')?.closest('.card');

  const usersGrid = document.createElement('div');
  usersGrid.className = 'security-module-grid';
  if (userCard) usersGrid.appendChild(userCard);
  if (usersCard) usersGrid.appendChild(usersCard);
  usersModule.appendChild(usersGrid);

  const profilesGrid = document.createElement('div');
  profilesGrid.className = 'security-module-grid';
  if (createCard) profilesGrid.appendChild(createCard);
  if (profilesCard) profilesGrid.appendChild(profilesCard);
  profilesModule.appendChild(profilesGrid);

  tabs.after(usersModule, profilesModule);
  const legacyTop = document.querySelector('.security-top');
  if (legacyTop && legacyTop.children.length === 0) legacyTop.remove();
  SECURITY_MODULE_READY = true;
  showSecurityModule(new URLSearchParams(window.location.search).get('security') || 'usuarios');
}

function showSecurityModule(moduleName) {
  ensureSecurityModules();
  const selected = moduleName === 'perfiles' ? 'perfiles' : 'usuarios';
  document.querySelectorAll('.security-module').forEach(module => {
    module.classList.toggle('active', module.id === `seg-module-${selected}`);
  });
  document.querySelectorAll('.security-module-tab').forEach(tab => {
    tab.classList.toggle('active', tab.dataset.securityTab === selected);
  });
}

function currentPermissionMap() {
  const raw = sessionStorage.getItem('arkhon_permissions') || sessionStorage.getItem('arkhon_permisos') || '[]';
  try {
    const parsed = JSON.parse(raw);
    if (Array.isArray(parsed)) return Object.fromEntries(parsed.map(code => [code, true]));
    return parsed && typeof parsed === 'object' ? parsed : {};
  } catch (_) {
    return {};
  }
}

function hasPermission(code) {
  if (!code) return true;
  const map = currentPermissionMap();
  if (Object.keys(map).length === 0) return true;
  return Boolean(map[code] || map['*']);
}

function applyPermissionVisibility() {
  const sectionPermissions = {
    'mi-perfil': null,
    seguridad: ['security.profiles.view', 'security.users.view'],
    licencia: ['billing.configure'],
    billing: ['billing.view'],
    'billing-qr': ['autofactura.qr.view', 'billing.view'],
    actualizaciones: ['config.edit'],
    estado: ['diagnostics.view'],
    logs: ['logs.view'],
    general: ['config.view']
  };
  document.querySelectorAll('.sb-item[data-sec]').forEach(el => {
    const required = sectionPermissions[el.dataset.sec];
    if (!required || required.some(hasPermission)) return;
    el.classList.add('mode-hidden');
  });
}

function firstAllowedSection(preferred) {
  const visible = Array.from(document.querySelectorAll(`.sb-item[data-panel="${PANEL_MODE}"][data-sec]`))
    .filter(el => !el.classList.contains('mode-hidden'));
  if (preferred && visible.some(el => el.dataset.sec === preferred)) return preferred;
  return visible[0]?.dataset.sec || null;
}
const ENGINEERING_SESSION_KEY = 'arkhon_engineering_access';
let ENGINEERING_SECRET = '';
let ENGINEERING_EMAIL = '';
const PANEL_MODE = getPanelMode();

function getPanelMode() {
  const p = window.location.pathname.toLowerCase();
  return p === '/arkhon-engineering' ? 'engineering' : 'owner';
}

function validateEngineeringAccess(email, password) {
  return String(email || '').trim().length > 3 && String(password || '').trim().length > 0;
}

function hasEngineeringAccess() {
  return sessionStorage.getItem(ENGINEERING_SESSION_KEY) === 'granted' && Boolean(ENGINEERING_SECRET) && Boolean(ENGINEERING_EMAIL);
}

function grantEngineeringAccess(email, secret) {
  sessionStorage.setItem(ENGINEERING_SESSION_KEY, 'granted');
  ENGINEERING_EMAIL = String(email || '').trim().toLowerCase();
  ENGINEERING_SECRET = String(secret || '').trim();
}

function clearEngineeringAccess() {
  sessionStorage.removeItem(ENGINEERING_SESSION_KEY);
  ENGINEERING_EMAIL = '';
  ENGINEERING_SECRET = '';
}

function canAccessArkhonEngineering(user) {
  return hasPermission('diagnostics.view') || hasPermission('config.edit');
}

function canAccessOwnerAdmin(user) {
  return hasPermission('security.users.view') || hasPermission('security.profiles.view');
}

function showEngineeringGate() {
  document.querySelector('.layout')?.classList.add('mode-hidden');
  document.getElementById('engineering-gate')?.classList.remove('mode-hidden');
  document.getElementById('engineering-email')?.focus();
}

function hideEngineeringGate() {
  document.querySelector('.layout')?.classList.remove('mode-hidden');
  document.getElementById('engineering-gate')?.classList.add('mode-hidden');
}

async function submitEngineeringAccess() {
  const input = document.getElementById('engineering-key');
  const emailInput = document.getElementById('engineering-email');
  const err = document.getElementById('engineering-key-error');
  const secret = String(input?.value || '').trim();
  const email = String(emailInput?.value || '').trim().toLowerCase();
  if (!validateEngineeringAccess(email, secret)) {
    if (err) err.textContent = 'Ingresa correo y contraseña.';
    return;
  }
  if (err) err.textContent = 'Validando acceso tecnico...';
  try {
    const d = await api('/billing/engineering-access', {
      method: 'POST',
      headers: { 'X-Arkhon-Engineering-Email': email, 'X-Arkhon-Engineering-Password': secret },
      body: JSON.stringify({ email, password: secret }),
    }, { noRedirect: true });
    if (d.ok) {
      grantEngineeringAccess(email, secret);
      hideEngineeringGate();
      applyPanelMode();
      showSec('general');
      return;
    }
  } catch (_) {}
  if (err) err.textContent = 'Correo o contraseña incorrectos.';
  if (input) {
    input.value = '';
    input.focus();
  }
}

document.getElementById('engineering-key')?.addEventListener('keydown', e => {
  if (e.key === 'Enter') submitEngineeringAccess();
});

function applyPanelMode() {
  const isEngineering = PANEL_MODE === 'engineering';
  document.documentElement.dataset.panelMode = PANEL_MODE;
  document.getElementById('panel-subtitle').textContent = isEngineering
    ? '/ Arkhon Engineering'
    : '/ Administración';

  document.querySelectorAll('[data-panel]').forEach(el => {
    el.classList.toggle('mode-hidden', el.dataset.panel !== PANEL_MODE);
  });

  document.querySelectorAll('.sec').forEach(sec => {
    sec.classList.remove('active');
  });

  if (isEngineering && !hasEngineeringAccess()) {
    showEngineeringGate();
  }
}

// ── Datos de usuarios (persistidos via /api/admin/config) ────────
const ROL_LABELS = {dueno:'Dueño', gerente:'Gerente', aux:'Aux. Contable'};
const ROL_INITIALS= {dueno:'D', gerente:'G', aux:'A'};

// Permisos base — dueno siempre tiene todo
const PERMISOS_DEF = [
  {key:'ver_dashboard',   label:'Ver Dashboard',           dueno:true,  gerente:true,  aux:true},
  {key:'ver_mesas',       label:'Ver Mesas',               dueno:true,  gerente:true,  aux:true},
  {key:'ver_graficas',    label:'Ver Graficas y Analisis', dueno:true,  gerente:true,  aux:false},
  {key:'ver_meseros',     label:'Ver Meseros',             dueno:true,  gerente:true,  aux:false},
  {key:'ver_turnos',      label:'Ver Turnos',              dueno:true,  gerente:true,  aux:true},
  {key:'ver_reportes',    label:'Ver Reportes (Corte X/Z)',dueno:true,  gerente:true,  aux:true},
  {key:'descargar_pdf',   label:'Descargar PDF',           dueno:true,  gerente:true,  aux:true},
  {key:'ver_gastos',      label:'Ver Gastos',              dueno:true,  gerente:true,  aux:true},
  {key:'registrar_gastos',label:'Registrar Gastos',        dueno:true,  gerente:true,  aux:false},
  {key:'ver_cancelaciones',label:'Ver Cancelaciones',      dueno:true,  gerente:true,  aux:false},
  {key:'ver_retiros',     label:'Ver Retiros de Caja',     dueno:true,  gerente:true,  aux:true},
  {key:'usar_ia',         label:'Usar Análisis avanzado',     dueno:true,  gerente:true,  aux:false},
  {key:'acceder_admin',   label:'Acceder a Admin',         dueno:true,  gerente:false, aux:false},
  {key:'configurar_metas',label:'Configurar Metas',        dueno:true,  gerente:true,  aux:false},
  {key:'billing.dashboard.view',label:'Facturacion: Dashboard',   dueno:true,  gerente:true,  aux:false},
  {key:'billing.tickets.view',label:'Facturacion: Tickets',       dueno:true,  gerente:true,  aux:false},
  {key:'billing.invoices.view',label:'Facturacion: Facturas',     dueno:true,  gerente:true,  aux:false},
  {key:'billing.invoices.create',label:'Facturacion: Timbrar',    dueno:true,  gerente:false, aux:false},
  {key:'billing.invoices.cancel',label:'Facturacion: Cancelar',   dueno:true,  gerente:false, aux:false},
  {key:'billing.global.create',label:'Facturacion: Global',       dueno:true,  gerente:false, aux:false},
  {key:'billing.manual.create',label:'Facturacion: Sin ticket',   dueno:true,  gerente:false, aux:false},
  {key:'billing.reports.view',label:'Facturacion: Reportes',      dueno:true,  gerente:true,  aux:false},
];

let usuarios = {
  dueno:   { nombre:'', pin:'', rol:'dueno',   permisos: buildPerms('dueno')   },
  gerente: { nombre:'', pin:'', rol:'gerente', permisos: buildPerms('gerente') },
  aux:     { nombre:'', pin:'', rol:'aux',     permisos: buildPerms('aux')     },
};
let editandoRol = null;

function buildPerms(rol) {
  const p = {};
  PERMISOS_DEF.forEach(d => { p[d.key] = d[rol]; });
  return p;
}

// ── Init ─────────────────────────────────────────────────────────
(async () => {
  applyPanelMode();
  readEmbeddedAuth();

  // 1. Intentar renovar token usando la cookie de refresh (funciona remoto y local)
  if (!KEY) {
    try {
      const r = await fetch('/api/auth/refresh', { method: 'POST', credentials: 'include' });
      const d = await r.json();
      if (d.ok && d.token) KEY = d.token;
    } catch(_) {}
  }

  // 2. Token de localStorage (set por login.html cuando admin inicia sesión via PIN)
  //    IMPORTANTE: leerlo pero NO borrarlo aquí — se borra solo después de usarlo exitosamente
  if (!KEY) {
    const lsToken = localStorage.getItem('arkhon_admin_token');
    if (lsToken) { KEY = lsToken; }
  }

  // 3. Fallback legacy: clave arkhon_token (compatibilidad con login.html antiguo)
  if (!KEY) {
    const lsToken = localStorage.getItem('arkhon_token');
    if (lsToken) { KEY = lsToken; }
  }

  if (!KEY) {
    const ssToken = sessionStorage.getItem('arkhon_token');
    if (ssToken) { KEY = ssToken; }
  }

  // 4. Fallback: local-token (solo localhost)
  if (!KEY) {
    try {
      const r = await fetch('/api/local-token');
      const d = await r.json();
      if (d.ok) KEY = d.token;
    } catch(_) {}
  }

  // 5. Sin token → mostrar pantalla de PIN de admin en lugar de redirigir
  if (!KEY) {
    mostrarPinAdmin();
    return;
  }

  // Token obtenido — limpiar localStorage para seguridad
  localStorage.removeItem('arkhon_admin_token');
  localStorage.removeItem('arkhon_token');

  if (PANEL_MODE === 'engineering' && !hasEngineeringAccess()) {
    showEngineeringGate();
    return;
  }

  hideEngineeringGate();
  applyPermissionVisibility();

  if (PANEL_MODE === 'owner') {
    const requestedSection = new URLSearchParams(window.location.search).get('tab') || 'seguridad';
    const initialSection = firstAllowedSection(requestedSection);
    if (!initialSection) {
      toast('Tu perfil no tiene secciones administrativas disponibles');
      return;
    }
    if (initialSection === 'seguridad') {
      cargarUsuarios();
      await cargarSeguridad();
    }
    showSec(initialSection);
    return;
  }

  await cargarConfig();
  await cargarSeguridad();
  showSec('general');
  initActualizadorResumen();
})();

// ── PIN de admin inline (cuando no hay sesión activa) ─────────────
function mostrarPinAdmin() {
  document.body.innerHTML = `
    <div style="min-height:100vh;background:#0f172a;display:flex;align-items:center;justify-content:center;font-family:'DM Sans',sans-serif;">
      <div style="background:#1e293b;border-radius:16px;padding:32px 28px;width:100%;max-width:340px;box-shadow:0 24px 64px rgba(0,0,0,.4);">
        <div style="text-align:center;margin-bottom:24px;">
          <div style="background:#f97316;width:48px;height:48px;border-radius:12px;display:inline-flex;align-items:center;justify-content:center;margin-bottom:12px;">
            <svg viewBox="0 0 24 24" style="width:26px;height:26px;fill:none;stroke:#fff;stroke-width:2.2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
          </div>
          <div style="color:#fff;font-size:18px;font-weight:700;">Panel de Administración</div>
          <div style="color:#94a3b8;font-size:12px;margin-top:4px;">Ingresa tu PIN para continuar</div>
        </div>
        <div id="adm-dots" style="display:flex;justify-content:center;gap:10px;margin-bottom:20px;">
          <div class="adm-dot" style="width:12px;height:12px;border-radius:50%;background:#334155;border:2px solid #475569;transition:.15s;"></div>
          <div class="adm-dot" style="width:12px;height:12px;border-radius:50%;background:#334155;border:2px solid #475569;transition:.15s;"></div>
          <div class="adm-dot" style="width:12px;height:12px;border-radius:50%;background:#334155;border:2px solid #475569;transition:.15s;"></div>
          <div class="adm-dot" style="width:12px;height:12px;border-radius:50%;background:#334155;border:2px solid #475569;transition:.15s;"></div>
        </div>
        <div id="adm-err" style="color:#f87171;font-size:12px;text-align:center;min-height:16px;margin-bottom:12px;"></div>
        <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;">
          ${[1,2,3,4,5,6,7,8,9,'',0,'⌫'].map(n => `
            <button onclick="admPin('${n}')" style="padding:14px;background:#0f172a;border:1.5px solid #334155;border-radius:10px;color:#fff;font-size:18px;font-weight:600;cursor:pointer;transition:.12s;" 
              onmouseover="this.style.background='#1e3a5f'" onmouseout="this.style.background='#0f172a'"
              ${n===''?'style="visibility:hidden"':''}>
              ${n}
            </button>`).join('')}
        </div>
      </div>
    </div>`;
  window._admBuf = '';
}

async function admPin(n) {
  if (n === '⌫') { window._admBuf = window._admBuf.slice(0,-1); }
  else if (n !== '') { if(window._admBuf.length >= 4) return; window._admBuf += n; }
  // Actualizar dots
  document.querySelectorAll('.adm-dot').forEach((d,i) => {
    d.style.background = i < window._admBuf.length ? '#f97316' : '#334155';
    d.style.borderColor = i < window._admBuf.length ? '#f97316' : '#475569';
    d.style.transform = i < window._admBuf.length ? 'scale(1.2)' : 'scale(1)';
  });
  if (window._admBuf.length < 4) return;
  // Enviar PIN
  const pin = window._admBuf;
  window._admBuf = '';
  document.querySelectorAll('.adm-dot').forEach(d => { d.style.background='#334155'; d.style.borderColor='#475569'; d.style.transform='scale(1)'; });
  try {
    const r = await fetch('/api/auth/login-admin', {
      method: 'POST', credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ pin }),
    });
    const d = await r.json();
    if (d.ok && d.token) {
      KEY = d.token;
      // Guardar con clave consistente para que el init lo encuentre tras el reload
      localStorage.setItem('arkhon_admin_token', d.token);
      // Recargar la página para inicializar admin correctamente
      location.reload();
    } else {
      document.getElementById('adm-err').textContent = d.error || 'PIN incorrecto';
      setTimeout(() => { const el = document.getElementById('adm-err'); if(el) el.textContent=''; }, 2000);
    }
  } catch(e) {
    const el = document.getElementById('adm-err');
    if(el) el.textContent = 'Error de conexión';
  }
}

// ── Navegacion ───────────────────────────────────────────────────
document.querySelectorAll('.sb-item').forEach(el => {
  el.addEventListener('click', () => {
    document.querySelectorAll('.sb-item').forEach(x => x.classList.remove('active'));
    el.classList.add('active');
    showSec(el.dataset.sec, el);
    // Cerrar sidebar en móvil/tablet al seleccionar sección
    closeAdminSidebar();
  });
});

function showSec(id, activeEl) {
  const sectionPermissions = {
    'mi-perfil': null,
    seguridad: ['security.profiles.view', 'security.users.view'],
    licencia: ['billing.configure'],
    billing: ['billing.view'],
    'billing-qr': ['autofactura.qr.view', 'billing.view'],
    actualizaciones: ['config.edit'],
    estado: ['diagnostics.view'],
    logs: ['logs.view'],
    general: ['config.view']
  };
  const required = sectionPermissions[id];
  if (required && !required.some(hasPermission)) {
    toast('No tienes permiso para abrir esta seccion');
    const fallback = firstAllowedSection();
    if (fallback && fallback !== id) {
      showSec(fallback);
    }
    return;
  }
  document.querySelectorAll('.sec').forEach(s => s.classList.remove('active'));
  const sec = document.getElementById('s-' + id);
  if (sec) sec.classList.add('active');
  // sync sidebar
  document.querySelectorAll('.sb-item').forEach(x => x.classList.remove('active'));
  if (activeEl) {
    activeEl.classList.add('active');
  } else {
    const first = Array.from(document.querySelectorAll('.sb-item'))
      .find(x => x.dataset.sec === id && !x.classList.contains('mode-hidden'));
    if (first) first.classList.add('active');
  }
  if (id === 'estado') cargarEstado();
  if (id === 'logs')   cargarLogs();
  if (id === 'seguridad') cargarSeguridad();
  if (id === 'mi-perfil') cargarMiPerfil();
  if (id === 'actualizaciones') initActualizadorResumen();
  if (id === 'billing') { simplifyBillingPanel(); cargarBilling(); }
  if (id === 'licencia') cargarLicencia();
}

// ── Licencia ─────────────────────────────────────────────────────────
function escapeHtml(value) {
  return String(value || '').replace(/[&<>"']/g, ch => ({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;'
  }[ch]));
}

async function cargarSeguridad() {
  ensureSecurityModules();
  const [profilesResp, usersResp] = await Promise.all([
    api('/security/profiles').catch(err => ({ ok: false, error: err.message })),
    api('/security/users').catch(err => ({ ok: false, error: err.message }))
  ]);
  const fallbackPermissions = !profilesResp.permissions?.length
    ? await api('/security/permissions').catch(() => ({ permissions: [] }))
    : { permissions: profilesResp.permissions };
  SECURITY_STATE = {
    permissions: profilesResp.ok === false ? SECURITY_STATE.permissions : (profilesResp.permissions || fallbackPermissions.permissions || SECURITY_STATE.permissions || []),
    profiles: profilesResp.ok === false ? SECURITY_STATE.profiles : (profilesResp.profiles || SECURITY_STATE.profiles || []),
    users: usersResp.ok === false ? SECURITY_STATE.users : (usersResp.users || SECURITY_STATE.users || [])
  };
  renderSeguridad();
  const result = document.getElementById('seg-profile-result');
  if (result && profilesResp.ok === false) result.textContent = profilesResp.error || 'No se pudieron cargar perfiles';
}

function customerProfileName(profileOrName) {
  const source = typeof profileOrName === 'string'
    ? profileOrName
    : (profileOrName?.name || profileOrName?.id || '');
  const value = String(source || '').trim();
  const key = value.toLowerCase();
  if (key.includes('owner')) return 'Administrador';
  if (key.includes('gerente')) return 'Gerencia';
  if (key.includes('auxiliar') || key === 'aux') return 'Auxiliar';
  if (key.includes('contador') || key.includes('contabilidad')) return 'Contabilidad';
  if (key.includes('soporte')) return 'Soporte';
  if (key.includes('sololectura') || key.includes('solo lectura')) return 'Consulta';
  return value || 'Perfil';
}

function customerUserName(user) {
  const display = String(user?.display_name || '').trim();
  if (display) return customerProfileName(display);
  return customerProfileName(user?.username || '');
}

function customerPermissionModule(module) {
  const key = String(module || '').toLowerCase();
  const names = {
    admin: 'Administracion',
    billing: 'Facturacion',
    reports: 'Reportes',
    reportes: 'Reportes',
    inventory: 'Inventario',
    inventario: 'Inventario',
    sales: 'Ventas',
    ventas: 'Ventas',
    users: 'Seguridad',
    seguridad: 'Seguridad'
  };
  return names[key] || (module ? String(module) : 'Operacion');
}

function modulesForProfile(profile) {
  const selected = new Set(profile?.permission_codes || []);
  const modules = new Set();
  for (const permission of SECURITY_STATE.permissions || []) {
    if (selected.has(permission.code)) modules.add(customerPermissionModule(permission.module));
  }
  return [...modules].sort();
}

function customerProfileBadge(profile) {
  return profile?.is_system_template
    ? '<span class="badge bp">Protegido</span>'
    : '<span class="badge bg">Personalizado</span>';
}

function renderSeguridad() {
  const profileOptions = SECURITY_STATE.profiles
    .map(p => `<option value="${escapeHtml(p.id)}">${escapeHtml(customerProfileName(p))}</option>`)
    .join('');
  const profileSelect = document.getElementById('seg-user-profiles');
  if (profileSelect) {
    profileSelect.innerHTML = profileOptions;
    if (location.pathname.startsWith('/admin-setup') && !Array.from(profileSelect.options).some(opt => opt.selected)) {
      const ownerOption = Array.from(profileSelect.options).find(opt => opt.value === 'owner');
      if (ownerOption) ownerOption.selected = true;
    }
  }

  const profiles = document.getElementById('seg-profiles');
  if (profiles) {
    profiles.innerHTML = SECURITY_STATE.profiles.map(profile => {
      const permissions = new Set(profile.permission_codes || []);
      const modules = modulesForProfile(profile);
      const moduleSummary = modules.length
        ? modules.map(module => `<span class="badge bg">${escapeHtml(module)}</span>`).join(' ')
        : '<span class="hint">Sin modulos asignados</span>';
      const rows = SECURITY_STATE.permissions.map(permission => `
        <label class="security-permission">
          <input type="checkbox" data-profile-permission="${escapeHtml(profile.id)}" value="${escapeHtml(permission.code)}" ${permissions.has(permission.code) ? 'checked' : ''} ${profile.is_system_template ? 'disabled' : ''}>
          <span class="security-permission-main">
            <span class="security-permission-title">${escapeHtml(permission.label)}</span>
            <span class="security-permission-help">${escapeHtml(customerPermissionModule(permission.module))}</span>
          </span>
        </label>
      `).join('');
      return `
        <details class="security-profile" ${profile.is_system_template ? '' : 'open'}>
          <summary><span class="security-profile-title">${escapeHtml(customerProfileName(profile))}</span> ${customerProfileBadge(profile)}</summary>
          <p class="hint">${escapeHtml(profile.description || '')}</p>
          <div class="security-user-profiles">${moduleSummary}</div>
          <div class="security-permissions">${rows}</div>
          ${profile.is_system_template ? '<p class="hint">Perfil protegido: no editable.</p>' : `<button class="btn btn-primary" onclick="guardarPerfilSeguridad('${escapeHtml(profile.id)}')">Guardar permisos</button>`}
        </details>
      `;
    }).join('');
  }

  const users = document.getElementById('seg-users');
  if (users) {
    const profileName = id => {
      const profile = SECURITY_STATE.profiles.find(p => p.id === id);
      return profile ? customerProfileName(profile) : id;
    };
    users.innerHTML = SECURITY_STATE.users.map(user => `
      <div class="security-user-row">
        <div>
          <div class="security-user-name">${escapeHtml(customerUserName(user))}</div>
          <div class="security-user-profiles">${escapeHtml((user.profile_ids || []).map(profileName).join(', ') || 'Sin perfil asignado')}</div>
        </div>
        <span class="badge ${user.active === false ? 'br' : 'bg'}">${user.active === false ? 'Inactivo' : 'Activo'}</span>
      </div>
    `).join('');
  }
}

async function crearPerfilPersonalizado() {
  const name = document.getElementById('seg-profile-name').value.trim();
  const description = document.getElementById('seg-profile-desc').value.trim();
  const result = document.getElementById('seg-profile-result');
  if (!name) { result.textContent = 'Nombre requerido'; return; }
  const response = await api('/security/profiles', {
    method: 'POST',
    body: JSON.stringify({ name, description, permission_codes: [] })
  });
  if (!response.ok) {
    result.textContent = response.error || 'No se pudo crear el perfil';
    return;
  }
  document.getElementById('seg-profile-name').value = '';
  document.getElementById('seg-profile-desc').value = '';
  result.textContent = 'Perfil creado';
  await cargarSeguridad();
  if (response.profile && !SECURITY_STATE.profiles.some(p => p.id === response.profile.id)) {
    SECURITY_STATE.profiles.push(response.profile);
    renderSeguridad();
  }
}

async function guardarPerfilSeguridad(profileId) {
  const profile = SECURITY_STATE.profiles.find(p => p.id === profileId);
  if (!profile || profile.is_system_template) return;
  const selector = `[data-profile-permission="${CSS.escape(profileId)}"]:checked`;
  const permission_codes = Array.from(document.querySelectorAll(selector)).map(el => el.value);
  await api('/security/profiles', {
    method: 'POST',
    body: JSON.stringify({ id: profileId, name: profile.name, description: profile.description, permission_codes })
  });
  toast('Perfil actualizado');
  await cargarSeguridad();
}

async function guardarUsuarioSeguridad() {
  const username = document.getElementById('seg-user-name').value.trim();
  const display_name = document.getElementById('seg-user-display').value.trim();
  const password = document.getElementById('seg-user-password').value;
  const pin = document.getElementById('seg-user-pin').value.trim();
  const profile_ids = Array.from(document.getElementById('seg-user-profiles').selectedOptions).map(opt => opt.value);
  const result = document.getElementById('seg-user-result');
  if (!username || !display_name || profile_ids.length === 0) {
    result.textContent = 'Usuario, nombre y perfil son requeridos';
    return;
  }
  if (location.pathname.startsWith('/admin-setup') && !password && !pin) {
    result.textContent = 'Configura contrasena o PIN para el usuario administrador inicial';
    return;
  }
  const saved = await api('/security/users', {
    method: 'POST',
    body: JSON.stringify({ username, display_name, password, pin, profile_ids })
  });
  if (!saved.ok) {
    result.textContent = saved.error || 'No se pudo guardar el usuario';
    return;
  }
  ['seg-user-name', 'seg-user-display', 'seg-user-password', 'seg-user-pin'].forEach(id => { document.getElementById(id).value = ''; });
  result.textContent = 'Usuario guardado';
  await cargarSeguridad();
  try {
    const completed = await api('/admin/setup-complete', { method: 'POST', body: JSON.stringify({ source: 'security_profiles' }) }, { noRedirect: true });
    if (completed.ok) {
      result.textContent = 'Configuracion lista. Abriendo inicio de sesion...';
      window.location.href = completed.redirect || '/login';
    } else {
      result.textContent = completed.error || 'Falta un usuario administrador con credencial valida';
    }
  } catch (_) {}
}

function badgeLicencia(valida, trial) {
  if (valida) return `<span class="badge ${trial ? 'bam' : 'bg'}">${trial ? 'Prueba' : 'Activa'}</span>`;
  return '<span class="badge br">Requiere licencia</span>';
}

function textoValidacionOnline(online) {
  if (!online) return 'Pendiente';
  if (online.ok) return `${online.status || 'active'} - ${online.checked_at || ''}`;
  if (online.reason === 'CONNECTOR_IDENTITY_INCOMPLETE') return online.message || 'Falta identidad Cloud del conector';
  return online.message || online.reason || 'Sin conexión';
}

async function cargarLicencia() {
  try {
    const d = await api('/admin/licencia', {}, { noRedirect: true });
    if (!d.ok) throw new Error(d.error || 'No se pudo leer la licencia');
    document.getElementById('lic-estado').outerHTML = badgeLicencia(d.valida, d.trial).replace('<span', '<span id="lic-estado"');
    document.getElementById('lic-plan').textContent = d.plan || '-';
    document.getElementById('lic-negocio').textContent = d.negocio || '-';
    document.getElementById('lic-control').textContent = d.control_number || '-';
    document.getElementById('lic-sucursal').textContent = [d.sucursal, d.branch_code].filter(Boolean).join(' · ') || '-';
    document.getElementById('lic-hwid').textContent = d.hwid || '-';
    document.getElementById('lic-expira').textContent = d.expira || '-';
    document.getElementById('lic-online').textContent = textoValidacionOnline(d.online);
    document.getElementById('lic-modulos').textContent = (d.modulos || []).filter(x => x !== '*').join(', ') || '-';
  } catch (e) {
    alerta('Error al cargar licencia: ' + e.message, 'err');
  }
}

async function vincularCloud() {
  const controlNumber = document.getElementById('cloud-control-number').value.trim();
  const controlPassword = document.getElementById('cloud-control-password').value;
  if (!controlNumber || !controlPassword) {
    alerta('Debes ingresar el número de control y la contraseña.', 'err');
    return;
  }
  const button = document.getElementById('cloud-link-button');
  try {
    button.disabled = true;
    button.textContent = 'Vinculando...';
    const d = await api('/api/license/provision', {
      method: 'POST',
      body: JSON.stringify({ controlNumber, controlPassword })
    }, { noRedirect: true });
    if (!d.ok) throw new Error(d.error || 'No se pudo vincular con la nube.');
    document.getElementById('cloud-control-password').value = '';
    alerta('Conectado a ARKHON Cloud exitosamente.', 'ok');
    await cargarLicencia();
  } catch (e) {
    alerta('Error de vinculación: ' + e.message, 'err');
  } finally {
    button.disabled = false;
    button.textContent = 'Vincular con ARKHON Cloud';
  }
}

async function guardarLicencia() {
  const licenseKey = document.getElementById('lic-key').value.trim();
  if (!licenseKey) {
    alerta('Pega una licencia antes de guardar.', 'err');
    return;
  }
  try {
    const d = await api('/admin/licencia', {
      method: 'POST',
      body: JSON.stringify({ license_key: licenseKey }),
    }, { noRedirect: true });
    if (!d.ok) throw new Error(d.error || d.reason || 'No se pudo guardar la licencia');
    document.getElementById('lic-key').value = '';
    alerta('Licencia activada correctamente.', 'ok');
    await cargarLicencia();
  } catch (e) {
    alerta('Licencia no aceptada: ' + e.message, 'err');
  }
}

async function verificarLicenciaOnline() {
  try {
    const d = await api('/admin/licencia/verificar-online', { method: 'POST', body: JSON.stringify({}) }, { noRedirect: true });
    if (!d.ok) throw new Error(d.error || 'No se pudo verificar en cloud');
    if (d.online?.skipped && d.online?.reason === 'CONNECTOR_IDENTITY_INCOMPLETE') {
      alerta(d.online.message || 'Falta identidad Cloud del conector para validar en linea.', 'err');
    } else {
      alerta(d.online?.valid === false ? 'Cloud rechazo la licencia.' : 'Verificacion cloud completada.', d.online?.valid === false ? 'err' : 'ok');
    }
    await cargarLicencia();
  } catch (e) {
    alerta('No se pudo verificar en cloud: ' + e.message, 'err');
  }
}

function simplifyBillingPanel() {
  const panel = document.getElementById('s-billing');
  if (!panel) return;
  panel.classList.add('customer-billing-mode');
  const copy = panel.querySelector('.ph p');
  if (copy) copy.textContent = 'Activa el portal Me-facturo de la sucursal. El proveedor y la integracion REST quedan gestionados nativamente por ARKHON.';
  const hideIds = ['bil-url','bil-user','bil-pass','bil-auth-path','bil-token-field','bil-env','bil-active','bil-auth-mode','bil-rfc','bil-name','bil-regime','bil-zip','bil-branch-ext','bil-branch-code'];
  hideIds.forEach((id) => {
    const el = document.getElementById(id);
    const wrap = el?.closest('.f');
    if (wrap) wrap.style.display = 'none';
  });
  const provider = Array.from(panel.querySelectorAll('input[readonly]')).find((el) => el.value === 'Portal de Autofactura');
  const providerWrap = provider?.closest('.f');
  if (providerWrap) providerWrap.style.display = 'none';
  const passMask = document.getElementById('bil-pass-mask');
  if (passMask) passMask.style.display = 'none';
  const titles = panel.querySelectorAll('.ct');
  if (titles[0]) titles[0].textContent = 'Datos fiscales de la sucursal';
  if (titles[1]) titles[1].style.display = 'none';
  const branchName = document.querySelector('label[for="bil-branch-name"]');
  const nameInput = document.getElementById('bil-branch-name');
  if (nameInput?.closest('.f')) {
    nameInput.closest('.f').querySelector('label').textContent = 'Nombre de la sucursal';
    nameInput.placeholder = 'Ej. Matriz Centro';
  }
  const cer = document.getElementById('bil-cer');
  if (cer?.closest('.f')) cer.closest('.f').querySelector('label').textContent = 'Certificado CSD / .cer';
  const keyPass = document.getElementById('bil-key-pass');
  if (keyPass?.closest('.f')) {
    keyPass.closest('.f').querySelector('label').textContent = 'Contraseña de la llave';
    keyPass.placeholder = 'Contraseña privada del CSD';
  }
  const portal = document.getElementById('bil-print-portal');
  if (portal?.closest('.f')) portal.closest('.f').querySelector('label').textContent = 'Dominio Me-facturo';
  panel.querySelectorAll('.billing-grid').forEach((grid) => { grid.style.gridTemplateColumns = '1fr 1fr'; });
  ['bil-branch-name','bil-key-pass','bil-print-portal','bil-portal-logo'].forEach((id) => {
    const wrap = document.getElementById(id)?.closest('.f');
    if (wrap) wrap.style.gridColumn = '1 / -1';
  });
  const saveBtn = panel.querySelector('button[onclick="guardarBilling()"]');
  if (saveBtn) saveBtn.textContent = 'Guardar facturacion';
  const testBtn = panel.querySelector('button[onclick="probarBilling()"]');
  if (testBtn) testBtn.textContent = 'Verificar portal';
}

// ── API helper ───────────────────────────────────────────────────
async function api(ep, opts = {}, { noRedirect = false } = {}) {
  const headers = { 'X-Api-Token': KEY, 'Content-Type': 'application/json', ...(opts.headers || {}) };
  const r = await fetch('/api' + ep, {
    ...opts,
    headers,
  });
  if (r.status === 401) {
    try {
      const ref = await fetch('/api/auth/refresh', { method: 'POST', credentials: 'include' });
      const rd  = await ref.json();
      if (rd.ok && rd.token) {
        KEY = rd.token;
        const retryHeaders = { 'X-Api-Token': KEY, 'Content-Type': 'application/json', ...(opts.headers || {}) };
        const r2 = await fetch('/api' + ep, {
          ...opts,
          headers: retryHeaders,
        });
        return r2.json();
      }
    } catch(_) {}
    if (noRedirect) throw new Error('Sesión expirada. Recarga la página e inicia sesión de nuevo.');
    if (window.parent && window.parent !== window) {
      window.parent.postMessage({ tipo: 'arkhon_session_expired' }, window.location.origin);
      return { ok: false, error: 'Sesion expirada' };
    }
    window.location.href = '/login';
    return { ok: false, error: 'Sesión expirada' };
  }
  return r.json();
}

function setMiPerfilStatus(message, type = '') {
  const el = document.getElementById('me-profile-result');
  if (!el) return;
  el.textContent = message || '';
  el.style.color = type === 'err' ? 'var(--red)' : (type === 'ok' ? 'var(--green)' : 'var(--text-3)');
}

async function cargarMiPerfil() {
  const input = document.getElementById('me-display-name');
  if (!input) return;
  setMiPerfilStatus('');
  try {
    const d = await api('/security/me', {}, { noRedirect: true });
    const name = d?.user?.nombre || d?.user?.display_name || sessionStorage.getItem('arkhon_nombre') || '';
    input.value = name;
  } catch (err) {
    input.value = sessionStorage.getItem('arkhon_nombre') || '';
    setMiPerfilStatus(err.message || 'No se pudo cargar tu perfil.', 'err');
  }
  ['me-current-password', 'me-new-password', 'me-confirm-password'].forEach(id => {
    const field = document.getElementById(id);
    if (field) field.value = '';
  });
}

async function guardarMiPerfil() {
  const name = document.getElementById('me-display-name')?.value.trim() || '';
  const currentPassword = document.getElementById('me-current-password')?.value || '';
  const newPassword = document.getElementById('me-new-password')?.value || '';
  const confirmPassword = document.getElementById('me-confirm-password')?.value || '';
  if (!name) return setMiPerfilStatus('El nombre no puede quedar vacio.', 'err');
  if (newPassword || confirmPassword) {
    if (newPassword !== confirmPassword) return setMiPerfilStatus('La confirmacion no coincide.', 'err');
    if (!currentPassword) return setMiPerfilStatus('Ingresa tu contrasena actual para cambiarla.', 'err');
  }
  setMiPerfilStatus('Guardando...');
  try {
    const d = await api('/security/me', {
      method: 'PUT',
      body: JSON.stringify({ nombre: name, current_password: currentPassword, password: newPassword }),
    }, { noRedirect: true });
    if (!d.ok) throw new Error(d.error || 'No se pudo guardar el perfil.');
    const savedName = d.user?.nombre || name;
    sessionStorage.setItem('arkhon_nombre', savedName);
    try {
      if (window.parent && window.parent !== window) {
        window.parent.sessionStorage.setItem('arkhon_nombre', savedName);
        const chipLabel = window.parent.document.querySelector('#user-chip span');
        if (chipLabel) chipLabel.textContent = savedName;
      }
    } catch (_) {}
    ['me-current-password', 'me-new-password', 'me-confirm-password'].forEach(id => {
      const field = document.getElementById(id);
      if (field) field.value = '';
    });
    setMiPerfilStatus('Perfil actualizado.', 'ok');
  } catch (err) {
    setMiPerfilStatus(err.message || 'No se pudo guardar el perfil.', 'err');
  }
}

// ── Cargar config ────────────────────────────────────────────────
async function cargarConfig() {
  try {
    const d = await api('/admin/config');
    if (!d.ok) return;
    const c = d.config;
    // General
    set('g-id',       c.SUCURSAL_ID    || '');
    set('g-nombre',   c.SUCURSAL_NOMBRE|| '');
    set('g-port',     c.PORT           || '3001');
    selSet('g-tz',    c.TZ_OFFSET_HOURS|| '0');
    // DB
    set('db-server',  c.DB_SERVER  || '');
    set('db-name',    c.DB_NAME    || '');
    set('db-user',    c.DB_USER    || '');
    set('db-port',    c.DB_PORT    || '1433');
    set('db-timeout', c.DB_CONNECT_TIMEOUT || '15000');
    // Acceso
    set('sec-key', c.LOCAL_API_KEY || '(vacio — ejecuta el instalador)');
    set('sec-jwt', c.JWT_SECRET ? '••••••••••••••••' : '(vacio)');
    // PIN de admin — solo indicar si está configurado
    const pinHint = document.getElementById('sec-admin-pin-hint');
    if (pinHint) {
      pinHint.textContent = c.ADMIN_PIN_SET
        ? 'PIN configurado - Escribe un nuevo PIN solo si quieres cambiarlo.'
        : 'Sin PIN configurado - escribe uno para proteger el panel.';
    }
    // Tunel — el .env real usa PUBLIC_DOMAIN (el codigo del servidor usa esta variable)
    const dom = c.PUBLIC_DOMAIN || '';
    set('tun-dom',  dom);
    set('tun-cors', c.CORS_ORIGINS || '');
    cargarCloudflareStatus();
    // Cache
    set('c-snap', c.CACHE_TTL_SNAPSHOT || '4000');
    set('c-graf', c.CACHE_TTL_GRAFICAS || '5000');
    set('c-mesa', c.CACHE_TTL_MESAS    || '1000');
    set('c-can',  c.CACHE_TTL_CANCEL   || '1000');
    set('c-prod', c.CACHE_TTL_PRODUCTOS|| '10000');
    set('c-pago', c.CACHE_TTL_PAGOS    || '10000');
    set('c-met',  c.CACHE_TTL_METRICAS || '1000');
    set('c-mes',  c.CACHE_TTL_MES      || '60000');
    // IA
    const configuredProvider = c.AI_PROVIDER || 'openai';
    const usableProvider = configuredProvider === 'anthropic' && !c.ANTHROPIC_API_KEY && c.OPENAI_API_KEY
      ? 'openai'
      : configuredProvider;
    selSet('ia-provider', usableProvider);
    set('ia-key', c.ANTHROPIC_API_KEY || '');
    set('openai-key', c.OPENAI_API_KEY || '');
    selSet('ia-model', c.ANTHROPIC_MODEL || 'claude-sonnet-4-6');
    selSet('openai-model', c.OPENAI_MODEL || 'gpt-5.6-terra');
    actualizarProveedorIA();
    // Usuarios (fuente de verdad: servidor)
    if (c.ARKHON_USUARIOS) {
      try {
        const parsed = JSON.parse(c.ARKHON_USUARIOS);
        ['dueno','gerente','aux'].forEach(rol => {
          if (parsed[rol]) {
            usuarios[rol].nombre   = parsed[rol].nombre   || '';
            usuarios[rol].pin      = parsed[rol].pin      || '';
            usuarios[rol].permisos = parsed[rol].permisos || buildPerms(rol);
          }
        });
        // Sincronizar también localStorage con los datos del servidor
        guardarUsuariosLocal();
      } catch(_) {}
    }
  } catch(e) { alerta('Error al cargar config: ' + e.message, 'err'); }
}

function set(id, val)    { const el = document.getElementById(id); if(el) el.value = val; }
function selSet(id, val) {
  const el = document.getElementById(id);
  if (!el) return;
  for (const o of el.options) if (o.value === String(val)) { o.selected = true; break; }
}

function engineeringHeaders() {
  const secret = ENGINEERING_SECRET || '';
  const email = ENGINEERING_EMAIL || '';
  return secret && email
    ? { 'X-Arkhon-Engineering-Email': email, 'X-Arkhon-Engineering-Password': secret }
    : {};
}

function htmlEscape(value) {
  return String(value ?? '').replace(/[&<>"']/g, ch => ({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;',
  }[ch]));
}

async function billingApi(ep, opts = {}) {
  return api('/billing' + ep, {
    ...opts,
    headers: { ...(opts.headers || {}), ...engineeringHeaders() },
  }, { noRedirect: true });
}

function fileToBase64(inputId) {
  const file = document.getElementById(inputId)?.files?.[0];
  if (!file) return Promise.resolve('');
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(String(reader.result || '').split(',').pop() || '');
    reader.onerror = () => reject(reader.error || new Error('No se pudo leer el archivo'));
    reader.readAsDataURL(file);
  });
}

async function cargarBilling() {
  try {
    const d = await billingApi('/settings');
    if (!d.ok) throw new Error(d.error || 'No se pudo cargar facturacion');
    const c = d.data || {};
    set('bil-url', c.base_url || 'https://micfdi.site/dev/ws/api_timbrado.php');
    set('bil-user', c.auth_username || '');
    set('bil-pass', '');
    set('bil-auth-path', c.auth_path || '?endpoint=login');
    set('bil-token-field', c.token_field || 'token');
    selSet('bil-env', c.environment || 'production');
    selSet('bil-active', String(c.active !== false));
    selSet('bil-auth-mode', c.auth_mode || 'login');

    const mask = document.getElementById('bil-pass-mask');
    if (mask) mask.textContent = c.auth_password_masked ? `Contraseña guardada: ${c.auth_password_masked}` : 'Sin contraseña guardada';

    const branch = (c.branches || [])[0] || {};
    const emitter = (c.emitters || [])[0] || {};
    set('bil-rfc', emitter.rfc || 'XAXX010101000');
    set('bil-name', emitter.legal_name || (branch.name || 'Sucursal ARKHON'));
    set('bil-regime', emitter.fiscal_regime || '');
    set('bil-zip', emitter.postal_code || '');
    set('bil-key-pass', '');
    const certHint = emitter.certificate_configured ? 'CSD/CDS configurado' : 'Sin certificado fiscal guardado';

    set('bil-branch-ext', branch.external_branch_id || '');
    set('bil-branch-name', branch.name || '');
    set('bil-branch-code', branch.code || '');
    try {
      const pc = await api('/admin/billing-print/config');
      const printCfg = pc.config || {};
      set('bil-print-portal', printCfg.portalUrl && printCfg.portalUrl !== '[configured]' ? printCfg.portalUrl : 'https://me-facturo.arkhonmonitor.com');
      const logoHint = document.getElementById('bil-portal-logo-hint');
      if (logoHint) logoHint.textContent = printCfg.logoPath ? 'Logo personalizado configurado' : 'Sin logo personalizado';
    } catch (_) {
      set('bil-print-portal', 'https://me-facturo.arkhonmonitor.com');
    }

    const list = document.getElementById('bil-branches');
    if (list) {
      list.innerHTML = (c.branches || [])
        .map(b => `<span class="badge bg">${htmlEscape(b.name || 'Sucursal')} (${htmlEscape(b.code || b.external_branch_id || 'sin codigo')})</span>`)
        .join('') || `<span class="hint">${certHint}</span>`;
    }
    await cargarBillingQrStatus();
  } catch(e) {
    alerta('Facturacion: ' + e.message, 'err');
  }
}

function renderBillingQrStatus(d) {
  set('bilqr-license', d.licenseState?.status || 'desconocida');
  set('bilqr-module', d.moduleEnabled ? 'activo' : 'inactivo');
  set('bilqr-status', d.status || 'desconocido');
  set('bilqr-folder', `${d.watchFolder || 'sin configurar'}${d.watchFolderExists ? '' : ' (no existe)'}`);
  set('bilqr-printer', `${d.outputPrinterName || d.printerName || 'sin configurar'}${d.printerAvailable === false ? ' (no disponible)' : ''}`);
  set('bilqr-capture-status', d.capture_status || (d.capturePrinterReady ? 'installed' : 'desconocido'));
  set('bilqr-capture-printer', `${d.capturePrinterName || 'ARKHON Ticket Capture'}${d.capturePrinterInstalled === false ? ' (no instalada)' : ''}`);
  set('bilqr-capture-port', `${d.capturePrinterPortName || d.capturePortName || 'sin configurar'}${d.capturePortMatches === false ? ' (no coincide)' : ''}`);
  set('bilqr-capture-driver', `${d.captureDriverName || 'sin detectar'}${d.captureDriverMatches === false ? ' (no coincide)' : ''}`);
  set('bilqr-capture-file', d.captureExpectedFile || 'sin configurar');
  set('bilqr-capture-file-exists', d.captureExpectedFileExists ? 'si' : 'no');
  set('bilqr-softrestaurant-target', d.softRestaurantPrintingToCapture === true ? 'si' : d.softRestaurantPrintingToCapture === false ? 'no' : 'sin evidencia');
  set('bilqr-recent-prn', (d.recentPrnDetected || d.recentPrnFiles || []).map(x => x.name || '').filter(Boolean).join(', ') || 'sin registros');
  set('bilqr-spooler', d.spoolerRunning ? 'running' : 'stopped');
  set('bilqr-last-ticket', d.lastTicketCapturedAt || 'sin registros');
  set('bilqr-last-qr', d.lastQrPrintedAt || 'sin registros');
  set('bilqr-error', d.softRestaurantOperationalError || d.captureLastError || d.lastError || '');
  set('bilqr-recommended', d.recommendedAction || '');
}

async function cargarImpresorasBilling(selected = '') {
  const select = document.getElementById('bilqr-output-printer');
  if (!select) return;
  select.innerHTML = '<option value="">Cargando impresoras...</option>';
  try {
    const d = await api('/billing-print/capture/output-printers');
    const printers = d.physicalPrinters || [];
    select.innerHTML = '<option value="">Seleccione impresora POS fisica</option>' + printers
      .map(p => `<option value="${htmlEscape(p.name)}">${htmlEscape(p.name)}${p.default ? ' (default)' : ''}</option>`)
      .join('');
    const current = selected || document.getElementById('bilqr-printer')?.value?.replace(/\s+\(no disponible\)$/,'') || '';
    if (current) select.value = current;
    if (!printers.length) select.innerHTML = '<option value="">Sin impresoras fisicas detectadas</option>';
  } catch (e) {
    select.innerHTML = '<option value="">Error al listar impresoras</option>';
    set('bilqr-error', e.message);
  }
}

async function cargarBillingQrStatus() {
  try {
    const d = await api('/billing-print/status');
    if (!d.ok) throw new Error(d.error || 'No se pudo leer QR Printing');
    renderBillingQrStatus(d);
    await cargarImpresorasBilling(d.outputPrinterName || d.printerName || '');
  } catch (e) {
    set('bilqr-status', 'error');
    set('bilqr-error', e.message);
  }
}

async function reiniciarBillingQr() {
  const res = document.getElementById('bilqr-res');
  if (res) res.textContent = 'Reiniciando...';
  try {
    const d = await api('/billing-print/restart', { method: 'POST', body: '{}' });
    renderBillingQrStatus(d);
    if (res) res.textContent = d.status || 'actualizado';
  } catch (e) {
    if (res) res.textContent = 'Error';
    alerta('QR Printing: ' + e.message, 'err');
  }
}

async function guardarImpresoraBilling() {
  const res = document.getElementById('bilqr-res');
  const outputPrinterName = v('bilqr-output-printer');
  if (res) res.textContent = 'Guardando POS...';
  try {
    if (!outputPrinterName) throw new Error('Seleccione una impresora POS fisica');
    const d = await api('/billing-print/capture/output-printer', {
      method: 'POST',
      body: JSON.stringify({ outputPrinterName }),
    });
    if (!d.ok) throw new Error(d.error || 'No se pudo guardar outputPrinterName');
    renderBillingQrStatus(d.restart || d);
    await cargarBillingQrStatus();
    if (res) res.textContent = 'POS guardada y QR Printing activado';
  } catch (e) {
    if (res) res.textContent = 'Error';
    alerta('Impresora POS: ' + e.message, 'err');
  }
}

async function recargarLicenciaBilling() {
  const res = document.getElementById('bilqr-res');
  if (res) res.textContent = 'Recargando...';
  try {
    const d = await api('/license/reload-entitlements', { method: 'POST', body: '{}' });
    renderBillingQrStatus(d);
    if (res) res.textContent = d.status || 'actualizado';
  } catch (e) {
    if (res) res.textContent = 'Error';
    alerta('Licencia: ' + e.message, 'err');
  }
}

async function repararTicketCapture() {
  const res = document.getElementById('bilqr-res');
  if (res) res.textContent = 'Reparando Ticket Capture...';
  try {
    const d = await api('/billing-print/capture/repair', { method: 'POST', body: '{}' });
    await cargarBillingQrStatus();
    if (res) res.textContent = d.capture_status || (d.ok ? 'reparado' : 'error');
  } catch (e) {
    if (res) res.textContent = 'Error';
    alerta('Ticket Capture: ' + e.message, 'err');
  }
}

async function probarTicketCapture() {
  const res = document.getElementById('bilqr-res');
  if (res) res.textContent = 'Enviando prueba...';
  try {
    const d = await api('/billing-print/capture/test-print', { method: 'POST', body: '{}' });
    await cargarBillingQrStatus();
    if (res) res.textContent = d.accepted ? 'Trabajo aceptado por spooler' : (d.capture_status || 'no aceptado');
  } catch (e) {
    if (res) res.textContent = 'Error';
    alerta('Prueba Ticket Capture: ' + e.message, 'err');
  }
}

async function probarSoftRestaurantReal() {
  const res = document.getElementById('bilqr-sr-real-res');
  set('bilqr-sr-real-state', 'esperando PRN real...');
  if (res) res.textContent = 'Imprima desde SoftRestaurant ahora (ventana 120s)...';
  try {
    const d = await api('/billing-print/capture/softrestaurant-real-test/start', { method: 'POST', body: '{}' });
    set('bilqr-sr-real-state', d.softRestaurantPrintingToCapture ? 'capturado' : (d.state || 'sin PRN'));
    set('bilqr-recommended', d.recommendedAction || '');
    await cargarBillingQrStatus();
    if (res) res.textContent = d.recommendedAction || (d.qrPrinted ? 'QR impreso' : d.printedToOutput ? 'Ticket impreso sin QR' : 'capturado');
  } catch (e) {
    set('bilqr-sr-real-state', 'error');
    if (res) res.textContent = 'Error';
    alerta('Prueba real SoftRestaurant: ' + e.message, 'err');
  }
}

async function guardarBilling() {
  const cerBase64 = await fileToBase64('bil-cer');
  const keyBase64 = await fileToBase64('bil-key');
  const payload = {
    base_url: v('bil-url') || 'https://micfdi.site/dev/ws/api_timbrado.php',
    auth_username: v('bil-user'),
    auth_password: v('bil-pass'),
    auth_mode: v('bil-auth-mode') || 'login',
    auth_path: v('bil-auth-path') || '?endpoint=login',
    token_field: v('bil-token-field') || 'token',
    environment: v('bil-env') || 'production',
    active: true,
    emitter: {
      rfc: v('bil-rfc') || 'XAXX010101000',
      legal_name: v('bil-name') || v('bil-branch-name') || 'Sucursal ARKHON',
      fiscal_regime: v('bil-regime'),
      postal_code: v('bil-zip'),
      cer_base64: cerBase64,
      key_base64: keyBase64,
      key_password: v('bil-key-pass'),
    },
    branches: [{
      external_branch_id: v('bil-branch-ext') || v('bil-branch-name'),
      name: v('bil-branch-name'),
      code: v('bil-branch-code') || v('bil-branch-name'),
    }],
  };

  try {
    const d = await billingApi('/settings', { method: 'PUT', body: JSON.stringify(payload) });
    if (!d.ok) throw new Error(d.error || 'No se pudo guardar el conector');
    set('bil-pass', '');
    set('bil-key-pass', '');
    const logoBase64 = await fileToBase64('bil-portal-logo');
    const logoFile = document.getElementById('bil-portal-logo')?.files?.[0];
    if (logoBase64 && logoFile) {
      const logoUpload = await api('/admin/billing-print/logo', {
        method: 'POST',
        body: JSON.stringify({ fileName: logoFile.name, dataBase64: logoBase64 }),
      });
      if (!logoUpload.ok) throw new Error(logoUpload.error || 'No se pudo guardar el logo del portal');
    }
    await api('/admin/billing-print/config', {
      method: 'POST',
      body: JSON.stringify({
        portalUrl: v('bil-print-portal') || 'https://me-facturo.arkhonmonitor.com',
        branchCode: v('bil-branch-code'),
        branchName: v('bil-branch-name'),
        mode: 'portal',
      }),
    });
    alerta('Conector de facturacion guardado.', 'ok');
    cargarBilling();
  } catch(e) {
    alerta('Facturacion: ' + e.message, 'err');
  }
}

async function probarBilling() {
  const res = document.getElementById('bil-res');
  if (res) res.textContent = 'Probando...';
  try {
    const d = await billingApi('/settings/test-connection', { method: 'POST', body: '{}' });
    if (!d.ok) throw new Error(d.error || 'No se pudo probar la conexión');
    if (res) res.textContent = d.data && d.data.ok ? 'Conexion correcta' : 'Proveedor respondio con advertencia';
    alerta('Prueba de conexión terminada.', 'ok');
  } catch(e) {
    if (res) res.textContent = 'Error de conexión';
    alerta('Facturacion: ' + e.message, 'err');
  }
}

// ── Guardar config ───────────────────────────────────────────────
function actualizarProveedorIA() {
  const provider = v('ia-provider') || 'openai';
  document.querySelectorAll('[data-ai-provider]').forEach((field) => {
    field.style.display = field.dataset.aiProvider === provider ? '' : 'none';
  });
}

function seleccionarOpenAIAlCapturar() {
  const key = v('openai-key');
  if (!key || key.endsWith('...')) return;
  const provider = document.getElementById('ia-provider');
  if (provider) provider.value = 'openai';
  actualizarProveedorIA();
}

async function guardar(sec) {
  const u = {};
  if (sec === 'general') {
    u.SUCURSAL_ID      = v('g-id');
    u.SUCURSAL_NOMBRE  = v('g-nombre');
    u.PORT             = v('g-port');
    u.TZ_OFFSET_HOURS  = v('g-tz');
  } else if (sec === 'db') {
    u.DB_SERVER          = v('db-server');
    u.DB_NAME            = v('db-name');
    u.DB_USER            = v('db-user');
    u.DB_PORT            = v('db-port');
    u.DB_CONNECT_TIMEOUT = v('db-timeout');
    const pw = v('db-pass');
    if (pw) u.DB_PASSWORD = pw;
  } else if (sec === 'tunel') {
    const dom = v('tun-dom').replace(/^https?:\/\//, '');
    u.PUBLIC_DOMAIN = dom;   // Nombre correcto según .env y routes/admin.js
    u.CORS_ORIGINS  = dom
      ? 'http://localhost:3001,https://' + dom
      : 'http://localhost:3001';
    set('tun-cors', u.CORS_ORIGINS);
  } else if (sec === 'cache') {
    u.CACHE_TTL_SNAPSHOT = v('c-snap');
    u.CACHE_TTL_GRAFICAS = v('c-graf');
    u.CACHE_TTL_MESAS    = v('c-mesa');
    u.CACHE_TTL_CANCEL   = v('c-can');
    u.CACHE_TTL_PRODUCTOS= v('c-prod');
    u.CACHE_TTL_PAGOS    = v('c-pago');
    u.CACHE_TTL_METRICAS = v('c-met');
    u.CACHE_TTL_MES      = v('c-mes');
  } else if (sec === 'ia') {
    u.AI_PROVIDER = v('ia-provider') || 'openai';
    const k = v('ia-key');
    const ok = v('openai-key');
    // No enviar si es valor enmascarado del backend
    if (k && !k.endsWith('...') && !k.endsWith('••••')) u.ANTHROPIC_API_KEY = k;
    if (ok && !ok.endsWith('...') && !ok.endsWith('••••')) u.OPENAI_API_KEY = ok;
    u.ANTHROPIC_MODEL = v('ia-model');
    u.OPENAI_MODEL = v('openai-model');
  }

  try {
    const d = await api('/admin/config', { method: 'POST', body: JSON.stringify({ updates: u }) });
    if (d.ok) alerta('Cambios guardados. Algunos requieren reiniciar el servidor.', 'ok');
    else alerta('Error: ' + (d.error || 'desconocido'), 'err');
  } catch(e) { alerta('Error de red: ' + e.message, 'err'); }
}

function v(id) { const el = document.getElementById(id); return el ? el.value.trim() : ''; }

async function cargarCloudflareStatus() {
  const el = document.getElementById('cf-status');
  if (!el) return;
  try {
    const d = await api('/admin/cloudflare-status');
    const s = d.status || {};
    if (s.installed) {
      el.innerHTML = `
        <div style="display:flex;align-items:center;gap:10px">
          <span style="width:9px;height:9px;border-radius:50%;background:${s.running ? '#22c55e' : '#f59e0b'};display:inline-block"></span>
          <strong>${s.running ? 'Cloudflare activo' : 'Cloudflare detectado'}</strong>
        </div>
        <div style="font-size:11px;color:var(--text-3);margin-top:4px">${s.displayName || s.serviceName || 'Cloudflare Tunnel'}</div>`;
    } else {
      el.innerHTML = `
        <div style="display:flex;align-items:center;gap:10px">
          <span style="width:9px;height:9px;border-radius:50%;background:#94a3b8;display:inline-block"></span>
          <strong>Cloudflare no detectado</strong>
        </div>
        <div style="font-size:11px;color:var(--text-3);margin-top:4px">Guarda la URL publica cuando el servicio este instalado.</div>`;
    }
  } catch (_) {
    el.textContent = 'No se pudo verificar Cloudflare en este momento.';
  }
}

// ── DB Test ──────────────────────────────────────────────────────
async function probarDB() {
  const sp = document.getElementById('db-spin');
  const rs = document.getElementById('db-res');
  sp.style.display = 'inline-block'; rs.innerHTML = '';
  try {
    const d = await api('/admin/test-db');
    rs.innerHTML = d.ok
      ? '<span class="badge bg">Conexion OK</span>'
      : '<span class="badge br">Fallo: ' + escAdmin(d.error || '?') + '</span>';
    const svcDb = document.getElementById('svc-db');
    const dbSub  = document.getElementById('db-sub');
    if (svcDb) { svcDb.className = d.ok ? 'badge bg' : 'badge br'; svcDb.textContent = d.ok ? 'Conectado' : 'Error'; }
    if (dbSub)  dbSub.textContent = d.ok ? 'SQL Server conectado' : (d.error || 'Error de conexión');
  } catch(e) { rs.innerHTML = '<span class="badge br">Error: ' + escAdmin(e.message) + '</span>'; }
  finally { sp.style.display = 'none'; }
}

// ── IA Test ──────────────────────────────────────────────────────
async function probarIA() {
  const sp = document.getElementById('ia-spin');
  const rs = document.getElementById('ia-res');
  sp.style.display = 'inline-block'; rs.innerHTML = '';
  try {
    const d = await api('/admin/test-ia');
    rs.innerHTML = d.ok
      ? '<span class="badge bg">Analisis disponible</span>'
      : '<span class="badge br">Error: ' + escAdmin(d.error || '?') + '</span>';
  } catch(e) { rs.innerHTML = '<span class="badge br">Error de red</span>'; }
  finally { sp.style.display = 'none'; }
}

// ── Cambiar PIN de admin ──────────────────────────────────────────
async function guardarAdminPin() {
  const pin  = document.getElementById('sec-admin-pin').value.trim();
  const pin2 = document.getElementById('sec-admin-pin2').value.trim();
  const rs   = document.getElementById('pin-res');

  if (!pin) { alerta('Escribe un nuevo PIN para cambiarlo.', 'err'); return; }
  if (!/^\d{4,}$/.test(pin)) { alerta('El PIN debe tener mínimo 4 dígitos numéricos.', 'err'); return; }
  if (pin !== pin2) { alerta('Los PINs no coinciden. Verifícalos e intenta de nuevo.', 'err'); return; }

  rs.innerHTML = '<span class="spin"></span>';
  try {
    const d = await api('/admin/config', {
      method: 'POST',
      body: JSON.stringify({ updates: { ADMIN_PIN: pin } }),
    });
    if (d.ok) {
      document.getElementById('sec-admin-pin').value  = '';
      document.getElementById('sec-admin-pin2').value = '';
      rs.innerHTML = '';
      alerta('PIN de administrador actualizado correctamente.', 'ok');
      // Recargar config para actualizar el hint
      await cargarConfig();
    } else {
      rs.innerHTML = '';
      alerta('Error al guardar PIN: ' + (d.error || 'respuesta inesperada'), 'err');
    }
  } catch(e) {
    rs.innerHTML = '';
    alerta('Error de red al guardar PIN: ' + e.message, 'err');
  }
}

// ── Regenerar claves ─────────────────────────────────────────────
async function regenerarClaves() {
  if (!confirm('Esto cierra sesión en todos los dispositivos. ¿Continuar?')) return;
  const d = await api('/admin/regenerar-claves', { method: 'POST' });
  if (d.ok) { alerta('Claves regeneradas. Recargando...', 'ok'); setTimeout(() => location.reload(), 2000); }
  else alerta('Error: ' + (d.error || ''), 'err');
}

// ── Cache ────────────────────────────────────────────────────────
async function limpiarCache() {
  const d = await api('/dashboard/cache/flush', { method: 'POST' });
  alerta(d.ok ? 'Cache limpiado.' : 'Error al limpiar cache.', d.ok ? 'ok' : 'err');
}

// ── Estado ───────────────────────────────────────────────────────
async function cargarEstado() {
  try {
    const d = await api('/health');
    if (!d.ok) return;
    set('k-uptime', fmtUp(d.uptime || 0));
    // /api/health ya retorna memoria.heap como string '##MB'
    set('k-mem',  d.memoria?.heap || '—');
    set('k-env',  d.env  || '—');
    set('k-hora', d.hora ? new Date(d.hora).toLocaleTimeString('es-MX') : '—');
  } catch(_) {}
  await probarDB();
}

function fmtUp(s) {
  if (s < 60)   return s + 's';
  if (s < 3600) return Math.floor(s / 60) + 'm ' + (s % 60) + 's';
  return Math.floor(s / 3600) + 'h ' + Math.floor((s % 3600) / 60) + 'm';
}

// ── Logs ─────────────────────────────────────────────────────────
async function cargarLogs() {
  const box = document.getElementById('logbox');
  box.textContent = 'Cargando...';
  try {
    const d = await api('/admin/logs');
    if (d.ok && d.lineas?.length) {
      box.innerHTML = d.lineas.map(l => {
        let cls = 'l-info';
        if (l.includes('"level":"error"') || l.includes('ERROR') || l.includes('uncaught')) cls = 'l-err';
        else if (l.includes('"level":"warn"') || l.includes('WARN'))  cls = 'l-warn';
        else if (l.includes('"level":"info"') || l.includes('INFO'))  cls = 'l-ok';
        return '<span class="' + cls + '">' + escAdmin(l) + '</span>';
      }).join('\n');
      box.scrollTop = box.scrollHeight;
    } else {
      box.innerHTML = '<span class="l-warn">Sin logs disponibles todavia.</span>';
    }
  } catch(e) { box.innerHTML = '<span class="l-err">Error: ' + escAdmin(e.message) + '</span>'; }
}

// ── Reinicio ─────────────────────────────────────────────────────
function confirmarReinicio() {
  if (!confirm('Reiniciar cerrará todas las conexiones activas. ¿Continuar?')) return;
  api('/admin/restart', { method: 'POST' }).catch(() => {});
  alerta('Reiniciando... La página se recargará en 6 segundos.', 'info');
  setTimeout(() => location.reload(), 6000);
}

// ── Alert ────────────────────────────────────────────────────────
function alerta(msg, tipo) {
  const el = document.getElementById('alert-g');
  const icon = tipo === 'ok'
    ? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>'
    : tipo === 'err'
    ? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>'
    : '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>';
  el.className = 'alert show a-' + (tipo === 'ok' ? 'ok' : tipo === 'err' ? 'err' : 'info');
  el.innerHTML = icon + escAdmin(msg);
  clearTimeout(el._t);
  el._t = setTimeout(() => { el.className = 'alert'; }, 5000);
}

function escAdmin(s) {
  return String(s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

// ═══════════════════════════════════════════════════════════════
// ─── MODULO DE USUARIOS ────────────────────────────────────────
// ═══════════════════════════════════════════════════════════════

async function cargarUsuariosDesdeServidor() {
  // Refresca los datos de usuarios desde el servidor (process.env) y re-renderiza.
  // Garantiza que lo que se muestra en pantalla coincide con lo que el sistema de login usa.
  try {
    const resp = await api('/admin/config');
    if (resp && resp.ok && resp.config && resp.config.ARKHON_USUARIOS) {
      const parsed = JSON.parse(resp.config.ARKHON_USUARIOS);
      ['dueno','gerente','aux'].forEach(rol => {
        if (parsed[rol]) {
          usuarios[rol].nombre   = parsed[rol].nombre   || '';
          usuarios[rol].pin      = parsed[rol].pin      || '';
          usuarios[rol].permisos = parsed[rol].permisos || buildPerms(rol);
        }
      });
      guardarUsuariosLocal();
    }
  } catch(_) {
    // Si falla, renderizar con lo que hay en memoria (localStorage)
  }
  renderUsuarios();
}

function cargarUsuarios() {
  // La fuente de verdad es el servidor (process.env.ARKHON_USUARIOS via /api/admin/config).
  // localStorage solo se usa como fallback cuando el servidor aún no responde.
  // La carga real ocurre en cargarConfigUI() que se llama en DOMContentLoaded.
  // Aquí solo aplicamos el fallback de localStorage para la primera renderización.
  try {
    const saved = localStorage.getItem('arkhon_usuarios');
    if (saved) {
      const parsed = JSON.parse(saved);
      ['dueno','gerente','aux'].forEach(rol => {
        if (parsed[rol]) {
          usuarios[rol].nombre   = parsed[rol].nombre   || '';
          // Nunca guardar PIN en localStorage — solo indicar si existe
          if (parsed[rol].pin) usuarios[rol].pin = parsed[rol].pin;
          usuarios[rol].permisos = parsed[rol].permisos || buildPerms(rol);
        }
      });
    }
  } catch(_) {}
}

function guardarUsuariosLocal() {
  try { localStorage.setItem('arkhon_usuarios', JSON.stringify(usuarios)); } catch(_) {}
}

function renderUsuarios() {
  renderTarjetas();
  renderPermisos();
}

function renderTarjetas() {
  const grid = document.getElementById('usr-grid');
  grid.innerHTML = ['dueno','gerente','aux'].map(rol => {
    const u = usuarios[rol];
    const initials = u.nombre
      ? u.nombre.split(' ').slice(0,2).map(w => w[0]).join('').toUpperCase()
      : ROL_INITIALS[rol];
    const pinMask  = u.pin ? '•'.repeat(u.pin.length) : 'Sin PIN';
    const rolLabel = ROL_LABELS[rol];
    return `
      <div class="usr-card ${rol}" onclick="abrirModal('${rol}')">
        <div class="usr-role-bar"></div>
        <div class="usr-avatar">${escAdmin(initials)}</div>
        <div class="usr-name">${u.nombre ? escAdmin(u.nombre) : '<span style="color:var(--text-3);font-weight:500">Sin configurar</span>'}</div>
        <div class="usr-rol">${rolLabel}</div>
        <div class="usr-status">
          <span class="usr-pin">${pinMask}</span>
          <button class="usr-edit-btn" onclick="event.stopPropagation();abrirModal('${rol}')">Editar</button>
        </div>
      </div>`;
  }).join('');
}

function renderPermisos() {
  const tbody = document.getElementById('perm-tbody');
  tbody.innerHTML = PERMISOS_DEF.map(p => {
    const rows = ['dueno','gerente','aux'].map(rol => {
      const isDueno = rol === 'dueno';
      const checked = usuarios[rol].permisos[p.key] ? 'checked' : '';
      const disabled = isDueno ? 'perm-disabled' : '';
      return `<td style="text-align:center">
        <input type="checkbox" class="perm-check ${disabled}" ${checked} ${isDueno ? 'disabled' : ''}
          onchange="togglePerm('${rol}','${p.key}',this.checked)">
      </td>`;
    }).join('');
    return `<tr><td>${p.label}</td>${rows}</tr>`;
  }).join('');
}

function togglePerm(rol, key, val) {
  if (rol === 'dueno') return; // dueno no se modifica
  usuarios[rol].permisos[key] = val;
  guardarUsuariosLocal();
  // persistir en backend si es posible
  persistirPermisos();
}

async function persistirPermisos() {
  // ARKHON_USUARIOS esta en la whitelist de routes/admin.js
  try {
    const resp = await api('/admin/config', {
      method: 'POST',
      body: JSON.stringify({ updates: { ARKHON_USUARIOS: JSON.stringify(usuarios) } }),
    });
    return resp;
  } catch(_) { return null; }
}

function abrirModal(rol) {
  editandoRol = rol;
  const u = usuarios[rol];
  document.getElementById('modal-usr-title').textContent = 'Editar — ' + ROL_LABELS[rol];
  document.getElementById('mu-nombre').value = u.nombre || '';
  document.getElementById('mu-pin').value    = '';
  document.getElementById('mu-rol').value    = ROL_LABELS[rol];
  // El dueno puede editar todos; bloquear campos sensibles para aux
  document.getElementById('modal-usr').classList.add('open');
}

function cerrarModal(e) {
  if (e.target === document.getElementById('modal-usr'))
    document.getElementById('modal-usr').classList.remove('open');
}

async function guardarUsuario() {
  const nombre = document.getElementById('mu-nombre').value.trim();
  const pin    = document.getElementById('mu-pin').value.trim();

  if (!nombre) { alerta('El nombre no puede estar vacio.', 'err'); return; }
  if (pin && (pin.length < 4 || !/^\d+$/.test(pin))) {
    alerta('El PIN debe ser exactamente 4 dígitos numéricos.', 'err'); return;
  }

  // Guardar copia del estado anterior por si hay que revertir
  const nombrePrev = usuarios[editandoRol].nombre;
  const pinPrev    = usuarios[editandoRol].pin;

  usuarios[editandoRol].nombre = nombre;
  if (pin) usuarios[editandoRol].pin = pin;

  // Persistir en servidor PRIMERO — no cerrar modal hasta confirmar
  try {
    guardarUsuariosLocal();
    const resp = await api('/admin/config', {
      method: 'POST',
      body: JSON.stringify({ updates: { ARKHON_USUARIOS: JSON.stringify(usuarios) } }),
    }, { noRedirect: true });

    if (resp && resp.ok) {
      document.getElementById('modal-usr').classList.remove('open');
      renderTarjetas();
      alerta('Usuario "' + nombre + '" guardado correctamente.', 'ok');
    } else {
      // Revertir cambio local si el servidor falló
      usuarios[editandoRol].nombre = nombrePrev;
      if (pin) usuarios[editandoRol].pin = pinPrev;
      guardarUsuariosLocal();
      alerta('Error al guardar: ' + (resp?.error || 'respuesta inesperada del servidor'), 'err');
    }
  } catch(e) {
    // Revertir cambio local si el servidor falló
    usuarios[editandoRol].nombre = nombrePrev;
    if (pin) usuarios[editandoRol].pin = pinPrev;
    guardarUsuariosLocal();
    const msg = e.message.includes('Sesión expirada')
      ? 'Sesión expirada — recarga la página e inicia sesión de nuevo.'
      : 'Error de red: ' + e.message;
    alerta(msg, 'err');
  }
}

// ── Actualizador de versión ────────────────────────────────────────────────
let _updateUrl = '';
let _electronUpdateDownloaded = false;
let _electronUpdaterBound = false;
let _updatePollTimer = null;
let _updateLastStatus = '';
let _updateLastChangeAt = 0;
let _actualizadorEstado = null;
let _actualizadorJob = null;
let _runtimeUpdateNotifiedVersion = '';
const UPDATE_TERMINAL_STATUSES = new Set(['completed', 'failed', 'blocked', 'no_update', 'rollback_completed', 'rollback_failed']);

function notifyRuntimeUpdateAvailable(info) {
  const version = info?.version_disponible || info?.manifest?.version || '';
  if (!version || !info?.hay_actualizacion || info?.manifest?.type !== 'runtime-zip') return;
  if (_runtimeUpdateNotifiedVersion === version) return;
  const key = `arkhon_update_available_notified_${version}`;
  try {
    if (localStorage.getItem(key) === '1') return;
    localStorage.setItem(key, '1');
  } catch (_) {}
  _runtimeUpdateNotifiedVersion = version;
  const notification = {
    id: `runtime-update-${version}`,
    eventId: `runtime-update-${version}`,
    type: 'actualizacion_disponible',
    title: 'Nueva version disponible',
    body: `ARKHON Monitor ${version} esta listo para instalarse.`,
    url: '/admin?tab=actualizaciones',
    priority: 'alta',
    createdAt: new Date().toISOString(),
    data: {
      version,
      installedVersion: info.version_actual || '',
      manifest: info.manifest || {},
    },
  };
  try {
    if (window.parent && window.parent !== window && window.parent.arkhonNotify?.handleServerEvent) {
      window.parent.arkhonNotify.handleServerEvent(notification);
      return;
    }
  } catch (_) {}
  toast(`${notification.title}: ${version}`);
}

function electronUpdaterDisponible() {
  return false;
}

function pintarBotonesActualizacion(estado) {
  const check = document.getElementById('u-btn-check');
  const apply = document.getElementById('u-btn-apply');
  const searchIcon = '<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><line x1="16.5" y1="16.5" x2="21" y2="21"/></svg>';
  const installIcon = '<svg viewBox="0 0 24 24"><path d="M12 3v12"/><polyline points="7 8 12 3 17 8"/><path d="M5 21h14"/></svg>';
  if (check) check.innerHTML = searchIcon + ' ' + (estado === 'checking' ? 'Verificando...' : 'Verificar actualizacion');
  if (apply) apply.innerHTML = installIcon + ' ' + (estado === 'retry' ? 'Reintentar' : 'Instalar actualizacion');
}

function pintarEstadoActualizador(d, verificando) {
  const versionActual = document.getElementById('u-version-actual');
  const versionNueva = document.getElementById('u-version-nueva');
  const notas = document.getElementById('u-notas');
  const apply = document.getElementById('u-btn-apply');
  const diag = document.getElementById('u-btn-diag');

  _actualizadorEstado = d || null;
  _updateUrl = d?.manifest?.download_url || d?.download_url || '';
  versionActual.textContent = d?.version_actual || '-';
  versionNueva.textContent = d?.version_disponible || (d?.ok ? 'Sin actualizaciones' : 'Sin datos');
  apply.style.display = 'none';
  if (diag) diag.style.display = d?.diagnostic ? '' : 'none';

  if (d?.hay_actualizacion && d?.manifest?.type === 'runtime-zip') {
    notas.textContent = 'Nueva version runtime ZIP disponible: ' + d.version_disponible;
    apply.style.display = '';
    notifyRuntimeUpdateAvailable(d);
  } else if (d?.hay_actualizacion) {
    notas.textContent = 'Hay una version disponible, pero el portal local solo aplica runtime ZIP.';
  } else if (d?.ok) {
    notas.textContent = verificando ? 'No hay actualizacion runtime disponible.' : 'Listo para verificar runtime ZIP.';
  } else {
    versionNueva.textContent = 'No disponible';
    notas.textContent = d?.error || 'No se pudo consultar ARKHON Cloud.';
  }
}

function bindElectronUpdater() {
  if (_electronUpdaterBound || !window.arkhon?.onUpdateStatus) return;
  _electronUpdaterBound = true;
  window.arkhon.onUpdateStatus((st) => {
    const versionNueva = document.getElementById('u-version-nueva');
    const notas = document.getElementById('u-notas');
    const apply = document.getElementById('u-btn-apply');
    const progreso = document.getElementById('u-progreso');
    const barra = document.getElementById('u-barra');
    const logEl = document.getElementById('u-log');
    const infoVersion = st?.info?.version || st?.version || '';

    if (st.state === 'checking') {
      versionNueva.textContent = 'Buscando...';
      notas.textContent = st.message || 'Buscando actualizaciones...';
      apply.style.display = 'none';
    } else if (st.state === 'available') {
      versionNueva.textContent = infoVersion || 'Disponible';
      notas.textContent = (st.message || 'Nueva version disponible.') + ' Descargando automaticamente...';
      apply.style.display = '';
      if (window.arkhon?.downloadUpdate && !_electronUpdateDownloaded) {
        window.arkhon.downloadUpdate().catch((err) => {
          notas.textContent = 'Error al descargar automaticamente: ' + err.message;
        });
      }
    } else if (st.state === 'downloading') {
      progreso.style.display = '';
      barra.style.width = (st.progress || 0) + '%';
      logEl.textContent = st.message || 'Descargando actualizacion...';
      notas.textContent = st.message || 'Descargando actualizacion...';
    } else if (st.state === 'downloaded') {
      _electronUpdateDownloaded = true;
      progreso.style.display = '';
      barra.style.width = '100%';
      versionNueva.textContent = infoVersion || versionNueva.textContent || 'Descargada';
      logEl.textContent = st.message || 'Actualizacion descargada.';
      notas.textContent = st.message || 'Actualizacion descargada.';
      apply.style.display = '';
      apply.textContent = 'Instalar actualizacion';
    } else if (st.state === 'none') {
      versionNueva.textContent = 'Sin actualizaciones';
      notas.textContent = st.message || 'Ya tienes la version mas reciente.';
      apply.style.display = 'none';
    } else if (st.state === 'error') {
      versionNueva.textContent = 'Error';
      notas.textContent = st.error || st.message || 'No se pudo verificar la actualizacion.';
      apply.style.display = 'none';
    }
  });
}

async function initActualizadorResumen() {
  pintarBotonesActualizacion('idle');
  try {
    if (electronUpdaterDisponible()) {
      bindElectronUpdater();
      const appInfo = await window.arkhon.getAppVersion();
      document.getElementById('u-version-actual').textContent = appInfo?.version || '-';
      document.getElementById('u-version-nueva').textContent = 'Listo para verificar';
      document.getElementById('u-notas').textContent = 'Auto-updater conectado a ARKHON Cloud.';
      return;
    }

    const d = await api('/actualizador/estado');
    pintarEstadoActualizador(d, false);
  } catch(e) {
    document.getElementById('u-version-nueva').textContent = 'Error';
    document.getElementById('u-notas').textContent = 'Error al consultar ARKHON Cloud: ' + e.message;
  }
}

async function verificarActualizacion() {
  const btn = document.getElementById('u-btn-check');
  btn.disabled = true;
  pintarBotonesActualizacion('checking');
  try {
    if (electronUpdaterDisponible()) {
      bindElectronUpdater();
      const appInfo = await window.arkhon.getAppVersion();
      document.getElementById('u-version-actual').textContent = appInfo?.version || '-';
      document.getElementById('u-version-nueva').textContent = 'Buscando...';
      document.getElementById('u-notas').textContent = 'Buscando actualizaciones en ARKHON Cloud...';
      document.getElementById('u-btn-apply').style.display = 'none';
      const r = await window.arkhon.checkForUpdates();
      if (!r?.ok && r?.error) {
        document.getElementById('u-version-nueva').textContent = 'No disponible';
        document.getElementById('u-notas').textContent = r.error;
      }
      return;
    }

    const d = await api('/actualizador/estado');
    pintarEstadoActualizador(d, true);
  } catch(e) {
    document.getElementById('u-notas').textContent = 'Error al verificar: ' + e.message;
  } finally {
    btn.disabled = false;
    pintarBotonesActualizacion('idle');
  }
}

async function aplicarActualizacion(autoStart = false) {
  if (electronUpdaterDisponible()) {
    const btnApply = document.getElementById('u-btn-apply');
    const btnCheck = document.getElementById('u-btn-check');
    btnApply.disabled = true;
    btnCheck.disabled = true;
    try {
      if (_electronUpdateDownloaded && window.arkhon.quitAndInstallUpdate) {
        await window.arkhon.quitAndInstallUpdate();
      } else {
        btnApply.textContent = 'Descargando...';
        await window.arkhon.downloadUpdate();
      }
    } catch(e) {
      document.getElementById('u-notas').textContent = 'Error al actualizar: ' + e.message;
      btnApply.disabled = false;
      btnCheck.disabled = false;
    }
    return;
  }

  if (!autoStart && !confirm('¿Instalar la actualización? ARKHON se reiniciará automáticamente en ~30 segundos.')) return;

  const btnApply = document.getElementById('u-btn-apply');
  const btnCheck = document.getElementById('u-btn-check');
  btnApply.disabled = true;
  btnCheck.disabled = true;
  btnApply.textContent = '⏳ Instalando...';

  const progreso = document.getElementById('u-progreso');
  const barra    = document.getElementById('u-barra');
  const logEl    = document.getElementById('u-log');
  progreso.style.display = '';
  logEl.textContent = 'Iniciando descarga...\n';

  // Simular progreso visual mientras el servidor trabaja
  let pct = 0;
  const tick = setInterval(() => {
    pct = Math.min(pct + 2, 90);
    barra.style.width = pct + '%';
  }, 600);

  try {
    const d = await api('/actualizador/aplicar', {
      method: 'POST',
      body: JSON.stringify({ download_url: _updateUrl }),
    });
    clearInterval(tick);
    barra.style.width = '100%';
    logEl.textContent = '✓ Actualización iniciada.\nARKHON se reiniciará en ~30 segundos.\nEsta página recargará automáticamente.';

    // Recargar página después del reinicio
    setTimeout(() => location.reload(), 35000);
  } catch(e) {
    clearInterval(tick);
    logEl.textContent = 'Error: ' + e.message;
    btnApply.disabled = false;
    btnCheck.disabled = false;
    pintarBotonesActualizacion('retry');
  }
}

function detenerPollingActualizador() {
  if (_updatePollTimer) clearInterval(_updatePollTimer);
  _updatePollTimer = null;
}

function errorActualizador(job) {
  return job?.error || job?.result?.error || job?.result?.message || 'Error desconocido del actualizador.';
}

function pintarJobActualizador(job) {
  _actualizadorJob = job || _actualizadorJob;
  const progreso = document.getElementById('u-progreso');
  const barra = document.getElementById('u-barra');
  const logEl = document.getElementById('u-log');
  const notas = document.getElementById('u-notas');
  const phase = document.getElementById('u-phase');
  const status = document.getElementById('u-job-status');
  const jobId = document.getElementById('u-job-id');
  const diag = document.getElementById('u-btn-diag');
  const progress = Math.max(0, Math.min(100, Number(job?.progress || 0)));

  progreso.style.display = '';
  barra.style.width = progress + '%';
  phase.textContent = job?.phase || '-';
  status.textContent = job?.status || '-';
  jobId.textContent = job?.job_id || '-';
  if (diag) diag.style.display = '';

  const details = job?.details && Object.keys(job.details).length ? '\nDetalles: ' + JSON.stringify(job.details) : '';
  const err = job?.error ? '\nError: ' + job.error : '';
  logEl.textContent = `${job?.phase || 'Actualizacion en curso'}\nProgreso: ${progress}%\nEstado: ${job?.status || '-'}\nJob: ${job?.job_id || '-'}${err}${details}`;
  notas.textContent = job?.phase || 'Actualizacion en curso';
}

function timeoutActualizador(job) {
  const tenMinutes = 10 * 60 * 1000;
  return _updateLastChangeAt && Date.now() - _updateLastChangeAt > tenMinutes && !UPDATE_TERMINAL_STATUSES.has(job?.status);
}

async function finalizarJobActualizador(job) {
  detenerPollingActualizador();
  const btnApply = document.getElementById('u-btn-apply');
  const btnCheck = document.getElementById('u-btn-check');
  const notas = document.getElementById('u-notas');

  if (job.status === 'completed') {
    notas.textContent = 'Actualizacion completada. Actualizando version visible...';
    const estado = await api('/actualizador/estado').catch(() => null);
    if (estado) pintarEstadoActualizador(estado, true);
    pintarJobActualizador(job);
    btnApply.disabled = false;
    btnCheck.disabled = false;
    return;
  }

  if (job.status === 'no_update') {
    notas.textContent = 'No hay actualizacion runtime disponible.';
  } else if (job.status === 'rollback_completed') {
    notas.textContent = 'La actualizacion fallo y el rollback fue completado. Revisa diagnostico.';
  } else {
    notas.textContent = `${errorActualizador(job)}. Diagnostico: C:\\ProgramData\\ARKHON\\Updater\\jobs y C:\\ProgramData\\ARKHON\\Updater\\logs`;
  }
  btnApply.disabled = false;
  btnCheck.disabled = false;
  pintarBotonesActualizacion('retry');
}

async function consultarJobActualizador(jobId) {
  const job = await api('/actualizador/jobs/' + encodeURIComponent(jobId));
  if (!job?.ok) throw new Error(job?.error || 'No se pudo consultar el job.');
  if (job.status !== _updateLastStatus) {
    _updateLastStatus = job.status;
    _updateLastChangeAt = Date.now();
  }
  pintarJobActualizador(job);
  if (timeoutActualizador(job)) {
    detenerPollingActualizador();
    document.getElementById('u-notas').textContent = `La actualizacion sigue ejecutandose en segundo plano. Ultimo job: ${job.job_id || jobId}`;
    document.getElementById('u-btn-check').disabled = false;
    document.getElementById('u-btn-apply').disabled = false;
    return;
  }
  if (UPDATE_TERMINAL_STATUSES.has(job.status)) await finalizarJobActualizador(job);
}

async function iniciarPollingActualizador(jobId) {
  detenerPollingActualizador();
  _updateLastStatus = '';
  _updateLastChangeAt = Date.now();
  await consultarJobActualizador(jobId);
  _updatePollTimer = setInterval(() => {
    consultarJobActualizador(jobId).catch((e) => {
      detenerPollingActualizador();
      document.getElementById('u-log').textContent = 'Error consultando job: ' + e.message;
      document.getElementById('u-notas').textContent = 'No se pudo consultar el progreso real: ' + e.message;
      document.getElementById('u-btn-check').disabled = false;
      document.getElementById('u-btn-apply').disabled = false;
      pintarBotonesActualizacion('retry');
    });
  }, 2500);
}

async function copiarDiagnosticoActualizador() {
  const payload = {
    ...(_actualizadorEstado?.diagnostic || {}),
    currentJobId: _actualizadorJob?.job_id || _actualizadorEstado?.diagnostic?.currentJobId || null,
    currentJobStatus: _actualizadorJob?.status || _actualizadorEstado?.diagnostic?.currentJobStatus || null,
    currentJobError: _actualizadorJob?.error || null,
    diagnosticPaths: {
      jobs: 'C:\\ProgramData\\ARKHON\\Updater\\jobs',
      logs: 'C:\\ProgramData\\ARKHON\\Updater\\logs',
    },
  };
  const text = JSON.stringify(payload, null, 2);
  try {
    await navigator.clipboard.writeText(text);
    document.getElementById('u-notas').textContent = 'Diagnostico copiado.';
  } catch (_) {
    document.getElementById('u-log').textContent = text;
    document.getElementById('u-notas').textContent = 'No se pudo copiar automaticamente; el diagnostico queda visible abajo.';
  }
}

async function aplicarActualizacion(autoStart = false) {
  if (!autoStart && !confirm('Instalar la actualizacion runtime ZIP? ARKHON puede reiniciarse durante el proceso.')) return;

  const btnApply = document.getElementById('u-btn-apply');
  const btnCheck = document.getElementById('u-btn-check');
  btnApply.disabled = true;
  btnCheck.disabled = true;
  btnApply.textContent = 'Iniciando...';

  const progreso = document.getElementById('u-progreso');
  const barra = document.getElementById('u-barra');
  const logEl = document.getElementById('u-log');
  progreso.style.display = '';
  barra.style.width = '0%';
  logEl.textContent = 'Creando job en ARKHON Updater...\n';

  try {
    const d = await api('/actualizador/aplicar', {
      method: 'POST',
      body: JSON.stringify({ download_url: _updateUrl }),
    });
    if (!d?.ok) {
      const msg = d?.error === 'no_runtime_update_available'
        ? 'No hay actualizacion runtime disponible.'
        : (d?.message || d?.error || 'No se pudo iniciar la actualizacion.');
      logEl.textContent = msg;
      document.getElementById('u-notas').textContent = msg;
      btnApply.disabled = false;
      btnCheck.disabled = false;
      pintarBotonesActualizacion('retry');
      return;
    }
    _actualizadorEstado = {
      ...(_actualizadorEstado || {}),
      diagnostic: { ...(_actualizadorEstado?.diagnostic || {}), currentJobId: d.job_id },
    };
    logEl.textContent = `Job creado: ${d.job_id}\nEsperando progreso real del updater...`;
    document.getElementById('u-job-id').textContent = d.job_id || '-';
    await iniciarPollingActualizador(d.job_id);
  } catch(e) {
    logEl.textContent = 'Error: ' + e.message;
    btnApply.disabled = false;
    btnCheck.disabled = false;
    pintarBotonesActualizacion('retry');
  }
}

</script>
</body>
</html>
