function buildLineChart(cd){
  if(!cd||!cd.length)return'';
  const W=480,H=100,pad={t:4,r:4,b:24,l:40};
  const vals=cd.map(d=>d.venta||0);
  const maxV=Math.max(...vals,1);
  const xs=(i)=>pad.l+(i/(cd.length-1||1))*(W-pad.l-pad.r);
  const ys=(v)=>pad.t+(1-(v/maxV))*(H-pad.t-pad.b);
  const pts=cd.map((d,i)=>`${xs(i)},${ys(d.venta||0)}`).join(' ');
  const area=`${pad.l},${H-pad.b} `+cd.map((d,i)=>`${xs(i)},${ys(d.venta||0)}`).join(' ')+` ${W-pad.r},${H-pad.b}`;
  // Formatear etiqueta del eje X — evita timestamps ISO largos
  const fmtLabel=(d)=>{
    if(d.hora!==undefined) return String(d.hora).padStart(2,'0')+'h';
    if(d.mes)  return String(d.mes);
    if(d.dia){
      // d.dia llega como Date objeto (mssql devuelve CAST(fecha AS DATE) como Date),
      // string ISO "2026-03-01T06:00:00.000Z", o string "YYYY-MM-DD"
      let iso;
      if(d.dia instanceof Date){
        const y=d.dia.getUTCFullYear();
        const m=String(d.dia.getUTCMonth()+1).padStart(2,'0');
        const day=String(d.dia.getUTCDate()).padStart(2,'0');
        iso=`${y}-${m}-${day}`;
      } else {
        iso=String(d.dia);
      }
      const match=iso.match(/\d{4}-(\d{2})-(\d{2})/);
      if(match) return `${parseInt(match[2])}/${parseInt(match[1])}`;
      return iso.slice(0,5);
    }
    return '';
  };
  // Mostrar máx 14 etiquetas para no saturar el eje en móvil
  const step=cd.length>14?Math.ceil(cd.length/14):1;
  const labels=cd.map((d,i)=>i%step===0?`<text x="${xs(i)}" y="${H-8}" text-anchor="middle" fill="#9ca3af" font-size="8" font-family="Plus Jakarta Sans" font-weight="600">${fmtLabel(d)}</text>`:'').join('');
  
  let metaLine='';
  try{
    const metaDiaria=cache.metasConfig&&cache.metasConfig.meta_diaria>0?cache.metasConfig.meta_diaria:0;
    if(metaDiaria>0&&periodoActual==='hoy'){
      const metaY=ys(metaDiaria);
      if(metaY>=pad.t&&metaY<=(H-pad.b)){
        metaLine=`<line x1="${pad.l}" y1="${metaY.toFixed(1)}" x2="${(W-pad.r-8).toFixed(1)}" y2="${metaY.toFixed(1)}" stroke="#c4c9d4" stroke-width="1.2" stroke-dasharray="4 3"/>
        <text x="${(W-pad.r-6).toFixed(1)}" y="${(metaY-2).toFixed(1)}" fill="#9ca3af" font-size="7.5" font-family="Plus Jakarta Sans" font-weight="700">Meta</text>`;
      }
    }
  }catch(_e){}
  return`<svg class="line-chart" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none">
    <defs><linearGradient id="lg1" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#f97316" stop-opacity=".15"/><stop offset="100%" stop-color="#f97316" stop-opacity="0"/></linearGradient></defs>
    <polygon points="${area}" fill="url(#lg1)"/>
    <polyline class="chart-line-main" points="${pts}" fill="none" stroke="#f97316" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
    <polyline class="chart-dash-runner" points="${pts}" fill="none" stroke="#ff8a2a" stroke-width="2.6" stroke-linejoin="round" stroke-linecap="round" pathLength="120"/>
    ${metaLine}
    ${cd.map((d,i)=>`<circle class="chart-dot" style="animation-delay:${(i%10)*80}ms" cx="${xs(i)}" cy="${ys(d.venta||0)}" r="3" fill="#f97316"/>`).join('')}
    ${labels}
  </svg>`;
}

