/* screen_finance_printplan.jsx — PrintPlan Dashboard (React)
 * Dados mock: screen_finance_printplan_data.js (carregado primeiro em index.html)
 * Gerado a partir de dashboard_printplan_v3 (24).html do Márcio */

const _PP_CSS = `
/* ===== TOKENS — bridge to portal design system ===== */
.pp-root {
  --red-700:#B53B3B;--red-100:#FFE3E0;
  --green-700:#1F8A52;--green-100:#E1F7E6;
  --yellow-700:#C99A2E;--yellow-100:#FFF6D6;
  --pumpkin-700:#E96D17;--pumpkin-100:#FFCFCA;
  --purple-700:#5B43C5;--purple-100:#ECE7FF;
  /* bridge to portal tokens */
  --dd-primary-900:var(--brand-900,#112954);
  --dd-primary-600:var(--brand-600,#3859D0);
  --bg-surface:var(--surface,#fff);
  --bg-app:var(--bg,#f5f6f8);
  --border-1:var(--border,#E1E7F3);
  --fg-1:var(--text,#1D2E38);
  --fg-2:var(--text-muted,#64748b);
  --fg-3:var(--text-dim,#94a4c4);
  --font-sans:var(--font-display,'Montserrat',sans-serif);
  --font-text:var(--font-body,'Inter',sans-serif);
  --space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:20px;--space-6:24px;
  --radius-xs:4px;--radius-sm:6px;--radius-md:8px;--radius-lg:10px;--radius-xl:10px;--radius-pill:999px;
  --shadow-card:none;
}
/* ===== LAYOUT ===== */
.pp-root{height:100%;display:flex;flex-direction:column;overflow:hidden}
.pp-root *{box-sizing:border-box}
.pp-root .pp-tab-content{flex:1;overflow-y:auto;min-height:0}
.page{max-width:1440px;margin:0 auto;padding:24px 28px 48px}
.tab-panel{display:none}.tab-panel.active{display:block}
/* ===== TAB NAV ===== */
.pp-root .pp-tabnav{display:flex;align-items:center;gap:0;background:var(--bg-surface);border-bottom:1px solid var(--border-1);padding:0 20px;flex-shrink:0}
.tab-btn{height:44px;padding:0 14px;background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;font-size:12.5px;font-weight:500;color:var(--fg-2);font-family:var(--font-sans);white-space:nowrap;display:flex;align-items:center;gap:6px;margin-bottom:-1px;transition:color .15s,border-color .15s}
.tab-btn.active{color:var(--fg-1);border-bottom-color:var(--dd-primary-900);font-weight:600}
.tab-btn:hover:not(.active){color:var(--fg-1)}
.tab-pill{display:inline-flex;align-items:center;justify-content:center;height:16px;min-width:16px;padding:0 4px;border-radius:8px;background:var(--red-700);color:#fff;font-size:9.5px;font-weight:700}
.topbar-date{font-size:11px;color:var(--fg-3);font-family:var(--font-mono,monospace)}
/* ===== KPI BANNER ===== */
.ov-banner{display:grid;grid-template-columns:repeat(6,1fr);background:var(--bg-surface);border:1px solid var(--border-1);border-radius:var(--radius-xl);overflow:hidden;margin-bottom:14px}
.ov-banner-item{padding:16px 20px;border-right:1px solid var(--border-1)}
.ov-banner-item:last-child{border-right:none}
.ov-banner-v{font-family:var(--font-sans);font-size:21px;font-weight:600;color:var(--fg-1);line-height:1.2}
.ov-banner-l{font-size:10px;font-weight:600;color:var(--fg-3);text-transform:uppercase;letter-spacing:.07em;margin-top:4px;font-family:var(--font-text)}
.ov-banner-val{font-family:var(--font-sans);font-size:21px;font-weight:600;color:var(--fg-1);line-height:1.2}
.ov-banner-val.accent{color:var(--pumpkin-700)}
.ov-banner-label{font-size:10px;font-weight:600;color:var(--fg-3);text-transform:uppercase;letter-spacing:.07em;margin-top:4px;font-family:var(--font-text)}
.ov-banner-sub{font-size:10px;color:var(--fg-2);margin-top:2px}
/* ===== CARDS ===== */
.card{background:var(--bg-surface);border-radius:var(--radius-xl);border:1px solid var(--border-1);padding:20px}
.card-title{font-size:11px;font-weight:700;color:var(--fg-2);font-family:var(--font-sans);letter-spacing:.06em;text-transform:uppercase;margin-bottom:16px;display:flex;align-items:center;gap:8px}
.card-title .ct{font-weight:400;color:var(--fg-3);font-size:11px;text-transform:none;letter-spacing:0}
/* ===== GRIDS ===== */
.g2{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px}
.g3{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:14px}
.g4{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:14px}
.g4>.card{min-width:0}
.g4>.card table{table-layout:fixed;width:100%}
.g4>.card .ccode{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* ===== KPI CARDS ===== */
.kpi-row{display:grid;gap:12px;margin-bottom:20px}
.kpi-row.cols-6{grid-template-columns:repeat(6,1fr)}
.kpi-row.cols-4{grid-template-columns:repeat(4,1fr)}
.kpi-card{background:var(--bg-surface);border:1px solid var(--border-1);border-radius:var(--radius-lg);padding:16px}
.kpi-card .k-label{font-size:10.5px;font-weight:600;color:var(--fg-3);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px;font-family:var(--font-text)}
.kpi-card .k-val{font-family:var(--font-sans);font-size:22px;font-weight:700;color:var(--fg-1);line-height:1.15}
.kpi-card .k-sub{font-size:11px;color:var(--fg-2);margin-top:4px}
.kpi-card.primary .k-val{color:var(--dd-primary-900)}
.kpi-card.success .k-val{color:var(--green-700)}
.kpi-card.warning .k-val{color:var(--pumpkin-700)}
.kpi-card.danger .k-val{color:var(--red-700)}
/* ===== TABLES ===== */
.tbl-wrap{border:1px solid var(--border-1);border-radius:var(--radius-lg);overflow:auto}
.tbl-wrap table,.pp-root table{width:100%;border-collapse:collapse;font-size:12.5px;color:var(--fg-1)}
.pp-root thead,.pp-root .tbl-wrap thead{background:var(--bg-app)}
.pp-root thead th,.pp-root .tbl-wrap thead th{padding:9px 12px;text-align:left;font-size:10.5px;font-weight:700;color:var(--fg-3);text-transform:uppercase;letter-spacing:.05em;border-bottom:1px solid var(--border-1);white-space:nowrap;font-family:var(--font-text)}
.pp-root tbody tr{border-bottom:1px solid var(--border-1);transition:background .1s}
.pp-root tbody tr:last-child{border-bottom:none}
.pp-root tbody td{padding:9px 12px;vertical-align:middle}
.pp-root tbody tr:hover{background:var(--bg-app)}
.num{text-align:right!important}
.cname{font-size:12.5px;font-weight:500;color:var(--fg-1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px}
.ccode{font-size:10.5px;color:var(--fg-3);font-family:var(--font-mono,monospace);margin-top:1px}
.th-tip{cursor:default;position:relative}
.th-tip::after{content:attr(title);display:none;position:absolute;top:100%;left:0;background:var(--fg-1);color:#fff;padding:4px 8px;border-radius:4px;font-size:10px;white-space:nowrap;z-index:100;pointer-events:none}
.th-tip:hover::after{display:block}
/* ===== BADGES ===== */
.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:var(--radius-pill);font-size:10.5px;font-weight:600}
.b-acima{background:var(--green-100);color:var(--green-700)}
.b-abaixo{background:var(--red-100);color:var(--red-700)}
.b-warn{background:var(--yellow-100);color:var(--yellow-700)}
.b-exp{background:var(--red-100);color:var(--red-700)}
.b-care{background:color-mix(in oklch,var(--dd-primary-900) 10%,transparent);color:var(--dd-primary-900)}
.b-careplus{background:var(--purple-100);color:var(--purple-700)}
.b-nocare{background:var(--bg-app);color:var(--fg-3);border:1px solid var(--border-1)}
.b-neutral{background:var(--bg-app);color:var(--fg-2)}
.b-zero{background:var(--bg-app);color:var(--fg-3)}
.bar-pct{display:inline-block;width:48px;height:4px;border-radius:2px;background:var(--border-1);vertical-align:middle;position:relative;overflow:hidden}
.bf{position:absolute;left:0;top:0;height:100%;border-radius:2px}
.bf-g{background:var(--green-700)}
.bw{background:var(--pumpkin-700)!important}
.bb{background:var(--red-700)!important}
.pct-g{color:var(--green-700);font-weight:700}
.alert-pill,.pill-crit,.pill-exp,.pill-warn,.pill-info{display:inline-flex;align-items:center;padding:1px 7px;border-radius:var(--radius-pill);font-size:10px;font-weight:700}
.pill-crit{background:var(--red-100);color:var(--red-700)}
.pill-exp{background:var(--pumpkin-100);color:var(--pumpkin-700)}
.pill-warn{background:var(--yellow-100);color:var(--yellow-700)}
.pill-info{background:var(--bg-app);color:var(--fg-2);border:1px solid var(--border-1)}
/* ===== FILTER ROW ===== */
.frow{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px}
.frow input,.frow select,.filter-row input,.filter-row select{height:32px;padding:0 10px;border:1px solid var(--border-1);border-radius:var(--radius-sm);font-size:12.5px;color:var(--fg-1);background:var(--bg-surface);outline:none;font-family:var(--font-text)}
.frow input:focus,.frow select:focus,.filter-row input:focus,.filter-row select:focus{border-color:var(--dd-primary-600);box-shadow:0 0 0 2px color-mix(in oklch,var(--dd-primary-600) 15%,transparent)}
.filter-row td{padding:8px 12px;border-bottom:1px solid var(--border-1)}
.rc{font-size:11px;color:var(--fg-3);font-family:var(--font-mono,monospace);padding:0 4px}
/* ===== BUTTONS ===== */
.fb{height:30px;padding:0 12px;background:var(--bg-surface);color:var(--fg-2);border:1px solid var(--border-1);border-radius:var(--radius-sm);cursor:pointer;font-size:12px;font-family:var(--font-text);transition:all .15s}
.fb:hover{background:var(--bg-app);color:var(--fg-1)}
.fb.on{background:var(--dd-primary-900);color:#fff;border-color:var(--dd-primary-900)}
.fb.on-r{background:var(--red-100);color:var(--red-700);border-color:color-mix(in oklch,var(--red-700) 30%,transparent)}
.fb.on-g{background:var(--green-100);color:var(--green-700);border-color:color-mix(in oklch,var(--green-700) 30%,transparent)}
.fb.on-o{background:var(--pumpkin-100);color:var(--pumpkin-700);border-color:color-mix(in oklch,var(--pumpkin-700) 30%,transparent)}
.btn-primary{background:var(--dd-primary-900);color:#fff;border:none;border-radius:var(--radius-sm);padding:6px 14px;cursor:pointer;font-size:12px;font-weight:600;display:inline-flex;align-items:center;gap:6px;font-family:var(--font-text)}
.btn-primary:hover{opacity:.9}
.btn-secondary{background:var(--bg-surface);color:var(--fg-1);border:1px solid var(--border-1);border-radius:var(--radius-sm);padding:6px 12px;cursor:pointer;font-size:12px;display:inline-flex;align-items:center;gap:6px;font-family:var(--font-text)}
.btn-secondary:hover{background:var(--bg-app)}
.btn-analisar{height:26px;padding:0 10px;background:var(--bg-app);color:var(--dd-primary-900);border:1px solid var(--border-1);border-radius:var(--radius-sm);cursor:pointer;font-size:11px;font-weight:600;white-space:nowrap}
.btn-analisar:hover{background:var(--dd-primary-900);color:#fff;border-color:var(--dd-primary-900)}
.btn-icon{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;background:var(--bg-app);border:1px solid var(--border-1);border-radius:var(--radius-sm);cursor:pointer;color:var(--fg-2)}
.btn-icon:hover{background:var(--border-1)}
.pp-btn-mini{height:24px;padding:0 8px;font-size:10.5px;border:1px solid var(--border-1);border-radius:var(--radius-sm);cursor:pointer;background:var(--bg-surface);color:var(--fg-2)}
.pp-btn-mini:hover{background:var(--bg-app);color:var(--fg-1)}
.pp-btn-mini.danger:hover{background:var(--red-100);color:var(--red-700);border-color:color-mix(in oklch,var(--red-700) 30%,transparent)}
.time-label{font-size:11px;color:var(--fg-3)}
.tm-btn{height:30px;padding:0 12px;background:var(--bg-surface);color:var(--fg-2);border:1px solid var(--border-1);border-radius:var(--radius-sm);cursor:pointer;font-size:12px}
.tm-btn.active{background:var(--dd-primary-900);color:#fff;border-color:var(--dd-primary-900)}
/* ===== OVERVIEW — HEALTH + RAG SIDE-BY-SIDE ===== */
.ov-health-rag-layout{display:grid;grid-template-columns:240px 1fr;gap:0;align-items:start}
.ov-health-side{padding-right:24px;border-right:1px solid var(--border-1)}
.ov-rag-side{padding-left:24px}
.ov-health-body{display:flex;flex-direction:column;align-items:center;gap:4px}
.ov-gauge-wrap svg{display:block;width:180px;height:auto}
.ov-health-score{font-family:var(--font-sans);font-size:32px;font-weight:700;line-height:1;text-align:center}
.ov-health-label{font-size:13px;font-weight:600;margin-top:2px;text-align:center}
.ov-health-desc{font-size:12px;color:var(--fg-2);margin-top:4px}
.ov-health-text{display:flex;flex-direction:column;align-items:center}
/* ===== OVERVIEW — RAG BARS ===== */
.rag-row{display:flex;align-items:center;gap:12px;padding:9px 0;border-bottom:1px solid var(--border-1)}
.rag-row:last-child{border-bottom:none}
.rag-label{font-size:12.5px;color:var(--fg-1);width:210px;flex-shrink:0}
.rag-track{flex:1;height:18px;border-radius:4px;overflow:hidden;display:flex;background:var(--bg-app)}
.rag-seg{height:100%;transition:opacity .15s}
.rag-seg:hover,.rag-seg[onclick]:hover{opacity:.8}
.rag-total{font-size:11px;color:var(--fg-3);font-family:var(--font-mono,monospace)}
.rag-count{display:flex;gap:4px;font-size:12px;font-family:var(--font-mono,monospace);min-width:70px;justify-content:flex-end}
/* ===== OVERVIEW — RISK TABLE ===== */
.ov-risk-table{width:100%;border-collapse:collapse;font-size:12.5px}
.ov-risk-table th{padding:8px 12px;text-align:left;font-size:10.5px;font-weight:700;color:var(--fg-3);text-transform:uppercase;letter-spacing:.05em;border-bottom:1px solid var(--border-1)}
.ov-risk-table td{padding:9px 12px;border-bottom:1px solid var(--border-1);color:var(--fg-1)}
.ov-risk-table tr:last-child td{border-bottom:none}
.ov-risk-table th.num,.ov-risk-table td.num{text-align:right}
.ov-risk-cell{font-weight:700;font-family:var(--font-mono,monospace);font-size:13px}
.ov-risk-cell:hover{text-decoration:underline}
/* ===== OVERVIEW — DRILLDOWN ===== */
.ov-drill-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}
.ov-drill-title{font-size:13px;font-weight:600;color:var(--fg-1)}
.ov-drill-title .ct{font-size:11px;font-weight:400;color:var(--fg-3)}
.ov-drill-close{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;background:var(--bg-app);border:1px solid var(--border-1);border-radius:50%;cursor:pointer;color:var(--fg-2);font-size:13px}
.ov-drill-close:hover{background:var(--border-1)}
.ov-drill-empty{text-align:center;padding:32px;color:var(--fg-3);font-size:13px}
/* ===== VENDOR BARS ===== */
.vbar{display:flex;align-items:center;gap:12px;padding:8px 0;border-bottom:1px solid var(--border-1)}
.vbar:last-child{border-bottom:none}
.vname{font-size:12px;color:var(--fg-1);width:90px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vtracks{flex:1;min-width:0}
.vtrack-label{font-size:10.5px;color:var(--fg-3);margin-bottom:4px}
.vtrack-bar{height:10px;border-radius:5px;background:var(--bg-app);overflow:hidden;position:relative}
.vtrack-fill{height:100%;border-radius:5px;transition:width .3s}
.vpct-badge{font-size:12px;font-weight:700;font-family:var(--font-mono,monospace);min-width:40px;text-align:right}
/* ===== OVERVIEW METRICS ===== */
.ov-metric{margin-bottom:12px}
.ov-metric label{display:flex;justify-content:space-between;font-size:12.5px;margin-bottom:5px;color:var(--fg-1)}
.ov-metric label strong{font-weight:600}
.gauge{height:6px;border-radius:3px;background:var(--bg-app);overflow:hidden}
.gauge-fill{height:100%;border-radius:3px;transition:width .3s}
/* ===== M2 TAB ===== */
.m2-group-row{background:color-mix(in oklch,var(--dd-primary-900) 5%,transparent);border-left:3px solid var(--dd-primary-600,#3859D0);transition:background .15s}
.m2-group-row td{padding:9px 12px}
.m2-group-row:hover{background:color-mix(in oklch,var(--dd-primary-900) 9%,transparent)}
.m2-group-row .cname{font-weight:700;color:var(--fg-1)}
.m2-detail-row td:first-child{padding-left:36px}
.m2-detail-row:hover{background:var(--bg-app)}
.m2-detail-row .cname{color:var(--fg-2)}
.m2-detail-row .ccode{color:var(--fg-3)}
.m2-group-count{font-size:11px;color:var(--fg-3);font-family:var(--font-mono,monospace)}
.m2-arrow-chip{font-size:10px;color:var(--dd-primary-600);margin-right:4px;transition:transform .15s;display:inline-block}
.m2-arrow-chip.is-open{transform:rotate(90deg)}
.ratio-stat{display:inline-flex;flex-direction:column;align-items:flex-end}
.ratio-nums{font-family:var(--font-mono,monospace);font-size:12px}
.ratio-sep{color:var(--fg-3);margin:0 2px}
.ratio-esp{color:var(--fg-3)}
.ratio-unit{font-size:10px;color:var(--fg-3);font-family:var(--font-mono,monospace)}
.interval-col{font-size:11px;color:var(--fg-3);font-family:var(--font-mono,monospace)}
.cell-stat{display:inline-block;position:relative}
.cell-stat::after{content:'';position:absolute;bottom:-2px;left:0;width:100%;height:2px;border-radius:1px;background:currentColor;opacity:.3}
/* ===== ALERT TAB ===== */
.alert-item{padding:14px 16px;border-bottom:1px solid var(--border-1);background:var(--bg-surface)}
.alert-item:last-child{border-bottom:none}
.alert-item.crit{border-left:3px solid var(--red-700)}
.alert-item.warn{border-left:3px solid var(--pumpkin-700)}
.alert-item.exp{border-left:3px solid var(--yellow-700)}
.alert-item.info{border-left:3px solid var(--dd-primary-600)}
.alert-item.pc{border-left:3px solid var(--purple-700)}
.alert-item.pc .alert-body span{color:var(--purple-700)}
.alert-body{font-size:12.5px;color:var(--fg-1);line-height:1.5}
.alert-body span{font-weight:600}
.alert-body strong{color:var(--fg-1);font-weight:700}
.pc-del{width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid var(--border-1);border-radius:50%;cursor:pointer;color:var(--fg-3);font-size:12px}
.pc-del:hover{background:var(--red-100);color:var(--red-700);border-color:color-mix(in oklch,var(--red-700) 30%,transparent)}
.pc-form{margin-top:12px;padding:12px;background:var(--bg-app);border-radius:var(--radius-md)}
.pc-form select,.pc-form input[type=text]{height:30px;padding:0 8px;border:1px solid var(--border-1);border-radius:var(--radius-sm);font-size:12px;background:var(--bg-surface);color:var(--fg-1)}
.pc-form select:focus,.pc-form input[type=text]:focus,.pc-form textarea:focus{border-color:var(--dd-primary-600);outline:none}
.pc-form textarea{padding:6px 8px;border:1px solid var(--border-1);border-radius:var(--radius-sm);font-size:12px;width:100%;resize:vertical;background:var(--bg-surface);color:var(--fg-1)}
/* ===== RENTING TAB ===== */
.pp-contract-card{background:var(--bg-surface);border:1px solid var(--border-1);border-radius:var(--radius-lg);overflow:hidden;margin-bottom:12px}
.pp-contract-card:last-child{margin-bottom:0}
.pp-contract-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border-1);background:var(--bg-app)}
.pp-contract-code{font-family:var(--font-mono,monospace);font-size:11.5px;font-weight:700;color:var(--dd-primary-900)}
.pp-contract-sub{font-size:11px;color:var(--fg-3)}
.pp-contract-body{padding:14px 16px}
.pp-field label{font-size:11px;font-weight:600;color:var(--fg-2);display:block;margin-bottom:4px}
.pp-field select,.pp-field input[type="number"]{height:30px;padding:0 8px;border:1px solid var(--border-1);border-radius:var(--radius-sm);font-size:12.5px;color:var(--fg-1);background:var(--bg-surface);outline:none}
.pp-field select:focus,.pp-field input[type="number"]:focus{border-color:var(--dd-primary-600);box-shadow:0 0 0 2px color-mix(in oklch,var(--dd-primary-600) 15%,transparent)}
.pp-field select.pp-undefined{color:var(--fg-3)}
.pp-renting-cmp{font-size:12px;color:var(--fg-2);line-height:1.5}
.pp-renting-cmp strong{color:var(--fg-1);font-weight:700}
/* ===== REPO TAB ===== */
.repo-suggest-list{display:flex;flex-direction:column;gap:4px;margin-top:8px}
.repo-suggest-item{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:var(--radius-sm);background:var(--bg-app);font-size:12px;color:var(--fg-2)}
.repo-suggest-item.signed{background:var(--green-100);color:var(--green-700)}
.repo-suggest-item.signed .repo-file{color:var(--green-700)}
.repo-suggest-item .repo-folder{font-size:11px;color:var(--fg-3)}
.repo-suggest-item .repo-file{font-weight:500;color:var(--fg-1)}
.repo-suggest-empty{text-align:center;padding:24px;color:var(--fg-3);font-size:12.5px}
.repo-section-label{font-size:10.5px;font-weight:700;color:var(--fg-3);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px}
.pp-attach-empty{text-align:center;padding:20px;color:var(--fg-3);font-size:12px;display:flex;flex-direction:column;align-items:center;gap:6px}
.pp-attach-empty span{display:block}
.pp-attach-file{display:flex;align-items:center;gap:8px;padding:8px 12px;border:1px solid var(--border-1);border-radius:var(--radius-sm);margin-bottom:6px;background:var(--bg-surface)}
.pp-attach-file .pp-af-info{flex:1;min-width:0}
.pp-attach-file .pp-af-info span{display:block;font-size:11.5px;color:var(--fg-3)}
/* ===== COMM TAB ===== */
.comm-layout{display:flex;height:100%}
.comm-sidebar{width:280px;flex-shrink:0;border-right:1px solid var(--border-1);display:flex;flex-direction:column;overflow:hidden;background:var(--bg-surface)}
.comm-sidebar-header{padding:14px 16px;border-bottom:1px solid var(--border-1)}
.comm-sidebar-header h3{font-size:13px;font-weight:600;color:var(--fg-1);margin-bottom:8px}
.comm-sidebar-header input{width:100%;height:30px;padding:0 10px;border:1px solid var(--border-1);border-radius:var(--radius-sm);font-size:12px;color:var(--fg-1);background:var(--bg-app);outline:none}
.comm-sidebar-header input:focus{border-color:var(--dd-primary-600)}
.comm-client-list{flex:1;overflow-y:auto;padding:8px 0}
.comm-client-item{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;cursor:pointer;transition:background .1s}
.comm-client-item:hover{background:var(--bg-app)}
.comm-client-item.active{background:color-mix(in oklch,var(--dd-primary-900) 6%,transparent)}
.comm-client-item:last-child{border-bottom:none}
.comm-client-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;margin-top:5px}
.comm-client-name{font-size:12.5px;font-weight:500;color:var(--fg-1)}
.comm-client-meta{font-size:11px;color:var(--fg-3);margin-top:1px}
.comm-client-flags{display:flex;gap:4px;margin-top:4px;flex-wrap:wrap}
.comm-main{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--bg-app)}
.comm-toolbar{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;border-bottom:1px solid var(--border-1);background:var(--bg-surface)}
.comm-toolbar h3{font-size:13px;font-weight:600;color:var(--fg-1)}
.email-preview-wrap{flex:1;overflow-y:auto;padding:20px;min-height:0}
/* ===== EMPTY STATE ===== */
.empty-state{text-align:center;padding:48px 24px;color:var(--fg-3)}
.empty-state svg{margin-bottom:12px;opacity:.4}
.empty-state h4{font-size:14px;font-weight:600;color:var(--fg-2);margin-bottom:6px}
.empty-state p{font-size:12.5px;line-height:1.6}
/* ===== ANALYSIS MODAL ===== */
.an-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .2s;padding:24px}
.an-overlay.open{opacity:1;pointer-events:all}
.an-modal{width:900px;max-width:100%;max-height:90vh;background:var(--bg-surface);box-shadow:0 8px 48px rgba(0,0,0,.18);border-radius:16px;display:flex;flex-direction:column;overflow:hidden}
.an-close{width:32px;height:32px;flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;background:var(--bg-app);border:1px solid var(--border-1);border-radius:50%;cursor:pointer;color:var(--fg-2);font-size:14px}
.an-close:hover{background:var(--border-1)}
.an-header{padding:20px 24px 16px;border-bottom:1px solid var(--border-1);display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
.an-header h3{font-size:20px;font-weight:700;color:var(--fg-1);line-height:1.25}
.an-header .an-sub{font-size:12px;color:var(--fg-3);margin-top:4px}
.an-tabs-row{padding:0 24px;border-bottom:1px solid var(--border-1)}
.an-tabs{display:flex}
.an-tab{height:36px;padding:0 14px;background:none;border:none;border-radius:20px;cursor:pointer;font-size:12.5px;font-weight:500;color:var(--fg-2);margin:6px 2px;transition:background .15s,color .15s}
.an-tab.active{background:var(--dd-primary-900,#3859D0);color:#fff;font-weight:600}
.an-tab:hover:not(.active){background:var(--bg-app);color:var(--fg-1)}
.an-body{flex:1;overflow-y:auto;padding:20px 24px}
.an-section{margin-bottom:20px}
.an-section:last-child{margin-bottom:0}
.an-section-title{font-size:11px;font-weight:700;color:var(--fg-3);text-transform:uppercase;letter-spacing:.06em;margin-bottom:10px}
.an-section table{width:100%;border-collapse:collapse;font-size:12.5px}
.an-section thead th{padding:6px 10px;text-align:left;font-size:10.5px;font-weight:700;color:var(--fg-3);border-bottom:1px solid var(--border-1)}
.an-section td{padding:8px 10px;border-bottom:1px solid var(--border-1)}
.an-info-grid{display:grid;gap:10px}
.an-info-section{margin-bottom:16px}
.an-info-section-title{font-size:11px;font-weight:700;color:var(--fg-3);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px}
.an-info-section:last-of-type{margin-bottom:0}
.an-info-item{background:var(--bg-app);border:1px solid var(--border-1);border-radius:var(--radius-sm,8px);padding:12px}
.an-info-label{font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--fg-3)}
.an-info-value{font-size:14px;font-weight:600;color:var(--fg-1);margin-top:6px;word-break:break-word}
.an-info-note{font-size:11px;color:var(--fg-3);margin-top:2px;font-style:italic}
.an-contract-alert{padding:12px;background:var(--bg-app);border-radius:var(--radius-sm);border-left:3px solid var(--red-700);margin-bottom:10px}
.an-contract-alert-title{font-size:12px;font-weight:700;color:var(--red-700)}
.an-contract-alert-text{font-size:12px;color:var(--fg-1);margin-top:4px;line-height:1.5}
.an-contract-alert-list{margin-top:6px;padding-left:16px}
.an-contract-alert-list li{font-size:11.5px;color:var(--fg-2);line-height:1.6}
.an-contract-alert-list li:last-child{margin-bottom:0}
.an-fin-yearrow{display:flex;align-items:center;gap:8px;margin-bottom:12px}
.an-fin-yearrow label{font-size:11.5px;color:var(--fg-2)}
.an-fin-yearrow select{height:28px;padding:0 8px;border:1px solid var(--border-1);border-radius:var(--radius-sm);font-size:12px;background:var(--bg-surface);color:var(--fg-1)}
.an-fin-yearrow select:focus{border-color:var(--dd-primary-600);outline:none}
.an-fin-note{font-size:11.5px;color:var(--fg-3);line-height:1.5}
.an-total-row td{font-weight:700;border-top:2px solid var(--border-1)}
.an-latest-badge{display:inline-flex;align-items:center;padding:1px 6px;border-radius:var(--radius-pill);font-size:9.5px;background:var(--dd-primary-900);color:#fff;margin-left:4px;font-family:var(--font-mono,monospace)}
.an-latest-row{background:color-mix(in oklch,var(--dd-primary-900) 4%,transparent)}
.an-chart-wrap{overflow-x:auto;margin-bottom:12px}
.an-chart-lg{display:flex;align-items:flex-end;gap:5px;height:100px;padding-bottom:4px}
.an-chart-lg i{flex:1;border-radius:3px 3px 0 0;background:var(--dd-primary-600,#3859D0);display:block;min-height:2px}
.an-chart-lg i.dashed{background:repeating-linear-gradient(135deg,var(--dd-primary-600,#3859D0),var(--dd-primary-600,#3859D0) 4px,transparent 4px,transparent 8px)}
.an-chart-legend{display:flex;gap:12px;font-size:11px;color:var(--fg-3);flex-wrap:wrap}
.an-avg-card{background:var(--bg-app);border-radius:var(--radius-sm);padding:12px;margin-bottom:12px}
.an-avg-body{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.an-avg-label{font-size:10.5px;color:var(--fg-3);text-transform:uppercase;font-weight:600}
.an-avg-value{font-size:18px;font-weight:700;color:var(--fg-1);font-family:var(--font-sans);line-height:1}
.an-avg-pct{font-size:12px;font-weight:700;margin-top:2px}
.an-avg-vs{font-size:11px;color:var(--fg-3);margin-top:2px}
.an-analyst{padding:10px 12px;border-radius:var(--radius-sm);margin-bottom:8px;border-left:3px solid transparent}
.an-analyst-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;margin-bottom:3px}
.an-analyst-text{font-size:12.5px;line-height:1.5}
.an-analyst.t-verde{background:var(--green-100);border-left-color:var(--green-700)}
.an-analyst.t-verde .an-analyst-label{color:var(--green-700)}
.an-analyst.t-amarelo{background:var(--yellow-100);border-left-color:var(--yellow-700)}
.an-analyst.t-amarelo .an-analyst-label{color:var(--yellow-700)}
.an-analyst.t-vermelho{background:var(--red-100);border-left-color:var(--red-700)}
.an-analyst.t-vermelho .an-analyst-label{color:var(--red-700)}
.an-empty-note{text-align:center;padding:24px;color:var(--fg-3);font-size:12.5px}
/* ===== EMAIL ===== */
.email-outer{max-width:640px;margin:0 auto}
.email-meta{padding:12px 16px;background:var(--bg-app);border-radius:var(--radius-md);margin-bottom:12px;font-size:12px;color:var(--fg-2);display:flex;flex-direction:column;gap:4px;border:1px solid var(--border-1)}
.email-meta strong{color:var(--fg-1)}
.email-frame{border:1px solid var(--border-1);border-radius:var(--radius-lg);overflow:hidden;background:#fff;font-family:var(--font-body,var(--font-sans,system-ui,-apple-system,sans-serif))}
.email-header-band{background:var(--dd-primary-900);padding:24px 32px;color:#fff}
.email-header-band .co-name{font-size:11px;font-weight:700;color:rgba(255,255,255,.5);letter-spacing:.1em;text-transform:uppercase;margin-bottom:6px}
.email-header-band h2{font-size:18px;font-weight:700;color:#fff;margin-bottom:4px}
.email-header-band .subtitle{color:rgba(255,255,255,.6);font-size:12px}
.email-body{padding:28px 32px}
.email-salutation{font-size:14px;margin-bottom:12px}
.email-intro{font-size:13px;line-height:1.65;margin-bottom:20px;color:#444}
.email-contract-block{border:1px solid #e0e0e0;border-radius:8px;overflow:hidden;margin-bottom:16px}
.ecb-header{padding:12px 16px;background:#f5f5f5;border-bottom:1px solid #e0e0e0;display:flex;align-items:baseline;justify-content:space-between}
.ecb-header .ecb-title{font-size:13px;font-weight:700;color:#1a1a2e}
.ecb-header .ecb-dates{font-size:11px;color:#888}
.ecb-body{padding:16px}
.ecb-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin-bottom:12px}
.ecb-stat{padding:10px 12px;border-radius:6px;background:#f9f9f9;border:1px solid #efefef}
.ecb-stat.bad{background:#fff5f5;border-color:#ffd0d0}
.ecb-stat.good{background:#f0fff8;border-color:#c3e6d4}
.ecb-stat.warn{background:#fffbf0;border-color:#fae5a0}
.ecb-stat .es-label{font-size:10.5px;font-weight:700;text-transform:uppercase;color:#888;letter-spacing:.04em;margin-bottom:4px}
.ecb-stat .es-value{font-size:14px;font-weight:700;color:#1a1a2e}
.ecb-stat .es-sub{font-size:11px;color:#666;margin-top:2px}
.ecb-progress{margin-bottom:12px}
.ecb-progress .ep-label{display:flex;justify-content:space-between;font-size:11px;color:#888;margin-bottom:4px}
.ecb-progress .ep-bar{height:8px;background:#eee;border-radius:4px;overflow:hidden}
.ecb-progress .ep-fill{height:100%;border-radius:4px}
.ecb-alert{display:flex;align-items:center;gap:12px;padding:12px;border-radius:6px;font-size:12.5px;margin-top:12px}
.ecb-alert.crit{background:#fff0f0;border:1px solid #ffcccc;color:#7a2020}
.ecb-alert.exp{background:#fff8e6;border:1px solid #fde68a;color:#7a5000}
.ecb-alert.warn{background:#fffbf0;border:1px solid #fae5a0;color:#7a5f10}
.ecb-alert strong{font-weight:700}
.ecb-focus-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px}
.ecb-focus-card{background:#fff;border:1px solid #e0e0e0;border-radius:6px;padding:10px 14px}
.ecb-focus-label{font-size:10.5px;color:#888;text-transform:uppercase;font-weight:700;letter-spacing:.04em;margin-bottom:4px}
.ecb-focus-val{font-size:16px;font-weight:700;color:#7a2020}
.ecb-focus-sub{font-size:11px;color:#888;margin-top:2px}
.email-closing{margin-top:24px;padding-top:16px;border-top:1px solid #eee;font-size:13px;line-height:1.8;color:#444}
.email-footer-band{display:flex;justify-content:space-between;align-items:center;padding:14px 32px;background:#f5f6f8;border-top:1px solid #e0e0e0}
.email-footer-band .ef-company{font-size:12px;font-weight:700;color:#1a1a2e}
.email-footer-band .ef-contact{font-size:11px;color:#888}
/* ===== RESPONSIVE ===== */
@media(max-width:1100px){
  .kpi-row.cols-6{grid-template-columns:repeat(3,1fr)}
  .g2,.g3{grid-template-columns:1fr}
  .ov-banner{grid-template-columns:repeat(3,1fr)}
  .ov-health-rag-layout{grid-template-columns:1fr}
  .ov-health-side{padding-right:0;border-right:none;padding-bottom:16px;border-bottom:1px solid var(--border-1)}
  .ov-rag-side{padding-left:0;padding-top:16px}
}
/* ===== PRINT ===== */
@media print {
  .comm-sidebar-header,nav,button{display:none!important}
  .an-overlay{display:none!important}
  .pp-root .pp-tabnav{display:none!important}
  .comm-sidebar{display:none!important}
  .comm-main{width:100%!important}
  .pp-tab-content{overflow:visible!important;height:auto!important}
}
`;

