// EL POS LOCAL: la pagina que abren las TABLETS de los meseros contra la
// laptop (http://IP-de-la-laptop:8738). Funciona CON o SIN internet: menu del
// espejo local, pedidos que imprimen de una, cuentas por mesa y cobros. Entra
// con el PIN de sala (se fija en el asistente del cerebrito).
import { createServer, type IncomingMessage } from 'node:http';
import { randomUUID, timingSafeEqual } from 'node:crypto';
import type { Espejo } from './espejo.js';
import type { ItemEntrante, Salon } from './salon.js';

export const PUERTO_POS = 8738;

// ===== EL FRENO DE LA PUERTA (auditoria 2026-07-30, hallazgo 2) =====
//
// Este servicio escucha en TODA la red del local (0.0.0.0), porque las tablets
// tienen que entrar. Y se protegia con un PIN de cuatro numeros SIN NINGUN
// freno: cualquiera sentado en el local con la clave del wifi podia probar
// las diez mil combinaciones en segundos y quedar adentro tomando pedidos y
// cobrando.
//
// Ahora: cinco intentos por dispositivo y la espera CRECE (30s, 1m, 2m, 4m...
// hasta media hora). Probar diez mil PIN pasa de ser cuestion de segundos a
// ser imposible en la practica, sin fastidiar a la mesera que se equivoco.
const MAX_INTENTOS = 5;
const ESPERA_BASE_SEG = 30;
const ESPERA_TOPE_SEG = 1800;
const fallos = new Map<string, { veces: number; hasta: number }>();

/** De que dispositivo viene el intento. Sirve para no castigar a toda la sala. */
function deQuien(req: IncomingMessage): string {
  return req.socket.remoteAddress ?? 'desconocido';
}

/** Segundos que faltan para poder volver a probar. 0 = puede intentar. */
function trancadoHasta(quien: string): number {
  const f = fallos.get(quien);
  if (!f || f.hasta <= Date.now()) return 0;
  return Math.ceil((f.hasta - Date.now()) / 1000);
}

function anotarFallo(quien: string): number {
  const previo = fallos.get(quien);
  // Los intentos viejos no cuentan: cinco dedazos repartidos en una semana no
  // pueden dejar a la mesera trancada un lunes en la noche.
  const frescos = previo && Date.now() - (previo.hasta - ESPERA_BASE_SEG * 1000) < 3600_000;
  const veces = (frescos ? previo.veces : 0) + 1;
  const castigo = veces < MAX_INTENTOS
    ? 0
    : Math.min(ESPERA_BASE_SEG * 2 ** (veces - MAX_INTENTOS), ESPERA_TOPE_SEG);
  fallos.set(quien, { veces, hasta: Date.now() + castigo * 1000 });
  return veces;
}

function limpiarFallos(quien: string): void {
  fallos.delete(quien);
}

/**
 * Compara el PIN sin delatar nada por el tiempo que tarda. Con un PIN corto
 * el ataque por tiempo es teorico, pero comparar bien no cuesta nada.
 */
function mismoPin(dado: string, bueno: string): boolean {
  const a = Buffer.from(dado, 'utf8');
  const b = Buffer.from(bueno, 'utf8');
  return a.length === b.length && timingSafeEqual(a, b);
}

export interface ContextoPos {
  pin(): string | null;
  espejo(): Espejo | null;
  salon(): Salon | null;
  enReserva(): boolean;
  nubeViva(): boolean;
  log(m: string): void;
}

