<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<title>Arkhon Card</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"/>
<link rel="stylesheet" href="/css/responsive.css?v=4136d"/>
<style>
:root{--bg:#f6f7fb;--surface:#fff;--border:#e5e7ef;--text:#111827;--muted:#6b7280;--muted2:#9ca3af;--orange:#f97316;--orange-lt:rgba(249,115,22,.10);--green:#0f9f6e;--green-lt:rgba(15,159,110,.10);--red:#dc2626;--red-lt:rgba(220,38,38,.10);--blue:#2563eb;--blue-lt:rgba(37,99,235,.10);--amber:#d97706;--amber-lt:rgba(217,119,6,.12);--r:8px;--shadow:0 2px 8px rgba(17,24,39,.08)}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-family:"Plus Jakarta Sans",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.topbar{height:56px;background:var(--surface);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 18px;position:sticky;top:0;z-index:5}.brand{display:flex;align-items:center;gap:10px;min-width:0}.logo{width:34px;height:34px;border-radius:8px;background:var(--orange);color:#fff;display:grid;place-items:center}.logo svg{width:19px;height:19px;stroke:currentColor;fill:none;stroke-width:2}.title{font-weight:800;font-size:15px}.sub{font-size:11px;color:var(--muted2);margin-top:2px}.main{padding:16px;max-width:1380px;margin:0 auto}.tabs{display:flex;gap:8px;overflow:auto;margin-bottom:14px}.tab{border:1px solid var(--border);background:#fff;border-radius:8px;padding:9px 12px;font-size:12px;font-weight:800;color:var(--muted);cursor:pointer;white-space:nowrap}.tab.active{background:var(--orange);border-color:var(--orange);color:#fff}.grid{display:grid;gap:12px}.kpis{grid-template-columns:repeat(4,minmax(0,1fr));margin-bottom:14px}.kpi,.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow)}.kpi{padding:14px}.kpi-label{font-size:10px;font-weight:800;text-transform:uppercase;color:var(--muted2)}.kpi-val{font-size:23px;font-weight:800;margin-top:4px}.kpi-sub{font-size:11px;color:var(--muted);margin-top:2px}.card{padding:14px;margin-bottom:14px}.card-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}.card-title{font-size:13px;font-weight:800}.actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.btn{border:0;border-radius:8px;padding:8px 11px;font-size:12px;font-weight:800;cursor:pointer;display:inline-flex;align-items:center;gap:6px;min-height:34px}.btn-primary{background:var(--orange);color:#fff}.btn-danger{background:var(--red);color:#fff}.btn-blue{background:var(--blue);color:#fff}.btn-ghost{background:#fff;color:var(--muted);border:1px solid var(--border)}.input,select{border:1px solid var(--border);background:#fff;border-radius:8px;padding:8px 10px;font-size:12px;min-height:34px;width:100%}.form-grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:9px;align-items:end}.field label{display:block;font-size:10px;font-weight:800;text-transform:uppercase;color:var(--muted2);margin-bottom:5px}.section{display:none}.section.active{display:block}.status-row{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}.status{display:flex;align-items:flex-start;gap:10px;padding:12px;border-radius:8px;border:1px solid var(--border);background:#fff}.dot{width:10px;height:10px;border-radius:50%;margin-top:4px;background:var(--red)}.status.ok .dot{background:var(--green)}.status-title{font-size:12px;font-weight:800}.status-sub{font-size:11px;color:var(--muted);margin-top:3px;word-break:break-word}.table-wrap{overflow:auto;border:1px solid var(--border);border-radius:8px;background:#fff}table{width:100%;border-collapse:collapse;min-width:900px}th,td{font-size:12px;text-align:left;padding:9px 11px;border-bottom:1px solid var(--border);vertical-align:top}th{font-size:10px;text-transform:uppercase;color:var(--muted2);background:#fafafa;position:sticky;top:0}.badge{display:inline-flex;border-radius:999px;padding:3px 8px;font-size:10px;font-weight:800}.badge.ok{background:var(--green-lt);color:var(--green)}.badge.warn{background:var(--amber-lt);color:var(--amber)}.badge.err{background:var(--red-lt);color:var(--red)}.mono{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;color:#374151}.empty{padding:18px;text-align:center;color:var(--muted2);font-size:12px}.alert{padding:10px 12px;background:var(--amber-lt);color:#92400e;border:1px solid rgba(217,119,6,.25);border-radius:8px;font-size:12px;font-weight:700;margin-bottom:12px}.split{display:grid;grid-template-columns:1fr 1fr;gap:12px}.checkrow{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:800}.checkrow input{width:18px;height:18px}.map-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.map-item{padding:12px;border:1px solid var(--border);border-radius:8px;background:#fff}.map-title{font-size:12px;font-weight:800}.map-tables{font-size:11px;color:var(--orange);font-weight:700;margin-top:6px}.map-desc{font-size:12px;color:var(--muted);line-height:1.5;margin-top:7px}@media(max-width:1000px){.kpis,.status-row,.split,.map-grid{grid-template-columns:1fr}.form-grid{grid-template-columns:1fr 1fr}.main{padding:12px}.sub{display:none}}@media(max-width:620px){.form-grid{grid-template-columns:1fr}.topbar{padding:0 12px}}
/* ARKHON visual system */
:root{--bg:#f4f6fb;--surface:#fff;--border:#e8ebf3;--text:#172033;--muted:#667085;--muted2:#98a2b3;--orange:#ff6b18;--orange-deep:#e94f08;--orange-lt:#fff1e8;--navy:#182235;--green:#12a878;--red:#e5484d;--blue:#3178f6;--r:16px;--shadow:0 8px 24px rgba(24,34,53,.065)}
body{background:radial-gradient(circle at 70% -20%,rgba(255,107,24,.08),transparent 32%),var(--bg)}
.topbar{height:72px;padding:0 24px;border:0;border-bottom:1px solid rgba(232,235,243,.9);box-shadow:0 1px 0 rgba(24,34,53,.02);background:rgba(255,255,255,.93);backdrop-filter:blur(14px)}
.brand{gap:13px}.logo{width:42px;height:42px;border-radius:13px;background:linear-gradient(145deg,#ff8a35,#f45a0b);box-shadow:0 8px 18px rgba(249,115,22,.24);position:relative}.logo:after{content:"";position:absolute;inset:5px;border:1px solid rgba(255,255,255,.35);border-radius:9px}.logo svg{width:21px;height:21px;position:relative;z-index:1}.title{font-size:17px;letter-spacing:-.025em}.sub{font-size:11px;color:var(--muted);font-weight:500}
.main{padding:22px 24px 44px;max-width:1500px}.tabs{display:grid;grid-template-columns:repeat(auto-fit,minmax(108px,1fr));gap:7px;padding:0;margin-bottom:18px;border:0;border-radius:0;background:transparent;box-shadow:none;overflow:visible}.tab{border:1px solid var(--border);background:#fff;border-radius:10px;padding:10px 9px;color:#667085;font-size:11px;transition:background .18s,color .18s,transform .18s;box-shadow:0 2px 8px rgba(24,34,53,.035)}.tab:hover{background:#fff7ed;border-color:rgba(255,107,24,.25);color:var(--orange)}.tab.active{background:var(--navy);border-color:var(--navy);color:#fff;box-shadow:0 5px 12px rgba(24,34,53,.16)}
.dashboard-hero{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:22px 24px;margin-bottom:16px;border-radius:20px;color:#fff;background:linear-gradient(120deg,#182235 0%,#26334a 62%,#3a2b24 100%);box-shadow:0 16px 32px rgba(24,34,53,.14);overflow:hidden;position:relative}.dashboard-hero:after{content:"";position:absolute;width:220px;height:220px;border-radius:50%;right:-55px;top:-105px;border:38px solid rgba(255,107,24,.14)}.hero-copy{position:relative;z-index:1}.hero-eyebrow{display:flex;align-items:center;gap:7px;font-size:10px;text-transform:uppercase;letter-spacing:.12em;color:#ffb787;font-weight:800}.hero-eyebrow:before{content:"";width:7px;height:7px;border-radius:50%;background:#ff792b;box-shadow:0 0 0 5px rgba(255,121,43,.12)}.hero-title{font-size:22px;font-weight:800;letter-spacing:-.035em;margin-top:8px}.hero-text{max-width:640px;margin-top:6px;font-size:12px;line-height:1.6;color:#bdc6d5}.hero-mark{font-size:56px;font-weight:800;color:rgba(255,255,255,.07);letter-spacing:-.08em;position:relative;z-index:1}
.grid{gap:14px}.kpis{margin-bottom:16px}.kpi,.card{border:1px solid rgba(226,230,239,.92);border-radius:var(--r);box-shadow:var(--shadow)}.kpi{padding:17px 17px 16px;position:relative;overflow:hidden;min-height:114px}.kpi:before{content:"";display:block;width:30px;height:30px;border-radius:10px;margin-bottom:12px;background:var(--orange-lt);box-shadow:inset 0 0 0 1px rgba(255,107,24,.12)}.kpi:after{content:"";position:absolute;width:8px;height:8px;border-radius:50%;left:28px;top:28px;background:var(--orange)}.kpi:nth-child(2):before{background:#eef4ff;box-shadow:inset 0 0 0 1px rgba(49,120,246,.12)}.kpi:nth-child(2):after{background:var(--blue)}.kpi:nth-child(3):before{background:#eafaf4;box-shadow:inset 0 0 0 1px rgba(18,168,120,.12)}.kpi:nth-child(3):after{background:var(--green)}.kpi:nth-child(4):before{background:#f3efff;box-shadow:inset 0 0 0 1px rgba(124,58,237,.12)}.kpi:nth-child(4):after{background:#7c3aed}.kpi-label{font-size:9.5px;letter-spacing:.07em}.kpi-val{font-size:26px;letter-spacing:-.04em;margin-top:5px}.kpi-sub{font-size:10.5px}
.card{padding:18px;margin-bottom:16px}.card-head{margin-bottom:15px}.card-title{font-size:13px;letter-spacing:-.015em;display:flex;align-items:center;gap:9px}.card-title:before{content:"";width:4px;height:16px;border-radius:4px;background:linear-gradient(#ff8b39,#f45a0b)}
.status-row{gap:10px}.status{padding:14px;border-radius:13px;background:#fbfcfe}.dot{width:9px;height:9px;box-shadow:0 0 0 5px rgba(229,72,77,.09)}.status.ok .dot{box-shadow:0 0 0 5px rgba(18,168,120,.10)}.status-title{font-size:11.5px}.status-sub{font-size:10.5px}
.btn{border-radius:10px;padding:9px 13px;min-height:36px;transition:transform .15s,box-shadow .15s,background .15s}.btn:hover{transform:translateY(-1px)}.btn-primary{background:linear-gradient(135deg,#ff7b2c,#f35a0d);box-shadow:0 6px 14px rgba(249,115,22,.18)}.btn-danger{background:#e5484d}.btn-blue{background:#3178f6}.btn-ghost{background:#fff;color:#475467}.input,select{border-color:#e2e6ef;border-radius:10px;min-height:38px;background:#fbfcfe;outline:none;transition:border .15s,box-shadow .15s}.input:focus,select:focus{border-color:#ff8a42;box-shadow:0 0 0 3px rgba(255,107,24,.11);background:#fff}.field label{color:#7a8495;letter-spacing:.05em}
.table-wrap{border-radius:12px;border-color:#e6e9f1}th{background:#f8f9fc;color:#8892a3;letter-spacing:.055em}th,td{padding:11px 12px}.badge{padding:4px 9px}.empty{padding:30px 18px}.alert{border-radius:12px;padding:12px 14px}.map-item{border-radius:13px;background:#fbfcfe}
.form-grid>.actions{grid-column:1/-1;justify-content:flex-start;align-items:center;width:100%;padding-top:2px}
@media(max-width:1000px){.dashboard-hero{padding:19px}.hero-mark{display:none}.topbar{height:auto;min-height:68px}.kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.status-row,.split,.map-grid{grid-template-columns:1fr}.main{padding:16px}.sub{display:block}}
@media(max-width:620px){.topbar{align-items:flex-start;padding:13px 14px}.topbar>.actions{justify-content:flex-end}.brand{min-width:180px}.sub{display:none}.main{padding:12px}.dashboard-hero{border-radius:16px}.hero-title{font-size:19px}.kpis{grid-template-columns:1fr 1fr}.kpi{min-height:108px;padding:14px}.tabs{grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.form-grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<header class="topbar">
  <div class="brand">
    <div class="logo"><svg viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M7 9h6M7 13h10M17 5v14"/></svg></div>
    <div><div class="title">Arkhon Card</div><div class="sub">Tarjetas, cortesias, limites y auditoria RestCard</div></div>
  </div>
  <div class="actions">
    <button class="btn btn-ghost" onclick="goBack()">Volver</button>
    <button class="btn btn-ghost" onclick="loadAll()">Actualizar</button>
    <button class="btn btn-danger" onclick="applyLimits()">Aplicar limites</button>
  </div>
</header>

<main class="main">
  <div class="tabs">
    <button class="tab active" data-tab="dashboard" onclick="showTab('dashboard')">Dashboard</button>
    <button class="tab" data-tab="generator" onclick="showTab('generator')">Generador</button>
    <button class="tab" data-tab="groups" onclick="showTab('groups')">Grupos</button>
    <button class="tab" data-tab="cards" onclick="showTab('cards')">Tarjetas</button>
    <button class="tab" data-tab="titulars" onclick="showTab('titulars')">Titulares</button>
    <button class="tab" data-tab="courtesies" onclick="showTab('courtesies')">Cortesias</button>
    <button class="tab" data-tab="operations" onclick="showTab('operations')">Operaciones</button>
    <button class="tab" data-tab="rules" onclick="showTab('rules')">Reglas</button>
    <button class="tab" data-tab="usage" onclick="showTab('usage')">Consumos</button>
    <button class="tab" data-tab="swipes" onclick="showTab('swipes')">Deslizamientos</button>
    <button class="tab" data-tab="audit" onclick="showTab('audit')">Auditoria</button>
    <button class="tab" data-tab="map" onclick="showTab('map')">Mapa</button>
    <button class="tab" data-tab="config" onclick="showTab('config')">Configuracion</button>
  </div>

  <section id="sec-dashboard" class="section active">
    <div class="dashboard-hero">
      <div class="hero-copy">
        <div class="hero-eyebrow">Centro de fidelización</div>
        <div class="hero-title">Control inteligente de tarjetas y beneficios</div>
        <div class="hero-text">Administra saldos, cortesías, titulares y reglas de consumo desde una sola vista conectada con RestCard y SoftRestaurant.</div>
      </div>
      <div class="hero-mark">CARD</div>
    </div>
    <div class="grid kpis">
      <div class="kpi"><div class="kpi-label">Tarjetas RestCard</div><div class="kpi-val" id="k-rest-cards">-</div><div class="kpi-sub">tarjetas registradas</div></div>
      <div class="kpi"><div class="kpi-label">Movimientos</div><div class="kpi-val" id="k-rest-mov">-</div><div class="kpi-sub">historial RestCard</div></div>
      <div class="kpi"><div class="kpi-label">Productos monedero</div><div class="kpi-val" id="k-soft-prod">-</div><div class="kpi-sub">catalogo SoftRestaurant</div></div>
      <div class="kpi"><div class="kpi-label">Reglas activas</div><div class="kpi-val" id="k-rules">-</div><div class="kpi-sub">limites Arkhon Card</div></div>
    </div>
    <div class="card">
      <div class="card-head"><div class="card-title">Estado operativo</div></div>
      <div class="status-row" id="status-row"></div>
    </div>
    <div class="split">
      <div class="card"><div class="card-head"><div class="card-title">RestCard</div></div><div id="rest-counts"></div></div>
      <div class="card"><div class="card-head"><div class="card-title">SoftRestaurant</div></div><div id="soft-counts"></div></div>
    </div>
  </section>

  <section id="sec-cards" class="section">
    <div class="card">
      <div class="card-head">
        <div class="card-title">Tarjetas RestCard</div>
        <div class="actions">
          <input id="card-search" class="input" placeholder="Tarjeta, cliente o titular"/>
          <select id="card-status"><option value="">Todos</option><option value="1">Activos</option><option value="0">Suspendidos</option><option value="2">Cancelados</option></select>
          <select id="card-type"><option value="">Tipo tarjeta</option><option value="DESCUENTO_Y_PUNTOS">Descuento y puntos</option><option value="DESCUENTO">Descuento</option><option value="PUNTOS">Puntos</option><option value="NINGUNO">Ninguno</option><option value="CREDITO">Credito</option></select>
          <button class="btn btn-primary" onclick="loadCards()">Buscar</button>
        </div>
      </div>
      <div id="cards-table"></div>
    </div>
  </section>

  <section id="sec-groups" class="section">
    <div class="card">
      <div class="card-head"><div class="card-title">Administrador de grupos RestCard</div></div>
      <div class="form-grid">
        <div class="field"><label>Clave</label><input id="group-id" class="input" maxlength="3" value="001"/></div>
        <div class="field"><label>Descripcion</label><input id="group-desc" class="input" value="CASINO ARENIA"/></div>
        <div class="field"><label>Prefijo</label><input id="group-prefix" class="input" maxlength="3" value="021"/></div>
        <div class="field"><label>Longitud consecutivo</label><select id="group-len"><option>5</option><option>6</option><option>7</option><option>8</option><option>9</option><option selected>10</option></select></div>
        <div class="field"><label>Estado</label><select id="group-status"><option value="1">ACTIVO</option><option value="0">SUSPENDIDO</option><option value="2">CANCELADO</option></select></div>
        <div class="field"><label>Empresa</label><input id="group-client" class="input" value="0001"/></div>
        <div class="actions"><button class="btn btn-primary" onclick="saveGroup()">Guardar grupo</button><button class="btn btn-ghost" onclick="loadGroups()">Actualizar</button></div>
      </div>
    </div>
    <div class="split">
      <div class="card"><div class="card-head"><div class="card-title">Grupos</div></div><div id="groups-table"></div></div>
      <div class="card"><div class="card-head"><div class="card-title">Empresas por grupo</div></div><div id="group-companies-table"></div></div>
    </div>
    <div class="card">
      <div class="card-head"><div class="card-title">Entrega de tarjetas disponibles</div></div>
      <div class="form-grid">
        <div class="field"><label>Grupo</label><input id="delivery-group" class="input" value="001"/></div>
        <div class="field"><label>Cliente</label><input id="delivery-client" class="input" value="0001"/></div>
        <div class="field"><label>Tarjetas</label><input id="delivery-cards" class="input" placeholder="02100100001, 02100100002"/></div>
        <div class="actions"><button class="btn btn-primary" onclick="deliverCards()">Entregar</button><button class="btn btn-ghost" onclick="loadDelivery()">Ver stock</button></div>
      </div>
      <div id="delivery-table"></div>
    </div>
  </section>

  <section id="sec-generator" class="section">
    <div class="card">
      <div class="card-head"><div class="card-title">Generador nativo RestCard</div></div>
      <div class="form-grid">
        <div class="field"><label>Cantidad</label><input id="gen-count" class="input" type="number" min="1" value="1"/></div>
        <div class="field"><label>Prefijo</label><input id="gen-prefix" class="input" value="100"/></div>
        <div class="field"><label>Grupo</label><input id="gen-group" class="input" value="001"/></div>
        <div class="field"><label>Consecutivo inicial</label><input id="gen-start" class="input" type="number" min="1" value="1"/></div>
        <div class="field"><label>Largo consecutivo</label><input id="gen-len" class="input" type="number" min="5" max="10" value="10"/></div>
        <div class="field"><label>ID cliente</label><input id="gen-client" class="input" value="0001"/></div>
        <div class="field"><label>Clasificacion</label><input id="gen-class" class="input" value="GRAL"/></div>
        <div class="field"><label>Tipo tarjeta</label><select id="gen-policy"><option value="DESCUENTO_Y_PUNTOS">Descuento y puntos</option><option value="DESCUENTO">Descuento</option><option value="PUNTOS">Puntos</option><option value="NINGUNO">Ninguno</option><option value="CREDITO">Credito</option></select></div>
        <div class="field"><label>Vigencia dias</label><input id="gen-days" class="input" type="number" min="1" value="365"/></div>
        <div class="field"><label>Limite carga diario</label><input id="gen-daily-charge" class="input" type="number" value="0"/></div>
        <div class="field"><label>QA limite diario</label><input id="gen-daily-limit" class="input" type="number" value="10"/></div>
        <div class="actions"><button class="btn btn-ghost" onclick="previewCards()">Vista previa</button><button class="btn btn-primary" onclick="createCards()">Generar real</button><button class="btn btn-blue" onclick="seedQa()">Seed QA</button></div>
      </div>
    </div>
    <div class="card"><div class="card-head"><div class="card-title">Resultado generador</div></div><div id="generator-result"><div class="empty">Sin vista previa</div></div></div>
  </section>

  <section id="sec-courtesies" class="section">
    <div class="card">
      <div class="card-head"><div class="card-title">Catalogo nativo de cortesias SoftRestaurant</div><button class="btn btn-ghost" onclick="loadCourtesies()">Actualizar</button></div>
      <div class="form-grid">
        <div class="field"><label>Clave</label><input id="courtesy-id" class="input" maxlength="5" value="01"/></div>
        <div class="field"><label>Descripcion</label><input id="courtesy-desc" class="input" maxlength="40" value="GENERAL"/></div>
        <div class="field"><label>Estatus</label><select id="courtesy-status"><option value="1">Activo</option><option value="0">Inactivo</option></select></div>
        <div class="field"><label>Vigencia dias</label><input id="courtesy-days" class="input" type="number" min="1" max="999" value="30"/></div>
        <div class="field"><label>Producto</label><input id="courtesy-product" class="input" placeholder="Clave o descripcion"/></div>
        <div class="field"><label>Cantidad por producto</label><input id="courtesy-qty" class="input" type="number" min="1" max="99" step="1" value="1"/></div>
        <div class="field"><label>Tarjetas a activar</label><input id="courtesy-save-cards" class="input" placeholder="Opcional: tarjetas separadas por coma"/></div>
        <div class="actions"><button class="btn btn-ghost" onclick="searchCourtesyProducts()">Buscar producto</button><button class="btn btn-blue" onclick="addCourtesyProduct()">Agregar producto</button><button class="btn btn-blue" onclick="addSearchResultsToCourtesy()">Agregar resultados</button><button class="btn btn-blue" onclick="addFullMenuToCourtesy()">Agregar todo el menu</button><button class="btn btn-primary" onclick="saveCourtesy()">Guardar y activar</button></div>
      </div>
    </div>
    <div class="split">
      <div class="card"><div class="card-head"><div class="card-title">Productos de la cortesia</div><div class="actions"><button class="btn btn-ghost" onclick="setCourtesyQtyAll()">Aplicar cantidad a todos</button><button class="btn btn-danger" onclick="clearCourtesyProducts()">Limpiar</button></div></div><div id="courtesy-products"><div class="empty">Sin productos</div></div></div>
      <div class="card"><div class="card-head"><div class="card-title">Resultados de producto</div></div><div id="product-results"><div class="empty">Busca por clave, descripcion, nombre corto o PLU</div></div></div>
    </div>
    <div class="card">
      <div class="card-head"><div class="card-title">Activar tarjetas para cortesia</div></div>
      <div class="form-grid">
        <div class="field"><label>Cortesia</label><input id="courtesy-activate-id" class="input" value="01"/></div>
        <div class="field"><label>Tarjetas</label><input id="courtesy-cards" class="input" placeholder="9900010000003, 9900010000004"/></div>
        <div class="field"><label>Fecha inicio</label><input id="courtesy-start" class="input" type="date"/></div>
        <div class="field"><label>Fecha fin</label><input id="courtesy-end" class="input" type="date"/></div>
        <div class="actions"><button class="btn btn-primary" onclick="activateCourtesyCards()">Activar en Soft/RestCard</button></div>
      </div>
    </div>
    <div class="card"><div class="card-head"><div class="card-title">Cortesias registradas</div></div><div id="courtesies-table"></div></div>
  </section>

  <section id="sec-titulars" class="section">
    <div class="card">
      <div class="card-head"><div class="card-title">Asignacion de tarjetas para cada titular</div></div>
      <div class="form-grid">
        <div class="field"><label>Filtro</label><select id="titular-filter"><option value="sin_tarjeta">Sin tarjeta</option><option value="con_tarjeta">Con tarjeta</option><option value="all">Todos</option></select></div>
        <div class="field"><label>Buscar</label><input id="titular-search" class="input" placeholder="Nombre, telefono, email o tarjeta"/></div>
        <div class="field"><label>ID titular</label><input id="titular-id" class="input" placeholder="Nuevo o existente"/></div>
        <div class="field"><label>Tarjeta</label><input id="titular-card" class="input" placeholder="Numero de tarjeta"/></div>
        <div class="field"><label>Nombres</label><input id="titular-name" class="input" placeholder="Nombre titular"/></div>
        <div class="field"><label>Apellidos</label><input id="titular-last" class="input" placeholder="Apellidos"/></div>
        <div class="actions"><button class="btn btn-ghost" onclick="loadTitulars()">Buscar</button><button class="btn btn-primary" onclick="assignTitular()">Asignar tarjeta</button></div>
      </div>
    </div>
    <div class="card"><div class="card-head"><div class="card-title">Titulares</div></div><div id="titulars-table"></div></div>
  </section>

  <section id="sec-operations" class="section">
    <div class="card">
      <div class="card-head"><div class="card-title">Panel de administracion de operaciones</div></div>
      <div class="form-grid">
        <div class="field"><label>Tarjeta</label><input id="op-card" class="input" placeholder="Numero de tarjeta"/></div>
        <div class="field"><label>Tipo</label><select id="op-type"><option value="abono">Abono F3</option><option value="cargo">Cargo F4</option><option value="traspaso">Traspaso F6</option><option value="suspender">Suspender F7</option><option value="activar">Activar F8</option><option value="cancelar">Cancelar F9</option></select></div>
        <div class="field"><label>Importe</label><input id="op-amount" class="input" type="number" step="0.01" value="0"/></div>
        <div class="field"><label>Tarjeta destino</label><input id="op-target" class="input" placeholder="Solo traspaso"/></div>
        <div class="field"><label>Vencimiento</label><input id="op-expiry" class="input" type="date"/></div>
        <div class="field"><label>Motivo/observaciones</label><input id="op-note" class="input" placeholder="Motivo"/></div>
        <div class="actions"><button class="btn btn-primary" onclick="runCardOperation()">Ejecutar</button><button class="btn btn-ghost" onclick="resetExpiration()">Restablecer vencimiento</button></div>
      </div>
      <div id="operation-result"><div class="empty">Sin operacion</div></div>
    </div>
  </section>

  <section id="sec-rules" class="section">
    <div class="card">
      <div class="card-head"><div class="card-title">Regla de limite diario</div></div>
      <div class="form-grid">
        <div class="field"><label>Alcance</label><select id="rule-scope"><option value="card">Tarjeta</option><option value="client">Cliente</option><option value="classification">Clasificacion</option><option value="global">Global</option></select></div>
        <div class="field"><label>Tarjeta</label><input id="rule-card" class="input" placeholder="24700100101"/></div>
        <div class="field"><label>ID cliente</label><input id="rule-client" class="input" placeholder="0001"/></div>
        <div class="field"><label>Clasificacion</label><input id="rule-class" class="input" placeholder="GRAL"/></div>
        <div class="field"><label>Cortesia opcional</label><input id="rule-courtesy" class="input" placeholder="03001"/></div>
        <div class="field"><label>Limite diario</label><input id="rule-limit" class="input" type="number" min="1" value="10"/></div>
        <div class="field"><label>Accion</label><select id="rule-action"><option value="suspend_card">Suspender tarjeta</option><option value="client_only">Solo alertar</option><option value="internal_block">Bloqueo interno</option></select></div>
        <div class="field"><label>Activa</label><label class="checkrow"><input id="rule-active" type="checkbox" checked/> Habilitada</label></div>
        <div class="actions"><button class="btn btn-primary" onclick="saveRule()">Guardar regla</button><button class="btn btn-ghost" onclick="evaluateLimits()">Evaluar</button></div>
      </div>
    </div>
    <div class="card"><div class="card-head"><div class="card-title">Reglas configuradas</div></div><div id="rules-table"></div></div>
    <div class="card"><div class="card-head"><div class="card-title">Evaluacion</div></div><div id="eval-table"><div class="empty">Sin evaluar</div></div></div>
  </section>

  <section id="sec-usage" class="section">
    <div class="card">
      <div class="card-head">
        <div class="card-title">Consumos de cortesias por dia</div>
        <div class="actions"><input id="usage-card" class="input" placeholder="Tarjeta opcional"/><button class="btn btn-primary" onclick="loadUsage()">Buscar</button></div>
      </div>
      <div id="usage-table"></div>
    </div>
  </section>

  <section id="sec-swipes" class="section">
    <div class="card">
      <div class="card-head">
        <div class="card-title">Actividad reciente por tarjeta</div>
        <div class="actions"><input id="swipe-card" class="input" placeholder="Tarjeta opcional"/><button class="btn btn-primary" onclick="loadSwipes()">Buscar</button></div>
      </div>
      <div id="swipes-table"></div>
    </div>
  </section>

  <section id="sec-audit" class="section">
    <div class="card"><div class="card-head"><div class="card-title">Auditoria Arkhon Card</div><button class="btn btn-primary" onclick="loadAudit()">Actualizar</button></div><div id="audit-table"></div></div>
  </section>

  <section id="sec-map" class="section">
    <div class="alert">Mapa levantado desde RestCard y desde las areas nativas de SoftRestaurant 11: Operaciones > Cortesias con tarjetas de monedero.</div>
    <div class="card"><div class="card-head"><div class="card-title">RestCard</div></div><div class="map-grid" id="rest-map"></div></div>
    <div class="card"><div class="card-head"><div class="card-title">SoftRestaurant</div></div><div class="map-grid" id="soft-map"></div></div>
  </section>

  <section id="sec-config" class="section">
    <div class="card">
      <div class="card-head"><div class="card-title">Conexion RestCard y modo productivo</div></div>
      <div class="form-grid">
        <div class="field"><label>Host/IP</label><input id="cfg-host" class="input"/></div>
        <div class="field"><label>Puerto</label><input id="cfg-port" class="input"/></div>
        <div class="field"><label>Usuario</label><input id="cfg-user" class="input"/></div>
        <div class="field"><label>Password</label><input id="cfg-password" class="input" type="password" autocomplete="new-password" placeholder="No cambiar"/></div>
        <div class="field"><label>Base de datos</label><input id="cfg-db" class="input"/></div>
        <div class="field"><label>Escritura</label><label class="checkrow"><input id="cfg-writes" type="checkbox"/> Productivo</label></div>
        <div class="field"><label>Automatico</label><label class="checkrow"><input id="cfg-auto" type="checkbox"/> Scheduler</label></div>
        <div class="actions"><button class="btn btn-primary" onclick="saveConfig()">Guardar configuracion</button><button class="btn btn-ghost" onclick="testStatus()">Probar conexion</button></div>
      </div>
    </div>
    <div class="card"><div class="card-head"><div class="card-title">Resultado</div></div><div id="config-result"><div class="empty">Sin cambios</div></div></div>
  </section>
</main>

<script>
let TOKEN = sessionStorage.getItem('arkhon_token') || '';
let courtesyProducts = [];
let courtesySearchResults = [];
async function ensureToken(){
  if(TOKEN) return TOKEN;
  const r=await fetch('/api/local-token');
  const d=await r.json();
  TOKEN=d.token||'';
  return TOKEN;
}
async function api(ep, opts={}){
  const token=await ensureToken();
  const r=await fetch('/api/arkhon-card'+ep,{method:opts.method||'GET',headers:{'Content-Type':'application/json','X-Api-Token':token,'bypass-tunnel-reminder':'true'},body:opts.body?JSON.stringify(opts.body):undefined});
  const d=await r.json();
  if(!d.ok) throw new Error(d.error||d.mensaje||'api_error');
  return d.data;
}
const fmt=n=>Number(n||0).toLocaleString('es-MX');
function esc(v){return String(v??'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
function table(rows, cols){
  if(!rows || !rows.length) return '<div class="empty">Sin datos</div>';
  return `<div class="table-wrap"><table><thead><tr>${cols.map(c=>`<th>${esc(c.label)}</th>`).join('')}</tr></thead><tbody>${rows.map(r=>`<tr>${cols.map(c=>`<td>${c.render?c.render(r):esc(r[c.key])}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`;
}
function countList(obj){return table(Object.entries(obj||{}).map(([table,rows])=>({table,rows})),[{key:'table',label:'Tabla'},{key:'rows',label:'Registros',render:r=>fmt(r.rows)}]);}
function goBack(){
  if(window.parent && window.parent !== window){
    try {
      if(typeof window.parent.switchTab === 'function'){
        window.parent.switchTab('dashboard', window.parent.document.querySelector('[data-tab="dashboard"]'));
        return;
      }
    } catch (_) {}
    window.parent.postMessage({tipo:'nav_dashboard'}, '*');
    return;
  }
  window.location.replace('/');
}
function showTab(name){
  document.querySelectorAll('.section').forEach(s=>s.classList.remove('active'));
  document.querySelectorAll('.tab').forEach(s=>s.classList.remove('active'));
  document.getElementById('sec-'+name).classList.add('active');
  const btn=document.querySelector(`[data-tab="${name}"]`); if(btn) btn.classList.add('active');
  if(name==='generator') loadCatalogs();
  if(name==='groups') { loadGroups(); loadDelivery(); }
  if(name==='cards') loadCards();
  if(name==='titulars') loadTitulars();
  if(name==='courtesies') loadCourtesies();
  if(name==='rules') loadRules();
  if(name==='usage') loadUsage();
  if(name==='swipes') loadSwipes();
  if(name==='audit') loadAudit();
  if(name==='config') loadConfig();
}
function renderStatus(status){
  const item=(name,s,sub)=>`<div class="status ${s.ok||s.schema_ready?'ok':''}"><div class="dot"></div><div><div class="status-title">${name}</div><div class="status-sub">${sub||''}</div>${s.error?`<div class="status-sub">${esc(s.error)}</div>`:''}</div></div>`;
  document.getElementById('status-row').innerHTML=
    item('RestCard',status.restcard,`${status.restcard.ok?'Conectado':'Sin conexion'} ${status.restcard.database||''}`)+
    item('SoftRestaurant',status.softrestaurant,`${status.softrestaurant.ok?'Conectado':'Sin conexion'} ${status.softrestaurant.database||''}`)+
    item('Arkhon Card',status.arkhon_card,`Escritura ${status.arkhon_card.writes_enabled?'ON':'OFF'} / Auto ${status.arkhon_card.auto_apply_enabled?'ON':'OFF'}`);
}
async function loadDashboard(){
  const d=await api('/dashboard');
  renderStatus(d.status);
  document.getElementById('k-rest-cards').textContent=fmt(d.restcard.tarjetas);
  document.getElementById('k-rest-mov').textContent=fmt(d.restcard.tarjetasmovtos);
  document.getElementById('k-soft-prod').textContent=fmt(d.softrestaurant.productosmonedero);
  document.getElementById('k-rules').textContent=fmt(d.rules.active);
  document.getElementById('rest-counts').innerHTML=countList(d.restcard);
  document.getElementById('soft-counts').innerHTML=countList(d.softrestaurant);
}
async function loadCards(){
  const q=document.getElementById('card-search').value.trim();
  const st=document.getElementById('card-status').value;
  const tp=document.getElementById('card-type').value;
  const qs=new URLSearchParams({limit:'200'});
  if(q) qs.set('search',q);
  if(st) qs.set('status',st);
  if(tp) qs.set('tipo_tarjeta',tp);
  const rows=await api('/cards?'+qs.toString());
  document.getElementById('cards-table').innerHTML=table(rows,[
    {key:'numerotarjeta',label:'Tarjeta',render:r=>`<span class="mono">${esc(r.numerotarjeta)}</span>`},
    {key:'titular',label:'Titular'},
    {key:'idcliente',label:'Cliente'},
    {key:'idclasificaciontarjeta',label:'Clasificacion'},
    {key:'tipo_tarjeta',label:'Tipo'},
    {key:'estatus',label:'Estado',render:r=>`<span class="badge ${Number(r.estatus)===1?'ok':'warn'}">${Number(r.estatus)===1?'Activa':'Suspendida'}</span>`},
    {key:'motivocancelacion',label:'Motivo'},
    {key:'saldomonederoelectronico',label:'Saldo'},
    {key:'saldopuntoselectronicos',label:'Puntos'},
    {key:'desc_recurrente',label:'Desc. rec.'},
    {key:'cred_limite',label:'Credito'},
    {key:'pin',label:'PIN'},
    {key:'acciones',label:'Acciones',render:r=>`<button class="btn btn-blue" onclick="activateCard('${esc(r.numerotarjeta)}','${esc(r.idcliente)}')">Activar</button> <button class="btn btn-danger" onclick="suspendCard('${esc(r.numerotarjeta)}','${esc(r.idcliente)}')">Suspender</button> <button class="btn btn-danger" onclick="cancelCard('${esc(r.numerotarjeta)}','${esc(r.idcliente)}')">Cancelar</button> <button class="btn btn-ghost" onclick="assignCard('${esc(r.numerotarjeta)}','${esc(r.idcliente)}')">Asignar</button> <button class="btn btn-ghost" onclick="syncSoftCard('${esc(r.numerotarjeta)}')">Sync Soft</button>`}
  ]);
}
async function activateCard(card,idcliente){await api('/cards/'+encodeURIComponent(card)+'/activate',{method:'POST',body:{idcliente,hours:24}});await Promise.all([loadCards(),loadAudit()]);}
async function suspendCard(card,idcliente){await api('/cards/'+encodeURIComponent(card)+'/suspend',{method:'POST',body:{idcliente}});await Promise.all([loadCards(),loadAudit()]);}
async function cancelCard(card,idcliente){
  const reason=prompt('Motivo de cancelacion','Cancelacion desde Arkhon Card'); if(reason===null) return;
  await api('/cards/'+encodeURIComponent(card)+'/cancel',{method:'POST',body:{idcliente,reason}});
  await Promise.all([loadCards(),loadAudit()]);
}
async function loadGroups(){
  const d=await api('/groups');
  document.getElementById('groups-table').innerHTML=table(d.groups,[
    {key:'clave_grupo',label:'Clave'},
    {key:'descripcion',label:'Descripcion'},
    {key:'prefijo',label:'Prefijo'},
    {key:'longconsecutivo',label:'Longitud'},
    {key:'estatus',label:'Estado',render:r=>Number(r.estatus)===1?'ACTIVO':Number(r.estatus)===2?'CANCELADO':'SUSPENDIDO'},
    {key:'stock_disponible',label:'Stock'},
    {key:'tarjetas_entregadas',label:'Entregadas'},
    {key:'accion',label:'Accion',render:r=>`<button class="btn btn-ghost" onclick="pickGroup('${esc(r.clave_grupo)}','${esc(r.descripcion)}','${esc(r.prefijo)}','${esc(r.longconsecutivo)}','${esc(r.estatus)}')">Editar</button>`}
  ]);
  document.getElementById('group-companies-table').innerHTML=table(d.companies,[
    {key:'clave_grupo',label:'Grupo'},
    {key:'idcliente',label:'Empresa'},
    {key:'nombrecomercial',label:'Nombre comercial'},
    {key:'estatus',label:'Estado'},
    {key:'contrasena',label:'Clave acceso'}
  ]);
}
function pickGroup(id,desc,prefix,len,status){
  document.getElementById('group-id').value=id;
  document.getElementById('group-desc').value=desc;
  document.getElementById('group-prefix').value=prefix;
  document.getElementById('group-len').value=len;
  document.getElementById('group-status').value=String(status);
  document.getElementById('delivery-group').value=id;
}
async function saveGroup(){
  await api('/groups',{method:'POST',body:{
    clave_grupo:document.getElementById('group-id').value,
    descripcion:document.getElementById('group-desc').value,
    prefijo:document.getElementById('group-prefix').value,
    longconsecutivo:Number(document.getElementById('group-len').value||10),
    estatus:document.getElementById('group-status').value,
    idcliente:document.getElementById('group-client').value
  }});
  await Promise.all([loadGroups(),loadCatalogs(),loadAudit()]);
}
async function loadDelivery(){
  const qs=new URLSearchParams({clave_grupo:document.getElementById('delivery-group')?.value||'001',idcliente:document.getElementById('delivery-client')?.value||'0001'});
  const rows=await api('/delivery?'+qs.toString());
  document.getElementById('delivery-table').innerHTML=table(rows,[
    {key:'numerotarjeta',label:'No. tarjeta'},
    {key:'idcliente',label:'Cliente'},
    {key:'entregado',label:'Entregado',render:r=>Number(r.entregado)===1?'Si':'No'},
    {key:'estatus',label:'Estado'},
    {key:'saldomonederoelectronico',label:'Saldo'}
  ]);
}
async function deliverCards(){
  const cards=document.getElementById('delivery-cards').value.split(/[,\s]+/).map(x=>x.trim()).filter(Boolean);
  await api('/delivery',{method:'POST',body:{clave_grupo:document.getElementById('delivery-group').value,idcliente:document.getElementById('delivery-client').value,cards}});
  await Promise.all([loadDelivery(),loadGroups(),loadCards(),loadAudit()]);
}
function generatorPayload(){
  return {
    count:Number(document.getElementById('gen-count').value||1),
    prefijo:document.getElementById('gen-prefix').value.trim(),
    clave_grupo:document.getElementById('gen-group').value.trim(),
    consecutivo_inicial:Number(document.getElementById('gen-start').value||1),
    longconsecutivo:Number(document.getElementById('gen-len').value||10),
    idcliente:document.getElementById('gen-client').value.trim(),
    classification:document.getElementById('gen-class').value.trim(),
    tipo_tarjeta:document.getElementById('gen-policy').value,
    vigencia_dias:Number(document.getElementById('gen-days').value||365),
    limitecargodiario:Number(document.getElementById('gen-daily-charge').value||0),
    daily_limit:Number(document.getElementById('gen-daily-limit').value||10)
  };
}
function renderCourtesyProducts(){
  document.getElementById('courtesy-products').innerHTML=table(courtesyProducts,[
    {key:'idproducto',label:'Clave'},
    {key:'descripcion',label:'Descripcion'},
    {key:'cantidad',label:'Limite 1-99'},
    {key:'accion',label:'Accion',render:r=>`<button class="btn btn-danger" onclick="removeCourtesyProduct('${esc(r.idproducto)}')">Eliminar</button>`}
  ]);
}
function courtesyQty(){
  const n=Number(document.getElementById('courtesy-qty').value||1);
  return Math.min(Math.max(Math.floor(Number.isFinite(n)?n:1),1),99);
}
async function searchCourtesyProducts(){
  const q=document.getElementById('courtesy-product').value.trim();
  const rows=await api('/soft-products?limit=500'+(q?'&search='+encodeURIComponent(q):''));
  courtesySearchResults=rows;
  document.getElementById('product-results').innerHTML=table(rows,[
    {key:'idproducto',label:'Clave'},
    {key:'descripcion',label:'Descripcion'},
    {key:'nombrecorto',label:'Nombre corto'},
    {key:'plu',label:'PLU'},
    {key:'accion',label:'Accion',render:r=>`<button class="btn btn-blue" onclick="pickCourtesyProduct('${esc(r.idproducto)}','${esc(r.descripcion)}')">Usar</button>`}
  ]);
}
function pickCourtesyProduct(idproducto, descripcion){
  document.getElementById('courtesy-product').value=idproducto;
  addCourtesyProduct(idproducto, descripcion);
}
function addCourtesyProduct(idproducto, descripcion){
  const id=String(idproducto||document.getElementById('courtesy-product').value||'').trim();
  if(!id) return;
  const qty=courtesyQty();
  const existing=courtesyProducts.find(x=>x.idproducto===id);
  if(existing){existing.cantidad=qty; if(descripcion) existing.descripcion=descripcion;}
  else courtesyProducts.push({idproducto:id, descripcion:descripcion||'', cantidad:qty});
  renderCourtesyProducts();
}
function addProductsToCourtesy(rows){
  const qty=courtesyQty();
  for(const row of rows||[]){
    const id=String(row.idproducto||'').trim();
    if(!id) continue;
    const existing=courtesyProducts.find(x=>x.idproducto===id);
    if(existing){existing.cantidad=qty; existing.descripcion=existing.descripcion||row.descripcion||'';}
    else courtesyProducts.push({idproducto:id, descripcion:row.descripcion||row.nombrecorto||'', cantidad:qty});
  }
  renderCourtesyProducts();
}
function addSearchResultsToCourtesy(){addProductsToCourtesy(courtesySearchResults);}
async function addFullMenuToCourtesy(){
  const rows=await api('/soft-products?limit=5000');
  courtesySearchResults=rows;
  addProductsToCourtesy(rows);
  document.getElementById('product-results').innerHTML=`<div class="alert">Menu cargado: ${fmt(rows.length)} productos. Cada producto queda con limite ${courtesyQty()}.</div>`;
}
function setCourtesyQtyAll(){
  const qty=courtesyQty();
  courtesyProducts=courtesyProducts.map(p=>({...p,cantidad:qty}));
  renderCourtesyProducts();
}
function clearCourtesyProducts(){
  courtesyProducts=[];
  renderCourtesyProducts();
}
function removeCourtesyProduct(idproducto){
  courtesyProducts=courtesyProducts.filter(x=>x.idproducto!==idproducto);
  renderCourtesyProducts();
}
async function loadCourtesies(){
  const rows=await api('/courtesies');
  const flat=[];
  for(const c of rows){
    flat.push({...c, idproducto:'', producto:'', cantidad:''});
    for(const p of c.productos||[]) flat.push({idcortesia:c.idcortesia,descripcion:c.descripcion,status:c.status,diasvigencia:c.diasvigencia,idproducto:p.idproducto,producto:p.descripcion,cantidad:p.cantidad});
  }
  document.getElementById('courtesies-table').innerHTML=table(flat,[
    {key:'idcortesia',label:'Clave'},
    {key:'descripcion',label:'Cortesia'},
    {key:'status',label:'Estatus',render:r=>Number(r.status)===1?'Activo':'Inactivo'},
    {key:'diasvigencia',label:'Vigencia'},
    {key:'idproducto',label:'Producto'},
    {key:'producto',label:'Descripcion producto'},
    {key:'cantidad',label:'Cantidad'},
    {key:'accion',label:'Accion',render:r=>`<button class="btn btn-ghost" onclick="editCourtesy('${esc(r.idcortesia)}')">Editar</button>`}
  ]);
}
async function editCourtesy(idcortesia){
  const rows=await api('/courtesies?idcortesia='+encodeURIComponent(idcortesia));
  const c=rows[0]; if(!c) return;
  document.getElementById('courtesy-id').value=c.idcortesia||'';
  document.getElementById('courtesy-activate-id').value=c.idcortesia||'';
  document.getElementById('courtesy-desc').value=c.descripcion||'';
  document.getElementById('courtesy-status').value=String(Number(c.status||0));
  document.getElementById('courtesy-days').value=Number(c.diasvigencia||30);
  courtesyProducts=(c.productos||[]).map(p=>({idproducto:p.idproducto,descripcion:p.descripcion,cantidad:p.cantidad}));
  renderCourtesyProducts();
}
async function saveCourtesy(){
  const body={
    idcortesia:document.getElementById('courtesy-id').value.trim(),
    descripcion:document.getElementById('courtesy-desc').value.trim(),
    status:document.getElementById('courtesy-status').value==='1',
    diasvigencia:Number(document.getElementById('courtesy-days').value||30),
    productos:courtesyProducts,
    replace_products:true,
    activate_cards:document.getElementById('courtesy-save-cards').value.trim(),
    fechainicio:document.getElementById('courtesy-start').value||undefined,
    fechafin:document.getElementById('courtesy-end').value||undefined
  };
  const d=await api('/courtesies',{method:'POST',body});
  document.getElementById('courtesy-activate-id').value=d.idcortesia;
  if(body.activate_cards) document.getElementById('courtesy-cards').value=body.activate_cards;
  await Promise.all([loadCourtesies(),loadDashboard(),loadAudit()]);
}
async function activateCourtesyCards(){
  const id=document.getElementById('courtesy-activate-id').value.trim();
  const cards=document.getElementById('courtesy-cards').value.split(/[,\s]+/).map(x=>x.trim()).filter(Boolean);
  await api('/courtesies/'+encodeURIComponent(id)+'/activate-cards',{method:'POST',body:{
    cards,
    fechainicio:document.getElementById('courtesy-start').value||undefined,
    fechafin:document.getElementById('courtesy-end').value||undefined,
    diasvigencia:Number(document.getElementById('courtesy-days').value||30)
  }});
  await Promise.all([loadCourtesies(),loadCards(),loadAudit()]);
}
async function loadCatalogs(){
  try{
    const c=await api('/catalogs');
    const g=(c.groups||[]).find(x=>Number(x.estatus)!==0)||(c.groups||[])[0];
    const cl=(c.group_clients||[]).find(x=>x.clave_grupo===g?.clave_grupo&&String(x.estatus)!=='0')||(c.group_clients||[]).find(x=>x.clave_grupo===g?.clave_grupo)||(c.group_clients||[])[0];
    const cls=(c.classifications||[]).find(x=>x.clave_grupo===g?.clave_grupo&&x.idcliente===(cl&&cl.idcliente))||(c.classifications||[]).find(x=>x.idcliente===(cl&&cl.idcliente))||(c.classifications||[])[0];
    if(g){document.getElementById('gen-group').value=g.clave_grupo||'001';document.getElementById('gen-prefix').value=g.prefijo||'100';document.getElementById('gen-len').value=g.longconsecutivo||10;}
    if(cl) document.getElementById('gen-client').value=cl.idcliente||'0001';
    if(cls) document.getElementById('gen-class').value=cls.idclasificaciontarjeta||cls.id||'GRAL';
  }catch(_){}
}
async function previewCards(){
  const d=await api('/generator/preview',{method:'POST',body:generatorPayload()});
  document.getElementById('generator-result').innerHTML=table(d.numbers,[{key:'numerotarjeta',label:'Tarjeta'},{key:'exists',label:'Existe',render:r=>r.exists?'Si':'No'}]);
}
async function createCards(){
  const d=await api('/generator/create',{method:'POST',body:generatorPayload()});
  document.getElementById('generator-result').innerHTML=`<div class="alert">Generadas: ${fmt(d.created.length)} / Omitidas: ${fmt(d.skipped.length)}</div>`+table(d.created,[{key:'numerotarjeta',label:'Tarjeta'},{key:'idcliente',label:'Cliente'},{key:'clave_grupo',label:'Grupo'},{key:'idclasificaciontarjeta',label:'Clasificacion'},{key:'estatus',label:'Estado'}]);
  await loadDashboard();
}
async function seedQa(){
  const d=await api('/qa/seed',{method:'POST',body:generatorPayload()});
  document.getElementById('generator-result').innerHTML=`<div class="alert">Seed QA creado. Regla QA: ${d.qa_rule?d.qa_rule.rule_id:'sin regla'}</div>`+table(d.created,[{key:'numerotarjeta',label:'Tarjeta QA'},{key:'idcliente',label:'Cliente'},{key:'clave_grupo',label:'Grupo'},{key:'idclasificaciontarjeta',label:'Clasificacion'}]);
  await Promise.all([loadCards(),loadRules(),loadAudit(),loadDashboard()]);
}
async function assignCard(card,idcliente){
  const targetClient=prompt('ID cliente RestCard',idcliente||'0001'); if(!targetClient) return;
  const softClient=prompt('ID cliente SoftRestaurant',String(targetClient).padStart(6,'0')); if(!softClient) return;
  const classification=prompt('Clasificacion RestCard',document.getElementById('gen-class')?.value||'GRAL')||'GRAL';
  await api('/cards/'+encodeURIComponent(card)+'/assignment',{method:'POST',body:{idcliente:targetClient,idcliente_soft:softClient,classification,clave_grupo:document.getElementById('gen-group')?.value||'001',active:true}});
  await Promise.all([loadCards(),loadAudit()]);
}
async function syncSoftCard(card){
  await api('/cards/'+encodeURIComponent(card)+'/sync-softrestaurant',{method:'POST',body:{assign_client:false}});
  await loadAudit();
}
async function loadTitulars(){
  const qs=new URLSearchParams();
  const filter=document.getElementById('titular-filter').value;
  const search=document.getElementById('titular-search').value.trim();
  if(filter) qs.set('filter',filter);
  if(search) qs.set('search',search);
  const rows=await api('/titulars?'+qs.toString());
  document.getElementById('titulars-table').innerHTML=table(rows,[
    {key:'idtitular',label:'Clave'},
    {key:'nombres',label:'Nombre'},
    {key:'apaterno',label:'Ap. paterno'},
    {key:'amaterno',label:'Ap. materno'},
    {key:'telefono',label:'Telefono'},
    {key:'celular',label:'Celular'},
    {key:'email',label:'E-mail'},
    {key:'numerotarjeta',label:'No. tarjeta'},
    {key:'accion',label:'Accion',render:r=>`<button class="btn btn-ghost" onclick="pickTitular('${esc(r.idtitular)}','${esc(r.nombres)}','${esc([r.apaterno,r.amaterno].filter(Boolean).join(' '))}','${esc(r.numerotarjeta||'')}')">Usar</button>`}
  ]);
}
function pickTitular(id,nombre,apellidos,card){
  document.getElementById('titular-id').value=id;
  document.getElementById('titular-name').value=nombre;
  document.getElementById('titular-last').value=apellidos;
  if(card) document.getElementById('titular-card').value=card;
}
async function assignTitular(){
  const last=document.getElementById('titular-last').value.trim().split(/\s+/);
  await api('/titulars/assign',{method:'POST',body:{
    idtitular:document.getElementById('titular-id').value||undefined,
    numerotarjeta:document.getElementById('titular-card').value,
    nombres:document.getElementById('titular-name').value||'GENERAL',
    apaterno:last[0]||'',
    amaterno:last.slice(1).join(' '),
    clave_grupo:document.getElementById('gen-group')?.value||'001',
    idcliente:document.getElementById('gen-client')?.value||'0001',
    classification:document.getElementById('gen-class')?.value||'GRAL'
  }});
  await Promise.all([loadTitulars(),loadCards(),loadAudit()]);
}
async function runCardOperation(){
  const card=document.getElementById('op-card').value.trim();
  if(!card) return;
  const d=await api('/cards/'+encodeURIComponent(card)+'/operation',{method:'POST',body:{
    type:document.getElementById('op-type').value,
    amount:Number(document.getElementById('op-amount').value||0),
    target_card:document.getElementById('op-target').value.trim()||undefined,
    fechavencimiento:document.getElementById('op-expiry').value||undefined,
    observaciones:document.getElementById('op-note').value
  }});
  document.getElementById('operation-result').innerHTML=table([d],[{key:'numerotarjeta',label:'Tarjeta'},{key:'type',label:'Operacion'},{key:'amount',label:'Importe'},{key:'saldo_anterior',label:'Saldo anterior'},{key:'saldo_actual',label:'Saldo actual'},{key:'target_card',label:'Destino'}]);
  await Promise.all([loadCards(),loadAudit()]);
}
async function resetExpiration(){
  const card=document.getElementById('op-card').value.trim();
  const fecha=document.getElementById('op-expiry').value;
  if(!card||!fecha) return;
  const d=await api('/cards/'+encodeURIComponent(card)+'/expiration',{method:'POST',body:{fechavencimiento:fecha}});
  document.getElementById('operation-result').innerHTML=table([d],[{key:'numerotarjeta',label:'Tarjeta'},{key:'fechavencimiento',label:'Vencimiento'},{key:'affected',label:'Actualizadas'}]);
  await Promise.all([loadCards(),loadAudit()]);
}
function rulePayload(){
  return {
    scope:document.getElementById('rule-scope').value,
    numerotarjeta:document.getElementById('rule-card').value.trim(),
    idcliente:document.getElementById('rule-client').value.trim(),
    idclasificaciontarjeta:document.getElementById('rule-class').value.trim(),
    idcortesia:document.getElementById('rule-courtesy').value.trim(),
    daily_limit:Number(document.getElementById('rule-limit').value||10),
    action_on_limit:document.getElementById('rule-action').value,
    active:document.getElementById('rule-active').checked
  };
}
async function saveRule(){await api('/rules',{method:'POST',body:rulePayload()});await loadRules();}
async function loadRules(){
  const rows=await api('/rules');
  document.getElementById('rules-table').innerHTML=table(rows,[
    {key:'rule_id',label:'ID'},
    {key:'scope',label:'Alcance'},
    {key:'numerotarjeta',label:'Tarjeta'},
    {key:'idcliente',label:'Cliente'},
    {key:'idclasificaciontarjeta',label:'Clasificacion'},
    {key:'idcortesia',label:'Cortesia'},
    {key:'daily_limit',label:'Limite'},
    {key:'action_on_limit',label:'Accion'},
    {key:'active',label:'Estado',render:r=>`<span class="badge ${r.active?'ok':'warn'}">${r.active?'Activa':'Inactiva'}</span>`}
  ]);
}
async function evaluateLimits(){
  const d=await api('/limits/evaluate',{method:'POST',body:{}});
  document.getElementById('eval-table').innerHTML=table(d.evaluations,[
    {key:'rule_id',label:'Regla'},
    {key:'numerotarjeta',label:'Tarjeta'},
    {key:'idcliente',label:'Cliente'},
    {key:'used_today',label:'Usadas'},
    {key:'daily_limit',label:'Limite'},
    {key:'decision',label:'Decision'},
    {key:'target_status',label:'Estado objetivo'}
  ]);
}
async function applyLimits(){
  const d=await api('/limits/apply',{method:'POST',body:{}});
  document.getElementById('eval-table').innerHTML=table(d.evaluations,[
    {key:'rule_id',label:'Regla'},
    {key:'numerotarjeta',label:'Tarjeta'},
    {key:'used_today',label:'Usadas'},
    {key:'daily_limit',label:'Limite'},
    {key:'decision',label:'Decision'},
    {key:'target_status',label:'Estado objetivo'}
  ]);
  await loadDashboard();
}
async function loadUsage(){
  const card=document.getElementById('usage-card').value.trim();
  const d=await api('/usage'+(card?'?numerotarjeta='+encodeURIComponent(card):''));
  document.getElementById('usage-table').innerHTML=table(d.rows,[{key:'numerotarjeta',label:'Tarjeta'},{key:'idcortesia',label:'Cortesia'},{key:'consumos',label:'Consumos'}]);
}
async function loadSwipes(){
  const card=document.getElementById('swipe-card').value.trim();
  const d=await api('/swipes?minutes=1440'+(card?'&numerotarjeta='+encodeURIComponent(card):''));
  document.getElementById('swipes-table').innerHTML=table(d.rows,[{key:'event_at',label:'Fecha'},{key:'source_table',label:'Origen'},{key:'numerotarjeta',label:'Tarjeta'},{key:'idcliente',label:'Cliente'},{key:'idcortesia',label:'Cortesia'},{key:'folio',label:'Folio'},{key:'event_type',label:'Tipo'}]);
}
async function loadAudit(){
  const rows=await api('/audit?limit=150');
  document.getElementById('audit-table').innerHTML=table(rows,[{key:'created_at',label:'Fecha'},{key:'event_name',label:'Evento'},{key:'numerotarjeta',label:'Tarjeta'},{key:'idcliente',label:'Cliente'},{key:'rule_id',label:'Regla'},{key:'actor',label:'Actor'}]);
}
async function loadMap(){
  const d=await api('/map');
  const render=items=>items.map(x=>`<div class="map-item"><div class="map-title">${esc(x.area)}</div><div class="map-tables">${esc((x.tables||[]).join(', '))}</div><div class="map-desc">${esc(x.behavior)}</div>${x.menu?`<div class="map-desc"><b>Menu:</b> ${esc(x.menu)}</div>`:''}</div>`).join('');
  document.getElementById('rest-map').innerHTML=render(d.restcard);
  document.getElementById('soft-map').innerHTML=render(d.softrestaurant);
}
async function loadConfig(){
  const c=await api('/config');
  document.getElementById('cfg-host').value=c.restcard_host||'';
  document.getElementById('cfg-port').value=c.restcard_port||'3306';
  document.getElementById('cfg-user').value=c.restcard_user||'root';
  document.getElementById('cfg-password').value='';
  document.getElementById('cfg-password').placeholder=c.restcard?.password_configured?'Configurado':'No configurado';
  document.getElementById('cfg-db').value=c.restcard_database||'restcard';
  document.getElementById('cfg-writes').checked=!!c.writes_enabled;
  document.getElementById('cfg-auto').checked=!!c.auto_apply_enabled;
}
async function saveConfig(){
  const body={restcard_host:cfgHost(),restcard_port:document.getElementById('cfg-port').value,restcard_user:document.getElementById('cfg-user').value,restcard_database:document.getElementById('cfg-db').value,writes_enabled:document.getElementById('cfg-writes').checked,auto_apply_enabled:document.getElementById('cfg-auto').checked};
  const pwd=document.getElementById('cfg-password').value;
  if(pwd) body.restcard_password=pwd;
  const d=await api('/config',{method:'POST',body});
  document.getElementById('config-result').innerHTML=`<div class="alert">Guardado. Escritura ${d.writes_enabled?'ON':'OFF'} / Automatico ${d.auto_apply_enabled?'ON':'OFF'}</div>`;
  await loadDashboard();
}
function cfgHost(){return document.getElementById('cfg-host').value.trim();}
async function testStatus(){const d=await api('/status');document.getElementById('config-result').innerHTML=table([{area:'RestCard',ok:d.restcard.ok,error:d.restcard.error||'',db:d.restcard.database||''},{area:'SoftRestaurant',ok:d.softrestaurant.ok,error:d.softrestaurant.error||'',db:d.softrestaurant.database||''}],[{key:'area',label:'Area'},{key:'ok',label:'OK',render:r=>r.ok?'Si':'No'},{key:'db',label:'BD'},{key:'error',label:'Error'}]);}
async function loadAll(){try{await Promise.all([loadDashboard(),loadMap()]);}catch(e){document.getElementById('status-row').innerHTML='<div class="alert">'+esc(e.message)+'</div>';}}
loadAll();
</script>
</body>
</html>