(function ppInjectCSS(){
  if(document.getElementById('pp-styles'))return;
  const s=document.createElement('style');s.id='pp-styles';
  s.textContent=_PP_CSS;document.head.appendChild(s);
})();

/* ===== RENTING localStorage ===== */
const PP_RENTING_KEY='pp_renting_overrides';
const PP_RENTING_VALOR_KEY='pp_renting_valor_overrides';
function ppLoadRentingOverrides(){
  let saved={};try{saved=JSON.parse(localStorage.getItem(PP_RENTING_KEY)||'{}');}catch(e){}
  let savedVal={};try{savedVal=JSON.parse(localStorage.getItem(PP_RENTING_VALOR_KEY)||'{}');}catch(e){}
  DATA.forEach(r=>{
    let t=saved[r.contrato]||null;
    if(t==='Renting Externo - Digirent')t='Renting Externo';
    r.tipo_renting=t;r.renting_valor=savedVal[r.contrato]!=null?savedVal[r.contrato]:null;
  });
}
function ppSetRentingType(contrato,value){
  const rec=DATA.find(r=>r.contrato===contrato);if(rec)rec.tipo_renting=value||null;
  let s={};try{s=JSON.parse(localStorage.getItem(PP_RENTING_KEY)||'{}');}catch(e){}
  if(value)s[contrato]=value;else delete s[contrato];
  localStorage.setItem(PP_RENTING_KEY,JSON.stringify(s));
}
function ppSetRentingValor(contrato,value){
  const rec=DATA.find(r=>r.contrato===contrato);if(rec)rec.renting_valor=value;
  let s={};try{s=JSON.parse(localStorage.getItem(PP_RENTING_VALOR_KEY)||'{}');}catch(e){}
  if(value!=null)s[contrato]=value;else delete s[contrato];
  localStorage.setItem(PP_RENTING_VALOR_KEY,JSON.stringify(s));
}

/* ===== IndexedDB attachments ===== */
let ppDB=null;
function ppDBOpen(){
  return new Promise((resolve,reject)=>{
    if(ppDB){resolve(ppDB);return;}
    const req=indexedDB.open('printplan_attachments',1);
    req.onupgradeneeded=e=>{const db=e.target.result;if(!db.objectStoreNames.contains('files'))db.createObjectStore('files',{keyPath:'contrato'});};
    req.onsuccess=e=>{ppDB=e.target.result;resolve(ppDB);};
    req.onerror=e=>reject(e.target.error);
  });
}
function ppAttachGet(c){return ppDBOpen().then(db=>new Promise((res,rej)=>{const t=db.transaction('files','readonly');const r=t.objectStore('files').get(c);r.onsuccess=()=>res(r.result);r.onerror=()=>res(null);}));}
function ppAttachPut(rec){return ppDBOpen().then(db=>new Promise((res,rej)=>{const t=db.transaction('files','readwrite');const r=t.objectStore('files').put(rec);r.onsuccess=()=>res();r.onerror=()=>rej(r.error);}));}
function ppAttachDelete(c){return ppDBOpen().then(db=>new Promise((res,rej)=>{const t=db.transaction('files','readwrite');const r=t.objectStore('files').delete(c);r.onsuccess=()=>res();r.onerror=()=>rej(r.error);}));}

/* ===== Helpers ===== */
function fmt(n,dec=0){const num=Number(n);const neg=num<0;const parts=Math.abs(num).toFixed(dec).split('.');parts[0]=parts[0].replace(/\B(?=(\d{3})+(?!\d))/g,'\u00a0');return(neg?'-':'')+(dec>0?parts.join(','):parts[0]);}
function fmtE(n){if(n>=1000000)return fmt(n/1000000,1)+'M €';if(n>=1000)return fmt(n/1000,1)+'K €';return fmt(n,0)+' €'}
function fmtEFull(n){return fmt(n,2)+' €'}
function fmtK(n){if(n>=1000000)return fmt(n/1000000,1)+'M';if(n>=1000)return fmt(n/1000,1)+'K';return fmt(n,0)}
function clamp(v,lo,hi){return Math.max(lo,Math.min(hi,v))}
function desvioColor(pct,sign,tol=15){const eff=(pct||0)*(sign||1);if(eff<0)return'var(--green-700)';if(eff>tol)return'var(--red-700)';return'var(--yellow-700)';}
function shortV(v){return(v||'').replace('DECAL PT ','').replace('Decal PT ','').replace('Decal ES ','ES·').replace('DECAL ES ','');}
function matchText(val,q){if(!q)return true;return String(val==null?'':val).toLowerCase().includes(q.toLowerCase());}
function matchNum(val,q){
  if(!q)return true;q=q.trim();if(val===null||val===undefined||val==='')return false;
  const n=Number(val);let m=q.match(/^(>=|<=|>|<)\s*(-?\d+(?:[.,]\d+)?)$/);
  if(m){const op=m[1],lim=parseFloat(m[2].replace(',','.'));if(op==='>=')return n>=lim;if(op==='<=')return n<=lim;if(op==='>')return n>lim;if(op==='<')return n<lim;}
  m=q.match(/^(-?\d+(?:[.,]\d+)?)\s*-\s*(-?\d+(?:[.,]\d+)?)$/);
  if(m){const lo=parseFloat(m[1].replace(',','.')),hi=parseFloat(m[2].replace(',','.'));return n>=lo&&n<=hi;}
  return String(val).toLowerCase().includes(q.toLowerCase());
}
function uniqueVals(arr,key){return[...new Set(arr.map(r=>r[key]).filter(v=>v!=null&&v!==''))].sort((a,b)=>String(a).localeCompare(String(b)));}
function finMargemColor(m){return m==null?'var(--fg-3)':m<0?'var(--red-700)':'var(--green-700)';}
function finPctTxt(m){return m==null?'—':(m>=0?'+':'')+fmt(m,1)+'%';}

/* ===== Campanha 0m² ===== */
const CAMPANHA_0M2=new Set(['26008','240007','240005','240001','240006','240020','240012 - SOLVENT','240003','240002']);

/* ===== Alertas helpers ===== */
// Gera os últimos N meses de calendário (temporal) com valor real ou 0 se sem leitura
function alertsM2UltimosMesesCalendario(r,n){
  const byMes={};
  (MONTHLY_REVENUE[r.contrato]||[]).forEach(d=>{byMes[d[0]]=d;});
  const hoje=new Date();
  const result=[];
  for(let i=1;i<=n;i++){
    const d=new Date(hoje.getFullYear(),hoje.getMonth()-i,1);
    const key=d.toISOString().slice(0,7); // YYYY-MM
    const row=byMes[key];
    result.push(row||[key,0,0,0]);
  }
  return result; // mais recente primeiro
}
function alertsM2UltimosMeses(r,n){
  return alertsM2UltimosMesesCalendario(r,n);
}
function alertsM2Criterio(r){
  const win=alertsM2UltimosMesesCalendario(r,3);
  if(!(r.m2_mensal>0))return null;
  const avgReal=win.reduce((s,row)=>s+(row[1]||0),0)/win.length;
  if(avgReal>=r.m2_mensal)return null;
  return{avgReal,n:win.length};
}
function commM2ContractSemEquip(r){return r.equip!==1&&!CAMPANHA_0M2.has(r.contrato)&&r.m2_status==='ABAIXO'&&alertsM2Criterio(r)!=null;}

/* ===== Helper: meses de calendário desde data_inicio ===== */
function calcRendasCalendario(dataInicio){
  if(!dataInicio)return null;
  const parts=dataInicio.match(/(\d{2})-(\d{2})-(\d{4})/)||dataInicio.match(/(\d{4})-(\d{2})/);
  let sd;
  if(parts&&parts[3]){sd=new Date(parseInt(parts[3]),parseInt(parts[2])-1,parseInt(parts[1]));}
  else if(parts&&!parts[3]){sd=new Date(parseInt(parts[1]),parseInt(parts[2])-1,1);}
  else{return null;}
  if(isNaN(sd))return null;
  const now=new Date();
  return Math.max(1,(now.getFullYear()-sd.getFullYear())*12+(now.getMonth()-sd.getMonth()));
}

/* ===== Data init (runs once) ===== */
const _ppFM={};
function ppInitData(){
  FINANCEIRA_DATA.forEach(r=>{_ppFM[r.cliente]=r;});
  const cc={};DATA.forEach(r=>{cc[r.cliente]=(cc[r.cliente]||0)+1;});
  // Recalcular rendas_decorridas com meses de calendário (não apenas leituras)
  DATA.forEach(r=>{
    const cal=calcRendasCalendario(r.data_inicio);
    if(cal!=null){
      r.rendas_decorridas=cal;
      // Recalcular métricas derivadas
      const m2_mensal=r.num_rendas?r.limite_m2/r.num_rendas:0;
      const esperado=Math.round(m2_mensal*cal);
      r.esperado_m2=esperado;
      r.m2_desvio=r.acumulado_m2-esperado;
      r.m2_desvio_pct=esperado>0?Math.round((r.m2_desvio/esperado*100)*10)/10:0;
      r.m2_status=r.m2_desvio>=0?'ACIMA':'ABAIXO';
      r.m2_mensal=m2_mensal;
      const rec_real=Math.round(r.acumulado_m2*(r.pvp||0)*100)/100;
      const rec_pot_atual=esperado*(r.pvp||0);
      const perf_atual=rec_real-rec_pot_atual;
      r.valor_em_atraso=Math.max(0,-perf_atual);
      const m2_restante=Math.max(0,(r.limite_m2||0)-r.acumulado_m2);
      r.m2_restante=m2_restante;
      r.novo_pvp=(r.equip===1&&typeof r.pvp==='number'&&r.valor_em_atraso>0&&m2_restante>0)?(r.pvp+r.valor_em_atraso/m2_restante):null;
    }
  });
  DATA.forEach(r=>{
    const fin=_ppFM[r.cliente];const att=fin&&cc[r.cliente]===1;
    r.fin_valor_liquido=att?fin.valor_liquido:null;
    r.fin_custo_total=att?fin.custo_total:null;
    r.fin_margem_pct=att?fin.margem_pct:null;
  });
  const byC={};DATA.forEach(r=>{(byC[r.cliente]=byC[r.cliente]||[]).push(r);});
  CLIENTS_LIST.forEach(c=>{
    const ct=byC[c.cliente]||[];
    c.has_m2_issue=ct.some(r=>r.novo_pvp!=null);
    c.has_m2_sem_equip_issue=ct.some(commM2ContractSemEquip);
    c.has_tinta_issue=ct.some(r=>r.tinta_tem_dados&&(desvioColor(r.tinta_desvio_pct,1)==='var(--red-700)'||r.tinta_ml_m2_real===0));
    c.has_suporte_issue=ct.some(r=>r.suporte_tem_dados&&desvioColor(r.suporte_desvio_pct,1)==='var(--red-700)');
  });
  ppLoadRentingOverrides();
}
ppInitData();

/* ===== applyMonthlySnapshot ===== */
function applyMonthlySnapshot(r,rows){
  if(!rows||!rows.length){return{...r,acumulado_m2:0,rendas_decorridas:0,m2_impressos_ultimo:0,data_consumo:null,rec_real:0,esperado_m2:0,m2_desvio:0,m2_desvio_pct:0,m2_status:'ABAIXO',rec_falta:Math.max(0,r.rec_pot),m2_mensal:r.num_rendas?r.limite_m2/r.num_rendas:0,perf_mensal_pct:0,rec_pot_atual:0,perf_atual:0,valor_em_atraso:0,m2_restante:Math.max(0,r.limite_m2),novo_pvp:null};}
  const sorted=[...rows].sort((a,b)=>a[0]<b[0]?-1:a[0]>b[0]?1:0);
  const acumulado_m2=sorted.reduce((s,x)=>s+x[1],0);const ultimo=sorted[sorted.length-1];
  // rendas_decorridas = meses de calendário desde data_inicio até hoje (não apenas meses com leitura)
  const rendas_decorridas=(()=>{
    const di=r.data_inicio;if(!di)return sorted.length;
    const parts=di.match(/(\d{2})-(\d{2})-(\d{4})/)||di.match(/(\d{4})-(\d{2})/);
    let startDate;
    if(parts&&parts[3]){startDate=new Date(parseInt(parts[3]),parseInt(parts[2])-1,parseInt(parts[1]));}
    else if(parts&&!parts[3]){startDate=new Date(parseInt(parts[1]),parseInt(parts[2])-1,1);}
    else{return sorted.length;}
    if(isNaN(startDate))return sorted.length;
    const now=new Date();
    return Math.max(1,(now.getFullYear()-startDate.getFullYear())*12+(now.getMonth()-startDate.getMonth()));
  })();
  const rec_real=Math.round(acumulado_m2*r.pvp*100)/100;const esperado_m2=r.num_rendas?Math.round(r.limite_m2/r.num_rendas*rendas_decorridas):0;
  const m2_desvio=acumulado_m2-esperado_m2;const m2_desvio_pct=esperado_m2>0?Math.round((m2_desvio/esperado_m2*100)*10)/10:0;
  const m2_status=m2_desvio>=0?'ACIMA':'ABAIXO';const rec_falta=Math.max(0,Math.round((r.rec_pot-rec_real)*100)/100);
  const m2_mensal=r.num_rendas?r.limite_m2/r.num_rendas:0;const perf_mensal_pct=m2_mensal>0?(ultimo[1]/m2_mensal*100):0;
  const rec_pot_atual=esperado_m2*r.pvp;const perf_atual=rec_real-rec_pot_atual;const valor_em_atraso=Math.max(0,-perf_atual);
  const m2_restante=Math.max(0,r.limite_m2-acumulado_m2);
  const novo_pvp=(r.equip===1&&typeof r.pvp==='number'&&valor_em_atraso>0&&m2_restante>0)?(r.pvp+valor_em_atraso/m2_restante):null;
  return{...r,acumulado_m2,rendas_decorridas,m2_impressos_ultimo:ultimo[1],data_consumo:ultimo[0],rec_real,esperado_m2,m2_desvio,m2_desvio_pct,m2_status,rec_falta,m2_mensal,perf_mensal_pct,rec_pot_atual,perf_atual,valor_em_atraso,m2_restante,novo_pvp};
}

/* ===== ppRentingAnalysis ===== */
function ppRentingAnalysis(r){
  const temValor=r.renting_valor!=null&&r.renting_valor>0;const pvp=r.pvp||0;
  const monthlyRows=MONTHLY_REVENUE[r.contrato]||[];const hasHistory=monthlyRows.length>0;
  const rows=monthlyRows.map(([mes,m2])=>({mes,m2,receita:m2*pvp,margem:temValor?(m2*pvp-r.renting_valor):null}));
  const count=rows.length;let custoAcumulado=null,receitaAcumulada=null,deficitEuro=null,deficitM2=null,status='SEM_VALOR',avgMonthly=null;
  if(hasHistory){receitaAcumulada=rows.reduce((s,row)=>s+row.receita,0);avgMonthly=receitaAcumulada/count;}
  else{avgMonthly=r.rendas_decorridas>0?(r.rec_real/r.rendas_decorridas):0;}
  if(temValor){
    if(hasHistory){custoAcumulado=r.renting_valor*count;deficitEuro=receitaAcumulada-custoAcumulado;deficitM2=pvp>0?((custoAcumulado-receitaAcumulada)/pvp):null;}
    else{deficitEuro=avgMonthly-r.renting_valor;}
    status=deficitEuro>=0?'COBRE':'NAO_COBRE';
  }
  return{temValor,hasHistory,count,custoAcumulado,receitaAcumulada,deficitEuro,deficitM2,status,avgMonthly,rows};
}

/* ===== commNeedsComm ===== */
function commNeedsComm(c){return c.has_m2_issue||c.has_m2_sem_equip_issue||c.has_tinta_issue||c.has_suporte_issue;}

/* ===== Overview gauge SVG ===== */
function ovGaugeSVG(score){
  const cx=110,cy=100,r=82,sw=16;
  const pt=(a,rad)=>{const rd=a*Math.PI/180;return[cx+rad*Math.cos(rd),cy-rad*Math.sin(rd)];};
  const arc=(a0,a1)=>{const[x1,y1]=pt(a0,r),[x2,y2]=pt(a1,r);const la=a0-a1>180?1:0;return`M${x1.toFixed(1)},${y1.toFixed(1)} A${r},${r} 0 ${la} 1 ${x2.toFixed(1)},${y2.toFixed(1)}`;};
  const bands=[[180,108,'var(--red-700)'],[108,54,'var(--pumpkin-700)'],[54,0,'var(--green-700)']];
  const bandsHTML=bands.map(([a0,a1,col])=>`<path d="${arc(a0,a1)}" stroke="${col}" stroke-width="${sw}" fill="none"/>`).join('');
  const clamped=clamp(score,0,100);const needleAngle=180-(clamped/100*180);const[nx,ny]=pt(needleAngle,r-26);
  const st=clamped<40?{color:'var(--red-700)'}:clamped<70?{color:'var(--pumpkin-700)'}:{color:'var(--green-700)'};
  return`<svg viewBox="0 0 220 112" width="220" height="112">${bandsHTML}<line x1="${cx}" y1="${cy}" x2="${nx.toFixed(1)}" y2="${ny.toFixed(1)}" stroke="${st.color}" stroke-width="4" stroke-linecap="round"/><circle cx="${cx}" cy="${cy}" r="7" fill="${st.color}"/></svg>`;
}

/* ===== commM2BlockHTML & commConsumoBlockHTML ===== */
function commM2BlockHTML(r){
  const util=r.limite_m2>0?(r.acumulado_m2/r.limite_m2*100):0;const progPct=Math.min(util,100);
  return`<div class="email-contract-block">
    <div class="ecb-header"><div class="ecb-title">Contrato ${r.contrato}</div><div class="ecb-dates">${r.data_inicio} → ${r.data_termino} · ${r.rendas_decorridas}/${r.num_rendas} meses</div></div>
    <div class="ecb-body">
      <div class="ecb-progress"><div class="ep-label"><span>M² Impressos: ${fmt(r.acumulado_m2)}</span><span>Esperado: ${fmt(r.esperado_m2)}</span></div><div class="ep-bar"><div class="ep-fill" style="width:${progPct}%;background:#B53B3B"></div></div></div>
      <div class="ecb-grid"><div class="ecb-stat bad"><div class="es-label">Défice face ao Contratado</div><div class="es-value">${fmt(r.m2_desvio)} m² (${fmt(r.m2_desvio_pct,1)}%)</div><div class="es-sub">${fmt(r.acumulado_m2)} m² impressos de ${fmt(r.esperado_m2)} m² esperados</div></div></div>
      <div class="ecb-alert crit" style="flex-direction:column;align-items:stretch;gap:0;background:var(--grey-50);border:1px solid var(--border-1)">
        <strong style="font-family:var(--font-body,var(--font-sans,system-ui,sans-serif))">Défice de M² Impressos face ao contratualizado</strong>
        <p style="font-size:13px;color:#444;line-height:1.65;margin:8px 0 12px;font-family:var(--font-body,var(--font-sans,system-ui,sans-serif))">Ao analisarmos o histórico do seu contrato, verificámos que o volume de m² impressos tem ficado, em média, abaixo do valor contratado. Para regularizarmos esta situação, gostaríamos de informar que o valor do contrato será atualizado para:</p>
        <div class="ecb-focus-grid"><div class="ecb-focus-card"><div class="ecb-focus-label">Novo PVP Recalculado</div><div class="ecb-focus-val">${fmtEFull(r.novo_pvp)}/m²</div><div class="ecb-focus-sub">Atual: ${fmtEFull(r.pvp)}/m²</div></div><div class="ecb-focus-card"><div class="ecb-focus-label">Valor em Atraso</div><div class="ecb-focus-val">${fmtEFull(r.valor_em_atraso)}</div><div class="ecb-focus-sub">${fmt(Math.abs(r.m2_desvio))} m² × ${fmtEFull(r.pvp)}/m²</div></div></div>
      </div>
    </div>
  </div>`;
}
function commConsumoBlockHTML(r){
  const blocks=[];
  if(r.tinta_tem_dados&&desvioColor(r.tinta_desvio_pct,1)==='var(--red-700)'){blocks.push(`<div class="ecb-stat bad"><div class="es-label">Consumo Tinta</div><div class="es-value">${fmt(r.tinta_ml_m2_real,1)} ml/m²</div><div class="es-sub">Esperado: ${fmt(r.tinta_ml_m2_esp,1)} ml/m² · ${r.tinta_desvio_pct>=0?'+':''}${fmt(r.tinta_desvio_pct,1)}% acima</div></div>`);}
  if(r.suporte_tem_dados&&desvioColor(r.suporte_desvio_pct,1)==='var(--red-700)'){blocks.push(`<div class="ecb-stat bad"><div class="es-label">Consumo Suportes</div><div class="es-value">${fmt(r.suporte_consumo_mt)} mt</div><div class="es-sub">Impresso: ${fmt(r.suporte_impresso_mt)} mt</div></div>`);}
  return`<div class="email-contract-block"><div class="ecb-header"><div class="ecb-title">Contrato ${r.contrato}</div><div class="ecb-dates">${r.data_inicio} → ${r.data_termino}</div></div><div class="ecb-body"><div class="ecb-grid">${blocks.join('')}</div></div></div>`;
}
function commM2SemEquipBlockHTML(r){
  const util=r.limite_m2>0?(r.acumulado_m2/r.limite_m2*100):0;
  const progPct=Math.min(util,100);
  const m2c=alertsM2Criterio(r)||{avgReal:0,n:3};
  const statBlock=`<div class="ecb-stat bad">
    <div class="es-label">Média dos Últimos ${m2c.n} Meses</div>
    <div class="es-value">${fmt(m2c.avgReal,0)} m²/mês</div>
    <div class="es-sub">Mínimo contratado: ${fmt(r.m2_mensal)} m²/mês · ${fmt(r.acumulado_m2)} m² impressos de ${fmt(r.esperado_m2)} m² esperados · prestação ${r.rendas_decorridas}/${r.num_rendas}</div>
  </div>`;
  const alertBlock=`<div class="ecb-alert crit" style="flex-direction:column;align-items:stretch;gap:0;background:var(--grey-50,#f5f6f8);border:1px solid var(--border-1)">
    <strong>Volume de Impressão Abaixo do Mínimo Contratado</strong>
    <div style="font-size:12px;margin:4px 0 12px;color:#555;line-height:1.6">Ao analisarmos o histórico do seu contrato, verificámos que, nos últimos ${m2c.n} meses, a média de m² impressos (${fmt(m2c.avgReal,0)} m²/mês) tem ficado abaixo do mínimo mensal contratado (${fmt(r.m2_mensal)} m²/mês). Gostaríamos de o assinalar desde já, para que possamos, em conjunto, perceber a melhor forma de regularizar a situação.</div>
    <div style="font-size:11px;line-height:1.6;color:#777">
      O contrato PrintPlan assinado entre o Cliente e a DIGIDELTA prevê, no seu ponto 2, o seguinte:<br>
      <em>"2. O PRIMEIRO CONTRAENTE admite que no período de dois meses seguidos a quantidade mínima de metros impressos pelo SEGUNDO CONTRAENTE seja inferior à quantidade mínima mensal constante na proposta, desde que ao terceiro mês subsequente aos dois meses suprarreferidos a média mínima mensal que se apurar seja igual ou superior ao valor mínimo mensal, o que a não suceder, constitui causa de resolução do contrato por parte do PRIMEIRO CONTRAENTE."</em>
    </div>
    <div style="font-size:11px;margin-top:10px;color:#777;font-style:italic">Nota: Não se trata, para já, de uma comunicação de rescisão — o objetivo é sinalizar a situação a tempo de a regularizarmos em conjunto.</div>
    <div style="font-size:12px;margin-top:12px;color:#555">Ficamos ao dispor para esclarecer qualquer dúvida e encontrar, em conjunto, a solução mais adequada — agradecemos que nos contacte assim que lhe for possível.</div>
  </div>`;
  return `<div class="email-contract-block">
    <div class="ecb-header">
      <div class="ecb-title">Contrato ${r.contrato}</div>
      <div class="ecb-dates">${r.data_inicio} → ${r.data_termino} · ${r.rendas_decorridas}/${r.num_rendas} meses</div>
    </div>
    <div class="ecb-body">
      <div class="ecb-progress">
        <div class="ep-label">
          <span>M² Impressos: ${fmt(r.acumulado_m2)}</span>
          <span>Esperado nesta fase: ${fmt(r.esperado_m2)}</span>
        </div>
        <div class="ep-bar"><div class="ep-fill" style="width:${progPct}%;background:#B53B3B"></div></div>
      </div>
      <div class="ecb-grid">${statBlock}</div>
      ${alertBlock}
    </div>
  </div>`;
}