const PAGINA = `<!doctype html><html lang="es"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Gustito · Punto de venta</title>
<style>
  * { box-sizing: border-box; margin: 0; -webkit-tap-highlight-color: transparent; }
  body { background:#0D0F1E; color:#FAFAF9; font-family:system-ui,'Segoe UI',sans-serif; line-height:1.45; padding-bottom:120px; }
  header { position:sticky; top:0; z-index:10; background:#0D0F1E; border-bottom:1px solid #262A47; padding:12px 14px; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
  .marca { font-weight:800; font-size:17px; } .marca span { color:#FBBF24; }
  .pill { border-radius:999px; padding:3px 10px; font-size:11px; font-weight:800; }
  .pill.v { background:rgba(38,169,108,.16); color:#26A96C; }
  .pill.a { background:rgba(251,191,36,.16); color:#FBBF24; }
  .pill.g { background:rgba(138,143,175,.16); color:#8A8FAF; }
  .crece { flex:1; }
  main { padding:14px; max-width:760px; margin:0 auto; }
  h2 { font-size:16px; margin-bottom:10px; }
  button { border:none; border-radius:12px; padding:12px 14px; font-size:15px; font-weight:800; cursor:pointer; }
  .btn { background:#FBBF24; color:#0D0F1E; }
  .sec { background:#1C1F38; color:#FAFAF9; border:1px solid #363b5e; font-weight:600; }
  .peligro { background:#26A96C; color:#0D0F1E; }
  input, select { width:100%; background:#141627; color:#fff; border:1px solid #363b5e; border-radius:10px; padding:11px 12px; font-size:15px; }
  label { display:block; font-size:12px; color:#8A8FAF; font-weight:700; margin:10px 0 4px; }
  .grid-mesas { display:grid; grid-template-columns:repeat(auto-fill,minmax(96px,1fr)); gap:10px; }
  .mesa { border-radius:14px; padding:12px 6px; text-align:center; border:1px solid #262A47; background:#141627; }
  .mesa.ocupada { border-color:rgba(251,191,36,.6); background:#1C1F38; }
  .mesa b { display:block; font-size:16px; }
  .mesa small { color:#8A8FAF; font-size:11px; }
  .mesa.ocupada small { color:#FBBF24; font-weight:700; }
  .fila { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
  .tarjeta { background:#141627; border:1px solid #262A47; border-radius:14px; padding:14px; margin-top:10px; }
  .prod { display:flex; justify-content:space-between; gap:10px; padding:11px 4px; border-bottom:1px solid #232744; font-size:15px; }
  .prod:last-child { border-bottom:none; }
  .prod .p { color:#FBBF24; font-weight:800; white-space:nowrap; }
  .prod.agotado { opacity:.4; }
  .renglon { display:flex; justify-content:space-between; gap:8px; font-size:14px; padding:7px 0; border-bottom:1px dashed #232744; }
  .renglon small { color:#8A8FAF; display:block; }
  .total { display:flex; justify-content:space-between; font-size:17px; font-weight:800; margin-top:10px; }
  .total small { display:block; color:#8A8FAF; font-weight:600; font-size:12px; text-align:right; }
  .barra-abajo { position:fixed; left:0; right:0; bottom:0; background:#141627; border-top:1px solid #262A47; padding:12px 14px calc(12px + env(safe-area-inset-bottom)); display:flex; gap:10px; align-items:center; }
  .barra-abajo .tot { font-weight:800; font-size:16px; }
  .error { color:#f0716b; font-size:13.5px; margin-top:8px; }
  .ok { color:#26A96C; font-size:14px; margin-top:8px; font-weight:700; }
  .velo { position:fixed; inset:0; background:rgba(0,0,0,.65); z-index:20; display:flex; align-items:flex-end; }
  .hoja { background:#141627; width:100%; max-height:88vh; overflow:auto; border-radius:18px 18px 0 0; padding:16px 16px calc(16px + env(safe-area-inset-bottom)); }
  .opcion { display:flex; align-items:center; gap:10px; padding:9px 2px; font-size:15px; }
  .opcion input { width:auto; }
  .cant { display:flex; align-items:center; gap:14px; justify-content:center; margin:10px 0; }
  .cant button { width:46px; height:46px; font-size:22px; border-radius:50%; }
  .cant b { font-size:20px; min-width:30px; text-align:center; }
  .volver { background:transparent; border:none; color:#8A8FAF; font-size:14px; font-weight:700; padding:6px 0; }
</style></head><body>
<header>
  <span class="marca" id="mNombre">Gustito <span>Xpress</span></span>
  <span class="crece"></span>
  <span id="chips"></span>
</header>
<main id="app"><p style="color:#8A8FAF;margin-top:16px">Cargando...</p></main>
<script>
const app = document.getElementById('app');
const chips = document.getElementById('chips');
let TOKEN = localStorage.getItem('gx_pos_token') || '';
let MESERO = localStorage.getItem('gx_pos_mesero') || '';
let D = null;            // datos del servidor (menu, mesas, resumen)
let carrito = [];        // {producto_id, nombre, cantidad, nota, extra_usd, precio}
let contexto = null;     // {tipo:'mesa'|'directo', mesa}
let catActiva = '';

function esc(s){return String(s??'').replace(/[&<>"]/g,(c)=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));}
function usd(n){return '$' + (Math.round(n*100)/100).toFixed(2);}
function bs(n){const t=D&&D.restaurante&&D.restaurante.tasa_bs; if(!t) return ''; return 'Bs ' + (n*t).toLocaleString('es-VE',{minimumFractionDigits:2,maximumFractionDigits:2});}
async function api(ruta, cuerpo){
  const r = await fetch('/pos/' + ruta, { method: cuerpo?'POST':'GET', headers: Object.assign({'Content-Type':'application/json'}, TOKEN?{'x-pos':TOKEN}:{}), body: cuerpo?JSON.stringify(cuerpo):undefined });
  const j = await r.json().catch(()=>({error:'sin respuesta'}));
  if (r.status === 401) { TOKEN=''; localStorage.removeItem('gx_pos_token'); vistaEntrar(); throw new Error(j.error||'sesion'); }
  if (!r.ok) throw new Error(j.error || 'error');
  return j;
}
function pintarChips(){
  if(!D){chips.innerHTML='';return;}
  let h = '<span class="pill v">LOCAL</span>';
  if (D.resumen && D.resumen.ventasPorSubir > 0) h += ' <span class="pill a">' + D.resumen.ventasPorSubir + ' por subir</span>';
  h += D.nubeViva ? ' <span class="pill g">nube ok</span>' : ' <span class="pill g">sin internet</span>';
  chips.innerHTML = h;
}
async function cargar(){
  try { D = await api('datos'); document.getElementById('mNombre').innerHTML = esc(D.restaurante.nombre); pintarChips(); vistaMesas(); }
  catch(e){ if (TOKEN) app.innerHTML = '<p class="error">' + esc(e.message) + '</p>'; }
}
function vistaEntrar(){
  app.innerHTML = '<div class="tarjeta"><h2>Entrar al punto de venta</h2>' +
    '<label>PIN de sala</label><input id="pin" inputmode="numeric" type="password">' +
    '<label>Tu nombre (sale en la comanda)</label><input id="mesero" value="' + esc(MESERO) + '">' +
    '<div style="margin-top:12px"><button class="btn" id="go" style="width:100%">Entrar</button></div><div id="err" class="error"></div></div>';
  document.getElementById('go').onclick = async () => {
    try {
      MESERO = document.getElementById('mesero').value.trim();
      localStorage.setItem('gx_pos_mesero', MESERO);
      const j = await api('entrar', { pin: document.getElementById('pin').value.trim() });
      TOKEN = j.token; localStorage.setItem('gx_pos_token', TOKEN); cargar();
    } catch(e){ document.getElementById('err').textContent = e.message; }
  };
}
function vistaMesas(){
  carrito = []; contexto = null;
  let h = '<div class="fila" style="justify-content:space-between"><h2>Mesas</h2>' +
    '<button class="btn" id="directa">Venta directa</button></div><div class="grid-mesas">';
  for (const m of D.mesas) {
    h += '<button class="mesa ' + (m.abierta?'ocupada':'') + '" data-mesa="' + esc(m.mesa) + '"><b>Mesa ' + esc(m.mesa) + '</b>' +
      (m.abierta ? '<small>' + usd(m.total_usd) + '</small>' : '<small>libre</small>') + '</button>';
  }
  h += '</div><p style="margin-top:14px;color:#8A8FAF;font-size:12.5px">Atiende: <b style="color:#FAFAF9">' + esc(MESERO||'sin nombre') + '</b> · <button class="volver" id="cambiarM">cambiar</button></p>';
  app.innerHTML = h;
  document.getElementById('directa').onclick = () => { contexto = {tipo:'directo'}; vistaTomar(); };
  document.getElementById('cambiarM').onclick = () => { const n = prompt ? '' : ''; vistaEntrar(); };
  app.querySelectorAll('[data-mesa]').forEach((b) => b.onclick = () => vistaMesa(b.dataset.mesa));
}
async function vistaMesa(mesa){
  const j = await api('cuenta?mesa=' + encodeURIComponent(mesa));
  const c = j.cuenta;
  let h = '<button class="volver" id="volver">&larr; Mesas</button><h2>Mesa ' + esc(mesa) + '</h2>';
  if (!c || !c.pedidos.length) {
    h += '<div class="tarjeta"><p style="color:#8A8FAF">Mesa libre.</p><div style="margin-top:10px"><button class="btn" id="tomar" style="width:100%">Tomar pedido</button></div></div>';
  } else {
    let total = 0;
    h += '<div class="tarjeta">';
    for (const p of c.pedidos) {
      total += p.subtotal_usd;
      h += '<p style="font-size:12px;color:#8A8FAF;margin-top:6px">#' + esc(p.codigo) + (p.para_llevar?' · PARA LLEVAR':'') + '</p>';
      for (const it of p.items) {
        h += '<div class="renglon"><span>' + it.cantidad + 'x ' + esc(it.nombre) + (it.nota?'<small>' + esc(it.nota) + '</small>':'') + '</span><span>' + usd(it.precio_usd*it.cantidad) + '</span></div>';
      }
    }
    h += '<div class="total"><span>Total</span><span>' + usd(total) + '<small>' + bs(total) + '</small></span></div></div>' +
      '<div class="fila" style="margin-top:12px"><button class="sec" id="tomar" style="flex:1">Agregar pedido</button>' +
      '<button class="sec" id="precuenta" style="flex:1">Imprimir cuenta</button>' +
      '<button class="peligro" id="cobrar" style="flex:1">Cobrar</button></div><div id="resMesa" class="ok"></div>';
  }
  app.innerHTML = h;
  document.getElementById('volver').onclick = () => cargar();
  document.getElementById('tomar').onclick = () => { contexto = {tipo:'mesa', mesa: mesa}; vistaTomar(); };
  const bc = document.getElementById('cobrar');
  if (bc) bc.onclick = () => vistaCobrar(mesa, c);
  const bpc = document.getElementById('precuenta');
  if (bpc) bpc.onclick = async () => { try { await api('cuenta-imprimir', { mesa: mesa }); document.getElementById('resMesa').textContent = 'Cuenta impresa (no es factura).'; } catch(e){ alertaPropia(e.message); } };
}
function productosDeCat(){ return D.productos.filter((p)=>p.categoria_id===catActiva); }
function vistaTomar(){
  if (!catActiva || !D.categorias.some((c)=>c.id===catActiva)) catActiva = (D.categorias[0]||{}).id || '';
  let h = '<button class="volver" id="volver">&larr; Cancelar</button>' +
    '<h2>' + (contexto.tipo==='mesa' ? 'Pedido · Mesa ' + esc(contexto.mesa) : 'Venta directa') + '</h2>' +
    '<label>Categoría</label><select id="cat">' + D.categorias.map((c)=>'<option value="'+c.id+'"'+(c.id===catActiva?' selected':'')+'>'+esc(c.nombre)+'</option>').join('') + '</select>' +
    '<div class="tarjeta" id="prods"></div>';
  app.innerHTML = h + '<div id="hoja"></div>';
  pintarProds();
  pintarBarra();
  document.getElementById('volver').onclick = () => contexto.tipo==='mesa' ? vistaMesa(contexto.mesa) : cargar();
  document.getElementById('cat').onchange = (e) => { catActiva = e.target.value; pintarProds(); };
}
function pintarProds(){
  const cont = document.getElementById('prods');
  cont.innerHTML = productosDeCat().map((p,i) =>
    '<div class="prod ' + (p.disponible?'':'agotado') + '" data-i="' + i + '"><span>' + esc(p.nombre) + (p.disponible?'':' (agotado)') + '</span><span class="p">' + usd(p.precio_usd) + '</span></div>'
  ).join('') || '<p style="color:#8A8FAF">Sin productos en esta categoría.</p>';
  cont.querySelectorAll('.prod').forEach((el) => el.onclick = () => {
    const p = productosDeCat()[Number(el.dataset.i)];
    if (p && p.disponible) hojaProducto(p);
  });
}
function hojaProducto(p){
  let cant = 1;
  const grupos = Array.isArray(p.opciones) ? p.opciones : [];
  let h = '<div class="velo" id="velo"><div class="hoja"><h2>' + esc(p.nombre) + ' · ' + usd(p.precio_usd) + '</h2>' +
    '<div class="cant"><button class="sec" id="menos">-</button><b id="n">1</b><button class="sec" id="mas">+</button></div>';
  grupos.forEach((g, gi) => {
    const label = g.nombre || g.titulo || g.label || 'Opciones';
    h += '<label>' + esc(label) + '</label>';
    (g.opciones || []).forEach((op, oi) => {
      const extra = Number(op.precio_extra) || 0;
      h += '<div class="opcion"><input type="checkbox" data-g="' + gi + '" data-o="' + oi + '" id="op' + gi + '_' + oi + '">' +
        '<label for="op' + gi + '_' + oi + '" style="margin:0;font-size:15px;color:#FAFAF9">' + esc(op.nombre) + (extra?' (+' + usd(extra) + ')':'') + '</label></div>';
    });
  });
  h += '<label>Nota (opcional)</label><input id="notaIt">' +
    '<div class="fila" style="margin-top:14px"><button class="sec" id="cerrar" style="flex:1">Cancelar</button>' +
    '<button class="btn" id="agregar" style="flex:2">Agregar</button></div></div></div>';
  document.getElementById('hoja').innerHTML = h;
  const n = document.getElementById('n');
  document.getElementById('mas').onclick = () => { cant++; n.textContent = cant; };
  document.getElementById('menos').onclick = () => { if (cant>1) cant--; n.textContent = cant; };
  document.getElementById('cerrar').onclick = () => document.getElementById('hoja').innerHTML = '';
  document.getElementById('velo').onclick = (e) => { if (e.target.id==='velo') document.getElementById('hoja').innerHTML=''; };
  document.getElementById('agregar').onclick = () => {
    let extra = 0; const partes = [];
    grupos.forEach((g, gi) => {
      const elegidas = [];
      (g.opciones || []).forEach((op, oi) => {
        const box = document.getElementById('op' + gi + '_' + oi);
        if (box && box.checked) { elegidas.push(op.nombre); extra += Number(op.precio_extra) || 0; }
      });
      if (elegidas.length) partes.push((g.nombre || g.titulo || g.label || 'Opciones') + ': ' + elegidas.join(', '));
    });
    const libre = document.getElementById('notaIt').value.trim();
    if (libre) partes.push(libre);
    carrito.push({ producto_id: p.id, nombre: p.nombre, cantidad: cant, nota: partes.join(' · ') || null, extra_usd: extra, precio: p.precio_usd + extra });
    document.getElementById('hoja').innerHTML = '';
    pintarBarra();
  };
}
function totalCarrito(){ return carrito.reduce((s,i)=>s+i.precio*i.cantidad,0); }
function pintarBarra(){
  let b = document.getElementById('barra');
  if (!carrito.length) { if (b) b.remove(); return; }
  if (!b) { b = document.createElement('div'); b.className = 'barra-abajo'; b.id = 'barra'; document.body.appendChild(b); }
  b.innerHTML = '<span class="tot">' + carrito.length + ' renglon(es) · ' + usd(totalCarrito()) + '</span><span class="crece"></span>' +
    (contexto.tipo==='mesa'
      ? '<button class="btn" id="enviar">Enviar a cocina</button>'
      : '<button class="btn" id="cobrarD">Cobrar</button>');
  const be = document.getElementById('enviar');
  if (be) be.onclick = enviarMesa;
  const bd = document.getElementById('cobrarD');
  if (bd) bd.onclick = () => hojaCobro(null);
}
async function enviarMesa(){
  try {
    const j = await api('pedido', { mesa: contexto.mesa, items: carrito.map((i)=>({producto_id:i.producto_id,cantidad:i.cantidad,nota:i.nota,extra_usd:i.extra_usd})), paraLlevar:false, mesero: MESERO });
    carrito = []; const b = document.getElementById('barra'); if (b) b.remove();
    vistaMesa(contexto.mesa);
  } catch(e){ alertaPropia(e.message); }
}
function hojaCobro(mesaInfo){
  const total = mesaInfo ? mesaInfo.total : totalCarrito();
  let h = '<div class="velo" id="velo"><div class="hoja"><h2>Cobrar ' + usd(total) + '</h2>' +
    '<p style="color:#8A8FAF;font-size:13px">' + bs(total) + '</p>' +
    '<label>Método de pago</label><select id="metodo"><option value="efectivo">Efectivo</option><option value="pago_movil">Pago Móvil</option><option value="transferencia">Transferencia</option><option value="zelle">Zelle</option></select>' +
    '<label>Referencia (si aplica)</label><input id="ref" inputmode="numeric">' +
    (mesaInfo ? '<label>Propina (opcional, $)</label><input id="prop" inputmode="decimal" value="">' : '') +
    ('<div class="fila" style="margin-top:14px"><button class="sec" id="cerrar" style="flex:1">Cancelar</button>' +
    '<button class="peligro" id="conf" style="flex:2">Confirmar cobro</button></div><div id="errC" class="error"></div></div></div>');
  document.getElementById('hoja').innerHTML = h;
  document.getElementById('cerrar').onclick = () => document.getElementById('hoja').innerHTML = '';
  document.getElementById('conf').onclick = async () => {
    const metodo = document.getElementById('metodo').value;
    const ref = document.getElementById('ref').value.trim();
    try {
      if (mesaInfo) {
        const prop = Number((document.getElementById('prop').value||'').replace(',','.')) || 0;
        await api('cobrar', { mesa: mesaInfo.mesa, metodo: metodo, referencia: ref, propina_usd: prop, mesero: MESERO });
      } else {
        await api('venta', { items: carrito.map((i)=>({producto_id:i.producto_id,cantidad:i.cantidad,nota:i.nota,extra_usd:i.extra_usd})), paraLlevar:true, mesero: MESERO, metodo: metodo, referencia: ref });
        carrito = []; const b = document.getElementById('barra'); if (b) b.remove();
      }
      document.getElementById('hoja').innerHTML = '';
      cargar();
    } catch(e){ document.getElementById('errC').textContent = e.message; }
  };
}
function vistaCobrar(mesa, c){
  const total = c.pedidos.reduce((s,p)=>s+p.subtotal_usd,0);
  if (!document.getElementById('hoja')) { const d = document.createElement('div'); d.id='hoja'; app.appendChild(d); }
  hojaCobro({ mesa: mesa, total: total });
}
function alertaPropia(msj){
  const d = document.createElement('div');
  d.className = 'velo';
  d.innerHTML = '<div class="hoja"><p class="error" style="font-size:15px">' + esc(msj) + '</p><div style="margin-top:12px"><button class="btn" style="width:100%">Entendido</button></div></div>';
  d.querySelector('button').onclick = () => d.remove();
  document.body.appendChild(d);
}
if (!TOKEN) vistaEntrar(); else cargar();
setInterval(() => { if (TOKEN && D && !contexto) cargar(); }, 25000);

// VIGIA DEL INTERNET: cuando la red del mundo vuelve, este POS local avisa y
// REGRESA SOLO al panel normal (si el mesero no esta a mitad de un pedido).
const URL_PANEL = 'https://gustitoxpress.app';
let internetVivo = false;
let regresando = false;
async function probarInternet(){
  try {
    const ctl = new AbortController();
    const t = setTimeout(() => ctl.abort(), 4000);
    await fetch(URL_PANEL + '/favicon.ico?x=' + Date.now(), { mode: 'no-cors', cache: 'no-store', signal: ctl.signal });
    clearTimeout(t);
    return true;
  } catch(e){ return false; }
}
function bannerRegreso(){
  if (regresando || document.getElementById('regreso')) return;
  regresando = true;
  const d = document.createElement('div');
  d.id = 'regreso';
  d.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:40;background:#26A96C;color:#0D0F1E;padding:10px 14px;font-weight:800;font-size:14px;display:flex;gap:10px;align-items:center;';
  let seg = 8;
  d.innerHTML = '<span class="crece" id="regTxt">Volvió el internet. Regresando al panel en ' + seg + '...</span>' +
    '<button id="regYa" style="background:#0D0F1E;color:#fff;border:none;border-radius:8px;padding:6px 12px;font-weight:800">Ir ya</button>' +
    '<button id="regNo" style="background:transparent;border:1px solid #0D0F1E;border-radius:8px;padding:6px 12px;font-weight:800;color:#0D0F1E">Quedarme</button>';
  document.body.appendChild(d);
  const timer = setInterval(() => {
    if (carrito.length || contexto) { document.getElementById('regTxt').textContent = 'Volvió el internet. Termina tu pedido y regresa al panel cuando quieras.'; return; }
    seg--;
    if (seg <= 0) { clearInterval(timer); location.href = URL_PANEL; return; }
    document.getElementById('regTxt').textContent = 'Volvió el internet. Regresando al panel en ' + seg + '...';
  }, 1000);
  document.getElementById('regYa').onclick = () => { clearInterval(timer); location.href = URL_PANEL; };
  document.getElementById('regNo').onclick = () => { clearInterval(timer); d.remove(); };
}
setInterval(async () => {
  const vivo = await probarInternet();
  if (vivo && !internetVivo && TOKEN) bannerRegreso();
  if (!vivo) { internetVivo = false; regresando = false; const d = document.getElementById('regreso'); if (d) d.remove(); return; }
  internetVivo = vivo;
}, 15000);
</script></body></html>`;

