<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover"/>
<title>Facturacion - 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"/>
<link rel="icon" type="image/png" href="/arkhon-icon.png?v=4137"/>
<style>
:root{--bg:#f7f8fc;--surface:#fff;--border:#eaecf4;--text:#111827;--text-2:#4b5563;--text-3:#9ca3af;--orange:#f97316;--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);--amber:#d97706;--amber-lt:rgba(217,119,6,.10);--shadow:0 2px 8px rgba(17,24,39,.08);--r:12px}
*{box-sizing:border-box}html,body{margin:0;font-family:"Plus Jakarta Sans",sans-serif;background:var(--bg);color:var(--text);font-size:13px}button,input,select,textarea{font-family:inherit}::-webkit-scrollbar{width:5px;height:5px}::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:8px}
.topbar{height:56px;background:var(--surface);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;padding:0 18px;position:sticky;top:0;z-index:5}.logo{width:32px;height:32px;border-radius:8px;background:var(--orange);display:grid;place-items:center;color:#fff}.title{font-weight:800}.sub{font-size:11px;color:var(--text-3)}.layout{display:grid;grid-template-columns:220px 1fr;min-height:calc(100vh - 56px)}.side{background:var(--surface);border-right:1px solid var(--border);padding:12px 0}.nav{display:flex;align-items:center;gap:9px;width:100%;border:0;background:none;padding:10px 18px;color:var(--text-2);font-size:12px;font-weight:700;cursor:pointer;text-align:left;border-left:3px solid transparent}.nav:hover{background:var(--bg);color:var(--text)}.nav.active{background:var(--orange-lt);color:var(--orange);border-left-color:var(--orange)}.nav svg,.btn svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.main{padding:18px;overflow:auto}.sec{display:none}.sec.active{display:block}.head{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin-bottom:14px}.head h1{font-size:18px;margin:0;font-weight:800}.head p{font-size:11px;color:var(--text-3);margin:3px 0 0}.tabs-actions{display:flex;gap:8px;flex-wrap:wrap}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px;margin-bottom:14px;box-shadow:var(--shadow)}.grid{display:grid;gap:12px}.kpis{grid-template-columns:repeat(4,minmax(0,1fr))}.kpi{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:14px}.kpi-label{font-size:10px;color:var(--text-3);font-weight:800;text-transform:uppercase;letter-spacing:.04em}.kpi-val{font-size:22px;font-weight:800;margin-top:4px}.kpi-sub{font-size:11px;color:var(--text-3);margin-top:2px}.filters{display:flex;gap:8px;align-items:end;flex-wrap:wrap}.f{display:flex;flex-direction:column;gap:5px}.f label{font-size:10px;color:var(--text-3);font-weight:800;text-transform:uppercase}.input,select,textarea{border:1px solid var(--border);border-radius:8px;background:var(--bg);color:var(--text);padding:8px 10px;font-size:12px;outline:none}.input:focus,select:focus,textarea:focus{border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-lt);background:#fff}textarea{min-height:74px;resize:vertical}.btn{display:inline-flex;align-items:center;gap:6px;border:0;border-radius:8px;padding:8px 12px;font-size:12px;font-weight:800;cursor:pointer}.btn-primary{background:var(--orange);color:#fff}.btn-ghost{background:var(--surface);border:1px solid var(--border);color:var(--text-2)}.btn-green{background:var(--green-lt);color:var(--green);border:1px solid #a7f3d0}.btn-red{background:var(--red-lt);color:var(--red);border:1px solid #fecaca}.btn-blue{background:var(--blue-lt);color:var(--blue);border:1px solid #bfdbfe}.btn:disabled{opacity:.5;cursor:not-allowed}.table-wrap{overflow:auto;border:1px solid var(--border);border-radius:10px;background:#fff}table{width:100%;border-collapse:collapse;min-width:820px}th,td{padding:10px 12px;border-bottom:1px solid var(--border);font-size:12px;text-align:left;vertical-align:middle}th{font-size:10px;color:var(--text-3);text-transform:uppercase;letter-spacing:.04em;background:#fafafa;position:sticky;top:0}tr:last-child td{border-bottom:0}.badge{display:inline-flex;padding:3px 8px;border-radius:999px;font-size:10px;font-weight:800}.ok{background:var(--green-lt);color:var(--green)}.warn{background:var(--amber-lt);color:var(--amber)}.err{background:var(--red-lt);color:var(--red)}.muted{color:var(--text-3)}.chart{display:grid;gap:7px}.bar{display:grid;grid-template-columns:140px 1fr 90px;align-items:center;gap:8px;font-size:11px}.bar-track{height:9px;background:#eef2f7;border-radius:999px;overflow:hidden}.bar-fill{height:100%;background:var(--orange);border-radius:999px}.form-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.form-grid.two{grid-template-columns:repeat(2,minmax(0,1fr))}.full{grid-column:1/-1}.wizard{display:flex;gap:8px;margin-bottom:14px}.step{flex:1;padding:8px;border:1px solid var(--border);border-radius:8px;font-size:11px;font-weight:800;color:var(--text-3);background:#fff}.step.active{border-color:var(--orange);background:var(--orange-lt);color:var(--orange)}.alert{display:none;margin-bottom:12px;padding:10px 12px;border-radius:8px;font-size:12px;font-weight:700}.alert.show{display:block}.alert.ok{background:var(--green-lt);color:#065f46}.alert.err{background:var(--red-lt);color:#991b1b}.audit{white-space:pre-wrap;font-size:11px;line-height:1.55;color:var(--text-2)}@media(max-width:900px){.layout{grid-template-columns:1fr}.side{display:flex;overflow:auto;border-right:0;border-bottom:1px solid var(--border);padding:8px}.nav{white-space:nowrap;border-left:0;border-bottom:3px solid transparent;border-radius:8px}.nav.active{border-left:0;border-bottom-color:var(--orange)}.kpis,.form-grid,.form-grid.two{grid-template-columns:1fr}.main{padding:12px}.bar{grid-template-columns:90px 1fr 70px}}
@media(max-width:1100px){
  .layout{grid-template-columns:184px 1fr}
  .kpis{grid-template-columns:repeat(2,minmax(0,1fr))}
  .sec#sec-dashboard>.grid[style*="grid-template-columns"]{grid-template-columns:1fr!important}
}
@media(max-width:900px){
  html,body{font-size:12px;min-height:100dvh;overflow:hidden}
  .topbar{height:auto;min-height:54px;padding:9px 12px;gap:9px}
  .logo{width:30px;height:30px;border-radius:8px;flex:0 0 30px}
  .title{font-size:15px}
  .sub{font-size:10px;line-height:1.25}
  #last-sync{display:none}
  .layout{display:flex;flex-direction:column;min-height:calc(100dvh - 54px)}
  .side{display:flex;gap:6px;overflow-x:auto;overflow-y:hidden;border-right:0;border-bottom:1px solid var(--border);padding:8px 10px;background:var(--surface);scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch}
  .nav{width:auto;min-width:max-content;flex:0 0 auto;height:40px;padding:0 12px;border-left:0;border-bottom:2px solid transparent;border-radius:10px;white-space:nowrap;scroll-snap-align:start;font-size:12px;background:#fff}
  .nav.active{border-left:0;border-bottom-color:var(--orange);box-shadow:inset 0 -1px 0 var(--orange)}
  .nav svg{width:14px;height:14px}
  .main{padding:12px;overflow:auto;min-height:0;flex:1;-webkit-overflow-scrolling:touch}
  .head{align-items:flex-start;gap:10px;flex-direction:column;margin-bottom:12px}
  .head h1{font-size:18px;line-height:1.15}
  .head p{font-size:12px;line-height:1.35}
  .tabs-actions{width:100%;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
  .tabs-actions .btn{justify-content:center}
  .card{border-radius:14px;padding:13px;margin-bottom:12px}
  .filters{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:stretch}
  .filters .f,.filters .input,.filters select{min-width:0;width:100%}
  .filters>.btn{justify-content:center}
  .kpis,.form-grid,.form-grid.two{grid-template-columns:1fr}
  .kpi{padding:12px;border-radius:12px}
  .kpi-val{font-size:20px}
  .bar{grid-template-columns:84px 1fr 72px;gap:7px}
  .bar span,.bar strong{font-size:10.5px;overflow:hidden;text-overflow:ellipsis}
  .wizard{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
  .step{padding:9px 8px;font-size:11px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .table-wrap{border-radius:12px;max-width:100%;overflow:auto;-webkit-overflow-scrolling:touch}
  table{min-width:720px}
  th,td{padding:9px 10px;font-size:11px}
  .btn{min-height:38px;justify-content:center}
}
@media(max-width:560px){
  .topbar{padding:8px 10px}
  .topbar .sub{display:none}
  .side{padding:7px 8px}
  .nav{height:38px;padding:0 10px;font-size:11.5px}
  .main{padding:10px 8px 14px}
  .filters{grid-template-columns:1fr}
  .tabs-actions{grid-template-columns:1fr}
  .card{padding:12px}
  .input,select,textarea{font-size:16px;padding:10px 11px;border-radius:12px}
  .btn{width:100%;padding:10px 12px}
  .wizard{grid-template-columns:1fr}
  .step{text-align:left}
  .bar{grid-template-columns:70px 1fr 66px}
  .audit{font-size:11px;overflow:auto}
}
@supports(padding:max(0px)){
  @media(max-width:900px){.main{padding-bottom:max(14px,env(safe-area-inset-bottom))}}
}

@media(min-width:901px){
  :root{--r:8px}
  html,body{font-size:12px}
  .topbar{height:48px;padding:0 14px}
  .logo{width:28px;height:28px;border-radius:7px}
  .layout{grid-template-columns:178px minmax(0,1fr);min-height:calc(100vh - 48px)}
  .side{padding:8px 0}
  .nav{padding:8px 14px;font-size:11px;gap:7px}
  .nav svg,.btn svg{width:13px;height:13px}
  .main{padding:12px;min-width:0}
  .head{padding:12px 14px;margin-bottom:10px;align-items:center}
  .head h1{font-size:16px}
  .head p{font-size:10px}
  .card{padding:12px;margin-bottom:10px}
  .filters{gap:7px}
  .input,select,textarea{padding:7px 9px;font-size:11px;border-radius:7px}
  .f{gap:4px;min-width:0}
  .f label{font-size:9px}
  .btn{padding:7px 10px;font-size:11px;border-radius:7px;min-height:32px}
  .kpis{gap:9px}
  .kpi{padding:11px;border-radius:8px}
  .kpi-label{font-size:9px}
  .kpi-val{font-size:18px}
  .kpi-sub{font-size:10px}
  .form-grid{gap:9px}
  .form-grid.two{grid-template-columns:repeat(2,minmax(0,1fr))}
  .table-wrap{border-radius:8px}
  table{min-width:720px}
  th,td{padding:8px 9px;font-size:11px}
  .wizard{gap:7px;margin-bottom:10px}
  .step{padding:7px;font-size:10px}
  .bar{grid-template-columns:minmax(72px,112px) minmax(0,1fr) minmax(64px,78px);gap:7px}
  .chart{min-height:118px}
  .sec#sec-dashboard>.grid[style*="grid-template-columns"]{gap:10px}
}

@media(min-width:901px) and (max-width:1280px){
  .layout{grid-template-columns:166px minmax(0,1fr)}
  .main{padding:10px}
  .head{padding:10px 12px}
  .head h1{font-size:15px}
  .filters{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));align-items:end}
  .filters>.btn{width:100%;justify-content:center}
  .kpis{grid-template-columns:repeat(4,minmax(0,1fr))}
  .form-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .sec#sec-dashboard>.grid[style*="grid-template-columns"]{grid-template-columns:1fr 1fr!important}
}

@media(min-width:901px) and (max-height:760px){
  .topbar{height:44px}
  .layout{min-height:calc(100vh - 44px)}
  .main{padding:9px}
  .head,.card{margin-bottom:8px}
  .card{padding:10px}
  .kpi{padding:9px}
  .kpi-val{font-size:17px}
  textarea{min-height:58px}
}

/* ARKHON Monitor polish layer */
body{min-height:100dvh;overflow:auto;background:
  radial-gradient(circle at 8% 0%, rgba(249,115,22,.10), transparent 32%),
  radial-gradient(circle at 90% 12%, rgba(16,185,129,.10), transparent 28%),
  linear-gradient(180deg,#fbfcff 0%,#f4f6fb 100%)}
.topbar{box-shadow:0 1px 3px rgba(17,24,39,.05);background:rgba(255,255,255,.92);backdrop-filter:blur(14px)}
.layout{min-width:0}
.side{overflow-y:auto;overflow-x:hidden}
.main{min-width:0;background:linear-gradient(180deg,#f8f9fd 0%,#f4f6fb 100%)}
.head{background:
  linear-gradient(135deg,rgba(255,247,237,.96),rgba(255,255,255,.94) 46%,rgba(236,253,245,.88));
  border:1px solid rgba(226,232,240,.95);border-radius:18px;padding:17px 18px;box-shadow:0 16px 38px rgba(17,24,39,.07)}
.head h1{letter-spacing:-.03em}
.card,.kpi,.table-wrap{box-shadow:0 10px 30px rgba(17,24,39,.06)}
.card{border-radius:16px}
.kpi{position:relative;overflow:hidden;border-radius:16px}
.kpi::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--orange)}
.kpi::after{content:"";position:absolute;right:-22px;top:-22px;width:64px;height:64px;border-radius:50%;background:rgba(249,115,22,.055)}
.kpi:nth-child(2)::before{background:var(--green)}
.kpi:nth-child(2)::after{background:rgba(16,185,129,.08)}
.kpi:nth-child(3)::before{background:var(--red)}
.kpi:nth-child(3)::after{background:rgba(239,68,68,.07)}
.kpi:nth-child(4)::before{background:var(--blue)}
.kpi:nth-child(4)::after{background:rgba(37,99,235,.07)}
.kpi-val{letter-spacing:-.04em}
.filters{align-items:end}
.filters .btn{min-height:35px}
.btn{border-radius:10px;transition:transform .15s,box-shadow .15s,background .15s}
.btn:hover{transform:translateY(-1px)}
.btn-primary{box-shadow:0 8px 20px rgba(249,115,22,.18)}
.btn-ghost{background:#fff}
.table-wrap{border-radius:16px;overflow:auto}
th{background:#fbfcff}
tbody tr:hover td{background:#fff7ed}
.chart{min-height:145px}
.chart:empty::before,.audit:empty::before{content:"Sin datos sincronizados";display:block;color:var(--text-3);font-weight:700;text-align:center;padding:36px 10px}
.card>strong{display:block;font-size:13px;letter-spacing:-.02em;margin-bottom:4px}
.bar{grid-template-columns:minmax(82px,140px) minmax(0,1fr) minmax(72px,90px)}
.bar span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bar-track{height:10px;background:#edf2f7}
.bar-fill{background:linear-gradient(90deg,#ff7a00,#ff9f43)}
.alert{box-shadow:0 10px 28px rgba(17,24,39,.06)}
@media(max-width:900px){
  html,body{overflow:auto}
  .topbar{position:sticky;top:0}
  .layout{min-height:calc(100dvh - 54px)}
  .side{position:sticky;top:54px;z-index:4}
  .main{overflow:auto}
  .head{padding:13px}
  .kpis{grid-template-columns:repeat(2,minmax(0,1fr))}
  .grid[style*="grid-template-columns"]{grid-template-columns:1fr!important}
}
@media(max-width:560px){
  .logo{display:none}
  .topbar{align-items:flex-start}
  .title{font-size:16px}
  .kpis{grid-template-columns:1fr}
  .table-wrap{border:0;background:transparent;box-shadow:none}
  .table-wrap table{min-width:680px;background:#fff;border:1px solid var(--border);border-radius:14px;overflow:hidden}
}
</style>
</head>
<body class="billing-view">
<div class="topbar">
  <div class="logo"><svg viewBox="0 0 24 24" width="17" height="17"><rect x="3" y="4" width="18" height="16" rx="2" fill="none" stroke="currentColor" stroke-width="2"/><path d="M7 8h10M7 12h10M7 16h5" fill="none" stroke="currentColor" stroke-width="2"/></svg></div>
  <div><div class="title">Facturacion</div><div class="sub">Portal de Autofactura como proveedor externo configurable por tenant</div></div>
  <div style="margin-left:auto" class="sub" id="last-sync">Cargando...</div>
</div>
<div class="layout">
  <aside class="side">
    <button class="nav active" onclick="showSec('dashboard',this)"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>Dashboard</button>
    <button class="nav" onclick="showSec('pending',this)"><svg viewBox="0 0 24 24"><path d="M4 4h16v16H4z"/><path d="M8 9h8M8 13h5"/></svg>Por facturar</button>
    <button class="nav" onclick="showSec('invoices',this)"><svg viewBox="0 0 24 24"><path d="M14 2H6v20h12V8z"/><path d="M14 2v6h6"/></svg>Facturados</button>
    <button class="nav" onclick="showSec('global',this)"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 010 18"/></svg>Factura global</button>
    <button class="nav" onclick="showSec('manual',this)"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>Sin ticket</button>
    <button class="nav" onclick="showSec('cancellations',this)"><svg viewBox="0 0 24 24"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/></svg>Cancelaciones</button>
    <button class="nav" onclick="showSec('reports',this)"><svg viewBox="0 0 24 24"><path d="M4 19V5M4 19h16"/><path d="M8 16v-5M13 16V8M18 16v-8"/></svg>Reportes</button>
  </aside>
  <main class="main">
    <div id="alert" class="alert"></div>

    <section class="sec active" id="sec-dashboard">
      <div class="head"><div><h1>Dashboard de facturacion</h1><p>KPIs, filtros rapidos y comportamiento por sucursal, mes y hora.</p></div><div class="tabs-actions"><button class="btn btn-ghost" onclick="syncTickets()">Sincronizar</button><button class="btn btn-primary" onclick="loadAll()">Actualizar</button></div></div>
      <div class="card filters">
        <div class="f"><label>Sucursal</label><select id="flt-branch"></select></div>
        <div class="f"><label>Fecha inicial</label><input class="input" type="date" id="flt-from"></div>
        <div class="f"><label>Fecha final</label><input class="input" type="date" id="flt-to"></div>
        <button class="btn btn-ghost" onclick="quickRange(0)">Hoy</button><button class="btn btn-ghost" onclick="quickRange(7)">7 dias</button><button class="btn btn-ghost" onclick="quickRange(30)">30 dias</button><button class="btn btn-ghost" onclick="monthRange()">Este mes</button><button class="btn btn-ghost" onclick="yearRange()">Este año</button>
        <button class="btn btn-primary" onclick="loadDashboard()">Aplicar</button><button class="btn btn-ghost" onclick="clearFilters()">Limpiar</button>
      </div>
      <div class="grid kpis">
        <div class="kpi"><div class="kpi-label">Ventas periodo</div><div class="kpi-val" id="k-ventas">0</div><div class="kpi-sub">tickets sincronizados</div></div>
        <div class="kpi"><div class="kpi-label">Monto facturado</div><div class="kpi-val" id="k-facturado">$0.00</div><div class="kpi-sub">CFDI emitidos</div></div>
        <div class="kpi"><div class="kpi-label">Cancelaciones</div><div class="kpi-val" id="k-cancelaciones">0</div><div class="kpi-sub">registradas</div></div>
        <div class="kpi"><div class="kpi-label">Tasa facturacion</div><div class="kpi-val" id="k-tasa">0%</div><div class="kpi-sub">vs. ventas</div></div>
      </div>
      <div class="grid" style="grid-template-columns:repeat(2,minmax(0,1fr))">
        <div class="card"><strong>Por sucursal</strong><div class="chart" id="chart-branch" style="margin-top:12px"></div></div>
        <div class="card"><strong>Por mes</strong><div class="chart" id="chart-month" style="margin-top:12px"></div></div>
        <div class="card"><strong>Por hora</strong><div class="chart" id="chart-hour" style="margin-top:12px"></div></div>
        <div class="card"><strong>Top productos</strong><div class="muted" style="margin-top:12px">Pendiente de endpoint confirmado por proveedor. No se inventan reglas ni datos fiscales.</div></div>
      </div>
    </section>

    <section class="sec" id="sec-pending">
      <div class="head"><div><h1>Tickets por facturar</h1><p>Listado pendiente con accion de timbrado.</p></div><button class="btn btn-primary" onclick="loadPending()">Actualizar</button></div>
      <div class="card filters"><input class="input" id="q-pending" placeholder="Buscar folio, RFC o ticket"><button class="btn btn-ghost" onclick="loadPending()">Buscar</button></div>
      <div class="table-wrap"><table><thead><tr><th>Folio</th><th>Sucursal</th><th>Fecha</th><th>Pago</th><th>Subtotal</th><th>Impuestos</th><th>Total</th><th></th></tr></thead><tbody id="pending-body"></tbody></table></div>
    </section>

    <section class="sec" id="sec-invoices">
      <div class="head"><div><h1>Tickets facturados</h1><p>PDF, XML, envio por correo y exportaciones.</p></div><div class="tabs-actions"><button class="btn btn-ghost" onclick="exportInvoices('csv')">CSV</button><button class="btn btn-ghost" onclick="exportInvoices('pdf')">PDF</button><button class="btn btn-primary" onclick="loadInvoices()">Actualizar</button></div></div>
      <div class="card filters"><input class="input" id="q-invoices" placeholder="Buscar UUID, folio, cliente o RFC"><button class="btn btn-ghost" onclick="loadInvoices()">Buscar</button></div>
      <div class="table-wrap"><table><thead><tr><th>Folio</th><th>Sucursal</th><th>Cliente</th><th>Pago</th><th>Total</th><th>Fecha</th><th>UUID</th><th></th></tr></thead><tbody id="invoices-body"></tbody></table></div>
    </section>

    <section class="sec" id="sec-global">
      <div class="head"><div><h1>Factura global</h1><p>Wizard de periodo, modo y seleccion/resumen.</p></div></div>
      <div class="wizard"><div class="step active" id="gw-s1">1. Periodo</div><div class="step" id="gw-s2">2. Modo</div><div class="step" id="gw-s3">3. Generar</div></div>
      <div class="card" id="global-step-1"><div class="form-grid"><div class="f"><label>Fecha inicial</label><input class="input" type="date" id="g-from"></div><div class="f"><label>Fecha final</label><input class="input" type="date" id="g-to"></div><div class="f"><label>Año</label><input class="input" type="number" id="g-year"></div><div class="f"><label>Mes</label><input class="input" type="number" min="1" max="12" id="g-month"></div><div class="f"><label>Periodicidad</label><select id="g-period"><option value="mensual">Mensual</option><option value="bimestral">Bimestral</option><option value="semanal">Semanal</option></select></div></div><div style="margin-top:14px"><button class="btn btn-primary" onclick="globalStep(2)">Continuar</button></div></div>
      <div class="card" id="global-step-2" style="display:none"><div class="form-grid"><div class="f"><label>Modo</label><select id="g-mode"><option value="all">Todos los tickets</option><option value="branch">Por sucursal</option><option value="cash">Efectivo</option><option value="card">Tarjeta</option></select></div><div class="f"><label>Forma de pago</label><select id="g-pay"><option>Efectivo</option><option>Tarjeta</option><option>Mixto</option><option>Todo</option></select></div><div class="f"><label>Total validado</label><input class="input" type="number" id="g-total" value="0"></div></div><div style="margin-top:14px"><button class="btn btn-ghost" onclick="globalStep(1)">Regresar</button> <button class="btn btn-primary" onclick="globalStep(3)">Continuar</button></div></div>
      <div class="card" id="global-step-3" style="display:none"><p class="muted">Se enviara una solicitud al proveedor. La validacion de importes y reglas CFDI global queda sujeta al contrato confirmado.</p><button class="btn btn-ghost" onclick="globalStep(2)">Regresar</button> <button class="btn btn-primary" onclick="createGlobal()">Generar CFDI global</button><div id="global-result" style="margin-top:12px"></div></div>
    </section>

    <section class="sec" id="sec-manual">
      <div class="head"><div><h1>Facturar sin ticket</h1><p>Formulario preparado para IVA/retenciones sin inventar reglas fiscales.</p></div></div>
      <div class="card"><div class="form-grid">
        <div class="f"><label>Monto MXN</label><input class="input" type="number" id="m-amount"></div><div class="f"><label>Forma de pago</label><select id="m-pay"><option>Efectivo</option><option>Tarjeta</option><option>Transferencia</option></select></div><div class="f"><label>Uso CFDI</label><input class="input" id="m-usage" placeholder="G03"></div>
        <div class="f full"><label>Concepto</label><input class="input" id="m-concept"></div><div class="f"><label>Clave producto/servicio</label><input class="input" id="m-product-key"></div><div class="f"><label>RFC receptor</label><input class="input" id="m-rfc"></div><div class="f"><label>Razon social receptor</label><input class="input" id="m-name"></div>
        <div class="f"><label>CP receptor</label><input class="input" id="m-zip"></div><div class="f"><label>Regimen fiscal receptor</label><input class="input" id="m-regime"></div><div class="f"><label>Email</label><input class="input" type="email" id="m-email"></div>
      </div><p class="muted">TODO fiscal: confirmar reglas de IVA, retenciones RESICO, claves SAT y validaciones CFDI con el proveedor.</p><button class="btn btn-primary" onclick="manualInvoice()">Timbrar</button><div id="manual-result" style="margin-top:12px"></div></div>
    </section>

    <section class="sec" id="sec-cancellations">
      <div class="head"><div><h1>Cancelaciones</h1><p>CFDI cancelados o en proceso.</p></div><button class="btn btn-primary" onclick="loadCancellations()">Actualizar</button></div>
      <div class="table-wrap"><table><thead><tr><th>UUID</th><th>Folio</th><th>Cliente</th><th>Total</th><th>Motivo SAT</th><th>Sustitucion</th><th>Estado</th><th>Acuse</th></tr></thead><tbody id="cancel-body"></tbody></table></div>
    </section>

    <section class="sec" id="sec-reports">
      <div class="head"><div><h1>Reportes</h1><p>Ventas, facturas emitidas, cancelaciones y cortes por periodo.</p></div></div>
      <div class="card filters"><select id="r-type"><option value="ventas">Ventas del periodo</option><option value="facturas">Facturas emitidas</option><option value="cancelaciones">Cancelaciones</option><option value="mes">Ventas por mes</option><option value="sucursal">Ventas por sucursal</option></select><button class="btn btn-primary" onclick="loadReport()">Ver</button><button class="btn btn-ghost" onclick="exportReport('csv')">CSV</button><button class="btn btn-ghost" onclick="exportReport('pdf')">PDF</button></div>
      <div class="card"><pre id="report-body" class="audit">Selecciona un reporte.</pre></div>
    </section>

  </main>
</div>
<script>
let KEY='';
const fmt=n=>'$'+Number(n||0).toLocaleString('es-MX',{minimumFractionDigits:2});
const $=id=>document.getElementById(id);
const esc=v=>String(v??'').replace(/[&<>"']/g,ch=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[ch]));
async function initToken(){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(_){}if(!KEY)KEY=localStorage.getItem('arkhon_admin_token')||localStorage.getItem('arkhon_token')||'';if(!KEY){try{const r=await fetch('/api/local-token');const d=await r.json();if(d.ok)KEY=d.token}catch(_){}}}
async function api(ep,opts={}){const r=await fetch('/api/billing'+ep,{...opts,headers:{'X-Api-Token':KEY,'Content-Type':'application/json',...(opts.headers||{})}});const ct=r.headers.get('content-type')||'';if(!ct.includes('application/json')){if(!r.ok)throw new Error(`Servicio de facturacion no disponible (${r.status})`);throw new Error('Respuesta invalida del servicio de facturacion')}const d=await r.json().catch(()=>null);if(r.status===401){show('Sesion expirada. Inicia sesion de nuevo.','err');throw new Error('Sesion expirada')}if(!r.ok||!d||!d.ok)throw new Error(d?.error||`Error API (${r.status})`);return d.data}
function show(msg,type='ok'){const a=$('alert');a.textContent=msg;a.className='alert show '+type;setTimeout(()=>a.className='alert',4500)}
function showSec(name,btn){document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));document.querySelectorAll('.nav').forEach(n=>n.classList.remove('active'));$('sec-'+name).classList.add('active');if(btn){btn.classList.add('active');btn.scrollIntoView({block:'nearest',inline:'center',behavior:'smooth'})}if(name==='pending')loadPending();if(name==='invoices')loadInvoices();if(name==='cancellations')loadCancellations()}
function params(extra={}){const p=new URLSearchParams();if($('flt-from').value)p.set('from',$('flt-from').value);if($('flt-to').value)p.set('to',$('flt-to').value);if($('flt-branch').value)p.set('branch_id',$('flt-branch').value);Object.entries(extra).forEach(([k,v])=>v&&p.set(k,v));return '?'+p.toString()}
function today(){return new Date().toLocaleDateString('sv-SE')}function quickRange(days){const d=new Date();const e=today();d.setDate(d.getDate()-days);$('flt-from').value=d.toLocaleDateString('sv-SE');$('flt-to').value=e;loadDashboard()}function monthRange(){const d=new Date();$('flt-from').value=`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-01`;$('flt-to').value=today();loadDashboard()}function yearRange(){const d=new Date();$('flt-from').value=`${d.getFullYear()}-01-01`;$('flt-to').value=today();loadDashboard()}function clearFilters(){$('flt-from').value='';$('flt-to').value='';$('flt-branch').value='';loadDashboard()}
function bars(id,rows){const max=Math.max(...(rows||[]).map(r=>Number(r.total||0)),1);$(id).innerHTML=(rows&&rows.length?rows:[{label:'Sin datos',total:0,count:0}]).map(r=>`<div class="bar"><span>${r.label}</span><div class="bar-track"><div class="bar-fill" style="width:${Math.round(Number(r.total||0)*100/max)}%"></div></div><strong>${fmt(r.total)}</strong></div>`).join('')}
async function loadBranches(){const branches=await api('/branches');const sel=$('flt-branch');sel.innerHTML='<option value="">Todas</option>'+(branches||[]).map(b=>`<option value="${esc(b.id)}">${esc(b.name)}</option>`).join('')}
async function syncTickets(){const d=await api('/sync',{method:'POST',body:'{}'});show(`Sincronizados: ${d.synced}`);await loadAll()}
async function loadDashboard(){const d=await api('/dashboard'+params());const metrics=d?.metrics||{};$('k-ventas').textContent=metrics.ventas_periodo||0;$('k-facturado').textContent=fmt(metrics.monto_facturado);$('k-cancelaciones').textContent=metrics.cancelaciones||0;$('k-tasa').textContent=Number(metrics.tasa_facturacion||0).toFixed(1)+'%';bars('chart-branch',Array.isArray(d?.by_branch)?d.by_branch:[]);bars('chart-month',Array.isArray(d?.by_month)?d.by_month:[]);const byHour=Array.isArray(d?.by_hour)?d.by_hour:[];bars('chart-hour',byHour.map(x=>({...x,label:String(x.label).padStart(2,'0')+':00'})));$('last-sync').textContent='Ultima actualizacion: '+new Date(d?.last_updated||Date.now()).toLocaleTimeString('es-MX')}
async function loadPending(){const q=$('q-pending')?.value||'';const rows=await api('/tickets/pending'+params({q}));$('pending-body').innerHTML=rows.map(r=>`<tr><td>${r.folio}</td><td>${r.branch_name||''}</td><td>${(r.date||'').slice(0,10)}</td><td>${r.payment_method||''}</td><td>${fmt(r.subtotal)}</td><td>${fmt(r.taxes)}</td><td><strong>${fmt(r.total)}</strong></td><td><button class="btn btn-green" onclick="invoiceTicket(${r.id})">Facturar</button></td></tr>`).join('')||'<tr><td colspan="8" class="muted">Sin tickets pendientes. Usa Sincronizar.</td></tr>'}
async function invoiceTicket(id){const rfc=prompt('RFC receptor');if(!rfc)return;const name=prompt('Razon social receptor')||'';const d=await api(`/tickets/${id}/invoice`,{method:'POST',body:JSON.stringify({rfc,legal_name:name,usage_cfdi:'G03'})});show('CFDI timbrado: '+d.uuid);loadPending();loadInvoices()}
async function blobDownload(ep,name){const r=await fetch('/api/billing'+ep,{headers:{'X-Api-Token':KEY}});if(!r.ok)throw new Error('No se pudo descargar');const b=await r.blob();const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download=name;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(a.href),1000)}
async function loadInvoices(){const q=$('q-invoices')?.value||'';const rows=await api('/invoices'+params({q}));$('invoices-body').innerHTML=rows.map(r=>`<tr><td>${r.folio||r.ticket_folio||''}</td><td>${r.branch_name||''}</td><td>${r.customer_name||r.customer_rfc||''}</td><td>${r.payment_method||''}</td><td><strong>${fmt(r.total)}</strong></td><td>${(r.stamped_at||r.created_at||'').slice(0,10)}</td><td><span class="muted">${r.uuid}</span></td><td><button class="btn btn-ghost" onclick="blobDownload('/invoices/${r.id}/pdf','cfdi-${r.id}.pdf')">PDF</button> <button class="btn btn-ghost" onclick="blobDownload('/invoices/${r.id}/xml','cfdi-${r.id}.xml')">XML</button> <button class="btn btn-blue" onclick="emailInvoice(${r.id})">Email</button> <button class="btn btn-red" onclick="cancelInvoice(${r.id})">Cancelar</button></td></tr>`).join('')||'<tr><td colspan="8" class="muted">Sin facturas emitidas.</td></tr>'}
function exportInvoices(format){blobDownload('/invoices'+params({format}),`facturas.${format}`)}
async function emailInvoice(id){const email=prompt('Email destino');if(!email)return;await api(`/invoices/${id}/email`,{method:'POST',body:JSON.stringify({email})});show('Correo enviado o aceptado por proveedor')}
async function cancelInvoice(id){const sat_reason=prompt('Motivo SAT (ej. 02)');if(!sat_reason)return;const replacement_uuid=prompt('UUID sustitucion si aplica')||'';await api(`/invoices/${id}/cancel`,{method:'POST',body:JSON.stringify({sat_reason,replacement_uuid})});show('Cancelacion solicitada');loadInvoices();loadCancellations()}
function globalStep(n){[1,2,3].forEach(i=>{$('global-step-'+i).style.display=i===n?'block':'none';$('gw-s'+i).classList.toggle('active',i===n)})}
async function createGlobal(){const d=new Date();const payload={from:$('g-from').value,to:$('g-to').value,year:$('g-year').value||d.getFullYear(),month:$('g-month').value||(d.getMonth()+1),periodicity:$('g-period').value,payment_scope:$('g-mode').value,total:$('g-total').value};const r=await api('/global-invoices',{method:'POST',body:JSON.stringify(payload)});$('global-result').innerHTML=`<span class="badge ok">UUID: ${r.uuid||'pendiente'}</span>`;show('Factura global generada')}
async function manualInvoice(){const payload={amount:$('m-amount').value,payment_method:$('m-pay').value,usage_cfdi:$('m-usage').value,concept:$('m-concept').value,product_service_key:$('m-product-key').value,receiver_rfc:$('m-rfc').value,receiver_name:$('m-name').value,receiver_postal_code:$('m-zip').value,receiver_fiscal_regime:$('m-regime').value,email:$('m-email').value};const r=await api('/manual-invoice',{method:'POST',body:JSON.stringify(payload)});$('manual-result').innerHTML=`<span class="badge ok">UUID: ${r.uuid}</span>`;show('Factura manual timbrada')}
async function loadCancellations(){const rows=await api('/cancellations');$('cancel-body').innerHTML=rows.map(r=>`<tr><td>${r.uuid}</td><td>${r.folio||''}</td><td>${r.customer_name||''}</td><td>${fmt(r.total)}</td><td>${r.sat_reason}</td><td>${r.replacement_uuid||''}</td><td><span class="badge warn">${r.status}</span></td><td>${r.acknowledgement_url_or_path||''}</td></tr>`).join('')||'<tr><td colspan="8" class="muted">Sin cancelaciones.</td></tr>'}
async function loadReport(){const t=$('r-type').value;const d=await api('/reports/'+t+params());const rows=Array.isArray(d?.rows)?d.rows:Array.isArray(d)?d:[];if(!rows.length){$('report-body').textContent='Sin datos reales para este reporte.';return}$('report-body').textContent=rows.map((row,i)=>`${i+1}. ${Object.entries(row).map(([k,v])=>`${k}: ${v??''}`).join(' | ')}`).join('\n')}
function exportReport(format){blobDownload('/reports/'+$('r-type').value+params({format}),`reporte-${$('r-type').value}.${format}`)}
async function guarded(label,fn){try{return await fn()}catch(e){show(`${label}: ${e.message}`,'err');return null}}
async function loadAll(){await guarded('Sucursales',loadBranches);await guarded('Dashboard',loadDashboard);await guarded('Tickets',loadPending);await guarded('Facturas',loadInvoices)}
(async()=>{await initToken();const d=new Date();$('g-year').value=d.getFullYear();$('g-month').value=d.getMonth()+1;const start=new Date();start.setDate(start.getDate()-30);$('flt-from').value=start.toLocaleDateString('sv-SE');$('flt-to').value=today();await loadAll()})()
</script>
</body>
</html>