function buildEmailHTML(clientName,topic,data,emailTo,forSend=false){
  const contratos=data.filter(r=>r.cliente===clientName);if(!contratos.length)return'';
  const c=contratos[0];
  const dateStr=new Date().toLocaleDateString('pt-PT',{day:'2-digit',month:'long',year:'numeric'});
  const mesAno=new Date().toLocaleDateString('pt-PT',{month:'long',year:'numeric'});
  const m2Contratos=contratos.filter(r=>r.novo_pvp!=null);
  const m2SemContratos=contratos.filter(commM2ContractSemEquip);
  const consumoContratos=contratos.filter(r=>{const t=r.tinta_tem_dados&&(desvioColor(r.tinta_desvio_pct,1)==='var(--red-700)'||r.tinta_ml_m2_real===0);const s=r.suporte_tem_dados&&desvioColor(r.suporte_desvio_pct,1)==='var(--red-700)';return t||s;});
  const hasM2=m2Contratos.length>0,hasM2Sem=m2SemContratos.length>0,hasConsumo=consumoContratos.length>0;
  if(!hasM2&&!hasM2Sem&&!hasConsumo)return'';
  const activeTopic=(!['m2','m2sem','consumo'].includes(topic)||!(topic==='m2'?hasM2:topic==='m2sem'?hasM2Sem:hasConsumo))
    ?(hasM2?'m2':hasM2Sem?'m2sem':'consumo'):topic;
  const activeContratos=activeTopic==='m2'?m2Contratos:activeTopic==='m2sem'?m2SemContratos:consumoContratos;
  const blockFn=activeTopic==='m2'?commM2BlockHTML:activeTopic==='m2sem'?commM2SemEquipBlockHTML:commConsumoBlockHTML;
  const blocks=activeContratos.map(blockFn).join('');
  const plural=activeContratos.length>1;
  const subj=activeTopic==='m2'
    ?`Retificação de PVP — Défice de M² Impressos — ${mesAno}`
    :activeTopic==='m2sem'
    ?`Ponto de Situação — M² Impressos — ${mesAno}`
    :`Controlo de Consumos — Tintas/Suportes — ${mesAno}`;
  const heading=activeTopic==='m2'?'Retificação de PVP':activeTopic==='m2sem'?'Ponto de Situação — M² Impressos':'Controlo de Consumos';
  const intro=activeTopic==='m2'
    ?`Vimos por este meio dar-lhe conhecimento da situação do${plural?'s':''} seu${plural?'s':''} contrato${plural?'s':''} PrintPlan relativamente ao volume de m² impressos, à data de <strong>${dateStr}</strong>.`
    :activeTopic==='m2sem'
    ?`Vimos por este meio partilhar o ponto de situação do${plural?'s':''} seu${plural?'s':''} contrato${plural?'s':''} PrintPlan relativamente ao volume de m² impressos, à data de <strong>${dateStr}</strong>.`
    :`Partilhamos o ponto de situação do controlo de consumos do contrato PrintPlan à data de <strong>${dateStr}</strong>.`;
  if(forSend){
    const m2Legal=activeTopic==='m2'?`<div style="font-family:system-ui,-apple-system,sans-serif;margin:16px 0 0;padding:14px 16px;background:#f8f8f8;border:1px solid #ddd;border-radius:6px;font-size:12.5px;color:#444;line-height:1.65">Nos termos da Cláusula Quinta, alínea 5) do contrato PrintPlan assinado entre o Cliente e a DIGIDELTA:<br><em>"5. O PRIMEIRO CONTRAENTE e o SEGUNDO CONTRAENTE acordam que, se a média dos m² impressos nos últimos 6 (seis) meses for inferior à contratada, seja recalculado, pela mesma fórmula de cálculo inicial o preço do m², face a essa média apurada."</em></div><div style="font-family:system-ui,-apple-system,sans-serif;margin:14px 0;padding:12px 16px;border:1.5px dashed #ccc;border-radius:8px;background:#fafafa;font-size:13px;color:#444"><span style="font-size:16px">📎</span> <em>Extrato mensal de M² Impressos (justificativo do recálculo) em <strong>anexo</strong>.</em></div><div style="font-family:system-ui,-apple-system,sans-serif;font-size:12.5px;color:#555;margin-bottom:10px;font-style:italic">Nota: Caso regularize os m² em atraso, o valor do contrato volta ao valor previamente acordado.</div>`:'';
    const fixedBlocks=blocks.replaceAll('var(--grey-50)','#f5f6f8').replaceAll('var(--grey-50,#f5f6f8)','#f5f6f8').replaceAll('var(--border-1)','#e0e0e0').replaceAll('var(--font-body,var(--font-sans,system-ui,-apple-system,sans-serif))','system-ui,-apple-system,sans-serif').replaceAll('var(--font-body,var(--font-sans,system-ui,sans-serif))','system-ui,sans-serif');
    const emailCSS=`body{margin:0;padding:20px;background:#f0f0f0;font-family:system-ui,-apple-system,sans-serif}.email-frame{max-width:640px;margin:0 auto;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden;background:#fff}.email-header-band{background:#112954;padding:24px 32px;color:#fff}.email-header-band .co-name{font-size:11px;font-weight:700;color:rgba(255,255,255,.5);letter-spacing:.1em;text-transform:uppercase;margin-bottom:6px}.email-header-band h2{font-size:18px;font-weight:700;color:#fff;margin-bottom:4px;margin-top:0}.email-header-band .subtitle{color:rgba(255,255,255,.6);font-size:12px}.email-body{padding:28px 32px}.email-salutation{font-size:14px;margin-bottom:12px}.email-intro{font-size:13px;line-height:1.65;margin-bottom:20px;color:#444}.email-contract-block{border:1px solid #e0e0e0;border-radius:8px;overflow:hidden;margin-bottom:16px}.ecb-header{padding:12px 16px;background:#f5f5f5;border-bottom:1px solid #e0e0e0}.ecb-title{font-size:13px;font-weight:700;color:#1a1a2e}.ecb-dates{font-size:11px;color:#888}.ecb-body{padding:16px}.ecb-grid{margin-bottom:12px}.ecb-stat{padding:10px 12px;border-radius:6px;background:#f9f9f9;border:1px solid #efefef;margin-bottom:8px}.ecb-stat.bad{background:#fff5f5;border-color:#ffd0d0}.ecb-stat.good{background:#f0fff8;border-color:#c3e6d4}.es-label{font-size:10.5px;font-weight:700;text-transform:uppercase;color:#888;letter-spacing:.04em;margin-bottom:4px}.es-value{font-size:14px;font-weight:700;color:#1a1a2e}.es-sub{font-size:11px;color:#666;margin-top:2px}.ecb-progress{margin-bottom:12px}.ep-label{font-size:11px;color:#888;margin-bottom:4px}.ep-bar{height:8px;background:#eee;border-radius:4px;overflow:hidden}.ep-fill{height:8px;border-radius:4px}.ecb-alert{padding:12px;border-radius:6px;font-size:12.5px;margin-top:12px}.ecb-alert.crit{background:#fff0f0;border:1px solid #ffcccc;color:#7a2020}.ecb-focus-grid{margin-top:12px}.ecb-focus-card{background:#fff;border:1px solid #e0e0e0;border-radius:6px;padding:10px 14px;margin-bottom:8px}.ecb-focus-label{font-size:10.5px;color:#888;text-transform:uppercase;font-weight:700;letter-spacing:.04em;margin-bottom:4px}.ecb-focus-val{font-size:16px;font-weight:700;color:#7a2020}.ecb-focus-sub{font-size:11px;color:#888;margin-top:2px}.email-closing{margin-top:24px;padding-top:16px;border-top:1px solid #eee;font-size:13px;line-height:1.8;color:#444}.email-footer-band{padding:14px 32px;background:#f5f6f8;border-top:1px solid #e0e0e0}.ef-company{font-size:12px;font-weight:700;color:#1a1a2e}.ef-contact{font-size:11px;color:#888}`;
    return`<!DOCTYPE html><html lang="pt"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${heading}</title><style>${emailCSS}</style></head><body><div class="email-frame"><div class="email-header-band"><div class="co-name">Decal PrintPlan</div><h2>${heading}</h2><div class="subtitle">${dateStr}</div></div><div class="email-body"><div class="email-salutation">Exmo(a). Sr./Sra.,</div><div class="email-intro">${intro}</div>${fixedBlocks}${m2Legal}<div style="font-size:11px;margin:14px 0;color:#777;font-style:italic">Caso a situação já se encontre regularizada, por favor, considere esta comunicação sem efeito.</div><div class="email-closing">Com os melhores cumprimentos,<br><strong>${shortV(c.vendedor)}</strong><br>Equipa Decal · PrintPlan<br><span style="font-size:11px;color:#888">printplan@decal.pt</span></div></div><div class="email-footer-band" style="flex-wrap:wrap;row-gap:0"><div><div class="ef-company">Decal · PrintPlan</div><div class="ef-contact">printplan@decal.pt</div></div><div style="font-size:10px;color:#999;text-align:right">Gerado em ${dateStr}.</div><div style="width:100%;margin-top:10px;padding-top:10px;border-top:1px solid #e0e0e0;font-size:10px;color:#aaa;font-style:italic;line-height:1.6">Este é um email automático — por favor não responda directamente a esta mensagem. Caso verifique alguma anomalia, contacte o seu gestor de conta.</div></div></div></body></html>`;
  }
  return`<div class="email-outer" style="font-family:var(--font-body,var(--font-sans,system-ui,-apple-system,sans-serif))"><div class="email-meta"><div><strong>Para:</strong> ${emailTo||'[email do cliente]'}</div><div><strong>De:</strong> ${shortV(c.vendedor)} · Decal PrintPlan</div><div><strong>Assunto:</strong> ${subj}</div><div><strong>Data:</strong> ${dateStr}</div></div><div class="email-frame" id="email-print-zone"><div class="email-header-band"><div class="co-name">Decal PrintPlan</div><h2>${heading}</h2><div class="subtitle">${dateStr}</div></div><div class="email-body"><div class="email-salutation">Exmo(a). Sr./Sra.,</div><div class="email-intro">${intro}</div>${blocks}${activeTopic==='m2'?`<div style="font-family:var(--font-body,var(--font-sans,system-ui,sans-serif));margin:16px 0 0;padding:14px 16px;background:#f8f8f8;border:1px solid #ddd;border-radius:6px;font-size:12.5px;color:#444;line-height:1.65">Nos termos da Cláusula Quinta, alínea 5) do contrato PrintPlan assinado entre o Cliente e a DIGIDELTA:<br><em>"5. O PRIMEIRO CONTRAENTE e o SEGUNDO CONTRAENTE acordam que, se a média dos m² impressos nos últimos 6 (seis) meses for inferior à contratada, seja recalculado, pela mesma fórmula de cálculo inicial o preço do m², face a essa média apurada."</em></div><div style="font-family:var(--font-body,var(--font-sans,system-ui,sans-serif));margin:14px 0;padding:12px 16px;border:1.5px dashed #ccc;border-radius:8px;background:#fafafa;display:flex;align-items:center;justify-content:space-between;gap:10px"><div style="display:flex;align-items:center;gap:10px;font-size:13px;color:#444;"><span style="font-size:16px;flex-shrink:0">📎</span><em>Extrato mensal de M² Impressos (justificativo do recálculo) em <strong>anexo</strong>.</em></div><button onclick="window._ppDownloadExtrato&&window._ppDownloadExtrato()" style="flex-shrink:0;padding:7px 14px;background:#fff;border:1px solid #ccc;border-radius:6px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:var(--font-body,var(--font-sans,system-ui,sans-serif))">Descarregar PDF</button></div><div style="font-family:var(--font-body,var(--font-sans,system-ui,sans-serif));font-size:12.5px;color:#555;margin-bottom:10px;font-style:italic">Nota: Caso regularize os m² em atraso, o valor do contrato volta ao valor previamente acordado.</div>`:''}<div style="font-size:11px;margin:14px 0;color:#777;font-style:italic">Caso a situação já se encontre regularizada, por favor, considere esta comunicação sem efeito.</div><div class="email-closing">Com os melhores cumprimentos,<br><strong>${shortV(c.vendedor)}</strong><br>Equipa Decal · PrintPlan<br><span style="font-size:11px;color:var(--fg-3)">printplan@decal.pt</span></div></div><div class="email-footer-band" style="flex-wrap:wrap;row-gap:0"><div><div class="ef-company">Decal · PrintPlan</div><div class="ef-contact">printplan@decal.pt</div></div><div style="font-size:10px;color:var(--fg-3);text-align:right">Gerado em ${dateStr}.</div><div style="width:100%;margin-top:10px;padding-top:10px;border-top:1px solid #e0e0e0;font-size:10px;color:#aaa;font-style:italic;line-height:1.6">Este é um email automático — por favor não responda directamente a esta mensagem. Caso verifique alguma anomalia, contacte o seu gestor de conta.</div></div></div></div>`;
}

/* ===== anFinanceiraHTML ===== */
function anFinanceiraHTML(cliente){
  const lines=FINANCEIRA_LINES[cliente]||[];
  if(!lines.length)return'<div class="an-empty-note">Sem dados de faturação para este cliente.</div>';
  const years=[...new Set(lines.map(l=>l[0].slice(0,4)))].sort().reverse();
  const renderYear=(year)=>{
    const all=year==='todos'?lines:lines.filter(l=>l[0].startsWith(year));
    if(!all.length)return'<div class="an-empty-note">Sem linhas neste período.</div>';
    const tVL=all.reduce((s,l)=>s+l[3],0),tCusto=all.reduce((s,l)=>s+l[4],0);
    const marg=tVL>0?(tVL-tCusto)/tVL*100:null;
    const byM={};all.forEach(l=>{if(!byM[l[0]])byM[l[0]]={vl:0,c:0};byM[l[0]].vl+=l[3];byM[l[0]].c+=l[4];});
    const months=Object.keys(byM).sort().reverse();
    const rows=months.map(m=>{const{vl,c}=byM[m];const mp=vl>0?(vl-c)/vl*100:null;return`<tr><td>${m}</td><td class="num">${fmtEFull(vl)}</td><td class="num" style="color:var(--fg-3)">${fmtEFull(c)}</td><td class="num" style="color:${finMargemColor(mp)};font-weight:700">${finPctTxt(mp)}</td></tr>`;}).join('');
    return`<div class="an-avg-card" style="margin-bottom:16px"><div class="an-avg-label">${year==='todos'?'Total histórico':'Total '+year}</div><div class="an-avg-body"><span class="an-avg-value">${fmtEFull(tVL)}</span><span class="an-avg-vs">custo ${fmtEFull(tCusto)}</span><span class="an-avg-pct" style="color:${finMargemColor(marg)}">${finPctTxt(marg)}</span></div></div><div class="tbl-wrap" style="box-shadow:none;border:1px solid var(--border-1)"><table><thead><tr><th>Mês</th><th class="num">Valor Líquido</th><th class="num">Custo</th><th class="num">Margem %</th></tr></thead><tbody>${rows}</tbody></table></div>`;
  };
  return`<div class="an-fin-note">Valores refletem a faturação de todo o cliente (todos os contratos).</div><div style="margin-bottom:12px"><label>Ano: <select onchange="document.getElementById('fin-year-body').innerHTML=anFinanceiraHTMLYear('${cliente.replace(/'/g,"\\'")}',this.value)"><option value="todos">Todos</option>${years.map(y=>`<option value="${y}">${y}</option>`).join('')}</select></label></div><div id="fin-year-body">${renderYear('todos')}</div>`;
}
function anFinanceiraHTMLYear(cliente,year){
  const lines=FINANCEIRA_LINES[cliente]||[];
  const all=year==='todos'?lines:lines.filter(l=>l[0].startsWith(year));
  if(!all.length)return'<div class="an-empty-note">Sem linhas neste período.</div>';
  const tVL=all.reduce((s,l)=>s+l[3],0),tCusto=all.reduce((s,l)=>s+l[4],0);
  const marg=tVL>0?(tVL-tCusto)/tVL*100:null;
  const byM={};all.forEach(l=>{if(!byM[l[0]])byM[l[0]]={vl:0,c:0};byM[l[0]].vl+=l[3];byM[l[0]].c+=l[4];});
  const months=Object.keys(byM).sort().reverse();
  const rows=months.map(m=>{const{vl,c}=byM[m];const mp=vl>0?(vl-c)/vl*100:null;return`<tr><td>${m}</td><td class="num">${fmtEFull(vl)}</td><td class="num" style="color:var(--fg-3)">${fmtEFull(c)}</td><td class="num" style="color:${finMargemColor(mp)};font-weight:700">${finPctTxt(mp)}</td></tr>`;}).join('');
  return`<div class="an-avg-card" style="margin-bottom:16px"><div class="an-avg-label">${year==='todos'?'Total histórico':'Total '+year}</div><div class="an-avg-body"><span class="an-avg-value">${fmtEFull(tVL)}</span><span class="an-avg-vs">custo ${fmtEFull(tCusto)}</span><span class="an-avg-pct" style="color:${finMargemColor(marg)}">${finPctTxt(marg)}</span></div></div><div class="tbl-wrap" style="box-shadow:none;border:1px solid var(--border-1)"><table><thead><tr><th>Mês</th><th class="num">Valor Líquido</th><th class="num">Custo</th><th class="num">Margem %</th></tr></thead><tbody>${rows}</tbody></table></div>`;
}

/* ===== InventarioTab ===== */
const InventarioTab = ({row}) => {
  const [openFamilias, setOpenFamilias] = React.useState({});
  if(!row.armazem||!INVENTARIO_DATA[row.armazem]) return <div className="an-empty-note">Sem dados de inventário para este contrato.</div>;
  const armazem=row.armazem;
  const items=INVENTARIO_DATA[armazem]||[];
  const descArm=INVENTARIO_ARMAZEM_DESC[armazem]||'';
  const meses=Math.max(1,row.rendas_decorridas||1);
  const consumoMap=INVENTARIO_CONSUMO[armazem]||{};
  // Group by familia
  const famMap={};
  for(const it of items){const f=it.familia||'Outros';if(!famMap[f])famMap[f]=[];famMap[f].push(it);}
  const today=new Date().toISOString().slice(0,10);
  const in90=new Date(Date.now()+90*24*60*60*1000).toISOString().slice(0,10);
  const valR=v=>v&&v<=in90;
  const famList=Object.keys(famMap).map(fam=>{
    const famItems=famMap[fam].slice().sort((a,b)=>(b.valor||0)-(a.valor||0));
    const totalStock=famItems.reduce((s,i)=>s+(i.stock||0),0);
    const totalValor=famItems.reduce((s,i)=>s+(i.valor||0),0);
    let cQtdF=0,cValorF=0;
    for(const it of famItems){const c=consumoMap[it.artigo];if(c){cQtdF+=c.qtd||0;cValorF+=c.valor||0;}}
    let alertCount=0;
    for(const it of famItems){const c=consumoMap[it.artigo];const mq=c?c.qtd/meses:0;const dias=mq>0?it.stock/mq*30:(it.stock>0?Infinity:0);if(it.stock>0&&dias>60)alertCount++;}
    const allVal=famItems.filter(i=>i.validade).map(i=>i.validade).sort();
    return{fam,artigoList:famItems,totalStock,totalValor,consumoQtd:cQtdF,consumoValor:cValorF,mediaQtd:cQtdF/meses,mediaValor:cValorF/meses,validadeMin:allVal[0]||null,alertCount,nArtigos:famItems.length};
  }).sort((a,b)=>b.totalValor-a.totalValor);
  const totalValorG=famList.reduce((s,f)=>s+f.totalValor,0);
  const totalStockG=famList.reduce((s,f)=>s+f.totalStock,0);
  const totalAlerts=famList.reduce((s,f)=>s+f.alertCount,0);
  const totalArtigos=items.length;
  const toggle=fam=>setOpenFamilias(prev=>({...prev,[fam]:!prev[fam]}));
  const cols='minmax(160px,1fr) 60px 115px 155px 135px 105px';
  const thS={textAlign:'right',padding:'8px 12px',fontSize:10,fontWeight:700,textTransform:'uppercase',letterSpacing:'.06em',color:'var(--fg-3)'};
  const hdS={textAlign:'left',...thS};
  return(<div>
    <div style={{background:'var(--bg-app)',border:'1px solid var(--border-1)',borderRadius:'var(--radius-sm,8px)',padding:'12px 16px',marginBottom:10}}>
      <div style={{fontSize:10,fontWeight:700,textTransform:'uppercase',letterSpacing:'.06em',color:'var(--fg-3)',marginBottom:4}}>VALOR EM STOCK — ARMAZÉM {armazem}{descArm?` · ${descArm}`:''}</div>
      <div style={{display:'flex',alignItems:'baseline',gap:14,flexWrap:'wrap'}}>
        <span style={{fontSize:22,fontWeight:700,color:'var(--fg-1)'}}>{fmtEFull(totalValorG)}</span>
        <span style={{fontSize:12,color:'var(--fg-3)'}}>{totalArtigos} artigos em {famList.length} famílias · {fmt(totalStockG,0)} unidades</span>
        {totalAlerts>0&&<span style={{fontSize:12,color:'var(--red-700)',fontWeight:600}}>⚠ {totalAlerts} artigos com +60 dias de stock</span>}
      </div>
    </div>
    <div style={{fontSize:12,color:'var(--fg-3)',lineHeight:1.55,marginBottom:10,padding:'10px 14px',background:'var(--bg-app)',border:'1px solid var(--border-1)',borderRadius:'var(--radius-sm,8px)'}}>
      Consumo Total e Média Mensal calculados a partir do histórico de movimentos de saída de armazém (desde dez. 2016). Média Mensal = Consumo Total ÷ {meses} meses decorridos do contrato. A coluna Stock fica a vermelho quando o stock actual representa mais de 60 dias de cobertura ao ritmo de consumo médio mensal (ou quando não há consumo registado). Artigos sem histórico de saídas mostram "sem histórico".
    </div>
    <div style={{border:'1px solid var(--border-1)',borderRadius:'var(--radius-sm,8px)',overflow:'hidden'}}>
      <div style={{display:'grid',gridTemplateColumns:cols,background:'var(--bg-app)',borderBottom:'1px solid var(--border-1)'}}>
        <div style={hdS}>ARTIGO / FAMÍLIA</div>
        <div style={thS}>STOCK</div>
        <div style={thS}>VALOR</div>
        <div style={thS}>CONSUMO TOTAL</div>
        <div style={thS}>MÉDIA MENSAL</div>
        <div style={thS}>VALIDADE</div>
      </div>
      {famList.map(({fam,artigoList,totalStock,totalValor,consumoQtd,consumoValor,mediaQtd,mediaValor,validadeMin,alertCount,nArtigos})=>{
        const isOpen=openFamilias[fam];
        return(<React.Fragment key={fam}>
          <div onClick={()=>toggle(fam)} style={{display:'grid',gridTemplateColumns:cols,padding:'12px 12px',borderBottom:'1px solid var(--border-1)',cursor:'pointer',alignItems:'center',background:isOpen?'color-mix(in oklch,var(--border-1) 25%,transparent)':'transparent',transition:'background .15s'}}>
            <div style={{display:'flex',alignItems:'center',gap:8}}>
              <span style={{fontSize:11,color:'var(--fg-2)',display:'inline-block',transform:isOpen?'rotate(90deg)':'none',transition:'transform .2s',flexShrink:0}}>▶</span>
              <span style={{fontWeight:600,fontSize:13}}>{fam}</span>
              <span style={{fontSize:9.5,fontWeight:700,padding:'2px 7px',borderRadius:20,background:'var(--dd-primary-900,#3859D0)',color:'#fff',flexShrink:0}}>{nArtigos} artigos</span>
              {alertCount>0&&<span style={{fontSize:10.5,fontWeight:700,color:'var(--red-700)',flexShrink:0}}>⚠{alertCount}</span>}
            </div>
            <div style={{textAlign:'right',fontWeight:600}}>{fmt(totalStock,0)}</div>
            <div style={{textAlign:'right',fontWeight:600}}>{fmtEFull(totalValor)}</div>
            <div style={{textAlign:'right'}}><div style={{fontWeight:600}}>{fmt(consumoQtd,0)} un.</div><div style={{fontSize:11,color:'var(--fg-3)'}}>{fmtEFull(consumoValor)}</div></div>
            <div style={{textAlign:'right'}}><div style={{fontWeight:600}}>{fmt(mediaQtd,1)} un.</div><div style={{fontSize:11,color:'var(--fg-3)'}}>{fmtEFull(mediaValor)}</div></div>
            <div style={{textAlign:'right',fontFamily:'var(--font-mono,monospace)',fontSize:11.5,color:valR(validadeMin)?'var(--red-700)':'inherit',fontWeight:valR(validadeMin)?700:'normal'}}>{validadeMin||'—'}</div>
          </div>
          {isOpen&&artigoList.map((a,ai)=>{
            const c=consumoMap[a.artigo];const cQ=c?c.qtd:0;const cV=c?c.valor:0;
            const mq=cQ/meses;const dias=mq>0?a.stock/mq*30:(a.stock>0?Infinity:0);
            const excesso=a.stock>0&&dias>60;
            const aVal=a.validade||null;
            return(<div key={ai+'-'+a.artigo} style={{display:'grid',gridTemplateColumns:cols,padding:'8px 12px 8px 38px',borderBottom:'1px solid var(--border-1)',alignItems:'center',background:'color-mix(in oklch,var(--bg-app) 70%,transparent)',fontSize:12}}>
              <div><div style={{fontFamily:'var(--font-mono,monospace)',fontSize:10.5,color:'var(--fg-3)'}}>{a.artigo}</div><div style={{fontSize:12,color:'var(--fg-1)',marginTop:1}}>{a.descricao||'—'}</div></div>
              <div style={{textAlign:'right',color:excesso?'var(--red-700)':'inherit',fontWeight:excesso?700:'normal'}}>{fmt(a.stock,a.stock%1!==0?1:0)}</div>
              <div style={{textAlign:'right',fontWeight:600}}>{fmtEFull(a.valor)}</div>
              <div style={{textAlign:'right'}}>{cQ>0?<><div>{fmt(cQ,0)} un.</div><div style={{fontSize:11,color:'var(--fg-3)'}}>{fmtEFull(cV)}</div></>:<span style={{color:'var(--fg-3)'}}>sem histórico</span>}</div>
              <div style={{textAlign:'right'}}>{mq>0?<><div>{fmt(mq,1)} un.</div><div style={{fontSize:11,color:'var(--fg-3)'}}>{fmtEFull(cV/meses)}</div></>:<span style={{color:'var(--fg-3)'}}>—</span>}</div>
              <div style={{textAlign:'right',fontFamily:'var(--font-mono,monospace)',fontSize:11,color:valR(aVal)?'var(--red-700)':'inherit',fontWeight:valR(aVal)?700:'normal'}}>{aVal||'—'}</div>
            </div>);
          })}
        </React.Fragment>);
      })}
    </div>
  </div>);
};

