/* =============================================================================
   screen_comercial_rpc_consumiveis_es.jsx — RPC Consumíveis (Decal & Biond)
   =============================================================================
   Tabs:
     1. Mapa RPC         — grelha A/B/C/D (carteira, família, SPIN, resultado)
     2. Show me the Money— facturação real mês a mês por comercial
     3. Forecast         — previsão de vendas (Fase 2)
     4. Preditividade    — fecho do mês (Fase 2)
     5. YTD Famílias     — mapa vendas YTD por família/artigo
     6. Media Sales AI   — sugestões AI (Fase 2)
     7. Sugestões AI     — acções de melhoria (Fase 2)
     8. Previsão AI      — previsão ML (Fase 3)

   Fase 1 (actual): Bloco D com dados reais Primavera.
   Blocos A, B, C, Encomendas disponíveis a partir da Fase 2 (após carteiras + SPIN).
   ============================================================================= */

// IIFE — protege variáveis internas do overwrite pelos ficheiros PT (scope Babel partilhado)
(function() {
// ─── Constants ─────────────────────────────────────────────────────────────────
const CONS_MESES    = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];
const CONS_ANO_DEF  = 2026;
const CONS_MES_FECH = new Date().getMonth(); // 0-based, avança automaticamente
const CONS_SUPS     = ['¹','²','³','⁴','⁵','⁶','⁷'];

const CONS_MARCAS   = ['Decal ES','Decal AD','Decal PA'];
const CONS_MARCA_EMP = {
  'Decal ES': 'DECAL_ES',
  'Decal AD': 'DECAL_AD',
  'Decal PA': 'DECAL_PA',
};
// Mapeamento para o tenant FM (GestorConnect)
const CONS_MARCA_FM = {
  'Decal ES': 'DIGES',
  'Decal AD': 'DIGES',
  'Decal PA': 'DIGES',
};

const CONS_TABS = [
  { id:'rpc',        label:'Mapa RPC' },
  { id:'money',      label:'Show me the Money' },
  { id:'forecast',   label:'Forecast' },
  { id:'predit',     label:'Preditividade' },
  { id:'ytd',        label:'YTD Famílias' },
  { id:'mediasales', label:'Media Sales AI' },
  { id:'sugestoes',  label:'Sugestões AI' },
  { id:'previsao',   label:'Previsão AI' },
];

const CONS_NOTES = [
  { n:1, text:'Cliente ativo = pelo menos 1 fatura nos últimos 90 dias. Carteira atribuída: lista fechada de clientes por comercial.' },
  { n:2, text:'SPIN Identificado: consumo anual estimado por família, registado no diagnóstico SPIN. Disponível na Fase 2 após formação da equipa no registo SPIN.' },
  { n:3, text:'Cobertura SPIN = SPIN identificado ativo (≤ 12 meses) ÷ objetivo anual BP. Substitui a Cobertura de Pipeline. Em rampa no 1.º ano.' },
  { n:4, text:'Previsão = baseline recorrente da carteira (média 6 meses × coef. sazonal) + potencial SPIN ponderado. Disponível na Fase 2.' },
  { n:5, text:'RPC Rating: Resultado 40% · Carteira 25% · SPIN & Novos 20% · Desenvolvimento 15%. Fase 1: apenas Bloco D contribui (rating parcial).' },
  { n:6, text:'Obj. proposto = Vendas recorrentes × (1+3%) + 30% do potencial não capturado. Calculado na Fase 3.' },
  { n:7, text:'Encomendas registadas pelo comercial: Confirmada (aberta no ERP) ou Potencial (estimada, ainda não aberta). Taxa de Concretização = % do potencial faturado.' },
];

// ─── Formatters ────────────────────────────────────────────────────────────────
const cE = (n, compact=true) => {
  if (n == null || isNaN(n)) return '—';
  const abs = Math.abs(n), s = n < 0 ? '−' : '';
  if (compact && abs >= 1000000) return s+(abs/1000000).toFixed(1).replace('.',',')+'M';
  if (compact && abs >= 1000)    { const k=abs/1000; return s+(k<10?parseFloat(k.toFixed(1)):Math.round(k))+'k'; }
  return s+new Intl.NumberFormat('pt-PT').format(Math.round(abs));
};
const cSign = n => n==null||isNaN(n) ? '—' : (n>=0?'+':'')+cE(n);
const cPct  = (v,t) => (t>0) ? (v/t*100).toFixed(1).replace('.',',')+'%' : '—';

// Map class string (p/n/a/m/b/f/fp) → inline style object
const cCls = (cls='') => {
  const s = {};
  if (cls.includes('n')) { s.color='#dc2626'; }
  else if (cls.includes('fp')) { s.color='#16a34a'; s.background='color-mix(in oklch,#3b82f6 8%,transparent)'; }
  else if (cls.includes('f')) { s.color='#3563c9'; s.background='color-mix(in oklch,#3b82f6 6%,transparent)'; }
  else if (cls.includes('p')) { s.color='#16a34a'; }
  else if (cls.includes('a')) { s.color='#d97706'; }
  else if (cls.includes('m')) { s.color='var(--dgd-fg-3)'; }
  if (cls.includes('b')) { s.fontWeight=700; }
  return s;
};

const parseCell = (v) => {
  if (Array.isArray(v)) return { val: v[0]||'—', cls: v[1]||'' };
  const s = (v == null || v === '') ? '—' : String(v);
  return { val: s, cls: s==='—' ? 'm' : '' };
};

// ─── Shared TD/TH base styles ─────────────────────────────────────────────────
const tdB = {
  padding:'4px 7px', textAlign:'right', fontSize:10.5,
  fontFamily:'var(--dgd-font-mono,monospace)', fontVariantNumeric:'tabular-nums',
  borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap',
};
const thB = { ...tdB, fontSize:9.5, fontWeight:600, letterSpacing:'0.06em', color:'var(--dgd-fg-3)', background:'var(--dgd-bg-surface)', borderBottom:'1px solid var(--dgd-border-1)' };

// ─── Hooks ─────────────────────────────────────────────────────────────────────

// Detecta as marcas consumíveis de um utilizador específico (personal view)
// Faz uma única query com todas as empresas e filtra pelo colaborador_id
const useMyConsMarcas = (userId, vendedorId) => {
  const [marcas, setMarcas] = React.useState(null);
  React.useEffect(() => {
    if (!userId && !vendedorId) { setMarcas(null); return; }
    const allEmpresas = Object.values(CONS_MARCA_EMP).join(',');
    fetch(`/api/rpc-es/comerciais?empresa=${allEmpresas}`)
      .then(r => r.ok ? r.json() : Promise.reject())
      .then(rows => {
        const myEmpresas = new Set(rows.filter(r =>
          (userId && (Number(r.colaborador_id) === Number(userId) || Number(r.manager_id) === Number(userId))) ||
          (vendedorId && r.vendedor === vendedorId)
        ).map(r => r.empresa));
        const found = CONS_MARCAS.filter(m => myEmpresas.has(CONS_MARCA_EMP[m]));
        setMarcas(found.length > 0 ? found : null);
      })
      .catch(() => setMarcas(null));
  }, [userId, vendedorId]);
  return marcas;
};

const useConsComerciais = (empresa) => {
  const [state, setState] = React.useState({ comerciais: null, loading: true });
  React.useEffect(() => {
    if (!empresa) { setState({ comerciais: null, loading: false }); return; }
    setState(s => ({ ...s, loading: true }));
    fetch(`/api/rpc-es/comerciais?empresa=${empresa}`)
      .then(r => r.ok ? r.json() : Promise.reject())
      .then(rows => setState({
        comerciais: rows.map(r => ({
          id: 'bd-'+r.colaborador_id+'-'+r.vendedor,
          nome: r.nome,
          vendedor: r.vendedor,
          gestor_vendedor_id: r.gestor_vendedor_id || null,
          empresa: r.empresa,
          foto_url: r.foto_url || null,
          seccao: r.seccao || 'Outros',
          manager_nome: r.manager_nome || null,
          manager_id: r.manager_id || null,
          colaborador_id: r.colaborador_id,
        })).sort((a,b) => a.nome.localeCompare(b.nome, 'pt')),
        loading: false,
      }))
      .catch(() => setState({ comerciais: null, loading: false }));
  }, [empresa]);
  return state;
};

const useConsYTD = (ano) => {
  const [state, setState] = React.useState({ byV: null, margemByV: null, ytdRows: null, loading: true });
  React.useEffect(() => {
    setState(s => ({ ...s, loading: true }));
    fetch(`/api/rpc-es/ytd?ano=${ano}`)
      .then(r => r.ok ? r.json() : Promise.reject())
      .then(data => {
        const byV = {}, margemByV = {};
        (data.mensal || []).forEach(r => {
          if (!byV[r.vendedor]) byV[r.vendedor] = new Array(12).fill(null);
          byV[r.vendedor][r.mes - 1] = parseFloat(r.valor) || 0;
        });
        (data.margemMensal || []).forEach(r => {
          if (!margemByV[r.vendedor]) margemByV[r.vendedor] = new Array(12).fill(null);
          margemByV[r.vendedor][r.mes - 1] = parseFloat(r.margem) || 0;
        });
        setState({ byV, margemByV, ytdRows: data.rows || [], loading: false });
      })
      .catch(() => setState({ byV: null, margemByV: null, ytdRows: null, loading: false }));
  }, [ano]);
  return state;
};

const useConsBP = (ano, refreshKey) => {
  const [bpMap, setBpMap] = React.useState({});
  React.useEffect(() => {
    fetch(`/api/rpc-es/bp-targets?ano=${ano}`)
      .then(r => r.ok ? r.json() : {})
      .then(d => setBpMap(d || {}))
      .catch(() => {});
  }, [ano, refreshKey]);
  return bpMap; // { vendedor: [12] }
};

const useConsObjetivos = (vendedor, ano) => {
  const [objs, setObjs] = React.useState({});
  React.useEffect(() => {
    if (!vendedor || !ano) { setObjs({}); return; }
    fetch(`/api/rpc/cons-objetivos?vendedor=${encodeURIComponent(vendedor)}&ano=${ano}`)
      .then(r => r.ok ? r.json() : {})
      .then(d => setObjs(d || {}))
      .catch(() => {});
  }, [vendedor, ano]);
  return [objs, setObjs];
};

const useConsFMPipeline = (fmEmpresa, ano) => {
  const [byVendedor, setByVendedor] = React.useState({});
  const [loading, setLoading] = React.useState(false);
  const [fromPostgres, setFromPostgres] = React.useState(false);
  React.useEffect(() => {
    if (!fmEmpresa) return;
    setLoading(true);
    fetch(`/api/rpc/fm-pipeline?empresa=${fmEmpresa}&ano=${ano}`)
      .then(r => r.ok ? r.json() : { byVendedor: {} })
      .then(d => { setByVendedor(d.byVendedor || {}); setFromPostgres(!!d.fromPostgres); setLoading(false); })
      .catch(() => { setByVendedor({}); setFromPostgres(false); setLoading(false); });
  }, [fmEmpresa, ano]);
  return { byVendedor, loading, fromPostgres };
};

const useConsCarteira = (ano) => {
  const [byVendedor, setByVendedor] = React.useState({});
  React.useEffect(() => {
    fetch(`/api/rpc-es/carteira?ano=${ano}`)
      .then(r => r.ok ? r.json() : { byVendedor: {} })
      .then(d => { console.log('[carteira] keys:', Object.keys(d.byVendedor||{}).length, Object.keys(d.byVendedor||{}).slice(0,5)); setByVendedor(d.byVendedor || {}); })
      .catch(e => console.error('[carteira] error:', e));
  }, [ano]);
  return { byVendedor };
};

const useConsSPIN = (vendedor, ano) => {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    if (!vendedor) { setData(null); return; }
    fetch(`/api/rpc/spin/aggregated?vendedor=${encodeURIComponent(vendedor)}&ano=${ano}`)
      .then(r => r.ok ? r.json() : null)
      .then(d => setData(d))
      .catch(() => setData(null));
  }, [vendedor, ano]);
  return data; // null = a carregar ou indisponível
};

const useConsEncomendas = (vendedor, ano) => {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    if (!vendedor) { setData(null); return; }
    fetch(`/api/rpc/encomendas/agregado?vendedor=${encodeURIComponent(vendedor)}&ano=${ano}`)
      .then(r => r.ok ? r.json() : null)
      .then(d => setData(d))
      .catch(() => setData(null));
  }, [vendedor, ano]);
  return data; // null = a carregar ou indisponível
};

const useConsF9 = (vendedor, ano) => {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    if (!vendedor) { setData(null); return; }
    fetch(`/api/rpc/f9?vendedor=${encodeURIComponent(vendedor)}&ano=${ano}`)
      .then(r => r.ok ? r.json() : null)
      .then(d => setData(d?.f9PerMes || null))
      .catch(() => setData(null));
  }, [vendedor, ano]);
  return data; // null = loading or unavailable; number[12] when ready
};

// ─── Info Tooltip (porta para document.body — escapa sticky/stacking) ──────────
const ConsInfoTip = ({ text }) => {
  const [pos, setPos] = React.useState(null);
  const ref = React.useRef(null);
  const handleEnter = () => {
    if (ref.current) {
      const r = ref.current.getBoundingClientRect();
      const flipDown = r.top < 160;
      setPos({ top: flipDown ? r.bottom + 8 : r.top - 8, left: r.left + r.width / 2, flipDown });
    }
  };
  const tooltip = pos ? ReactDOM.createPortal(
    <div style={{ position:'fixed', left:pos.left, top:pos.top,
      transform: pos.flipDown ? 'translate(-50%,0)' : 'translate(-50%,-100%)',
      zIndex:99999, background:'var(--dgd-bg-card,white)', border:'1px solid var(--dgd-border-1)',
      borderRadius:6, padding:'7px 10px', fontSize:11, color:'var(--dgd-fg-1)',
      whiteSpace:'normal', boxShadow:'0 4px 16px rgba(0,0,0,0.18)',
      lineHeight:1.5, width:240, pointerEvents:'none' }}>
      {text}
    </div>,
    document.body
  ) : null;
  return (
    <span style={{ position:'relative', display:'inline-block', marginLeft:3, marginRight:1, verticalAlign:'middle' }}>
      <span ref={ref}
        onMouseEnter={handleEnter}
        onMouseLeave={() => setPos(null)}
        style={{ cursor:'help', color:'var(--dgd-fg-3)', fontSize:9, fontWeight:700, border:'1px solid var(--dgd-border-1)', borderRadius:'50%', width:13, height:13, display:'inline-flex', alignItems:'center', justifyContent:'center', lineHeight:1, userSelect:'none' }}
      >?</span>
      {tooltip}
    </span>
  );
};

