{"id":590,"date":"2025-11-29T17:57:05","date_gmt":"2025-11-29T17:57:05","guid":{"rendered":"https:\/\/ruru.foodya.es\/?page_id=590"},"modified":"2026-08-25T11:49:04","modified_gmt":"2026-08-25T09:49:04","slug":"ticket","status":"publish","type":"page","link":"https:\/\/ruru.foodya.es\/index.php\/ticket\/","title":{"rendered":""},"content":{"rendered":"\n<h1 style=\"\n  text-align: center;\n  color: #f97316;\n  font-size: 32px;\n  font-weight: 800;\n  margin: 25px 0;\n  letter-spacing: -0.5px;\n\">\n  Ticket de tu pedido\n<\/h1>\n\n\n<div id=\"foodya-ticket-wrapper\">\n  \n  <div class=\"foodya-ticket-card\">\n    <div class=\"foodya-ticket-inner\">\n      <div class=\"foodya-ticket-header\">\n        <div class=\"foodya-ticket-chip\">Ticket<\/div>\n        <h1>Ticket de tu pedido<\/h1>\n        <p class=\"foodya-ticket-subtitle\" id=\"foodya-ticket-meta\">\n          Cargando ticket...\n        <\/p>\n      <\/div>\n\n      <!-- \ud83d\udd39 Banner de mesa, se rellena por JS -->\n      <div id=\"foodya-ticket-mesa-banner\" class=\"foodya-mesa-banner\" style=\"display:none;\">\n        <div class=\"foodya-mesa-pill\" id=\"foodya-ticket-mesa-pill\"><\/div>\n        <div class=\"foodya-mesa-text\" id=\"foodya-ticket-mesa-text\"><\/div>\n      <\/div>\n\n      <div class=\"foodya-ticket-legal\">\n        <strong>Importante:<\/strong>\n        Este ticket digital es una <strong>copia<\/strong> del ticket original.\n        El ticket original est\u00e1 disponible en el establecimiento, que es el \u00fanico\n        responsable de su custodia. Esta copia se conservar\u00e1 durante\n        <strong>7 d\u00edas<\/strong> y despu\u00e9s se eliminar\u00e1 autom\u00e1ticamente.\n      <\/div>\n\n      <div id=\"foodya-ticket-content\" class=\"foodya-ticket-content\">\n        <p>Cargando ticket...<\/p>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <!-- BARRA FLOTANTE -->\n  <div id=\"foodya-ticket-bottom\">\n    <div class=\"foodya-bottom-left\">\n      <div class=\"foodya-bottom-text\" id=\"foodya-ticket-bottom-text\"><\/div>\n    <\/div>\n    <div class=\"foodya-bottom-right\">\n      <button type=\"button\" id=\"foodya-ticket-rate\" class=\"foodya-bottom-btn ghost\">\n        \u2b50 Valorar experiencia\n      <\/button>\n      <button type=\"button\" id=\"foodya-ticket-print\" class=\"foodya-bottom-btn ghost\">\n        \ud83e\uddfe Descargar \/ PDF\n      <\/button>\n      <button type=\"button\" id=\"foodya-ticket-share\" class=\"foodya-bottom-btn primary\">\n        Compartir ticket\n      <\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<style>\n  :root{\n    --fy-accent:#ff5722;\n    --fy-muted:#6b7280;\n    --fy-border:#e5e7eb;\n    --fy-soft:#f9fafb;\n    --fy-radius:16px;\n  }\n  body.foodya-ticket-page .fy-bottom,\n  body.foodya-ticket-page .foodya-bottom-nav,\n  body.foodya-ticket-page .foodya-staff-tabbar,\n  body.foodya-ticket-page .fy-staff-tabbar,\n  body.foodya-ticket-page #foodya-staff-tabbar,\n  body.foodya-ticket-page .foodya-staff-bar,\n  body.foodya-ticket-page .fy-staff-bar{ display:none !important; }\n  body.foodya-ticket-page.foodya-staff-ui-active{ padding-bottom:0 !important; }\n  .foodya-ticket-staff-link{position:fixed;top:calc(env(safe-area-inset-top,0px) + 10px);left:14px;z-index:1000000;display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:0 16px;border-radius:999px;background:#111;color:#fff!important;text-decoration:none;font-weight:950;box-shadow:0 8px 26px rgba(0,0,0,.18)}\n  @media(max-width:720px){.foodya-ticket-staff-link{top:calc(env(safe-area-inset-top,0px) + 8px);left:10px;min-height:42px;padding:0 14px;font-size:16px}}\n\n  body.foodya-ticket-page{\n    background:#f3f4f6;\n  }\n\n  #foodya-ticket-wrapper{\n    max-width:480px;\n    margin:24px auto 96px;\n    padding:0 16px 24px;\n    font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;\n  }\n\n  .foodya-ticket-card{\n    background:linear-gradient(135deg,#fb923c 0,#f97316 40%,#fed7aa 100%);\n    border-radius:26px;\n    padding:1px;\n    box-shadow:0 18px 40px rgba(15,23,42,0.18);\n  }\n\n  .foodya-ticket-inner{\n    background:#ffffff;\n    border-radius:24px;\n    padding:18px 18px 20px;\n    position:relative;\n    overflow:hidden;\n  }\n\n  .foodya-ticket-inner::before,\n  .foodya-ticket-inner::after{\n    content:\"\";\n    position:absolute;\n    top:18px;\n    bottom:18px;\n    width:8px;\n    background:\n      radial-gradient(circle at 0 6px,#f3f4f6 0, #f3f4f6 4px, transparent 4px) repeat-y;\n    background-size:8px 16px;\n    opacity:0.8;\n  }\n  .foodya-ticket-inner::before{ left:-4px; transform:scaleX(-1); }\n  .foodya-ticket-inner::after{ right:-4px; }\n\n  .foodya-ticket-header{\n    padding-bottom:8px;\n    margin-bottom:10px;\n    border-bottom:1px dashed #e5e7eb;\n  }\n  .foodya-ticket-chip{\n    display:inline-flex;\n    align-items:center;\n    gap:4px;\n    padding:2px 10px;\n    border-radius:999px;\n    font-size:11px;\n    font-weight:700;\n    text-transform:uppercase;\n    letter-spacing:.08em;\n    background:#fef3c7;\n    color:#92400e;\n    margin-bottom:4px;\n  }\n  .foodya-ticket-header h1{\n    margin:0 0 2px;\n    font-size:20px;\n    font-weight:800;\n  }\n  .foodya-ticket-subtitle{\n    margin:0;\n    font-size:12px;\n    color:var(--fy-muted);\n  }\n\n  \/* \ud83d\udd39 Banner mesa *\/\n  .foodya-mesa-banner{\n    display:flex;\n    align-items:center;\n    gap:10px;\n    margin:4px 0 10px;\n    padding:8px 10px;\n    border-radius:12px;\n    background:rgba(255,87,34,0.06);\n    border:1px solid rgba(255,87,34,0.28);\n  }\n  .foodya-mesa-pill{\n    padding:4px 10px;\n    background:#ffffff;\n    border-radius:999px;\n    font-weight:800;\n    font-size:12px;\n    color:var(--fy-accent);\n    text-transform:uppercase;\n  }\n  .foodya-mesa-text{\n    font-size:13px;\n  }\n\n  .foodya-ticket-content{\n    margin-top:10px;\n    border-radius:14px;\n    background:var(--fy-soft);\n    padding:10px;\n    overflow:hidden;\n    border:1px dashed #e5e7eb;\n  }\n\n  .foodya-ticket-legal{\n    margin-top:8px;\n    margin-bottom:8px;\n    padding:8px 10px;\n    border-radius:12px;\n    background:#eef2ff;\n    border:1px solid #e0e7ff;\n    font-size:11px;\n    color:#374151;\n    line-height:1.5;\n  }\n  .foodya-ticket-legal strong{\n    font-weight:700;\n  }\n\n  .fyc-group{\n    background:#fff;\n    border-radius:12px;\n    border:1px solid var(--fy-border);\n    padding:10px 10px 8px;\n    margin-bottom:8px;\n  }\n  .fyc-group .t{\n    font-weight:700;\n    font-size:13px;\n    margin-bottom:6px;\n    text-transform:uppercase;\n    letter-spacing:.06em;\n    color:#4b5563;\n  }\n  .fyc-list{list-style:none;margin:0;padding:0;}\n  .fyc-item{\n    display:grid;\n    grid-template-columns: minmax(0,1fr) auto auto;\n    column-gap:8px;row-gap:2px;\n    align-items:flex-start;\n    padding:6px 0;\n    border-bottom:1px dashed #e5e7eb;\n    font-size:12px;\n  }\n  .fyc-item:last-child{border-bottom:none;}\n  .fyc-item .n{\n    font-weight:700;\n    font-size:13px;\n  }\n  .fyc-item .opt{\n    grid-column:1\/4;\n    font-size:11px;\n    color:#6b7280;\n  }\n  .fyc-item .share{\n    grid-column:1\/4;\n    font-size:11px;\n    color:#4b5563;\n  }\n  .fyc-item .q-static{\n    text-align:center;\n    font-weight:600;\n    white-space:nowrap;\n    font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n  }\n  .fyc-item .ln{\n    text-align:right;\n    font-weight:600;\n    white-space:nowrap;\n    font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n  }\n  .fyc-sub{\n    margin-top:4px;\n    padding-top:4px;\n    border-top:1px dashed #e5e7eb;\n    font-size:12px;\n    display:flex;\n    justify-content:space-between;\n  }\n\n  .foodya-iva-box{\n    margin-top:10px;\n    padding:8px 10px;\n    border-radius:12px;\n    background:#111827;\n    color:#f9fafb;\n    font-size:12px;\n  }\n  .foodya-iva-row{\n    display:flex;\n    justify-content:space-between;\n    margin-bottom:2px;\n    font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n  }\n  .foodya-iva-row span{\n    opacity:0.8;\n  }\n  .foodya-iva-row strong{\n    font-weight:700;\n  }\n\n  .foodya-iva-sep{\n    margin:8px 0;\n    border-top:1px solid rgba(255,255,255,0.18);\n  }\n\n  \/* \ud83d\udcf1 Bloque Bizum (n\u00famero de m\u00f3vil para Bizum) *\/\n  .foodya-bizum-box{\n    margin-top:10px;\n    padding:8px 10px;\n    border-radius:12px;\n    background:#fef3c7;\n    border:1px solid #fde68a;\n    font-size:12px;\n  }\n  .foodya-bizum-label{\n    font-weight:700;\n    margin-bottom:4px;\n  }\n  .foodya-bizum-row{\n    display:flex;\n    align-items:center;\n    gap:8px;\n    flex-wrap:wrap;\n    margin-bottom:4px;\n  }\n  .foodya-bizum-phone{\n    font-weight:700;\n    font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n  }\n  .foodya-bizum-copy{\n    padding:4px 10px;\n    border-radius:999px;\n    border:1px solid #e5e7eb;\n    background:#ffffff;\n    cursor:pointer;\n    font-size:11px;\n    font-weight:600;\n  }\n  .foodya-bizum-help{\n    font-size:11px;\n    color:#92400e;\n  }\n\n  #foodya-ticket-bottom{\n    position:fixed;\n    left:0;right:0;bottom:0;\n    z-index:99980;\n    background:#ffffff;\n    border-top:1px solid var(--fy-border);\n    color:#111;\n    padding:8px 10px;\n    display:flex;\n    justify-content:space-between;\n    align-items:center;\n    gap:10px;\n  }\n  .foodya-bottom-left{\n    flex:1;\n    min-width:0;\n  }\n  #foodya-ticket-bottom-text{\n    font-weight:700;\n    font-size:13px;\n    white-space:nowrap;\n    overflow:hidden;\n    text-overflow:ellipsis;\n  }\n  .foodya-bottom-right{\n    display:flex;\n    gap:8px;\n  }\n  .foodya-bottom-btn{\n    border-radius:999px;\n    padding:8px 12px;\n    font-size:13px;\n    font-weight:700;\n    border:0;\n    cursor:pointer;\n    white-space:nowrap;\n    flex:0 0 auto;\n  }\n  .foodya-bottom-btn.ghost{\n    background:#f3f4f6;\n    color:#111827;\n  }\n  .foodya-bottom-btn.primary{\n    background:var(--fy-accent);\n    color:#fff;\n  }\n\n  \/* FoodYA 1.5.8: solo interacci\u00f3n. No cambia el dise\u00f1o de la barra. *\/\n  #foodya-ticket-bottom{ pointer-events:auto !important; z-index:2147483000 !important; }\n  #foodya-ticket-bottom .foodya-bottom-right,\n  #foodya-ticket-bottom .foodya-bottom-btn{ pointer-events:auto !important; }\n  #foodya-ticket-bottom .foodya-bottom-btn{ touch-action:manipulation; -webkit-tap-highlight-color:transparent; }\n\n  @media(max-width:600px){\n    #foodya-ticket-wrapper{\n      padding:0 8px 96px;\n    }\n    .foodya-ticket-inner{\n      padding:16px 14px 16px;\n    }\n    .foodya-ticket-header h1{\n      font-size:18px;\n    }\n    .fyc-item{\n      grid-template-columns:minmax(0,1fr) auto;\n      grid-template-rows:auto auto;\n    }\n    .fyc-item .ln{\n      grid-row:2;\n      grid-column:2;\n    }\n    .fyc-item .q-static{\n      grid-row:2;\n      grid-column:1;\n      text-align:left;\n      font-size:11px;\n    }\n\n    .foodya-bottom-left{\n      display:none;\n    }\n    #foodya-ticket-bottom{\n      padding:8px 8px;\n    }\n    .foodya-bottom-right{\n      gap:6px;\n      max-width:100%;\n      overflow-x:auto;\n      -webkit-overflow-scrolling:touch;\n      justify-content:flex-start;\n    }\n    .foodya-bottom-right::-webkit-scrollbar{\n      display:none;\n    }\n    .foodya-bottom-btn{\n      padding:6px 10px;\n      font-size:12px;\n    }\n  }\n\n  @media print{\n    body.foodya-ticket-page{\n      background:#ffffff !important;\n    }\n    #wpadminbar,\n    #foodya-ticket-bottom,\n    header, footer,\n    .site-header,.site-footer,\n    .entry-header,.entry-footer{\n      display:none !important;\n    }\n    #foodya-ticket-wrapper{\n      max-width:480px;\n      margin:0 auto;\n      padding:0;\n      box-shadow:none;\n    }\n    .foodya-ticket-card{\n      box-shadow:none;\n    }\n  }\n<\/style>\n\n<script>\ndocument.addEventListener('DOMContentLoaded', function(){\n  document.body.classList.add('foodya-ticket-page');\n  var fyBottom = document.querySelector('.fy-bottom');\n  if (fyBottom) fyBottom.style.display = 'none';\n  function fyHideStaffBarsOnTicket(){\n    ['.fy-bottom','.foodya-bottom-nav','.foodya-staff-tabbar','.fy-staff-tabbar','#foodya-staff-tabbar','.foodya-staff-bar','.fy-staff-bar'].forEach(function(sel){\n      document.querySelectorAll(sel).forEach(function(el){ el.style.setProperty('display','none','important'); });\n    });\n    document.body.style.setProperty('padding-bottom','0px','important');\n  }\n  fyHideStaffBarsOnTicket();\n  setTimeout(fyHideStaffBarsOnTicket,300);\n  setTimeout(fyHideStaffBarsOnTicket,1200);\n  new MutationObserver(fyHideStaffBarsOnTicket).observe(document.documentElement,{childList:true,subtree:true});\n\n  var root      = document.getElementById('foodya-ticket-content');\n  var metaEl    = document.getElementById('foodya-ticket-meta');\n  var bottomTxt = document.getElementById('foodya-ticket-bottom-text');\n  var editBtn   = document.getElementById('foodya-ticket-edit');\n  var shareBtn  = document.getElementById('foodya-ticket-share');\n  var rateBtn   = document.getElementById('foodya-ticket-rate');\n  var printBtn  = document.getElementById('foodya-ticket-print');\n\n  \/\/ iOS\/Safari: dentro de un contenedor horizontal desplazable puede cancelar el click.\n  \/\/ Convertimos un toque corto en click real sin impedir el swipe horizontal.\n  (function fyTicketTouchBridge(){\n    var bar=document.getElementById('foodya-ticket-bottom');\n    if(!bar || !window.PointerEvent) return;\n    var start=null;\n    bar.addEventListener('pointerdown',function(e){\n      var b=e.target && e.target.closest ? e.target.closest('.foodya-bottom-btn') : null;\n      if(!b) return;\n      start={id:e.pointerId,x:e.clientX,y:e.clientY,btn:b,t:Date.now()};\n    },true);\n    bar.addEventListener('pointerup',function(e){\n      if(!start || start.id!==e.pointerId) return;\n      var dx=Math.abs(e.clientX-start.x), dy=Math.abs(e.clientY-start.y), dt=Date.now()-start.t;\n      var b=start.btn; start=null;\n      if(dx>10 || dy>10 || dt>900) return; \/\/ era un arrastre, no un tap\n      e.preventDefault();\n      e.stopPropagation();\n      setTimeout(function(){ if(b && document.contains(b)) b.click(); },0);\n    },true);\n    bar.addEventListener('pointercancel',function(){start=null;},true);\n  })();\n\n  \/\/ \ud83d\udd39 elementos mesa\n  var mesaBanner = document.getElementById('foodya-ticket-mesa-banner');\n  var mesaPill   = document.getElementById('foodya-ticket-mesa-pill');\n  var mesaText   = document.getElementById('foodya-ticket-mesa-text');\n\n  var menuUrl   = \"https:\\\/\\\/ruru.foodya.es\\\/index.php\\\/menu\\\/\";\n  var menuUrlMap = {\"*\":\"https:\\\/\\\/ruru.foodya.es\\\/index.php\\\/menu\\\/\",\"1\":\"https:\\\/\\\/ruru.foodya.es\\\/index.php\\\/menuceqfveq5498\\\/\"};\n  var rateUrl   = \"https:\\\/\\\/ruru.foodya.es\\\/\";\n  var apiUrl    = \"https:\\\/\\\/ruru.foodya.es\\\/index.php\\\/wp-json\\\/foodya\\\/v1\\\/ticket\";\n  var statusUrl = \"https:\\\/\\\/ruru.foodya.es\\\/index.php\\\/wp-json\\\/foodya-orderflow\\\/v2\\\/status\";\n  var paymentsUrl = \"https:\\\/\\\/ruru.foodya.es\\\/index.php\\\/wp-json\\\/foodya\\\/v1\\\/stripe\\\/payments\";\n  var pdfBase   = \"https:\\\/\\\/ruru.foodya.es\\\/index.php\\\/wp-json\\\/foodya\\\/v1\\\/ticket-pdf\";\n\n  \/\/ Servicios: usamos primero la configuraci\u00f3n incrustada por WordPress.\n  \/\/ La llamada REST queda \u00fanicamente como respaldo\/actualizaci\u00f3n.\n  var stripeCfg = {\"services_vat_percent\":21,\"services_online_enabled\":false,\"services_online_fixed_cents\":0,\"services_online_percent\":1.5,\"services_tpv_enabled\":false,\"services_tpv_fixed_cents\":0,\"services_tpv_percent\":1.5,\"services_cash_enabled\":false,\"services_cash_fixed_cents\":0,\"services_cash_percent\":1.5};\n  var STRIPE_CFG_URL = '\/wp-json\/foodya\/v1\/stripe\/config';\n\n  var ticketSummary   = '';\n  var ticketMesaLabel = '';\n  var countdownInterval = null;\n  var expirationMs    = null;\n  var currentToken    = null;\n  var currentTicket   = null;\n  var ticketLocked    = false;\n  var bizumPhone      = ''; \/\/ \ud83d\udc48 aqu\u00ed guardamos el m\u00f3vil Bizum para copiar\n\n  function esc(s){\n    return String(s).replace(\/[&<>\"']\/g, function(m){\n      return ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[m] || m);\n    });\n  }\n  function money(n){\n    try{\n      return new Intl.NumberFormat('es-ES',{style:'currency',currency:'EUR'}).format(n || 0);\n    }catch(e){\n      n = n || 0;\n      return n.toFixed(2) + ' \u20ac';\n    }\n  }\n  function groupByGuest(items){\n    var out = {};\n    items.forEach(function(it){\n      var gs = (it.guests && it.guests.length) ? it.guests : ['Comensales'];\n      var share = 1 \/ gs.length;\n      gs.forEach(function(g){\n        if (!out[g]) out[g] = [];\n        var cp = Object.assign({}, it, { share: share });\n        out[g].push(cp);\n      });\n    });\n    return out;\n  }\n  function subtotal(arr){\n    return arr.reduce(function(t,it){\n      return t + (it.price || 0) * (it.qty || 1) * (it.share || 1);\n    }, 0);\n  }\n\n  \/\/ Fiscal nativo multi-IVA: calcula directamente desde las l\u00edneas del ticket.\n  \/\/ Evita el render inicial al 10% y el parche posterior por addons.\n  function fiscalBreakdown(arr){\n    var byRate = {};\n    (arr || []).forEach(function(it){\n      var gross = Number(it.price || 0) * Number(it.qty || 1) * Number(it.share || 1);\n      var rateRaw = (it.iva !== undefined && it.iva !== null && it.iva !== '') ? it.iva : ((it.vat !== undefined && it.vat !== null && it.vat !== '') ? it.vat : 10);\n      var rate = Number(rateRaw);\n      if (!isFinite(rate) || rate < 0 || rate > 100) rate = 10;\n      var key = String(rate);\n      if (!byRate[key]) byRate[key] = {rate:rate,gross:0};\n      byRate[key].gross += gross;\n    });\n    return Object.keys(byRate).map(function(k){\n      var x = byRate[k];\n      var gross = round2(x.gross);\n      var base = round2(gross \/ (1 + x.rate\/100));\n      var iva = round2(gross - base);\n      return {rate:x.rate,base:base,iva:iva,gross:gross};\n    }).sort(function(a,b){ return a.rate-b.rate; });\n  }\n  function fiscalRowsHtml(arr){\n    var rows = fiscalBreakdown(arr), s='';\n    rows.forEach(function(x){\n      var lbl = (Math.round(x.rate*100)\/100).toString().replace('.', ',');\n      s += '<div class=\"foodya-iva-row\"><span>Base imponible ('+lbl+'%)<\/span><strong>'+money(x.base)+'<\/strong><\/div>';\n      s += '<div class=\"foodya-iva-row\"><span>IVA ('+lbl+'%)<\/span><strong>'+money(x.iva)+'<\/strong><\/div>';\n    });\n    return s;\n  }\n  function normalizeMetodo(m){\n    m = String(m || '').toLowerCase().trim();\n    var compact = m.normalize ? m.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'') : m;\n    compact = compact.replace(\/[\\s-]+\/g,'_');\n    if (['tarjeta','card','card_local','tarjeta_local','tpv','dataphone','pos'].indexOf(compact) >= 0\n        || compact.indexOf('tarjeta') >= 0 || compact.indexOf('tpv') >= 0 || compact.indexOf('dataphone') >= 0) return 'card_local';\n    if (['efectivo','cash','metalico','local_cash'].indexOf(compact) >= 0\n        || compact.indexOf('efectivo') >= 0 || compact.indexOf('cash') >= 0 || compact.indexOf('metalico') >= 0) return 'cash';\n    if (['online','stripe','redsys','getnet','applepay','googlepay'].indexOf(compact) >= 0\n        || compact.indexOf('online') >= 0 || compact.indexOf('stripe') >= 0 || compact.indexOf('redsys') >= 0\n        || compact.indexOf('getnet') >= 0 || compact.indexOf('applepay') >= 0 || compact.indexOf('googlepay') >= 0) return 'online';\n    return compact || 'cash';\n  }\n  function metodoLabel(m){\n    m = normalizeMetodo(m);\n    if (m === 'card_local') return 'Tarjeta';\n    if (m === 'online') return 'Pago online';\n    return 'Efectivo';\n  }\n\n  function round2(n){\n    return Math.round((Number(n||0) + Number.EPSILON) * 100) \/ 100;\n  }\n\n  function serviceMethodKey(m){\n    m = normalizeMetodo(m);\n    if (m === 'card_local') return 'tpv';\n    if (m === 'cash') return 'cash';\n    return 'online';\n  }\n  function feeBreakdown(amountProductsIncl, cfg, metodo){\n    cfg = cfg || {};\n    var key = serviceMethodKey(metodo);\n    var enabled = cfg['services_'+key+'_enabled'];\n    if (enabled === undefined && key === 'online') enabled = true;\n    if (!enabled) return {base:0,iva:0,total:0,enabled:false,vatPct:Number(cfg.services_vat_percent||21)||21};\n    var fixedCents = Number(cfg['services_'+key+'_fixed_cents']);\n    var pct = Number(cfg['services_'+key+'_percent']);\n    if (!isFinite(fixedCents) && key === 'online') fixedCents = Number(cfg.fee_fixed_cents||0);\n    if (!isFinite(pct) && key === 'online') pct = (Number(cfg.stripe_percent||0)+Number(cfg.foodya_percent||0)) || Number(cfg.fee_percent||0);\n    fixedCents = isFinite(fixedCents) ? fixedCents : 0; pct = isFinite(pct) ? pct : 0;\n    var vatPct = Number(cfg.services_vat_percent||21)||21;\n\n    \/\/ Fuente \u00fanica de verdad monetaria: redondear una sola vez el total de Servicios.\n    var rawBase = (fixedCents\/100) + (pct\/100)*(Number(amountProductsIncl||0)||0);\n    var total = round2(rawBase * (1 + vatPct\/100));\n    var base = round2(total \/ (1 + vatPct\/100));\n    var iva = round2(total - base);\n    return {base:base,iva:iva,total:total,enabled:true,vatPct:vatPct};\n  }\n\n  function fetchStripeCfg(){\n    if (stripeCfg) return Promise.resolve(stripeCfg);\n    return fetch(STRIPE_CFG_URL)\n      .then(function(r){\n        if (!r.ok) throw new Error('HTTP '+r.status);\n        return r.json();\n      })\n      .then(function(cfg){\n        stripeCfg = cfg || {};\n        return stripeCfg;\n      })\n      .catch(function(){\n        stripeCfg = {};\n        return stripeCfg;\n      });\n  }\n\n  function getTokenFromURL(){\n    try{\n      var params = new URLSearchParams(window.location.search);\n      return params.get('k');\n    }catch(e){\n      return null;\n    }\n  }\n\n  function loadLastTicket(){\n    try{\n      var raw = localStorage.getItem('foodya:lastTicket');\n      return raw ? JSON.parse(raw) : null;\n    }catch(e){\n      return null;\n    }\n  }\n\n  function saveEditCartAndGo(ticket){\n    if (!ticket || !ticket.items || !ticket.items.length) {\n      alert('No se ha podido cargar el pedido para modificar.');\n      return;\n    }\n    if (!currentToken) {\n      alert('No se ha encontrado el token del pedido.');\n      return;\n    }\n    try{\n      localStorage.setItem('foodya:cart', JSON.stringify(ticket.items || []));\n      localStorage.setItem('foodya:editTicketToken', currentToken);\n      localStorage.setItem('foodya:editTicketNumber', ticket.public_ticket_number || ticket.ticket_number || '');\n      var editMesa = ticket.mesa ? String(ticket.mesa) : '';\n      if (editMesa) {\n        localStorage.setItem('foodya:editMesa', editMesa);\n        localStorage.setItem('foodya:mesa', editMesa);\n      }\n    }catch(e){}\n    var mesa = ticket.mesa ? String(ticket.mesa) : '';\n    var url = (mesa && menuUrlMap && menuUrlMap[mesa]) ? menuUrlMap[mesa] : ((menuUrlMap && menuUrlMap['*']) || menuUrl || '\/');\n    var sep = url.indexOf('?') === -1 ? '?' : '&';\n    if (mesa) { url += sep + 'mesa=' + encodeURIComponent(mesa); sep='&'; }\n    url += sep + 'edit_k=' + encodeURIComponent(currentToken);\n    window.location.href = url;\n  }\n\n  function refreshEditButton(ticket){\n    if (!editBtn) return;\n    if (!ticket || !ticket.items || !ticket.items.length) {\n      editBtn.style.display = 'none';\n      return;\n    }\n\n    function lockEditButton(){\n      ticketLocked = true;\n      editBtn.disabled = true;\n      editBtn.style.display = 'none';\n    }\n\n    if (ticket.edit_locked || ticket.paid || ticket.payment_status === 'paid' || ticket.client_status === 'delivered' || ticket.client_status === 'cancelled') {\n      lockEditButton();\n      return;\n    }\n\n    editBtn.style.display = '';\n    editBtn.disabled = false;\n    editBtn.textContent = '\u270f\ufe0f Modificar pedido';\n    if (!currentToken) return;\n\n    \/\/ Primero comprobamos el estado general de OrderFlow.\n    fetch(statusUrl + '?id=' + encodeURIComponent(currentToken), {credentials:'same-origin', cache:'no-store'})\n      .then(function(r){return r.ok ? r.json() : null;})\n      .then(function(s){\n        if (!s || !s.ok || !s.order) return false;\n        var o = s.order || {};\n        var locked = !!(o.paid || o.archived || o.hidden || o.cancelled || o.payment_status === 'paid' || o.payment_status === 'cancelled' || o.client_status === 'delivered' || o.client_status === 'cancelled');\n        if (locked) lockEditButton();\n        return locked;\n      })\n      .then(function(locked){\n        if (locked) return;\n        var method = normalizeMetodo(ticket.payment_method || ticket.metodo || ticket.method || '');\n        if (method !== 'online' || !paymentsUrl) return;\n\n        \/\/ En online el pedido se bloquea desde que Stripe confirma el primer cobro,\n        \/\/ aunque el camarero todav\u00eda no haya validado el pago en el KDS.\n        return fetch(paymentsUrl + '?k=' + encodeURIComponent(currentToken) + '&refresh=1&_=' + Date.now(), {\n          credentials:'same-origin',\n          cache:'no-store'\n        })\n          .then(function(r){return r.ok ? r.json() : null;})\n          .then(function(data){\n            var rows = data && Array.isArray(data.payments) ? data.payments : [];\n            var hasPaid = rows.some(function(row){\n              return String((row && row.status) || '').toLowerCase() === 'paid';\n            });\n            if (hasPaid) lockEditButton();\n          });\n      })\n      .catch(function(){});\n  }\n\n  function setupCountdown(){\n    if (!bottomTxt || !expirationMs) return;\n\n    if (countdownInterval){\n      clearInterval(countdownInterval);\n      countdownInterval = null;\n    }\n\n    function update(){\n      var now = Date.now();\n      var diff = expirationMs - now;\n\n      if (diff <= 0){\n        if (bottomTxt) bottomTxt.textContent = 'Esta copia digital del ticket ha caducado y puede haber sido eliminada.';\n        clearInterval(countdownInterval);\n        countdownInterval = null;\n        return;\n      }\n\n      var totalSeconds = Math.floor(diff \/ 1000);\n      var days    = Math.floor(totalSeconds \/ 86400);\n      var hours   = Math.floor((totalSeconds % 86400) \/ 3600);\n      var minutes = Math.floor((totalSeconds % 3600) \/ 60);\n\n      var partes = [];\n      if (days > 0)   partes.push(days + ' d');\n      if (hours > 0 || days > 0) partes.push(hours + ' h');\n      partes.push(minutes + ' min');\n\n      var base = ticketSummary || 'Ticket activo';\n      if (ticketMesaLabel) {\n        base = ticketMesaLabel + ' \u00b7 ' + base;\n      }\n      if (bottomTxt){\n        bottomTxt.textContent = base + ' \u00b7 Se eliminar\u00e1 en ' + partes.join(' ');\n      }\n    }\n\n    update();\n    countdownInterval = setInterval(update, 60000);\n  }\n\n  function renderTicket(ticket, cfg){\n    if (!ticket || !ticket.items || !ticket.items.length){\n      root.innerHTML = '<p>No hay ning\u00fan ticket disponible.<\/p>';\n      if (metaEl) metaEl.textContent = 'No se ha encontrado ning\u00fan pedido (puede que haya caducado).';\n      if (bottomTxt) bottomTxt.textContent = '';\n      if (mesaBanner) mesaBanner.style.display = 'none';\n      ticketMesaLabel = '';\n      bizumPhone = '';\n      return;\n    }\n\n    var items    = ticket.items || [];\n    var metodo   = normalizeMetodo(ticket.payment_method || ticket.metodo || ticket.method || 'efectivo');\n    \/\/ \"single\" = un solo pedido (una persona paga todo)\n    \/\/ \"split\"  = pedidos individuales por comensal\n    var paymode  = ticket.paymode || null;\n    var mesa     = ticket.mesa || '';\n    var telefono = ticket.telefono || ''; \/\/ \ud83d\udc48 viene del backend\n    var cashGivenRaw = (ticket.cash_given !== undefined && ticket.cash_given !== null) ? ticket.cash_given : ((ticket.cash_received !== undefined && ticket.cash_received !== null) ? ticket.cash_received : ((ticket.amount_received !== undefined && ticket.amount_received !== null) ? ticket.amount_received : null));\n    var cashGiven = cashGivenRaw !== null ? parseFloat(cashGivenRaw) : null;\n\n    currentTicket = ticket;\n    if (ticket.k || ticket.token) currentToken = ticket.k || ticket.token;\n    ticketLocked = !!(ticket.edit_locked || ticket.paid || ticket.payment_status === 'paid' || ticket.client_status === 'delivered' || ticket.client_status === 'cancelled');\n    bizumPhone = telefono || '';\n\n    var ts   = ticket.ts  || Date.now();\n    var exp  = ticket.exp || (ts + 7*24*60*60*1000);\n    expirationMs = exp;\n\n    var date = new Date(ts);\n    var fechaTxt;\n    try{\n      fechaTxt = date.toLocaleString('es-ES',{\n        day:'2-digit',month:'2-digit',year:'numeric',\n        hour:'2-digit',minute:'2-digit'\n      });\n    }catch(e){\n      fechaTxt = date.toISOString();\n    }\n\n    \/\/ \ud83d\udd39 texto mesa arriba\n    if (mesa && mesaBanner && mesaPill && mesaText){\n      mesaBanner.style.display = 'flex';\n      mesaPill.textContent = 'Mesa ' + mesa;\n      mesaText.innerHTML = 'Pedido de la <strong>mesa ' + esc(mesa) + '<\/strong>.';\n      ticketMesaLabel = 'Mesa ' + mesa;\n    } else {\n      if (mesaBanner) mesaBanner.style.display = 'none';\n      ticketMesaLabel = '';\n    }\n\n    \/\/ \ud83d\udd25 A\u00f1adimos countdown tambi\u00e9n en la l\u00ednea de meta (para m\u00f3vil)\n    var countdownFragment = '';\n    (function(){\n      var now = Date.now();\n      var diff = exp - now;\n      if (diff > 0){\n        var totalSeconds = Math.floor(diff \/ 1000);\n        var days    = Math.floor(totalSeconds \/ 86400);\n        var hours   = Math.floor((totalSeconds % 86400) \/ 3600);\n        var minutes = Math.floor((totalSeconds % 3600) \/ 60);\n\n        var partes = [];\n        if (days > 0)   partes.push(days + ' d');\n        if (hours > 0 || days > 0) partes.push(hours + ' h');\n        partes.push(minutes + ' min');\n\n        countdownFragment = ' \u00b7 Se eliminar\u00e1 en ' + partes.join(' ');\n      }\n    })();\n\n    if (metaEl){\n      var meta = 'Pedido realizado el ' + fechaTxt;\n      metaEl.textContent = meta;\n    }\n\n    var groups = groupByGuest(items);\n    var grand  = 0;\n    var count  = 0;\n    var html   = '';\n\n    \/\/ Por defecto, si hay varios comensales y es pago online, interpretamos split.\n    if (!paymode && metodo === 'online') {\n      var k = Object.keys(groups);\n      paymode = (k.length > 1) ? 'split' : 'single';\n    }\n    if (!paymode) paymode = 'single';\n\n    \/\/ N\u00famero p\u00fablico estable recibido del backend.\n    \/\/ Si no existe en tickets antiguos, se deriva del token k.\n    function basePublicTicket(){\n      var n = ticket.public_ticket_number || ticket.ticket_number || '';\n      if (n) return String(n);\n      var tk = String(ticket.k || ticket.token || (new URLSearchParams(location.search).get('k') || ''));\n      tk = tk.replace(\/[^A-Za-z0-9]\/g,'');\n      if (tk) {\n        var d = new Date(ts);\n        var dd = String(d.getDate()).padStart(2,'0');\n        var mm = String(d.getMonth()+1).padStart(2,'0');\n        return dd + mm + '-' + tk.slice(-4).toUpperCase();\n      }\n      var d2 = new Date(ts);\n      return String(d2.getDate()).padStart(2,'0') + String(d2.getMonth()+1).padStart(2,'0') + '-LOCAL';\n    }\n    function orderNo(idx){\n      var base = basePublicTicket();\n      return (paymode === 'split') ? (base + '-' + String(idx+1)) : base;\n    }\n\n    if (paymode === 'single') {\n      \/\/ Un solo pedido: mostramos todo consolidado (sin dividir por comensal)\n      html += '<div class=\"fyc-group\">';\n      html +=   '<div class=\"t\">\ud83e\uddfe Pedido \u00fanico \u00b7 Pedido n\u00ba ' + esc(orderNo(0)) + '<\/div>';\n      html +=   '<ul class=\"fyc-list\">';\n\n      items.forEach(function(it){\n        var qty  = parseInt(it.qty || 1, 10) || 1;\n        var line = (it.price || 0) * qty;\n        count   += qty;\n        grand   += line;\n\n        var detallePartes = [];\n\n        if (it.size){\n          var txtSize = 'Tama\u00f1o: ' + it.size;\n          if (it.sizePrice) txtSize += ' (' + money(it.sizePrice) + ')';\n          detallePartes.push(txtSize);\n        }\n        if (Array.isArray(it.ingredients)){\n          var removed = it.ingredients\n            .filter(function(ing){ return ing && ing.included === false; })\n            .map(function(ing){ return ing.name; });\n          if (removed.length) detallePartes.push('Sin: ' + removed.join(', '));\n        }\n        if (Array.isArray(it.extras) && it.extras.length){\n          var extrasTxt = it.extras.map(function(ex){\n            var label = ex.name || '';\n            if (ex.price) label += ' (' + money(ex.price) + ')';\n            return label;\n          }).join(', ');\n          detallePartes.push('Extras: ' + extrasTxt);\n        }\n        if (Array.isArray(it.guarniciones) && it.guarniciones.length){\n          var guaTxt = it.guarniciones.map(function(g){\n            var label = g.name || '';\n            if (g.price) label += ' (' + money(g.price) + ')';\n            return label;\n          }).join(', ');\n          detallePartes.push('Guarniciones: ' + guaTxt);\n        }\n\n        var compartidoHtml = '';\n        if (Array.isArray(it.guests) && it.guests.length > 1){\n          compartidoHtml = '\ud83e\udd1d Compartido con: ' + it.guests.join(', ');\n        }\n\n        html += '<li class=\"fyc-item\">';\n        html +=   '<div class=\"n\">' + esc(it.name || '') + '<\/div>';\n        html +=   '<div class=\"q-static\">x ' + qty + '<\/div>';\n        html +=   '<div class=\"ln\">' + money(line) + '<\/div>';\n        if (detallePartes.length){\n          html += '<div class=\"opt\">' + esc(detallePartes.join(' \u2022 ')) + '<\/div>';\n        }\n        if (compartidoHtml){\n          html += '<div class=\"share\">' + esc(compartidoHtml) + '<\/div>';\n        }\n        if (it.note && String(it.note).trim()){\n          html += '<div class=\"note\" style=\"margin-top:6px;font-size:13px;line-height:1.35;color:#6b7280;\">\ud83d\udcdd Nota: ' + esc(String(it.note).trim()) + '<\/div>';\n        }\n        html += '<\/li>';\n      });\n\n      html +=   '<\/ul>';\n      html += '<\/div>';\n\n      \/\/ Totales fiscales del pedido \u00fanico: multi-IVA nativo desde la primera pintura.\n      html += '<div class=\"foodya-iva-box\">';\n      html += fiscalRowsHtml(items);\n      html +=   '<div class=\"foodya-iva-row\"><span>Total (IVA incl.)<\/span><strong>' + money(grand) + '<\/strong><\/div>';\n\n      \/\/ Si es pago online, a\u00f1adimos en el mismo bloque el desglose de la Tasa de Servicios\n      if (cfg && Object.keys(cfg).length){\n        var feeU = feeBreakdown(grand, cfg, metodo);\n        if (feeU.enabled && feeU.total > 0) {\n        html +=   '<div class=\"foodya-iva-sep\"><\/div>';\n        html +=   '<div class=\"foodya-iva-row\"><span>Servicios (base imponible)<\/span><strong>' + money(feeU.base) + '<\/strong><\/div>';\n        html +=   '<div class=\"foodya-iva-row\"><span>IVA Servicios (' + feeU.vatPct + '%)<\/span><strong>' + money(feeU.iva) + '<\/strong><\/div>';\n        html +=   '<div class=\"foodya-iva-row\"><span>Servicios (IVA incl.)<\/span><strong>' + money(feeU.total) + '<\/strong><\/div>';\n        html +=   '<div class=\"foodya-iva-sep\"><\/div>';\n        html +=   '<div class=\"foodya-iva-row\"><span><strong>Total del pedido<\/strong><\/span><strong>' + money(round2(grand + feeU.total)) + '<\/strong><\/div>';\n        }\n      }\n      html += '<\/div>';\n    } else {\n      \/\/ Split: pedidos individuales por comensal. Mostramos solo cada pedido, sin suma final.\n      var guestKeys = Object.keys(groups);\n      \/\/ Servicios se calculan UNA sola vez sobre el pedido completo y despu\u00e9s se reparten.\n      \/\/ As\u00ed no se duplica ning\u00fan fijo ni aparecen totales distintos entre ticket y pago.\n      var globalProducts = guestKeys.reduce(function(sum, g){ return sum + subtotal(groups[g]); }, 0);\n      var globalFee = (cfg && Object.keys(cfg).length) ? feeBreakdown(globalProducts, cfg, metodo) : {enabled:false,total:0,vatPct:21};\n      var remainingServiceCents = Math.round((globalFee.total || 0) * 100);\n      var totalProductCents = Math.round(globalProducts * 100);\n\n      guestKeys.forEach(function(guest, idx){\n        var list = groups[guest];\n        var sub  = subtotal(list);\n        var personServiceCents = 0;\n        if (globalFee.enabled && globalFee.total > 0) {\n          if (idx === guestKeys.length - 1) {\n            personServiceCents = remainingServiceCents;\n          } else {\n            var personProductCents = Math.round(sub * 100);\n            personServiceCents = totalProductCents > 0 ? Math.round(Math.round(globalFee.total*100) * personProductCents \/ totalProductCents) : 0;\n            personServiceCents = Math.max(0, Math.min(personServiceCents, remainingServiceCents));\n            remainingServiceCents -= personServiceCents;\n          }\n        }\n        \/\/ Para el resumen inferior (sin mostrar suma final en el cuerpo).\n        grand += sub;\n\n        html += '<div class=\"fyc-group\">';\n        html +=   '<div class=\"t\">\ud83e\uddcd ' + esc(guest) + ' \u00b7 Pedido n\u00ba ' + esc(orderNo(idx)) + '<\/div>';\n        html +=   '<ul class=\"fyc-list\">';\n\n      list.forEach(function(it){\n        var qty  = parseInt(it.qty || 1, 10) || 1;\n        var line = (it.price || 0) * qty * (it.share || 1);\n        count   += qty * (it.share || 1);\n\n        var detallePartes = [];\n\n        if (it.size){\n          var txtSize = 'Tama\u00f1o: ' + it.size;\n          if (it.sizePrice) txtSize += ' (' + money(it.sizePrice) + ')';\n          detallePartes.push(txtSize);\n        }\n        if (Array.isArray(it.ingredients)){\n          var removed = it.ingredients\n            .filter(function(ing){ return ing && ing.included === false; })\n            .map(function(ing){ return ing.name; });\n          if (removed.length) detallePartes.push('Sin: ' + removed.join(', '));\n        }\n        if (Array.isArray(it.extras) && it.extras.length){\n          var extrasTxt = it.extras.map(function(ex){\n            var label = ex.name || '';\n            if (ex.price) label += ' (' + money(ex.price) + ')';\n            return label;\n          }).join(', ');\n          detallePartes.push('Extras: ' + extrasTxt);\n        }\n        if (Array.isArray(it.guarniciones) && it.guarniciones.length){\n          var guaTxt = it.guarniciones.map(function(g){\n            var label = g.name || '';\n            if (g.price) label += ' (' + money(g.price) + ')';\n            return label;\n          }).join(', ');\n          detallePartes.push('Guarniciones: ' + guaTxt);\n        }\n\n        var compartidoHtml = '';\n        if (Array.isArray(it.guests) && it.guests.length > 1){\n          compartidoHtml = '\ud83e\udd1d Compartido con: ' + it.guests.join(', ');\n        }\n\n        html += '<li class=\"fyc-item\">';\n        html +=   '<div class=\"n\">' + esc(it.name || '') + '<\/div>';\n        html +=   '<div class=\"q-static\">x ' + qty + '<\/div>';\n        html +=   '<div class=\"ln\">' + money(line) + '<\/div>';\n\n        if (detallePartes.length){\n          html += '<div class=\"opt\">' + esc(detallePartes.join(' \u2022 ')) + '<\/div>';\n        }\n        if (compartidoHtml){\n          html += '<div class=\"share\">' + esc(compartidoHtml) + '<\/div>';\n        }\n        if (it.note && String(it.note).trim()){\n          html += '<div class=\"note\" style=\"margin-top:6px;font-size:13px;line-height:1.35;color:#6b7280;\">\ud83d\udcdd Nota: ' + esc(String(it.note).trim()) + '<\/div>';\n        }\n\n        html += '<\/li>';\n      });\n\n        html +=   '<\/ul>';\n\n        \/\/ Desglose fiscal del pedido por comensal: multi-IVA nativo desde la primera pintura.\n        html += '<div class=\"foodya-iva-box\">';\n        html += fiscalRowsHtml(list);\n        html +=   '<div class=\"foodya-iva-row\"><span>Total (IVA incl.)<\/span><strong>' + money(sub) + '<\/strong><\/div>';\n\n        \/\/ Si es pago online, a\u00f1adimos Servicios + total online por pedido (sin suma final)\n        if (globalFee.enabled && personServiceCents > 0){\n          var feeTotalG = personServiceCents \/ 100;\n          var feeBaseG = round2(feeTotalG \/ (1 + globalFee.vatPct\/100));\n          var feeIvaG = round2(feeTotalG - feeBaseG);\n          html +=   '<div class=\"foodya-iva-sep\"><\/div>';\n          html +=   '<div class=\"foodya-iva-row\"><span>Servicios (base imponible)<\/span><strong>' + money(feeBaseG) + '<\/strong><\/div>';\n          html +=   '<div class=\"foodya-iva-row\"><span>IVA Servicios (' + globalFee.vatPct + '%)<\/span><strong>' + money(feeIvaG) + '<\/strong><\/div>';\n          html +=   '<div class=\"foodya-iva-row\"><span>Servicios (IVA incl.)<\/span><strong>' + money(feeTotalG) + '<\/strong><\/div>';\n          html +=   '<div class=\"foodya-iva-sep\"><\/div>';\n          html +=   '<div class=\"foodya-iva-row\"><span><strong>Total<\/strong><\/span><strong>' + money(round2(sub + feeTotalG)) + '<\/strong><\/div>';\n        } else {\n          html +=   '<div class=\"foodya-iva-sep\"><\/div>';\n          html +=   '<div class=\"foodya-iva-row\"><span><strong>Total<\/strong><\/span><strong>' + money(round2(sub)) + '<\/strong><\/div>';\n        }\n        html += '<\/div>';\n        html += '<\/div>';\n      });\n\n      \/\/ En split NO a\u00f1adimos suma final (para que quede claro que son pedidos separados).\n    }\n\n    \/\/ \ud83d\udd39 Cambio en efectivo: se muestra SIEMPRE, tanto pedido \u00fanico como split.\n    if ((metodo === 'efectivo' || metodo === 'metalico' || metodo === 'cash') && cashGiven && !isNaN(cashGiven)) {\n      var diff = (ticket.cash_change !== undefined && ticket.cash_change !== null && !isNaN(parseFloat(ticket.cash_change))) ? parseFloat(ticket.cash_change) : (cashGiven - grand);\n      html += '<div class=\"foodya-iva-box\">';\n      html += '<div class=\"foodya-iva-row\"><span>Efectivo recibido<\/span><strong>' + money(cashGiven) + '<\/strong><\/div>';\n      if (diff >= 0) {\n        html += '<div class=\"foodya-iva-row\"><span>Cambio<\/span><strong>' + money(diff) + '<\/strong><\/div>';\n      } else {\n        html += '<div class=\"foodya-iva-row\"><span>Falta<\/span><strong>' + money(Math.abs(diff)) + '<\/strong><\/div>';\n      }\n      html += '<\/div>';\n    }\n\n    \/\/ \ud83d\udd39 Bloque Bizum: si el checkout guard\u00f3 un tel\u00e9fono, se muestra en el ticket.\n    \/\/ El checkout ya decide cu\u00e1ndo solicitarlo seg\u00fan la configuraci\u00f3n del m\u00e9todo y del split.\n    if (telefono){\n      html += '<div class=\"foodya-bizum-box\">';\n      html +=   '<div class=\"foodya-bizum-label\">M\u00f3vil para Bizum:<\/div>';\n      html +=   '<div class=\"foodya-bizum-row\">';\n      html +=     '<span class=\"foodya-bizum-phone\" id=\"foodya-bizum-phone\">' + esc(telefono) + '<\/span>';\n      html +=     '<button type=\"button\" class=\"foodya-bizum-copy\">Copiar<\/button>';\n      html +=   '<\/div>';\n      html +=   '<div class=\"foodya-bizum-help\">Comparte para que el resto de comensales te hagan Bizum.<\/div>';\n      html += '<\/div>';\n    }\n\n    root.innerHTML = html;\n\n    ticketSummary = (count || 0) + ' art\u00edculo' + (count === 1 ? '' : 's') +\n                    ' \u2014 ' + money(grand) + ' (IVA incl.)';\n\n    refreshEditButton(ticket);\n    setupCountdown();\n  }\n\n  (function(){\n    var token = getTokenFromURL();\n    currentToken = token;\n\n    if (!token){\n      var localTicket = loadLastTicket();\n      if (localTicket && localTicket.items && localTicket.items.length){\n        if (metaEl){\n          metaEl.textContent = 'Ticket guardado en este dispositivo. El original est\u00e1 en el establecimiento.';\n        }\n        \/\/ La configuraci\u00f3n de Servicios se necesita para cualquier m\u00e9todo de pago\n        \/\/ (online, TPV\/tarjeta local o efectivo), no solo para online.\n        fetchStripeCfg().then(function(cfg){\n          renderTicket(localTicket, cfg);\n        });\n        return;\n      }\n\n      root.innerHTML = '<p>No se ha proporcionado ning\u00fan identificador de ticket.<\/p>';\n      if (metaEl) metaEl.textContent = 'Falta el identificador de ticket.';\n      if (bottomTxt) bottomTxt.textContent = '';\n      if (mesaBanner) mesaBanner.style.display = 'none';\n      ticketMesaLabel = '';\n      bizumPhone = '';\n      return;\n    }\n\n    var ticketReadGeneration=0;\n    function fetchTicketThenRender(){\n      var generation=++ticketReadGeneration;\n      var configRead=fetchStripeCfg();\n      return fetch(apiUrl + '?k=' + encodeURIComponent(token), {credentials:'same-origin', cache:'no-store'})\n      .then(function(r){\n        if (!r.ok) throw new Error('HTTP '+r.status);\n        return r.json();\n      })\n      .then(function(data){\n        \/\/ Cargar siempre la configuraci\u00f3n central de Servicios para que el\n        \/\/ ticket pueda desglosarlos tambi\u00e9n en TPV y efectivo.\n        return configRead.then(function(cfg){\n          if(generation===ticketReadGeneration)renderTicket(data, cfg);\n          return data;\n        });\n      })\n      .catch(function(error){\n        if(generation!==ticketReadGeneration)return;\n        root.innerHTML = '<p>No se ha podido cargar el ticket (puede que haya caducado).<\/p>';\n        if (metaEl) metaEl.textContent = 'Error al cargar el ticket o ticket caducado.';\n        if (bottomTxt) bottomTxt.textContent = '';\n        if (mesaBanner) mesaBanner.style.display = 'none';\n        ticketMesaLabel = '';\n        bizumPhone = '';\n        throw error;\n      });\n    }\n\n    window.addEventListener('foodya:ticket-refresh-request', function(event){\n      var detail = event && event.detail ? event.detail : {};\n      var suppliedGeneration=detail.ticket&&detail.ticket.items?++ticketReadGeneration:0;\n      var refresh = detail.ticket && detail.ticket.items\n        ? fetchStripeCfg().then(function(cfg){ if(suppliedGeneration===ticketReadGeneration)renderTicket(detail.ticket, cfg); return detail.ticket; })\n        : fetchTicketThenRender();\n      refresh.then(function(value){\n        if (typeof detail.resolve === 'function') detail.resolve(value);\n      }).catch(function(error){\n        if (typeof detail.reject === 'function') detail.reject(error);\n      });\n    });\n\n    fetchTicketThenRender().catch(function(){});\n  })();\n\n  if (editBtn){\n    editBtn.addEventListener('click', function(){\n      if (ticketLocked) {\n        alert('Este pedido ya est\u00e1 bloqueado. Una vez cobrado no se puede modificar.');\n        return;\n      }\n      if (!currentTicket) {\n        alert('No se ha cargado el ticket todav\u00eda.');\n        return;\n      }\n      \/\/ \u00daltima comprobaci\u00f3n contra OrderFlow antes de abrir el men\u00fa.\n      if (currentToken && statusUrl) {\n        fetch(statusUrl + '?id=' + encodeURIComponent(currentToken), {credentials:'same-origin'})\n          .then(function(r){return r.ok ? r.json() : null;})\n          .then(function(s){\n            var o = s && s.order ? s.order : null;\n            if (o && (o.paid || o.archived || o.hidden || o.cancelled || o.payment_status === 'paid' || o.payment_status === 'cancelled')) {\n              ticketLocked = true;\n              alert('Este pedido ya est\u00e1 cobrado o cerrado y no se puede modificar.');\n              refreshEditButton(currentTicket);\n              return;\n            }\n            saveEditCartAndGo(currentTicket);\n          })\n          .catch(function(){ saveEditCartAndGo(currentTicket); });\n      } else {\n        saveEditCartAndGo(currentTicket);\n      }\n    });\n  }\n\n  if (rateBtn){\n    rateBtn.addEventListener('click', function(){\n      window.location.href = rateUrl;\n    });\n  }\n  if (shareBtn){\n    shareBtn.addEventListener('click', function(){\n      var url = window.location.href;\n      if (navigator.share){\n        navigator.share({\n          title: 'Ticket FOODYA',\n          text: ticketSummary || 'Consulta el detalle de tu pedido',\n          url: url\n        }).catch(function(){});\n      } else if (navigator.clipboard && navigator.clipboard.writeText){\n        navigator.clipboard.writeText(url).then(function(){\n          alert('Enlace del ticket copiado al portapapeles');\n        }).catch(function(){\n          alert('No se pudo copiar el enlace. Copia la URL manualmente.');\n        });\n      } else {\n        alert('Tu navegador no permite compartir autom\u00e1ticamente. Copia la URL de la barra de direcciones.');\n      }\n    });\n  }\n  if (printBtn){\n    printBtn.addEventListener('click', function(){\n      if (currentToken && pdfBase){\n        var url = pdfBase + '?k=' + encodeURIComponent(currentToken);\n        window.open(url, '_blank');\n        return;\n      }\n      window.print();\n    });\n  }\n\n  \/\/ \ud83d\udccb Copiar m\u00f3vil Bizum\n  document.addEventListener('click', function(e){\n    var btn = e.target.closest('.foodya-bizum-copy');\n    if (!btn) return;\n    if (!bizumPhone) return;\n\n    if (navigator.clipboard && navigator.clipboard.writeText){\n      navigator.clipboard.writeText(bizumPhone)\n        .then(function(){\n          var old = btn.textContent;\n          btn.textContent = 'Copiado';\n          setTimeout(function(){ btn.textContent = old; }, 1500);\n        })\n        .catch(function(){\n          alert('No se ha podido copiar el n\u00famero.');\n        });\n    } else {\n      var temp = document.createElement('input');\n      temp.value = bizumPhone;\n      document.body.appendChild(temp);\n      temp.select();\n      try { document.execCommand('copy'); } catch(err){}\n      document.body.removeChild(temp);\n    }\n  });\n});\n<\/script>\n","protected":false},"excerpt":{"rendered":"<p>Ticket de tu pedido<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-590","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ruru.foodya.es\/index.php\/wp-json\/wp\/v2\/pages\/590","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ruru.foodya.es\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/ruru.foodya.es\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/ruru.foodya.es\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ruru.foodya.es\/index.php\/wp-json\/wp\/v2\/comments?post=590"}],"version-history":[{"count":3,"href":"https:\/\/ruru.foodya.es\/index.php\/wp-json\/wp\/v2\/pages\/590\/revisions"}],"predecessor-version":[{"id":1806,"href":"https:\/\/ruru.foodya.es\/index.php\/wp-json\/wp\/v2\/pages\/590\/revisions\/1806"}],"wp:attachment":[{"href":"https:\/\/ruru.foodya.es\/index.php\/wp-json\/wp\/v2\/media?parent=590"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}