/* ===== AnalysisModal ===== */
const AnalysisModal = ({row, onClose}) => {
  const [sec, setSec] = React.useState('info');
  const secs = [
    {k:'info',l:'Informações Gerais'},
    {k:'m2',l:'M² Impressos'},
    {k:'tinta',l:'Controlo de Tintas'},
    {k:'suporte',l:'Controlo de Suportes'},
    {k:'financeira',l:'Análise Financeira'},
    {k:'inventario',l:'Inventário'},
  ];

  const renderSec = () => {
    if(sec==='info'){
      const cc={'Care':'b-care','Care Plus':'b-careplus','No Care':'b-nocare','—':'b-neutral'};
      const item=(l,v)=><div className="an-info-item"><div className="an-info-label">{l}</div><div className="an-info-value">{v}</div></div>;
      const section=(t,items,cols=2)=><div className="an-info-section"><div className="an-info-section-title">{t}</div><div className="an-info-grid" style={{gridTemplateColumns:`repeat(${cols},1fr)`}}>{items}</div></div>;
      return(<div>{section('Identificação',[item('Contrato Nº',row.contrato),item('Cliente',row.cliente),item('Nº Cliente',row.num_cliente||'—'),item('Vendedor',shortV(row.vendedor)),item('Armazém',row.armazem||'—'),item('Código Artigo',row.cod_artigo||'—')],6)}{section('Equipamento & Tintas',[item('Equip',row.num_equip||1),item('Modelo do Equipamento',row.equip_modelo||'—'),item('Nº de Série',row.numero_serie||'—'),item('Tintas Instaladas',row.tintas_flag?<span className="badge b-acima">Incluídas</span>:<span className="badge b-abaixo">Não incluídas</span>),item('Limite Máximo',row.tinta_ml_m2_esp?fmt(row.tinta_ml_m2_esp,1)+' ml/m²':'—')],5)}{section('Suportes & Assistência',[item('Suportes de Impressão',row.sup_imp===1?<span className="badge b-acima">Incluído</span>:<span className="badge b-abaixo">Excluído</span>),item('Assistência Técnica',<span className={`badge ${cc[row.care_type]||'b-neutral'}`}>{row.care_type}</span>)],2)}{section('Condições Comerciais',[item('M² Mensais',fmt(row.m2_mensal)),item('Prazo',`${row.rendas_decorridas}/${row.num_rendas} meses`),item('Total M² Contratados',fmt(row.limite_m2)),item('Preço M² Contratado',typeof row.pvp==='number'?'€'+fmt(row.pvp,2):row.pvp),item('Data Início',row.data_inicio||'—'),item('Data Término',row.data_termino||'—')],6)}</div>);
    }
    if(sec==='m2'){
      const monthly=(MONTHLY_REVENUE[row.contrato]||[]).slice().sort((a,b)=>a[0]<b[0]?1:-1);
      const chronological=(MONTHLY_REVENUE[row.contrato]||[]).slice().sort((a,b)=>a[0]>b[0]?1:-1);
      const esp=row.m2_mensal;
      const n=Math.max(1,row.rendas_decorridas||1);
      const avgReal=row.acumulado_m2/n;
      const statusColor=row.m2_status==='ACIMA'?'var(--green-700)':'var(--red-700)';
      const pctSign=row.m2_desvio_pct>=0?'+':'';
      const cW=820,cH=180,pL=60,pR=20,pT=16,pB=36;
      const plotW=cW-pL-pR,plotH=cH-pT-pB;
      const vals=chronological.map(d=>d[1]||0);
      const yMax=Math.max(...vals,esp)*1.18||1;
      const xS=i=>pL+i/Math.max(chronological.length-1,1)*plotW;
      const yS=v=>pT+plotH*(1-v/yMax);
      const pts=chronological.map((d,i)=>({x:xS(i),y:yS(d[1]||0),v:d[1]||0,mes:d[0],above:(d[1]||0)>=esp}));
      const polyPts=pts.map(p=>`${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');
      const espY=yS(esp);
      const step=Math.ceil(chronological.length/7)||1;
      const xTicks=chronological.reduce((acc,d,i)=>{if(i%step===0||i===chronological.length-1)acc.push({x:xS(i),label:d[0].slice(2)});return acc;},[]);
      const yMid=yMax*0.5;
      const mesesFora=vals.filter(v=>Math.abs(v-esp)/(esp||1)>0.05).length;
      // Últimos 6 meses de calendário (meses sem leitura = 0 m²)
      const last6=alertsM2UltimosMesesCalendario(row,6);
      const last6avg=last6.reduce((s,d)=>s+(d[1]||0),0)/last6.length;
      const last6desvio=esp>0?((last6avg-esp)/esp*100):0;
      const last6sign=last6.reduce((s,d)=>s+((d[1]||0)>=esp?1:-1),0);
      const trendText=last6sign>0?'em recuperação':last6sign<0?'com desvio acentuado':'estável';
      const last6mesesFora=last6.filter(d=>Math.abs((d[1]||0)-esp)/(esp||1)>0.05).length;
      return(<div style={{display:'flex',flexDirection:'column',gap:14}}>
        <div style={{background:'var(--bg-app)',border:'1px solid var(--border-1)',borderRadius:8,padding:'10px 16px',display:'flex',alignItems:'baseline',gap:10,flexWrap:'wrap'}}>
          <span style={{fontSize:10,fontWeight:700,color:'var(--fg-3)',textTransform:'uppercase',letterSpacing:'.06em'}}>Média Mensal ({n} {n===1?'mês':'meses'})</span>
          <span style={{fontSize:22,fontWeight:700,color:'var(--fg-1)',fontFamily:'var(--font-sans)',marginLeft:8}}>{fmt(avgReal,1)}</span>
          <span style={{fontSize:12,color:'var(--fg-3)'}}>de {fmt(esp,1)} esperado</span>
          <span style={{fontSize:13,fontWeight:700,color:statusColor}}>{pctSign}{fmt(row.m2_desvio_pct,1)}%</span>
        </div>
        {chronological.length>1&&<div style={{background:'var(--bg-surface)',border:'1px solid var(--border-1)',borderRadius:8,overflow:'hidden'}}>
          <div style={{padding:'10px 14px 0',display:'flex',gap:18,alignItems:'center'}}>
            <span style={{display:'flex',alignItems:'center',gap:5,fontSize:11,color:'var(--fg-2)'}}><svg width="20" height="2"><line x1="0" y1="1" x2="20" y2="1" stroke="#3859D0" strokeWidth="2"/></svg>M² Real</span>
            <span style={{display:'flex',alignItems:'center',gap:5,fontSize:11,color:'var(--fg-2)'}}><svg width="22" height="2"><line x1="0" y1="1" x2="22" y2="1" stroke="#94A4C4" strokeWidth="1.5" strokeDasharray="4 3"/></svg>M² Esperado</span>
          </div>
          <svg viewBox={`0 0 ${cW} ${cH}`} style={{width:'100%',display:'block'}}>
            <line x1={pL} y1={yS(yMid)} x2={cW-pR} y2={yS(yMid)} stroke="var(--border-1)" strokeWidth="1"/>
            <line x1={pL} y1={yS(yMax*0.9)} x2={cW-pR} y2={yS(yMax*0.9)} stroke="var(--border-1)" strokeWidth="1"/>
            <text x={pL-6} y={yS(yMid)+4} textAnchor="end" fontSize="10" fill="#94A4C4">{fmt(yMid,0)}</text>
            <text x={pL-6} y={yS(yMax*0.9)+4} textAnchor="end" fontSize="10" fill="#94A4C4">{fmt(yMax*0.9,0)}</text>
            <line x1={pL} y1={espY} x2={cW-pR} y2={espY} stroke="#94A4C4" strokeWidth="1.5" strokeDasharray="5 4"/>
            <polyline points={polyPts} fill="none" stroke="#3859D0" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round"/>
            {pts.map((p,i)=><circle key={i} cx={p.x} cy={p.y} r="4" fill={p.above?'#1F8A52':'#B53B3B'} stroke="#fff" strokeWidth="1.5"><title>{p.mes}: {fmt(p.v)} m²</title></circle>)}
            {xTicks.map((t,i)=><text key={i} x={t.x} y={cH-6} textAnchor="middle" fontSize="10" fill="#94A4C4">{t.label}</text>)}
          </svg>
        </div>}
        <div style={{border:'1px solid var(--border-1)',borderRadius:8,padding:'12px 16px'}}>
          <div style={{fontSize:10,fontWeight:700,color:'var(--fg-3)',textTransform:'uppercase',letterSpacing:'.06em',marginBottom:8}}>Comentário do Analista</div>
          <p style={{fontSize:13,lineHeight:1.65,color:'var(--fg-1)',margin:0}}>
            Nos últimos {Math.min(6,n)} meses, o desempenho está <strong>{trendText}</strong> (desvio médio {last6desvio>=0?'+':''}{fmt(last6desvio,1)}%) — em linha com a média histórica do contrato ({pctSign}{fmt(row.m2_desvio_pct,1)}%). Ao longo do histórico registaram-se <strong>{mesesFora} {mesesFora===1?'mês':'meses'} fora da margem de tolerância</strong> (±5%).{row.m2_desvio<0?<> O contrato acumula um défice de <strong>{fmt(Math.abs(row.m2_desvio))} m²</strong> face ao esperado.</>:<> O contrato está acima do esperado em <strong>{fmt(row.m2_desvio)} m²</strong>.</>}
          </p>
        </div>
        {row.novo_pvp!=null&&(()=>{const reajAvg=last6avg;return(<div style={{border:'1px solid color-mix(in oklch,#E96D17 40%,transparent)',background:'color-mix(in oklch,#E96D17 6%,transparent)',borderRadius:8,padding:'12px 16px'}}>
          <div style={{fontSize:10,fontWeight:700,color:'#C05010',textTransform:'uppercase',letterSpacing:'.06em',marginBottom:8}}>Reajuste de Contrato Recomendado</div>
          <p style={{fontSize:12.5,lineHeight:1.65,color:'var(--fg-1)',margin:'0 0 10px'}}>
            Nos últimos 6 meses, a média de M² impressos ({fmt(reajAvg,0)} m²/mês) ficou abaixo do contratualizado ({fmt(esp,0)} m²/mês) — défice de <strong>{fmt(esp-reajAvg,0)} m²/mês</strong>. O valor deve ser reajustado:
          </p>
          <ul style={{margin:'0 0 0 18px',padding:0,fontSize:12.5,lineHeight:1.9,color:'var(--fg-1)'}}>
            <li><strong>Novo PVP recalculado:</strong> {fmt(row.novo_pvp,2)} €/m² (atual {fmt(row.pvp,2)} €/m²) — a aplicar aos {fmt(row.m2_restante,0)} m² restantes para recuperar o défice de {fmt(row.valor_em_atraso,2)} €.</li>
            <li><strong>Alternativa:</strong> cobrar ao cliente {fmt(row.valor_em_atraso,2)} €, referentes ao défice acumulado ao contratado, mantendo o PVP atual ({fmt(row.pvp,2)} €/m²).</li>
          </ul>
        </div>);})()}
        {monthly.length>0&&<div style={{border:'1px solid var(--border-1)',borderRadius:8,overflow:'hidden'}}>
          <table style={{width:'100%',borderCollapse:'collapse',fontSize:12.5}}>
            <thead><tr style={{background:'var(--bg-app)'}}>
              {['Mês','M² Real','M² Esperado','Desvio','Desvio %'].map((h,i)=><th key={h} style={{padding:'8px 12px',textAlign:i===0?'left':'right',fontSize:10,fontWeight:700,color:'var(--fg-3)',textTransform:'uppercase',letterSpacing:'.05em',borderBottom:'1px solid var(--border-1)'}}>{h}</th>)}
            </tr></thead>
            <tbody>
              <tr style={{background:'color-mix(in oklch,var(--border-1) 40%,transparent)'}}>
                <td style={{padding:'8px 12px',fontWeight:700,fontSize:13}}>Total / Média</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700}}>{fmt(row.acumulado_m2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',color:'var(--fg-3)'}}>{fmt(row.esperado_m2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:row.m2_desvio>=0?'var(--green-700)':'var(--red-700)'}}>{(row.m2_desvio>=0?'+':'')+fmt(row.m2_desvio)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:row.m2_desvio>=0?'var(--green-700)':'var(--red-700)'}}>{pctSign}{fmt(row.m2_desvio_pct,1)}%</td>
              </tr>
              {monthly.map((row2,idx)=>{const real=row2[1]||0;const dev=real-esp;const pct=esp>0?(dev/esp*100):0;const col=dev>=0?'var(--green-700)':'var(--red-700)';return(<tr key={row2[0]} style={{borderTop:'1px solid var(--border-1)'}}>
                <td style={{padding:'8px 12px'}}>{row2[0]}{idx===0&&<span style={{marginLeft:6,fontSize:9,fontWeight:700,padding:'2px 7px',borderRadius:10,background:'color-mix(in oklch,#3859D0 15%,transparent)',color:'#3859D0',verticalAlign:'middle'}}>MAIS RECENTE</span>}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:600}}>{fmt(real)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',color:'var(--fg-3)'}}>{fmt(esp)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:col}}>{(dev>=0?'+':'')+fmt(dev)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',color:col}}>{(pct>=0?'+':'')+fmt(pct,1)}%</td>
              </tr>);})}
            </tbody>
          </table>
        </div>}
      </div>);
    }
    if(sec==='tinta'){
      if(!row.tinta_tem_dados) return <div className="an-empty-note">Sem dados de consumo de tintas para este contrato.</div>;
      const esp=row.tinta_ml_m2_esp||12;
      const n=Math.max(1,row.rendas_decorridas||1);
      const pctSign=row.tinta_desvio_pct>=0?'+':'';
      const statusColor=row.tinta_desvio_pct<0?'var(--green-700)':row.tinta_desvio_pct<=15?'var(--yellow-700)':'var(--red-700)';
      /* monthly ml/m²: index3=tinta_ml_total, index1=m2 */
      const rawMonthly=(MONTHLY_REVENUE[row.contrato]||[]).filter(d=>d[1]>0&&d[3]!=null&&d[3]>0).map(d=>([d[0],Math.round(d[3]/d[1]*100)/100]));
      const chronological=rawMonthly.slice().sort((a,b)=>a[0]>b[0]?1:-1);
      const monthly=rawMonthly.slice().sort((a,b)=>a[0]<b[0]?1:-1);
      const hasMontly=chronological.length>1;
      const avgReal=hasMontly?chronological.reduce((s,d)=>s+d[1],0)/chronological.length:row.tinta_ml_m2_real;
      /* SVG chart */
      const cW=820,cH=180,pL=60,pR=20,pT=16,pB=36;
      const plotW=cW-pL-pR,plotH=cH-pT-pB;
      const vals=chronological.map(d=>d[1]);
      const yMax=Math.max(...vals,esp)*1.18||1;
      const xS=i=>pL+i/Math.max(chronological.length-1,1)*plotW;
      const yS=v=>pT+plotH*(1-v/yMax);
      const tintaPct=v=>esp>0?(v-esp)/esp*100:0;
      const tintaColor=pct=>pct<0?'#1F8A52':pct<=15?'#C99A2E':'#B53B3B';
      const pts=chronological.map((d,i)=>({x:xS(i),y:yS(d[1]),v:d[1],mes:d[0],pct:tintaPct(d[1])}));
      const polyPts=pts.map(p=>`${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');
      const espY=yS(esp);
      const step=Math.ceil(chronological.length/7)||1;
      const xTicks=chronological.reduce((acc,d,i)=>{if(i%step===0||i===chronological.length-1)acc.push({x:xS(i),label:d[0].slice(2)});return acc;},[]);
      const yMid=yMax*0.5;
      /* analyst */
      const last6=chronological.slice(-6);
      const last6avg=last6.length?last6.reduce((s,d)=>s+d[1],0)/last6.length:avgReal;
      const last6desvio=esp>0?((last6avg-esp)/esp*100):0;
      const mesesFora=vals.filter(v=>(v-esp)/esp>0.15).length;
      const trendText=last6desvio>15?'acima do limite de tolerância':last6desvio>10?'próximo do limite de tolerância':last6desvio<0?'abaixo do esperado':'dentro da margem de tolerância';
      return(<div style={{display:'flex',flexDirection:'column',gap:14}}>
        {/* Summary */}
        <div style={{background:'var(--bg-app)',border:'1px solid var(--border-1)',borderRadius:8,padding:'10px 16px',display:'flex',alignItems:'baseline',gap:10,flexWrap:'wrap'}}>
          <span style={{fontSize:10,fontWeight:700,color:'var(--fg-3)',textTransform:'uppercase',letterSpacing:'.06em'}}>Média ml/m² ({n} {n===1?'mês':'meses'})</span>
          <span style={{fontSize:22,fontWeight:700,color:'var(--fg-1)',fontFamily:'var(--font-sans)',marginLeft:8}}>{fmt(avgReal,2)} ml/m²</span>
          <span style={{fontSize:12,color:'var(--fg-3)'}}>de {fmt(esp,1)} ml/m² esperado</span>
          <span style={{fontSize:13,fontWeight:700,color:statusColor}}>{pctSign}{fmt(row.tinta_desvio_pct,1)}%</span>
        </div>
        {/* Chart */}
        {hasMontly&&<div style={{background:'var(--bg-surface)',border:'1px solid var(--border-1)',borderRadius:8,overflow:'hidden'}}>
          <div style={{padding:'10px 14px 0',display:'flex',gap:18,alignItems:'center'}}>
            <span style={{display:'flex',alignItems:'center',gap:5,fontSize:11,color:'var(--fg-2)'}}><svg width="20" height="2"><line x1="0" y1="1" x2="20" y2="1" stroke="#3859D0" strokeWidth="2"/></svg>Tinta Real (ml/m²)</span>
            <span style={{display:'flex',alignItems:'center',gap:5,fontSize:11,color:'var(--fg-2)'}}><svg width="22" height="2"><line x1="0" y1="1" x2="22" y2="1" stroke="#94A4C4" strokeWidth="1.5" strokeDasharray="4 3"/></svg>Tinta Esperada (ml/m²)</span>
          </div>
          <svg viewBox={`0 0 ${cW} ${cH}`} style={{width:'100%',display:'block'}}>
            <line x1={pL} y1={yS(yMid)} x2={cW-pR} y2={yS(yMid)} stroke="var(--border-1)" strokeWidth="1"/>
            <line x1={pL} y1={yS(yMax*0.9)} x2={cW-pR} y2={yS(yMax*0.9)} stroke="var(--border-1)" strokeWidth="1"/>
            <text x={pL-6} y={yS(yMid)+4} textAnchor="end" fontSize="10" fill="#94A4C4">{fmt(yMid,1)}</text>
            <text x={pL-6} y={yS(yMax*0.9)+4} textAnchor="end" fontSize="10" fill="#94A4C4">{fmt(yMax*0.9,1)}</text>
            <line x1={pL} y1={espY} x2={cW-pR} y2={espY} stroke="#94A4C4" strokeWidth="1.5" strokeDasharray="5 4"/>
            <polyline points={polyPts} fill="none" stroke="#3859D0" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round"/>
            {pts.map((p,i)=><circle key={i} cx={p.x} cy={p.y} r="4" fill={tintaColor(p.pct)} stroke="#fff" strokeWidth="1.5"><title>{p.mes}: {fmt(p.v,2)} ml/m²</title></circle>)}
            {xTicks.map((t,i)=><text key={i} x={t.x} y={cH-6} textAnchor="middle" fontSize="10" fill="#94A4C4">{t.label}</text>)}
          </svg>
        </div>}
        {/* Analyst comment */}
        <div style={{border:'1px solid var(--border-1)',borderRadius:8,padding:'12px 16px'}}>
          <div style={{fontSize:10,fontWeight:700,color:'var(--fg-3)',textTransform:'uppercase',letterSpacing:'.06em',marginBottom:8}}>Comentário do Analista</div>
          <p style={{fontSize:13,lineHeight:1.65,color:'var(--fg-1)',margin:0}}>
            Nos últimos {Math.min(6,n)} meses, o desempenho está <strong>{trendText}</strong> (desvio médio {last6desvio>=0?'+':''}{fmt(last6desvio,1)}%) — em linha com a média histórica do contrato ({pctSign}{fmt(row.tinta_desvio_pct,1)}%). Ao longo do histórico registaram-se <strong>{mesesFora} {mesesFora===1?'mês':'meses'} fora da margem de tolerância</strong> (>15%).
          </p>
        </div>
        {/* Table */}
        {monthly.length>0&&<div style={{border:'1px solid var(--border-1)',borderRadius:8,overflow:'hidden'}}>
          <table style={{width:'100%',borderCollapse:'collapse',fontSize:12.5}}>
            <thead><tr style={{background:'var(--bg-app)'}}>
              {['Mês','Tinta Real (ml/m²)','Tinta Esperada (ml/m²)','Desvio','Desvio %'].map((h,i)=><th key={h} style={{padding:'8px 12px',textAlign:i===0?'left':'right',fontSize:10,fontWeight:700,color:'var(--fg-3)',textTransform:'uppercase',letterSpacing:'.05em',borderBottom:'1px solid var(--border-1)'}}>{h}</th>)}
            </tr></thead>
            <tbody>
              <tr style={{background:'color-mix(in oklch,var(--border-1) 40%,transparent)'}}>
                <td style={{padding:'8px 12px',fontWeight:700,fontSize:13}}>Total / Média</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700}}>{fmt(avgReal,2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',color:'var(--fg-3)'}}>{fmt(esp,1)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:statusColor}}>{(row.tinta_desvio_pct>=0?'+':'')+fmt(avgReal-esp,2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:statusColor}}>{pctSign}{fmt(row.tinta_desvio_pct,1)}%</td>
              </tr>
              {monthly.map((row2,idx)=>{const dev=row2[1]-esp;const pct=esp>0?(dev/esp*100):0;const col=pct<0?'var(--green-700)':pct<=15?'var(--yellow-700)':'var(--red-700)';return(<tr key={row2[0]} style={{borderTop:'1px solid var(--border-1)'}}>
                <td style={{padding:'8px 12px'}}>{row2[0]}{idx===0&&<span style={{marginLeft:6,fontSize:9,fontWeight:700,padding:'2px 7px',borderRadius:10,background:'color-mix(in oklch,#3859D0 15%,transparent)',color:'#3859D0',verticalAlign:'middle'}}>MAIS RECENTE</span>}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:600}}>{fmt(row2[1],2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',color:'var(--fg-3)'}}>{fmt(esp,1)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:col}}>{(dev>=0?'+':'')+fmt(dev,2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',color:col}}>{(pct>=0?'+':'')+fmt(pct,1)}%</td>
              </tr>);})}
            </tbody>
          </table>
        </div>}
      </div>);
    }
    if(sec==='suporte'){
      if(!row.suporte_tem_dados) return <div className="an-empty-note">Sem dados de consumo de suportes para este contrato.</div>;
      const n=Math.max(1,row.rendas_decorridas||1);
      const pctSign=row.suporte_desvio_pct>=0?'+':'';
      const supColor=pct=>pct<0?'var(--green-700)':pct<=15?'var(--yellow-700)':'var(--red-700)';
      const statusColor=supColor(row.suporte_desvio_pct);
      /* index4=impresso (output real), index5=consumido (input total) */
      const rawMonthly=(MONTHLY_REVENUE[row.contrato]||[]).filter(d=>d[4]!=null&&d[5]!=null&&d[4]>0).map(d=>([d[0],Math.round(d[4]*100)/100,Math.round(d[5]*100)/100]));
      const chronological=rawMonthly.slice().sort((a,b)=>a[0]>b[0]?1:-1);
      const monthly=rawMonthly.slice().sort((a,b)=>a[0]<b[0]?1:-1);
      const hasMontly=chronological.length>1;
      /* d[1]=impresso, d[2]=consumido */
      const avgImpresso=hasMontly?chronological.reduce((s,d)=>s+d[1],0)/chronological.length:row.suporte_consumo_mt;
      const avgConsumo=hasMontly?chronological.reduce((s,d)=>s+d[2],0)/chronological.length:row.suporte_impresso_mt;
      /* SVG chart: solid=consumido, dashed=impresso */
      const cW=820,cH=180,pL=60,pR=20,pT=16,pB=36;
      const plotW=cW-pL-pR,plotH=cH-pT-pB;
      const allVals=chronological.flatMap(d=>[d[1],d[2]]);
      const yMax=Math.max(...allVals)*1.18||1;
      const xS=i=>pL+i/Math.max(chronological.length-1,1)*plotW;
      const yS=v=>pT+plotH*(1-v/yMax);
      const ptsC=chronological.map((d,i)=>({x:xS(i),y:yS(d[2]),v:d[2],mes:d[0],pct:d[1]>0?(d[2]-d[1])/d[1]*100:0}));
      const ptsI=chronological.map((d,i)=>({x:xS(i),y:yS(d[1]),v:d[1]}));
      const polyC=ptsC.map(p=>`${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');
      const polyI=ptsI.map(p=>`${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');
      const step=Math.ceil(chronological.length/7)||1;
      const xTicks=chronological.reduce((acc,d,i)=>{if(i%step===0||i===chronological.length-1)acc.push({x:xS(i),label:d[0].slice(2)});return acc;},[]);
      const yMid=yMax*0.5;
      /* analyst: last 6 months */
      const last6=chronological.slice(-6);
      const last6pct=last6.length?last6.reduce((s,d)=>s+(d[1]>0?(d[2]-d[1])/d[1]*100:0),0)/last6.length:row.suporte_desvio_pct;
      const mesesFora=chronological.filter(d=>d[1]>0&&(d[2]-d[1])/d[1]*100>15).length;
      const trendText=last6pct>15?'acima do limite de tolerância':last6pct>5?'próximo do limite de tolerância':last6pct<0?'abaixo do esperado':'dentro da margem de tolerância';
      return(<div style={{display:'flex',flexDirection:'column',gap:14}}>
        {/* Summary */}
        <div style={{background:'var(--bg-app)',border:'1px solid var(--border-1)',borderRadius:8,padding:'10px 16px',display:'flex',alignItems:'baseline',gap:10,flexWrap:'wrap'}}>
          <span style={{fontSize:10,fontWeight:700,color:'var(--fg-3)',textTransform:'uppercase',letterSpacing:'.06em'}}>Média Consumida ({n} {n===1?'mês':'meses'})</span>
          <span style={{fontSize:22,fontWeight:700,color:'var(--fg-1)',fontFamily:'var(--font-sans)',marginLeft:8}}>{fmt(avgConsumo,2)} mt</span>
          <span style={{fontSize:12,color:'var(--fg-3)'}}>de {fmt(avgImpresso,2)} mt impresso</span>
          <span style={{fontSize:13,fontWeight:700,color:statusColor}}>{pctSign}{fmt(row.suporte_desvio_pct,1)}%</span>
        </div>
        {/* Chart */}
        {hasMontly&&<div style={{background:'var(--bg-surface)',border:'1px solid var(--border-1)',borderRadius:8,overflow:'hidden'}}>
          <div style={{padding:'10px 14px 0',display:'flex',gap:18,alignItems:'center'}}>
            <span style={{display:'flex',alignItems:'center',gap:5,fontSize:11,color:'var(--fg-2)'}}><svg width="20" height="2"><line x1="0" y1="1" x2="20" y2="1" stroke="#3859D0" strokeWidth="2"/></svg>Consumido (mt)</span>
            <span style={{display:'flex',alignItems:'center',gap:5,fontSize:11,color:'var(--fg-2)'}}><svg width="22" height="2"><line x1="0" y1="1" x2="22" y2="1" stroke="#94A4C4" strokeWidth="1.5" strokeDasharray="4 3"/></svg>Impresso (mt)</span>
          </div>
          <svg viewBox={`0 0 ${cW} ${cH}`} style={{width:'100%',display:'block'}}>
            <line x1={pL} y1={yS(yMid)} x2={cW-pR} y2={yS(yMid)} stroke="var(--border-1)" strokeWidth="1"/>
            <line x1={pL} y1={yS(yMax*0.9)} x2={cW-pR} y2={yS(yMax*0.9)} stroke="var(--border-1)" strokeWidth="1"/>
            <text x={pL-6} y={yS(yMid)+4} textAnchor="end" fontSize="10" fill="#94A4C4">{fmt(yMid,1)}</text>
            <text x={pL-6} y={yS(yMax*0.9)+4} textAnchor="end" fontSize="10" fill="#94A4C4">{fmt(yMax*0.9,1)}</text>
            <polyline points={polyI} fill="none" stroke="#94A4C4" strokeWidth="1.5" strokeDasharray="5 4"/>
            <polyline points={polyC} fill="none" stroke="#3859D0" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round"/>
            {ptsC.map((p,i)=><circle key={i} cx={p.x} cy={p.y} r="4" fill={supColor(p.pct)} stroke="#fff" strokeWidth="1.5"><title>{p.mes}: {fmt(p.v,2)} mt consumido</title></circle>)}
            {xTicks.map((t,i)=><text key={i} x={t.x} y={cH-6} textAnchor="middle" fontSize="10" fill="#94A4C4">{t.label}</text>)}
          </svg>
        </div>}
        {/* Analyst comment */}
        <div style={{border:'1px solid var(--border-1)',borderRadius:8,padding:'12px 16px'}}>
          <div style={{fontSize:10,fontWeight:700,color:'var(--fg-3)',textTransform:'uppercase',letterSpacing:'.06em',marginBottom:8}}>Comentário do Analista</div>
          <p style={{fontSize:13,lineHeight:1.65,color:'var(--fg-1)',margin:0}}>
            Nos últimos {Math.min(6,n)} meses, o consumo de suportes está <strong>{trendText}</strong> (desvio médio {last6pct>=0?'+':''}{fmt(last6pct,1)}%) — em linha com a média histórica do contrato ({pctSign}{fmt(row.suporte_desvio_pct,1)}%). Ao longo do histórico registaram-se <strong>{mesesFora} {mesesFora===1?'mês':'meses'} fora da margem de tolerância</strong> (>15%).
          </p>
        </div>
        {/* Table: impresso first, consumido second */}
        {monthly.length>0&&<div style={{border:'1px solid var(--border-1)',borderRadius:8,overflow:'hidden'}}>
          <table style={{width:'100%',borderCollapse:'collapse',fontSize:12.5}}>
            <thead><tr style={{background:'var(--bg-app)'}}>
              {['Mês','mt Impresso','mt Consumido','Desvio (mt)','Desvio %'].map((h,i)=><th key={h} style={{padding:'8px 12px',textAlign:i===0?'left':'right',fontSize:10,fontWeight:700,color:'var(--fg-3)',textTransform:'uppercase',letterSpacing:'.05em',borderBottom:'1px solid var(--border-1)'}}>{h}</th>)}
            </tr></thead>
            <tbody>
              <tr style={{background:'color-mix(in oklch,var(--border-1) 40%,transparent)'}}>
                <td style={{padding:'8px 12px',fontWeight:700,fontSize:13}}>Total / Média</td>
                <td style={{padding:'8px 12px',textAlign:'right',color:'var(--fg-3)'}}>{fmt(row.suporte_consumo_mt,2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700}}>{fmt(row.suporte_impresso_mt,2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:statusColor}}>{(row.suporte_desvio_pct>=0?'+':'')+fmt(row.suporte_impresso_mt-row.suporte_consumo_mt,2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:statusColor}}>{pctSign}{fmt(row.suporte_desvio_pct,1)}%</td>
              </tr>
              {monthly.map((row2,idx)=>{const dev=row2[2]-row2[1];const pct=row2[1]>0?(dev/row2[1]*100):0;const col=supColor(pct);return(<tr key={row2[0]} style={{borderTop:'1px solid var(--border-1)'}}>
                <td style={{padding:'8px 12px'}}>{row2[0]}{idx===0&&<span style={{marginLeft:6,fontSize:9,fontWeight:700,padding:'2px 7px',borderRadius:10,background:'color-mix(in oklch,#3859D0 15%,transparent)',color:'#3859D0',verticalAlign:'middle'}}>MAIS RECENTE</span>}</td>
                <td style={{padding:'8px 12px',textAlign:'right',color:'var(--fg-3)'}}>{fmt(row2[1],2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:600}}>{fmt(row2[2],2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:col}}>{(dev>=0?'+':'')+fmt(dev,2)}</td>
                <td style={{padding:'8px 12px',textAlign:'right',color:col}}>{(pct>=0?'+':'')+fmt(pct,1)}%</td>
              </tr>);})}
            </tbody>
          </table>
        </div>}
      </div>);
    }
    if(sec==='financeira'){
      if(!FINANCEIRA_LINES[row.cliente]) return <div className="an-empty-note">Sem dados de faturação para este cliente.</div>;
      const finLines=FINANCEIRA_LINES[row.cliente]||[];
      /* group by month, newest first */
      const byMonth={};
      finLines.forEach(l=>{if(!byMonth[l[0]])byMonth[l[0]]={vl:0,c:0,lines:[]};byMonth[l[0]].vl+=l[3];byMonth[l[0]].c+=l[4];byMonth[l[0]].lines.push(l);});
      const months=Object.keys(byMonth).sort().reverse();
      const margColor=p=>p==null?'var(--fg-3)':p<0?'var(--red-700)':'var(--green-700)';
      const margTxt=p=>p==null?'—':(p>=0?'+':'')+fmt(p,1)+'%';
      return(<div style={{display:'flex',flexDirection:'column',gap:14}}>
        {/* existing summary */}
        <div dangerouslySetInnerHTML={{__html:anFinanceiraHTML(row.cliente)}}/>
        {/* Faturas e Créditos por Mês */}
        <div>
          <div style={{fontSize:14,fontWeight:700,color:'var(--fg-1)',marginBottom:10}}>Faturas e Créditos por Mês</div>
          <div style={{border:'1px solid var(--border-1)',borderRadius:8,overflow:'hidden'}}>
            <table style={{width:'100%',borderCollapse:'collapse',fontSize:12.5}}>
              <thead><tr style={{background:'var(--bg-app)'}}>
                {['Mês','Documento','Artigo','Valor Líquido','Custo','Margem %'].map((h,i)=><th key={h} style={{padding:'8px 12px',textAlign:i<3?'left':'right',fontSize:10,fontWeight:700,color:'var(--fg-3)',textTransform:'uppercase',letterSpacing:'.05em',borderBottom:'1px solid var(--border-1)'}}>{h}</th>)}
              </tr></thead>
              <tbody>
                {months.map(m=>{
                  const{vl,c,lines}=byMonth[m];
                  const mp=vl>0?(vl-c)/vl*100:null;
                  return(<React.Fragment key={m}>
                    {/* month total row */}
                    <tr style={{background:'color-mix(in oklch,var(--border-1) 30%,transparent)',borderTop:'1px solid var(--border-1)'}}>
                      <td style={{padding:'8px 12px',fontWeight:700,fontFamily:'var(--font-mono)',fontSize:12}}>{m}</td>
                      <td style={{padding:'8px 12px',fontWeight:700,color:'var(--fg-2)'}} colSpan={2}>Total do mês</td>
                      <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700}}>{fmtEFull(vl)}</td>
                      <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:'var(--fg-3)'}}>{fmtEFull(c)}</td>
                      <td style={{padding:'8px 12px',textAlign:'right',fontWeight:700,color:margColor(mp)}}>{margTxt(mp)}</td>
                    </tr>
                    {/* detail lines */}
                    {lines.map((l,i)=>{const lmp=l[3]>0?(l[3]-l[4])/l[3]*100:null;return(<tr key={i} style={{borderTop:'1px solid color-mix(in oklch,var(--border-1) 60%,transparent)'}}>
                      <td style={{padding:'6px 12px',color:'var(--fg-3)',fontFamily:'var(--font-mono)',fontSize:11}}>{l[0]}</td>
                      <td style={{padding:'6px 12px',color:'var(--fg-2)',fontSize:11.5}}>{l[1]}</td>
                      <td style={{padding:'6px 12px',fontFamily:'var(--font-mono)',fontSize:11}}>{l[2]}</td>
                      <td style={{padding:'6px 12px',textAlign:'right',color:l[3]<0?'var(--red-700)':'inherit'}}>{fmtEFull(l[3])}</td>
                      <td style={{padding:'6px 12px',textAlign:'right',color:'var(--fg-3)'}}>{fmtEFull(l[4])}</td>
                      <td style={{padding:'6px 12px',textAlign:'right',color:margColor(lmp)}}>{l[4]===0?'—':margTxt(lmp)}</td>
                    </tr>);})}
                  </React.Fragment>);
                })}
              </tbody>
            </table>
          </div>
        </div>
      </div>);
    }
    if(sec==='inventario') return <InventarioTab row={row} />;
    return null;
  };

  return(
    <div className="an-overlay open" onClick={e=>e.target===e.currentTarget&&onClose()}>
      <div className="an-modal">
        <div className="an-header">
          <div><h3 id="an-title">{row.cliente}</h3><div className="an-sub">{row.contrato}{row.contrato_unificado?' Unificado':''} · {(MONTHLY_REVENUE[row.contrato]||[]).length} meses com histórico</div></div>
          <button className="an-close" onClick={onClose}>✕</button>
        </div>
        <div style={{display:'flex',gap:4,padding:'0 20px',borderBottom:'1px solid var(--border-1)',flexWrap:'wrap'}}>
          {secs.map(s=><button key={s.k} className={`an-tab${sec===s.k?' active':''}`} onClick={()=>setSec(s.k)}>{s.l}</button>)}
        </div>
        <div className="an-body">{renderSec()}</div>
      </div>
    </div>
  );
};