function buildDonut(pct,color,size=80){
  const r=32,cx=size/2,cy=size/2,circ=2*Math.PI*r;
  const dash=((pct/100)*circ).toFixed(1);
  return`<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
    <circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="#f3f4f6" stroke-width="6"/>
    <circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${color}" stroke-width="6"
      stroke-dasharray="${dash} ${circ}" stroke-linecap="round"
      transform="rotate(-90 ${cx} ${cy})"/>
    <text x="${cx}" y="${cy+1}" text-anchor="middle" dominant-baseline="middle" fill="${color}" font-size="13" font-weight="800" font-family="Plus Jakarta Sans">${pct}%</text>
  </svg>`;
}

function renderHoraBars(){
  const vh=cache.ventasHora||[];
  const horaActual=new Date().getHours();
  // BUG-FIX: incluir horas que solo tienen venta_en_curso (mesas abiertas, sin cerradas aún)
  const hd=vh.filter(h=>(h.venta||0)>0||(h.venta_en_curso||0)>0).sort((a,b)=>Number(a.hora)-Number(b.hora));

  if(!hd.length){
    $('hora-bars').innerHTML='<div class="empty" style="padding:20px;text-align:center;color:var(--text-3);font-size:11px;">Sin ventas registradas</div>';
    $('hora-sub').textContent='Sin datos';
    const tv=$('hora-total-val');if(tv)tv.textContent='$0.00';
    return;
  }

  // BUG-FIX: usar venta_total (pagada + en curso) para la escala y la línea principal
  const totalH=hd.reduce((a,h)=>a+(h.venta||0),0);
  const horaPico=hd.reduce((b,h)=>(h.venta||0)>(b.venta||0)?h:b,hd[0]);

  const W=600,H=160,pad={t:20,r:20,b:36,l:52};
  const maxV=Math.max(...hd.map(h=>Math.max(h.venta||0,h.venta_total||0)),1);
  const xs=i=>pad.l+(i/(hd.length-1||1))*(W-pad.l-pad.r);
  const ys=v=>pad.t+(1-(v/maxV))*(H-pad.t-pad.b);

  const gridLevels=[0,0.25,0.5,0.75,1];
  const gridLines=gridLevels.map((pct,gi)=>{
    const val=maxV*pct;
    const y=ys(val);
    const lbl=val>=1000000?`$${(val/1000000).toFixed(1)}M`:val>=1000?`$${Math.round(val/1000)}k`:`$${Math.round(val)}`;
    const bg=gi%2===0?`<rect x="${pad.l}" y="${y}" width="${W-pad.l-pad.r}" height="${ys(maxV*Math.max(0,pct-0.25))-y}" fill="rgba(0,0,0,.018)" rx="0"/>`:'';
    return`${bg}<line x1="${pad.l}" y1="${y.toFixed(1)}" x2="${W-pad.r}" y2="${y.toFixed(1)}" stroke="var(--border)" stroke-width="${pct===0?1.5:0.8}" stroke-dasharray="${pct===0?'':'4 4'}"/>
    <text x="${pad.l-6}" y="${(y+4).toFixed(1)}" text-anchor="end" fill="var(--text-3)" font-size="9" font-family="Plus Jakarta Sans" font-weight="600">${lbl}</text>`;
  }).join('');

  // Línea de cuentas PAGADAS (área sólida naranja)
  const areaPoints=`${pad.l},${H-pad.b} `+hd.map((_,i)=>`${xs(i).toFixed(1)},${ys(hd[i].venta||0).toFixed(1)}`).join(' ')+` ${xs(hd.length-1).toFixed(1)},${H-pad.b}`;
  const linePoints=hd.map((_,i)=>`${xs(i).toFixed(1)},${ys(hd[i].venta||0).toFixed(1)}`).join(' ');

  // BUG-FIX: Línea de venta TOTAL (pagada + en curso) — punteada azul
  const hayEnCurso=hd.some(h=>(h.venta_en_curso||0)>0);
  const lineTotal=hayEnCurso
    ?hd.map((_,i)=>`${xs(i).toFixed(1)},${ys(hd[i].venta_total||hd[i].venta||0).toFixed(1)}`).join(' ')
    :null;

  const xLabels=hd.map((h,i)=>{
    const x=xs(i);
    const esActual=Number(h.hora)===horaActual;
    const esPico=Number(h.hora)===Number(horaPico.hora);
    const color=esActual?'var(--orange)':esPico?'var(--text)':'var(--text-3)';
    const weight=esActual||esPico?'700':'500';
    return`<text x="${x.toFixed(1)}" y="${H-10}" text-anchor="middle" fill="${color}" font-size="9.5" font-family="Plus Jakarta Sans" font-weight="${weight}">${String(h.hora).padStart(2,'0')}h</text>`;
  }).join('');

  const puntos=hd.map((h,i)=>{
    const esActual=Number(h.hora)===horaActual;
    const esPico=Number(h.hora)===Number(horaPico.hora);
    const vTotal=h.venta_total||h.venta||0;
    const vPagada=h.venta||0;
    const r=esActual?6:esPico?5:3.5;
    const sw=esActual||esPico?2:1.5;
    const tipX=xs(i);
    const tipY=ys(vPagada);
    const lblVal=esActual&&(h.venta_en_curso||0)>0?vTotal:vPagada;
    const lbl=`$${Number(lblVal).toLocaleString('es-MX',{minimumFractionDigits:0,maximumFractionDigits:0})}`;
    const subLbl=esActual&&(h.venta_en_curso||0)>0
      ?`+$${Number(h.venta_en_curso).toLocaleString('es-MX',{minimumFractionDigits:0,maximumFractionDigits:0})} abierto`
      :null;
    const labelH=subLbl?28:15;
    const label=(esActual||esPico)?`
      <rect x="${(tipX-28).toFixed(1)}" y="${(tipY-labelH-8).toFixed(1)}" width="56" height="${labelH}" rx="4" fill="${esActual?'var(--orange)':'var(--dark,#111827)'}" opacity="0.9"/>
      <text x="${tipX.toFixed(1)}" y="${(tipY-labelH+4).toFixed(1)}" text-anchor="middle" fill="white" font-size="8.5" font-weight="700" font-family="Plus Jakarta Sans">${lbl}</text>
      ${subLbl?`<text x="${tipX.toFixed(1)}" y="${(tipY-8).toFixed(1)}" text-anchor="middle" fill="rgba(255,255,255,.8)" font-size="7.5" font-weight="600" font-family="Plus Jakarta Sans">${subLbl}</text>`:''}
    `:'';
    const puntoTotal=(h.venta_en_curso||0)>0
      ?`<circle class="chart-dot chart-dot-blue" style="animation-delay:${(i%10)*80}ms" cx="${tipX.toFixed(1)}" cy="${ys(vTotal).toFixed(1)}" r="3" fill="#2563eb" stroke="white" stroke-width="1.5" opacity="0.8"/>`
      :'';
    return`${label}<circle class="chart-dot" style="animation-delay:${(i%10)*80}ms" cx="${tipX.toFixed(1)}" cy="${tipY.toFixed(1)}" r="${r}" fill="${esActual||esPico?'var(--orange)':'var(--orange)'}" stroke="white" stroke-width="${sw}"/>${puntoTotal}`;
  }).join('');

  const svg=`<svg viewBox="0 0 ${W} ${H}" class="hora-line-svg" preserveAspectRatio="xMidYMid meet" style="width:100%;height:auto;min-height:130px;max-height:200px;">
    <defs>
      <linearGradient id="horaGrad" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stop-color="#f97316" stop-opacity=".22"/>
        <stop offset="75%" stop-color="#f97316" stop-opacity=".05"/>
        <stop offset="100%" stop-color="#f97316" stop-opacity="0"/>
      </linearGradient>
    </defs>
    ${gridLines}
    <polygon points="${areaPoints}" fill="url(#horaGrad)"/>
    <polyline class="chart-line-main" points="${linePoints}" fill="none" stroke="#f97316" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
    <polyline class="chart-dash-runner" points="${linePoints}" fill="none" stroke="#ff8a2a" stroke-width="3" stroke-linejoin="round" stroke-linecap="round" pathLength="120"/>
    ${lineTotal?`<polyline class="chart-dash-runner chart-dash-blue" points="${lineTotal}" fill="none" stroke="#2563eb" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" pathLength="120" opacity="0.75"/>`:''}
    ${puntos}
    ${xLabels}
    ${hayEnCurso?`<text x="${W-pad.r}" y="${pad.t+10}" text-anchor="end" fill="#2563eb" font-size="8.5" font-family="Plus Jakarta Sans" font-weight="700" opacity="0.8">- - - con mesas abiertas</text>`:''}
  </svg>`;

  const maxVhora=hd.reduce((mx,h)=>Math.max(mx,h.venta_total||h.venta||0),1);
  const tabla=`<table class="hora-tabla">
    ${hd.map(h=>{
      const esActual=Number(h.hora)===horaActual;
      const esPico=Number(h.hora)===Number(horaPico.hora)&&!esActual;
      const vTotal=h.venta_total||h.venta||0;
      const vEnCurso=h.venta_en_curso||0;
      const barPct=Math.round(vTotal/maxVhora*100);
      const barPctCerrado=vTotal>0?Math.round((h.venta||0)/vTotal*100):100;
      const barColor=esActual?'var(--orange)':esPico?'#ea580c':'var(--border)';
      const barraHtml=vEnCurso>0
        ?`<div style="height:4px;background:var(--bg);border-radius:4px;overflow:hidden;display:flex"><div style="height:100%;width:${barPctCerrado}%;background:${barColor};border-radius:4px 0 0 4px;transition:width .3s"></div><div style="height:100%;flex:1;background:#2563eb;opacity:.6;border-radius:0 4px 4px 0;transition:width .3s"></div></div>`
        :`<div style="height:4px;background:var(--bg);border-radius:4px;overflow:hidden"><div style="height:100%;width:${barPct}%;background:${barColor};border-radius:4px;transition:width .3s"></div></div>`;
      const ticketsLabel=vEnCurso>0?`${h.tickets||0} cerrados + ${h.tickets_curso||0} en curso`:`${h.tickets||0} tickets`;
      const ventaLabel=vEnCurso>0
        ?`<span style="color:var(--text)">$${Number(h.venta||0).toLocaleString('es-MX',{minimumFractionDigits:2,maximumFractionDigits:2})}</span><span style="color:#2563eb;font-size:10px"> +$${Number(vEnCurso).toLocaleString('es-MX',{minimumFractionDigits:0,maximumFractionDigits:0})}</span>`
        :`$${Number(h.venta||0).toLocaleString('es-MX',{minimumFractionDigits:2,maximumFractionDigits:2})}`;
      return`<tr class="${esActual?'hora-actual':esPico?'hora-pico':''}">
        <td style="white-space:nowrap">${String(h.hora).padStart(2,'0')}:00</td>
        <td style="color:var(--text-3);font-size:10px;white-space:nowrap">${ticketsLabel}</td>
        <td style="width:100%;padding:5px 8px">${barraHtml}</td>
        <td style="text-align:right;white-space:nowrap">${ventaLabel}</td>
      </tr>`;
    }).join('')}
  </table>`;

  $('hora-bars').innerHTML=`<div class="hora-chart-wrap" style="padding:12px 14px 4px">${svg}</div><div class="hora-tabla-wrap">${tabla}</div>`;
  $('hora-sub').textContent=`${hd.length} hora${hd.length!==1?'s':''} · pico ${String(horaPico.hora).padStart(2,'0')}:00`;
  const tv=$('hora-total-val');
  if(tv)tv.textContent=fmt(totalH);
}


function renderMesasInicio(){
  const wrap=$('inicio-mesas-wrap');
  if(!wrap)return;
  const mesas=cache.mesas||[];
  if(!mesas.length){wrap.innerHTML='';return;}

  const sorted=[...mesas].sort((a,b)=>(b.minutos_abierta||0)-(a.minutos_abierta||0));
  const total=sorted.length;
  const totalEnCurso=sorted.reduce((a,m)=>a+(m.total||0),0);
  const MAX=4;
  const visibles=sorted.slice(0,MAX);
  const ocultas=total-MAX;

  const rows=visibles.map((m,i)=>{
    const nombre=m.mesa&&m.mesa.trim()?m.mesa.trim():`#${m.foliocuenta||i+1}`;
    const mesero=m.nombre_mesero&&m.nombre_mesero!=='Sin asignar'?m.nombre_mesero:'—';
    const mins=m.minutos_abierta||0;
    const tiempoTxt=mins>=60?`${Math.floor(mins/60)}h ${mins%60}m`:`${mins}m`;
    const urgente=mins>90,riesgo=mins>45&&!urgente;
    const dot=urgente?'#ef4444':riesgo?'#f59e0b':'#10b981';
    return`<div style="display:flex;align-items:center;gap:10px;padding:8px 12px;border-bottom:1px solid var(--border);">
      <div style="width:8px;height:8px;border-radius:50%;background:${dot};flex-shrink:0;"></div>
      <div style="flex:1;min-width:0;">
        <div style="font-size:12px;font-weight:800;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${nombre} <span style="font-weight:500;color:var(--text-3);font-size:11px;">· ${mesero}</span></div>
        <div style="font-size:10px;color:var(--text-3);">⏱ ${tiempoTxt}</div>
      </div>
      <div style="font-size:13px;font-weight:800;color:var(--green);flex-shrink:0;">${fmt(m.total)}</div>
    </div>`;
  }).join('');

  const verTodosBtn=`<div onclick="switchTab('mesas',document.querySelector('[data-tab=mesas]'))" style="display:flex;align-items:center;justify-content:center;gap:6px;padding:9px;cursor:pointer;color:var(--orange);font-size:11px;font-weight:700;border-top:1px solid var(--border);">
    ${ocultas>0?`Ver todas (${total} mesas)`:'Ver módulo de mesas'}
    <svg viewBox="0 0 24 24" style="width:13px;height:13px;stroke:currentColor;stroke-width:2.5;fill:none;"><polyline points="9 18 15 12 9 6"/></svg>
  </div>`;

  wrap.innerHTML=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow-sm);overflow:hidden;margin-top:8px;">
    <div style="display:flex;align-items:center;justify-content:space-between;padding:10px 12px 9px;border-bottom:1px solid var(--border);">
      <div style="font-size:12px;font-weight:700;color:var(--text);">
        Mesas abiertas
        <span style="margin-left:6px;background:var(--orange-xlt);color:var(--orange);font-size:10px;font-weight:800;padding:1px 7px;border-radius:20px;">${total}</span>
        <span style="margin-left:8px;color:var(--green);font-size:11px;">${fmt(totalEnCurso)} en curso</span>
      </div>
      <span style="display:flex;align-items:center;gap:4px;font-size:10px;font-weight:700;color:var(--orange);">
        <span style="width:6px;height:6px;border-radius:50%;background:var(--orange);animation:pulse 1.5s infinite;display:inline-block;"></span>En vivo
      </span>
    </div>
    ${rows}
    ${verTodosBtn}
  </div>`;
}