// ─── Cell Info Modal ───────────────────────────────────────────────────────────
const ConsInfoModal = ({ info, onClose }) => {
  const [drill, setDrill] = React.useState(null);
  const [drillLoading, setDrillLoading] = React.useState(false);
  const [carteiraRows, setCarteiraRows] = React.useState(null);
  const [opsRows, setOpsRows] = React.useState(null);
  const [opsLoading, setOpsLoading] = React.useState(false);
  const [carteiraLoading, setCarteiraLoading] = React.useState(false);
  const [f9Rows, setF9Rows] = React.useState(null);
  const [f9Loading, setF9Loading] = React.useState(false);
  const [encPotRows, setEncPotRows] = React.useState(null);
  const [encPotLoading, setEncPotLoading] = React.useState(false);
  const [drillView, setDrillView] = React.useState('artigo');
  const [docsRows, setDocsRows] = React.useState(null);
  const [docsLoading, setDocsLoading] = React.useState(false);
  const [expandedFamClientes, setExpandedFamClientes] = React.useState({});
  const [famClienteData, setFamClienteData] = React.useState({});

  React.useEffect(() => {
    const handler = e => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [onClose]);

  // Fetch drill-down when info has drillParams; use drillData directly if provided
  React.useEffect(() => {
    setDrillView('artigo');
    setDocsRows(null);
    if (!info) { setDrill(null); return; }
    if (info.drillData) { setDrill(info.drillData); setDrillLoading(false); return; }
    if (!info.drillParams) { setDrill(null); return; }
    setDrillLoading(true);
    const { vendedor, ano, mes } = info.drillParams;
    const mesQ = mes != null ? `&mes=${mes+1}` : '';
    fetch(`/api/rpc-es/ytd-detail?vendedor=${vendedor}&ano=${ano}${mesQ}`)
      .then(r => r.ok ? r.json() : [])
      .then(rows => { setDrill(rows); setDrillLoading(false); })
      .catch(() => { setDrill([]); setDrillLoading(false); });
  }, [info]);

  // Reset accordion on new modal
  React.useEffect(() => { setExpandedFamClientes({}); setFamClienteData({}); }, [info]);

  const toggleFamCliente = (cliente, vendedor, ano) => {
    const nowExpanded = !expandedFamClientes[cliente];
    setExpandedFamClientes(prev => ({ ...prev, [cliente]: nowExpanded }));
    if (nowExpanded && !famClienteData[cliente]) {
      setFamClienteData(d => ({ ...d, [cliente]: { loading: true, rows: [] } }));
      fetch(`/api/rpc-es/carteira-familias?vendedor=${encodeURIComponent(vendedor)}&ano=${ano}&cliente=${encodeURIComponent(cliente)}`)
        .then(r => r.ok ? r.json() : { rows: [] })
        .then(d => setFamClienteData(prev => ({ ...prev, [cliente]: { loading: false, rows: d.rows || [] } })))
        .catch(() => setFamClienteData(prev => ({ ...prev, [cliente]: { loading: false, rows: [] } })));
    }
  };

  // Fetch carteira client drill-down
  React.useEffect(() => {
    if (!info?.carteiraParams) { setCarteiraRows(null); return; }
    const { tipo, vendedor, ano, mes } = info.carteiraParams;
    setCarteiraLoading(true);
    if (tipo === 'spin_reunioes' || tipo === 'spin_potencial') {
      const mesParam = mes != null ? `&mes=${mes+1}` : '';
      fetch(`/api/rpc/spin/detail?vendedor=${encodeURIComponent(vendedor)}&ano=${ano}${mesParam}`)
        .then(r => r.ok ? r.json() : [])
        .then(rows => { setCarteiraRows(rows); setCarteiraLoading(false); })
        .catch(() => { setCarteiraRows([]); setCarteiraLoading(false); });
      return;
    }
    const mesQ = mes != null ? `&mes=${mes+1}` : '';
    fetch(`/api/rpc-es/carteira-detail?tipo=${tipo}&vendedor=${encodeURIComponent(vendedor)}&ano=${ano}${mesQ}`)
      .then(r => r.ok ? r.json() : { rows: [] })
      .then(d => { setCarteiraRows(d.rows || []); setCarteiraLoading(false); })
      .catch(() => { setCarteiraRows([]); setCarteiraLoading(false); });
  }, [info]);

  // Fetch encomendas potenciais detalhe por entidade
  React.useEffect(() => {
    if (!info?.encPotParams) { setEncPotRows(null); return; }
    const { vendedor, ano, mes } = info.encPotParams;
    setEncPotLoading(true);
    const mesQ = mes != null ? `&mes=${mes + 1}` : '';
    fetch(`/api/rpc/encomendas/detalhe?vendedor=${encodeURIComponent(vendedor)}&ano=${ano}${mesQ}`)
      .then(r => r.ok ? r.json() : [])
      .then(d => { setEncPotRows(d); setEncPotLoading(false); })
      .catch(() => { setEncPotRows([]); setEncPotLoading(false); });
  }, [info]);

  // Fetch f9 ECW client drill-down (Encomendas Confirmadas)
  React.useEffect(() => {
    if (!info?.f9Params) { setF9Rows(null); return; }
    const { vendedor, ano, mes } = info.f9Params;
    setF9Loading(true);
    fetch(`/api/rpc/f9/detail?vendedor=${encodeURIComponent(vendedor)}&ano=${ano}&mes=${mes+1}`)
      .then(r => r.ok ? r.json() : { rows: [] })
      .then(d => { setF9Rows(d.rows || []); setF9Loading(false); })
      .catch(() => { setF9Rows([]); setF9Loading(false); });
  }, [info]);

  // Fetch vendas/documentos when switching to 'entidade' view in drill-down
  React.useEffect(() => {
    if (!info?.drillParams || drillView !== 'entidade') { setDocsRows(null); setDocsLoading(false); return; }
    const { vendedor, ano, mes } = info.drillParams;
    if (mes == null) { setDocsRows([]); setDocsLoading(false); return; }
    setDocsLoading(true);
    fetch(`/api/rpc/vendas/documentos?vendedor=${encodeURIComponent(vendedor)}&ano=${ano}&mes=${mes+1}`)
      .then(r => r.ok ? r.json() : { rows: [] })
      .then(d => { setDocsRows(d.rows || []); setDocsLoading(false); })
      .catch(() => { setDocsRows([]); setDocsLoading(false); });
  }, [info, drillView]);

  // Fetch ops FM drill-down (OPTMKT / OPTCOM / OPMANAGERF9)
  React.useEffect(() => {
    if (!info?.opsParams) { setOpsRows(null); return; }
    const { tipo, vendedor, ano, mes, empresa } = info.opsParams;
    setOpsLoading(true);
    const mesQ = mes != null ? `&mes=${mes+1}` : `&toMes=${CONS_MES_FECH+1}`;
    fetch(`/api/rpc/ops-detail?tipo=${tipo}&vendedor=${encodeURIComponent(vendedor)}&ano=${ano}&empresa=${empresa}${mesQ}`)
      .then(r => r.ok ? r.json() : { ops: [] })
      .then(d => { setOpsRows(d.ops || []); setOpsLoading(false); })
      .catch(() => { setOpsRows([]); setOpsLoading(false); });
  }, [info]);

  if (!info) return null;
  const secColors = { 'Gestão de Carteira':'#3b82f6', 'Desenvolvimento por Família':'#8b5cf6', 'Prospeção SPIN & Novos Clientes':'#8b5cf6', 'Encomendas do Mês':'#f59e0b', 'Resultado':'#22c55e' };
  const accentColor = secColors[info.section] || '#64748b';
  const totalValor = drill ? drill.reduce((s,r) => s + parseFloat(r.valor||0), 0) : 0;

  return ReactDOM.createPortal(
    <>
      <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:2000, background:'rgba(0,0,0,0.45)', backdropFilter:'blur(2px)' }}></div>
      <div style={{ position:'fixed', top:'50%', left:'50%', transform:'translate(-50%,-50%)', zIndex:2001, width: info.drillParams ? 'min(820px,96vw)' : info.opsParams ? 'min(820px,96vw)' : info.carteiraParams ? 'min(680px,96vw)' : info.encPotParams ? 'min(560px,96vw)' : 'min(480px,94vw)', background:'var(--dgd-bg-card,white)', borderRadius:12, boxShadow:'0 24px 64px rgba(0,0,0,0.22)', border:'1px solid var(--dgd-border-1)', overflow:'hidden', display:'flex', flexDirection:'column', maxHeight:'85vh' }}>
        {/* Header */}
        <div style={{ padding:'14px 20px 12px', borderBottom:'1px solid var(--dgd-border-1)', display:'flex', alignItems:'center', gap:10, flexShrink:0 }}>
          <div style={{ fontFamily:'var(--dgd-font-mono,monospace)', fontSize:9, fontWeight:700, letterSpacing:'0.1em', textTransform:'uppercase', color:'white', background:`color-mix(in oklch,${accentColor} 80%,#1e293b)`, padding:'3px 8px', borderRadius:4, whiteSpace:'nowrap' }}>{info.section}</div>
          <div style={{ fontSize:13, fontWeight:600, color:'var(--dgd-fg-1)', flex:1 }}>{info.label}</div>
          <button onClick={onClose} style={{ background:'none', border:'none', cursor:'pointer', color:'var(--dgd-fg-3)', fontSize:20, lineHeight:1, padding:'0 4px', marginLeft:4 }}>×</button>
        </div>
        <div style={{ padding:'20px 24px', overflowY:'auto', flex:1 }}>
          {/* Valor seleccionado */}
          {info.val && info.val !== '—' && (
            <div style={{ marginBottom:16, padding:'10px 16px', borderRadius:8, background:'var(--dgd-bg-surface)', border:'1px solid var(--dgd-border-1)', display:'flex', alignItems:'baseline', gap:10 }}>
              <div style={{ fontSize:10, color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', letterSpacing:'0.08em', flexShrink:0 }}>{info.period}</div>
              <div style={{ fontSize:20, fontWeight:700, color:'var(--dgd-fg-1)', fontFamily:'var(--dgd-font-mono,monospace)', letterSpacing:'-0.01em' }}>{info.val}</div>
            </div>
          )}
          {/* Descrição */}
          {info.tip && (
            <div style={{ fontSize:12.5, color:'var(--dgd-fg-2)', lineHeight:1.65, marginBottom: info.formula ? 16 : 0 }}>
              {info.tip}
            </div>
          )}
          {/* Fórmula */}
          {info.formula && (
            <div style={{ marginTop:0, padding:'10px 14px', borderRadius:7, background:`color-mix(in oklch,${accentColor} 6%,transparent)`, border:`1px solid color-mix(in oklch,${accentColor} 22%,transparent)`, fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)', color:'var(--dgd-fg-1)', whiteSpace:'pre-line', lineHeight:1.7 }}>
              {info.formula}
            </div>
          )}
          {/* Fase placeholder */}
          {info.fase && (
            <div style={{ marginTop:14, padding:'8px 12px', borderRadius:6, background:'color-mix(in oklch,#f59e0b 7%,transparent)', border:'1px solid color-mix(in oklch,#f59e0b 25%,transparent)', fontSize:11, color:'#92400e', fontWeight:500 }}>
              ⏳ Disponível na {info.fase} — após integração de carteiras e SPIN.
            </div>
          )}
          {/* Carteira client drill-down */}
          {info.carteiraParams && (() => {
            const { tipo, vendedor, vendedorNome, ano } = info.carteiraParams;
            const anoAnt = ano - 1;
            const thS = { padding:'5px 10px', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap', fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)' };
            const tdS = { padding:'4px 10px', borderBottom:'1px solid var(--dgd-border-1)', fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)', whiteSpace:'nowrap' };
            const total = carteiraRows ? carteiraRows.reduce((s,r) => s+parseFloat(r.valor||r.valor_ano||r.valor_mes||0), 0) : 0;
            // ─── SPIN drill-down (different table layout) ─────────────────────
            if (tipo === 'spin_reunioes' || tipo === 'spin_potencial') {
              return (
                <div style={{ marginTop:20 }}>
                  <div style={{ fontSize:10, fontWeight:700, letterSpacing:'0.1em', textTransform:'uppercase', color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', marginBottom:8 }}>
                    {vendedorNome} · {info.period}
                  </div>
                  {carteiraLoading && <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>A carregar...</div>}
                  {!carteiraLoading && carteiraRows && carteiraRows.length === 0 && (
                    <div style={{ padding:'16px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>Sem dados para este período.</div>
                  )}
                  {!carteiraLoading && carteiraRows && carteiraRows.length > 0 && (
                    <div style={{ overflowX:'auto', borderRadius:7, border:'1px solid var(--dgd-border-1)' }}>
                      <table style={{ borderCollapse:'collapse', width:'100%', fontSize:12 }}>
                        <thead>
                          <tr style={{ background:'var(--dgd-bg-surface)' }}>
                            <th style={{ ...thS, textAlign:'left' }}>Data</th>
                            <th style={{ ...thS, textAlign:'left' }}>Cliente (id)</th>
                            <th style={{ ...thS, textAlign:'left' }}>Tipo</th>
                            <th style={{ ...thS, textAlign:'right' }}>Nº Famílias</th>
                            <th style={{ ...thS, textAlign:'right' }}>Potencial (€)</th>
                          </tr>
                        </thead>
                        <tbody>
                          {carteiraRows.map((r, i) => (
                            <tr key={i} style={{ background: i % 2 === 0 ? 'transparent' : 'var(--dgd-bg-surface)' }}>
                              <td style={{ ...tdS, fontFamily:'var(--dgd-font-mono,monospace)' }}>{String(r.data_diagnostico).slice(0,10)}</td>
                              <td style={{ ...tdS, color:'var(--dgd-fg-2)' }}>{r.id_entidade}</td>
                              <td style={{ ...tdS }}>{r.tipo_cliente}</td>
                              <td style={{ ...tdS, textAlign:'right' }}>{r.n_familias}</td>
                              <td style={{ ...tdS, textAlign:'right', fontWeight:600 }}>{cE(parseFloat(r.potencial_total || 0))}</td>
                            </tr>
                          ))}
                        </tbody>
                        <tfoot>
                          <tr style={{ background:'var(--dgd-bg-surface)' }}>
                            <td colSpan={4} style={{ ...tdS, fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>Total ({carteiraRows.length} diagnósticos)</td>
                            <td style={{ ...tdS, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(carteiraRows.reduce((s,r)=>s+parseFloat(r.potencial_total||0),0))}</td>
                          </tr>
                        </tfoot>
                      </table>
                    </div>
                  )}
                </div>
              );
            }
            return (
              <div style={{ marginTop:20 }}>
                <div style={{ fontSize:10, fontWeight:700, letterSpacing:'0.1em', textTransform:'uppercase', color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', marginBottom:8 }}>
                  {vendedorNome} · {info.period}
                </div>
                {carteiraLoading && <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>A carregar...</div>}
                {!carteiraLoading && carteiraRows && carteiraRows.length === 0 && (
                  <div style={{ padding:'16px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>Sem dados para este período.</div>
                )}
                {!carteiraLoading && carteiraRows && carteiraRows.length > 0 && (
                  <div style={{ overflowX:'auto', borderRadius:7, border:'1px solid var(--dgd-border-1)' }}>
                    <table style={{ borderCollapse:'collapse', width:'100%' }}>
                      <thead>
                        <tr style={{ background:'var(--dgd-bg-surface)' }}>
                          <th style={{ ...thS, textAlign:'left' }}>Cliente</th>
                          {tipo === 'ativos' && <><th style={{ ...thS, textAlign:'right' }}>Valor</th><th style={{ ...thS, textAlign:'right' }}>Fam.</th></>}
                          {tipo === 'risco' && <><th style={{ ...thS, textAlign:'right' }}>Val. {anoAnt}</th><th style={{ ...thS, textAlign:'right' }}>Últ. mês</th></>}
                          {tipo === 'retencao' && (() => { const mLbl = info.carteiraParams.mes != null ? CONS_MESES[info.carteiraParams.mes] + ' ' : ''; return <><th style={{ ...thS, textAlign:'right' }}>{mLbl}{anoAnt}</th><th style={{ ...thS, textAlign:'right' }}>{mLbl}{ano}</th><th style={{ ...thS, textAlign:'right' }}>Δ%</th></>; })()}
                          {tipo === 'novos' && info.carteiraParams.mes != null && <><th style={{ ...thS, textAlign:'right' }}>Val. mês</th><th style={{ ...thS, textAlign:'right' }}>Val. {ano}</th></>}
                          {tipo === 'novos' && info.carteiraParams.mes == null && <><th style={{ ...thS, textAlign:'right' }}>1.ª compra</th><th style={{ ...thS, textAlign:'right' }}>Val. {ano}</th></>}
                          {(tipo === 'vcart' || tipo === 'vnovos') && <th style={{ ...thS, textAlign:'right' }}>Valor</th>}
                          {tipo === 'familias' && <><th style={{ ...thS, textAlign:'right' }}>Nº Fam.</th><th style={{ ...thS, textAlign:'right' }}>Valor</th></>}
                        </tr>
                      </thead>
                      <tbody>
                        {carteiraRows.map((r, i) => {
                          const bg = i%2===0 ? 'transparent' : 'var(--dgd-bg-surface)';
                          const vAnt = parseFloat(r.valor_ant||0), vAno = parseFloat(r.valor_ano||0);
                          const deltaPct = vAnt > 0 ? ((vAno-vAnt)/vAnt*100) : null;
                          const isExpFam = tipo === 'familias' && !!expandedFamClientes[r.cliente];
                          const fd = isExpFam ? (famClienteData[r.cliente] || null) : null;
                          const famAccordion = isExpFam
                            ? (!fd || fd.loading
                                ? [<tr key="load"><td colSpan={3} style={{ ...tdS, color:'var(--dgd-fg-3)', fontStyle:'italic', paddingLeft:28 }}>a carregar...</td></tr>]
                                : fd.rows.map((f, fi) => (
                                    <tr key={fi} style={{ background:'color-mix(in oklch,#8b5cf6 4%,transparent)' }}>
                                      <td style={{ ...tdS, paddingLeft:28, color:'var(--dgd-fg-2)' }}>
                                        <span style={{ fontFamily:'var(--dgd-font-mono,monospace)', fontSize:10, color:'var(--dgd-fg-3)', marginRight:8 }}>{f.familia}</span>
                                        <span>{f.familia_nome || f.familia}</span>
                                      </td>
                                      <td style={{ ...tdS, textAlign:'right' }}></td>
                                      <td style={{ ...tdS, textAlign:'right', fontWeight:500 }}>{cE(parseFloat(f.valor||0))}</td>
                                    </tr>
                                  )))
                            : null;
                          return (
                            <React.Fragment key={i}>
                              <tr style={{ background: bg, cursor: tipo==='familias'?'pointer':'default' }}
                                onClick={tipo==='familias' ? () => toggleFamCliente(r.cliente, vendedor, ano) : undefined}>
                                <td style={{ ...tdS, color:'var(--dgd-fg-1)', maxWidth:200, overflow:'hidden', textOverflow:'ellipsis' }}>
                                  {tipo === 'familias' && <span style={{ display:'inline-block', width:12, marginRight:5, color:'var(--dgd-fg-3)', fontSize:9 }}>{isExpFam ? '▼' : '▶'}</span>}
                                  {r.cliente}
                                </td>
                                {tipo === 'ativos' && <>
                                  <td style={{ ...tdS, textAlign:'right', fontWeight:600 }}>{cE(parseFloat(r.valor||0))}</td>
                                  <td style={{ ...tdS, textAlign:'right', color:'var(--dgd-fg-3)' }}>{r.n_fam}</td>
                                </>}
                                {tipo === 'risco' && <>
                                  <td style={{ ...tdS, textAlign:'right', fontWeight:600 }}>{cE(parseFloat(r.valor_ant||0))}</td>
                                  <td style={{ ...tdS, textAlign:'right', color:'var(--dgd-fg-3)' }}>{CONS_MESES[(parseInt(r.ultimo_mes)||1)-1]}</td>
                                </>}
                                {tipo === 'retencao' && <>
                                  <td style={{ ...tdS, textAlign:'right', color:'var(--dgd-fg-3)' }}>{cE(vAnt)}</td>
                                  <td style={{ ...tdS, textAlign:'right', fontWeight:600 }}>{cE(vAno)}</td>
                                  <td style={{ ...tdS, textAlign:'right', color: deltaPct==null?'var(--dgd-fg-3)':deltaPct>=0?'#16a34a':'#dc2626' }}>
                                    {deltaPct != null ? (deltaPct>=0?'+':'')+deltaPct.toFixed(0)+'%' : '—'}
                                  </td>
                                </>}
                                {tipo === 'novos' && info.carteiraParams.mes != null && <>
                                  <td style={{ ...tdS, textAlign:'right', fontWeight:600 }}>{cE(parseFloat(r.valor_mes||0))}</td>
                                  <td style={{ ...tdS, textAlign:'right', color:'var(--dgd-fg-3)' }}>{cE(parseFloat(r.valor_ano||0))}</td>
                                </>}
                                {tipo === 'novos' && info.carteiraParams.mes == null && <>
                                  <td style={{ ...tdS, textAlign:'right', color:'var(--dgd-fg-3)' }}>{CONS_MESES[(parseInt(r.primeiro_mes)||1)-1]}</td>
                                  <td style={{ ...tdS, textAlign:'right', fontWeight:600 }}>{cE(parseFloat(r.valor_ano||0))}</td>
                                </>}
                                {(tipo === 'vcart' || tipo === 'vnovos') && (
                                  <td style={{ ...tdS, textAlign:'right', fontWeight:600 }}>{cE(parseFloat(r.valor||0))}</td>
                                )}
                                {tipo === 'familias' && <>
                                  <td style={{ ...tdS, textAlign:'right', fontWeight:600, color: parseInt(r.n_fam)>=3?'#16a34a':parseInt(r.n_fam)>=2?'#d97706':'var(--dgd-fg-2)' }}>{r.n_fam}</td>
                                  <td style={{ ...tdS, textAlign:'right' }}>{cE(parseFloat(r.valor||0))}</td>
                                </>}
                              </tr>
                              {famAccordion}
                            </React.Fragment>
                          );
                        })}
                      </tbody>
                      <tfoot>
                        <tr style={{ background:'var(--dgd-bg-surface)' }}>
                          <td style={{ ...tdS, fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>Total ({carteiraRows.length} clientes)</td>
                          {tipo === 'ativos' && <><td style={{ ...tdS, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(carteiraRows.reduce((s,r)=>s+parseFloat(r.valor||0),0))}</td><td style={{ ...tdS, borderTop:'2px solid var(--dgd-border-1)' }}></td></>}
                          {tipo === 'risco' && <><td style={{ ...tdS, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(carteiraRows.reduce((s,r)=>s+parseFloat(r.valor_ant||0),0))}</td><td style={{ ...tdS, borderTop:'2px solid var(--dgd-border-1)' }}></td></>}
                          {tipo === 'retencao' && <><td style={{ ...tdS, borderTop:'2px solid var(--dgd-border-1)', textAlign:'right' }}>{cE(carteiraRows.reduce((s,r)=>s+parseFloat(r.valor_ant||0),0))}</td><td style={{ ...tdS, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(carteiraRows.reduce((s,r)=>s+parseFloat(r.valor_ano||0),0))}</td><td style={{ ...tdS, borderTop:'2px solid var(--dgd-border-1)' }}></td></>}
                          {tipo === 'novos' && info.carteiraParams.mes != null && <><td style={{ ...tdS, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(carteiraRows.reduce((s,r)=>s+parseFloat(r.valor_mes||0),0))}</td><td style={{ ...tdS, borderTop:'2px solid var(--dgd-border-1)' }}></td></>}
                          {tipo === 'novos' && info.carteiraParams.mes == null && <><td style={{ ...tdS, borderTop:'2px solid var(--dgd-border-1)' }}></td><td style={{ ...tdS, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(carteiraRows.reduce((s,r)=>s+parseFloat(r.valor_ano||0),0))}</td></>}
                          {(tipo === 'vcart' || tipo === 'vnovos') && <td style={{ ...tdS, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(carteiraRows.reduce((s,r)=>s+parseFloat(r.valor||0),0))}</td>}
                          {tipo === 'familias' && <><td style={{ ...tdS, borderTop:'2px solid var(--dgd-border-1)' }}></td><td style={{ ...tdS, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(carteiraRows.reduce((s,r)=>s+parseFloat(r.valor||0),0))}</td></>}
                        </tr>
                      </tfoot>
                    </table>
                  </div>
                )}
              </div>
            );
          })()}
          {/* Drill-down table */}
          {/* Ops FM drill-down (OPTMKT / OPTCOM / OPMANAGERF9) */}
          {info.opsParams && (
            <div style={{ marginTop:20 }}>
              <div style={{ fontSize:10, fontWeight:700, letterSpacing:'0.1em', textTransform:'uppercase', color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', marginBottom:8 }}>
                Oportunidades — {info.period} · {info.opsParams.vendedorNome}
              </div>
              {opsLoading && <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>A carregar...</div>}
              {!opsLoading && opsRows && opsRows.length === 0 && (
                <div style={{ padding:'16px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>Sem oportunidades para este período.</div>
              )}
              {!opsLoading && opsRows && opsRows.length > 0 && (() => {
                const thO = { padding:'5px 10px', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap', fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)' };
                const tdO = { padding:'4px 10px', borderBottom:'1px solid var(--dgd-border-1)', fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)', whiteSpace:'nowrap' };
                const isValor = info.opsParams.tipo === 'opmanagerf9';
                const sortedOps = [...opsRows].sort((a,b) => (parseInt(b.stage_num)||0) - (parseInt(a.stage_num)||0));
                const totalOps = opsRows.reduce((s,o) => s + (o.valor||0), 0);
                return (
                  <div style={{ overflowX:'auto', borderRadius:7, border:'1px solid var(--dgd-border-1)' }}>
                    <table style={{ borderCollapse:'collapse', width:'100%' }}>
                      <thead>
                        <tr style={{ background:'var(--dgd-bg-surface)' }}>
                          <th style={{ ...thO, textAlign:'left' }}>Entidade</th>
                          <th style={{ ...thO, textAlign:'left' }}>Produto</th>
                          <th style={{ ...thO, textAlign:'left' }}>Source</th>
                          <th style={{ ...thO, textAlign:'left' }}>Stage</th>
                          <th style={{ ...thO, textAlign:'left' }}>Data</th>
                          {isValor && <th style={{ ...thO, textAlign:'right' }}>Valor</th>}
                        </tr>
                      </thead>
                      <tbody>
                        {sortedOps.map((o, i) => (
                          <tr key={i} style={{ background: i%2===0 ? 'transparent' : 'var(--dgd-bg-surface)' }}>
                            <td style={{ ...tdO, color:'var(--dgd-fg-1)', maxWidth:180, overflow:'hidden', textOverflow:'ellipsis' }}>{o.entidade || '—'}</td>
                            <td style={{ ...tdO, color:'var(--dgd-fg-2)', maxWidth:140, overflow:'hidden', textOverflow:'ellipsis' }}>{o.produto || '—'}</td>
                            <td style={{ ...tdO, color:'var(--dgd-fg-3)' }}>{o.source_nome || o.source || '—'}</td>
                            <td style={{ ...tdO, color:'var(--dgd-fg-3)' }}>{o.stage || '—'}</td>
                            <td style={{ ...tdO, color:'var(--dgd-fg-3)' }}>{o.data ? o.data.slice(0,10) : '—'}</td>
                            {isValor && <td style={{ ...tdO, textAlign:'right', fontWeight:600, color:'var(--dgd-fg-1)' }}>{cE(o.valor||0)}</td>}
                          </tr>
                        ))}
                      </tbody>
                      <tfoot>
                        <tr style={{ background:'var(--dgd-bg-surface)' }}>
                          <td colSpan={isValor ? 5 : 5} style={{ ...tdO, fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>
                            Total ({opsRows.length} oportunidade{opsRows.length !== 1 ? 's' : ''})
                          </td>
                          {isValor && <td style={{ ...tdO, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(totalOps)}</td>}
                        </tr>
                      </tfoot>
                    </table>
                  </div>
                );
              })()}
            </div>
          )}
          {info.f9Params && (
            <div style={{ marginTop:20 }}>
              <div style={{ fontSize:10, fontWeight:700, letterSpacing:'0.1em', textTransform:'uppercase', color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', marginBottom:8 }}>
                Encomendas Confirmadas — {info.period} · {info.f9Params.vendedorNome}
              </div>
              {f9Loading && <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>A carregar...</div>}
              {!f9Loading && f9Rows && f9Rows.length === 0 && (
                <div style={{ padding:'16px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>Sem encomendas ECW para este período.</div>
              )}
              {!f9Loading && f9Rows && f9Rows.length > 0 && (() => {
                const thF = { padding:'5px 10px', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap', fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)' };
                const tdF = { padding:'4px 10px', borderBottom:'1px solid var(--dgd-border-1)', fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)', whiteSpace:'nowrap' };
                const totalF = f9Rows.reduce((s,r) => s + (r.valor||0), 0);
                return (
                  <div style={{ overflowX:'auto', borderRadius:7, border:'1px solid var(--dgd-border-1)' }}>
                    <table style={{ borderCollapse:'collapse', width:'100%' }}>
                      <thead>
                        <tr style={{ background:'var(--dgd-bg-surface)' }}>
                          <th style={{ ...thF, textAlign:'left' }}>Cliente</th>
                          <th style={{ ...thF, textAlign:'right' }}>Valor ECW</th>
                        </tr>
                      </thead>
                      <tbody>
                        {f9Rows.map((r, i) => (
                          <tr key={i} style={{ background: i%2===0 ? 'transparent' : 'var(--dgd-bg-surface)' }}>
                            <td style={{ ...tdF, color:'var(--dgd-fg-1)', maxWidth:260, overflow:'hidden', textOverflow:'ellipsis' }}>{r.cliente || '—'}</td>
                            <td style={{ ...tdF, textAlign:'right', fontWeight:600, color:'var(--dgd-fg-1)' }}>{cE(r.valor||0)}</td>
                          </tr>
                        ))}
                      </tbody>
                      <tfoot>
                        <tr style={{ background:'var(--dgd-bg-surface)' }}>
                          <td style={{ ...tdF, fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>Total ({f9Rows.length} cliente{f9Rows.length !== 1 ? 's' : ''})</td>
                          <td style={{ ...tdF, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(totalF)}</td>
                        </tr>
                      </tfoot>
                    </table>
                  </div>
                );
              })()}
            </div>
          )}
          {info.encPotParams && (
            <div style={{ marginTop:20 }}>
              <div style={{ fontSize:10, fontWeight:700, letterSpacing:'0.1em', textTransform:'uppercase', color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', marginBottom:8 }}>
                Encomendas Potenciais — {info.period} · {info.encPotParams.vendedorNome}
              </div>
              {encPotLoading && <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>A carregar...</div>}
              {!encPotLoading && encPotRows && encPotRows.length === 0 && (
                <div style={{ padding:'16px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>Sem encomendas potenciais para este período.</div>
              )}
              {!encPotLoading && encPotRows && encPotRows.length > 0 && (() => {
                const th = { padding:'5px 10px', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap', fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)' };
                const td = { padding:'4px 10px', borderBottom:'1px solid var(--dgd-border-1)', fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)', whiteSpace:'nowrap' };
                const total = encPotRows.reduce((s,r) => s + parseFloat(r.valor_potencial||0), 0);
                const showMes = info.encPotParams.mes == null;
                const MESES = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];
                return (
                  <div style={{ overflowX:'auto', borderRadius:7, border:'1px solid var(--dgd-border-1)' }}>
                    <table style={{ borderCollapse:'collapse', width:'100%' }}>
                      <thead>
                        <tr style={{ background:'var(--dgd-bg-surface)' }}>
                          {showMes && <th style={{ ...th, textAlign:'left' }}>Mês</th>}
                          <th style={{ ...th, textAlign:'left' }}>Entidade</th>
                          <th style={{ ...th, textAlign:'right' }}>Potencial</th>
                        </tr>
                      </thead>
                      <tbody>
                        {encPotRows.map((r, i) => (
                          <tr key={i} style={{ background: i%2===0 ? 'transparent' : 'var(--dgd-bg-surface)' }}>
                            {showMes && <td style={{ ...td, color:'var(--dgd-fg-3)' }}>{MESES[(r.mes||1)-1]}</td>}
                            <td style={{ ...td, color:'var(--dgd-fg-1)', maxWidth:280, overflow:'hidden', textOverflow:'ellipsis' }}>{r.nome || r.id_entidade}</td>
                            <td style={{ ...td, textAlign:'right', fontWeight:600, color:'var(--dgd-fg-1)' }}>{cE(parseFloat(r.valor_potencial||0))}</td>
                          </tr>
                        ))}
                      </tbody>
                      <tfoot>
                        <tr style={{ background:'var(--dgd-bg-surface)' }}>
                          {showMes && <td style={{ ...td, borderTop:'2px solid var(--dgd-border-1)' }}></td>}
                          <td style={{ ...td, fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>Total ({encPotRows.length} entrada{encPotRows.length !== 1 ? 's' : ''})</td>
                          <td style={{ ...td, textAlign:'right', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(total)}</td>
                        </tr>
                      </tfoot>
                    </table>
                  </div>
                );
              })()}
            </div>
          )}
          {info.drillParams && (
            <div style={{ marginTop:20 }}>
              <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:8 }}>
                <div style={{ fontSize:10, fontWeight:700, letterSpacing:'0.1em', textTransform:'uppercase', color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)' }}>
                  Detalhe — {info.period} · {info.drillParams.vendedorNome}
                </div>
                {info.drillParams.mes != null && (
                  <div style={{ display:'flex', gap:4 }}>
                    {['artigo','entidade'].map(v => (
                      <button key={v} onClick={() => setDrillView(v)}
                        style={{ fontSize:10, fontWeight:600, letterSpacing:'0.06em', padding:'3px 9px', borderRadius:5, border:'1px solid var(--dgd-border-1)', cursor:'pointer', fontFamily:'var(--dgd-font-mono,monospace)', textTransform:'uppercase',
                          background: drillView===v ? 'var(--dgd-fg-1)' : 'transparent',
                          color: drillView===v ? 'var(--dgd-bg-card,white)' : 'var(--dgd-fg-3)' }}>
                        {v === 'artigo' ? 'Por Artigo' : 'Por Entidade'}
                      </button>
                    ))}
                  </div>
                )}
              </div>

              {/* ── Por Artigo ── */}
              {drillView === 'artigo' && (
                <>
                  {drillLoading && <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>A carregar...</div>}
                  {!drillLoading && drill && drill.length === 0 && (
                    <div style={{ padding:'16px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>Sem linhas de detalhe para este período.</div>
                  )}
                  {!drillLoading && drill && drill.length > 0 && (
                    <div style={{ overflowX:'auto', borderRadius:7, border:'1px solid var(--dgd-border-1)' }}>
                      <table style={{ borderCollapse:'collapse', width:'100%', fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)' }}>
                        <thead>
                          <tr style={{ background:'var(--dgd-bg-surface)' }}>
                            <th style={{ padding:'5px 10px', textAlign:'left', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>Família</th>
                            <th style={{ padding:'5px 10px', textAlign:'left', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>Artigo</th>
                            <th style={{ padding:'5px 10px', textAlign:'right', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>Qtd</th>
                            <th style={{ padding:'5px 10px', textAlign:'right', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>Valor</th>
                            <th style={{ padding:'5px 10px', textAlign:'right', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>Margem%</th>
                          </tr>
                        </thead>
                        <tbody>
                          {drill.map((r, i) => {
                            const v = parseFloat(r.valor||0);
                            const m = parseFloat(r.margem||0);
                            const mPct = v > 0 ? (m/v*100) : null;
                            return (
                              <tr key={i} style={{ background: i%2===0 ? 'transparent' : 'var(--dgd-bg-surface)' }}>
                                <td style={{ padding:'4px 10px', color:'var(--dgd-fg-2)', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>{r.familia || '—'}</td>
                                <td style={{ padding:'4px 10px', color:'var(--dgd-fg-1)', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap', maxWidth:200, overflow:'hidden', textOverflow:'ellipsis' }}>{r.artigo || '—'}</td>
                                <td style={{ padding:'4px 10px', textAlign:'right', color:'var(--dgd-fg-3)', borderBottom:'1px solid var(--dgd-border-1)' }}>{parseFloat(r.qtd||0).toFixed(0)}</td>
                                <td style={{ padding:'4px 10px', textAlign:'right', fontWeight:600, color:'var(--dgd-fg-1)', borderBottom:'1px solid var(--dgd-border-1)' }}>{cE(v)}</td>
                                <td style={{ padding:'4px 10px', textAlign:'right', borderBottom:'1px solid var(--dgd-border-1)', color: mPct==null?'var(--dgd-fg-3)':mPct>=35?'#16a34a':mPct>=25?'#d97706':'#dc2626' }}>
                                  {mPct != null ? mPct.toFixed(1)+'%' : '—'}
                                </td>
                              </tr>
                            );
                          })}
                        </tbody>
                        <tfoot>
                          <tr style={{ background:'var(--dgd-bg-surface)' }}>
                            <td colSpan={3} style={{ padding:'5px 10px', color:'var(--dgd-fg-2)', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>Total ({drill.length} artigos)</td>
                            <td style={{ padding:'5px 10px', textAlign:'right', fontWeight:700, color:'var(--dgd-fg-1)', borderTop:'2px solid var(--dgd-border-1)' }}>{cE(totalValor)}</td>
                            <td style={{ padding:'5px 10px', textAlign:'right', borderTop:'2px solid var(--dgd-border-1)', color:'var(--dgd-fg-3)' }}>
                              {(() => { const tm = drill.reduce((s,r)=>s+parseFloat(r.margem||0),0); return totalValor>0?(tm/totalValor*100).toFixed(1)+'%':'—'; })()}
                            </td>
                          </tr>
                        </tfoot>
                      </table>
                    </div>
                  )}
                </>
              )}

              {/* ── Por Entidade ── */}
              {drillView === 'entidade' && (
                <>
                  {docsLoading && <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>A carregar...</div>}
                  {!docsLoading && docsRows && docsRows.length === 0 && (
                    <div style={{ padding:'16px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>Sem documentos para este período.</div>
                  )}
                  {!docsLoading && docsRows && docsRows.length > 0 && (() => {
                    const totalDocs = docsRows.reduce((s,r) => s + parseFloat(r.valor||0), 0);
                    return (
                      <div style={{ overflowX:'auto', borderRadius:7, border:'1px solid var(--dgd-border-1)' }}>
                        <table style={{ borderCollapse:'collapse', width:'100%', fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)' }}>
                          <thead>
                            <tr style={{ background:'var(--dgd-bg-surface)' }}>
                              <th style={{ padding:'5px 10px', textAlign:'left', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>Cliente</th>
                              <th style={{ padding:'5px 10px', textAlign:'left', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>Nº Documento</th>
                              <th style={{ padding:'5px 10px', textAlign:'left', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>Data</th>
                              <th style={{ padding:'5px 10px', textAlign:'right', color:'var(--dgd-fg-3)', fontWeight:600, letterSpacing:'0.06em', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>Valor</th>
                            </tr>
                          </thead>
                          <tbody>
                            {docsRows.map((r, i) => (
                              <tr key={i} style={{ background: i%2===0 ? 'transparent' : 'var(--dgd-bg-surface)' }}>
                                <td style={{ padding:'4px 10px', color:'var(--dgd-fg-1)', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap', maxWidth:220, overflow:'hidden', textOverflow:'ellipsis' }}>{r.cliente || r.id_entidade || '—'}</td>
                                <td style={{ padding:'4px 10px', color:'var(--dgd-fg-2)', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>{r.num_documento || '—'}</td>
                                <td style={{ padding:'4px 10px', color:'var(--dgd-fg-3)', borderBottom:'1px solid var(--dgd-border-1)', whiteSpace:'nowrap' }}>{r.data_doc ? new Date(r.data_doc).toLocaleDateString('pt-PT') : '—'}</td>
                                <td style={{ padding:'4px 10px', textAlign:'right', fontWeight:600, color:'var(--dgd-fg-1)', borderBottom:'1px solid var(--dgd-border-1)' }}>{cE(parseFloat(r.valor||0))}</td>
                              </tr>
                            ))}
                          </tbody>
                          <tfoot>
                            <tr style={{ background:'var(--dgd-bg-surface)' }}>
                              <td colSpan={3} style={{ padding:'5px 10px', color:'var(--dgd-fg-2)', fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>Total ({docsRows.length} documentos)</td>
                              <td style={{ padding:'5px 10px', textAlign:'right', fontWeight:700, color:'var(--dgd-fg-1)', borderTop:'2px solid var(--dgd-border-1)' }}>{cE(totalDocs)}</td>
                            </tr>
                          </tfoot>
                        </table>
                      </div>
                    );
                  })()}
                </>
              )}
            </div>
          )}
        </div>
      </div>
    </>,
    document.body
  );
};

// ─── Note Tooltip ─────────────────────────────────────────────────────────────
const ConsNoteBadge = ({ n, activeNote, setActiveNote }) => (
  <sup
    onClick={() => setActiveNote(activeNote === n ? null : n)}
    title={CONS_NOTES.find(x => x.n === n)?.text || ''}
    style={{ cursor:'pointer', color:'#3563c9', fontWeight:700, fontSize:9, marginLeft:3, userSelect:'none' }}
  >{CONS_SUPS[n-1]}</sup>
);

// ─── Tab: Mapa RPC ─────────────────────────────────────────────────────────────
const TabMapaRPCCons = ({ com, ytdMensal, bpMensal, margemMensal, onBpSave, fmData, carteiraByV, comerciaisFiltrados, spinData, encData, f9Data, empresa, fmEmpresa, userTipo, canEditDesvio, ano: anoProp }) => {
  const mes = CONS_MES_FECH;
  const canEditBP = ['admin', 'board', 'dev'].includes(userTipo);
  const [activeNote, setActiveNote] = React.useState(null);
  const [infoModal, setInfoModal]   = React.useState(null);
  const [editCell, setEditCell]     = React.useState(null); // { mesIdx, val } — edição BP inline
  const [saving, setSaving]         = React.useState(false);
  const [editObjMk, setEditObjMk]   = React.useState(null); // metrica key a editar na col Obj.
  const [objSaving, setObjSaving]   = React.useState(null); // metrica key a mostrar "✓"

  const isDecalPTES = empresa === 'DECAL_PT' || empresa === 'DECAL_ES';
  const [prevDesvio, setPrevDesvio]       = React.useState({});
  const [editDesvioCell, setEditDesvioCell] = React.useState(null); // { mesIdx, val }
  const [savingDesvio, setSavingDesvio]   = React.useState(false);

  React.useEffect(() => {
    if (!isDecalPTES) return;
    fetch(`/api/rpc/previsao-desvio?ano=${anoProp || CONS_ANO_DEF}`)
      .then(r => r.ok ? r.json() : {})
      .then(setPrevDesvio)
      .catch(() => {});
  }, [isDecalPTES, anoProp]);

  const saveDesvio = async (mesI, rawVal) => {
    if (!com || !canEditDesvio) return;
    const valorN = Number(String(rawVal || '').replace(',', '.')) || 0;
    setEditDesvioCell(null);
    setSavingDesvio(true);
    try {
      await fetch('/api/rpc/previsao-desvio', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ vendedor: com.vendedor, ano: anoProp || CONS_ANO_DEF, mes: mesI + 1, valor: valorN }),
      });
      setPrevDesvio(prev => {
        const arr = (prev[com.vendedor] || Array(12).fill(null)).slice();
        arr[mesI] = valorN !== 0 ? valorN : null;
        return { ...prev, [com.vendedor]: arr };
      });
    } catch (e) { /* ignore */ }
    setSavingDesvio(false);
  };

  // useConsObjetivos: individual quando com está definido, null em aggregate
  const comVendKey = com ? (com.gestor_vendedor_id || com.vendedor || null) : null;
  const [objEdits, setObjEdits] = useConsObjetivos(comVendKey, anoProp || CONS_ANO_DEF);

  const saveObj = async (mk, rawVal) => {
    const val = String(rawVal || '').trim();
    setEditObjMk(null);
    if (!val || !com || !canEditBP) return;
    const vendKey = com.gestor_vendedor_id || com.vendedor || null;
    if (!vendKey) return;
    setObjSaving(mk);
    setObjEdits(prev => ({ ...prev, [mk]: val }));
    fetch('/api/rpc/cons-objetivos', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ vendedor: vendKey, ano: anoProp || CONS_ANO_DEF, metrica: mk, valor: val }),
    }).catch(() => {});
    setTimeout(() => setObjSaving(null), 1500);
  };
  // Threshold dinâmico de Margem (baseado no obj. editável — default 35%)
  const margemObj = parseFloat(String(objEdits['margem'] ?? '35').replace(/[^0-9.]/g, '')) || 35;
  const margemYellow = margemObj - 10; // amarelo = 10pp abaixo do objectivo

  const noteProps = { activeNote, setActiveNote };
  const DASH12 = Array(12).fill('—');

  const saveBP = async (mesIdx, rawVal) => {
    setEditCell(null);
    if (!com) return;
    const vendKey = com.gestor_vendedor_id || com.vendedor;
    if (!vendKey) return;
    const valor = parseFloat(String(rawVal).replace(',', '.').replace(/\s/g, '')) || 0;
    setSaving(true);
    try {
      await fetch('/api/rpc-es/bp-targets', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ vendedor: vendKey, ano: CONS_ANO_DEF, mes: mesIdx + 1, valor }),
      });
      if (onBpSave) onBpSave();
    } catch {}
    setSaving(false);
  };

  const DRILL_ROWS = new Set(['Vendas Fact.','Margem','BP Desvio','RPC Rating']);
  const CARTEIRA_DRILL = { 'Clientes Ativos':'ativos', 'Clientes em Risco':'risco', 'Retenção 12m':'retencao', 'Novos Clientes (1.ª compra)':'novos', 'Vendas Carteira':'vcart', 'Vendas Novos Clientes':'vnovos', 'Famílias / Cliente':'familias', 'Reuniões SPIN Consumíveis':'spin_reunioes', 'SPIN Identificado':'spin_potencial' };
  const OPS_DRILL = { 'Leads / OP Mkt':'optmkt', 'Leads / OP Com':'optcom', 'OP Criadas (valor)':'opmanagerf9' };

  const openCell = (row, sec, val, period, mesIdx) => {
    if (!row.tip && !row.formula) return;
    const drillParams = DRILL_ROWS.has(row.l) && com
      ? { vendedor: com.vendedor, vendedorNome: com.nome, ano: CONS_ANO_DEF, mes: mesIdx != null ? mesIdx : null }
      : null;
    const carteiraTipo = CARTEIRA_DRILL[row.l];
    const carteiraParams = carteiraTipo && com
      ? { tipo: carteiraTipo, vendedor: com.vendedor, vendedorNome: com.nome, ano: CONS_ANO_DEF, mes: mesIdx != null ? mesIdx : null }
      : null;
    const opsTipo = OPS_DRILL[row.l];
    const opsParams = opsTipo && com
      ? { tipo: opsTipo, vendedor: com.vendedor, vendedorNome: com.nome, ano: CONS_ANO_DEF, mes: mesIdx, empresa: fmEmpresa || empresa || 'PTDIG' }
      : null;
    const f9Params = row.l === 'Encomendas Confirmadas' && com && mesIdx != null
      ? { vendedor: com.vendedor, vendedorNome: com.nome, ano: CONS_ANO_DEF, mes: mesIdx }
      : null;
    const encPotParams = row.l === 'Encomendas Potenciais' && com
      ? { vendedor: com.vendedor, vendedorNome: com.nome, ano: CONS_ANO_DEF, mes: mesIdx != null ? mesIdx : null }
      : null;
    const resolvedFormula = typeof row.formula === 'function' ? row.formula(mesIdx) : (row.formula || null);
    setInfoModal({ label:row.l, section:sec.label, tip:row.tip||null, formula:resolvedFormula, fase:row.fase||null, val, period, drillParams, carteiraParams, opsParams, f9Params, encPotParams });
  };

  // ─── Block D computed values ────────────────────────────────────────────────

  const bpYTD   = bpMensal.slice(0, mes+1).reduce((s,v) => s+(v||0), 0);
  const bpAnual  = bpMensal.reduce((s,v) => s+(v||0), 0);
  const vendasYTD= ytdMensal.slice(0, mes+1).reduce((s,v) => s+(v||0), 0);

  // Margem %
  const margemPctByMonth = ytdMensal.map((v,i) => {
    const m = margemMensal[i];
    if (i > mes) return null;
    if (!v || !m) return null;
    return (m / v) * 100;
  });
  const margemYTDV   = margemMensal.slice(0, mes+1).reduce((s,v) => s+(v||0), 0);
  const margemYTDPct = vendasYTD > 0 ? (margemYTDV / vendasYTD) * 100 : null;

  // BP Desvio (cumulative per closed month)
  let cumDesvio = 0;
  const desvioByMonth = CONS_MESES.map((_, i) => {
    if (i > mes) return null;
    const v = ytdMensal[i], b = bpMensal[i];
    if (v == null && b == null) return null;
    cumDesvio += ((v||0) - (b||0));
    return cumDesvio;
  });
  const desvioYTD = vendasYTD - bpYTD;

  // ─── FM helpers ─────────────────────────────────────────────────────────────
  const fmArr  = (key) => fmData?.[key] || new Array(12).fill(0);
  const fmYTD  = (key) => fmArr(key).slice(0, mes+1).reduce((s,v)=>s+(v||0), 0);
  const fmCell = (key, i, fmt) => {
    if (!fmData) return ['—','m'];
    const v = fmArr(key)[i];
    if (i > mes) return ['—','m'];
    return v > 0 ? [fmt(v), ''] : ['—','m'];
  };
  const fmCellYTD = (key, fmt) => {
    if (!fmData) return ['—','m'];
    const t = fmYTD(key);
    return t > 0 ? [fmt(t), 'b'] : ['—','m'];
  };
  const fmHasData = !!fmData;

  // ─── Carteira data ───────────────────────────────────────────────────────────
  const carteiraData = React.useMemo(() => {
    if (!carteiraByV) return null;
    if (com) return carteiraByV[com.vendedor] || null;
    const coms = comerciaisFiltrados || [];
    if (!coms.length) return null;
    const agg = {
      ativosPerMes: new Array(12).fill(0), valorMedioPerMes: new Array(12).fill(null),
      novosPerMes: new Array(12).fill(0), vendasNovosPerMes: new Array(12).fill(0),
      vendasCarteiraPerMes: new Array(12).fill(0),
      familiasCliente: null, risco: 0, totalClientes2025: 0, totalClientes2026: 0,
      retencao: null, riscoPct: null,
    };
    let famTotal = 0, famCount = 0, prevTotal = 0, retainedTotal = 0;
    coms.forEach(c => {
      const d = carteiraByV[c.vendedor];
      if (!d) return;
      d.ativosPerMes.forEach((v,i) => agg.ativosPerMes[i] += v);
      d.novosPerMes.forEach((v,i) => agg.novosPerMes[i] += v);
      d.vendasNovosPerMes.forEach((v,i) => agg.vendasNovosPerMes[i] += v);
      d.vendasCarteiraPerMes.forEach((v,i) => agg.vendasCarteiraPerMes[i] += v);
      agg.risco += d.risco || 0;
      agg.totalClientes2025 += d.totalClientes2025 || 0;
      agg.totalClientes2026 += d.totalClientes2026 || 0;
      if (d.familiasCliente != null) { famTotal += d.familiasCliente; famCount++; }
      if (d.totalClientes2025 > 0) { prevTotal += d.totalClientes2025; retainedTotal += Math.round((d.retencao||0)*d.totalClientes2025); }
    });
    if (famCount > 0) agg.familiasCliente = famTotal / famCount;
    agg.riscoPct = agg.totalClientes2025 > 0 ? agg.risco / agg.totalClientes2025 : null;
    agg.retencao = prevTotal > 0 ? retainedTotal / prevTotal : null;
    agg.ativosPerMes.forEach((n,i) => {
      agg.valorMedioPerMes[i] = n > 0 && ytdMensal[i] != null ? ytdMensal[i] / n : null;
    });
    return agg;
  }, [carteiraByV, com, comerciaisFiltrados, ytdMensal]);

  const sumArr = (arr, upTo) => (arr||[]).slice(0,upTo+1).reduce((s,v)=>s+(v||0),0);
  const cd = carteiraData;

  // ─── SPIN Consumíveis computed values ────────────────────────────────────────
  const cobertura = spinData && bpAnual > 0 ? spinData.potencialAtivo / bpAnual : null;
  const conv      = spinData?.conversaoProspects;
  const convPct   = conv && conv.spins > 0 ? (conv.convertidos / conv.spins) * 100 : null;

  // Rating = (% achievement Vendas + % achievement Margem) / 2
  const ratingMensal = CONS_MESES.map((_, i) => {
    if (i > mes) return null;
    const fat  = ytdMensal[i];
    const bp   = bpMensal[i];
    const marg = margemMensal[i];
    if (!fat || !bp || bp <= 0) return null;
    const vendAch = fat / bp * 100;
    const margPct = fat > 0 && marg != null ? marg / fat * 100 : null;
    const margAch = margPct != null ? margPct / margemObj * 100 : null;
    return margAch != null ? (vendAch + margAch) / 2 : null;
  });
  const ratingYTD = (() => {
    if (bpYTD <= 0 || vendasYTD <= 0) return null;
    const vendasAch = vendasYTD / bpYTD * 100;
    const margemAch = margemYTDPct != null ? margemYTDPct / margemObj * 100 : null;
    return margemAch != null ? (vendasAch + margemAch) / 2 : null;
  })();

  const crtAtivos  = cd && cd.totalClientes2025 > 0
    ? CONS_MESES.map((_, i) => {
        if (i > mes) return ['—', 'm'];
        const v = cd.ativosPerMes[i];
        if (!v) return ['—', 'm'];
        const pct = v / cd.totalClientes2025 * 100;
        return [String(v), pct >= 85 ? 'p' : pct >= 70 ? 'a' : 'n'];
      })
    : DASH12;
  const crtValMed  = cd ? CONS_MESES.map((_,i) => i>mes?['—','m']:cd.valorMedioPerMes[i]>0?[cE(cd.valorMedioPerMes[i]),'']:['—','m']) : DASH12;
  const crtNovos   = cd ? CONS_MESES.map((_,i) => i>mes?['—','m']:cd.novosPerMes[i]>0?[String(cd.novosPerMes[i]),'']:['—','m']) : DASH12;
  const crtVNovos  = cd ? CONS_MESES.map((_,i) => i>mes?['—','m']:cd.vendasNovosPerMes[i]>0?[cE(cd.vendasNovosPerMes[i]),'']:['—','m']) : DASH12;
  const crtVCart   = cd ? CONS_MESES.map((_,i) => i>mes?['—','m']:cd.vendasCarteiraPerMes[i]!=0?[cE(cd.vendasCarteiraPerMes[i]),'']:['—','m']) : DASH12;
  const crtAtivosYTD = cd && cd.totalClientes2025 > 0 && cd.ativosPerMes[mes] > 0
    ? (() => { const pct = cd.ativosPerMes[mes] / cd.totalClientes2025 * 100; return [Math.round(pct)+'%', 'b '+(pct>=85?'p':pct>=70?'a':'n')]; })()
    : ['—','m'];
  const crtValMedYTD = (() => {
    if (!cd) return ['—','m'];
    const vals = cd.valorMedioPerMes.slice(0, mes+1).filter(v => v > 0);
    if (!vals.length) return ['—','m'];
    return [cE(vals.reduce((s,v)=>s+v,0)/vals.length), 'b'];
  })();
  const crtRiscoAno  = cd&&cd.risco>0 ? [String(cd.risco)+(cd.riscoPct!=null?' ('+Math.round(cd.riscoPct*100)+'%)':''),'n'] : ['—','m'];
  const crtRetAno    = cd&&cd.retencao!=null ? [(cd.retencao*100).toFixed(0)+'%','b '+(cd.retencao>=0.92?'p':cd.retencao>=0.8?'a':'n')] : ['—','m'];
  const crtFamYTD    = cd&&cd.familiasCliente!=null ? [cd.familiasCliente.toFixed(1).replace('.',','),'b '+(cd.familiasCliente>=2.5?'p':'a')] : ['—','m'];
  // ── Bloco A mensais (rpc-carteira-mensais) ───────────────────────────────
  const crtRiscoMes = cd && cd.totalClientes2025 > 0
    ? CONS_MESES.map((_, i) => {
        if (i > mes) return ['—', 'm'];
        const v = cd.riscoPerMes?.[i];
        if (v == null) return ['—', 'm'];
        const pct = v / cd.totalClientes2025 * 100;
        return [String(v), pct <= 8 ? 'p' : 'n'];
      })
    : DASH12;
  const crtRiscoYTD = cd && cd.risco > 0
    ? (() => { const pct = cd.riscoPct != null ? cd.riscoPct * 100 : cd.risco / cd.totalClientes2025 * 100; return [Math.round(pct)+'%', 'b '+(pct<=8?'p':'n')]; })()
    : ['—', 'm'];

  const crtRetMes = cd
    ? CONS_MESES.map((_, i) => {
        if (i > mes) return ['—', 'm'];
        const v = cd.retencaoPerMes?.[i];
        return v != null
          ? [(v * 100).toFixed(0) + '%', 'b ' + (v >= 0.92 ? 'p' : v >= 0.80 ? 'a' : 'n')]
          : ['—', 'm'];
      })
    : DASH12;
  const crtRetYTD = (cd && cd.retencao != null)
    ? [(cd.retencao * 100).toFixed(0) + '%',
       'b ' + (cd.retencao >= 0.92 ? 'p' : cd.retencao >= 0.80 ? 'a' : 'n')]
    : ['—', 'm'];

  const crtFamMes = cd
    ? CONS_MESES.map((_, i) => {
        if (i > mes) return ['—', 'm'];
        const v = cd.familiasPerMes?.[i];
        return v != null
          ? [v.toFixed(1).replace('.', ','), 'b ' + (v >= 2.5 ? 'p' : 'a')]
          : ['—', 'm'];
      })
    : DASH12;
  const crtNovosYTD  = cd ? (() => { const t=sumArr(cd.novosPerMes,mes); return t>0?[String(t),'b']:['—','m']; })() : ['—','m'];
  const crtVNovosYTD = cd ? (() => { const t=sumArr(cd.vendasNovosPerMes,mes); return t>0?[cE(t),'b']:['—','m']; })() : ['—','m'];
  const crtVCartYTD  = cd ? (() => { const t=sumArr(cd.vendasCarteiraPerMes,mes); return t>0?[cE(t),'b']:['—','m']; })() : ['—','m'];

  // ── Encomendas do Mês (rpc-encomendas-mes) ───────────────────────────────────
  const crtEncPot = encData
    ? CONS_MESES.map((_, i) => {
        if (i > mes) return ['—', 'm'];
        const pot = encData.potencialPerMes[i] || 0;
        const conf = (f9Data && f9Data[i]) || 0;
        const v = Math.max(0, pot - conf);
        return v > 0 ? [cE(v), ''] : ['—', 'm'];
      })
    : DASH12;
  const crtEncPotYTD = encData
    ? (() => {
        let t = 0;
        for (let i = 0; i <= mes; i++) {
          const pot = encData.potencialPerMes[i] || 0;
          const conf = (f9Data && f9Data[i]) || 0;
          t += Math.max(0, pot - conf);
        }
        return t > 0 ? [cE(t), 'b'] : ['—', 'm'];
      })()
    : ['—', 'm'];

  const crtEncConf = f9Data
    ? CONS_MESES.map((_, i) => {
        if (i > mes) return ['—', 'm'];
        const conf = f9Data[i] || 0;
        const fat  = ytdMensal[i] || 0;
        const v = Math.max(0, conf - fat);
        return v > 0 ? [cE(v), ''] : ['—', 'm'];
      })
    : DASH12;
  const crtEncConfYTD = f9Data
    ? (() => {
        let t = 0;
        for (let i = 0; i <= mes; i++) {
          const conf = f9Data[i] || 0;
          const fat  = ytdMensal[i] || 0;
          t += Math.max(0, conf - fat);
        }
        return t > 0 ? [cE(t), 'b'] : ['—', 'm'];
      })()
    : ['—', 'm'];

  // Taxa de Concretização — 3 meses móveis: Σ faturado(M,M-1,M-2) / Σ potencial(M,M-1,M-2)
  const rolling3 = (arr, i) => {
    let s = 0;
    for (let j = Math.max(0, i - 2); j <= i; j++) s += (arr?.[j] || 0);
    return s;
  };
  const crtTaxaConc = (encData && ytdMensal)
    ? CONS_MESES.map((_, i) => {
        if (i > mes) return ['—', 'm'];
        const fat = rolling3(ytdMensal, i);
        const pot = rolling3(encData.potencialPerMes, i);
        if (pot === 0) return ['—', 'm'];
        const pct = fat / pot;
        return [(pct * 100).toFixed(0) + '%', 'b ' + (pct >= 0.70 ? 'p' : pct >= 0.50 ? 'a' : 'n')];
      })
    : DASH12;
  const crtTaxaConcYTD = (() => {
    if (!encData || !ytdMensal) return ['—', 'm'];
    const fatYTD = sumArr(ytdMensal, mes);
    const potYTD = sumArr(encData.potencialPerMes, mes);
    if (potYTD === 0) return ['—', 'm'];
    const pct = fatYTD / potYTD;
    return [(pct * 100).toFixed(0) + '%', 'b ' + (pct >= 0.70 ? 'p' : pct >= 0.50 ? 'a' : 'n')];
  })();

  const growthRate = (() => {
    if (!cd?.prevYearPerMes) return 0;
    const currYTD = sumArr(ytdMensal, mes > 0 ? mes - 1 : 0);
    const prevYTD = sumArr(cd.prevYearPerMes, mes > 0 ? mes - 1 : 0);
    return prevYTD > 0 ? (currYTD / prevYTD - 1) : 0;
  })();

  // Rollover: se o mês corrente não tem potencial inserido, carrega o não executado do mês anterior
  const rolloverPot = (() => {
    if (!encData || !f9Data || mes === 0) return 0;
    const potCurr = encData.potencialPerMes[mes] || 0;
    if (potCurr > 0) return 0;
    const prevPot = encData.potencialPerMes[mes - 1] || 0;
    const prevECW = f9Data[mes - 1] || 0;
    return Math.max(0, prevPot - prevECW);
  })();

  // desvioArr: ajuste manual por mês para Decal PT/ES (array 12 posições, null = sem ajuste)
  const desvioArr = React.useMemo(() => {
    if (!isDecalPTES) return Array(12).fill(null);
    const coms = comerciaisFiltrados || [];
    if (com) return prevDesvio[com.vendedor] || Array(12).fill(null);
    return Array.from({ length: 12 }, (_, i) =>
      coms.reduce((s, c) => s + (prevDesvio[c.vendedor]?.[i] || 0), 0)
    ).map(v => v !== 0 ? v : null);
  }, [isDecalPTES, com, prevDesvio, comerciaisFiltrados]);

  const crtPrevisao = CONS_MESES.map((_, i) => {
    if (i < mes) {
      const v = ytdMensal[i];
      return (v != null && v > 0) ? [cE(v), ''] : ['—', 'm'];
    }
    if (isDecalPTES) {
      // Mês corrente e futuros: homólogo × crescimento + desvio manual
      const prev = cd?.prevYearPerMes?.[i];
      if (prev == null) return ['—', 'm'];
      const forecast = prev * (1 + growthRate) + (desvioArr[i] || 0);
      return forecast > 0 ? [cE(forecast), ''] : ['—', 'm'];
    }
    if (i === mes) {
      const fat  = ytdMensal[i] || 0;
      const conf = f9Data?.[i] || 0;
      const pot  = (encData?.potencialPerMes[i] || 0) + rolloverPot;
      const ecwRest = Math.max(0, conf - fat);
      const potRest = Math.max(0, pot  - conf);
      const total   = fat + ecwRest + potRest;
      return total > 0 ? [cE(total), ''] : ['—', 'm'];
    }
    // Futuro: homólogo × (1 + crescimento)
    const prev = cd?.prevYearPerMes?.[i];
    if (prev == null) return ['—', 'm'];
    const forecast = prev * (1 + growthRate);
    return forecast > 0 ? [cE(forecast), ''] : ['—', 'm'];
  });
  const crtPrevisaoYTD = (() => {
    let total = 0;
    for (let i = 0; i <= 11; i++) {
      if (i < mes) {
        total += ytdMensal[i] || 0;
      } else if (isDecalPTES) {
        const prev = cd?.prevYearPerMes?.[i];
        if (prev != null) total += prev * (1 + growthRate) + (desvioArr[i] || 0);
      } else if (i === mes) {
        const fat2  = ytdMensal[i] || 0;
        const conf2 = f9Data?.[i] || 0;
        const pot2  = (encData?.potencialPerMes[i] || 0) + rolloverPot;
        total += Math.max(0, conf2 - fat2) + Math.max(0, pot2 - conf2);
      } else {
        const prev = cd?.prevYearPerMes?.[i];
        if (prev != null) total += prev * (1 + growthRate);
      }
    }
    return total > 0 ? [cE(total), ''] : ['—', 'm'];
  })();

  // ─── Grid section definitions ───────────────────────────────────────────────
  const sections = [
    {
      id: 'C', label: 'Prospeção SPIN & Novos Clientes',
      grpBg: 'color-mix(in oklch,#8b5cf6 5%,transparent)',
      rows: [
        { l:'Reuniões SPIN Consumíveis', mk:'spin_reunioes', obj:'10/mês', tip:'Nº de reuniões de diagnóstico SPIN Consumíveis realizadas no mês. Obj 10/mês. Inclui 1.ªs visitas e revisões de potencial.', formula:'Contagem de reuniões registadas no CRM\nInclui: 1.ª visita, revisão SPIN, follow-up qualificado',
          fase: spinData ? undefined : 'Fase 2',
          m: spinData
            ? CONS_MESES.map((_,i) => i>mes ? ['—','m'] : spinData.reunioesPorMes[i] > 0 ? [String(spinData.reunioesPorMes[i]),''] : ['0','m'])
            : DASH12,
          ytd: spinData ? [String(spinData.reunioesYTD), 'b '+(spinData.reunioesYTD>=10*(mes+1)?'p':'n')] : ['—','m'],
          res: spinData ? [String(spinData.reunioesYTD - 10*(mes+1)), spinData.reunioesYTD>=10*(mes+1)?'p':'n'] : ['—','m'],
          ano: '—',
        },
        {
          l:'Leads / OP Mkt', obj:'—',
          tip:'Oportunidades abertas via Marketing (Source: ADS, NEWSLETTER, WEBPAGE). Contagem pelo mês de abertura. Fonte: GestorConnect FM (FMRS_API_OPORTUNIDADES).',
          formula:'Contagem de OPs com Source_GrupoFilter = MKT\npor mês de abertura (StartDate)',
          m: CONS_MESES.map((_,i) => fmCell('optmkt', i, v => String(v))),
          ytd: fmCellYTD('optmkt', v => String(v)),
          res: '—', ano: '—',
        },
        {
          l:'Leads / OP Com', obj:'—',
          tip:'Oportunidades abertas via acção comercial (excl. Marketing). Contagem pelo mês de abertura. Fonte: GestorConnect FM (FMRS_API_OPORTUNIDADES).',
          formula:'Contagem de OPs com Source_GrupoFilter ≠ MKT\npor mês de abertura (StartDate)',
          m: CONS_MESES.map((_,i) => fmCell('optcom', i, v => String(v))),
          ytd: fmCellYTD('optcom', v => String(v)),
          res: '—', ano: '—',
        },
        {
          l:'Leads / OP SDR', obj:'—',
          tip:'Oportunidades geradas via SDR AI (em implementação). Dados disponíveis brevemente.',
          formula:'Em implementação',
          m: DASH12,
          ytd: ['—','m'],
          res: '—', ano: '—',
        },
        {
          l:'OP Criadas (valor)', obj:'—',
          tip:'Soma do valor de pipeline (Produto_Valor) de todas as OPs abertas no mês (todos os stages, excl. WON e LOST). Fonte: GestorConnect FM.',
          formula:'= Σ Produto_Valor de OPs (todos os stages)\npor mês de abertura (StartDate)\nexcl. WON e LOST',
          m: CONS_MESES.map((_,i) => fmCell('opmanagerf9', i, cE)),
          ytd: fmCellYTD('opmanagerf9', cE),
          res: '—', ano: '—',
        },
        { l:'SPIN Identificado', note:2, obj:'—', tip:'Consumo anual estimado do cliente em consumíveis, identificado no diagnóstico SPIN Consumíveis. Acumulado por família. Base para Share of Wallet e Cobertura SPIN.', formula:'= Σ consumo anual estimado por família (diagnóstico SPIN Consumíveis)\nRegistado por cliente, acumulado na carteira',
          fase: spinData ? undefined : 'Fase 2',
          m: spinData
            ? CONS_MESES.map((_,i) => i>mes ? ['—','m'] : spinData.potencialYTD > 0 ? [cE(spinData.potencialYTD / 12),''] : ['—','m'])
            : DASH12,
          ytd: spinData ? [cE(spinData.potencialYTD), 'b'] : ['—','m'],
          res: '—', ano: '—',
        },
        { l:'Novos Clientes (1.ª compra)', mk:'novos_clientes', obj:'2/mês', tip:'Clientes que realizaram a 1.ª compra em 2026 (não existiam em 2025). Conta apenas 1 vez por cliente, no mês da 1.ª compra. Fonte: Primavera ytd_snap.', formula:'Clientes em 2026 com fatura, que não têm registo em 2025\nContados no mês da 1.ª fatura do ano\nFonte: comercial_ytd_snap', m:crtNovos, ytd:crtNovosYTD, res:['—','m'], ano:'—' },
      ],
    },
    {
      id: 'A', label: 'Gestão de Carteira',
      grpBg: 'color-mix(in oklch,#3b82f6 5%,transparent)',
      rows: [
        { l:'Clientes Ativos',       mk:'clientes_ativos', note:1, obj:'≥85%', tip:'Nº de clientes da carteira atribuída com pelo menos 1 fatura nos últimos 90 dias.', formula:'Clientes Ativos = Clientes com fatura nos últimos 90 dias\n\nJanela deslizante: mês M + M-1 + M-2 (≈90 dias)\nBase de carteira: total de clientes distintos com fatura em 2025\nFonte: comercial_ytd_snap\n\nVerde ≥ 85% da carteira · Amarelo ≥ 70% · Vermelho < 70%', m:crtAtivos, ytd:crtAtivosYTD, res:['—','m'], ano:'—' },
        { l:'Clientes em Risco',            mk:'clientes_risco', obj:'≤8%', tip:'Clientes com histórico de recompra sem fatura há 90 dias.', formula:'Clientes em Risco = Clientes 2025 sem fatura nos últimos 90 dias\n\nJanela deslizante: mês M + M-1 + M-2 (≈90 dias)\nBase de carteira: total de clientes distintos com fatura em 2025\nFonte: comercial_ytd_snap\n\nVerde ≤ 8% da carteira · Vermelho > 8%', m:crtRiscoMes, ytd:crtRiscoYTD, res:['—','m'], ano:crtRiscoAno },
        { l:'Retenção 12m',                 mk:'retencao', obj:'≥92%',    tip:'Percentagem dos clientes ativos há 12 meses que continuam ativos hoje.', formula:(mesIdx) => {
            const base = 'Mensal: clientes ativos mês M / clientes ativos mês M (ano-1)\nYTD: clientes 2025 que compraram em 2026 ÷ total clientes 2025\nFonte: comercial_ytd_snap';
            if (mesIdx == null && cd && cd.retencao != null && cd.totalClientes2025 > 0) {
              const retained = Math.round(cd.retencao * cd.totalClientes2025);
              return base + `\n\n= ${retained} / ${cd.totalClientes2025} clientes → ${(cd.retencao * 100).toFixed(0)}%`;
            }
            return base;
          }, m:crtRetMes, ytd:crtRetYTD, res:['—','m'], ano:crtRetAno },
        { l:'Valor Médio / Cliente',        mk:'valor_medio', obj:'≥med+5%', tip:'Vendas de carteira do mês a dividir pelo nº de clientes ativos.', formula:'= Σ Vendas mês ÷ Nº clientes activos no mês\nFonte: comercial_ytd_snap', m:crtValMed, ytd:crtValMedYTD, res:['—','m'], ano:'—' },
      ],
    },
    {
      id: 'B', label: 'Desenvolvimento por Família',
      grpBg: 'color-mix(in oklch,#8b5cf6 5%,transparent)',
      rows: [
        { l:'Famílias / Cliente', mk:'familias_cliente', note:2, obj:'≥2,5', tip:'Nº médio de famílias de produto distintas compradas por cliente em 2026. Obj ≥2,5 — promove cross-sell de consumíveis. Fonte: Primavera ytd_snap.', formula:'= Σ famílias distintas por cliente (ano) ÷ Nº clientes activos\nFonte: comercial_ytd_snap', m:crtFamMes, ytd:crtFamYTD, res:['—','m'], ano:'—' },
        { l:'Share of Wallet',    mk:'share_wallet', note:2, obj:'≥40%', tip:'% do consumo total estimado (SPIN Identificado) capturado pela Decal/Biond. Vendas YTD ÷ SPIN Identificado. Obj ≥40%.', formula:(mesIdx) => {
            const base = '= Vendas Fact. YTD ÷ SPIN Identificado × 100\n\nVerde ≥ 40%\nAmarelo ≥ 25%\nVermelho < 25%\nFonte: ytd_snap ÷ spin_consumiveis';
            if (mesIdx == null && spinData && spinData.potencialYTD > 0 && vendasYTD > 0) {
              const pct = vendasYTD / spinData.potencialYTD * 100;
              return base + `\n\n= ${cE(vendasYTD)} ÷ ${cE(spinData.potencialYTD)} → ${pct.toFixed(0)}%`;
            }
            return base;
          }, fase: (spinData && spinData.potencialYTD > 0) ? undefined : 'Fase 2',
          m: spinData && spinData.potencialYTD > 0
            ? CONS_MESES.map((_,i) => {
                if (i > mes) return ['—','m'];
                const cumVendas = ytdMensal.slice(0,i+1).reduce((s,v)=>s+(v||0),0);
                const pct = cumVendas / spinData.potencialYTD * 100;
                return [pct.toFixed(0)+'%', 'b '+(pct>=40?'p':pct>=25?'a':'n')];
              })
            : DASH12,
          ytd: (() => { if (!spinData || spinData.potencialYTD <= 0) return ['—','m']; const pct = vendasYTD / spinData.potencialYTD * 100; return [pct.toFixed(0)+'%', 'b '+(pct>=40?'p':pct>=25?'a':'n')]; })(),
          res: (() => { if (!spinData || spinData.potencialYTD <= 0) return ['—','m']; const pct = vendasYTD / spinData.potencialYTD * 100; return [(pct-40>=0?'+':'')+(pct-40).toFixed(0)+'pp', pct>=40?'p':'n']; })(),
          ano: '—' },
      ],
    },
    {
      id: 'ENC', label: 'Encomendas do Mês',
      grpBg: 'color-mix(in oklch,#f59e0b 5%,transparent)',
      rows: [
        { l:'Encomendas Potenciais', note:7, obj:'—', tip:'Encomendas estimadas pelo comercial ainda não confirmadas no ERP. Descontadas automaticamente à medida que o Primavera regista ECW no mesmo mês.', formula:(mesIdx) => {
            const pot  = (encData && mesIdx != null) ? (encData.potencialPerMes[mesIdx] || 0) : 0;
            const conf = (f9Data  && mesIdx != null) ? (f9Data[mesIdx] || 0) : 0;
            const rest = Math.max(0, pot - conf);
            const mn   = mesIdx != null ? CONS_MESES[mesIdx] : 'Mês';
            return `= MAX(0, Potencial inserido − ECW Primavera)\n\n${mn}:\n  Potencial inserido:   ${cE(pot)}\n  ECW Primavera:       −${cE(conf)}\n  ─────────────────────\n  Restante:             ${cE(rest)}`;
          }, m:crtEncPot, ytd:crtEncPotYTD, res:'—', ano:'—' },
        { l:'Encomendas Confirmadas', note:7, obj:'—', tip:'Encomendas ECW abertas no Primavera ainda não faturadas. Descontadas automaticamente à medida que as vendas são faturadas no mesmo mês.', formula:(mesIdx) => {
            const conf = (f9Data  && mesIdx != null) ? (f9Data[mesIdx]  || 0) : 0;
            const fat  = mesIdx != null ? (ytdMensal[mesIdx] || 0) : 0;
            const rest = Math.max(0, conf - fat);
            const mn   = mesIdx != null ? CONS_MESES[mesIdx] : 'Mês';
            return `= MAX(0, ECW Primavera − Vendas Faturadas)\n\n${mn}:\n  ECW Primavera:        ${cE(conf)}\n  Vendas Faturadas:    −${cE(fat)}\n  ─────────────────────\n  Restante:             ${cE(rest)}`;
          }, m:crtEncConf, ytd:crtEncConfYTD, res:'—', ano:'—' },
        { l:'Taxa de Concretização', mk:'taxa_concretizacao', obj:'≥70%', tip:'Valor faturado com origem em encomendas potenciais ÷ valor potencial registado (3 meses móveis). Calibra o β da previsão. Não entra no rating v0.2.', formula:'= Σ Faturado(M, M-1, M-2) ÷ Σ Potencial registado(M, M-1, M-2) × 100\nFonte: comercial_ytd_snap ÷ rpc_encomendas', m:crtTaxaConc, ytd:crtTaxaConcYTD, res:['—','m'], ano:'—' },
      ],
    },
    {
      id: 'D', label: 'Resultado',
      grpBg: 'color-mix(in oklch,#22c55e 5%,transparent)',
      rows: [
        {
          l:'Objectivo BP', obj:'—', tip:'Objetivo mensal de vendas do Business Plan. Definido no início do ano fiscal. Fonte: comercial_bp_targets (portal BD).', formula:'Fonte: tabela comercial_bp_targets\nInserido pelo gestor de equipa por vendedor/mês\nBP YTD = Σ target Jan → mês actual',
          m: bpMensal.map((v,i) => v!=null ? cE(v) : '—'),
          ytd: bpAnual>0 ? [cE(bpAnual),'b'] : ['—','m'],
          res: '—',
          ano: '—',
        },
        {
          l:'BP Desvio', note:4, obj:'—', tip:'Desvio acumulado = Vendas Fact. acumuladas − Objetivo BP acumulado. Positivo (verde) = acima do objetivo. Negativo (vermelho) = abaixo.', formula:'Desvio = Σ Vendas Fact.(Jan→mês) − Σ BP(Jan→mês)\n\nPositivo (verde)  = acima do objectivo\nNegativo (vermelho) = abaixo do objectivo',
          m: desvioByMonth.map((d,i) => {
            if (i > mes) return ['—','f m'];
            if (d == null) return ['—','m'];
            return [cSign(d), 'b '+(d>=0?'p':'n')];
          }),
          ytd: [cSign(desvioYTD), 'b '+(desvioYTD>=0?'p':'n')],
          res: '—',
          ano: ['—','m'],
        },
        ...(isDecalPTES ? [{
          l:'Desvio Previsão', obj:'—', tip:'Ajuste manual à previsão do sistema. Positivo aumenta, negativo diminui. Editável no mês corrente e seguintes. Meses fechados sem edição.',
          formula:'Ajuste = Previsão sistema + Desvio manual\n\nEditável no mês corrente e meses seguintes\nSó Decal PT e Decal ES\nGrava em comercial_previsao_desvio',
          m: CONS_MESES.map((_, i) => {
            if (i < mes) return ['—', 'm'];
            const d = desvioArr[i];
            if (d == null || d === 0) return ['—', 'm'];
            return [d > 0 ? '+'+cE(d) : cE(d), d > 0 ? 'p' : 'n'];
          }),
          ytd: (() => {
            const sum = desvioArr.slice(mes).reduce((s, v) => s + (v || 0), 0);
            if (!sum) return ['—', 'm'];
            return [sum > 0 ? '+'+cE(sum) : cE(sum), 'b '+(sum > 0 ? 'p' : 'n')];
          })(),
          res: '—', ano: '—',
        }] : []),
        { l:'Previsão Vendas', note:4, obj:'—', tip:'Passado: faturado real. Mês corrente: faturado + ECW ainda por faturar + potencial não confirmado. Rollover automático do não executado do mês anterior. Futuro: homólogo ano passado × crescimento YTD.', formula: (mesIdx) => {
          if (mesIdx == null) return 'Passado: ytd_snap\nMês corrente: faturado + ECW_restante + pot_restante\nRollover: não executado do mês anterior se pot_atual = 0\nFuturo: homólogo × (1 + crescimento%)\nFonte: f9_snap + rpc_encomendas';
          const i = mesIdx;
          if (i < mes) {
            const v = ytdMensal[i] || 0;
            return `Passado — faturado real\n\nFaturado (ytd_snap):  ${cE(v)}\n\nFonte: comercial_ytd_snap`;
          }
          if (isDecalPTES) {
            const prev = cd?.prevYearPerMes?.[i];
            const gPct = (growthRate * 100).toFixed(1);
            const base = prev != null ? prev * (1 + growthRate) : null;
            const desvio = desvioArr[i] || 0;
            const total = base != null ? base + desvio : null;
            const desvioLine = desvio !== 0 ? `\nDesvio manual:         ${desvio > 0 ? '+' : ''}${cE(desvio)}` : '';
            return `Homólogo × crescimento${desvio !== 0 ? ' + desvio' : ''}\n\nHomólogo ${CONS_ANO_DEF - 1}:        ${cE(prev)}\nCrescimento YTD:       × ${growthRate >= 0 ? '+' : ''}${gPct}%${desvioLine}\n──────────────────────────\nPrevisão:                ${cE(total)}\n\nFonte: ytd_snap`;
          }
          if (i === mes) {
            const fat     = ytdMensal[i] || 0;
            const conf    = f9Data?.[i] || 0;
            const potBase = encData?.potencialPerMes[i] || 0;
            const pot     = potBase + rolloverPot;
            const ecwRest = conf - fat;
            const potRest = Math.max(0, pot - conf);
            const ecwSign = ecwRest >= 0 ? '+' : '';
            const rolloverLine = rolloverPot > 0 ? `\nRollover Set→Out:      + ${cE(rolloverPot)}` : '';
            const totalFull = fat + Math.max(0, ecwRest) + potRest;
            return `Mês corrente — faturado + pipeline pendente\n\nFaturado:              ${cE(fat)}\nECW ainda p/ faturar:  ${ecwSign}${cE(ecwRest)}  (ECW ${cE(conf)} − fat ${cE(fat)})\nPot. não confirmado:   + ${cE(potRest)}  (pot ${cE(pot)} − ECW ${cE(conf)})${rolloverLine}\n──────────────────────────────\nPrevisão total:           ${cE(totalFull)}\n\nFonte: f9_snap + rpc_encomendas`;
          }
          const prev = cd?.prevYearPerMes?.[i];
          const gPct = (growthRate * 100).toFixed(1);
          const forecast = prev != null ? prev * (1 + growthRate) : null;
          return `Futuro — homólogo × crescimento YTD\n\nHomólogo ${CONS_ANO_DEF - 1}:        ${cE(prev)}\nCrescimento YTD:       × ${growthRate >= 0 ? '+' : ''}${gPct}%\n──────────────────────────\nPrevisão:                ${cE(forecast)}\n\nFonte: ytd_snap`;
        }, m:crtPrevisao, ytd:crtPrevisaoYTD, res:'—', ano:'—' },
        { l:'Vendas Carteira',       obj:'—', tip:'Vendas a clientes que já compraram em 2025 (recorrentes). Separa crescimento orgânico de novos clientes. Fonte: Primavera ytd_snap.', formula:'= Σ vendas a clientes presentes em 2025 E em 2026\nFonte: comercial_ytd_snap', m:crtVCart, ytd:crtVCartYTD, res:'—', ano:'—' },
        { l:'Vendas Novos Clientes', obj:'—', tip:'Vendas a clientes sem histórico em 2025 (1.ª compra em 2026). Mede crescimento orgânico de nova carteira. Fonte: Primavera ytd_snap.', formula:'= Σ vendas a clientes sem fatura em 2025\nFonte: comercial_ytd_snap', m:crtVNovos, ytd:crtVNovosYTD, res:'—', ano:'—' },
        {
          l:'Vendas Fact.', obj:'—', tip:'Total de vendas faturadas no Primavera. Inclui todos os tipos de documento de venda activos. Fonte: comercial_ytd_snap (sync diário).', formula:'Fonte: comercial_ytd_snap (sync Primavera diário às 03h)\nTipos de doc activos: FA, VD, FR, FAC, FAT, FAD\nVendas YTD = Σ valor Jan → mês actual',
          m: ytdMensal.map((v,i) => i>mes ? ['—','m'] : (v!=null ? cE(v) : '—')),
          ytd: vendasYTD>0 ? [cE(vendasYTD),'b'] : ['—','m'],
          res: '—',
          ano: '—',
        },
        {
          l:'Margem', mk:'margem', obj:'35%', tip:'Margem bruta % = (Vendas − Custo mercadoria) ÷ Vendas. Obj ≥35%. Verde ≥35%, Amarelo ≥25%, Vermelho <25%. Fonte: comercial_ytd_snap.', formula:'Margem % = Σ margem_bruta ÷ Σ vendas × 100\n\nVerde  ≥ 35%\nAmarelo ≥ 25%\nVermelho < 25%',
          m: margemPctByMonth.map((pct,i) => {
            if (i > mes) return ['—','m'];
            if (pct == null) return ['—','m'];
            const s = pct.toFixed(1).replace('.',',')+'%';
            const cls = pct>=margemObj?'p':pct>=margemYellow?'a':'n';
            return [s, cls];
          }),
          ytd: margemYTDPct!=null
            ? [margemYTDPct.toFixed(1).replace('.',',')+'%', 'b '+(margemYTDPct>=margemObj?'p':margemYTDPct>=margemYellow?'a':'n')]
            : ['—','m'],
          res: margemYTDPct!=null
            ? [(margemYTDPct-margemObj>=0?'+':'')+(margemYTDPct-margemObj).toFixed(1).replace('.',',')+'pp', margemYTDPct>=margemObj?'p':margemYTDPct>=margemYellow?'a':'n']
            : ['—','m'],
          ano: '—',
        },
        {
          l:'RPC Rating', mk:'rpc_rating', obj:'100%', tip:'RPC Rating = (% achievement Vendas + % achievement Margem) / 2. Objectivo: 100%. Verde ≥80, Amarelo ≥60, Vermelho <60. Fonte: Primavera + BP.', formula:'Rating = (% achievement Vendas + % achievement Margem) / 2\n\n% achievement Vendas = Vendas YTD ÷ BP YTD × 100\n% achievement Margem = Margem YTD% ÷ 35% × 100\n\nVerde ≥ 80 · Amarelo ≥ 60 · Vermelho < 60',
          m: ratingMensal.map(v => v == null ? ['—','m'] : [v.toFixed(0)+'%', v>=80?'p':v>=60?'a':'n']),
          ytd: ratingYTD!=null
            ? [ratingYTD.toFixed(0)+'%', 'b '+(ratingYTD>=80?'p':ratingYTD>=60?'a':'n')]
            : ['—','m'],
          res: ratingYTD!=null
            ? [(ratingYTD-100>=0?'+':'')+(ratingYTD-100).toFixed(0)+'pp', ratingYTD>=80?'p':ratingYTD>=60?'a':'n']
            : ['—','m'],
          ano: '—',
        },
      ],
    },
  ].map(sec => {
    let rows = sec.rows;
    if (sec.id === 'A' && (empresa === 'DECAL_PT' || empresa === 'DECAL_EX')) rows = rows.filter(r => r.l !== 'Retenção 12m');
    if (sec.id === 'ENC') {
      rows = rows.filter(r => {
        if (empresa === 'DECAL_PT') return false;
        if (empresa === 'DECAL_EX' && r.l === 'Taxa de Concretização') return false;
        return true;
      });
    }
    if (sec.id === 'D') rows = rows.filter(r => r.l !== 'Vendas Carteira' && r.l !== 'Vendas Novos Clientes');
    return { ...sec, rows };
  }).filter(sec => sec.rows.length > 0);

  // ─── Render ───────────────────────────────────────────────────────────────
  return (
    <div>
      <ConsInfoModal info={infoModal} onClose={() => setInfoModal(null)} />
      {/* Active note */}
      {activeNote && (() => {
        const note = CONS_NOTES.find(x => x.n === activeNote);
        return note ? (
          <div style={{ marginBottom:12, padding:'8px 14px', borderRadius:7, background:'color-mix(in oklch,#3b82f6 8%,transparent)', border:'1px solid color-mix(in oklch,#3b82f6 20%,transparent)', fontSize:12, color:'var(--dgd-fg-1)', lineHeight:1.55 }}>
            <strong>Nota {CONS_SUPS[note.n-1]}:</strong> {note.text}
          </div>
        ) : null;
      })()}

      {/* Table */}
      <div style={{ overflowX:'auto', borderRadius:8, border:'1px solid var(--dgd-border-1)' }}>
        <table style={{ borderCollapse:'collapse', width:'100%', minWidth:1200 }}>
          <thead>
            <tr>
              <th style={{ ...thB, textAlign:'left', minWidth:230, position:'sticky', left:0, zIndex:2 }}>—</th>
              <th style={{ ...thB, color:'#7c3aed', background:'color-mix(in oklch,#7c3aed 6%,transparent)', minWidth:70 }}>Obj.</th>
              {CONS_MESES.map((m, i) => (
                <th key={m} style={{
                  ...thB,
                  color:      i===mes ? '#3563c9' : i>mes ? 'var(--dgd-fg-3)' : undefined,
                  background: i===mes ? 'color-mix(in oklch,#3b82f6 7%,transparent)' : undefined,
                  minWidth: 52,
                }}>{m}</th>
              ))}
              <th style={{ ...thB, borderLeft:'2px solid var(--dgd-border-1)', minWidth:64 }}>Total</th>
              <th style={{ ...thB, borderLeft:'1px solid var(--dgd-border-1)', minWidth:64 }}>Result.</th>
            </tr>
          </thead>
          <tbody>
            {sections.map((sec, si) => (
              <React.Fragment key={sec.id}>
                {/* Section label row */}
                <tr>
                  <td colSpan={17} style={{ padding:'3px 7px', fontSize:9, letterSpacing:'0.12em', textTransform:'uppercase', fontWeight:700, color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', background:'var(--dgd-bg-surface)', borderTop: si>0 ? '2px solid var(--dgd-border-1)' : undefined, borderBottom:'1px solid var(--dgd-border-1)' }}>
                    {sec.label}
                  </td>
                </tr>
                {sec.rows.map((row, ri) => {
                  const ytdC = parseCell(row.ytd);
                  const resC = parseCell(row.res);

                  const btop = {};
                  const bg = sec.grpBg || 'transparent';
                  const isBPRow = row.l === 'Objectivo BP';
                  const isDesvioRow = row.l === 'Desvio Previsão';
                  return (
                    <tr key={ri} style={{ background: bg }}>
                      <td style={{ ...tdB, textAlign:'left', position:'sticky', left:0, background: bg, zIndex:1, color:'var(--dgd-fg-1)', ...btop }}>
                        {row.l}
                        {isBPRow && com && canEditBP && (
                          <span style={{ marginLeft:6, fontSize:9, color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', letterSpacing:'0.06em' }}>
                            {saving ? 'a guardar…' : '✎ clique para editar'}
                          </span>
                        )}
                        {isDesvioRow && com && canEditDesvio && (
                          <span style={{ marginLeft:6, fontSize:9, color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', letterSpacing:'0.06em' }}>
                            {savingDesvio ? 'a guardar…' : '✎ clique para editar'}
                          </span>
                        )}
                        {row.tip && <ConsInfoTip text={row.tip} />}
                        {row.note && <ConsNoteBadge n={row.note} {...noteProps} />}
                      </td>
                      <td style={{ ...tdB, color:'#7c3aed', background:'color-mix(in oklch,#7c3aed 6%,transparent)', fontWeight:500, ...btop, position:'relative' }}>
                        {row.mk && canEditBP && com ? (
                          editObjMk === row.mk ? (
                            <input
                              autoFocus
                              defaultValue={objEdits?.[row.mk] ?? row.obj}
                              style={{ width:60, fontSize:10.5, fontFamily:'var(--dgd-font-mono,monospace)', textAlign:'right', border:'1.5px solid #7c3aed', borderRadius:3, padding:'1px 3px', background:'white', outline:'none', color:'#7c3aed' }}
                              onKeyDown={e => {
                                if (e.key === 'Enter') saveObj(row.mk, e.target.value);
                                if (e.key === 'Escape') setEditObjMk(null);
                              }}
                              onBlur={e => saveObj(row.mk, e.target.value)}
                            />
                          ) : (
                            <span
                              onClick={() => setEditObjMk(row.mk)}
                              title="Clique para editar objectivo"
                              style={{ cursor:'text', display:'block', width:'100%' }}
                            >
                              {objSaving === row.mk ? '✓' : (objEdits?.[row.mk] ?? row.obj)}
                            </span>
                          )
                        ) : (objEdits?.[row.mk] ?? row.obj)}
                      </td>
                      {row.m.map((v, i) => {
                        const {val, cls} = parseCell(v);
                        const isCur = i === mes;
                        const isFut = i > mes;

                        // ─ Desvio Previsão editable cell (Decal PT/ES, mês corrente e futuros)
                        if (isDesvioRow && com && canEditDesvio && i >= mes) {
                          const isEditing = editDesvioCell?.mesIdx === i;
                          if (isEditing) {
                            return (
                              <td key={i} style={{ ...tdB, background: isCur ? 'color-mix(in oklch,#3b82f6 7%,transparent)' : bg, padding:'2px 4px' }}>
                                <input
                                  autoFocus
                                  defaultValue={editDesvioCell.val}
                                  style={{ width:58, fontSize:10.5, fontFamily:'var(--dgd-font-mono,monospace)', textAlign:'right', border:'1.5px solid #22c55e', borderRadius:3, padding:'1px 3px', background:'white', outline:'none' }}
                                  onKeyDown={e => {
                                    if (e.key === 'Enter') saveDesvio(i, e.target.value);
                                    if (e.key === 'Escape') setEditDesvioCell(null);
                                  }}
                                  onBlur={e => saveDesvio(i, e.target.value)}
                                />
                              </td>
                            );
                          }
                          const curDesvio = desvioArr[i];
                          const curVal = curDesvio != null ? Math.round(curDesvio) : '';
                          return (
                            <td key={i}
                              onClick={() => setEditDesvioCell({ mesIdx: i, val: curVal !== '' ? String(curVal) : '' })}
                              title="Clique para editar desvio de previsão"
                              style={{ ...tdB, background: isCur ? 'color-mix(in oklch,#3b82f6 7%,transparent)' : bg, ...cCls(cls), cursor:'text', ...btop }}>{val}</td>
                          );
                        }

                        // ─ BP editable cell
                        if (isBPRow && com && canEditBP) {
                          const isEditing = editCell?.mesIdx === i;
                          if (isEditing) {
                            return (
                              <td key={i} style={{ ...tdB, background: isCur ? 'color-mix(in oklch,#3b82f6 7%,transparent)' : bg, padding:'2px 4px' }}>
                                <input
                                  autoFocus
                                  defaultValue={editCell.val}
                                  style={{ width:52, fontSize:10.5, fontFamily:'var(--dgd-font-mono,monospace)', textAlign:'right', border:'1.5px solid #3b82f6', borderRadius:3, padding:'1px 3px', background:'white', outline:'none' }}
                                  onKeyDown={e => {
                                    if (e.key === 'Enter') saveBP(i, e.target.value);
                                    if (e.key === 'Escape') setEditCell(null);
                                  }}
                                  onBlur={e => saveBP(i, e.target.value)}
                                />
                              </td>
                            );
                          }
                          return (
                            <td key={i}
                              onClick={() => setEditCell({ mesIdx: i, val: bpMensal[i] ? String(Math.round(bpMensal[i])) : '' })}
                              title="Clique para editar objectivo BP"
                              style={{ ...tdB, background: isCur ? 'color-mix(in oklch,#3b82f6 7%,transparent)' : bg, ...cCls(cls), cursor:'text', ...btop }}>{val}</td>
                          );
                        }

                        const clickable = (row.tip || row.formula) && (!isFut || row.l === 'Previsão Vendas');
                        return (
                          <td key={i}
                            onClick={clickable ? () => openCell(row, sec, val, CONS_MESES[i], i) : undefined}
                            style={{
                              ...tdB,
                              background: isCur ? 'color-mix(in oklch,#3b82f6 7%,transparent)' : bg,
                              color: (isFut && !cls) ? 'var(--dgd-fg-3)' : undefined,
                              ...cCls(cls),
                              ...btop,
                              cursor: clickable ? 'pointer' : 'default',
                            }}>{val}</td>
                        );
                      })}
                      <td onClick={() => openCell(row, sec, ytdC.val, 'YTD', null)} style={{ ...tdB, borderLeft:'2px solid var(--dgd-border-1)', fontWeight:600, ...cCls(ytdC.cls), ...btop, cursor:(row.tip||row.formula)?'pointer':'default' }}>{ytdC.val}</td>
                      <td onClick={() => openCell(row, sec, resC.val, 'Resultado', null)} style={{ ...tdB, borderLeft:'1px solid var(--dgd-border-1)', ...cCls(resC.cls), ...btop, cursor:(row.tip||row.formula)?'pointer':'default' }}>{resC.val}</td>
                    </tr>
                  );
                })}
              </React.Fragment>
            ))}
          </tbody>
        </table>
      </div>

      {/* Phase 1 notice */}
      <div style={{ marginTop:16, padding:'10px 14px', borderRadius:7, background:'color-mix(in oklch,#3b82f6 6%,transparent)', border:'1px solid color-mix(in oklch,#3b82f6 25%,transparent)', fontSize:11.5, color:'#1e40af', lineHeight:1.5 }}>
        <strong>Dados em tempo real:</strong> BP · Vendas Fact. · Margem · BP Desvio (Primavera) · Leads / OP Mkt · Leads / OP Com · OP Criadas (valor) · F9FACTURAR (GestorConnect FM).
        {' '}Reuniões SPIN · Carteira · SPIN Potencial disponíveis na <strong>Fase 2</strong>.
      </div>

      {/* Notes legend */}
      <div style={{ marginTop:10, display:'flex', flexWrap:'wrap', gap:'6px 16px' }}>
        {CONS_NOTES.map(note => (
          <span key={note.n}
            onClick={() => setActiveNote(activeNote===note.n ? null : note.n)}
            style={{ fontSize:11, color:'var(--dgd-fg-3)', cursor:'pointer', textDecoration: activeNote===note.n ? 'underline' : 'none' }}>
            {CONS_SUPS[note.n-1]} {note.text.split('.')[0]}.
          </span>
        ))}
      </div>
    </div>
  );
};

// ─── Tab: Show me the Money ────────────────────────────────────────────────────
const TabShowMoneyCons = ({ comerciais, byV, margemByV, ano }) => {
  const mes = CONS_MES_FECH;
  const [modal, setModal] = React.useState(null);

  if (!comerciais || comerciais.length === 0) {
    return <div style={{ padding:'40px', textAlign:'center', color:'var(--dgd-fg-3)', fontSize:13 }}>Sem comerciais para esta marca.</div>;
  }

  const totVendas = new Array(12).fill(0);
  const totMargem = new Array(12).fill(0);

  comerciais.forEach(c => {
    (byV[c.vendedor]||[]).forEach((v,i) => { if(v!=null) totVendas[i] += v; });
    (margemByV[c.vendedor]||[]).forEach((v,i) => { if(v!=null) totMargem[i] += v; });
  });

  const ytdTot = totVendas.slice(0, mes+1).reduce((s,v) => s+v, 0);

  return (
    <>
    <ConsInfoModal info={modal} onClose={() => setModal(null)} />
    <div style={{ overflowX:'auto', borderRadius:8, border:'1px solid var(--dgd-border-1)' }}>
      <table style={{ borderCollapse:'collapse', width:'100%', minWidth:1100 }}>
        <thead>
          <tr>
            <th style={{ ...thB, textAlign:'left', minWidth:180, position:'sticky', left:0, background:'var(--dgd-bg-card,white)' }}>Comercial</th>
            {CONS_MESES.map((m, i) => (
              <th key={m} style={{ ...thB, color: i===mes?'#3563c9':i>mes?'var(--dgd-fg-3)':undefined, background: i===mes?'color-mix(in oklch,#3b82f6 7%,transparent)':undefined, minWidth:52 }}>{m}</th>
            ))}
            <th style={{ ...thB, borderLeft:'2px solid var(--dgd-border-1)', minWidth:64 }}>YTD</th>
            <th style={{ ...thB, borderLeft:'1px solid var(--dgd-border-1)', minWidth:52 }}>Margem</th>
          </tr>
        </thead>
        <tbody>
          {comerciais.map((c, ci) => {
            const vals = byV[c.vendedor] || new Array(12).fill(null);
            const mvals = margemByV[c.vendedor] || new Array(12).fill(null);
            const ytd = vals.slice(0, mes+1).reduce((s,v) => s+(v||0), 0);
            const ytdM = mvals.slice(0, mes+1).reduce((s,v) => s+(v||0), 0);
            const margemPct = ytd > 0 ? (ytdM/ytd)*100 : null;
            const isLast = ci === comerciais.length - 1;
            return (
              <tr key={c.id} style={{ background: ci%2===0?'var(--dgd-bg-card,white)':'var(--dgd-bg-surface,#fafafa)' }}>
                <td style={{ ...tdB, textAlign:'left', position:'sticky', left:0, background: ci%2===0?'var(--dgd-bg-card,white)':'var(--dgd-bg-surface,#fafafa)', color:'var(--dgd-fg-1)', fontWeight:500, borderBottom:isLast?'2px solid var(--dgd-border-1)':undefined }}>{c.nome}</td>
                {vals.map((v, i) => {
                  const clickable = i <= mes && v != null;
                  const cellVal = i > mes ? '—' : v != null ? cE(v) : '—';
                  return (
                    <td key={i}
                      onClick={clickable ? () => setModal({ label:c.nome, section:'Show me the Money', tip:`Vendas faturadas por ${c.nome} em ${CONS_MESES[i]} ${ano}.`, formula:'Fonte: comercial_ytd_snap (sync diário às 03h)\nDetalhe por família e artigo.', val:cellVal, period:`${CONS_MESES[i]} ${ano}`, drillParams:{ vendedor:c.vendedor, vendedorNome:c.nome, ano, mes:i } }) : undefined}
                      style={{ ...tdB, color:i>mes?'var(--dgd-fg-3)':v==null?'var(--dgd-fg-3)':undefined, background:i===mes?'color-mix(in oklch,#3b82f6 4%,transparent)':undefined, borderBottom:isLast?'2px solid var(--dgd-border-1)':undefined, cursor:clickable?'pointer':'default' }}>
                      {cellVal}
                    </td>
                  );
                })}
                <td
                  onClick={ytd>0 ? () => setModal({ label:c.nome, section:'Show me the Money', tip:`Vendas YTD de ${c.nome} em ${ano}.`, formula:'Fonte: comercial_ytd_snap — acumulado anual.', val:ytd>0?cE(ytd):'—', period:`YTD ${ano}`, drillParams:{ vendedor:c.vendedor, vendedorNome:c.nome, ano, mes:null } }) : undefined}
                  style={{ ...tdB, fontWeight:700, borderLeft:'2px solid var(--dgd-border-1)', color:'var(--dgd-fg-1)', borderBottom:isLast?'2px solid var(--dgd-border-1)':undefined, cursor:ytd>0?'pointer':'default' }}>{ytd>0?cE(ytd):'—'}</td>
                <td style={{ ...tdB, borderLeft:'1px solid var(--dgd-border-1)', color: margemPct==null?'var(--dgd-fg-3)':margemPct>=35?'#16a34a':margemPct>=25?'#d97706':'#dc2626', borderBottom:isLast?'2px solid var(--dgd-border-1)':undefined }}>
                  {margemPct!=null ? margemPct.toFixed(1).replace('.',',')+'%' : '—'}
                </td>
              </tr>
            );
          })}
          {/* Totals row */}
          <tr style={{ background:'var(--dgd-bg-surface,#f8fafc)' }}>
            <td style={{ ...tdB, textAlign:'left', position:'sticky', left:0, background:'var(--dgd-bg-surface,#f8fafc)', color:'var(--dgd-fg-2)', fontWeight:700, fontSize:11, letterSpacing:'0.08em', textTransform:'uppercase' }}>Totais</td>
            {totVendas.map((v, i) => (
              <td key={i} style={{ ...tdB, color: i>mes?'var(--dgd-fg-3)':undefined, background: i===mes?'color-mix(in oklch,#3b82f6 4%,transparent)':undefined, fontWeight:600 }}>
                {i>mes ? '—' : v>0 ? cE(v) : '—'}
              </td>
            ))}
            <td style={{ ...tdB, fontWeight:700, borderLeft:'2px solid var(--dgd-border-1)', color:'var(--dgd-fg-1)' }}>{ytdTot>0?cE(ytdTot):'—'}</td>
            <td style={{ ...tdB, borderLeft:'1px solid var(--dgd-border-1)', color:'var(--dgd-fg-2)' }}>
              {(() => {
                const m = totMargem.slice(0,mes+1).reduce((s,v)=>s+v,0);
                return ytdTot>0 ? (m/ytdTot*100).toFixed(1).replace('.',',')+'%' : '—';
              })()}
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    </>
  );
};

// ─── Tab: YTD Famílias ─────────────────────────────────────────────────────────
const TabYTDFamiliasCons = ({ ytdRows, selCom, vendedoresFiltrados }) => {
  const mes = CONS_MES_FECH;
  const mLabel = CONS_MESES[mes];
  const [modal, setModal] = React.useState(null);

  // Filter rows for selected comercial, or by current equipa/marca if in aggregate mode
  const vSet = React.useMemo(() => new Set(vendedoresFiltrados || []), [vendedoresFiltrados]);
  const rows = selCom
    ? ytdRows.filter(r => r.vendedor === selCom.vendedor)
    : ytdRows.filter(r => vSet.size === 0 || vSet.has(r.vendedor));

  // Aggregate by familia
  const famMap = {};
  rows.forEach(r => {
    if (!famMap[r.familia]) famMap[r.familia] = { familia:r.familia, valor:0, qtd:0 };
    famMap[r.familia].valor += parseFloat(r.valor)||0;
    famMap[r.familia].qtd   += parseFloat(r.qtd)||0;
  });
  const fams = Object.values(famMap).sort((a,b) => b.valor-a.valor);
  const totalValor = fams.reduce((s,f) => s+f.valor, 0);

  if (!fams.length) {
    return (
      <div style={{ padding:'40px', textAlign:'center', color:'var(--dgd-fg-3)', fontSize:13 }}>
        {selCom ? `Sem dados de famílias para ${selCom.nome} em ${CONS_ANO_DEF}.` : `Sem dados de famílias para ${CONS_ANO_DEF}.`}
      </div>
    );
  }

  return (
    <div>
      <ConsInfoModal info={modal} onClose={() => setModal(null)} />
      <div style={{ marginBottom:10, fontSize:12, color:'var(--dgd-fg-3)' }}>
        YTD Jan–{mLabel} · {selCom ? selCom.nome : 'Todas as equipas'} · {fams.length} família{fams.length!==1?'s':''}
      </div>
      <div style={{ overflowX:'auto', borderRadius:8, border:'1px solid var(--dgd-border-1)' }}>
        <table style={{ borderCollapse:'collapse', width:'100%', minWidth:500 }}>
          <thead>
            <tr>
              <th style={{ ...thB, textAlign:'left', minWidth:200 }}>Família</th>
              <th style={{ ...thB }}>Vendas YTD</th>
              <th style={{ ...thB }}>Mix %</th>
              <th style={{ ...thB }}>Potencial SPIN</th>
              <th style={{ ...thB }}>Share of Wallet</th>
              <th style={{ ...thB, color:'#7c3aed' }}>Obj. 2027 proposto</th>
            </tr>
          </thead>
          <tbody>
            {fams.map((f, i) => {
              const mix = totalValor > 0 ? (f.valor/totalValor*100) : 0;
              const barW = Math.round(mix * 1.2);
              const drillData = rows
                .filter(r => r.familia === f.familia)
                .map(r => ({ familia:r.familia, artigo:r.artigo, qtd:r.qtd, valor:r.valor, margem:null }))
                .sort((a,b) => parseFloat(b.valor||0) - parseFloat(a.valor||0));
              return (
                <tr key={f.familia}
                  onClick={() => setModal({ label:f.familia||'(sem família)', section:'YTD Famílias', tip:`Artigos da família ${f.familia||'(sem família)'} — YTD Jan–${mLabel}.`, val:cE(f.valor), period:`YTD ${CONS_ANO_DEF}`, drillData })}
                  style={{ background:i%2===0?'var(--dgd-bg-card,white)':'var(--dgd-bg-surface,#fafafa)', cursor:'pointer' }}>
                  <td style={{ ...tdB, textAlign:'left', color:'var(--dgd-fg-1)' }}>
                    <span style={{ display:'inline-block', width:Math.min(barW,80)+'px', height:5, borderRadius:3, background:'color-mix(in oklch,#3b82f6 55%,transparent)', verticalAlign:'middle', marginRight:8 }} />
                    {f.familia || '(sem família)'}
                  </td>
                  <td style={{ ...tdB, fontWeight:600 }}>{cE(f.valor)}</td>
                  <td style={{ ...tdB, color:'var(--dgd-fg-2)' }}>{mix.toFixed(0)}%</td>
                  <td style={{ ...tdB, color:'var(--dgd-fg-3)' }}>—</td>
                  <td style={{ ...tdB, color:'var(--dgd-fg-3)' }}>—</td>
                  <td style={{ ...tdB, color:'#7c3aed' }}>—</td>
                </tr>
              );
            })}
          </tbody>
          <tfoot>
            <tr>
              <td style={{ ...tdB, textAlign:'left', fontWeight:700, color:'var(--dgd-fg-1)', borderTop:'2px solid var(--dgd-border-1)' }}>Total</td>
              <td style={{ ...tdB, fontWeight:700, borderTop:'2px solid var(--dgd-border-1)' }}>{cE(totalValor)}</td>
              <td style={{ ...tdB, borderTop:'2px solid var(--dgd-border-1)', color:'var(--dgd-fg-2)' }}>100%</td>
              <td colSpan={3} style={{ ...tdB, borderTop:'2px solid var(--dgd-border-1)', color:'var(--dgd-fg-3)', textAlign:'left' }}>Potencial SPIN disponível na Fase 2</td>
            </tr>
          </tfoot>
        </table>
      </div>
    </div>
  );
};

// ─── Tab: Placeholder ──────────────────────────────────────────────────────────
const TabConsPlaceholder = ({ tab }) => {
  const info = {
    forecast:   { title:'Forecast', desc:'Previsão de vendas por comercial: baseline recorrente da carteira (média 6 meses × coef. sazonal) + potencial SPIN ponderado por probabilidade. Disponível na Fase 2.', fase:'Fase 2' },
    predit:     { title:'Preditividade', desc:'Fecho do mês em curso: faturado + encomendas confirmadas + 60% do potencial registado pelo comercial. Drilldown de oportunidades/encomendas ativas. Disponível na Fase 2.', fase:'Fase 2' },
    mediasales: { title:'Media Sales AI', desc:'Análise AI de padrões de compra, alertas de clientes em risco e sugestões de próxima ação por comercial. Disponível na Fase 2.', fase:'Fase 2' },
    sugestoes:  { title:'Sugestões AI', desc:'Acções de melhoria por comercial: famílias com potencial não capturado, clientes sem visita SPIN, comparação entre pares. Disponível na Fase 2.', fase:'Fase 2' },
    previsao:   { title:'Previsão AI', desc:'Modelos preditivos ML (ARIMA/Prophet) adaptados ao modelo de consumíveis: baseline de carteira + sazonalidade histórica + potencial SPIN. Disponível na Fase 3.', fase:'Fase 3' },
  }[tab] || { title: tab, desc: 'Em construção.', fase: 'Fase 2' };

  return (
    <div style={{ display:'flex', alignItems:'center', justifyContent:'center', minHeight:320 }}>
      <div style={{ textAlign:'center', maxWidth:440, padding:'40px 32px', borderRadius:12, background:'var(--dgd-bg-card,white)', border:'1px solid var(--dgd-border-1)', boxShadow:'0 2px 8px rgba(0,0,0,0.06)' }}>
        <div style={{ fontSize:32, marginBottom:12 }}>🔧</div>
        <div style={{ fontSize:15, fontWeight:700, color:'var(--dgd-fg-1)', marginBottom:8 }}>{info.title}</div>
        <div style={{ fontSize:13, color:'var(--dgd-fg-2)', lineHeight:1.6, marginBottom:16 }}>{info.desc}</div>
        <div style={{ display:'inline-block', padding:'4px 14px', borderRadius:20, background:'color-mix(in oklch,#f59e0b 12%,transparent)', border:'1px solid color-mix(in oklch,#f59e0b 30%,transparent)', fontSize:11, fontWeight:600, color:'#92400e' }}>{info.fase}</div>
      </div>
    </div>
  );
};

// ─── Main Screen ───────────────────────────────────────────────────────────────
const ScreenComercialRPCConsumiveis = ({ userTipo, userName, user, vendedorId }) => {
  const isAdminView = !vendedorId; // admin/chairman sem vendedorId: acesso total

  const [tab,      setTab]      = React.useState('rpc');
  const [ano,      setAno]      = React.useState(CONS_ANO_DEF);
  const [marca,    setMarca]    = React.useState('Decal PT');
  const [selEquipa, setSelEquipa] = React.useState(null); // null = todas
  const [selComId, setSelComId] = React.useState(null);

  // Personal view: detecta quais marcas pertencem a este utilizador
  const myMarcas = useMyConsMarcas(!isAdminView ? user?.id : null, !isAdminView ? vendedorId : null);
  const marcasVisiveis = (!isAdminView && myMarcas) ? myMarcas : CONS_MARCAS;

  // Auto-selecciona a primeira marca correcta quando myMarcas carrega
  React.useEffect(() => {
    if (!isAdminView && myMarcas && myMarcas.length > 0 && !myMarcas.includes(marca)) {
      setMarca(myMarcas[0]);
    }
  }, [isAdminView, myMarcas]);

  const empresa = CONS_MARCA_EMP[marca];
  // Em personal view, não carregar dados enquanto myMarcas ainda está a resolver
  // ou enquanto a marca activa ainda não pertence ao utilizador (auto-select pendente)
  const empresaToLoad = (!isAdminView && (myMarcas === null || !myMarcas.includes(marca))) ? null : empresa;

  const [bpRefresh, setBpRefresh] = React.useState(0);
  const handleBpSave = React.useCallback(() => setBpRefresh(k => k + 1), []);


  const { comerciais, loading: comsLoading } = useConsComerciais(empresaToLoad);

  // Hierarquia de vistas:
  //   Admin/Chairman (sem vendedorId) → vê tudo
  //   Manager (com vendedorId, tem equipa) → vê a sua equipa + ele próprio
  //   Comercial (com vendedorId, sem equipa) → vê só ele próprio
  // Usa colaborador_id (não erp_code) para suportar utilizadores com múltiplos canais
  // (ex: Andreia tem EQ7 para equipamento mas C39 para consumíveis — gestor_vendedor_id = EQ7)
  const comerciaisBase = React.useMemo(() => {
    if (!comerciais) return [];
    if (isAdminView) return comerciais;
    const userId = user?.id;
    if (!userId) return comerciais;
    // Mostra: ele próprio + quem ele gere (equipa)
    return comerciais.filter(c => Number(c.colaborador_id) === Number(userId) || Number(c.manager_id) === Number(userId));
  }, [comerciais, isAdminView, user]);
  const { byV, margemByV, ytdRows, loading: ytdLoading } = useConsYTD(ano);
  const bpMap = useConsBP(ano, bpRefresh);

  const fmEmpresa = CONS_MARCA_FM[marca];
  const { byVendedor: fmByV, loading: fmLoading, fromPostgres: fmFromPostgres } = useConsFMPipeline(fmEmpresa, ano);
  const { byVendedor: carteiraByV } = useConsCarteira(ano);

  // Equipas disponíveis (baseado na lista filtrada por hierarquia)
  const equipas = React.useMemo(() => {
    if (!comerciaisBase) return [];
    const seen = new Set();
    return comerciaisBase
      .map(c => c.seccao)
      .filter(s => { if (seen.has(s)) return false; seen.add(s); return true; })
      .sort((a,b) => a.localeCompare(b, 'pt'));
  }, [comerciaisBase]);

  // Reset equipa quando muda de marca
  React.useEffect(() => { setSelEquipa(null); setSelComId(null); }, [marca]);

  // Auto-selecciona se só existe um comercial (view individual)
  React.useEffect(() => {
    if (!isAdminView && comerciaisBase.length === 1 && selComId === null) {
      setSelComId(comerciaisBase[0].id);
    }
  }, [isAdminView, comerciaisBase, selComId]);

  // Comerciais filtrados pela equipa seleccionada
  const comerciaisFiltrados = React.useMemo(() => {
    if (!comerciaisBase) return [];
    if (!selEquipa) return comerciaisBase;
    return comerciaisBase.filter(c => c.seccao === selEquipa);
  }, [comerciaisBase, selEquipa]);

  // "Todos" = selComId null; comercial específico = selComId = c.id
  const isAll = selComId === null;

  const selCom = React.useMemo(() => {
    if (isAll || !comerciaisFiltrados.length) return null;
    return comerciaisFiltrados.find(c => c.id === selComId) || null;
  }, [isAll, comerciaisFiltrados, selComId]);

  const spinData = useConsSPIN(selCom?.vendedor || null, ano);
  const encData  = useConsEncomendas(selCom?.vendedor || null, ano);
  const f9Data   = useConsF9(selCom?.vendedor || null, ano);

  // Agrega arrays mensais de todos os comerciais filtrados
  const aggMensal = React.useCallback((map) => {
    const result = new Array(12).fill(null);
    comerciaisFiltrados.forEach(c => {
      (map?.[c.vendedor] || []).forEach((v, i) => {
        if (v != null) result[i] = (result[i] || 0) + v;
      });
    });
    return result;
  }, [comerciaisFiltrados]);

  const ytdMensal    = React.useMemo(() => isAll ? aggMensal(byV)       : (selCom ? (byV?.[selCom.vendedor]       || new Array(12).fill(null)) : new Array(12).fill(null)), [isAll, selCom, aggMensal, byV]);
  const margemMensal = React.useMemo(() => isAll ? aggMensal(margemByV) : (selCom ? (margemByV?.[selCom.vendedor] || new Array(12).fill(null)) : new Array(12).fill(null)), [isAll, selCom, aggMensal, margemByV]);
  const bpMensal     = React.useMemo(() => isAll ? aggMensal(bpMap)     : (selCom ? (bpMap[selCom.gestor_vendedor_id] || bpMap[selCom.vendedor] || new Array(12).fill(null)) : new Array(12).fill(null)), [isAll, selCom, aggMensal, bpMap]);

  // Vendedores filtrados (para YTD Famílias em modo Todos)
  const vendedoresFiltrados = React.useMemo(() => comerciaisFiltrados.map(c => c.vendedor), [comerciaisFiltrados]);

  // FM pipeline data — agrega para todos os comerciais filtrados (modo Todos) ou por comercial individual
  const FM_KEYS = ['optmkt','optcom','opmanagerf9','wonValor'];
  const aggFMData = React.useMemo(() => {
    if (!fmByV || !comerciaisFiltrados.length) return null;
    const result = {};
    FM_KEYS.forEach(k => { result[k] = new Array(12).fill(0); });
    let hasAny = false;
    comerciaisFiltrados.forEach(c => {
      const vd = fmByV[c.vendedor];
      if (!vd) return;
      hasAny = true;
      FM_KEYS.forEach(k => {
        (vd[k] || []).forEach((v, i) => { result[k][i] += v || 0; });
      });
    });
    return hasAny ? result : null;
  }, [fmByV, comerciaisFiltrados]);

  const fmData = React.useMemo(() => {
    if (isAll) return aggFMData;
    if (!selCom || !fmByV) return null;
    return fmByV[selCom.vendedor] || null;
  }, [isAll, selCom, fmByV, aggFMData]);

  // KPIs strip
  const mLabel   = CONS_MESES[CONS_MES_FECH];
  const ytdTotal = ytdMensal.slice(0, CONS_MES_FECH+1).reduce((s,v) => s+(v||0), 0);
  const bpTotal  = bpMensal.slice(0, CONS_MES_FECH+1).reduce((s,v) => s+(v||0), 0);
  const desvio   = ytdTotal - bpTotal;
  const pctBP    = bpTotal > 0 ? (ytdTotal/bpTotal)*100 : null;
  const kpisLabel = isAll ? (selEquipa || 'Todos os comerciais') : (selCom?.nome || '');

  const kpis = (ytdTotal > 0 || bpTotal > 0) ? [
    { l:'VENDAS YTD', v: cE(ytdTotal), c: ytdTotal>=bpTotal ? '#16a34a' : '#dc2626', d:`Jan–${mLabel} · ${kpisLabel}` },
    { l:'BP YTD',     v: cE(bpTotal),  c: 'var(--dgd-fg-2)', d:`Objectivo acumulado Jan–${mLabel}` },
    { l:'DESVIO',     v: cSign(desvio), c: desvio>=0?'#16a34a':'#dc2626', d:'Vendas − BP acumulado' },
    { l:'% BP',       v: pctBP!=null ? pctBP.toFixed(1).replace('.',',')+'%' : '—', c: pctBP==null?'var(--dgd-fg-3)':pctBP>=100?'#16a34a':pctBP>=85?'#d97706':'#dc2626', d:'Realizado vs objectivo' },
  ] : [];

  const hasData = !!(byV && Object.keys(byV).length > 0);
  const showComSelector = ['rpc','money','ytd'].includes(tab);

  return (
    <div className="scrollbar" style={{height:'100%',overflowY:'auto',background:'var(--dgd-bg-app,#f8fafc)'}}>
      <div style={{padding:'18px 24px 80px'}}>

        {/* Header */}
        <div style={{display:'flex',alignItems:'flex-start',gap:12,marginBottom:16,flexWrap:'wrap'}}>
          <div style={{flex:1}}>
            <div style={{fontSize:10,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.1em',marginBottom:2}}>COMERCIAL · RPC CONSUMÍVEIS · {ano}</div>
            <h2 className="font-display" style={{margin:0,fontSize:21,fontWeight:600,letterSpacing:'-0.01em',color:'var(--dgd-fg-1)'}}>
              RPC Consumíveis
              {hasData
                ? <span style={{fontSize:11,fontWeight:400,color:'#166534',marginLeft:10,fontFamily:'var(--dgd-font-mono,monospace)',background:'color-mix(in oklch,#22c55e 10%,transparent)',padding:'2px 7px',borderRadius:4,border:'1px solid color-mix(in oklch,#22c55e 35%,transparent)'}}>REAL</span>
                : <span style={{fontSize:11,fontWeight:400,color:'#b45309',marginLeft:10,fontFamily:'var(--dgd-font-mono,monospace)',background:'color-mix(in oklch,#f59e0b 10%,transparent)',padding:'2px 7px',borderRadius:4,border:'1px solid color-mix(in oklch,#f59e0b 35%,transparent)'}}>A CARREGAR</span>
              }
            </h2>
          </div>
          {/* Ano segmented control */}
          <div style={{display:'flex',border:'1px solid var(--dgd-border-1)',borderRadius:7,overflow:'hidden',background:'var(--dgd-bg-card,white)'}}>
            {[2025,2026].map(y => (
              <button key={y} onClick={() => setAno(y)}
                style={{padding:'4px 11px',fontSize:12,fontWeight:y===ano?700:400,background:y===ano?'var(--dgd-fg-1)':'transparent',color:y===ano?'white':'var(--dgd-fg-2)',border:'none',cursor:'pointer'}}>
                {y}
              </button>
            ))}
          </div>
        </div>

        {/* Marca pills — oculta se só existe uma marca visível (view pessoal mono-marca) */}
        {marcasVisiveis.length > 1 && (
          <div style={{display:'flex',gap:6,marginBottom:8,flexWrap:'wrap',alignItems:'center'}}>
            <span style={{fontSize:10,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.08em',minWidth:52}}>MARCA</span>
            {marcasVisiveis.map(m => (
              <button key={m} onClick={() => setMarca(m)}
                style={{padding:'4px 10px',borderRadius:6,fontSize:11,fontWeight:marca===m?700:400,background:marca===m?'var(--dgd-fg-1)':'var(--dgd-bg-card,white)',color:marca===m?'white':'var(--dgd-fg-2)',border:'1px solid '+(marca===m?'var(--dgd-fg-1)':'var(--dgd-border-1)'),cursor:'pointer'}}>
                {m}
              </button>
            ))}
            {comsLoading && <span style={{fontSize:11,color:'var(--dgd-fg-3)'}}>A carregar...</span>}
          </div>
        )}

        {/* Equipa pills */}
        {showComSelector && equipas.length > 1 && (
          <div style={{display:'flex',gap:6,marginBottom:8,flexWrap:'wrap',alignItems:'center'}}>
            <span style={{fontSize:10,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.08em',minWidth:52}}>EQUIPA</span>
            <button onClick={() => { setSelEquipa(null); setSelComId(null); }}
              style={{padding:'4px 10px',borderRadius:6,fontSize:11,fontWeight:selEquipa===null?700:400,background:selEquipa===null?'#6366f1':'var(--dgd-bg-card,white)',color:selEquipa===null?'white':'var(--dgd-fg-2)',border:'1px solid '+(selEquipa===null?'#6366f1':'var(--dgd-border-1)'),cursor:'pointer'}}>
              Todas
            </button>
            {equipas.map(eq => (
              <button key={eq} onClick={() => { setSelEquipa(eq); setSelComId(null); }}
                style={{padding:'4px 10px',borderRadius:6,fontSize:11,fontWeight:selEquipa===eq?700:400,background:selEquipa===eq?'#6366f1':'var(--dgd-bg-card,white)',color:selEquipa===eq?'white':'var(--dgd-fg-2)',border:'1px solid '+(selEquipa===eq?'#6366f1':'var(--dgd-border-1)'),cursor:'pointer'}}>
                {eq}
              </button>
            ))}
          </div>
        )}

        {/* Comercial pills (só nos tabs que precisam) */}
        {showComSelector && comerciaisFiltrados.length > 0 && (
          <div style={{display:'flex',gap:6,marginBottom:14,flexWrap:'wrap',alignItems:'center'}}>
            <span style={{fontSize:10,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.08em',minWidth:52}}>COMERCIAL</span>
            <button onClick={() => setSelComId(null)}
              style={{padding:'4px 10px',borderRadius:6,fontSize:11,fontWeight:isAll?700:400,background:isAll?'var(--dgd-fg-1)':'var(--dgd-bg-card,white)',color:isAll?'white':'var(--dgd-fg-2)',border:'1px solid '+(isAll?'var(--dgd-fg-1)':'var(--dgd-border-1)'),cursor:'pointer'}}>
              Todos
            </button>
            {comerciaisFiltrados.map(c => (
              <button key={c.id} onClick={() => setSelComId(c.id)}
                style={{padding:'4px 10px',borderRadius:6,fontSize:11,fontWeight:selCom?.id===c.id?700:400,background:selCom?.id===c.id?'var(--dgd-fg-1)':'var(--dgd-bg-card,white)',color:selCom?.id===c.id?'white':'var(--dgd-fg-2)',border:'1px solid '+(selCom?.id===c.id?'var(--dgd-fg-1)':'var(--dgd-border-1)'),cursor:'pointer'}}>
                {c.nome}
              </button>
            ))}
          </div>
        )}

        {/* Sem comerciais */}
        {!comsLoading && comerciaisFiltrados.length === 0 && comerciaisBase.length > 0 && selEquipa && (
          <div style={{marginBottom:12,padding:'8px 12px',borderRadius:6,background:'color-mix(in oklch,#6366f1 8%,transparent)',border:'1px solid color-mix(in oklch,#6366f1 25%,transparent)',fontSize:11,color:'#3730a3'}}>
            Sem comerciais na equipa <strong>{selEquipa}</strong>.
          </div>
        )}
        {!comsLoading && comerciaisBase.length === 0 && comerciais && (
          <div style={{marginBottom:12,padding:'8px 12px',borderRadius:6,background:'color-mix(in oklch,#f59e0b 8%,transparent)',border:'1px solid color-mix(in oklch,#f59e0b 25%,transparent)',fontSize:11,color:'#92400e'}}>
            Sem comerciais para <strong>{marca}</strong> (empresa: {empresa}). Verificar <code>colaborador_erp_ids</code>.
          </div>
        )}

        {/* KPIs rápidos */}
        {kpis.length > 0 && (
          <div style={{display:'flex',gap:8,marginBottom:14,flexWrap:'wrap'}}>
            {kpis.map(k => (
              <div key={k.l} style={{flex:1,minWidth:120,padding:'10px 18px',borderRadius:8,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)'}}>
                <div style={{fontSize:10,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.08em',marginBottom:3}}>{k.l}</div>
                <div className="font-display" style={{fontSize:22,fontWeight:700,color:k.c,letterSpacing:'-0.02em',lineHeight:1.1}}>{k.v}</div>
                <div style={{fontSize:10.5,color:'var(--dgd-fg-3)',marginTop:4}}>{k.d}</div>
              </div>
            ))}
          </div>
        )}

        {/* Sub-tabs */}
        <div style={{display:'flex',gap:2,marginBottom:16,borderBottom:'1px solid var(--dgd-border-1)',flexWrap:'wrap'}}>
          {CONS_TABS.filter(t => isAdminView || t.id === 'rpc').map(t => (
            <button key={t.id} onClick={() => setTab(t.id)}
              style={{padding:'7px 14px',fontSize:12,fontWeight:tab===t.id?700:400,color:tab===t.id?'var(--dgd-fg-1)':'var(--dgd-fg-3)',background:'none',border:'none',borderBottom:tab===t.id?'2px solid var(--dgd-fg-1)':'2px solid transparent',cursor:'pointer',marginBottom:-1,whiteSpace:'nowrap'}}>
              {t.label}
            </button>
          ))}
        </div>

        {/* Loading */}
        {(comsLoading || ytdLoading || fmLoading) && (
          <div style={{padding:'20px 0',color:'var(--dgd-fg-3)',fontSize:12}}>A carregar dados {empresa}{fmLoading?' (FM pipeline)':''}...</div>
        )}

        {/* Tab content */}
        {!comsLoading && !ytdLoading && (
          <>
            {tab==='rpc' && fmFromPostgres && (
              <div style={{margin:'0 0 10px',padding:'8px 14px',borderRadius:7,background:'color-mix(in oklch,#f59e0b 8%,transparent)',border:'1px solid color-mix(in oklch,#f59e0b 30%,transparent)',fontSize:11,color:'#92400e',display:'flex',alignItems:'center',gap:6}}>
                <span style={{fontWeight:700}}>⚠ GestorConnect em baixo</span> — Oportunidades FM lidas do snapshot PostgreSQL (sincronizado às {new Date().toLocaleTimeString('pt-PT',{hour:'2-digit',minute:'2-digit'})}).
              </div>
            )}
            {tab==='rpc' && comerciaisFiltrados.length > 0 && <TabMapaRPCCons com={selCom} ytdMensal={ytdMensal} bpMensal={bpMensal} margemMensal={margemMensal} onBpSave={handleBpSave} fmData={fmData} carteiraByV={carteiraByV} comerciaisFiltrados={comerciaisFiltrados} spinData={spinData} encData={encData} f9Data={f9Data} empresa={empresa} fmEmpresa={fmEmpresa} userTipo={userTipo} canEditDesvio={['admin','board','dev'].includes(userTipo) || (selCom != null && selCom.gestor_vendedor_id === vendedorId)} ano={ano} />}
            {tab==='rpc' && comerciaisFiltrados.length === 0 && <div style={{padding:'40px',textAlign:'center',color:'var(--dgd-fg-3)',fontSize:13}}>Seleciona uma marca para ver o Mapa RPC.</div>}
            {tab==='money' && <TabShowMoneyCons comerciais={comerciaisFiltrados} byV={byV||{}} margemByV={margemByV||{}} ano={ano} />}
            {tab==='ytd'   && <TabYTDFamiliasCons ytdRows={ytdRows||[]} selCom={selCom} vendedoresFiltrados={vendedoresFiltrados} />}
            {['forecast','predit','mediasales','sugestoes','previsao'].includes(tab) && <TabConsPlaceholder tab={tab} />}
          </>
        )}

        {/* Nota dados */}
        {hasData
          ? <div style={{marginTop:24,padding:'10px 14px',borderRadius:7,background:'color-mix(in oklch,#22c55e 7%,transparent)',border:'1px solid color-mix(in oklch,#22c55e 25%,transparent)',fontSize:11,color:'#166534'}}>
              <strong>Dados em tempo real</strong> — Vendas Fact. + Margem: Primavera (comercial_ytd_snap) · BP: portal (comercial_bp_targets) · Comerciais: PostgreSQL (colaborador_erp_ids). Blocos A/B/C: Fase 2.
            </div>
          : <div style={{marginTop:24,padding:'10px 14px',borderRadius:7,background:'color-mix(in oklch,#f59e0b 7%,transparent)',border:'1px solid color-mix(in oklch,#f59e0b 25%,transparent)',fontSize:11,color:'#92400e'}}>
              <strong>A carregar dados...</strong> — Primavera sync.
            </div>
        }
      </div>
    </div>
  );
};

// ─── PVConsMapaEmbed — embed do Mapa RPC Consumíveis para Previsão de Vendas ──
// Props: { empresa, fmEmpresa, selVendedor, ano, userTipo }
//   empresa:     código BD (ex: 'DECAL_PT', 'DECAL_EX', 'DECAL_ES') — já é único por equipa
//   fmEmpresa:   tenant FM (ex: 'PTDIG', 'DIGES')
//   selVendedor: vendedor code seleccionado ou null (null = totais equipa)
//   ano:         ano
//   userTipo:    tipo de utilizador (para canEditBP)
const PVConsMapaEmbed = ({ empresa, fmEmpresa, selVendedor, ano, userTipo }) => {
  const [bpRefresh, setBpRefresh] = React.useState(0);
  const handleBpSave = React.useCallback(() => setBpRefresh(k => k + 1), []);

  const { comerciais, loading: comsLoading } = useConsComerciais(empresa);
  const { byV, margemByV, loading: ytdLoading } = useConsYTD(ano);
  const bpMap = useConsBP(ano, bpRefresh);
  const { byVendedor: fmByV } = useConsFMPipeline(fmEmpresa, ano);
  const { byVendedor: carteiraByV } = useConsCarteira(ano);

  // empresa já é único por equipa — usa todos os colaboradores (não só os com BP)
  const comsBase = comerciais || [];

  const comerciaisFiltrados = React.useMemo(() => {
    if (!selVendedor) return comsBase;
    return comsBase.filter(c => c.vendedor === selVendedor);
  }, [comsBase, selVendedor]);

  const isAll = !selVendedor;
  const selCom = !isAll && comerciaisFiltrados.length === 1 ? comerciaisFiltrados[0] : null;

  const spinData = useConsSPIN(selCom?.vendedor || null, ano);
  const encData  = useConsEncomendas(selCom?.vendedor || null, ano);
  const f9Data   = useConsF9(selCom?.vendedor || null, ano);

  const aggMensal = React.useCallback((map) => {
    const result = new Array(12).fill(null);
    comerciaisFiltrados.forEach(c => {
      (map?.[c.vendedor] || []).forEach((v, i) => {
        if (v != null) result[i] = (result[i] || 0) + v;
      });
    });
    return result;
  }, [comerciaisFiltrados]);

  const ytdMensal = React.useMemo(() =>
    isAll ? aggMensal(byV) : (selCom ? (byV?.[selCom.vendedor] || new Array(12).fill(null)) : new Array(12).fill(null)),
    [isAll, selCom, aggMensal, byV]);
  const margemMensal = React.useMemo(() =>
    isAll ? aggMensal(margemByV) : (selCom ? (margemByV?.[selCom.vendedor] || new Array(12).fill(null)) : new Array(12).fill(null)),
    [isAll, selCom, aggMensal, margemByV]);
  const bpMensal = React.useMemo(() =>
    isAll ? aggMensal(bpMap) : (selCom ? (bpMap[selCom.gestor_vendedor_id] || bpMap[selCom.vendedor] || new Array(12).fill(null)) : new Array(12).fill(null)),
    [isAll, selCom, aggMensal, bpMap]);

  const FM_KEYS = ['optmkt','optcom','opmanagerf9','wonValor'];
  const aggFMData = React.useMemo(() => {
    if (!fmByV || !comerciaisFiltrados.length) return null;
    const result = {};
    FM_KEYS.forEach(k => { result[k] = new Array(12).fill(0); });
    let hasAny = false;
    comerciaisFiltrados.forEach(c => {
      const vd = fmByV[c.vendedor];
      if (!vd) return;
      hasAny = true;
      FM_KEYS.forEach(k => {
        (vd[k] || []).forEach((v, i) => { result[k][i] += v || 0; });
      });
    });
    return hasAny ? result : null;
  }, [fmByV, comerciaisFiltrados]);

  const fmData = React.useMemo(() => {
    if (isAll) return aggFMData;
    if (!selCom || !fmByV) return null;
    return fmByV[selCom.vendedor] || null;
  }, [isAll, selCom, fmByV, aggFMData]);

  if (comsLoading || ytdLoading) {
    return <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>A carregar RPC Consumíveis...</div>;
  }
  if (!comerciaisFiltrados.length) {
    return <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>Sem comerciais para esta equipa.</div>;
  }

  return (
    <TabMapaRPCCons
      com={selCom}
      ytdMensal={ytdMensal}
      bpMensal={bpMensal}
      margemMensal={margemMensal}
      onBpSave={handleBpSave}
      fmData={fmData}
      carteiraByV={carteiraByV}
      comerciaisFiltrados={comerciaisFiltrados}
      spinData={spinData}
      encData={encData}
      f9Data={f9Data}
      empresa={empresa}
      fmEmpresa={fmEmpresa}
      userTipo={userTipo}
      canEditDesvio={true}
      ano={ano}
    />
  );
};

window.ScreenComercialRPCConsumiveisES = ScreenComercialRPCConsumiveis;
window.PVConsMapaEmbedES = PVConsMapaEmbed;

})(); // fim IIFE