/* ===== OverviewTab ===== */
const OverviewTab = ({data}) => {
  const [drillKey, setDrillKey] = React.useState(null);
  const ovData = React.useMemo(()=>{
    const expirados=data.filter(r=>r.expirado).length;
    const alertasRen=data.filter(r=>r.dias_termino!=null&&r.dias_termino>=0&&r.dias_termino<=90).length;
    const pvpARever=data.filter(r=>r.novo_pvp!=null);
    const m2Acima=data.filter(r=>r.m2_status==='ACIMA');
    const m2Abaixo=data.filter(r=>r.m2_status==='ABAIXO');
    const tintaAll=data.filter(r=>r.tinta_tem_dados);
    const tintaV=[],tintaA=[],tintaR=[];
    tintaAll.forEach(r=>{const c=desvioColor(r.tinta_desvio_pct,1);(c==='var(--green-700)'?tintaV:c==='var(--red-700)'?tintaR:tintaA).push(r);});
    const supAll=data.filter(r=>r.suporte_tem_dados);
    const supV=[],supA=[],supR=[];
    supAll.forEach(r=>{const c=desvioColor(r.suporte_desvio_pct,1);(c==='var(--green-700)'?supV:c==='var(--red-700)'?supR:supA).push(r);});
    const finV=[],finA=[],finR=[];
    FINANCEIRA_DATA.forEach(f=>{if(f.margem_pct==null)return;(f.margem_pct<0?finR:f.margem_pct<20?finA:finV).push(f);});
    const pvpOk=data.filter(r=>r.novo_pvp==null);
    const tFat=FINANCEIRA_DATA.reduce((s,r)=>s+r.valor_liquido,0);
    const tCusto=FINANCEIRA_DATA.reduce((s,r)=>s+r.custo_total,0);
    const margemMedia=tFat>0?(tFat-tCusto)/tFat*100:0;
    const tEsp=data.reduce((s,r)=>s+(r.rec_pot_atual||0),0);
    const h1=data.length?m2Acima.length/data.length*100:null;
    const h2=tintaAll.length?(tintaV.length+0.5*tintaA.length)/tintaAll.length*100:null;
    const h3=supAll.length?(supV.length+0.5*supA.length)/supAll.length*100:null;
    const h4=FINANCEIRA_DATA.length?(finV.length+0.5*finA.length)/FINANCEIRA_DATA.length*100:null;
    const h5=data.length?pvpOk.length/data.length*100:null;
    const hp=[h1,h2,h3,h4,h5].filter(v=>v!=null);
    const health=hp.length?hp.reduce((s,v)=>s+v,0)/hp.length:0;
    const segments={
      m2_acima:{title:'M² Acima do Esperado',rows:m2Acima,cols:['M² Acumulados','M² Esperado','Desvio'],vals:r=>[fmt(r.acumulado_m2),fmt(r.esperado_m2),(r.m2_desvio>=0?'+':'')+fmt(r.m2_desvio)+' m²'],colors:[null,null,r=>r.m2_desvio>=0?'var(--green-700)':'var(--red-700)']},
      m2_abaixo:{title:'M² Abaixo do Esperado',rows:m2Abaixo,cols:['M² Acumulados','M² Esperado','Desvio'],vals:r=>[fmt(r.acumulado_m2),fmt(r.esperado_m2),(r.m2_desvio>=0?'+':'')+fmt(r.m2_desvio)+' m²'],colors:[null,null,r=>r.m2_desvio>=0?'var(--green-700)':'var(--red-700)']},
      tinta_r:{title:'Tintas — Em incumprimento',rows:tintaR,cols:['ml/m² Real','ml/m² Esperado','Desvio %'],vals:r=>[fmt(r.tinta_ml_m2_real,1),fmt(r.tinta_ml_m2_esp,1),(r.tinta_desvio_pct>=0?'+':'')+fmt(r.tinta_desvio_pct,1)+'%'],colors:[null,null,r=>desvioColor(r.tinta_desvio_pct,1)]},
      tinta_a:{title:'Tintas — A monitorizar',rows:tintaA,cols:['ml/m² Real','ml/m² Esperado','Desvio %'],vals:r=>[fmt(r.tinta_ml_m2_real,1),fmt(r.tinta_ml_m2_esp,1),(r.tinta_desvio_pct>=0?'+':'')+fmt(r.tinta_desvio_pct,1)+'%'],colors:[null,null,r=>desvioColor(r.tinta_desvio_pct,1)]},
      tinta_v:{title:'Tintas — Dentro do esperado',rows:tintaV,cols:['ml/m² Real','ml/m² Esperado'],vals:r=>[fmt(r.tinta_ml_m2_real,1),fmt(r.tinta_ml_m2_esp,1)],colors:[null,null]},
      sup_r:{title:'Suportes — Em incumprimento',rows:supR,cols:['mt Impressos','mt Consumidos','Desvio %'],vals:r=>[fmt(r.suporte_impresso_mt),fmt(r.suporte_consumo_mt),(r.suporte_desvio_pct>=0?'+':'')+fmt(r.suporte_desvio_pct,1)+'%'],colors:[null,null,r=>desvioColor(r.suporte_desvio_pct,1)]},
      sup_a:{title:'Suportes — A monitorizar',rows:supA,cols:['mt Impressos','mt Consumidos','Desvio %'],vals:r=>[fmt(r.suporte_impresso_mt),fmt(r.suporte_consumo_mt),(r.suporte_desvio_pct>=0?'+':'')+fmt(r.suporte_desvio_pct,1)+'%'],colors:[null,null,r=>desvioColor(r.suporte_desvio_pct,1)]},
      sup_v:{title:'Suportes — Dentro do esperado',rows:supV,cols:['mt Impressos','mt Consumidos'],vals:r=>[fmt(r.suporte_impresso_mt),fmt(r.suporte_consumo_mt)],colors:[null,null]},
      fin_r:{title:'Margem Negativa',rows:finR,cols:['Faturação','Custo','Margem %'],vals:r=>[fmtEFull(r.valor_liquido),fmtEFull(r.custo_total),finPctTxt(r.margem_pct)],colors:[null,null,r=>finMargemColor(r.margem_pct)]},
      fin_a:{title:'Margem 0–20%',rows:finA,cols:['Faturação','Custo','Margem %'],vals:r=>[fmtEFull(r.valor_liquido),fmtEFull(r.custo_total),finPctTxt(r.margem_pct)],colors:[null,null,r=>finMargemColor(r.margem_pct)]},
      pvp_rever:{title:'Contratos com PVP a Rever',rows:pvpARever,cols:['PVP Atual','PVP Novo','Valor em Atraso'],vals:r=>[typeof r.pvp==='number'?fmt(r.pvp,2)+' €':r.pvp,r.novo_pvp?fmtEFull(r.novo_pvp)+'/m²':'—',r.valor_em_atraso>0?fmtEFull(r.valor_em_atraso):'—'],colors:[null,r=>r.novo_pvp!=null?'var(--pumpkin-700)':null,r=>r.valor_em_atraso>0?'var(--red-700)':null]},
    };
    const rag=[
      {label:'M² Impressos vs. Contratualizado',segs:[{c:'var(--green-700)',n:m2Acima.length,k:'m2_acima',l:'Acima'},{c:'var(--red-700)',n:m2Abaixo.length,k:'m2_abaixo',l:'Abaixo'}],total:data.length},
      {label:'Controlo de Tintas',segs:[{c:'var(--green-700)',n:tintaV.length,k:'tinta_v',l:'OK'},{c:'var(--yellow-700)',n:tintaA.length,k:'tinta_a',l:'Monitorizar'},{c:'var(--red-700)',n:tintaR.length,k:'tinta_r',l:'Incumprimento'}],total:tintaAll.length},
      {label:'Controlo de Suportes',segs:[{c:'var(--green-700)',n:supV.length,k:'sup_v',l:'OK'},{c:'var(--yellow-700)',n:supA.length,k:'sup_a',l:'Monitorizar'},{c:'var(--red-700)',n:supR.length,k:'sup_r',l:'Incumprimento'}],total:supAll.length},
      {label:'Análise Financeira (Margem)',segs:[{c:'var(--green-700)',n:finV.length,k:null,l:'≥20%'},{c:'var(--yellow-700)',n:finA.length,k:'fin_a',l:'0–20%'},{c:'var(--red-700)',n:finR.length,k:'fin_r',l:'Negativa'}],total:FINANCEIRA_DATA.length},
      {label:'PVP a Rever',segs:[{c:'var(--green-700)',n:pvpOk.length,k:null,l:'OK'},{c:'var(--red-700)',n:pvpARever.length,k:'pvp_rever',l:'A Rever'}],total:data.length},
    ];
    const maxPot=Math.max(...VEND_DATA.map(v=>v.rec_pot),1);
    return{expirados,alertasRen,pvpARever,m2Acima,m2Abaixo,tintaR,supR,finR,health,segments,rag,tFat,tCusto,margemMedia,tEsp,maxPot,pvpOk};
  },[data]);

  const drill = drillKey&&ovData.segments[drillKey];
  const hst = ovData.health<40?{l:'Crítico',c:'var(--red-700)'}:ovData.health<70?{l:'Médio',c:'var(--pumpkin-700)'}:{l:'Saudável',c:'var(--green-700)'};

  const RagBar = ({label,segs,total}) => {
    const tot=segs.reduce((s,sg)=>s+sg.n,0)||1;
    return(<div className="rag-row">
      <div className="rag-label">{label}</div>
      <div className="rag-track">{segs.filter(sg=>sg.n>0).map((sg,i)=>(<div key={i} className="rag-seg" style={{width:`${sg.n/tot*100}%`,background:sg.c,cursor:sg.k?'pointer':'default'}} onClick={()=>sg.k&&setDrillKey(prev=>prev===sg.k?null:sg.k)} title={`${sg.l}: ${sg.n}`}/>))}</div>
      <div className="rag-count">{segs.map((sg,i)=><span key={i} style={{color:sg.c,fontWeight:600,marginRight:8,cursor:sg.k?'pointer':'default'}} onClick={()=>sg.k&&setDrillKey(prev=>prev===sg.k?null:sg.k)}>{sg.n}</span>)}</div>
    </div>);
  };

  return(
    <div className="pp-tab-content"><div className="page">
      {/* Banner */}
      <div className="ov-banner">
        {[
          ['Contratos',fmt(data.length),''],
          ['Receita Potencial',fmtE(data.reduce((s,r)=>s+(r.rec_pot||0),0)),''],
          ['Faturado Real',fmtE(ovData.tFat),''],
          ['Margem Média',fmt(ovData.margemMedia,1)+'%',ovData.margemMedia<0?'var(--red-700)':'var(--green-700)'],
          ['PVP a Rever',String(ovData.pvpARever.length),ovData.pvpARever.length>0?'var(--pumpkin-700)':''],
          ['Expirados',String(ovData.expirados),ovData.expirados>0?'var(--red-700)':''],
        ].map(([l,v,c])=><div key={l} className="ov-banner-item"><div className="ov-banner-v" style={{color:c||undefined}}>{v}</div><div className="ov-banner-l">{l}</div></div>)}
      </div>

      {/* Health + RAG combined */}
      <div className="card" style={{marginBottom:14}}>
        <div className="ov-health-rag-layout">
          <div className="ov-health-side">
            <div className="card-title">Saúde dos Contratos</div>
            <div className="ov-health-body">
              <div className="ov-gauge-wrap" dangerouslySetInnerHTML={{__html:ovGaugeSVG(ovData.health)}}/>
              <div className="ov-health-text">
                <div className="ov-health-score" style={{color:hst.c}}>{fmt(ovData.health,0)}%</div>
                <div className="ov-health-label" style={{color:hst.c}}>{hst.l}</div>
              </div>
            </div>
          </div>
          <div className="ov-rag-side">
            <div className="card-title">5 Indicadores-Chave <span className="ct">Clique numa barra para ver o detalhe</span></div>
            {ovData.rag.map(row=><RagBar key={row.label} label={row.label} segs={row.segs} total={row.total}/>)}
            {drill&&<div style={{marginTop:14,borderTop:'1px solid var(--border-1)',paddingTop:14}}>
              <div className="ov-drill-header">
                <div className="ov-drill-title">{drill.title} <span className="ct">{drill.rows.length} registos</span></div>
                <button className="ov-drill-close" onClick={()=>setDrillKey(null)}>✕</button>
              </div>
              <div className="tbl-wrap" style={{maxHeight:300,overflowY:'auto'}}>
                <table><thead><tr><th>Cliente / Contrato</th>{drill.cols.map(col=><th key={col} className="num">{col}</th>)}</tr></thead>
                <tbody>{drill.rows.map((r,i)=><tr key={i}><td><div className="cname" title={r.cliente}>{r.cliente}</div>{r.contrato&&<div className="ccode">{r.contrato}</div>}</td>{drill.vals(r).map((v,j)=><td key={j} className="num" style={drill.colors[j]?(typeof drill.colors[j]==='function'?{color:drill.colors[j](r),fontWeight:700}:{color:drill.colors[j],fontWeight:700}):{}}>{v}</td>)}</tr>)}</tbody>
                </table>
              </div>
            </div>}
          </div>
        </div>
      </div>

      {/* Risk table */}
      <div className="card" style={{marginBottom:'var(--space-4)'}}>
        <div className="card-title">Riscos e Ações <span className="ct">Clique num número para ver os contratos</span></div>
        <table className="ov-risk-table"><thead><tr><th>Indicador</th><th className="num">Crítico</th><th className="num">A Monitorizar</th></tr></thead>
        <tbody>
          {[['M² Impressos vs. Contratualizado','m2_abaixo',ovData.m2Abaixo.length,null,0],['Controlo de Tintas','tinta_r',ovData.tintaR&&ovData.tintaR.length,'tinta_a',ovData.segments.tinta_a&&ovData.segments.tinta_a.rows.length],['Controlo de Suportes','sup_r',ovData.supR&&ovData.supR.length,'sup_a',ovData.segments.sup_a&&ovData.segments.sup_a.rows.length],['Análise Financeira (Margem)','fin_r',ovData.finR&&ovData.finR.length,'fin_a',ovData.segments.fin_a&&ovData.segments.fin_a.rows.length],['PVP a Rever','pvp_rever',ovData.pvpARever.length,null,0]].map(([l,kCrit,nCrit,kMon,nMon])=>(
            <tr key={l}><td>{l}</td>
              <td className="num"><span className="ov-risk-cell" style={{color:'var(--red-700)',cursor:kCrit&&nCrit?'pointer':'default'}} onClick={()=>kCrit&&nCrit&&setDrillKey(prev=>prev===kCrit?null:kCrit)}>{nCrit||0}</span></td>
              <td className="num"><span className="ov-risk-cell" style={{color:'var(--yellow-700)',cursor:kMon&&nMon?'pointer':'default'}} onClick={()=>kMon&&nMon&&setDrillKey(prev=>prev===kMon?null:kMon)}>{nMon||0}</span></td>
            </tr>
          ))}
        </tbody></table>
      </div>



      {/* Vendor bars */}
      <div className="g2" style={{marginBottom:'var(--space-4)'}}>
        <div className="card">
          <div className="card-title">Receita por Vendedor</div>
          {[...VEND_DATA].sort((a,b)=>b.rec_pot-a.rec_pot).map(v=>{
            const w=v.rec_pot/ovData.maxPot*100;const rw=clamp(v.rec_real/v.rec_pot*100,0,100);
            const color=v.util_pct>=100?'var(--green-700)':v.util_pct>=50?'var(--dd-primary-600)':v.util_pct>=25?'var(--pumpkin-700)':'var(--red-700)';
            return(<div key={v.vendedor} className="vbar">
              <div className="vname">{v.vendedor}</div>
              <div className="vtracks">
                <div className="vtrack-label">Potencial: {fmtE(v.rec_pot)} · Realizado: {fmtE(v.rec_real)}</div>
                <div className="vtrack-bar" style={{width:`${w}%`}}><div className="vtrack-fill" style={{width:`${rw}%`,background:color}}/></div>
              </div>
              <div className="vpct-badge" style={{color}}>{fmt(v.util_pct,0)}%</div>
            </div>);
          })}
        </div>
        <div className="card">
          <div className="card-title">Distribuição Care</div>
          {Object.entries(data.reduce((acc,r)=>{acc[r.care_type]=(acc[r.care_type]||0)+1;return acc;},{})).map(([k,v])=>{
            const pct=v/data.length*100;const cc={'Care Plus':'var(--purple-700)','Care':'var(--dd-primary-600)','No Care':'var(--fg-3)'}[k]||'var(--fg-3)';
            return(<div key={k} className="ov-metric"><label><span>{k}</span><strong>{v} ({fmt(pct,0)}%)</strong></label><div className="gauge"><div className="gauge-fill" style={{width:`${pct}%`,background:cc}}/></div></div>);
          })}
        </div>
      </div>

      {/* Top lists */}
      <div className="g4">
        <div className="card"><div className="card-title">Receita em Falta — Top 5</div><table><tbody>{data.filter(r=>r.rec_falta>0).sort((a,b)=>b.rec_falta-a.rec_falta).slice(0,5).map(r=><tr key={r.contrato}><td><div className="cname" title={r.cliente}>{r.cliente}</div><div className="ccode">{r.contrato}</div></td><td className="num" style={{color:'var(--red-700)',fontWeight:700}}>{fmtEFull(r.rec_falta)}</td></tr>)}</tbody></table></div>
        <div className="card"><div className="card-title">Desvio M² Negativo — Top 5</div><table><tbody>{data.filter(r=>r.m2_status==='ABAIXO').sort((a,b)=>a.m2_desvio-b.m2_desvio).slice(0,5).map(r=><tr key={r.contrato}><td><div className="cname" title={r.cliente}>{r.cliente}</div></td><td className="num" style={{color:'var(--red-700)',fontWeight:700}}>{fmt(r.m2_desvio)} m²</td></tr>)}</tbody></table></div>
        <div className="card"><div className="card-title">PVP a Rever — Top 5</div><table><tbody>{data.filter(r=>r.novo_pvp!=null).sort((a,b)=>b.valor_em_atraso-a.valor_em_atraso).slice(0,5).map(r=><tr key={r.contrato}><td><div className="cname" title={r.cliente}>{r.cliente}</div></td><td className="num" style={{color:'var(--pumpkin-700)',fontWeight:700}}>{fmtEFull(r.novo_pvp)}/m²</td></tr>)}</tbody></table></div>
        <div className="card"><div className="card-title">Margem Negativa — Top 5</div><table><tbody>{FINANCEIRA_DATA.filter(r=>r.margem_pct!=null&&r.margem_pct<0).sort((a,b)=>b.valor_liquido-a.valor_liquido).slice(0,5).map(r=><tr key={r.cliente}><td><div className="cname" title={r.cliente}>{r.cliente}</div></td><td className="num" style={{color:'var(--red-700)',fontWeight:700}}>{finPctTxt(r.margem_pct)}</td></tr>)}</tbody></table></div>
      </div>
    </div></div>
  );
};

