{"id":21,"date":"2026-05-26T18:37:11","date_gmt":"2026-05-26T18:37:11","guid":{"rendered":"https:\/\/app.micaldo.es\/?page_id=21"},"modified":"2026-05-26T18:37:11","modified_gmt":"2026-05-26T18:37:11","slug":"compras","status":"publish","type":"page","link":"https:\/\/app.micaldo.es\/index.php\/compras\/","title":{"rendered":"Compras"},"content":{"rendered":"\n<style>\n*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\nbody { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f4f5f7; color: #1a1a1a; font-size: 15px; }\nheader { background: #fff; border-bottom: 1px solid #e2e4e8; padding: 14px 24px; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 10; }\nheader h1 { font-size: 17px; font-weight: 600; color: #1a1a1a; display: flex; align-items: center; gap: 8px; }\nheader h1 span { color: #2d7a3a; }\n#status-txt { font-size: 12px; color: #888; }\n.container { max-width: 1200px; margin: 0 auto; padding: 24px; }\n.setup-card { background: #fff; border: 1px solid #e2e4e8; border-radius: 10px; padding: 20px 24px; margin-bottom: 24px; }\n.setup-card h2 { font-size: 14px; font-weight: 600; margin-bottom: 14px; color: #444; }\n.setup-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }\n.setup-row label { font-size: 12px; color: #666; display: block; margin-bottom: 4px; }\n.setup-row select, .setup-row input[type=\"date\"] { font-size: 13px; border: 1px solid #ddd; border-radius: 6px; padding: 7px 10px; background: #fff; color: #1a1a1a; height: 36px; }\n.setup-row select.wide { min-width: 220px; max-width: 320px; }\n#custom-dates { display: none; gap: 10px; flex-wrap: wrap; }\n.btn { background: #2d7a3a; color: #fff; border: none; border-radius: 6px; padding: 0 18px; height: 36px; font-size: 13px; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 6px; }\n.btn:hover { background: #235f2c; }\n.btn-outline { background: #fff; color: #444; border: 1px solid #ddd; border-radius: 6px; padding: 0 14px; height: 32px; font-size: 12px; cursor: pointer; }\n.btn-outline:hover { background: #f4f5f7; }\n.btn-ghost { background: transparent; color: #888; border: 1px solid #e2e4e8; border-radius: 6px; padding: 0 12px; height: 36px; font-size: 12px; cursor: pointer; }\n.btn-ghost:hover { background: #f4f5f7; }\n.err { background: #fff0ed; color: #b94040; border: 1px solid #fbc9b8; border-radius: 6px; padding: 10px 14px; font-size: 13px; margin-top: 10px; display: none; }\n.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-bottom: 24px; }\n.kpi-card { background: #fff; border: 1px solid #e2e4e8; border-radius: 10px; padding: 16px 18px; }\n.kpi-label { font-size: 12px; color: #888; margin-bottom: 6px; }\n.kpi-value { font-size: 22px; font-weight: 700; color: #1a1a1a; line-height: 1.1; }\n.kpi-sub { font-size: 11px; color: #aaa; margin-top: 5px; }\n.kpi-card.green .kpi-value { color: #2d7a3a; }\n.kpi-card.red .kpi-value { color: #b94040; }\n.kpi-card.orange .kpi-value { color: #c47a1a; }\n.kpi-card.blue .kpi-value { color: #1a6ab9; }\n.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; }\n@media (max-width: 800px) { .grid-2 { grid-template-columns: 1fr; } }\n.card { background: #fff; border: 1px solid #e2e4e8; border-radius: 10px; padding: 20px; }\n.card-title { font-size: 13px; font-weight: 600; color: #444; margin-bottom: 14px; }\n.top-list { list-style: none; }\n.top-list li { display: flex; justify-content: space-between; align-items: center; padding: 7px 0; border-bottom: 1px solid #f0f0f0; font-size: 13px; gap: 8px; cursor: pointer; }\n.top-list li:last-child { border-bottom: none; }\n.top-list li:hover { background: #f9f9f9; margin: 0 -8px; padding: 7px 8px; border-radius: 6px; }\n.top-list li.no-click { cursor: default; }\n.top-list li.no-click:hover { background: none; margin: 0; padding: 7px 0; }\n.top-list .nm { color: #1a1a1a; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.top-list .am { font-weight: 600; color: #1a1a1a; white-space: nowrap; }\n.rank { display: inline-block; width: 18px; color: #aaa; font-size: 11px; flex-shrink: 0; }\n.bar-wrap { flex: 1; height: 6px; background: #f0f0f0; border-radius: 3px; margin: 0 8px; min-width: 40px; }\n.bar-fill { height: 6px; border-radius: 3px; background: #2d7a3a; }\n.arrow { color: #ccc; font-size: 14px; flex-shrink: 0; }\ntable { width: 100%; border-collapse: collapse; font-size: 13px; }\nth { text-align: left; color: #888; font-weight: 600; font-size: 11px; padding: 0 8px 10px; border-bottom: 1px solid #f0f0f0; text-transform: uppercase; letter-spacing: 0.04em; }\ntd { padding: 8px 8px; border-bottom: 1px solid #f5f5f5; color: #1a1a1a; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\ntr:last-child td { border-bottom: none; }\n.badge { display: inline-block; padding: 3px 9px; border-radius: 99px; font-size: 11px; font-weight: 600; }\n.b-warn { background: #fff4e0; color: #a06800; }\n.b-danger { background: #fff0ed; color: #b94040; }\n.b-ok { background: #e8f5eb; color: #2d7a3a; }\n.loading { text-align: center; padding: 48px; color: #888; font-size: 14px; display: none; }\n.spinner { display: inline-block; width: 20px; height: 20px; border: 2px solid #ddd; border-top-color: #2d7a3a; border-radius: 50%; animation: spin 0.7s linear infinite; margin-right: 8px; vertical-align: middle; }\n@keyframes spin { to { transform: rotate(360deg); } }\n#dashboard { display: none; }\n\n\/* MODAL *\/\n.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000; justify-content: center; align-items: flex-start; padding: 32px 16px; overflow-y: auto; }\n.modal-overlay.open { display: flex; }\n.modal { background: #fff; border-radius: 14px; width: 100%; max-width: 860px; margin: auto; box-shadow: 0 24px 60px rgba(0,0,0,0.25); }\n.modal-header { padding: 20px 24px; border-bottom: 1px solid #e2e4e8; display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }\n.modal-header h2 { font-size: 16px; font-weight: 700; margin-bottom: 3px; }\n.modal-header .sub { font-size: 12px; color: #888; }\n.modal-close { background: #f0f0f0; border: none; border-radius: 8px; width: 32px; height: 32px; font-size: 18px; cursor: pointer; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }\n.modal-close:hover { background: #e0e0e0; }\n.modal-body { padding: 20px 24px 28px; }\n.m-kpis { display: grid; grid-template-columns: repeat(3,1fr); gap: 10px; margin-bottom: 20px; }\n.m-kpi { background: #f8f9fa; border-radius: 8px; padding: 12px 14px; }\n.m-kpi .lbl { font-size: 11px; color: #888; margin-bottom: 4px; }\n.m-kpi .val { font-size: 18px; font-weight: 700; }\n.m-kpi.green .val { color: #2d7a3a; }\n.m-kpi.orange .val { color: #c47a1a; }\n.m-section { margin-top: 20px; }\n.m-section-title { font-size: 11px; font-weight: 700; color: #aaa; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 12px; }\n.m-chart { position: relative; height: 170px; margin-bottom: 4px; }\n.m-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.m-table th { text-align: left; color: #888; font-size: 11px; font-weight: 600; padding: 0 8px 8px; border-bottom: 1px solid #eee; text-transform: uppercase; letter-spacing: 0.04em; }\n.m-table td { padding: 8px; border-bottom: 1px solid #f5f5f5; }\n.m-table tr:last-child td { border-bottom: none; }\n.m-table .r { text-align: right; font-weight: 600; }\n<\/style>\n\n<header>\n  <h1>\ud83d\udce6 Compras <span>MiCaldo<\/span><\/h1>\n  <div style=\"display:flex;align-items:center;gap:12px;\">\n    <span id=\"status-txt\"><\/span>\n    <button class=\"btn-outline\" onclick=\"toggleSetup()\">\u2699 Configurar<\/button>\n  <\/div>\n<\/header>\n\n<div class=\"container\">\n  <div class=\"setup-card\" id=\"setup-card\">\n    <h2>Filtros<\/h2>\n    <div class=\"setup-row\">\n      <div>\n        <label>Periodo<\/label>\n        <select id=\"period-select\" onchange=\"onPeriodChange()\">\n          <option value=\"month\">Este mes<\/option>\n          <option value=\"quarter\">Este trimestre<\/option>\n          <option value=\"year\" selected>Este a\u00f1o<\/option>\n          <option value=\"last_year\">A\u00f1o pasado<\/option>\n          <option value=\"custom\">Personalizado<\/option>\n        <\/select>\n      <\/div>\n      <div id=\"custom-dates\" style=\"display:none;gap:10px;flex-wrap:wrap;\">\n        <div><label>Desde<\/label><input type=\"date\" id=\"date-from\"\/><\/div>\n        <div><label>Hasta<\/label><input type=\"date\" id=\"date-to\"\/><\/div>\n      <\/div>\n      <div>\n        <label>Proveedor<\/label>\n        <select id=\"supplier-select\" class=\"wide\">\n          <option value=\"\">\u2014 Todos los proveedores \u2014<\/option>\n        <\/select>\n      <\/div>\n      <button class=\"btn\" onclick=\"loadData()\">\u21bb Cargar datos<\/button>\n      <button class=\"btn-ghost\" onclick=\"resetFilters()\">\u2715 Reset<\/button>\n    <\/div>\n    <div class=\"err\" id=\"err-box\"><\/div>\n  <\/div>\n\n  <div class=\"loading\" id=\"loading-msg\"><span class=\"spinner\"><\/span> Consultando Holded&#8230;<\/div>\n\n  <div id=\"dashboard\">\n    <div class=\"kpi-grid\">\n      <div class=\"kpi-card red\"><div class=\"kpi-label\">Total compras<\/div><div class=\"kpi-value\" id=\"k-total\">\u2014<\/div><div class=\"kpi-sub\" id=\"k-total-s\">\u2014<\/div><\/div>\n      <div class=\"kpi-card\"><div class=\"kpi-label\">N\u00ba pedidos<\/div><div class=\"kpi-value\" id=\"k-count\">\u2014<\/div><div class=\"kpi-sub\">facturas de compra<\/div><\/div>\n      <div class=\"kpi-card\"><div class=\"kpi-label\">Ticket medio<\/div><div class=\"kpi-value\" id=\"k-avg\">\u2014<\/div><div class=\"kpi-sub\">por pedido<\/div><\/div>\n      <div class=\"kpi-card orange\"><div class=\"kpi-label\">Mayor proveedor<\/div><div class=\"kpi-value\" id=\"k-top-supplier\">\u2014<\/div><div class=\"kpi-sub\" id=\"k-top-supplier-s\">\u2014<\/div><\/div>\n    <\/div>\n\n    <div class=\"grid-2\">\n      <div class=\"card\"><div class=\"card-title\">Compras mensuales<\/div><div style=\"position:relative;height:220px;\"><canvas id=\"c-monthly\"><\/canvas><\/div><\/div>\n      <div class=\"card\"><div class=\"card-title\">Acumulado compras<\/div><div style=\"position:relative;height:220px;\"><canvas id=\"c-cumul\"><\/canvas><\/div><\/div>\n    <\/div>\n\n    <div class=\"grid-2\">\n      <div class=\"card\">\n        <div class=\"card-title\">Top proveedores <small style=\"color:#aaa;font-weight:400\">clic = ficha<\/small><\/div>\n        <ul class=\"top-list\" id=\"list-suppliers\"><\/ul>\n      <\/div>\n      <div class=\"card\">\n        <div class=\"card-title\">Top materias \/ productos comprados<\/div>\n        <ul class=\"top-list\" id=\"list-categories\"><\/ul>\n      <\/div>\n    <\/div>\n\n    <div class=\"card\" style=\"margin-bottom:16px\">\n      <div class=\"card-title\">\u00daltimas facturas de compra<\/div>\n      <table style=\"table-layout:fixed\">\n        <thead><tr>\n          <th style=\"width:90px\">Fecha<\/th>\n          <th style=\"width:110px\">N\u00ba Factura<\/th>\n          <th>Proveedor<\/th>\n          <th style=\"width:110px;text-align:right\">Importe<\/th>\n          <th style=\"width:90px\">Estado<\/th>\n        <\/tr><\/thead>\n        <tbody id=\"t-purchases\"><\/tbody>\n      <\/table>\n    <\/div>\n  <\/div>\n<\/div>\n\n<!-- MODAL FICHA PROVEEDOR -->\n<div class=\"modal-overlay\" id=\"modal-overlay\">\n  <div class=\"modal\" id=\"modal-box\">\n    <div class=\"modal-header\">\n      <div>\n        <h2 id=\"m-name\">\u2014<\/h2>\n        <div class=\"sub\" id=\"m-sub\">\u2014<\/div>\n      <\/div>\n      <button class=\"modal-close\" id=\"modal-close-btn\">\u2715<\/button>\n    <\/div>\n    <div class=\"modal-body\">\n      <div class=\"m-kpis\">\n        <div class=\"m-kpi\"><div class=\"lbl\">Total comprado<\/div><div class=\"val\" id=\"m-total\">\u2014<\/div><\/div>\n        <div class=\"m-kpi\"><div class=\"lbl\">N\u00ba pedidos<\/div><div class=\"val\" id=\"m-count\">\u2014<\/div><\/div>\n        <div class=\"m-kpi\"><div class=\"lbl\">Ticket medio<\/div><div class=\"val\" id=\"m-avg\">\u2014<\/div><\/div>\n      <\/div>\n      <div class=\"m-section\">\n        <div class=\"m-section-title\">Evoluci\u00f3n mensual<\/div>\n        <div class=\"m-chart\"><canvas id=\"c-m-monthly\"><\/canvas><\/div>\n      <\/div>\n      <div class=\"m-section\">\n        <div class=\"m-section-title\">Historial de pedidos<\/div>\n        <table class=\"m-table\">\n          <thead><tr>\n            <th style=\"width:80px\">Fecha<\/th>\n            <th style=\"width:110px\">N\u00ba Factura<\/th>\n            <th>Productos<\/th>\n            <th style=\"width:100px;text-align:right\">Importe<\/th>\n            <th style=\"width:85px\">Estado<\/th>\n          <\/tr><\/thead>\n          <tbody id=\"m-purch-body\"><\/tbody>\n        <\/table>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n<script>\n(function() {\n  const WEBHOOK = 'https:\/\/n8n.linktelservices.com\/webhook\/holded-compras';\n  let cMonthly, cCumul, cMModal;\n  let setupVisible = true;\n  let _purchasesRaw = [];\n\n  const fmt = n => new Intl.NumberFormat('es-ES',{style:'currency',currency:'EUR',maximumFractionDigits:0}).format(n);\n  const fmtS = n => Math.abs(n)>=1e6?(n\/1e6).toFixed(1)+'M \u20ac':Math.abs(n)>=1000?(n\/1000).toFixed(1)+'k \u20ac':fmt(n);\n  const fmtDate = ts => { if(!ts) return '\u2014'; const d=new Date(ts*1000); return d.toLocaleDateString('es-ES',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\n\n  function showErr(msg){const b=document.getElementById('err-box');b.textContent='\u26a0 '+msg;b.style.display='block';}\n  function clearErr(){document.getElementById('err-box').style.display='none';}\n\n  function onPeriodChange(){\n    document.getElementById('custom-dates').style.display=\n      document.getElementById('period-select').value==='custom'?'flex':'none';\n  }\n  window.onPeriodChange = onPeriodChange;\n\n  function toggleSetup(){\n    setupVisible=!setupVisible;\n    document.getElementById('setup-card').style.display=setupVisible?'block':'none';\n  }\n  window.toggleSetup = toggleSetup;\n\n  function resetFilters(){\n    document.getElementById('period-select').value='year';\n    document.getElementById('supplier-select').value='';\n    document.getElementById('custom-dates').style.display='none';\n  }\n  window.resetFilters = resetFilters;\n\n  function getTs(){\n    const now=new Date(),y=now.getFullYear(),m=now.getMonth();\n    const p=document.getElementById('period-select').value;\n    let from,to;\n    if(p==='month'){from=new Date(y,m,1,0,0,0);to=new Date(y,m+1,0,23,59,59);}\n    else if(p==='quarter'){const q=Math.floor(m\/3);from=new Date(y,q*3,1,0,0,0);to=new Date(y,q*3+3,0,23,59,59);}\n    else if(p==='year'){from=new Date(y,0,1,0,0,0);to=new Date(y,11,31,23,59,59);}\n    else if(p==='last_year'){from=new Date(y-1,0,1,0,0,0);to=new Date(y-1,11,31,23,59,59);}\n    else{\n      const[fy,fm,fd]=document.getElementById('date-from').value.split('-').map(Number);\n      const[ty,tm,td]=document.getElementById('date-to').value.split('-').map(Number);\n      from=new Date(fy,fm-1,fd,0,0,0);to=new Date(ty,tm-1,td,23,59,59);\n    }\n    return{startp:Math.floor(from.getTime()\/1000),endp:Math.floor(to.getTime()\/1000)};\n  }\n\n  async function loadData(){\n    clearErr();\n    document.getElementById('loading-msg').style.display='block';\n    document.getElementById('dashboard').style.display='none';\n    const{startp,endp}=getTs();\n    const filterSupplier=document.getElementById('supplier-select').value;\n    try{\n      const res=await fetch(WEBHOOK,{\n        method:'POST',headers:{'Content-Type':'application\/json'},\n        body:JSON.stringify({startp,endp,filterSupplier})\n      });\n      if(!res.ok) throw new Error('Error HTTP '+res.status);\n      const data=await res.json();\n      if(!data.kpis) throw new Error('Respuesta inesperada de n8n.');\n\n      if(data.purchasesRaw) _purchasesRaw=data.purchasesRaw;\n\n      if(data.allSuppliers&&data.allSuppliers.length>0){\n        const sel=document.getElementById('supplier-select');\n        const cur=sel.value;\n        sel.innerHTML='<option value=\"\">\u2014 Todos los proveedores \u2014<\/option>';\n        data.allSuppliers.forEach(s=>{\n          const o=document.createElement('option');\n          o.value=s;o.textContent=s;\n          if(s===cur)o.selected=true;\n          sel.appendChild(o);\n        });\n      }\n\n      render(data);\n      document.getElementById('status-txt').textContent='Actualizado: '+new Date().toLocaleTimeString('es-ES',{hour:'2-digit',minute:'2-digit'});\n      document.getElementById('setup-card').style.display='none';\n      setupVisible=false;\n    }catch(e){showErr(e.message);}\n    document.getElementById('loading-msg').style.display='none';\n    document.getElementById('dashboard').style.display='block';\n  }\n  window.loadData=loadData;\n\n  \/\/ \u2500\u2500 MODAL FICHA PROVEEDOR \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  function openSupplierModal(supplierName){\n    const purs=_purchasesRaw.filter(p=>(p.supplier||'')===supplierName);\n    const now=Math.floor(Date.now()\/1000);\n    let total=0;\n    const monthMap={};\n    const rows=[];\n\n    purs.forEach(p=>{\n      const amount=parseFloat(p.total)||0;\n      total+=amount;\n      const date=p.date||0;\n      if(date>0){\n        const d=new Date(date*1000);\n        const mk=d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0');\n        monthMap[mk]=(monthMap[mk]||0)+amount;\n      }\n      const prods=(p.products||[]).map(l=>(l.name||l.desc||'').split('\\n')[0].trim()).filter(Boolean);\n      const prodStr=prods.slice(0,2).join(', ')+(prods.length>2?` +${prods.length-2}`:'');\n\n      let badge='';\n      const isPaid=p.status===1||(p.paymentsTotal>0&&p.paymentsTotal>=amount-0.01&&amount>0);\n      if(isPaid) badge='<span class=\"badge b-ok\">Pagada<\/span>';\n      else badge=(p.dueDate&&p.dueDate<now)?'<span class=\"badge b-danger\">Vencida<\/span>':'<span class=\"badge b-warn\">Pendiente<\/span>';\n\n      rows.push({date,docNumber:p.docNumber||'\u2014',prodStr,amount,badge});\n    });\n\n    rows.sort((a,b)=>b.date-a.date);\n\n    document.getElementById('m-name').textContent=supplierName;\n    document.getElementById('m-sub').textContent=purs.length+' pedidos en el per\u00edodo';\n    document.getElementById('m-total').textContent=fmtS(total);\n    document.getElementById('m-count').textContent=purs.length;\n    document.getElementById('m-avg').textContent=purs.length>0?fmtS(Math.round(total\/purs.length)):'\u2014';\n\n    document.getElementById('m-purch-body').innerHTML=rows.map(r=>\n      `<tr>\n        <td>${fmtDate(r.date)}<\/td>\n        <td style=\"color:#999;font-size:12px\">${r.docNumber}<\/td>\n        <td style=\"font-size:12px;color:#666\">${r.prodStr||'\u2014'}<\/td>\n        <td class=\"r\">${fmt(r.amount)}<\/td>\n        <td>${r.badge}<\/td>\n      <\/tr>`\n    ).join('')||'<tr><td colspan=\"5\" style=\"color:#aaa;text-align:center;padding:16px\">Sin pedidos<\/td><\/tr>';\n\n    const months=Object.keys(monthMap).sort();\n    const amounts=months.map(mk=>monthMap[mk]);\n    if(cMModal)cMModal.destroy();\n    cMModal=new Chart(document.getElementById('c-m-monthly'),{\n      type:'bar',\n      data:{labels:months,datasets:[{label:'\u20ac',data:amounts,backgroundColor:'#2d7a3a',borderRadius:4}]},\n      options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},\n        scales:{y:{ticks:{callback:v=>fmtS(v),font:{size:11}},grid:{color:'rgba(0,0,0,0.05)'}},\n                x:{ticks:{font:{size:10}}}}}\n    });\n\n    document.getElementById('modal-overlay').classList.add('open');\n    document.body.style.overflow='hidden';\n  }\n\n  document.getElementById('modal-close-btn').addEventListener('click',closeModal);\n  document.getElementById('modal-overlay').addEventListener('click',function(e){if(e.target===this)closeModal();});\n  document.addEventListener('keydown',function(e){if(e.key==='Escape')closeModal();});\n  function closeModal(){\n    document.getElementById('modal-overlay').classList.remove('open');\n    document.body.style.overflow='';\n  }\n\n  \/\/ \u2500\u2500 RENDER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  function render(data){\n    const k=data.kpis;\n    document.getElementById('k-total').textContent=fmtS(k.totalAmount);\n    document.getElementById('k-total-s').textContent=k.totalPurchases+' pedidos';\n    document.getElementById('k-count').textContent=k.totalPurchases;\n    document.getElementById('k-avg').textContent=fmtS(k.avgTicket);\n\n    const top=data.topSuppliers[0];\n    document.getElementById('k-top-supplier').textContent=top?top.name.split(' ')[0]+'...':'\u2014';\n    document.getElementById('k-top-supplier-s').textContent=top?fmtS(top.amount):'';\n\n    const months=data.monthly.map(m=>m.month);\n    const amounts=data.monthly.map(m=>m.amount);\n\n    if(cMonthly)cMonthly.destroy();\n    cMonthly=new Chart(document.getElementById('c-monthly'),{\n      type:'bar',data:{labels:months,datasets:[{label:'\u20ac',data:amounts,backgroundColor:'#2d7a3a',borderRadius:4}]},\n      options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},\n        scales:{y:{ticks:{callback:v=>fmtS(v),font:{size:11}},grid:{color:'rgba(0,0,0,0.05)'}},\n                x:{ticks:{font:{size:10},autoSkip:false,maxRotation:45}}}}\n    });\n\n    let cum=0;\n    const cumData=amounts.map(a=>{cum+=a;return cum;});\n    if(cCumul)cCumul.destroy();\n    cCumul=new Chart(document.getElementById('c-cumul'),{\n      type:'line',data:{labels:months,datasets:[{label:'Acumulado',data:cumData,borderColor:'#2d7a3a',backgroundColor:'rgba(45,122,58,0.08)',fill:true,tension:0.3,pointRadius:3}]},\n      options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},\n        scales:{y:{ticks:{callback:v=>fmtS(v),font:{size:11}},grid:{color:'rgba(0,0,0,0.05)'}},\n                x:{ticks:{font:{size:10},autoSkip:false,maxRotation:45}}}}\n    });\n\n    \/\/ Top proveedores\n    const maxS=data.topSuppliers[0]?.amount||1;\n    const listS=document.getElementById('list-suppliers');\n    listS.innerHTML='';\n    data.topSuppliers.forEach((s,i)=>{\n      const li=document.createElement('li');\n      li.innerHTML=`<span class=\"rank\">${i+1}.<\/span>\n        <span class=\"nm\" title=\"${s.name}\">${s.name}<\/span>\n        <div class=\"bar-wrap\"><div class=\"bar-fill\" style=\"width:${Math.round(s.amount\/maxS*100)}%\"><\/div><\/div>\n        <span class=\"am\">${fmtS(s.amount)}<\/span>\n        <span class=\"arrow\">\u203a<\/span>`;\n      li.addEventListener('click',()=>openSupplierModal(s.name));\n      listS.appendChild(li);\n    });\n    if(!data.topSuppliers.length) listS.innerHTML='<li style=\"color:#aaa;font-size:13px\">Sin datos<\/li>';\n\n    \/\/ Top categor\u00edas\n    const maxCat=data.topCategories[0]?.amount||1;\n    document.getElementById('list-categories').innerHTML='';\n    const listCat=document.getElementById('list-categories');\n    data.topCategories.forEach((c,i)=>{\n      const li=document.createElement('li');\n      li.className='no-click';\n      li.innerHTML=`<span class=\"rank\">${i+1}.<\/span>\n        <span class=\"nm\" title=\"${c.name}\">${c.name}<\/span>\n        <div class=\"bar-wrap\"><div class=\"bar-fill\" style=\"width:${Math.round(c.amount\/maxCat*100)}%;background:#1a6ab9\"><\/div><\/div>\n        <span class=\"am\">${fmtS(c.amount)}<\/span>`;\n      listCat.appendChild(li);\n    });\n    if(!data.topCategories.length) listCat.innerHTML='<li style=\"color:#aaa;font-size:13px\">Sin datos<\/li>';\n\n    \/\/ Tabla \u00faltimas facturas\n    const now=Math.floor(Date.now()\/1000);\n    document.getElementById('t-purchases').innerHTML=(data.purchasesRaw||[]).slice(0,20).map(p=>{\n      const amount=parseFloat(p.total)||0;\n      const isPaid=p.status===1||(p.paymentsTotal>0&&p.paymentsTotal>=amount-0.01&&amount>0);\n      let badge='';\n      if(isPaid) badge='<span class=\"badge b-ok\">Pagada<\/span>';\n      else badge=(p.dueDate&&p.dueDate<now)?'<span class=\"badge b-danger\">Vencida<\/span>':'<span class=\"badge b-warn\">Pendiente<\/span>';\n      return `<tr>\n        <td>${fmtDate(p.date)}<\/td>\n        <td style=\"color:#999\">${p.docNumber||'\u2014'}<\/td>\n        <td>${p.supplier}<\/td>\n        <td style=\"text-align:right;font-weight:600\">${fmt(amount)}<\/td>\n        <td>${badge}<\/td>\n      <\/tr>`;\n    }).join('')||'<tr><td colspan=\"5\" style=\"color:#aaa;text-align:center;padding:16px\">Sin facturas<\/td><\/tr>';\n  }\n\n})();\n<\/script>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udce6 Compras MiCaldo \u2699 Configurar Filtros Periodo Este mesEste trimestreEste a\u00f1oA\u00f1o pasadoPersonalizado Desde Hasta Proveedor \u2014 Todos los proveedores \u2014 \u21bb Cargar datos \u2715 Reset Consultando Holded&#8230; Total compras \u2014 \u2014 N\u00ba pedidos \u2014 facturas de compra Ticket medio \u2014 por pedido Mayor proveedor \u2014 \u2014 Compras mensuales Acumulado compras Top proveedores clic = ficha [&hellip;]<\/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-21","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/app.micaldo.es\/index.php\/wp-json\/wp\/v2\/pages\/21","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/app.micaldo.es\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/app.micaldo.es\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/app.micaldo.es\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/app.micaldo.es\/index.php\/wp-json\/wp\/v2\/comments?post=21"}],"version-history":[{"count":1,"href":"https:\/\/app.micaldo.es\/index.php\/wp-json\/wp\/v2\/pages\/21\/revisions"}],"predecessor-version":[{"id":22,"href":"https:\/\/app.micaldo.es\/index.php\/wp-json\/wp\/v2\/pages\/21\/revisions\/22"}],"wp:attachment":[{"href":"https:\/\/app.micaldo.es\/index.php\/wp-json\/wp\/v2\/media?parent=21"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}