export function iniciarPos(ctx: ContextoPos): void {
  const fichas = new Set<string>();

  const server = createServer((req, res) => {
    const responder = (obj: unknown, status = 200) => {
      res.writeHead(status, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(obj));
    };
    const url = req.url ?? '/';

    if (req.method === 'GET' && (url === '/' || url === '/index.html')) {
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
      res.end(PAGINA);
      return;
    }
    if (!url.startsWith('/pos/')) return responder({ error: 'No existe' }, 404);

    let cuerpo = '';
    req.on('data', (c) => (cuerpo += c));
    req.on('end', () => {
      let datos: Record<string, unknown> = {};
      try {
        datos = cuerpo ? (JSON.parse(cuerpo) as Record<string, unknown>) : {};
      } catch {
        /* vacio */
      }
      try {
        const salon = ctx.salon();
        const espejo = ctx.espejo();

        if (url === '/pos/entrar' && req.method === 'POST') {
          const pin = ctx.pin();
          if (!pin) return responder({ error: 'El PIN de sala no esta configurado. Fijalo en el cerebrito (la laptop).' }, 400);
          const quien = deQuien(req);
          const espera = trancadoHasta(quien);
          if (espera > 0) {
            return responder({ error: `Demasiados intentos. Espera ${espera} segundo(s) antes de volver a probar.` }, 429);
          }
          if (!mismoPin(String(datos.pin ?? ''), pin)) {
            const veces = anotarFallo(quien);
            ctx.log(`PIN incorrecto desde ${quien} (intento ${veces}).`);
            return responder({ error: 'PIN incorrecto.' }, 400);
          }
          limpiarFallos(quien);
          const token = randomUUID();
          fichas.add(token);
          return responder({ token });
        }

        // Todo lo demas requiere ficha.
        const ficha = req.headers['x-pos'];
        if (typeof ficha !== 'string' || !fichas.has(ficha)) return responder({ error: 'Entra con el PIN.' }, 401);
        if (ctx.enReserva()) return responder({ error: 'Esta laptop esta EN RESERVA (no vende). Usa la titular.' }, 400);
        if (!salon || !espejo) return responder({ error: 'El menu local aun no se ha descargado. Conecta el internet una primera vez.' }, 400);

        if (url === '/pos/datos' && req.method === 'GET') {
          return responder({
            restaurante: { nombre: espejo.restaurante.nombre, tasa_bs: espejo.restaurante.tasa_bs },
            categorias: espejo.categorias.map((c) => ({ id: c.id, nombre: c.nombre })),
            productos: espejo.productos.map((p) => ({
              id: p.id,
              nombre: p.nombre,
              precio_usd: Number(p.precio_usd),
              categoria_id: p.categoria_id,
              disponible: p.disponible,
              opciones: p.opciones ?? [],
            })),
            mesas: salon.mesas(),
            resumen: salon.resumen(),
            nubeViva: ctx.nubeViva(),
          });
        }

        if (url.startsWith('/pos/cuenta') && req.method === 'GET') {
          const mesa = new URL(url, 'http://x').searchParams.get('mesa') ?? '';
          return responder({ cuenta: salon.cuenta(mesa) });
        }

        if (url === '/pos/pedido' && req.method === 'POST') {
          const p = salon.tomarPedidoMesa({
            mesa: String(datos.mesa ?? ''),
            items: (datos.items ?? []) as ItemEntrante[],
            paraLlevar: !!datos.paraLlevar,
            nota: datos.nota ? String(datos.nota) : undefined,
            mesero: datos.mesero ? String(datos.mesero) : undefined,
          });
          return responder({ codigo: p.codigo });
        }

        if (url === '/pos/cuenta-imprimir' && req.method === 'POST') {
          salon.imprimirCuenta(String(datos.mesa ?? ''));
          return responder({ ok: true });
        }

        if (url === '/pos/cobrar' && req.method === 'POST') {
          salon.cobrarMesa({
            mesa: String(datos.mesa ?? ''),
            metodo: String(datos.metodo ?? 'efectivo'),
            referencia: datos.referencia ? String(datos.referencia) : undefined,
            propina_usd: Number(datos.propina_usd) || 0,
            mesero: datos.mesero ? String(datos.mesero) : undefined,
          });
          return responder({ ok: true });
        }

        if (url === '/pos/venta' && req.method === 'POST') {
          const p = salon.ventaDirecta({
            items: (datos.items ?? []) as ItemEntrante[],
            paraLlevar: datos.paraLlevar !== false,
            nota: datos.nota ? String(datos.nota) : undefined,
            mesero: datos.mesero ? String(datos.mesero) : undefined,
            metodo: String(datos.metodo ?? 'efectivo'),
            referencia: datos.referencia ? String(datos.referencia) : undefined,
          });
          return responder({ codigo: p.codigo });
        }

        responder({ error: 'No existe' }, 404);
      } catch (e) {
        responder({ error: (e as Error).message }, 400);
      }
    });
  });

  // En TODA la red del local: las tablets entran por la IP de la laptop.
  server.listen(PUERTO_POS, '0.0.0.0', () => {
    ctx.log(`POS local listo en el puerto ${PUERTO_POS} (tablets: http://IP-de-esta-laptop:${PUERTO_POS})`);
  });
  server.on('error', (e) => ctx.log('POS no pudo arrancar: ' + e.message));
}