/* ===== exportContratosCSV ===== */
function exportContratosCSV(){
  const cols=['Cliente','Contrato Nº','Nº Cliente','Vendedor','Armazém','Código Artigo','Equipamento','Modelo Equipamento','Nº Série',
    'Tintas Instaladas','Limite Máximo (ml/m²)','Suportes de Impressão','Assistência Técnica','Preço Assistência',
    'M² Mensais','Prazo','Total M² Contratados','Preço M² Contratado','Data Início','Data Término','Estado Assinatura'];
  const esc=v=>{if(v===null||v===undefined)return'';const s=String(v);return/[;"\n]/.test(s)?'"'+s.replace(/"/g,'""')+'"':s;};
  const rows=[...DATA].sort((a,b)=>(a.cliente||'').localeCompare(b.cliente||'','pt')).map(r=>[
    r.cliente,r.contrato,r.num_cliente,r.vendedor,r.armazem,r.cod_artigo,
    r.equip!=null?r.equip:'',r.equip_modelo||'',r.numero_serie||'',
    r.tintas_flag?'Incluídas':'Excluídas',r.ml_esp!=null?r.ml_esp:'',
    r.sup_imp===1?'Incluído':r.sup_imp===0?'Excluído':'',r.care_type||'',
    r.preco_assistencia!=null?r.preco_assistencia:'',
    r.m2_mensal!=null?Math.round(r.m2_mensal):(r.num_rendas?Math.round(r.limite_m2/r.num_rendas):''),
    `${r.rendas_decorridas}/${r.num_rendas} meses`,
    r.limite_m2,r.pvp,r.data_inicio,r.data_termino,r.estado_assinatura||''
  ]);
  const csv='\uFEFF'+[cols,...rows].map(row=>row.map(esc).join(';')).join('\r\n');
  const blob=new Blob([csv],{type:'text/csv;charset=utf-8;'});
  const a=document.createElement('a');
  a.href=URL.createObjectURL(blob);
  a.download='Contratos_PrintPlan_'+new Date().toISOString().slice(0,10).replace(/-/g,'')+'.csv';
  document.body.appendChild(a);a.click();document.body.removeChild(a);
  URL.revokeObjectURL(a.href);
}

/* ===== M2Tab ===== */
const M2Tab = ({data, onOpenAnalysis}) => {
  const [search, setSearch] = React.useState('');
  const [sortKey, setSortKey] = React.useState('m2_desvio_pct');
  const [sortDir, setSortDir] = React.useState('asc');
  const [timeMode, setTimeMode] = React.useState('atual');
  const [refMonth, setRefMonth] = React.useState('');
  const [intFrom, setIntFrom] = React.useState('');
  const [intTo, setIntTo] = React.useState('');
  const [groupBy, setGroupBy] = React.useState(true);
  const [expanded, setExpanded] = React.useState({});
  const [equipFilter, setEquipFilter] = React.useState('todos');

  const ALL_MONTHS = React.useMemo(()=>[...new Set(Object.values(MONTHLY_REVENUE).flatMap(rows=>rows.map(r=>r[0])))].sort(),[]);

  const baseData = React.useMemo(()=>{
    if(timeMode==='refmonth'&&refMonth){
      return data.map(r=>{const m=MONTHLY_REVENUE[r.contrato];if(!m)return r;return applyMonthlySnapshot(r,m.filter(x=>x[0]<=refMonth));});
    }
    if(timeMode==='interval'&&intFrom&&intTo){
      return data.map(r=>{const m=MONTHLY_REVENUE[r.contrato];if(!m)return{...r,periodo_m2:null,periodo_receita:null};const rows=m.filter(x=>x[0]>=intFrom&&x[0]<=intTo);return{...r,periodo_m2:rows.reduce((s,x)=>s+x[1],0),periodo_receita:Math.round(rows.reduce((s,x)=>s+x[1],0)*r.pvp*100)/100};});
    }
    return data;
  },[data,timeMode,refMonth,intFrom,intTo]);

  const filtered = React.useMemo(()=>{
    const q=search.toLowerCase();
    let arr=baseData.filter(r=>{
      if(q&&!r.cliente.toLowerCase().includes(q)&&!r.contrato.toLowerCase().includes(q))return false;
      if(equipFilter==='equip'&&r.equip!==1)return false;
      if(equipFilter==='sem'&&r.equip===1)return false;
      if(equipFilter==='campanha'&&!CAMPANHA_0M2.has(r.contrato))return false;
      return true;
    });
    arr.sort((a,b)=>{let va=a[sortKey],vb=b[sortKey];if(typeof va==='string')va=va.toLowerCase();if(typeof vb==='string')vb=vb.toLowerCase();if(va==null)va=-Infinity;if(vb==null)vb=-Infinity;return sortDir==='asc'?(va<vb?-1:va>vb?1:0):(va>vb?-1:va<vb?1:0);});
    return arr;
  },[baseData,search,sortKey,sortDir,equipFilter]);

  const doSort = k => {setSortDir(prev=>sortKey===k?(prev==='asc'?'desc':'asc'):'asc');setSortKey(k);};

  const aggGroup = (cliente, contratos) => {
    const sum=k=>contratos.reduce((s,r)=>s+(r[k]||0),0);
    const limite_m2=sum('limite_m2'),acumulado_m2=sum('acumulado_m2'),esperado_m2=sum('esperado_m2'),rec_real=sum('rec_real'),rec_pot=sum('rec_pot'),rec_falta=sum('rec_falta'),valor_em_atraso=sum('valor_em_atraso');
    const m2_desvio=acumulado_m2-esperado_m2;const m2_desvio_pct=esperado_m2>0?Math.round(m2_desvio/esperado_m2*1000)/10:0;
    // tinta aggregation
    const tintaRows=contratos.filter(r=>r.tinta_tem_dados);
    const tinta_tem_dados=tintaRows.length>0;
    const tinta_ml_m2_real=tinta_tem_dados?tintaRows.reduce((s,r)=>s+(r.tinta_ml_m2_real||0),0)/tintaRows.length:null;
    const tinta_ml_m2_esp=tinta_tem_dados?tintaRows.reduce((s,r)=>s+(r.tinta_ml_m2_esp||0),0)/tintaRows.length:null;
    const tinta_desvio_pct=tinta_ml_m2_esp>0?(tinta_ml_m2_real-tinta_ml_m2_esp)/tinta_ml_m2_esp*100:null;
    // suporte aggregation
    const supRows=contratos.filter(r=>r.suporte_tem_dados);
    const suporte_tem_dados=supRows.length>0;
    const suporte_consumo_mt=suporte_tem_dados?sum('suporte_consumo_mt'):null;
    const suporte_impresso_mt=suporte_tem_dados?sum('suporte_impresso_mt'):null;
    const suporte_desvio_pct=suporte_tem_dados&&supRows[0]?(supRows.reduce((s,r)=>s+(r.suporte_desvio_pct||0),0)/supRows.length):null;
    // fin from _ppFM (client level)
    const fin=_ppFM[cliente];
    return{cliente,contrato:'',vendedor:contratos[0].vendedor,rendas_decorridas:null,num_rendas:null,limite_m2,acumulado_m2,esperado_m2,rec_real,rec_pot,rec_falta,m2_desvio,m2_desvio_pct,m2_status:m2_desvio>=0?'ACIMA':'ABAIXO',pvp:null,m2_mensal:null,novo_pvp:contratos.some(r=>r.novo_pvp!=null)?true:null,valor_em_atraso,tinta_tem_dados,tinta_ml_m2_real,tinta_ml_m2_esp,tinta_desvio_pct,suporte_tem_dados,suporte_consumo_mt,suporte_impresso_mt,suporte_desvio_pct,fin_valor_liquido:fin?fin.valor_liquido:null,fin_margem_pct:fin?fin.margem_pct:null,_isGroup:true,_n:contratos.length};
  };

  const SortTh = ({k,label,tip,className}) => {
    const active=sortKey===k;
    return <th className={className} title={tip} onClick={()=>doSort(k)} style={{cursor:'pointer',userSelect:'none'}}>{label}{active?` ${sortDir==='asc'?'↑':'↓'}`:''}</th>;
  };

  const m2StatusCell = r => {
    const color=r.m2_status==='ACIMA'?'var(--green-700)':'var(--red-700)';
    const pct=r.esperado_m2>0?fmt(r.acumulado_m2/r.esperado_m2*100,1)+'%':'—';
    const tip=`Desvio: ${r.m2_desvio>=0?'+':''}${fmt(r.m2_desvio)} m² (${pct})`;
    if(!r.acumulado_m2&&r.acumulado_m2!==0) return <span style={{color:'var(--fg-3)'}}>—</span>;
    return <span style={{color,fontWeight:700,cursor:'help',whiteSpace:'nowrap'}} title={tip}>{fmt(r.acumulado_m2)}<span style={{color:'var(--fg-3)',fontWeight:400,margin:'0 2px'}}>/</span><span style={{color:'var(--fg-3)',fontWeight:400}}>{fmt(r.esperado_m2)}</span><span style={{color:'var(--fg-3)',fontWeight:400,fontSize:10,marginLeft:3}}>m²</span></span>;
  };

  const renderRow = (r, isGroup=false, groupId=null, hidden=false) => {
    const key = r.contrato||r.cliente;
    if(hidden) return null;
    return(
      <tr key={key} className={isGroup?'m2-group-row':groupId?`m2-detail-row m2-detail-${groupId}`:''} onClick={isGroup?()=>setExpanded(prev=>({...prev,[groupId]:!prev[groupId]})):null} style={isGroup?{cursor:'pointer'}:{}}>
        <td><div className="cname" title={r.cliente}>{isGroup&&<span className="m2-arrow-chip">{expanded[groupId]?'▾':'▸'}</span>}{r.cliente}</div></td>
        <td><div className="ccode" title={r.contrato||''}>{isGroup?<span className="m2-group-count">{r._n} contratos</span>:r.contrato}</div></td>
        <td>{!isGroup&&<button className="btn-analisar" onClick={e=>{e.stopPropagation();onOpenAnalysis(r);}}>Analisar</button>}</td>
        <td className="num" style={{color:'var(--fg-3)'}}>{isGroup?<span className="m2-group-count">{r._n} contratos</span>:`${r.rendas_decorridas}/${r.num_rendas}`}</td>
        <td className="num">{r.limite_m2!=null?fmt(r.limite_m2):'—'}</td>
        <td className="num">{r.m2_mensal!=null?fmt(r.m2_mensal):'—'}</td>
        <td className="num">{typeof r.pvp==='number'?fmt(r.pvp,2)+' €':r.pvp||'—'}</td>
        <td className="num">{m2StatusCell(r)}</td>
        <td className="num" style={{whiteSpace:'nowrap'}}>{r.novo_pvp!=null?<span>{isGroup?<span style={{color:'var(--pumpkin-700)',fontWeight:700}}>Rever</span>:<span><span style={{color:'var(--pumpkin-700)',fontWeight:700}}>{fmt(r.novo_pvp,2)+' €/m²'}</span>{r.valor_em_atraso>0&&<span style={{color:'var(--fg-3)',fontWeight:400}}>{' · '+fmt(r.valor_em_atraso,0)+' €'}</span>}</span>}</span>:'—'}</td>
        <td className="num">{r.tinta_tem_dados?<span style={{color:desvioColor(r.tinta_desvio_pct,1),fontWeight:700,whiteSpace:'nowrap'}} title={`Desvio: ${r.tinta_desvio_pct>=0?'+':''}${fmt(r.tinta_desvio_pct,1)}%`}>{fmt(r.tinta_ml_m2_real,1)}<span style={{color:'var(--fg-3)',fontWeight:400,margin:'0 2px'}}>/</span><span style={{color:'var(--fg-3)',fontWeight:400}}>{fmt(r.tinta_ml_m2_esp,1)}</span><span style={{color:'var(--fg-3)',fontWeight:400,fontSize:10,marginLeft:3}}>ml/m²</span></span>:<span style={{color:'var(--fg-3)'}}>—</span>}</td>
        <td className="num">{r.suporte_tem_dados?<span style={{color:desvioColor(r.suporte_desvio_pct,1),fontWeight:700,whiteSpace:'nowrap'}} title={`Desvio: ${r.suporte_desvio_pct>=0?'+':''}${fmt(r.suporte_desvio_pct,1)}%`}>{fmt(r.suporte_consumo_mt)}<span style={{color:'var(--fg-3)',fontWeight:400,margin:'0 2px'}}>/</span><span style={{color:'var(--fg-3)',fontWeight:400}}>{fmt(r.suporte_impresso_mt)}</span><span style={{color:'var(--fg-3)',fontWeight:400,fontSize:10,marginLeft:3}}>mt</span></span>:<span style={{color:'var(--fg-3)'}}>—</span>}</td>
        <td className="num" style={{whiteSpace:'nowrap'}}>{r.fin_valor_liquido!=null?fmtEFull(r.fin_valor_liquido):<span style={{color:'var(--fg-3)'}}>—</span>}</td>
        <td className="num" style={{whiteSpace:'nowrap'}}>{r.fin_margem_pct!=null?<span style={{color:finMargemColor(r.fin_margem_pct),fontWeight:700}}>{finPctTxt(r.fin_margem_pct)}</span>:<span style={{color:'var(--fg-3)'}}>—</span>}</td>
      </tr>
    );
  };

  const rows = React.useMemo(()=>{
    if(!groupBy) return filtered.map(r=>renderRow(r));
    const map={};filtered.forEach(r=>{(map[r.cliente]=map[r.cliente]||[]).push(r);});
    return Object.entries(map).flatMap(([cliente,contratos])=>{
      if(contratos.length===1) return [renderRow(contratos[0])];
      const safeId=cliente.replace(/[^a-zA-Z0-9]/g,'_');
      const rep=aggGroup(cliente,contratos);
      const groupRow=renderRow(rep,true,safeId);
      const detailRows=expanded[safeId]?contratos.map(r=>renderRow(r,false,safeId)):[];
      return[groupRow,...detailRows];
    });
  },[filtered,groupBy,expanded,sortKey,sortDir]);

  return(
    <div className="pp-tab-content"><div className="page">
      <div className="frow" style={{flexWrap:'wrap',gap:8,marginBottom:12}}>
        <input type="text" value={search} onChange={e=>setSearch(e.target.value)} placeholder="Pesquisar cliente ou contrato…" style={{flex:1,minWidth:200}}/>
        {[['todos','Todos'],['equip','Com Equipamento'],['sem','Sem Equipamento'],['campanha','Campanha 0m²']].map(([k,l])=><button key={k} className={`fb${equipFilter===k?' on':''}`} onClick={()=>setEquipFilter(k)}>{l}</button>)}
        <button className={`fb${groupBy?' on':''}`} onClick={()=>setGroupBy(v=>!v)}>Agrupar por cliente</button>
        <button className="fb" title="Descarrega todos os contratos (colunas iguais ao ficheiro de validação, com o Estado de Assinatura)" onClick={exportContratosCSV}>⬇ Descarregar</button>
        <span className="rc">{filtered.length} contratos</span>
      </div>
      <div className="time-mode-row" style={{marginBottom:12,display:'flex',gap:8,alignItems:'center',flexWrap:'wrap'}}>
        {[['atual','Atual (YTD)'],['refmonth','Por Mês'],['interval','Por Intervalo']].map(([k,l])=><button key={k} className={`tm-btn${timeMode===k?' active':''}`} onClick={()=>setTimeMode(k)}>{l}</button>)}
        {timeMode==='refmonth'&&<select value={refMonth} onChange={e=>setRefMonth(e.target.value)}><option value="">Mês de referência…</option>{ALL_MONTHS.map(m=><option key={m} value={m}>{m}</option>)}</select>}
        {timeMode==='interval'&&<><select value={intFrom} onChange={e=>setIntFrom(e.target.value)}><option value="">De…</option>{ALL_MONTHS.map(m=><option key={m} value={m}>{m}</option>)}</select><select value={intTo} onChange={e=>setIntTo(e.target.value)}><option value="">Até…</option>{ALL_MONTHS.filter(m=>!intFrom||m>=intFrom).map(m=><option key={m} value={m}>{m}</option>)}</select></>}
      </div>
      <div className="tbl-wrap">
        <table>
          <colgroup><col style={{width:'10%'}}/><col style={{width:'7%'}}/><col style={{width:'6%'}}/><col style={{width:'5%'}}/><col style={{width:'7%'}}/><col style={{width:'6%'}}/><col style={{width:'6%'}}/><col style={{width:'9%'}}/><col style={{width:'10%'}}/><col style={{width:'7%'}}/><col style={{width:'7%'}}/><col style={{width:'8%'}}/><col style={{width:'6%'}}/></colgroup>
          <thead><tr>
            <SortTh k="cliente" label="Cliente"/>
            <SortTh k="contrato" label="Contrato"/>
            <th>Análise</th>
            <SortTh k="rendas_decorridas" label="Meses" className="num" tip="Rendas decorridas / total rendas"/>
            <SortTh k="limite_m2" label="M² Total" className="num"/>
            <SortTh k="m2_mensal" label="M²/Mês" className="num"/>
            <SortTh k="pvp" label="PVP €/m²" className="num"/>
            <SortTh k="acumulado_m2" label="M² Impressos" className="num" tip="M² acumulados até à data (hover na célula para desvio vs esperado)"/>
            <SortTh k="novo_pvp" label="PVP a Rever" className="num"/>
            <SortTh k="tinta_desvio_pct" label="Tintas" className="num" tip="ml/m² real / ml/m² esperado (hover para desvio %)"/>
            <SortTh k="suporte_desvio_pct" label="Suportes" className="num" tip="mt consumidos / mt impressos (hover para desvio %)"/>
            <SortTh k="fin_valor_liquido" label="Faturado" className="num"/>
            <SortTh k="fin_margem_pct" label="Margem %" className="num"/>
          </tr></thead>
          <tbody>{rows}</tbody>
        </table>
      </div>
    </div></div>
  );
};

/* ===== AlertasTab ===== */
const AlertasTab = ({data}) => {
  const [search, setSearch] = React.useState('');
  const [tema, setTema] = React.useState('');
  const [equipView, setEquipView] = React.useState('equip');
  const [pcList, setPcList] = React.useState(()=>{try{return JSON.parse(localStorage.getItem('pp_casos_particulares')||'[]');}catch(e){return[];}});
  const [pcCliente, setPcCliente] = React.useState('');
  const [pcContrato, setPcContrato] = React.useState('');
  const [pcNota, setPcNota] = React.useState('');

  const alertRows = React.useMemo(()=>{
    const rows=[];
    data.forEach(r=>{
      const equip=r.equip===1;
      const m2c=alertsM2Criterio(r);
      if(m2c)rows.push({cliente:r.cliente,contrato:r.contrato,vendedor:r.vendedor,equip,tema:'M²',detalhe:`Média ${m2c.n} ${m2c.n===1?'mês':'meses'}: ${fmt(m2c.avgReal,0)} m²/mês (contratado: ${fmt(r.m2_mensal,0)} m²/mês) · Propor negociação ou término`});
      if(r.prazo_excedido)rows.push({cliente:r.cliente,contrato:r.contrato,vendedor:r.vendedor,equip,tema:'Prazo',detalhe:`Decorridos ${r.rendas_decorridas} meses de ${r.num_rendas} contratados · Contrato a renegociar`});
      if(r.tinta_tem_dados){const c=desvioColor(r.tinta_desvio_pct,1);if(c==='var(--red-700)')rows.push({cliente:r.cliente,contrato:r.contrato,vendedor:r.vendedor,equip,tema:'Tinta',detalhe:`${r.tinta_desvio_pct>=0?'+':''}${fmt(r.tinta_desvio_pct,1)}% acima do esperado`});else if(r.tinta_ml_m2_real===0)rows.push({cliente:r.cliente,contrato:r.contrato,vendedor:r.vendedor,equip,tema:'Tinta',detalhe:'Consumo de tinta zero'});}
      if(r.suporte_tem_dados&&desvioColor(r.suporte_desvio_pct,1)==='var(--red-700)')rows.push({cliente:r.cliente,contrato:r.contrato,vendedor:r.vendedor,equip,tema:'Suportes',detalhe:`${r.suporte_desvio_pct>=0?'+':''}${fmt(r.suporte_desvio_pct,1)}% acima do esperado`});
    });
    return rows;
  },[data]);

  const filteredAlerts = React.useMemo(()=>{
    const q=search.toLowerCase();
    return alertRows.filter(r=>(!tema||r.tema===tema)&&(!q||(r.cliente.toLowerCase().includes(q)||r.contrato.toLowerCase().includes(q))));
  },[alertRows,search,tema]);

  const comEquip=filteredAlerts.filter(r=>r.equip);
  const semEquip=filteredAlerts.filter(r=>!r.equip);

  const addPc = () => {
    if(!pcCliente||!pcNota)return;
    const item={id:'pc_'+Date.now(),cliente:pcCliente,contrato:pcContrato,nota:pcNota,data:new Date().toLocaleDateString('pt-PT')};
    const next=[...pcList,item];
    setPcList(next);localStorage.setItem('pp_casos_particulares',JSON.stringify(next));
    setPcCliente('');setPcContrato('');setPcNota('');
  };
  const deletePc = id => {
    if(!confirm('Remover este registo?'))return;
    const next=pcList.filter(x=>x.id!==id);
    setPcList(next);localStorage.setItem('pp_casos_particulares',JSON.stringify(next));
  };

  const temaColor={'M²':'var(--red-700)','Prazo':'var(--red-700)','Tinta':'var(--pumpkin-700)','Suportes':'var(--pumpkin-700)'};
  const AlertTable = ({rows}) => (
    <div className="tbl-wrap">
      <table><thead><tr><th>Cliente</th><th>Contrato</th><th>Tema</th><th>Detalhe</th><th>Vendedor</th></tr></thead>
      <tbody>{rows.length===0?<tr><td colSpan={5} style={{textAlign:'center',padding:24,color:'var(--fg-3)',fontSize:12}}>Nenhum caso identificado.</td></tr>:rows.map((r,i)=><tr key={i}><td><div className="cname" title={r.cliente}>{r.cliente}</div></td><td><div className="ccode">{r.contrato}</div></td><td><span className="badge b-warn" style={{color:temaColor[r.tema]}}>{r.tema}</span></td><td style={{fontSize:11,color:'var(--fg-3)'}}>{r.detalhe}</td><td style={{fontSize:11,color:'var(--fg-3)'}}>{shortV(r.vendedor)}</td></tr>)}</tbody>
      </table>
    </div>
  );

  return(
    <div className="pp-tab-content"><div className="page">
      <div className="card">
        <div className="card-title">Casos Identificados nas Comunicações <span className="ct">M² (défice face à média dos últimos 3 meses) / prazo excedido / desvios de tinta e suportes</span></div>
        <div className="frow" style={{marginBottom:8}}>
          <input type="text" value={search} onChange={e=>setSearch(e.target.value)} placeholder="Pesquisar cliente ou contrato…"/>
          <select value={tema} onChange={e=>setTema(e.target.value)}><option value="">Todos os temas</option><option value="M²">M² (Propor Negociação/Término)</option><option value="Prazo">Prazo (Contrato a Renegociar)</option><option value="Tinta">Tinta</option><option value="Suportes">Suportes</option></select>
        </div>
        <div className="frow" style={{marginBottom:12}}>
          <button className={`fb${equipView==='equip'?' on':''}`} onClick={()=>setEquipView('equip')}>Contratos com Equipamento <span style={{marginLeft:4,fontSize:10,fontWeight:700}}>({comEquip.length})</span></button>
          <button className={`fb${equipView==='sem'?' on':''}`} onClick={()=>setEquipView('sem')}>Contratos sem Equipamento <span style={{marginLeft:4,fontSize:10,fontWeight:700}}>({semEquip.length})</span></button>
        </div>
        {equipView==='equip'&&<AlertTable rows={comEquip}/>}
        {equipView==='sem'&&<AlertTable rows={semEquip}/>}
      </div>

      <div className="card" style={{marginTop:'var(--space-4)'}}>
        <div className="card-title">Casos Particulares <span className="ct">acordos e exceções registadas manualmente</span></div>
        <div id="alert-pc-list">
          {pcList.length===0?<p style={{fontSize:12,color:'var(--fg-3)',padding:'8px 0'}}>Nenhum caso particular registado.</p>:pcList.slice().reverse().map(item=><div key={item.id} className="alert-item pc"><div className="alert-body"><strong>{item.cliente}{item.contrato?' · '+item.contrato:''}</strong><span>{item.nota}</span><span style={{display:'block',marginTop:4,fontSize:10,color:'var(--fg-3)'}}>Registado em {item.data}</span></div><button className="pc-del" onClick={()=>deletePc(item.id)} title="Remover">×</button></div>)}
        </div>
        <div className="pc-form" style={{marginTop:12}}>
          <select value={pcCliente} onChange={e=>setPcCliente(e.target.value)}><option value="">Selecione o cliente…</option>{[...new Set(data.map(r=>r.cliente))].sort().map(c=><option key={c} value={c}>{c}</option>)}</select>
          <input type="text" value={pcContrato} onChange={e=>setPcContrato(e.target.value)} placeholder="Contrato (opcional)"/>
          <textarea value={pcNota} onChange={e=>setPcNota(e.target.value)} rows={2} placeholder="Descreva o acordo ou caso particular…"/>
          <button className="btn-primary" style={{alignSelf:'flex-start'}} onClick={addPc}>Adicionar registo</button>
        </div>
      </div>
    </div></div>
  );
};

/* ===== RentingTab ===== */
const RentingTab = ({data}) => {
  const [search, setSearch] = React.useState('');
  const [filter, setFilter] = React.useState('all');
  const [sortKey, setSortKey] = React.useState('deficitEuro');
  const [sortDir, setSortDir] = React.useState('asc');
  const [expanded, setExpanded] = React.useState({});
  const [rentingTypes, setRentingTypes] = React.useState(()=>{const m={};data.forEach(r=>{m[r.contrato]=r.tipo_renting||null;});return m;});
  const [rentingValores, setRentingValores] = React.useState(()=>{const m={};data.forEach(r=>{m[r.contrato]=r.renting_valor??null;});return m;});

  const rentingAll = React.useMemo(()=>{
    return data.filter(r=>(rentingTypes[r.contrato]||r.tipo_renting)==='Renting Interno').map(r=>{
      const rr={...r,tipo_renting:'Renting Interno',renting_valor:rentingValores[r.contrato]??null};
      return{...rr,...ppRentingAnalysis(rr)};
    });
  },[data,rentingTypes,rentingValores]);

  const filtered = React.useMemo(()=>{
    const q=search.toLowerCase();
    let arr=rentingAll.filter(r=>{
      if(filter==='cobre'&&r.status!=='COBRE')return false;
      if(filter==='naocobre'&&r.status!=='NAO_COBRE')return false;
      if(filter==='semvalor'&&r.status!=='SEM_VALOR')return false;
      if(q&&!r.cliente.toLowerCase().includes(q)&&!r.contrato.toLowerCase().includes(q))return false;
      return true;
    });
    arr.sort((a,b)=>{let va=a[sortKey],vb=b[sortKey];if(va==null)va=-Infinity;if(vb==null)vb=-Infinity;return sortDir==='asc'?(va<vb?-1:va>vb?1:0):(va>vb?-1:va<vb?1:0);});
    return arr;
  },[rentingAll,filter,search,sortKey,sortDir]);

  const badgeCount=rentingAll.filter(r=>r.status==='NAO_COBRE').length;
  const setRentingType=(contrato,value)=>{ppSetRentingType(contrato,value);setRentingTypes(prev=>({...prev,[contrato]:value||null}));};
  const setRentingValorState=(contrato,value)=>{const n=value===''?null:Number(value);ppSetRentingValor(contrato,n);setRentingValores(prev=>({...prev,[contrato]:n}));};

  return(
    <div className="pp-tab-content"><div className="page">
      {badgeCount>0&&<div style={{padding:'8px 14px',background:'color-mix(in srgb,var(--red-100) 60%,white)',borderRadius:8,marginBottom:12,fontSize:12,color:'var(--red-700)',fontWeight:600}}>{badgeCount} contrato{badgeCount>1?'s não cobrem':'não cobre'} a despesa de renting</div>}
      <div className="frow" style={{marginBottom:12}}>
        <input type="text" value={search} onChange={e=>setSearch(e.target.value)} placeholder="Pesquisar…"/>
        {[['all','Todos'],['cobre','Cobre'],['naocobre','Não cobre'],['semvalor','Sem valor']].map(([k,l])=><button key={k} className={`fb${filter===k?' on':''}${k==='naocobre'?' on-r':''&&filter===k}`} onClick={()=>setFilter(k)}>{l}</button>)}
        <span className="rc">{filtered.length} contratos Renting Interno</span>
      </div>
      <div className="tbl-wrap">
        <table><thead><tr>
          <th onClick={()=>{setSortDir(p=>sortKey==='cliente'?(p==='asc'?'desc':'asc'):'asc');setSortKey('cliente');}}>Cliente</th>
          <th>Contrato</th>
          <th style={{fontSize:11}}>Vendedor</th>
          <th style={{fontSize:11}}>Armazém</th>
          <th className="num" style={{fontSize:11}}>Valor Renting/mês</th>
          <th className="num" style={{fontSize:11}}>Meses</th>
          <th className="num" style={{fontSize:11}}>Custo Acumulado</th>
          <th className="num" style={{fontSize:11}}>Receita Acumulada</th>
          <th className="num" onClick={()=>{setSortDir(p=>sortKey==='deficitEuro'?(p==='asc'?'desc':'asc'):'asc');setSortKey('deficitEuro');}} style={{cursor:'pointer',fontSize:11}}>Défice/Superávit {sortKey==='deficitEuro'?sortDir==='asc'?'↑':'↓':''}</th>
          <th>Estado</th>
        </tr></thead>
        <tbody>{filtered.length===0?<tr><td colSpan={10} style={{textAlign:'center',padding:40,color:'var(--fg-3)',fontSize:12}}>Nenhum contrato Renting Interno{filter!=='all'?' com este filtro':''}.</td></tr>:filtered.map(r=>{
          const safeId=r.contrato.replace(/[^a-zA-Z0-9]/g,'_');
          const badge=r.status==='SEM_VALOR'?<span className="badge b-neutral">sem valor</span>:r.status==='COBRE'?<span className="badge b-acima">▲ Cobre</span>:<span className="badge b-abaixo">▼ Não cobre</span>;
          return(<React.Fragment key={r.contrato}>
            <tr style={{cursor:'pointer'}} onClick={()=>setExpanded(prev=>({...prev,[safeId]:!prev[safeId]}))}>
              <td><span style={{color:'var(--fg-3)',display:'inline-block',width:12,marginRight:4}}>{expanded[safeId]?'▾':'▸'}</span><div className="cname" style={{display:'inline-block'}} title={r.cliente}>{r.cliente}</div></td>
              <td><div className="ccode">{r.contrato}</div></td>
              <td style={{fontSize:11,color:'var(--fg-3)'}}>{shortV(r.vendedor)}</td>
              <td style={{fontSize:11,color:'var(--fg-3)'}}>{r.armazem||'—'}</td>
              <td className="num">{r.renting_valor!=null?fmtEFull(r.renting_valor):'—'}</td>
              <td className="num">{r.hasHistory?`${r.count}/${r.num_rendas}`:'—'}</td>
              <td className="num">{r.custoAcumulado!=null?fmtEFull(r.custoAcumulado):'—'}</td>
              <td className="num">{r.receitaAcumulada!=null?fmtEFull(r.receitaAcumulada):'—'}</td>
              <td className="num" style={{fontWeight:700,color:r.deficitEuro==null?'var(--fg-3)':r.deficitEuro>=0?'var(--green-700)':'var(--red-700)'}}>{r.deficitEuro!=null?(r.deficitEuro>=0?'+':'')+fmtEFull(r.deficitEuro):'—'}</td>
              <td>{badge}</td>
            </tr>
            {expanded[safeId]&&<tr><td colSpan={10} style={{background:'var(--grey-50)',padding:'12px 20px'}}>
              <div style={{marginBottom:8,fontSize:12}}>
                <strong>Tipo de Renting:</strong>{' '}
                <select value={rentingTypes[r.contrato]||''} onChange={e=>setRentingType(r.contrato,e.target.value)} style={{fontSize:12,marginLeft:8}}><option value="">Não definido</option><option value="Renting Interno">Renting Interno</option><option value="Renting Externo">Renting Externo</option></select>
                {' '}<strong style={{marginLeft:16}}>Valor mensal (€):</strong>{' '}
                <input type="number" value={rentingValores[r.contrato]??''} onChange={e=>setRentingValorState(r.contrato,e.target.value)} style={{width:100,fontSize:12,marginLeft:8}} placeholder="ex: 350"/>
              </div>
              {r.hasHistory&&<table style={{width:'100%',borderCollapse:'collapse',fontSize:11}}><thead><tr style={{borderBottom:'1px solid var(--border-1)'}}><th style={{textAlign:'left',padding:'3px 6px',color:'var(--fg-3)'}}>Mês</th><th style={{textAlign:'right',padding:'3px 6px',color:'var(--fg-3)'}}>m²</th><th style={{textAlign:'right',padding:'3px 6px',color:'var(--fg-3)'}}>Receita</th><th style={{textAlign:'right',padding:'3px 6px',color:'var(--fg-3)'}}>Margem</th></tr></thead><tbody>{r.rows.map(row=><tr key={row.mes} style={{borderBottom:'1px solid var(--border-1)'}}><td style={{padding:'3px 6px'}}>{row.mes}</td><td style={{textAlign:'right',padding:'3px 6px'}}>{fmt(row.m2)}</td><td style={{textAlign:'right',padding:'3px 6px'}}>{fmtEFull(row.receita)}</td><td style={{textAlign:'right',padding:'3px 6px',fontWeight:700,color:row.margem==null?'var(--fg-3)':row.margem>=0?'var(--green-700)':'var(--red-700)'}}>{row.margem==null?'—':(row.margem>=0?'+':'')+fmtEFull(row.margem)}</td></tr>)}</tbody></table>}
            </td></tr>}
          </React.Fragment>);
        })}</tbody>
        </table>
      </div>
    </div></div>
  );
};

/* ===== RepoTab ===== */
const RepoTab = ({data}) => {
  const [search, setSearch] = React.useState('');
  const [selected, setSelected] = React.useState(null);
  const [attachments, setAttachments] = React.useState({});

  const clientsSorted = React.useMemo(()=>[...new Set(data.map(r=>r.cliente))].sort(),[data]);
  const filteredClients = React.useMemo(()=>{const q=search.toLowerCase();return clientsSorted.filter(c=>c.toLowerCase().includes(q));},[clientsSorted,search]);

  const loadAttachment = async (contrato) => {
    const rec = await ppAttachGet(contrato);
    setAttachments(prev=>({...prev,[contrato]:rec||null}));
  };

  const selectClient = async (cliente) => {
    setSelected(cliente);
    const contratos = data.filter(r=>r.cliente===cliente);
    for(const r of contratos) loadAttachment(r.contrato);
  };

  const handleFile = (contrato, file) => {
    if(!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      ppAttachPut({contrato,filename:file.name,type:file.type,size:file.size,data:reader.result}).then(()=>loadAttachment(contrato));
    };
    reader.readAsArrayBuffer(file);
  };

  const removeAttach = async (contrato) => {
    if(!confirm('Remover o ficheiro anexado?'))return;
    await ppAttachDelete(contrato);
    setAttachments(prev=>({...prev,[contrato]:undefined}));
  };

  const viewAttach = (contrato) => {
    const rec=attachments[contrato];if(!rec)return;
    const blob=new Blob([rec.data],{type:rec.type||'application/pdf'});
    window.open(URL.createObjectURL(blob),'_blank');
  };

  const repoEstadoLabel = (estado) => {
    if(estado==='confiante')return{cls:'b-acima',label:'Confiante'};
    if(estado==='revisar')return{cls:'b-warn',label:'A confirmar'};
    if(estado==='ambiguo')return{cls:'b-warn',label:'Ambíguo'};
    return{cls:'b-abaixo',label:'Não encontrado'};
  };

  const selectedContratos = selected ? data.filter(r=>r.cliente===selected) : [];

  return(
    <div className="pp-tab-content" style={{overflow:'hidden'}}><div className="page" style={{padding:0,height:'100%',overflow:'hidden'}}>
      <div className="comm-layout">
        <div className="comm-sidebar">
          <div className="comm-sidebar-header"><h3>Clientes</h3><input type="text" value={search} onChange={e=>setSearch(e.target.value)} placeholder="Pesquisar cliente…"/></div>
          <div className="comm-client-list">
            {filteredClients.map(c=><div key={c} className={`comm-client-item${selected===c?' active':''}`} onClick={()=>selectClient(c)}><div><strong style={{fontSize:13}}>{c}</strong></div></div>)}
          </div>
        </div>
        <div className="comm-main">
          <div className="comm-toolbar"><h3 id="repo-toolbar-title">{selected||'Selecione um cliente para ver os contratos'}</h3></div>
          <div id="repo-cards" style={{padding:'16px 24px'}}>
            {selectedContratos.map(r=>{
              const entry=DOC_MATCH[r.contrato]||['nao_encontrado',[]];
              const est=repoEstadoLabel(entry[0]);
              const attach=attachments[r.contrato];
              return(<div key={r.contrato} className="pp-contract-card">
                <div className="pp-contract-header"><div><div className="pp-contract-code">{r.contrato}</div><div className="pp-contract-sub">{r.vendedor||''} · {r.armazem||''}</div></div><span className={`badge ${est.cls}`}>{est.label}</span></div>
                <div className="repo-contract-body">
                  <div>
                    <div className="repo-section-label">Documentos sugeridos (na pasta de contratos)</div>
                    {entry[1].length===0?<div className="repo-suggest-empty">Sem sugestão — não encontrado no arquivo.</div>:<div className="repo-suggest-list">{entry[1].map(([path,signed],idx)=>{const parts=path.split(/[\\/]/);const file=parts.pop();const folder=parts.join('/');return(<div key={idx} className={`repo-suggest-item${signed?' signed':''}`}><span className="repo-folder">{folder}/</span><span className="repo-file">{file}</span></div>);})}</div>}
                  </div>
                  <div>
                    <div className="repo-section-label">Ficheiro anexado</div>
                    {attach===undefined?<span style={{color:'var(--fg-3)',fontSize:12}}>A carregar…</span>:attach?<div className="pp-attach-file"><div className="pp-af-info"><span style={{fontWeight:600,fontSize:12}}>{attach.filename}</span><span style={{fontSize:11,color:'var(--fg-3)'}}>{Math.round(attach.size/1024)} KB</span></div><div style={{display:'flex',gap:6}}><button className="pp-btn-mini" onClick={()=>viewAttach(r.contrato)}>Ver</button><button className="pp-btn-mini" style={{color:'var(--red-700)'}} onClick={()=>removeAttach(r.contrato)}>Remover</button></div></div>:<div className="pp-attach-empty"><span>Nenhum ficheiro anexado</span><label className="pp-btn-mini" style={{cursor:'pointer'}}>Adicionar<input type="file" style={{display:'none'}} onChange={e=>handleFile(r.contrato,e.target.files[0])}/></label></div>}
                  </div>
                </div>
              </div>);
            })}
          </div>
        </div>
      </div>
    </div></div>
  );
};

/* ===== CommTab ===== */
const CommTab = ({data}) => {
  const [search, setSearch] = React.useState('');
  const [selected, setSelected] = React.useState(null);
  const [topic, setTopic] = React.useState('m2');
  const [sendEmail, setSendEmail] = React.useState('');
  const [draftEmail, setDraftEmail] = React.useState('');
  const [sending, setSending] = React.useState(false);
  const [sendResult, setSendResult] = React.useState(null); // null | 'ok' | 'error'
  const [showSendDialog, setShowSendDialog] = React.useState(false);
  const [commTab, setCommTab] = React.useState('send');
  const [emailLog, setEmailLog] = React.useState([]);
  const [logLoading, setLogLoading] = React.useState(false);
  const [selectedLog, setSelectedLog] = React.useState(null);
  const [sentClientes, setSentClientes] = React.useState(new Set());

  React.useEffect(()=>{
    fetch('/api/printplan/email-log').then(r=>r.ok?r.json():null).then(d=>{
      if(d) setSentClientes(new Set(d.map(x=>x.cliente)));
    }).catch(()=>{});
  },[]);

  const fetchLog = async (name) => {
    setLogLoading(true);
    try {
      const r = await fetch(`/api/printplan/email-log?cliente=${encodeURIComponent(name)}`);
      if(r.ok){ const d=await r.json(); setEmailLog(d); setSentClientes(prev=>{const n=new Set(prev);if(d.length)n.add(name);return n;}); }
    } catch {}
    setLogLoading(false);
  };

  const allClients = React.useMemo(()=>CLIENTS_LIST.filter(commNeedsComm),[]);
  const filtered = React.useMemo(()=>{const q=search.toLowerCase();return allClients.filter(c=>c.cliente.toLowerCase().includes(q));},[allClients,search]);

  const selectClient = async (name) => {
    setSelected(name);
    setCommTab('send');
    setEmailLog([]);
    setSelectedLog(null);
    setSendEmail('');
    setDraftEmail('');
    const c=CLIENTS_LIST.find(x=>x.cliente===name);
    if(c) setTopic(c.has_m2_issue?'m2':c.has_m2_sem_equip_issue?'m2sem':'consumo');
    fetchLog(name);
    const contract=(data||DATA).find(r=>r.cliente===name);
    if(contract?.num_cliente){
      try{
        const r=await fetch(`/api/printplan/client-email?num_cliente=${encodeURIComponent(contract.num_cliente)}`);
        if(r.ok){const d=await r.json();if(d.email)setDraftEmail(d.email);}
      }catch{}
    }
  };

  const emailHTML = React.useMemo(()=>{
    if(!selected) return '';
    return buildEmailHTML(selected,topic,data,draftEmail);
  },[selected,topic,data,draftEmail]);

  const copyEmail = () => {
    const zone=document.getElementById('email-print-zone');if(!zone)return;
    const text=zone.innerText;
    navigator.clipboard.writeText(text).then(()=>{alert('Email copiado para a área de transferência');}).catch(()=>{});
  };

  const downloadExtrato = () => {
    const contratos=(data||DATA).filter(r=>r.cliente===selected&&r.novo_pvp!=null);
    if(!contratos.length)return;
    const dateStr=new Date().toLocaleDateString('pt-PT',{day:'2-digit',month:'long',year:'numeric'});
    const fmtN=(n,d=0)=>typeof n==='number'?n.toLocaleString('pt-PT',{minimumFractionDigits:d,maximumFractionDigits:d}):String(n||'—');
    const sections=contratos.map(r=>{
      const monthly=(MONTHLY_REVENUE[r.contrato]||[]).slice().sort((a,b)=>a[0].localeCompare(b[0]));
      const espMes=r.num_rendas>0?r.limite_m2/r.num_rendas:0;
      const trs=monthly.map(([mes,m2])=>{const dev=m2-espMes;const pct=espMes>0?dev/espMes*100:0;const ok=dev>=0;return`<tr><td>${mes}</td><td class="num">${fmtN(m2)}</td><td class="num">${fmtN(Math.round(espMes))}</td><td class="num" style="color:${ok?'#1a7f4b':'#b53b3b'};font-weight:600">${ok?'+':''}${fmtN(Math.round(dev))}</td><td class="num" style="color:${ok?'#1a7f4b':'#b53b3b'}">${ok?'+':''}${pct.toFixed(1)}%</td></tr>`;}).join('');
      return`<div class="section"><div class="section-title">Contrato ${r.contrato} · ${r.data_inicio} → ${r.data_termino}</div><div class="section-meta">${r.rendas_decorridas}/${r.num_rendas} meses &nbsp;·&nbsp; ${fmtN(r.acumulado_m2)} m² impressos &nbsp;·&nbsp; Esperado: ${fmtN(r.esperado_m2)} m² &nbsp;·&nbsp; Défice: ${fmtN(Math.abs(r.m2_desvio))} m²</div><table><thead><tr><th>Mês</th><th class="num">M² Impressos</th><th class="num">M² Esperados/Mês</th><th class="num">Desvio (m²)</th><th class="num">Desvio (%)</th></tr></thead><tbody>${trs}</tbody><tfoot><tr><td><strong>Total</strong></td><td class="num"><strong>${fmtN(r.acumulado_m2)}</strong></td><td class="num"><strong>${fmtN(Math.round(espMes*r.rendas_decorridas))}</strong></td><td class="num" style="color:${r.m2_desvio>=0?'#1a7f4b':'#b53b3b'};font-weight:700">${r.m2_desvio>=0?'+':''}${fmtN(r.m2_desvio)}</td><td class="num" style="color:${r.m2_desvio>=0?'#1a7f4b':'#b53b3b'};font-weight:700">${r.m2_desvio>=0?'+':''}${r.m2_desvio_pct.toFixed(1)}%</td></tr></tfoot></table></div>`;
    }).join('');
    const html=`<!DOCTYPE html><html lang="pt"><head><meta charset="utf-8"><title>Extrato M² — ${selected}</title><style>*{box-sizing:border-box;margin:0;padding:0}body{font-family:system-ui,-apple-system,'Segoe UI',Helvetica,sans-serif;font-size:13px;color:#1a1a2e;background:#fff;padding:32px 40px}@media print{body{padding:20px 28px}.no-print{display:none!important}@page{margin:18mm 16mm;size:A4}}h1{font-size:20px;font-weight:700;color:#1a1a2e;margin-bottom:4px}.subtitle{font-size:12px;color:#888;margin-bottom:6px}.client{font-size:15px;font-weight:600;color:#1a1a2e;margin-bottom:20px}.divider{border:none;border-top:1px solid #e0e0e0;margin:20px 0}.section{margin-bottom:32px}.section-title{font-size:13px;font-weight:700;color:#1a1a2e;padding:8px 14px;background:#1a1a2e;color:#fff;border-radius:6px 6px 0 0}.section-meta{font-size:11.5px;color:#555;padding:7px 14px 10px;background:#f5f5f5;border:1px solid #e0e0e0;border-top:none;border-bottom:none}table{width:100%;border-collapse:collapse;border:1px solid #e0e0e0}thead tr{background:#f0f0f0}thead th{padding:9px 12px;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#444;text-align:left}tbody tr:nth-child(even){background:#fafafa}td{padding:7px 12px;border-bottom:1px solid #eeeeee;font-size:12.5px}tfoot tr{background:#f5f5f5;border-top:2px solid #ccc}tfoot td{padding:9px 12px;font-size:12.5px}.num{text-align:right;font-variant-numeric:tabular-nums}.footer{margin-top:32px;padding-top:14px;border-top:1px solid #e0e0e0;font-size:11px;color:#aaa;display:flex;justify-content:space-between}.no-print{margin-bottom:24px;display:flex;gap:10px}</style></head><body><div class="no-print"><button onclick="window.print()" style="padding:9px 20px;background:#1a1a2e;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">↓ Guardar como PDF</button><button onclick="window.close()" style="padding:9px 18px;background:#eee;color:#333;border:none;border-radius:6px;font-size:13px;cursor:pointer;font-family:inherit">Fechar</button></div><h1>Extrato Mensal de M² Impressos</h1><div class="subtitle">Justificativo do Recálculo de PVP · ${dateStr}</div><div class="client">${selected}</div><hr class="divider">${sections}<div class="footer"><span>Decal · PrintPlan</span><span>Gerado em ${dateStr}</span></div></body></html>`;
    const w=window.open('','_blank','width=920,height=720');
    if(w){w.document.write(html);w.document.close();setTimeout(()=>w.print(),500);}
  };
  window._ppDownloadExtrato = downloadExtrato;

  const sendExtrato = async () => {
    if(!selected||!sendEmail.trim())return;
    setSending(true);setSendResult(null);
    try{
      let resp;
      if(topic==='m2'){
        const contratos=(data||DATA).filter(r=>r.cliente===selected&&r.novo_pvp!=null);
        if(!contratos.length){setSending(false);return;}
        const payload={
          clienteNome:selected,
          emailTo:sendEmail.trim(),
          contratos:contratos.map(r=>({
            contrato:r.contrato,cliente:r.cliente,
            rendas_decorridas:r.rendas_decorridas,num_rendas:r.num_rendas,
            acumulado_m2:r.acumulado_m2,esperado_m2:r.esperado_m2,
            m2_desvio:r.m2_desvio,m2_desvio_pct:r.m2_desvio_pct,
            limite_m2:r.limite_m2,novo_pvp:r.novo_pvp,pvp:r.pvp,valor_em_atraso:r.valor_em_atraso,
            data_inicio:r.data_inicio,data_termino:r.data_termino,
            monthly:(MONTHLY_REVENUE[r.contrato]||[]).map(([mes,m2])=>[mes,m2]),
          })),
        };
        resp=await fetch('/api/printplan/send-extrato',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});
      } else {
        const monthStr=new Date().toLocaleDateString('pt-PT',{month:'long',year:'numeric'});
        const subject=topic==='m2sem'
          ?`Volume Abaixo do Mínimo Contratado — M² Impressos — ${monthStr}`
          :`Controlo de Consumos — Tintas/Suportes — ${monthStr}`;
        resp=await fetch('/api/printplan/send-email',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({clienteNome:selected,emailTo:sendEmail.trim(),subject,topic,htmlBody:buildEmailHTML(selected,topic,data,sendEmail.trim(),true)})});
      }
      if(resp.ok){setSendResult('ok');fetchLog(selected);}else{setSendResult('error');}
    }catch(e){setSendResult('error');}
    setSending(false);
  };

  const selectedClient = selected && CLIENTS_LIST.find(c=>c.cliente===selected);
  const hasM2 = selectedClient && selectedClient.has_m2_issue;
  const hasM2Sem = selectedClient && selectedClient.has_m2_sem_equip_issue;
  const hasConsumo = selectedClient && (selectedClient.has_tinta_issue||selectedClient.has_suporte_issue);

  return(
    <div className="pp-tab-content" style={{overflow:'hidden'}}><div style={{padding:0,height:'100%',overflow:'hidden',display:'flex',flexDirection:'column'}}>
      <div className="comm-layout" style={{flex:1,minHeight:0}}>
        <div className="comm-sidebar">
          <div className="comm-sidebar-header"><h3>Clientes</h3><input type="text" value={search} onChange={e=>setSearch(e.target.value)} placeholder="Pesquisar cliente…"/></div>
          <div className="comm-client-list">
            {filtered.map(c=>{
              const flags=[];
              if(c.has_m2_issue)flags.push(<span key="m2" className="badge b-exp" style={{fontSize:'9px',padding:'1px 6px'}}>M²</span>);
              if(c.has_m2_sem_equip_issue)flags.push(<span key="m2sem" className="badge b-exp" style={{fontSize:'9px',padding:'1px 6px'}}>M² Mínimo</span>);
              if(c.has_tinta_issue)flags.push(<span key="t" className="badge b-warn" style={{fontSize:'9px',padding:'1px 6px'}}>Tinta</span>);
              if(c.has_suporte_issue)flags.push(<span key="s" className="badge b-warn" style={{fontSize:'9px',padding:'1px 6px'}}>Suportes</span>);
              const wasSent=sentClientes.has(c.cliente);
              return(<div key={c.cliente} className={`comm-client-item${selected===c.cliente?' active':''}`} onClick={()=>selectClient(c.cliente)}>
                <div className="comm-client-dot" style={{background:wasSent?'#1a7f4b':'var(--pumpkin-700)',marginTop:5}}/>
                <div>
                  <div className="comm-client-name">{c.cliente}</div>
                  <div className="comm-client-meta">{c.n_contratos} contrato{c.n_contratos>1?'s':''} · {shortV(c.vendedor)}</div>
                  {flags.length>0&&<div className="comm-client-flags">{flags}</div>}
                  {wasSent&&<div style={{fontSize:'9px',color:'#1a7f4b',fontWeight:600,marginTop:2}}>✓ notificado</div>}
                </div>
              </div>);
            })}
          </div>
        </div>
        <div className="comm-main">
          <div className="comm-toolbar">
            <h3>{selected||'Selecione um cliente para gerar a comunicação'}</h3>
            {selected&&<><button className="btn-secondary" onClick={copyEmail}><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> Copiar</button><button className="btn-primary" onClick={()=>window.print()}>Exportar PDF</button><button className="btn btn-sm" style={{background:'var(--dd-primary-900,#3859D0)',color:'#fff',border:'none',display:'flex',alignItems:'center',gap:5}} onClick={()=>{setSendResult(null);setShowSendDialog(true);}}><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>{topic==='m2'?'Enviar Extrato':'Enviar Email'}</button></>}
          </div>
          {selected&&<div style={{display:'flex',gap:0,borderBottom:'1px solid var(--border-1)',padding:'0 20px'}}>
            <button className={`an-tab${commTab==='send'?' active':''}`} onClick={()=>setCommTab('send')} style={{marginBottom:'-1px'}}>A enviar</button>
            <button className={`an-tab${commTab==='sent'?' active':''}`} onClick={()=>setCommTab('sent')} style={{marginBottom:'-1px'}}>Enviados{emailLog.length>0?` (${emailLog.length})`:''}</button>
          </div>}
          {commTab==='send'&&<>
            {selected&&(hasM2||hasM2Sem||hasConsumo)&&[hasM2,hasM2Sem,hasConsumo].filter(Boolean).length>1&&<div style={{padding:'8px 20px',display:'flex',gap:8}}>
              {hasM2&&<button className={`an-tab${topic==='m2'?' active':''}`} onClick={()=>setTopic('m2')}>Défice de M²</button>}
              {hasM2Sem&&<button className={`an-tab${topic==='m2sem'?' active':''}`} onClick={()=>setTopic('m2sem')}>Volume Abaixo do Mínimo</button>}
              {hasConsumo&&<button className={`an-tab${topic==='consumo'?' active':''}`} onClick={()=>setTopic('consumo')}>Desvios de Consumo</button>}
            </div>}
            {showSendDialog&&<div style={{margin:'12px 20px',padding:'14px 16px',border:'1px solid var(--border-1)',borderRadius:'var(--radius-sm,8px)',background:'var(--bg-app)',display:'flex',flexDirection:'column',gap:10}}>
              <div style={{fontSize:13,fontWeight:600}}>Enviar Extrato por Email</div>
              <div style={{fontSize:12,color:'var(--fg-3)'}}>O PDF do extrato mensal de M² será gerado e enviado como anexo. Em produção, o email será preenchido automaticamente a partir do Primavera.</div>
              <div style={{display:'flex',gap:8,alignItems:'center',flexWrap:'wrap'}}>
                <input value={sendEmail} onChange={e=>setSendEmail(e.target.value)} placeholder="email@cliente.pt" style={{flex:1,minWidth:220,padding:'7px 10px',border:'1px solid var(--border-1)',borderRadius:6,fontSize:13,fontFamily:'var(--font-body,inherit)'}}/>
                {draftEmail&&!sendEmail&&<button className="btn btn-sm" style={{whiteSpace:'nowrap'}} onClick={()=>setSendEmail(draftEmail)}>↙ Preencher email</button>}
                <button className="btn btn-sm" style={{background:'var(--dd-primary-900,#3859D0)',color:'#fff',border:'none',opacity:sending?0.6:1,cursor:sending?'default':'pointer'}} disabled={sending||!sendEmail.trim()} onClick={sendExtrato}>{sending?'A enviar…':'Enviar'}</button>
                <button className="btn btn-sm" onClick={()=>{setShowSendDialog(false);setSendResult(null);}}>Cancelar</button>
              </div>
              {sendResult==='ok'&&<div style={{fontSize:12,color:'var(--green-700,#1a7f4b)',fontWeight:600}}>✓ Extrato enviado com sucesso para {sendEmail}</div>}
              {sendResult==='error'&&<div style={{fontSize:12,color:'var(--red-700,#b53b3b)',fontWeight:600}}>✗ Erro ao enviar — tenta de novo ou verifica o endereço</div>}
            </div>}
            <div className="email-preview-wrap" dangerouslySetInnerHTML={{__html:emailHTML||'<div class="empty-state"><svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg><h4>Nenhum cliente selecionado</h4><p>Escolha um cliente na lista para gerar o email de ponto de situação.</p></div>'}}/>
          </>}
          {commTab==='sent'&&<div style={{flex:1,display:'flex',minHeight:0,overflow:'hidden'}}>
            <div style={{width:280,flexShrink:0,borderRight:'1px solid var(--border-1)',overflowY:'auto',padding:'12px 0'}}>
              {logLoading&&<div style={{color:'var(--fg-3)',fontSize:13,padding:'20px 16px'}}>A carregar…</div>}
              {!logLoading&&emailLog.length===0&&<div style={{color:'var(--fg-3)',fontSize:13,textAlign:'center',padding:'48px 16px'}}>Nenhum email enviado.</div>}
              {!logLoading&&emailLog.map(e=>(
                <div key={e.id} onClick={()=>setSelectedLog(e)} style={{padding:'10px 16px',cursor:'pointer',borderBottom:'1px solid var(--border-1)',background:selectedLog?.id===e.id?'var(--bg-app)':'transparent'}}>
                  <div style={{fontWeight:600,fontSize:12.5,marginBottom:2}}>{e.email_to}</div>
                  <div style={{fontSize:11,color:'var(--fg-3)'}}>{new Date(e.sent_at).toLocaleString('pt-PT',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}</div>
                  <div style={{fontSize:11,color:'var(--fg-3)',marginTop:1}}>{e.n_contratos} contrato{e.n_contratos!==1?'s':''} · {e.sent_by}</div>
                </div>
              ))}
            </div>
            <div className="email-preview-wrap" style={{flex:1}} dangerouslySetInnerHTML={{__html:
              selectedLog?.email_html
                ? selectedLog.email_html
                : selectedLog
                  ? '<div class="empty-state"><svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg><p style=\"color:#888;font-size:13px\">Preview não disponível para este envio.<br><span style=\"font-size:11px\">Os próximos envios ficam guardados automaticamente.</span></p></div>'
                  : '<div class="empty-state"><svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg><p>Selecione um envio para ver o email.</p></div>'
            }}/>
          </div>}
        </div>
      </div>
    </div></div>
  );
};


/* ===== MassivoTab ===== */
const MassivoTab = ({data}) => {
  const [subTab, setSubTab] = React.useState('send');
  const [items, setItems] = React.useState(null);
  const [sending, setSending] = React.useState(false);
  const [sendResult, setSendResult] = React.useState(null);
  const [batches, setBatches] = React.useState(null);

  React.useEffect(()=>{
    const clients=CLIENTS_LIST.filter(commNeedsComm);
    const allItems=[];
    clients.forEach(c=>{
      if(c.has_m2_issue) allItems.push({cliente:c.cliente,topic:'m2',email:'',emailLoading:true,selected:false});
      if(c.has_m2_sem_equip_issue) allItems.push({cliente:c.cliente,topic:'m2sem',email:'',emailLoading:true,selected:false});
      if(c.has_tinta_issue||c.has_suporte_issue) allItems.push({cliente:c.cliente,topic:'consumo',email:'',emailLoading:true,selected:false});
    });
    setItems(allItems);
    clients.forEach(async(c)=>{
      const contract=(data||DATA).find(r=>r.cliente===c.cliente);
      let email='';
      if(contract?.num_cliente){
        try{const r=await fetch(`/api/printplan/client-email?num_cliente=${encodeURIComponent(contract.num_cliente)}`);if(r.ok){const d=await r.json();email=d.email||'';}}catch{}
      }
      setItems(prev=>prev.map(it=>it.cliente===c.cliente?{...it,email,emailLoading:false}:it));
    });
  },[]);

  React.useEffect(()=>{
    if(subTab!=='sent')return;
    setBatches(null);
    fetch('/api/printplan/email-log-massivo').then(r=>r.ok?r.json():null).then(d=>{if(d)setBatches(d);}).catch(()=>setBatches([]));
  },[subTab]);

  const topicLabel=t=>t==='m2'?'M²':t==='m2sem'?'M² Mínimo':'Consumo';
  const topicClass=t=>t==='m2'||t==='m2sem'?'b-exp':'b-warn';
  const getSubject=t=>{const m=new Date().toLocaleDateString('pt-PT',{month:'long',year:'numeric'});return t==='m2'?`Retificação de PVP — Défice de M² Impressos — ${m}`:t==='m2sem'?`Volume Abaixo do Mínimo Contratado — M² Impressos — ${m}`:`Controlo de Consumos — Tintas/Suportes — ${m}`;};

  const allSelected=items?.length>0&&items.every(i=>i.selected);
  const selectedWithEmail=(items||[]).filter(i=>i.selected&&i.email.trim());

  const sendSelected=async()=>{
    if(!selectedWithEmail.length)return;
    setSending(true);setSendResult(null);
    try{
      const payload={items:selectedWithEmail.map(i=>({clienteNome:i.cliente,emailTo:i.email.trim(),topic:i.topic,subject:getSubject(i.topic),htmlBody:buildEmailHTML(i.cliente,i.topic,data,i.email.trim(),true)}))};
      const r=await fetch('/api/printplan/send-massivo',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});
      if(r.ok){const d=await r.json();setSendResult(d);if(d.n_sent>0)setTimeout(()=>setSubTab('sent'),1500);}
      else setSendResult({error:true});
    }catch{setSendResult({error:true});}
    setSending(false);
  };

  return(
    <div className="pp-tab-content"><div className="page">
      <div style={{display:'flex',gap:0,borderBottom:'1px solid var(--border-1)',marginBottom:16}}>
        <button className={`an-tab${subTab==='send'?' active':''}`} onClick={()=>setSubTab('send')}>Enviar</button>
        <button className={`an-tab${subTab==='sent'?' active':''}`} onClick={()=>setSubTab('sent')}>Enviados</button>
      </div>

      {subTab==='send'&&<div className="card">
        <div className="card-title">Envio Massivo <span className="ct">envia todos os emails de comunicação de uma só vez</span></div>
        {!items&&<div style={{color:'var(--fg-3)',fontSize:13,padding:'12px 0'}}>A preparar lista…</div>}
        {items&&<>
          <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:12}}>
            <label style={{display:'flex',alignItems:'center',gap:6,fontSize:13,cursor:'pointer'}}>
              <input type="checkbox" checked={allSelected} onChange={()=>setItems(prev=>prev.map(i=>({...i,selected:!allSelected})))} style={{width:15,height:15}}/>
              Selecionar todos
            </label>
            <span style={{fontSize:12,color:'var(--fg-3)'}}>
              {selectedWithEmail.length} com email
              {items.filter(i=>i.selected&&!i.email.trim()).length>0&&` · ${items.filter(i=>i.selected&&!i.email.trim()).length} sem email (não enviados)`}
            </span>
          </div>
          <div className="tbl-wrap"><table>
            <thead><tr><th style={{width:36}}></th><th>Cliente</th><th>Tópico</th><th>Email</th></tr></thead>
            <tbody>{items.map((item,i)=>(
              <tr key={item.cliente} style={{opacity:item.selected?1:0.45}}>
                <td><input type="checkbox" checked={item.selected} onChange={()=>setItems(prev=>{const n=[...prev];n[i]={...n[i],selected:!n[i].selected};return n;})} style={{width:15,height:15}}/></td>
                <td><div className="cname" title={item.cliente}>{item.cliente}</div></td>
                <td><span className={`badge ${topicClass(item.topic)}`} style={{fontSize:10}}>{topicLabel(item.topic)}</span></td>
                <td>{item.emailLoading
                  ?<span style={{fontSize:11,color:'var(--fg-3)'}}>A carregar…</span>
                  :<input value={item.email} onChange={e=>setItems(prev=>{const n=[...prev];n[i]={...n[i],email:e.target.value};return n;})} placeholder="sem email" style={{width:'100%',padding:'4px 8px',border:'1px solid var(--border-1)',borderRadius:4,fontSize:12,fontFamily:'var(--font-body,inherit)',background:item.email?'':'color-mix(in oklch,var(--red-700) 8%,transparent)'}}/>
                }</td>
              </tr>
            ))}</tbody>
          </table></div>
          <div style={{marginTop:16,display:'flex',alignItems:'center',gap:12,flexWrap:'wrap'}}>
            <button className="btn-primary" disabled={sending||!selectedWithEmail.length} style={{opacity:sending||!selectedWithEmail.length?0.6:1}} onClick={sendSelected}>
              {sending?'A enviar…':`Enviar Selecionados (${selectedWithEmail.length})`}
            </button>
            {sendResult&&!sendResult.error&&<span style={{fontSize:13,color:'var(--green-700,#1a7f4b)',fontWeight:600}}>✓ {sendResult.n_sent} emails enviados{sendResult.n_errors>0?` · ${sendResult.n_errors} com erro`:''}</span>}
            {sendResult?.error&&<span style={{fontSize:13,color:'var(--red-700,#b53b3b)',fontWeight:600}}>✗ Erro ao enviar — tenta de novo</span>}
          </div>
        </>}
      </div>}

      {subTab==='sent'&&<div className="card">
        <div className="card-title">Histórico de Envios Massivos</div>
        {batches===null&&<div style={{color:'var(--fg-3)',fontSize:13,padding:'12px 0'}}>A carregar…</div>}
        {batches?.length===0&&<div style={{color:'var(--fg-3)',fontSize:13,textAlign:'center',padding:'32px 0'}}>Nenhum envio massivo registado.</div>}
        {batches?.length>0&&<div className="tbl-wrap"><table>
          <thead><tr><th>Data</th><th>Clientes</th><th>Enviado por</th></tr></thead>
          <tbody>{batches.map(b=>(
            <tr key={b.batch_id}>
              <td style={{fontSize:13}}>{new Date(b.sent_at).toLocaleString('pt-PT',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}</td>
              <td style={{fontSize:13,fontWeight:600}}>{b.n_clientes} cliente{b.n_clientes!==1?'s':''}</td>
              <td style={{fontSize:12,color:'var(--fg-3)'}}>{b.sent_by}</td>
            </tr>
          ))}</tbody>
        </table></div>}
      </div>}
    </div></div>
  );
};

/* ===== computeLiveFields — enrich live store data with computed fields ===== */
function computeLiveFields(contracts, monthly) {
  const today = new Date();
  return contracts.map(r => {
    // Parse data_termino (DD-MM-YYYY or YYYY-MM-DD)
    let diasTermino = null;
    const dtStr = r.data_termino;
    if (dtStr) {
      let d;
      if (/^\d{2}-\d{2}-\d{4}$/.test(dtStr)) {
        const [dd, mm, yyyy] = dtStr.split('-');
        d = new Date(+yyyy, +mm - 1, +dd);
      } else { d = new Date(dtStr); }
      if (!isNaN(d)) diasTermino = Math.round((d - today) / 864e5);
    }

    const pvp              = typeof r.pvp === 'number' ? r.pvp : (parseFloat(r.pvp) || 0);
    const limite_m2        = Number(r.limite_m2)        || 0;
    const acumulado_m2     = Number(r.acumulado_m2)     || 0;
    const num_rendas       = Number(r.num_rendas)       || 0;
    const rendas_dec       = Number(r.rendas_decorridas) || 0;

    const prazo_excedido   = num_rendas > 0 && rendas_dec > num_rendas;
    const esperado_m2      = num_rendas > 0 ? Math.min(limite_m2, Math.round(limite_m2 / num_rendas * rendas_dec)) : 0;
    const m2_desvio        = Math.round(acumulado_m2 - esperado_m2);
    const m2_desvio_pct    = esperado_m2 > 0 ? Math.round(m2_desvio / esperado_m2 * 1000) / 10 : 0;
    const m2_status        = m2_desvio >= 0 ? 'ACIMA' : 'ABAIXO';
    const rec_pot          = Math.round(limite_m2  * pvp * 100) / 100;
    const rec_real         = Math.round(acumulado_m2 * pvp * 100) / 100;
    const rec_falta        = Math.max(0, Math.round((rec_pot - rec_real) * 100) / 100);
    const rec_pot_atual    = Math.round(esperado_m2 * pvp * 100) / 100;
    const perf_atual       = Math.round((rec_real - rec_pot_atual) * 100) / 100;
    const valor_em_atraso  = Math.max(0, -perf_atual);
    const m2_restante      = Math.max(0, limite_m2 - acumulado_m2);
    const m2_mensal        = num_rendas > 0 ? Math.round(limite_m2 / num_rendas * 10) / 10 : 0;
    const equip            = Number(r.equip) || 0;
    const novo_pvp         = (equip === 1 && pvp > 0 && valor_em_atraso > 0 && m2_restante > 0)
      ? Math.round((pvp + valor_em_atraso / m2_restante) * 100) / 100 : null;

    // Last consumption date + m2 from monthly
    const mRows = (monthly[r.contrato] || []).slice().sort((a, b) => a[0] > b[0] ? -1 : 1);
    const data_consumo       = mRows[0]?.[0] || null;
    const m2_impressos_ultimo = mRows[0]?.[1] || 0;
    const perf_mensal_pct    = m2_mensal > 0 ? Math.round(m2_impressos_ultimo / m2_mensal * 1000) / 10 : 0;

    // Tinta: avg ml/m² from monthly [3]=total_ml, [1]=m2
    const tintas_flag = Number(r.tintas_flag) || 0;
    const tintaRows = (monthly[r.contrato] || []).filter(d => d[1] > 0 && d[3] != null && d[3] > 0);
    const tinta_tem_dados = tintaRows.length > 0 && tintas_flag === 1;
    const tinta_ml_m2_real = tinta_tem_dados
      ? Math.round(tintaRows.reduce((s, d) => s + d[3] / d[1], 0) / tintaRows.length * 10) / 10 : null;
    const tinta_ml_m2_esp  = r.tinta_ml_m2_esp || null;
    const tinta_desvio_pct = (tinta_ml_m2_real != null && tinta_ml_m2_esp != null && tinta_ml_m2_esp > 0)
      ? Math.round((tinta_ml_m2_real - tinta_ml_m2_esp) / tinta_ml_m2_esp * 1000) / 10 : null;
    const tinta_ml_zero    = tinta_tem_dados && tinta_ml_m2_real === 0;

    // Suporte: from monthly [4]=mt_imp, [5]=mt_lin_contracted
    const sup_imp = Number(r.sup_imp) || 0;
    const supRows = (monthly[r.contrato] || []).filter(d => d[4] != null && d[5] != null && d[5] > 0);
    const suporte_tem_dados     = supRows.length > 0 && sup_imp === 1;
    const suporte_consumo_mt    = suporte_tem_dados ? Math.round(supRows.reduce((s,d)=>s+Number(d[5]),0)*10)/10 : null;
    const suporte_impresso_mt   = suporte_tem_dados ? Math.round(supRows.reduce((s,d)=>s+Number(d[4]),0)*10)/10 : null;
    const suporte_desvio_pct    = (suporte_consumo_mt && suporte_consumo_mt > 0)
      ? Math.round((suporte_impresso_mt - suporte_consumo_mt) / suporte_consumo_mt * 1000) / 10 : null;

    return {
      ...r,
      equip, sup_imp, tintas_flag, pvp, limite_m2, acumulado_m2,
      num_rendas, rendas_decorridas: rendas_dec,
      bloqueado: Boolean(r.bloqueado),
      dias_termino: diasTermino,
      expirado: diasTermino !== null && diasTermino < 0,
      prazo_excedido,
      esperado_m2, m2_desvio, m2_desvio_pct, m2_status,
      rec_pot, rec_real, rec_falta, rec_pot_atual, perf_atual,
      valor_em_atraso, m2_restante, m2_mensal, novo_pvp,
      data_consumo, m2_impressos_ultimo, perf_mensal_pct,
      tinta_tem_dados, tinta_ml_m2_real, tinta_ml_m2_esp, tinta_desvio_pct,
      ml_real: tinta_ml_m2_real, ml_esp: tinta_ml_m2_esp,
      ml_desvio: tinta_ml_m2_real != null && tinta_ml_m2_esp != null ? tinta_ml_m2_real - tinta_ml_m2_esp : null,
      ml_desvio_pct: tinta_desvio_pct, ml_zero: tinta_ml_zero,
      suporte_tem_dados, suporte_consumo_mt, suporte_impresso_mt, suporte_desvio_pct,
    };
  });
}

/* ===== PrintPlanScreen (main) ===== */
const PrintPlanScreen = () => {
  const [tab, setTab] = React.useState('overview');
  const [analysisRow, setAnalysisRow] = React.useState(null);
  const [dataVersion, setDataVersion] = React.useState(0);
  const [uploadState, setUploadState] = React.useState(null);
  const [lastUpload, setLastUpload] = React.useState(null);
  const [priSyncState, setPriSyncState] = React.useState(null);
  const [priLastSync, setPriLastSync] = React.useState(null);
  const uploadRef = React.useRef();

  React.useEffect(() => {
    fetch('/api/printplan/store-data')
      .then(r => r.json())
      .then(d => {
        console.log('[PP] store-data:', d.empty ? 'empty' : (d.contracts?.length + ' contracts'));
        if (!d.empty && d.contracts?.length) {
          window.DATA = computeLiveFields(d.contracts, d.monthly || {});
          window.MONTHLY_REVENUE = d.monthly || {};
          // Rebuild CLIENTS_LIST from live data
          const cliMap = {};
          window.DATA.forEach(r => { if (!cliMap[r.cliente]) cliMap[r.cliente] = { cliente: r.cliente, contratos: 0 }; cliMap[r.cliente].contratos++; });
          window.CLIENTS_LIST = Object.values(cliMap);
          // Recalculate rendas + flags without ppInitData (avoids side effects during render)
          window.DATA.forEach(r => {
            const cal = calcRendasCalendario(r.data_inicio);
            if (cal != null) {
              r.rendas_decorridas = cal;
              const m2m = r.num_rendas ? r.limite_m2 / r.num_rendas : 0;
              const esp = Math.round(m2m * cal);
              r.esperado_m2 = esp; r.m2_desvio = r.acumulado_m2 - esp;
              r.m2_desvio_pct = esp > 0 ? Math.round((r.m2_desvio / esp * 100) * 10) / 10 : 0;
              r.m2_status = r.m2_desvio >= 0 ? 'ACIMA' : 'ABAIXO';
              r.m2_mensal = m2m;
            }
          });
          // Flags for CLIENTS_LIST
          const byC = {}; window.DATA.forEach(r => { (byC[r.cliente] = byC[r.cliente] || []).push(r); });
          window.CLIENTS_LIST.forEach(c => {
            const ct = byC[c.cliente] || [];
            c.has_m2_issue = ct.some(r => r.novo_pvp != null);
            c.has_m2_sem_equip_issue = ct.some(commM2ContractSemEquip);
            c.has_tinta_issue = ct.some(r => r.tinta_tem_dados && (desvioColor(r.tinta_desvio_pct, 1) === 'var(--red-700)' || r.tinta_ml_m2_real === 0));
            c.has_suporte_issue = ct.some(r => r.suporte_tem_dados && desvioColor(r.suporte_desvio_pct, 1) === 'var(--red-700)');
          });
          console.log('[PP] Live data loaded:', window.DATA.length, 'contracts,', window.CLIENTS_LIST.length, 'clients');
          setLastUpload(d.lastUpload);
          setDataVersion(v => v + 1);
        }
      })
      .catch(e => console.error('[PP] store-data fetch error:', e));
  }, []);

  const handleUpload = async (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    e.target.value = '';
    setUploadState('uploading');
    try {
      const fd = new FormData();
      fd.append('file', file);
      const r = await fetch('/api/printplan/upload-store', { method: 'POST', body: fd });
      const d = await r.json();
      if (r.ok && d.ok) {
        const rd = await fetch('/api/printplan/store-data').then(x => x.json());
        if (!rd.empty) {
          window.DATA = computeLiveFields(rd.contracts, rd.monthly || {});
          window.MONTHLY_REVENUE = rd.monthly || {};
          setLastUpload(rd.lastUpload);
          setDataVersion(v => v + 1);
        }
        setUploadState('ok:' + d.contracts);
        setTimeout(() => setUploadState(null), 4000);
      } else {
        setUploadState('error');
        setTimeout(() => setUploadState(null), 4000);
      }
    } catch {
      setUploadState('error');
      setTimeout(() => setUploadState(null), 4000);
    }
  };

  const handlePriSync = async () => {
    setPriSyncState('syncing');
    try {
      const r = await fetch('/api/printplan/primavera-data');
      const d = await r.json();
      if (r.ok && d.inventario) {
        window.INVENTARIO_DATA    = d.inventario;
        window.INVENTARIO_CONSUMO = d.consumo || {};
        setPriLastSync(d.lastSync);
        setDataVersion(v => v + 1);
        setPriSyncState('ok');
        setTimeout(() => setPriSyncState(null), 4000);
      } else {
        setPriSyncState('error');
        setTimeout(() => setPriSyncState(null), 4000);
      }
    } catch {
      setPriSyncState('error');
      setTimeout(() => setPriSyncState(null), 4000);
    }
  };

  const TABS = [
    {k:'overview',l:'Overview'},
    {k:'m2',l:'Dashboard M²'},
    {k:'alertas',l:()=>{const n=DATA.filter(r=>r.expirado||r.novo_pvp!=null).length;return<>Alertas{n>0&&<span className="tab-pill">{n}</span>}</>;}},
    {k:'repo',l:'Repositório de Contratos'},
    {k:'comunicacao',l:<><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{marginRight:5}}><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>Comunicação</>},
    {k:'massivo',l:'Envio Massivo'},
    {k:'snapshot',l:'Snapshot'},
  ];

  const uploadBtnLabel = uploadState === 'uploading' ? 'A importar…'
    : uploadState === 'error' ? '✗ Erro'
    : uploadState?.startsWith('ok:') ? `✓ ${uploadState.slice(3)} contratos`
    : '⬆ Upload dados Store';
  const priBtnLabel = priSyncState === 'syncing' ? 'A sincronizar…'
    : priSyncState === 'error' ? '✗ Erro'
    : priSyncState === 'ok' ? '✓ Sincronizado'
    : '↻ Inventário Primavera';

  const isLive = !!lastUpload;

  return(
    <div className="pp-root">
      <div className="pp-tabnav">
        <div className="topbar-logo" style={{padding:'0 16px 0 0',display:'flex',alignItems:'center'}}>
          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" style={{marginRight:8}}><rect width="24" height="24" rx="6" fill="#3859D0"/><path d="M6 8h8a4 4 0 0 1 0 8H6V8Z" fill="#fff" opacity=".9"/><path d="M6 12h6" stroke="#fff" strokeWidth="2" strokeLinecap="round"/></svg>
          <span style={{color:'var(--fg-1)',fontWeight:700,fontSize:13,fontFamily:'var(--font-sans)'}}>PrintPlan</span>
        </div>
        {TABS.map(t=><button key={t.k} className={`tab-btn${tab===t.k?' active':''}`} onClick={()=>setTab(t.k)}>{typeof t.l==='function'?t.l():t.l}</button>)}
        <div style={{marginLeft:'auto',display:'flex',alignItems:'center',gap:10,paddingRight:16}}>
          {isLive
            ? <span style={{fontSize:10,color:'var(--fg-3)',fontFamily:'var(--font-mono)',whiteSpace:'nowrap'}}>Store {new Date(lastUpload).toLocaleDateString('pt-PT',{day:'2-digit',month:'short',year:'2-digit'})}</span>
            : <span style={{fontSize:10,color:'#b45309',background:'#fef3c7',border:'1px solid #fde68a',padding:'2px 8px',borderRadius:4,fontFamily:'var(--font-mono)',fontWeight:600}}>DEMO</span>
          }
          <button
            className="btn btn-xs"
            style={priSyncState==='ok' ? {color:'#1a7f4b',borderColor:'#bbf7d0'} : priSyncState==='error' ? {color:'#b53b3b'} : {}}
            disabled={priSyncState==='syncing'}
            onClick={handlePriSync}
          >{priBtnLabel}{priLastSync&&<span style={{marginLeft:6,fontSize:9,color:'var(--fg-3)',fontFamily:'var(--font-mono)'}}>  {new Date(priLastSync).toLocaleTimeString('pt-PT',{hour:'2-digit',minute:'2-digit'})}</span>}</button>
          <input ref={uploadRef} type="file" accept=".xlsx,.xls" style={{display:'none'}} onChange={handleUpload}/>
          <button
            className="btn btn-xs"
            style={uploadState?.startsWith('ok:') ? {color:'#1a7f4b',borderColor:'#bbf7d0'} : uploadState==='error' ? {color:'#b53b3b'} : {}}
            disabled={uploadState==='uploading'}
            onClick={()=>uploadRef.current?.click()}
          >{uploadBtnLabel}</button>
        </div>
      </div>
      {tab==='overview'&&<OverviewTab key={dataVersion} data={DATA}/>}
      {tab==='m2'&&<M2Tab key={dataVersion} data={DATA} onOpenAnalysis={r=>setAnalysisRow(r)}/>}
      {tab==='alertas'&&<AlertasTab key={dataVersion} data={DATA}/>}
      {tab==='repo'&&<RepoTab key={dataVersion} data={DATA}/>}
      {tab==='comunicacao'&&<CommTab key={dataVersion} data={DATA}/>}
      {tab==='massivo'&&<MassivoTab key={dataVersion} data={DATA}/>}
      {tab==='snapshot'&&<SnapshotTab/>}
      {analysisRow&&<AnalysisModal row={analysisRow} onClose={()=>setAnalysisRow(null)}/>}
    </div>
  );
};

/* ===== SnapshotTab ===== */
const SnapshotTab = () => {
  const [info, setInfo] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [dlState, setDlState] = React.useState(null); // null | 'loading' | 'ok' | 'error'

  React.useEffect(() => {
    fetch('/api/printplan/snapshot/fichas-tecnicas/info')
      .then(r => r.ok ? r.json() : null)
      .then(d => { setInfo(d); setLoading(false); })
      .catch(() => setLoading(false));
  }, []);

  const handleDownload = async () => {
    setDlState('loading');
    try {
      const r = await fetch('/api/printplan/snapshot/fichas-tecnicas.csv');
      if (!r.ok) throw new Error();
      const blob = await r.blob();
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `fichas_tecnicas_${new Date().toISOString().slice(0,10)}.csv`;
      a.click();
      URL.revokeObjectURL(url);
      setDlState('ok');
      setTimeout(() => setDlState(null), 3000);
    } catch {
      setDlState('error');
      setTimeout(() => setDlState(null), 3000);
    }
  };

  return (
    <div className="pp-tab-content scrollbar" style={{padding:'28px 32px',overflowY:'auto'}}>
      <div className="font-mono" style={{fontSize:11,color:'var(--fg-3)',letterSpacing:'0.08em',textTransform:'uppercase'}}>PrintPlan · Snapshot</div>
      <h2 className="font-display" style={{margin:'6px 0 0',fontSize:22,fontWeight:500}}>Fichas Técnicas BOM</h2>
      <p style={{fontSize:13,color:'var(--fg-2)',marginTop:8,maxWidth:560,lineHeight:1.6}}>
        Exportação diária das fichas técnicas do Primavera (GPR_ArtigoComponentes) — estrutura PA → PI → MP.
        Actualizado automaticamente às 04:00 através da ligação ao servidor Primavera.
      </p>
      <div className="card" style={{marginTop:24,padding:'16px 20px',maxWidth:520,display:'flex',alignItems:'center',justifyContent:'space-between',gap:16}}>
        <div>
          <div style={{fontSize:13,fontWeight:600}}>fichas_tecnicas.csv</div>
          {loading
            ? <div style={{fontSize:11,color:'var(--fg-3)',marginTop:3}}>A carregar…</div>
            : info
              ? <div style={{fontSize:11,color:'var(--fg-3)',marginTop:3,fontFamily:'var(--font-mono)'}}>
                  {info.rows?.toLocaleString('pt-PT')} registos · actualizado {info.updated_at ? new Date(info.updated_at).toLocaleDateString('pt-PT',{day:'2-digit',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}) : '—'}
                </div>
              : <div style={{fontSize:11,color:'var(--fg-3)',marginTop:3}}>Sem dados disponíveis</div>
          }
        </div>
        <button
          className="btn btn-xs btn-primary"
          style={dlState==='ok'?{background:'#1a7f4b',borderColor:'#1a7f4b'}:dlState==='error'?{background:'#b53b3b',borderColor:'#b53b3b'}:{}}
          disabled={dlState==='loading' || loading || !info?.rows}
          onClick={handleDownload}
        >
          {dlState==='loading' ? 'A descarregar…' : dlState==='ok' ? '✓ Descarregado' : dlState==='error' ? '✗ Erro' : '⬇ Download CSV'}
        </button>
      </div>
      <div style={{marginTop:20,maxWidth:520}}>
        <div style={{fontSize:11,color:'var(--fg-3)',fontWeight:600,letterSpacing:'0.05em',textTransform:'uppercase',marginBottom:8}}>Colunas exportadas</div>
        <div style={{display:'flex',flexWrap:'wrap',gap:'4px 8px'}}>
          {['pa_codigo','pa_descricao','pa_marca','pa_familia','pi_codigo','pi_descricao','pi_consumo','pi_consumopor','pi_consumounitario','pi_unidade','mp_codigo','mp_descricao','mp_marca','mp_consumo','mp_consumopor','mp_consumounitario','mp_unidade'].map(c=>(
            <span key={c} style={{fontSize:10.5,fontFamily:'var(--font-mono)',background:'var(--bg-app)',border:'1px solid var(--border-1)',borderRadius:4,padding:'2px 7px',color:'var(--fg-2)'}}>{c}</span>
          ))}
        </div>
      </div>
    </div>
  );
};

window.PrintPlanScreen = PrintPlanScreen;
