/* =============================================================================
   screen_comercial_rpc_es.jsx — Produção Comercial ES / Mimaki ES
   =============================================================================
   Sub-tabs:
     1. Mapa RPC         — grelha SPIN/OPTMKT/OPTCOM/OPMANAGERF9/BP/F9/VENDAS/DESVIO
     2. Show me the Money— facturação real mês a mês por gestor
     3. Forecast         — equipamentos e vendedores (pipeline projectado)
     4. Preditividade    — fecho do mês + drilldown OPs
     5. YTD Famílias     — mapa vendas YTD por família/artigo
     6. Media Sales AI   — controlo PrintPlan, Digirent, tintas, alertas
     7. Sugestões AI     — acções de melhoria por comercial

   Dados em tempo real: FM GestorConnect (DIGES / Gestor_Dimension) + Primavera ES Sync.
   Pendente Walter: SPINS layout, ForecastDate em FMRS_API_OPORTUNIDADES, OBJECTIVOS layout.
   ============================================================================= */

// IIFE — protege variáveis internas do overwrite pelos ficheiros PT (scope Babel partilhado)
(function() {
// ─── Constantes ───────────────────────────────────────────────────────────────
const RPC_MESES    = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];
const RPC_ANO_DEF  = 2026;
const RPC_MES_FECH = new Date().getMonth(); // mês actual 0-based — avança automaticamente

// ─── Helpers ──────────────────────────────────────────────────────────────────
const rE = (n, compact=true) => {
  if (n == null) 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)    return s + Math.round(abs/1000) + 'k';
  return s + new Intl.NumberFormat('pt-PT').format(Math.round(abs));
};
// Objectivo BP: uma casa, para 45.500€ não aparecer como 46k
const rE1 = (n) => {
  if (n == null) return '—';
  const abs = Math.abs(n), s = n < 0 ? '-' : '';
  if (abs >= 1000) {
    const [int, dec] = (abs / 1000).toFixed(1).split('.');
    return s + int.replace(/\B(?=(\d{3})+(?!\d))/g, '.') + ',' + dec + 'k';
  }
  return s + abs.toFixed(1).replace('.', ',');
};
const rN = (n) => n == null ? '—' : String(n);
const rPct = (v, t) => t > 0 ? (v/t*100).toFixed(1).replace('.',',')+'%' : '—';
const fmtFmDate = s => { if (!s) return '—'; const p = s.split('/'); return p.length === 3 ? p[1]+'/'+p[0]+'/'+p[2].slice(-2) : s; };
const dStyle = (d, bp) => {
  if (d == null || bp == null) return { color:'var(--dgd-fg-3)' };
  const p = d / bp;
  if (p >= 0)    return { color:'#16a34a', fontWeight:600 };
  if (p > -0.10) return { color:'#d97706', fontWeight:600 };
  return               { color:'#dc2626', fontWeight:600 };
};
const badgeStyle = (sev) => ({
  display:'inline-block', padding:'2px 8px', borderRadius:4, fontSize:10, fontWeight:700, letterSpacing:'0.04em',
  background: sev==='alto'?'#fee2e2': sev==='medio'?'#fef3c7':'#dcfce7',
  color:       sev==='alto'?'#991b1b': sev==='medio'?'#92400e':'#166534',
});

const InfoTip = ({ text }) => {
  const [pos, setPos] = React.useState(null);
  const ref = React.useRef(null);
  const handleEnter = () => {
    if (ref.current) {
      const r = ref.current.getBoundingClientRect();
      // Flip abaixo do trigger se perto do topo do viewport
      const flipDown = r.top < 160;
      setPos({ top: flipDown ? r.bottom + 8 : r.top - 8, left: r.left + r.width / 2, flipDown });
    }
  };
  // Portal: renderiza no document.body para escapar qualquer stacking context (sticky, transform, etc.)
  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:220, pointerEvents:'none'}}>
      {text}
    </div>,
    document.body
  ) : null;
  return (
    <span style={{position:'relative', display:'inline-block', marginLeft:3, 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>
  );
};

const _zeros12 = [0,0,0,0,0,0,0,0,0,0,0,0];
const _null12  = [null,null,null,null,null,null,null,null,null,null,null,null];
const RPC_EMPTY = { spin:_zeros12, optmkt:_zeros12, optcom:_zeros12, opmanagerf9:_null12, bp:_zeros12, f9:_null12, faturado:_null12, opsWon:_null12, lostValor:_zeros12 };

// ─── Mapeamento marca (UI) → empresa BD (para useComerciais / colaborador_erp_ids) ──
const MARCA_EMPRESA = {
  'Mimaki ES': 'MIMAKI_ES',
};
const MARCAS = Object.keys(MARCA_EMPRESA);

// ─── Mapeamento empresa BD → empresa FM-pipeline/ops-detail ──────────────────
// Decal PT/EX estão em Gestor_Digidelta (mesma empresa PT); NETSCREEN → NSC
const EMPRESA_FM_KEY = {
  'MIMAKI_ES': 'DIGES',
};


// ─── Hook: FM pipeline por vendedor ───────────────────────────────────────────
// Fonte: /api/rpc/fm-pipeline → GestorDeveloper (PTDIG) ou Gestor_Dimension (DIGES)
// byVendedor[id] = { opmanagerf9, optmkt, optcom, won, wonValor } — arrays 12 meses
const useFMPipeline = (empresa, ano) => {
  const [state, setState] = React.useState({ byVendedor: null, totalOps: 0, loading: false, fromPostgres: false });
  React.useEffect(() => {
    if (!empresa) { setState({ byVendedor: null, totalOps: 0, loading: false }); return; }
    setState(s => ({ ...s, loading: true, fromPostgres: false }));
    fetch(`/api/rpc/fm-pipeline?empresa=${empresa}&ano=${ano}`)
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(data => setState({ byVendedor: data.byVendedor || {}, totalOps: data.totalOps || 0, loading: false, fromPostgres: !!data.fromPostgres }))
      .catch(() => setState({ byVendedor: null, totalOps: 0, loading: false, fromPostgres: false }));
  }, [empresa, ano]);
  return state;
};

// Agrega todos os vendedores de um byVendedor FM num único array de 12 meses
function fmAggregateAll(byVendedor, field) {
  const result = new Array(12).fill(0);
  if (!byVendedor) return result;
  Object.values(byVendedor).forEach(v => {
    if (!v[field]) return;
    v[field].forEach((n, i) => { result[i] += (n || 0); });
  });
  return result;
}

// ─── Context: FM pipeline data (SPIN, OPMANAGERF9, VENDAS para ES) ───────────
const FMCtx = React.createContext({});

// ─── Hook: equipa real da BD ──────────────────────────────────────────────────
const useTeam = (managerId) => {
  const [state, setState] = React.useState({ team: null, loading: true });
  React.useEffect(() => {
    if (!managerId) { setState({ team: null, loading: false }); return; }
    fetch(`/api/rpc/team?managerId=${managerId}`)
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(rows => {
        const team = rows.map(r => ({
          id:       'db-' + r.id,
          nome:     r.nome_apresentar || r.nome_completo,
          funcao:   r.funcao || '',
          vendedor: r.gestor_vendedor_id || '—',
          foto_url: r.foto_url || null,
          db_id:    r.id,
        }));
        setState({ team, loading: false });
      })
      .catch(() => setState({ team: null, loading: false }));
  }, [managerId]);
  return state;
};

// ─── Hook: comerciais BD por empresa ─────────────────────────────────────────
// Fonte: /api/rpc-es/comerciais → colaborador_erp_ids JOIN colaboradores
const useComerciais = (empresa) => {
  const [state, setState] = React.useState({ comerciais: null, loading: true });
  React.useEffect(() => {
    if (!empresa) { setState({ comerciais: [], loading: false }); return; }
    setState(s => ({ ...s, loading: true, fromPostgres: false }));
    fetch(`/api/rpc-es/comerciais?empresa=${empresa}`)
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(rows => {
        const 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,
          canal_negocio: r.canal_negocio || null,
          gestor_fm_id: r.gestor_fm_id,
          foto_url: r.foto_url || null,
          db_id:    r.colaborador_id,
        }));
        setState({ comerciais, loading: false });
      })
      .catch(() => setState({ comerciais: null, loading: false }));
  }, [empresa]);
  return state;
};

// ─── Context + Hook: Backlog (OPEN OPs de anos anteriores) ───────────────────
const BacklogCtx = React.createContext({});

const useBacklog = (empresa, ano) => {
  const [data, setData] = React.useState({});
  React.useEffect(() => {
    fetch(`/api/rpc/backlog?empresa=${empresa}&ano=${ano}`)
      .then(r => r.ok ? r.json() : {})
      .then(d => setData(d.byVendedor || {}))
      .catch(() => {});
  }, [empresa, ano]);
  return data;
};

// ─── Hook: YTD mensal por vendedor (Primavera real) ───────────────────────────
const YTDCtx   = React.createContext({});
const MargemCtx = React.createContext({});
const BPCtx    = React.createContext({ bpMap: {}, saveBP: () => {} });

const useYTDMensal = (ano, excluirNC = false) => {
  const [state, setState] = React.useState({ byVendedor: null, margemMap: null, loading: true });
  React.useEffect(() => {
    const url = `/api/rpc-es/ytd?ano=${ano}${excluirNC ? '&excluirNC=true' : ''}`;
    fetch(url)
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(data => {
        const map = {};
        (data.mensal || []).forEach(row => {
          if (!map[row.vendedor]) map[row.vendedor] = new Array(12).fill(null);
          map[row.vendedor][row.mes - 1] = parseFloat(row.valor) || 0;
        });
        const mmap = {};
        (data.margemMensal || []).forEach(row => {
          if (!mmap[row.vendedor]) mmap[row.vendedor] = new Array(12).fill(null);
          mmap[row.vendedor][row.mes - 1] = parseFloat(row.margem) || 0;
        });
        setState({ byVendedor: map, margemMap: mmap, loading: false });
      })
      .catch(() => setState({ byVendedor: null, margemMap: null, loading: false }));
  }, [ano, excluirNC]);
  return state;
};

// ─── DRILL-DOWN MODAL ─────────────────────────────────────────────────────────
// ─── OpNotasModal — notas/talks de uma OP FM ────────────────────────────────
const OpNotasModal = ({ op, onClose }) => {
  const [notas, setNotas]   = React.useState(null);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    setLoading(true);
    fetch(`/api/rpc/op-notas?op_id=${encodeURIComponent(op.op_id)}&db=${encodeURIComponent(op.db_source || '')}`)
      .then(r => r.ok ? r.json() : Promise.reject())
      .then(d => { setNotas(d.notas || []); setLoading(false); })
      .catch(() => { setNotas([]); setLoading(false); });
  }, [op.op_id, op.db_source]);

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

  const fmtVal = v => v == null ? '—' : new Intl.NumberFormat('pt-PT', { style:'currency', currency:'EUR', maximumFractionDigits:0 }).format(v);

  const stageColor = (status) => {
    if (status === 'WON')  return { bg:'#dcfce7', fg:'#166534' };
    if (status === 'LOST') return { bg:'#fee2e2', fg:'#991b1b' };
    return { bg:'color-mix(in oklch,#3b82f6 12%,transparent)', fg:'#1e40af' };
  };
  const sc = stageColor(op.status);

  const tipoIcon = (tipo) => {
    const t = (tipo || '').toUpperCase();
    if (t === 'TALK') return { icon:'💬', label:'Talk', bg:'#dbeafe', fg:'#1e40af' };
    if (t === 'EMAIL') return { icon:'✉️', label:'Email', bg:'#ede9fe', fg:'#5b21b6' };
    if (t === 'CALL') return { icon:'📞', label:'Call', bg:'#dcfce7', fg:'#166534' };
    return { icon:'📝', label: tipo || 'Nota', bg:'#f3f4f6', fg:'#374151' };
  };

  const fmtTimestamp = (ts) => {
    if (!ts) return '';
    // FM format: "MM/DD/YYYY HH:MM:SS" or "YYYY-MM-DD HH:MM:SS"
    try {
      const d = new Date(ts.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$1-$2'));
      if (isNaN(d)) return ts;
      return d.toLocaleDateString('pt-PT', { day:'2-digit', month:'2-digit', year:'numeric' }) + ' ' +
             d.toLocaleTimeString('pt-PT', { hour:'2-digit', minute:'2-digit' });
    } catch(_) { return ts; }
  };

  return (
    <>
      <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:3000, background:'rgba(0,0,0,0.35)' }} />
      <div style={{ position:'fixed', top:'50%', left:'50%', transform:'translate(-50%,-50%)', zIndex:3001, width:'min(700px,92vw)', maxHeight:'82vh', display:'flex', flexDirection:'column', background:'var(--dgd-bg-card,white)', borderRadius:12, boxShadow:'0 24px 64px rgba(0,0,0,0.28)', border:'1px solid var(--dgd-border-1)' }}>
        {/* Header */}
        <div style={{ padding:'14px 20px 12px', borderBottom:'1px solid var(--dgd-border-1)', flexShrink:0 }}>
          <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:6 }}>
            <span style={{ fontSize:14, fontWeight:700, color:'var(--dgd-fg-1)', flex:1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{op.entidade}</span>
            <button onClick={onClose} style={{ background:'none', border:'none', cursor:'pointer', color:'var(--dgd-fg-3)', fontSize:18, lineHeight:1, padding:'0 4px', flexShrink:0 }}>×</button>
          </div>
          <div style={{ display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' }}>
            <span style={{ fontSize:11, color:'var(--dgd-fg-2)', fontWeight:500 }}>{op.produto}</span>
            <span style={{ display:'inline-block', padding:'1px 6px', borderRadius:4, fontSize:10, fontWeight:700, background:sc.bg, color:sc.fg }}>
              {op.stage_num ? op.stage_num + ' – ' : ''}{op.stage || op.status}
            </span>
            <span style={{ fontSize:11, color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)' }}>{fmtFmDate(op.data)}</span>
            <span style={{ fontSize:12, fontWeight:700, color:'var(--dgd-fg-1)', fontFamily:'var(--dgd-font-mono,monospace)', marginLeft:'auto' }}>{fmtVal(op.valor)}</span>
          </div>
        </div>

        {/* Body — lista de notas */}
        <div style={{ overflowY:'auto', flex:1, padding:'12px 16px', display:'flex', flexDirection:'column', gap:10 }}>
          {loading && <div style={{ padding:'24px', textAlign:'center', color:'var(--dgd-fg-3)', fontSize:13 }}>A carregar notas...</div>}
          {!loading && notas.length === 0 && <div style={{ padding:'24px', textAlign:'center', color:'var(--dgd-fg-3)', fontSize:13 }}>Sem notas para esta oportunidade.</div>}
          {!loading && notas.map((n, i) => {
            const ti = tipoIcon(n.tipo);
            return (
              <div key={i} style={{ background:'var(--dgd-bg-surface,#f8fafc)', borderRadius:8, padding:'10px 14px', border:'1px solid var(--dgd-border-1)' }}>
                <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:6 }}>
                  <span style={{ display:'inline-block', padding:'1px 7px', borderRadius:4, fontSize:10, fontWeight:700, background:ti.bg, color:ti.fg }}>{ti.icon} {ti.label}</span>
                  {n.autor && <span style={{ fontSize:11, fontWeight:600, color:'var(--dgd-fg-2)' }}>{n.autor}</span>}
                  {n.timestamp && <span style={{ fontSize:10, color:'var(--dgd-fg-3)', marginLeft:'auto', fontFamily:'var(--dgd-font-mono,monospace)' }}>{fmtTimestamp(n.timestamp)}</span>}
                </div>
                {n.nota && <div style={{ fontSize:12, color:'var(--dgd-fg-1)', lineHeight:1.55, whiteSpace:'pre-wrap', wordBreak:'break-word' }}>{n.nota}</div>}
                {n.categoria && <div style={{ marginTop:5, fontSize:10, color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', textTransform:'uppercase', letterSpacing:'0.05em' }}>{n.categoria}</div>}
              </div>
            );
          })}
        </div>

        {/* Footer */}
        {!loading && notas.length > 0 && (
          <div style={{ padding:'8px 20px', borderTop:'1px solid var(--dgd-border-1)', flexShrink:0, background:'var(--dgd-bg-surface,#f8fafc)', borderRadius:'0 0 12px 12px' }}>
            <span style={{ fontSize:11, color:'var(--dgd-fg-3)' }}>{notas.length} nota{notas.length !== 1 ? 's' : ''}</span>
          </div>
        )}
      </div>
    </>
  );
};

const DrilldownModal = ({ drill, onClose, excluirNC = false }) => {
  const { com, tipo, mesIdx, ano, empresa, racioVP, totalOpen, winRate: drillWinRate, previsaoTotYTD, pipeVivoPast,
          calcOnly, mPassado, mAtual, mMais1, mMais2plus, pipeline: drillPipeline, formulaAno, comList } = drill;
  const [data, setData]       = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError]     = React.useState(null);
  const [opDrill, setOpDrill] = React.useState(null);
  const [sortCol, setSortCol] = React.useState('stage_num');
  const [sortDir, setSortDir] = React.useState('desc');
  const [filtStage,  setFiltStage]  = React.useState('');
  const [filtSource, setFiltSource] = React.useState('');
  const [filtStatus, setFiltStatus] = React.useState('');
  const [filtStar,   setFiltStar]   = React.useState(false);
  const toggleSort = (col) => { setSortCol(col); setSortDir(s => s === 'desc' && sortCol === col ? 'asc' : 'desc'); };
  const sortIcon = (col) => sortCol !== col ? ' ↕' : sortDir === 'desc' ? ' ↓' : ' ↑';

  const isYTD  = mesIdx === 'ytd';
  const mes    = isYTD ? null : mesIdx + 1;
  const mesLbl = isYTD ? `YTD ${ano}` : RPC_MESES[mesIdx];
  const isVendas   = tipo === 'vendas' || tipo === 'faturado' || tipo === 'margem';
  const isPrevisao = tipo === 'previsao';
  const isWinrate  = tipo === 'winrate';
  const isCurrMesP = isPrevisao && mesIdx === RPC_MES_FECH;  // mês activo: stage 90/92
  const endpoint   = isVendas
    ? `/api/rpc-es/vendas-detail?vendedor=${com.vendedor}&ano=${ano}${mes ? `&mes=${mes}` : ''}${excluirNC ? '&excluirNC=true' : ''}`
    : isPrevisao
      ? isCurrMesP
        ? `/api/rpc/ops-detail?vendedor=${com.vendedor}&ano=${ano}&tipo=stage90&empresa=${empresa || 'PTDIG'}`
        : `/api/rpc/ops-detail?vendedor=${com.vendedor}&ano=${ano}&tipo=pipeline_open&empresa=${empresa || 'PTDIG'}`
      : isWinrate
        ? `/api/rpc/ops-detail?vendedor=${com.vendedor}${mes !== null ? `&mes=${mes}` : `&toMes=${RPC_MES_FECH+1}`}&ano=${ano}&tipo=winrate&empresa=${empresa || 'PTDIG'}`
        : `/api/rpc/ops-detail?vendedor=${com.vendedor}${mes !== null ? `&mes=${mes}` : `&toMes=${RPC_MES_FECH+1}`}&ano=${ano}&tipo=${tipo}&empresa=${empresa || 'PTDIG'}`;

  const isBPRating   = tipo === 'bprating';
  const isCobertura  = tipo === 'cobertura';
  const isRacioVP    = tipo === 'raciovp';

  React.useEffect(() => {
    if (isBPRating || isCobertura || isRacioVP || calcOnly) { setLoading(false); return; }
    setLoading(true); setData(null); setError(null);
    if (isCurrMesP && comList?.length > 0) {
      // Equipa: fetch stage90 de cada comercial, juntar e filtrar next_date futura
      Promise.all(comList.map(({ vendedor, nome }) =>
        fetch(`/api/rpc/ops-detail?vendedor=${vendedor}&ano=${ano}&tipo=stage90&empresa=${empresa || 'PTDIG'}`)
          .then(r => r.ok ? r.json() : { ops: [] })
          .then(d => (d.ops || []).map(o => ({ ...o, _comercialNome: nome })))
      )).then(results => {
        setData({ ops: results.flat() });
        setLoading(false);
      }).catch(() => { setError('Erro ao carregar dados'); setLoading(false); });
      return;
    }
    fetch(endpoint)
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { setData(d); setLoading(false); })
      .catch(() => { setError('Erro ao carregar dados'); setLoading(false); });
  }, [endpoint, isBPRating, isCurrMesP, comList]);

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

  const fmtVal = v => v == null ? '—' : new Intl.NumberFormat('pt-PT', { style:'currency', currency:'EUR', maximumFractionDigits:0 }).format(v);
  const tipoLabel = { optmkt:'OPTMKT', optcom:'OPTCOM', opmanagerf9:'OPMANAGERF9', vendas:'VENDAS', faturado:'FATURADO', opswon:'OPs WON', margem:'MARGEM', winrate:'WIN RATE', bprating:'BP RATING', cobertura:'COBERTURA DE PIPELINE', raciovp:'RÁCIO VENDAS/PIPELINE', previsao: isCurrMesP ? 'PREVISÃO (Stage 90/92)' : 'PREVISÃO (Pipeline OPEN)' }[tipo] || tipo.toUpperCase();

  const C = { th: { padding:'6px 10px', textAlign:'left', fontSize:10, fontWeight:700, color:'var(--dgd-fg-3)', letterSpacing:'0.06em', textTransform:'uppercase', borderBottom:'2px solid var(--dgd-border-1)', whiteSpace:'nowrap' }, td: { padding:'7px 10px', fontSize:12, borderBottom:'1px solid var(--dgd-border-1)', verticalAlign:'middle', color:'var(--dgd-fg-1)', whiteSpace:'nowrap' } };

  return (
    <>
      {opDrill && (() => {
        const Panel = window.OpDetailPanel;
        if (!Panel) return <OpNotasModal op={opDrill} onClose={() => setOpDrill(null)} />;
        // Map drilldown OP fields to FM format for OpDetailPanel
        const fmOp = {
          ID_OPORTUNIDADE: opDrill.op_id || '',
          Entidade_Nome: opDrill.entidade || opDrill.cliente || '',
          Produto_Name: opDrill.produto || '',
          Stage_Name: opDrill.stage || '',
          Stage_Number: String(opDrill.stage_num || ''),
          Produto_Valor: String(opDrill.val || opDrill.valor || 0),
          Status: opDrill.status || 'OPEN',
          Opt_Top: opDrill.opt_top ? '1' : '0',
          StartDate: opDrill.data || '',
          ID_Empresa: opDrill.empresa || empresa || '',
          ID_Source: opDrill.source_id || '',
          Equipa_Name: opDrill.equipa || '',
          ID_Entidade: opDrill.entidade_id || '',
          Next_Date: opDrill.next_date || '',
        };
        return (
          <div onClick={() => setOpDrill(null)} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.45)', zIndex: 3000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <div onClick={e => e.stopPropagation()} style={{ borderRadius: 12, overflow: 'hidden', boxShadow: '0 20px 60px rgba(0,0,0,0.3)' }}>
              <Panel op={fmOp} onClose={() => setOpDrill(null)} empresa={empresa || 'PTDIG'} />
            </div>
          </div>
        );
      })()}
      <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:2000, background:'rgba(0,0,0,0.45)', backdropFilter:'blur(2px)' }} />
      <div style={{ position:'fixed', top:'50%', left:'50%', transform:'translate(-50%,-50%)', zIndex:2001, width:'min(900px,94vw)', maxHeight:'80vh', display:'flex', flexDirection:'column', 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)' }}>
        {/* Header */}
        <div style={{ padding:'14px 20px 12px', borderBottom:'1px solid var(--dgd-border-1)', display:'flex', alignItems:'center', gap:12, flexShrink:0 }}>
          <div style={{ fontFamily:'var(--dgd-font-mono,monospace)', fontSize:10, fontWeight:700, letterSpacing:'0.08em', color:'white', background:'var(--dgd-fg-1)', padding:'3px 8px', borderRadius:4 }}>{tipoLabel}</div>
          <div style={{ fontSize:14, fontWeight:700, color:'var(--dgd-fg-1)' }}>{com.nome}</div>
          <div style={{ fontSize:12, color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)' }}>{com.vendedor}</div>
          <div style={{ fontSize:12, color:'var(--dgd-fg-3)' }}>·</div>
          <div style={{ fontSize:13, fontWeight:600, color:'var(--dgd-fg-2)' }}>{isYTD ? mesLbl : `${mesLbl} ${ano}`}</div>
          <button onClick={onClose} style={{ marginLeft:'auto', background:'none', border:'none', cursor:'pointer', color:'var(--dgd-fg-3)', fontSize:18, lineHeight:1, padding:'0 4px' }}>×</button>
        </div>

        {/* Body */}
        <div style={{ overflowY:'auto', flex:1 }}>
          {loading && <div style={{ padding:'32px', textAlign:'center', color:'var(--dgd-fg-3)', fontSize:13 }}>A carregar...</div>}
          {error   && <div style={{ padding:'32px', textAlign:'center', color:'#dc2626', fontSize:13 }}>{error}</div>}
          {!loading && isBPRating && (() => {
            const { fatVal, bpVal, margVal, margBPVal, margPct, objMargem: oMarg, vendAch, margAch, bpRat, isYtd: isYtdDrill } = drill;
            const rPctD = v => v == null ? '—' : v.toFixed(1) + '%';
            const rE2   = v => v == null ? '—' : new Intl.NumberFormat('pt-PT',{style:'currency',currency:'EUR',maximumFractionDigits:0}).format(v);
            const col   = v => v == null ? 'var(--dgd-fg-3)' : v >= 100 ? '#16a34a' : v >= 80 ? '#d97706' : '#dc2626';
            const scopeLabel = isYtdDrill ? 'YTD acumulado' : 'Mensal';
            return (
              <div style={{padding:'24px'}}>
                <div style={{fontSize:10,color:'var(--dgd-fg-3)',marginBottom:8}}>{scopeLabel} · Fórmula: (Vendas/BP + Margem€/MargBP€) ÷ 2</div>
                <table style={{width:'100%',borderCollapse:'collapse'}}>
                  <thead>
                    <tr>
                      {['Componente','Realizado','Objectivo','Achievement'].map((h,i)=>(
                        <th key={i} style={{...C.th, textAlign: i===0?'left':'right'}}>{h}</th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    <tr>
                      <td style={{...C.td}}>Vendas Faturadas</td>
                      <td style={{...C.td,textAlign:'right',fontWeight:600,fontFamily:'var(--dgd-font-mono,monospace)'}}>{rE2(fatVal)}</td>
                      <td style={{...C.td,textAlign:'right',color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)'}}>{rE2(bpVal)}</td>
                      <td style={{...C.td,textAlign:'right',fontWeight:700,color:col(vendAch)}}>{rPctD(vendAch)}</td>
                    </tr>
                    <tr>
                      <td style={{...C.td}}>Margem € <span style={{fontSize:10,color:'var(--dgd-fg-3)',fontWeight:400}}>(real {rPctD(margPct)} · obj {oMarg != null ? oMarg+'%' : '—'})</span></td>
                      <td style={{...C.td,textAlign:'right',fontWeight:600,fontFamily:'var(--dgd-font-mono,monospace)'}}>{rE2(margVal)}</td>
                      <td style={{...C.td,textAlign:'right',color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)'}}>{rE2(margBPVal)}</td>
                      <td style={{...C.td,textAlign:'right',fontWeight:700,color:col(margAch)}}>{rPctD(margAch)}</td>
                    </tr>
                  </tbody>
                </table>
                <div style={{marginTop:16,padding:'12px 16px',background:'var(--dgd-bg-sunken,#f8fafc)',borderRadius:8,display:'flex',alignItems:'center',gap:8}}>
                  <span style={{fontSize:11,color:'var(--dgd-fg-3)'}}>BP Rating = (Achievement Vendas + Achievement Margem€) / 2</span>
                  <span style={{marginLeft:'auto',fontSize:20,fontWeight:700,color:col(bpRat)}}>{rPctD(bpRat)}</span>
                </div>
              </div>
            );
          })()}
                    {!loading && isCobertura && (() => {
            const { pipe, bpAnual: bpA, cumFat, falta, cob, isYtd: isYtdDrill,
                    pipe2026, pipeAnterio,
                    pipe2026Mes, pipeAnterioMensal: pipeAntMes } = drill;
            const rMult = v => v == null ? '—' : v.toFixed(1).replace('.',',') + 'x';
            const rE2   = v => v == null ? '—' : new Intl.NumberFormat('pt-PT',{style:'currency',currency:'EUR',maximumFractionDigits:0}).format(v);
            const bench = 4;
            const col   = v => v == null ? 'var(--dgd-fg-3)' : v >= bench ? '#16a34a' : v >= bench * 0.7 ? '#d97706' : '#dc2626';
            return (
              <div style={{padding:'24px'}}>
                <table style={{width:'100%',borderCollapse:'collapse'}}>
                  <thead>
                    <tr>
                      {['Componente','Valor'].map((h,j)=>(
                        <th key={j} style={{...C.th, textAlign: j===0?'left':'right'}}>{h}</th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    <tr>
                      <td style={{...C.td,fontWeight:600}}>Pipeline open (todos os anos)</td>
                      <td style={{...C.td,textAlign:'right',fontWeight:600,fontFamily:'var(--dgd-font-mono,monospace)'}}>{rE2(pipe)}</td>
                    </tr>
                    <tr>
                      <td style={{...C.td}}>BP Anual</td>
                      <td style={{...C.td,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',color:'var(--dgd-fg-3)'}}>{rE2(bpA)}</td>
                    </tr>
                    {bpA > 0 && (() => {
                      const pipeNec = bpA * bench;
                      const gap     = pipe - pipeNec;
                      return (<>
                        <tr style={{background:'color-mix(in oklch,#3b82f6 4%,transparent)'}}>
                          <td style={{...C.td}}>Pipeline benchmark (BP Anual × {rMult(bench)})</td>
                          <td style={{...C.td,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',color:'#1e40af',fontWeight:600}}>{rE2(pipeNec)}</td>
                        </tr>
                        <tr style={{background:gap>=0?'color-mix(in oklch,#16a34a 5%,transparent)':'color-mix(in oklch,#dc2626 5%,transparent)'}}>
                          <td style={{...C.td,fontWeight:600}}>{gap>=0?'Excesso vs objetivo':'Défice vs objetivo'}</td>
                          <td style={{...C.td,textAlign:'right',fontWeight:700,fontFamily:'var(--dgd-font-mono,monospace)',color:gap>=0?'#166534':'#991b1b'}}>{gap>=0?'+':''}{rE2(gap)}</td>
                        </tr>
                      </>);
                    })()}
                  </tbody>
                </table>
                <div style={{marginTop:16,padding:'12px 16px',background:'var(--dgd-bg-sunken,#f8fafc)',borderRadius:8}}>
                  <div style={{fontSize:11,color:'var(--dgd-fg-3)',marginBottom:8}}>
                    {'Cobertura = Pipeline OPEN ÷ BP Anual · Benchmark: ≥ 4x'}
                  </div>
                  <div style={{display:'flex',alignItems:'center',gap:8}}>
                    <span style={{fontSize:12,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)'}}>{rE2(pipe)} ÷ {rE2(bpA)}</span>
                    <span style={{marginLeft:'auto',fontSize:20,fontWeight:700,color:col(cob)}}>{rMult(cob)}</span>
                  </div>
                </div>
              </div>
            );
          })()}
          {!loading && isRacioVP && (() => {
            const { vendas, pipe, racio, racioYTD, isYtd: isYtdR, racioVPMeses } = drill;
            const rE2   = v => v == null ? '—' : new Intl.NumberFormat('pt-PT',{style:'currency',currency:'EUR',maximumFractionDigits:0}).format(v);
            const rPct2 = v => v == null ? '—' : v.toFixed(1) + '%';
            const col   = v => v == null ? 'var(--dgd-fg-3)' : v >= 10 ? '#16a34a' : v >= 5 ? '#d97706' : '#dc2626';
            if (isYtdR) {
              const mesesComValor = (racioVPMeses || []).filter(v => v != null).length;
              return (
                <div style={{padding:'24px'}}>
                  <div style={{fontSize:10,color:'var(--dgd-fg-3)',marginBottom:16}}>
                    Média dos rácios mensais de Jan ao mês activo, sem meses sem valor. Obj: ≥ 10%
                  </div>
                  <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:20}}>
                    <span style={{fontSize:32,fontWeight:700,color:col(racio)}}>{rPct2(racio)}</span>
                    <span style={{fontSize:12,color:'var(--dgd-fg-3)'}}>média de {mesesComValor} {mesesComValor === 1 ? 'mês' : 'meses'} com valor</span>
                  </div>
                  <table style={{width:'100%',borderCollapse:'collapse'}}>
                    <thead>
                      <tr>
                        <th style={{...C.th}}>Mês</th>
                        <th style={{...C.th,textAlign:'right'}}>Rácio VP</th>
                      </tr>
                    </thead>
                    <tbody>
                      {(racioVPMeses || []).map((r, i) => r != null && (
                        <tr key={i}>
                          <td style={{...C.td}}>{RPC_MESES[i]}</td>
                          <td style={{...C.td,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontWeight:600,color:col(r)}}>{rPct2(r)}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              );
            }
            return (
              <div style={{padding:'24px'}}>
                <div style={{fontSize:10,color:'var(--dgd-fg-3)',marginBottom:16}}>
                  Fórmula: vendas do mês ÷ pipeline em aberto à data do mês
                </div>
                <table style={{width:'100%',borderCollapse:'collapse'}}>
                  <thead>
                    <tr>
                      <th style={{...C.th}}>Componente</th>
                      <th style={{...C.th,textAlign:'right'}}>Valor</th>
                    </tr>
                  </thead>
                  <tbody>
                    <tr>
                      <td style={{...C.td}}>Vendas do mês</td>
                      <td style={{...C.td,textAlign:'right',fontWeight:600,fontFamily:'var(--dgd-font-mono,monospace)'}}>{rE2(vendas)}</td>
                    </tr>
                    <tr>
                      <td style={{...C.td}}>Pipeline open à data do mês</td>
                      <td style={{...C.td,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',color:'var(--dgd-fg-3)'}}>{rE2(pipe)}</td>
                    </tr>
                    <tr>
                      <td style={{...C.td,fontWeight:600}}>Rácio Vendas/Pipeline</td>
                      <td style={{...C.td,textAlign:'right',fontWeight:700,fontFamily:'var(--dgd-font-mono,monospace)',color:col(racio)}}>{rPct2(racio)}</td>
                    </tr>
                    {racioYTD != null && (
                      <tr style={{background:'color-mix(in oklch,#3b82f6 4%,transparent)'}}>
                        <td style={{...C.td}}>Rácio VP YTD (média)</td>
                        <td style={{...C.td,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',color:'#1e40af',fontWeight:600}}>{rPct2(racioYTD)}</td>
                      </tr>
                    )}
                  </tbody>
                </table>
                <div style={{marginTop:16,padding:'12px 16px',background:'var(--dgd-bg-sunken,#f8fafc)',borderRadius:8}}>
                  <div style={{fontSize:11,color:'var(--dgd-fg-3)',marginBottom:8}}>Rácio VP = Vendas ÷ Pipeline Open · Obj: ≥ 10%</div>
                  <div style={{display:'flex',alignItems:'center',gap:8}}>
                    <span style={{fontSize:12,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)'}}>{rE2(vendas)} ÷ {rE2(pipe)}</span>
                    <span style={{marginLeft:'auto',fontSize:20,fontWeight:700,color:col(racio)}}>{rPct2(racio)}</span>
                  </div>
                </div>
              </div>
            );
          })()}
          {!loading && calcOnly && isPrevisao && (() => {
            const rE2  = v => v == null ? '—' : new Intl.NumberFormat('pt-PT',{style:'currency',currency:'EUR',maximumFractionDigits:0}).format(v);
            const rPct = v => v == null ? '—' : (v).toFixed(1) + '%';
            const mPastLabel = RPC_MESES.slice(0, RPC_MES_FECH).join('-') || ('Jan-' + (RPC_MESES[RPC_MES_FECH - 1] || ''));
            const mNow  = RPC_MESES[RPC_MES_FECH];
            const mNext = RPC_MESES[RPC_MES_FECH + 1] || 'M+1';
            const mRest = RPC_MESES.slice(RPC_MES_FECH + 2).join(' + ') || 'M+2+';
            const total = previsaoTotYTD;
            return (
              <div style={{padding:'24px'}}>
                <table style={{width:'100%',borderCollapse:'collapse'}}>
                  <thead>
                    <tr>{['Componente','Cálculo','Valor'].map((h,j)=>(
                      <th key={j} style={{...C.th, textAlign: j===2?'right':'left'}}>{h}</th>
                    ))}</tr>
                  </thead>
                  <tbody>
                    {mPassado != null && (
                      <tr>
                        <td style={{...C.td,fontWeight:600}}>{formulaAno ? 'Facturado até ao mês anterior' : mPastLabel + ' — Meses passados'}</td>
                        <td style={{...C.td,color:'var(--dgd-fg-3)',fontSize:11}}>{formulaAno ? 'Vendas já facturadas' : 'Pipeline vivo por mês × Rácio VP YTD'}</td>
                        <td style={{...C.td,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontWeight:600}}>{rE2(mPassado)}</td>
                      </tr>
                    )}
                    <tr>
                      <td style={{...C.td,fontWeight:600}}>{mNow} — Mês actual</td>
                      <td style={{...C.td,color:'var(--dgd-fg-3)',fontSize:11}}>{formulaAno ? 'Previsão do mês − já facturado neste mês' : 'Stage 90/92 em aberto'}</td>
                      <td style={{...C.td,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontWeight:600}}>{rE2(mAtual)}</td>
                    </tr>
                    <tr>
                      <td style={{...C.td,fontWeight:600}}>{mNext} — M+1</td>
                      <td style={{...C.td,color:'var(--dgd-fg-3)',fontSize:11}}>⭐ OPs prioritárias (Opt_Top)</td>
                      <td style={{...C.td,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontWeight:600}}>{rE2(mMais1)}</td>
                    </tr>
                    <tr>
                      <td style={{...C.td,fontWeight:600}}>{mRest} — M+2+</td>
                        <td style={{...C.td,color:'var(--dgd-fg-3)',fontSize:11}}>{formulaAno ? 'Pipeline aberto − previsão do mês − estrelas' : 'Pipeline'} ({rE2(drillPipeline)}) × Rácio VP ({rPct(drillWinRate)})</td>
                      <td style={{...C.td,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontWeight:600}}>{rE2(mMais2plus)}</td>
                    </tr>
                    <tr style={{background:'color-mix(in oklch,#3b82f6 4%,transparent)'}}>
                      <td style={{...C.td,fontWeight:700}} colSpan={2}>Total Previsão Anual</td>
                      <td style={{...C.td,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontWeight:700,fontSize:14}}>{rE2(total)}</td>
                    </tr>
                  </tbody>
                </table>
              </div>
            );
          })()}
          {data && !isVendas && !isWinrate && !calcOnly && (() => {
            // helper: next_date futura → mês 0-based > RPC_MES_FECH
            const _isFutureNd = (nd) => {
              if (!nd) return false;
              const p = nd.split('/');
              if (p.length !== 3) return false;
              const ndM = parseInt(p[0]) - 1, ndY = parseInt(p[2]);
              return ndY === new Date().getFullYear() && ndM > RPC_MES_FECH;
            };
            // M+2+ previsao: excluir OPs quentes (já contabilizadas em M+1)
            // Stage 90/92 mês actual: excluir ops com next_date num mês futuro (redistribuídas)
            const baseOps = (isPrevisao && !isCurrMesP && totalOpen != null)
              ? data.ops.filter(o => !o.opt_top)
              : isCurrMesP
                ? data.ops.filter(o => !_isFutureNd(o.next_date))
                : data.ops;
            const stages  = [...new Set(baseOps.map(o => o.stage_num ? o.stage_num+' – '+(o.stage||'') : (o.stage||'')).filter(Boolean))].sort((a,b)=>parseInt(b)-parseInt(a));
            const sources = [...new Set(baseOps.map(o => o.source_nome || o.source_grupo || o.source).filter(Boolean))].sort();
            const statuses= [...new Set(baseOps.map(o => o.status).filter(Boolean))].sort();
            const nStarredD = baseOps.filter(o => o.opt_top).length;
            let ops = baseOps
              .filter(o => {
                if (filtStage  && (o.stage_num ? o.stage_num+' – '+(o.stage||'') : (o.stage||'')) !== filtStage)  return false;
                if (filtSource && (o.source_nome || o.source_grupo || o.source) !== filtSource) return false;
                if (filtStatus && o.status !== filtStatus) return false;
                if (filtStar   && !o.opt_top) return false;
                return true;
              })
              .slice()
              .sort((a, b) => {
                const va = sortCol==='stage_num'?parseInt(a.stage_num||0):sortCol==='valor'?a.valor:sortCol==='age'?(a.age||9999):a.data||'';
                const vb = sortCol==='stage_num'?parseInt(b.stage_num||0):sortCol==='valor'?b.valor:sortCol==='age'?(b.age||9999):b.data||'';
                return sortDir==='desc' ? (vb>va?1:vb<va?-1:0) : (va>vb?1:va<vb?-1:0);
              });
            const filterPill = (label, val, setVal, opts) => (
              <select value={val} onChange={e=>setVal(e.target.value)}
                style={{fontSize:10,padding:'2px 6px',borderRadius:5,border:'1px solid var(--dgd-border-1)',background:val?'color-mix(in oklch,#3b82f6 10%,white)':'var(--dgd-bg-surface)',color:val?'#1e40af':'var(--dgd-fg-3)',cursor:'pointer'}}>
                <option value="">Todos {label}</option>
                {opts.map(o=><option key={o} value={o}>{o}</option>)}
              </select>
            );
            return ops.length === 0 && !filtStage && !filtSource && !filtStatus
              ? <div style={{ padding:'32px', textAlign:'center', color:'var(--dgd-fg-3)', fontSize:13 }}>Não foram abertas oportunidades neste mês.</div>
              : <>
                  <div style={{display:'flex',gap:6,padding:'8px 16px',borderBottom:'1px solid var(--dgd-border-1)',flexWrap:'wrap',alignItems:'center'}}>
                    {filterPill('Stage',filtStage,setFiltStage,stages)}
                    {filterPill('Source',filtSource,setFiltSource,sources)}
                    {filterPill('Status',filtStatus,setFiltStatus,statuses)}
                    {nStarredD > 0 && <button onClick={()=>setFiltStar(s=>!s)} style={{fontSize:10,padding:'2px 8px',borderRadius:5,border:'1px solid var(--dgd-border-1)',background:filtStar?'#fef3c7':'var(--dgd-bg-surface)',color:filtStar?'#92400e':'var(--dgd-fg-3)',cursor:'pointer',display:'flex',alignItems:'center',gap:3}}><span style={{fontSize:12}}>⭐</span> Quentes ({nStarredD})</button>}
                    {(filtStage||filtSource||filtStatus||filtStar)&&<button onClick={()=>{setFiltStage('');setFiltSource('');setFiltStatus('');setFiltStar(false);}} style={{fontSize:10,padding:'2px 8px',borderRadius:5,border:'1px solid var(--dgd-border-1)',background:'#fee2e2',color:'#991b1b',cursor:'pointer'}}>✕ Limpar</button>}
                    <span style={{marginLeft:'auto',fontSize:10,color:'var(--dgd-fg-3)'}}>{ops.length} OP{ops.length!==1?'s':''}</span>
                  </div>
                  <table style={{ width:'100%', borderCollapse:'collapse' }}>
                    <thead>
                      <tr style={{ background:'var(--dgd-bg-surface,#f8fafc)', position:'sticky', top:0 }}>
                        {[nStarredD>0?['⭐',null]:null,comList?.length>0?['Comercial',null]:null,['Data','data'],tipo==='opmanagerf9'?['Age','age']:null,['Cliente',null],['Stage','stage_num'],['Produto',null],['Source',null],['Status',null],['Próx. contacto',null],['Fecho',null],['Valor','valor']].filter(Boolean).map(([h,col])=>(
                          <th key={h} onClick={col?()=>toggleSort(col):undefined}
                            style={{...C.th,cursor:col?'pointer':'default',userSelect:'none',color:sortCol===col?'var(--dgd-fg-1)':undefined,minWidth:h==='Cliente'?180:h==='Produto'?160:undefined}}>
                            {h}{col?sortIcon(col):''}
                          </th>
                        ))}
                      </tr>
                    </thead>
                    <tbody>
                      {ops.map((op, i) => (
                        <tr key={i}
                          onClick={() => op.op_id ? setOpDrill(op) : null}
                          style={{ background: op.opt_top?'color-mix(in oklch,#f59e0b 6%,transparent)':i%2===0?'transparent':'color-mix(in oklch,#94a3b8 3%,transparent)', cursor: op.op_id ? 'pointer' : 'default', transition:'background 0.1s' }}
                          onMouseEnter={e => { if (op.op_id) e.currentTarget.style.background='color-mix(in oklch,#3b82f6 8%,transparent)'; }}
                          onMouseLeave={e => { e.currentTarget.style.background = op.opt_top?'color-mix(in oklch,#f59e0b 6%,transparent)':i%2===0?'transparent':'color-mix(in oklch,#94a3b8 3%,transparent)'; }}
                        >
                          {nStarredD > 0 && <td style={{...C.td, textAlign:'center', fontSize:13, padding:'7px 6px'}}>{op.opt_top ? '⭐' : ''}</td>}
                          {comList?.length > 0 && <td style={{...C.td, fontSize:11}}>{op._comercialNome || '—'}</td>}
                          <td style={{...C.td, fontFamily:'var(--dgd-font-mono,monospace)', fontSize:11}}>{fmtFmDate(op.data)}</td>
                          {tipo === 'opmanagerf9' && <td style={{...C.td, fontFamily:'var(--dgd-font-mono,monospace)', fontSize:11, color:op.age>180?'#dc2626':op.age>90?'#d97706':'var(--dgd-fg-2)'}}>{op.age != null ? op.age+'d' : '—'}</td>}
                          <td style={{...C.td, maxWidth:220, overflow:'hidden', textOverflow:'ellipsis'}}>{op.entidade}</td>
                          <td style={{...C.td, fontSize:11}}>
                            <span style={{ display:'inline-block', padding:'1px 6px', borderRadius:4, fontSize:10, fontWeight:700, background: op.status==='WON'?'#dcfce7': op.status==='LOST'?'#fee2e2':'color-mix(in oklch,#3b82f6 12%,transparent)', color: op.status==='WON'?'#166534': op.status==='LOST'?'#991b1b':'#1e40af' }}>
                              {op.stage_num ? op.stage_num + ' – ' : ''}{op.stage || op.status}
                            </span>
                          </td>
                          <td style={{...C.td, fontSize:11, maxWidth:180, overflow:'hidden', textOverflow:'ellipsis'}}>{op.produto}</td>
                          <td style={{...C.td, fontSize:11, color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)'}}>{op.source_nome || op.source_grupo || op.source}</td>
                          <td style={{...C.td, fontSize:11}}>{op.status}</td>
                          <td style={{...C.td, fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)', color: op.next_date ? '#2563eb' : 'var(--dgd-fg-3)'}}>{op.next_date ? fmtFmDate(op.next_date) : '—'}</td>
                          <td style={{...C.td, fontSize:11, fontFamily:'var(--dgd-font-mono,monospace)', color:'var(--dgd-fg-3)'}}>{op.status!=='OPEN'&&op.close_date?fmtFmDate(op.close_date):'—'}</td>
                          <td style={{...C.td, textAlign:'right', fontWeight:600, fontFamily:'var(--dgd-font-mono,monospace)'}}>{fmtVal(op.valor)}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </>;
          })()}
          {data && isVendas && (
            data.rows.length === 0
              ? <div style={{ padding:'32px', textAlign:'center', color:'var(--dgd-fg-3)', fontSize:13 }}>Sem vendas Primavera neste período.</div>
              : <table style={{ width:'100%', borderCollapse:'collapse' }}>
                  <thead>
                    <tr style={{ background:'var(--dgd-bg-surface,#f8fafc)', position:'sticky', top:0 }}>
                      {data.rows.some(r => r.cliente) && <th style={{...C.th, minWidth:180}}>Cliente</th>}
                      <th style={{...C.th, minWidth:120}}>Artigo</th>
                      <th style={C.th}>Família</th>
                      {tipo !== 'margem' && <th style={{...C.th, textAlign:'right'}}>Qtd</th>}
                      <th style={{...C.th, textAlign:'right'}}>Valor</th>
                      {tipo === 'margem' && <th style={{...C.th, textAlign:'right'}}>Margem</th>}
                      {tipo === 'margem' && <th style={{...C.th, textAlign:'right'}}>%</th>}
                    </tr>
                  </thead>
                  <tbody>
                    {data.rows.map((r, i) => {
                      const mPct = parseFloat(r.margem||0) && parseFloat(r.valor||0) ? (parseFloat(r.margem)/parseFloat(r.valor)*100) : null;
                      const mCol = mPct == null ? 'var(--dgd-fg-3)' : mPct >= 30 ? '#16a34a' : mPct >= 15 ? '#d97706' : '#dc2626';
                      return (
                        <tr key={i} style={{ background: i%2===0?'transparent':'color-mix(in oklch,#94a3b8 3%,transparent)' }}>
                          {data.rows.some(r => r.cliente) && <td style={{...C.td, fontSize:12, maxWidth:220, overflow:'hidden', textOverflow:'ellipsis'}}>{r.cliente || '—'}</td>}
                          <td style={{...C.td, fontFamily:'var(--dgd-font-mono,monospace)', fontSize:11}}>{r.artigo}</td>
                          <td style={{...C.td, fontSize:11, color:'var(--dgd-fg-3)'}}>{r.familia}</td>
                          {tipo !== 'margem' && <td style={{...C.td, textAlign:'right', fontFamily:'var(--dgd-font-mono,monospace)', fontSize:11}}>{r.qtd}</td>}
                          <td style={{...C.td, textAlign:'right', fontWeight:600, fontFamily:'var(--dgd-font-mono,monospace)'}}>{fmtVal(parseFloat(r.valor))}</td>
                          {tipo === 'margem' && <td style={{...C.td, textAlign:'right', fontWeight:600, fontFamily:'var(--dgd-font-mono,monospace)', color:mCol}}>{fmtVal(parseFloat(r.margem||0))}</td>}
                          {tipo === 'margem' && <td style={{...C.td, textAlign:'right', fontWeight:700, color:mCol}}>{mPct != null ? mPct.toFixed(1)+'%' : '—'}</td>}
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
          )}
          {data && isWinrate && (() => {
            const wonOps  = (data.ops||[]).filter(o=>o.status==='WON').sort((a,b)=>b.valor-a.valor);
            const lostOps = (data.ops||[]).filter(o=>o.status==='LOST').sort((a,b)=>b.valor-a.valor);
            const wonVal  = wonOps.reduce((s,o)=>s+(o.valor||0),0);
            const lostVal = lostOps.reduce((s,o)=>s+(o.valor||0),0);
            const cols    = ['Fecho','Cliente','Produto','Fonte','Valor'];
            const mkRow   = (op,color,idx) => (
              <tr key={idx} style={{borderBottom:'1px solid var(--dgd-border-1)',background:idx%2===0?'transparent':'color-mix(in oklch,#94a3b8 3%,transparent)'}}>
                <td style={{...C.td,fontFamily:'var(--dgd-font-mono,monospace)',fontSize:11,color:'var(--dgd-fg-3)',whiteSpace:'nowrap'}}>{fmtFmDate(op.close_date||op.data)}</td>
                <td style={{...C.td,maxWidth:220,overflow:'hidden',textOverflow:'ellipsis'}}>{op.entidade||'—'}</td>
                <td style={{...C.td,fontSize:11,maxWidth:180,overflow:'hidden',textOverflow:'ellipsis'}}>{op.produto||'—'}</td>
                <td style={{...C.td,fontSize:11,color:'var(--dgd-fg-3)'}}>{op.source_nome||op.source_grupo||op.source||'—'}</td>
                <td style={{...C.td,textAlign:'right',fontWeight:700,fontFamily:'var(--dgd-font-mono,monospace)',color}}>{fmtVal(op.valor)}</td>
              </tr>
            );
            const mkSection = (label,ops2,val,color,bg) => (
              <>
                <div style={{padding:'6px 16px',background:bg,borderBottom:'1px solid var(--dgd-border-1)',display:'flex',alignItems:'center',gap:10}}>
                  <span style={{fontSize:10,fontWeight:700,color,letterSpacing:'0.08em'}}>{label}</span>
                  <span style={{fontSize:11,color:'var(--dgd-fg-3)'}}>{ops2.length} OP{ops2.length!==1?'s':''}</span>
                  <span style={{marginLeft:'auto',fontWeight:700,fontFamily:'var(--dgd-font-mono,monospace)',color}}>{fmtVal(val)}</span>
                </div>
                <table style={{width:'100%',borderCollapse:'collapse'}}>
                  <thead><tr style={{background:'var(--dgd-bg-surface,#f8fafc)'}}>
                    {cols.map(h=><th key={h} style={{...C.th,textAlign:h==='Valor'?'right':'left'}}>{h}</th>)}
                  </tr></thead>
                  <tbody>
                    {ops2.length>0?ops2.map((op,i)=>mkRow(op,color,i))
                      :<tr><td colSpan={5} style={{...C.td,color:'var(--dgd-fg-3)',textAlign:'center',fontStyle:'italic'}}>Sem OPs</td></tr>}
                  </tbody>
                </table>
              </>
            );
            if (wonOps.length === 0 && lostOps.length === 0) return (
              <div style={{padding:'48px 32px',textAlign:'center',color:'var(--dgd-fg-3)',fontSize:13}}>
                Sem fechos neste período.
              </div>
            );
            return (
              <>
                {mkSection('WON',wonOps,wonVal,'#166634','color-mix(in oklch,#16a34a 7%,transparent)')}
                <div style={{borderTop:'2px solid var(--dgd-border-1)'}}/>
                {mkSection('LOST',lostOps,lostVal,'#991b1b','color-mix(in oklch,#dc2626 7%,transparent)')}
              </>
            );
          })()}
        </div>

        {/* Footer total */}
        {data && !isWinrate && (()=>{
          const fOps = !isVendas ? data.ops.filter(o => {
            // Stage 90/92 mês actual: excluir ops redistribuídas para mês futuro
            if (isCurrMesP && o.next_date) {
              const p = o.next_date.split('/');
              if (p.length === 3) {
                const ndM = parseInt(p[0]) - 1, ndY = parseInt(p[2]);
                if (ndY === new Date().getFullYear() && ndM > RPC_MES_FECH) return false;
              }
            }
            const stgK = o.stage_num ? o.stage_num+' – '+(o.stage||'') : (o.stage||'');
            const srcK = o.source_nome || o.source_grupo || o.source;
            if (filtStage  && stgK   !== filtStage)  return false;
            if (filtSource && srcK   !== filtSource) return false;
            if (filtStatus && o.status !== filtStatus) return false;
            return true;
          }) : data.rows;
          const fTotal = !isVendas
            ? fOps.reduce((s,o)=>s+(o.valor||0),0)
            : fOps.reduce((s,r)=>s+parseFloat(r.valor||0),0);
          const fMargem = (isVendas && tipo === 'margem')
            ? fOps.reduce((s,r)=>s+parseFloat(r.margem||0),0) : null;
          const fMargemPct = (fMargem != null && fTotal > 0) ? fMargem/fTotal*100 : null;
          const hasF = !!(filtStage||filtSource||filtStatus);
          const parts = [filtStage&&('Stage: '+filtStage.split(' – ')[0]), filtSource&&('Source: '+filtSource), filtStatus&&('Status: '+filtStatus)].filter(Boolean);
          const lbl = hasF ? 'Total ('+parts.join(' · ')+')' : 'Total';
          const isPastMes = typeof mesIdx === 'number' && mesIdx < RPC_MES_FECH;
          const previsaoProj = (!isVendas && isPrevisao && !isCurrMesP)
            ? previsaoTotYTD != null ? previsaoTotYTD
              : pipeVivoPast != null && drillWinRate != null ? pipeVivoPast * drillWinRate / 100
              : totalOpen != null && drillWinRate != null ? totalOpen * drillWinRate / 100
              : null
            : null;
          return (
            <div style={{ padding:'10px 20px', borderTop:'1px solid var(--dgd-border-1)', display:'flex', justifyContent:'space-between', alignItems:'center', flexShrink:0, background:'var(--dgd-bg-surface,#f8fafc)', borderRadius:'0 0 12px 12px' }}>
              <span style={{ fontSize:11, color:'var(--dgd-fg-3)' }}>{fOps.length} {isVendas ? 'artigos' : 'oportunidades'}</span>
              <div style={{textAlign:'right'}}>
                {hasF && <div style={{fontSize:10,color:'#3b82f6',marginBottom:1}}>{lbl}</div>}
                {previsaoProj != null && pipeVivoPast != null && previsaoTotYTD == null && (
                  <div style={{fontSize:10,color:'var(--dgd-fg-3)',marginBottom:3}}>
                    Pipeline open à data: {fmtVal(pipeVivoPast)} × Rácio VP YTD: {drillWinRate != null ? drillWinRate.toFixed(1)+'%' : '—'}
                  </div>
                )}
                {previsaoProj != null && pipeVivoPast == null && previsaoTotYTD == null && (
                  <div style={{fontSize:10,color:'var(--dgd-fg-3)',marginBottom:3}}>
                    Pipeline OPEN: {fmtVal(totalOpen ?? fTotal)} × Rácio VP YTD: {drillWinRate != null ? drillWinRate.toFixed(1)+'%' : '—'}
                  </div>
                )}
                {previsaoProj != null && previsaoTotYTD != null && (
                  <div style={{fontSize:10,color:'var(--dgd-fg-3)',marginBottom:3}}>
                    Previsão = Mês actual (Stage 90/92) + ⭐ M+1 + Pipeline×WR M+2+
                  </div>
                )}
                {fMargem != null && (
                  <div style={{fontSize:10,color:'var(--dgd-fg-3)',marginBottom:3}}>
                    Valor total: {fmtVal(fTotal)}
                  </div>
                )}
                <div style={{display:'flex',alignItems:'baseline',gap:8,justifyContent:'flex-end'}}>
                  {previsaoProj != null && <span style={{fontSize:10,color:'var(--dgd-fg-3)'}}>Previsão:</span>}
                  {fMargem != null && <span style={{fontSize:10,color:'var(--dgd-fg-3)'}}>Margem total:</span>}
                  <span style={{ fontFamily:'var(--dgd-font-mono,monospace)', fontSize:14, fontWeight:700, color:'var(--dgd-fg-1)' }}>{fmtVal(previsaoProj != null ? previsaoProj : fMargem != null ? fMargem : fTotal)}</span>
                  {fMargemPct != null && <span style={{fontFamily:'var(--dgd-font-mono,monospace)',fontSize:13,fontWeight:700,color:fMargemPct>=30?'#16a34a':fMargemPct>=15?'#d97706':'#dc2626'}}>{fMargemPct.toFixed(1)}%</span>}
                </div>
              </div>
            </div>
          );
        })()}
        {data && isWinrate && (()=>{
          const wonOps  = (data.ops||[]).filter(o=>o.status==='WON');
          const lostOps = (data.ops||[]).filter(o=>o.status==='LOST');
          const wonVal  = wonOps.reduce((s,o)=>s+(o.valor||0),0);
          const lostVal = lostOps.reduce((s,o)=>s+(o.valor||0),0);
          const wr      = (wonVal+lostVal)>0?wonVal/(wonVal+lostVal)*100:null;
          const wrColor = wr==null?'var(--dgd-fg-3)':wr>=25?'#16a34a':wr>=15?'#d97706':'#dc2626';
          return (
            <div style={{padding:'12px 20px',borderTop:'2px solid var(--dgd-border-1)',background:'var(--dgd-bg-surface,#f8fafc)',borderRadius:'0 0 12px 12px',flexShrink:0}}>
              <div style={{display:'flex',alignItems:'baseline',gap:8,flexWrap:'wrap'}}>
                <span style={{fontFamily:'var(--dgd-font-mono,monospace)',color:'#166534',fontWeight:700}}>{fmtVal(wonVal)}</span>
                <span style={{color:'var(--dgd-fg-3)'}}>÷</span>
                <span style={{color:'var(--dgd-fg-2)',fontSize:12}}>({fmtVal(wonVal)} + {fmtVal(lostVal)})</span>
                <span style={{color:'var(--dgd-fg-3)'}}>= </span>
                <span style={{fontFamily:'var(--dgd-font-mono,monospace)',fontSize:18,fontWeight:700,color:wrColor}}>{wr!=null?wr.toFixed(1)+'%':'—'}</span>
                <span style={{fontSize:11,color:'var(--dgd-fg-3)'}}>Win Rate</span>
                {wr!=null&&<span style={{marginLeft:'auto',fontSize:11,color:'var(--dgd-fg-3)'}}>{wonOps.length} WON · {lostOps.length} LOST</span>}
              </div>
              {(()=>{const wrU=(wonOps.length+lostOps.length)>0?wonOps.length/(wonOps.length+lostOps.length)*100:null;const wrUColor=wrU==null?'var(--dgd-fg-3)':wrU>=25?'#16a34a':wrU>=15?'#d97706':'#dc2626';return(
              <div style={{display:'flex',alignItems:'baseline',gap:8,flexWrap:'wrap',marginTop:4,paddingTop:4,borderTop:'1px dashed var(--dgd-border-1)'}}>
                <span style={{fontFamily:'var(--dgd-font-mono,monospace)',color:'#166534',fontWeight:700,fontSize:13}}>{wonOps.length} OPs</span>
                <span style={{color:'var(--dgd-fg-3)',fontSize:13}}>÷</span>
                <span style={{color:'var(--dgd-fg-2)',fontSize:11}}>({wonOps.length} + {lostOps.length})</span>
                <span style={{color:'var(--dgd-fg-3)',fontSize:13}}>= </span>
                <span style={{fontFamily:'var(--dgd-font-mono,monospace)',fontSize:14,fontWeight:700,color:wrUColor}}>{wrU!=null?wrU.toFixed(1)+'%':'—'}</span>
                <span style={{fontSize:11,color:'var(--dgd-fg-3)'}}>Win Rate Unitário</span>
              </div>);})()}
            </div>
          );
        })()}
      </div>
    </>
  );
};

// ─── Hook: BP targets por vendedor/mês ────────────────────────────────────────
const useBPTargets = (ano) => {
  const [bpMap, setBPMap] = React.useState({});
  React.useEffect(() => {
    fetch(`/api/rpc-es/bp-targets?ano=${ano}`)
      .then(r => r.ok ? r.json() : {})
      .then(data => setBPMap(data || {}))
      .catch(() => {});
  }, [ano]);
  const saveBP = React.useCallback((vendedor, mes, valor) => {
    setBPMap(prev => {
      const arr = [...(prev[vendedor] || Array(12).fill(0))];
      arr[mes] = Number(valor) || 0;
      return { ...prev, [vendedor]: arr };
    });
    fetch('/api/rpc-es/bp-targets', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ vendedor, ano, mes: mes + 1, valor: Number(valor) || 0 }),
    }).catch(() => {});
  }, [ano]);
  return { bpMap, saveBP };
};

// ─── BacklogModal ─────────────────────────────────────────────────────────────
const BacklogModal = ({ com, backlogEntry, tipo, ytdTot, ano, empresa, onClose }) => {
  const [sortCol2, setSortCol2] = React.useState('stage');
  const [sortDir2, setSortDir2] = React.useState('desc');
  const [opDrill, setOpDrill] = React.useState(null);
  const [filtBStage,  setFiltBStage]  = React.useState('');
  const [filtBSource, setFiltBSource] = React.useState('');
  const [filtBStatus, setFiltBStatus] = React.useState('');
  const [filtBStar,   setFiltBStar]   = React.useState(false);
  const toggleSort2 = (col) => { setSortCol2(col); setSortDir2(s => s === 'desc' && sortCol2 === col ? 'asc' : 'desc'); };
  const sortIcon2 = (col) => sortCol2 !== col ? ' ↕' : sortDir2 === 'desc' ? ' ↓' : ' ↑';
  const allOps = backlogEntry?.ops || [];
  const hasComercial = allOps.some(o => o._comercialNome);
  // helper: parseia next_date (MM/DD/YYYY) → mês 0-based do ano corrente, ou null
  const _ndMonth = (nd) => {
    if (!nd) return null;
    const p = nd.split('/');
    if (p.length !== 3) return null;
    const m = parseInt(p[0]) - 1, y = parseInt(p[2]);
    return y === new Date().getFullYear() ? m : null;
  };
  const ops = (tipo === 'optmkt'    ? allOps.filter(o => o.isMKT)
             : tipo === 'optcom'    ? allOps.filter(o => !o.isMKT)
             // M+1: opt_top não-stage90 + stage90 com next_date em M+1
             : tipo === 'opttop'    ? allOps.filter(o => {
                 if (o.opt_top && (o.stage_num || 0) < 90) return true;
                 const ndM = _ndMonth(o.next_date);
                 return (o.stage_num || 0) >= 90 && ndM === RPC_MES_FECH + 1;
               })
             // Stage 90/92 mês actual: exclui ops com next_date num mês futuro (já redistribuídas)
             : tipo === 'stage9092' ? allOps.filter(o => {
                 if ((o.stage_num || 0) < 90) return false;
                 const ndM = _ndMonth(o.next_date);
                 return ndM === null || ndM <= RPC_MES_FECH;
               })
             : allOps)
    .filter(o => {
      if (filtBStage  && (o.stage||'') !== filtBStage)  return false;
      if (filtBSource && (o.source||'') !== filtBSource) return false;
      if (filtBStatus && (o.status||'') !== filtBStatus) return false;
      if (filtBStar   && !o.opt_top) return false;
      return true;
    })
    .slice()
    .sort((a, b) => {
      const stgN = s => { const s2=(s||'').trim().toUpperCase(); const n=parseInt(s2.split(/\D/)[0]); if(!isNaN(n)&&n>0) return n; const M={'AG FINANCEIRO':90,'FECHO':92,'AG DECISAO':80,'AG PROPOSTA':70,'DEMO':50,'EVENTO':50,'QUALIFICADO':40,'INTERESSE':20,'LEAD':10}; return M[s2]||0; };
      const va = sortCol2==='stage'?stgN(a.stage):sortCol2==='val'?a.val:sortCol2==='age'?(a.age||9999):a.data||'';
      const vb = sortCol2==='stage'?stgN(b.stage):sortCol2==='val'?b.val:sortCol2==='age'?(b.age||9999):b.data||'';
      return sortDir2==='desc'?(vb>va?1:vb<va?-1:0):(va>vb?1:va<vb?-1:0);
    });
  const tipoLabel = tipo === 'opmanagerf9' ? 'OP Pipeline' : tipo === 'optmkt' ? 'Lead/OP Mkt' : tipo === 'opttop' ? '⭐ OPs Quentes M+1' : tipo === 'stage9092' ? 'Stage 90/92' : 'Lead/OP Com';
  const backlogVal = tipo === 'opmanagerf9' ? (backlogEntry?.opmanagerf9 || 0)
                   : tipo === 'optmkt'       ? (backlogEntry?.optmkt || 0)
                   : tipo === 'stage9092'    ? (ytdTot || 0)
                   : (backlogEntry?.optcom || 0);
  const fmtE = v => new Intl.NumberFormat('pt-PT', { maximumFractionDigits:0 }).format(v) + ' €';
  const fmtDate = s => {
    if (!s) return '—';
    const p = s.split('/'); // MM/DD/YYYY
    return p.length === 3 ? `${p[1]}/${p[0]}/${p[2].slice(-2)}` : s;
  };
  const stageBadge = name => {
    const n = parseInt((name||'').split(/\D/)[0]) || 0;
    const bg = n >= 90 ? '#16a34a' : n >= 80 ? '#d97706' : n >= 50 ? '#f59e0b' : n >= 20 ? '#3b82f6' : '#f97316';
    return { display:'inline-block', padding:'2px 7px', borderRadius:4, fontSize:10, fontWeight:700, color:'white', background:bg, whiteSpace:'nowrap' };
  };
  const sourceColor = s => ({ WEB:'#3b82f6',ADS:'#3b82f6',NEWSLETTER:'#6366f1',COMERCIAL:'#0ea5e9',FEIRA:'#0d9488',PARTNER:'#8b5cf6','GESTORA CONTA':'#7c3aed' }[s] || '#94a3b8');
  const statusStyle = s => ({ color: s==='OPEN'?'#16a34a':s==='WON'?'#16a34a':s==='LOST'?'#dc2626':'var(--dgd-fg-2)', fontWeight:600, fontSize:11 });
  const totalVal = ops.reduce((s,o) => s + (o.val||0), 0);
  const baseOps2 = tipo === 'optmkt' ? allOps.filter(o => o.isMKT) : tipo === 'optcom' ? allOps.filter(o => !o.isMKT) : allOps;
  return (<>
    <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.45)',zIndex:1000,display:'flex',alignItems:'flex-start',justifyContent:'center',paddingTop:40}} onClick={onClose}>
      <div style={{background:'var(--dgd-bg-card,white)',borderRadius:12,border:'1px solid var(--dgd-border-1)',width:'min(960px,96vw)',maxHeight:'80vh',display:'flex',flexDirection:'column',boxShadow:'0 8px 32px rgba(0,0,0,.18)'}} onClick={e=>e.stopPropagation()}>
        {/* Header */}
        <div style={{padding:'12px 16px',borderBottom:'1px solid var(--dgd-border-1)',display:'flex',alignItems:'center',gap:10}}>
          <span style={{background:'#1e293b',color:'white',fontSize:10,fontWeight:700,padding:'3px 8px',borderRadius:4,letterSpacing:'0.04em'}}>{tipoLabel.toUpperCase()}</span>
          <span style={{fontSize:13,fontWeight:700,color:'var(--dgd-fg-1)'}}>{com.nome}</span>
          <span style={{fontSize:11,color:'var(--dgd-fg-3)'}}>·</span>
          <span style={{fontSize:11,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)'}}>{com.vendedor}</span>
          <span style={{fontSize:11,color:'var(--dgd-fg-3)'}}>· Pipeline actual</span>
          <button onClick={onClose} style={{marginLeft:'auto',background:'none',border:'none',cursor:'pointer',fontSize:20,color:'var(--dgd-fg-3)',lineHeight:1,padding:'0 4px'}}>×</button>
        </div>
        {/* Filter bar */}
        {(() => {
          const bStages  = [...new Set(allOps.map(o=>o.stage||'').filter(Boolean))].sort((a,b)=>parseInt(b)-parseInt(a));
          const bSources = [...new Set(allOps.map(o=>o.source||'').filter(Boolean))].sort();
          const bStatuses= [...new Set(allOps.map(o=>o.status||'').filter(Boolean))].sort();
          const fpill = (label,val,setVal,opts) => (
            <select value={val} onChange={e=>setVal(e.target.value)} style={{fontSize:10,padding:'2px 6px',borderRadius:5,border:'1px solid var(--dgd-border-1)',background:val?'color-mix(in oklch,#3b82f6 10%,white)':'var(--dgd-bg-surface)',color:val?'#1e40af':'var(--dgd-fg-3)',cursor:'pointer'}}>
              <option value="">Todos {label}</option>
              {opts.map(o=><option key={o} value={o}>{o}</option>)}
            </select>
          );
          return (
            <div style={{display:'flex',gap:6,padding:'8px 16px',borderBottom:'1px solid var(--dgd-border-1)',flexWrap:'wrap',alignItems:'center'}}>
              {fpill('Stage',filtBStage,setFiltBStage,bStages)}
              {fpill('Source',filtBSource,setFiltBSource,bSources)}
              {fpill('Status',filtBStatus,setFiltBStatus,bStatuses)}
              {(filtBStage||filtBSource||filtBStatus)&&<button onClick={()=>{setFiltBStage('');setFiltBSource('');setFiltBStatus('');}} style={{fontSize:10,padding:'2px 8px',borderRadius:5,border:'1px solid var(--dgd-border-1)',background:'#fee2e2',color:'#991b1b',cursor:'pointer'}}>✕ Limpar</button>}
              <span style={{marginLeft:'auto',fontSize:10,color:'var(--dgd-fg-3)'}}>{ops.length} OP{ops.length!==1?'s':''}</span>
            </div>
          );
        })()}
        {/* Table */}
        <div style={{overflowY:'auto',flex:1}}>
          {ops.length === 0
            ? <div style={{padding:'32px',textAlign:'center',color:'var(--dgd-fg-3)',fontSize:12}}>Sem OPs em aberto.</div>
            : (
              <table style={{width:'100%',borderCollapse:'collapse',fontSize:12}}>
                <thead>
                  <tr style={{background:'var(--dgd-bg-surface,#f8fafc)',position:'sticky',top:0,zIndex:1}}>
                    {[['⭐','center',null],...(hasComercial?[['COMERCIAL','left',null]]:[]),['DATA','left',null],['CLIENTE','left',null],['STAGE','left','stage'],['PRODUTO','left',null],['SOURCE','left',null],['STATUS','left',null],['PRÓX. CONTACTO','left',null],['FECHO','left',null],['VAL','right','val']].map(([h,a,col]) => (
                      <th key={h} onClick={col?()=>toggleSort2(col):undefined}
                        style={{padding:'6px 10px',textAlign:a,fontWeight:600,color:sortCol2===col?'var(--dgd-fg-1)':'var(--dgd-fg-3)',fontSize:10,letterSpacing:'0.06em',borderBottom:'1px solid var(--dgd-border-1)',whiteSpace:'nowrap',cursor:col?'pointer':'default',userSelect:'none'}}>
                        {h}{col?sortIcon2(col):''}
                      </th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {ops.map((op,i) => (
                    <tr key={i} onClick={() => setOpDrill(op)} style={{borderBottom:'1px solid var(--dgd-border-1)',background:op.opt_top?'color-mix(in oklch,#f59e0b 6%,transparent)':i%2===0?'transparent':'color-mix(in oklch,#94a3b8 3%,transparent)',cursor:'pointer'}}
                      onMouseEnter={e=>e.currentTarget.style.background='color-mix(in oklch,#3b82f6 8%,transparent)'}
                      onMouseLeave={e=>e.currentTarget.style.background=op.opt_top?'color-mix(in oklch,#f59e0b 6%,transparent)':i%2===0?'transparent':'color-mix(in oklch,#94a3b8 3%,transparent)'}>
                      <td style={{padding:'5px 10px',textAlign:'center',fontSize:13,whiteSpace:'nowrap'}}>{op.opt_top?'⭐':''}</td>
                      {hasComercial&&<td style={{padding:'5px 10px',color:'var(--dgd-fg-2)',fontSize:11,whiteSpace:'nowrap'}}>{op._comercialNome||'—'}</td>}
                      <td style={{padding:'5px 10px',color:'var(--dgd-fg-2)',fontFamily:'var(--dgd-font-mono,monospace)',fontSize:11,whiteSpace:'nowrap'}}>{fmtDate(op.data)}</td>
                      <td style={{padding:'5px 10px',color:'var(--dgd-fg-1)',maxWidth:180,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{op.entidade||'—'}</td>
                      <td style={{padding:'5px 10px',whiteSpace:'nowrap'}}>{op.stage ? <span style={stageBadge(op.stage)}>{op.stage}</span> : '—'}</td>
                      <td style={{padding:'5px 10px',color:'var(--dgd-fg-1)',maxWidth:160,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{op.produto||'—'}</td>
                      <td style={{padding:'5px 10px',whiteSpace:'nowrap'}}><span style={{fontSize:11,fontWeight:600,color:sourceColor(op.source)}}>{op.source||'—'}</span></td>
                      <td style={{padding:'5px 10px',whiteSpace:'nowrap'}}><span style={statusStyle(op.status)}>{op.status||'—'}</span></td>
                      <td style={{padding:'5px 10px',color:op.next_date?'#2563eb':'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',fontSize:11,whiteSpace:'nowrap'}}>{op.next_date?fmtDate(op.next_date):'—'}</td>
                      <td style={{padding:'5px 10px',color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',fontSize:11,whiteSpace:'nowrap'}}>{op.status!=='OPEN'&&op.close_date?fmtDate(op.close_date):'—'}</td>
                      <td style={{padding:'5px 10px',textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontWeight:600,color:'var(--dgd-fg-1)',whiteSpace:'nowrap'}}>{fmtE(op.val)}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            )
          }
        </div>
        {/* Footer */}
        {(()=>{
          const totalAno2 = backlogVal > 0 ? baseOps2.filter(o=>{const p=(o.data||'').split('/');return parseInt(p[2])===ano;}).reduce((s,o)=>s+(o.val||0),0) : 0;
          const totalAnt2 = backlogVal - totalAno2;
          const rS = {display:'flex',justifyContent:'space-between',alignItems:'center',padding:'3px 0'};
          const mS = {fontFamily:'var(--dgd-font-mono,monospace)',color:'var(--dgd-fg-2)',fontSize:12};
          const hasF2 = !!(filtBStage||filtBSource||filtBStatus);
          const partsF2 = [filtBStage&&('Stage: '+filtBStage.split(' – ')[0]),filtBSource&&('Source: '+filtBSource),filtBStatus&&('Status: '+filtBStatus)].filter(Boolean);
          return (
            <div style={{padding:'10px 16px',borderTop:'1px solid var(--dgd-border-1)',background:'var(--dgd-bg-surface,#f8fafc)',fontSize:12}}>
              <div style={{...rS,marginBottom:totalAnt2>0?4:0}}>
                <span style={{color:'var(--dgd-fg-3)'}}>Pipeline OPEN actual<InfoTip text='OPs com status OPEN neste momento (qualquer ano). É o stock de pipeline activo do vendedor.' /></span>
                <div style={{textAlign:'right'}}>
                  {hasF2&&<div style={{fontSize:10,color:'#3b82f6',marginBottom:1}}>{'Total ('+partsF2.join(' · ')+')'}</div>}
                  <span style={{fontWeight:700,...mS,color:'var(--dgd-fg-1)',fontSize:13}}>{fmtE(totalVal)}</span>
                </div>
              </div>
              {totalAnt2>0&&(
                <div style={{borderTop:'1px solid var(--dgd-border-1)',paddingTop:4}}>
                  <div style={rS}><span style={{color:'var(--dgd-fg-3)'}}>Pipeline {ano}<InfoTip text={'OPs OPEN criadas em '+ano+'. Exclui WON e LOST — só o que está activo.'} /></span><span style={mS}>{fmtE(totalAno2)}</span></div>
                  <div style={rS}><span style={{color:'var(--dgd-fg-3)'}}>Anteriores a {ano}<InfoTip text={'OPs OPEN criadas antes de '+ano+' que ainda não foram fechadas (backlog histórico).'} /></span><span style={mS}>{fmtE(totalAnt2)}</span></div>
                </div>
              )}
            </div>
          );
        })()}
      </div>
    </div>
    {opDrill && (() => {
      const Panel = window.OpDetailPanel;
      const fmOp = {
        ID_OPORTUNIDADE: opDrill.op_id || '',
        Entidade_Nome:   opDrill.entidade || '',
        Produto_Name:    opDrill.produto || '',
        Stage_Name:      opDrill.stage || '',
        Stage_Number:    String(opDrill.stage_num || ''),
        Produto_Valor:   String(opDrill.val || 0),
        Status:          opDrill.status || 'OPEN',
        Opt_Top:         opDrill.opt_top ? '1' : '0',
        StartDate:       opDrill.data || '',
        ID_Empresa:      empresa || 'PTDIG',
        ID_Source:       '',
        Equipa_Name:     '',
        ID_Entidade:     '',
        Next_Date:       opDrill.next_date || '',
      };
      if (!Panel) return <OpNotasModal op={fmOp} onClose={() => setOpDrill(null)} />;
      return (
        <div onClick={() => setOpDrill(null)} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.45)', zIndex: 4000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <div onClick={e => e.stopPropagation()} style={{ borderRadius: 12, overflow: 'hidden', boxShadow: '0 20px 60px rgba(0,0,0,0.3)' }}>
            <Panel op={fmOp} onClose={() => setOpDrill(null)} empresa={empresa || 'PTDIG'} />
          </div>
        </div>
      );
    })()}
  </>);
};

// ─── 1. MAPA RPC ──────────────────────────────────────────────────────────────
const RPCGrid = ({ com, d, onDrilldown, canEditBP, onBPEdit, backlogEntry, onBacklog, objMap, onObjEdit, hideAI, comList }) => {
  const { optmkt,optcom,opmanagerf9,bp,f9,faturado,opsWon,lostValor,pipelineStock,totalOpenPipeline,stage90,margemMensal } = d;
  const [editCell, setEditCell] = React.useState(null);
  const [editObjRow, setEditObjRow] = React.useState(null);
  const [objInfoModal, setObjInfoModal] = React.useState(null);
  const [aiData, setAiData]       = React.useState(null);
  const [aiLoading, setAiLoading] = React.useState(false);
  const s    = (arr,n) => arr.slice(0,n).reduce((a,v)=>a+(v||0),0);
  const mf   = RPC_MES_FECH+1;
  const rPct  = v => v == null ? '—' : v.toFixed(0) + '%';
  const rPctK = v => v == null ? '—' : v >= 1000 ? Math.round(v/1000)+'k%' : v.toFixed(0)+'%';
  const rMult = v => v == null ? '—' : v.toFixed(1).replace('.',',') + 'x';
  const _n12  = new Array(12).fill(null);
  const lostV  = lostValor    || _n12;
  const stockV = pipelineStock || _n12;

  const winRate = Array.from({length:12}, (_,i) => {
    const w = opsWon[i]||0, l = lostV[i]||0;
    return (w+l) > 0 ? w/(w+l)*100 : null;
  });
  const winRateTot = (() => { const w=s(opsWon,mf), l=s(lostV,mf); return (w+l)>0?w/(w+l)*100:null; })();

  // Cobertura mensal: stockV[i] / (BP Anual × Cobertura YTD)
  // = "que fracção do pipeline total necessário para o ano tenho construída neste mês?"
  // 1x = tenho o pipeline suficiente para cumprir o BP ao ritmo actual de conversão
  const bpAnual = s(bp,12);
  const totalOpen = backlogEntry?.opmanagerf9 || totalOpenPipeline || stockV[mf-1]; // backlog endpoint é fonte primária (todos os anos)
  const bpFaltaYTD = bpAnual - s(faturado, mf);
  const coberturaTot = totalOpen && bpAnual > 0 ? totalOpen / bpAnual : null;
  const pipelineGap4x = totalOpen != null && bpAnual > 0 ? totalOpen - bpAnual * 4 : null;
  const pipe2026YTD = stockV[mf-1] || 0;
  // Cobertura mensal: pipeline total (2026 + anos anteriores) ÷ BP Anual
  // pipeAnterio = OPs de anos anteriores ainda abertas (constante para todos os meses)
  const pipeAnterioMensal = Math.max((totalOpen || 0) - pipe2026YTD, 0);
  const cobertura = Array.from({length:12}, (_,i) => {
    // Mês activo e futuros: pipeline OPEN real (todos os anos). Passados: stock histórico.
    const pipe = i >= RPC_MES_FECH
      ? (totalOpen || 0)
      : (stockV[i] || 0) + pipeAnterioMensal;
    return pipe > 0 && bpAnual > 0 ? pipe / bpAnual : null;
  });

  // Rácio Vendas/Pipeline = Vendas Faturadas / OPMANAGERF9 do mês (pipeline gerado)
  // Denominador é o pipeline GERADO no mês (não o stock total em open) — decisão reunião 24 Ago
  const racioVP = Array.from({length:12}, (_,i) => {
    // denominador = pipeline open à data do mês (não o open gerado nesse mês)
    const pipe = stockV[i];
    return pipe && faturado[i] != null ? faturado[i]/pipe*100 : null;
  });
  // YTD = média simples dos meses que têm valor (não total/total)
  const racioVPTot = (() => {
    const vals = racioVP.slice(0, mf).filter(v => v != null);
    return vals.length > 0 ? vals.reduce((a,v) => a+v, 0) / vals.length : null;
  })();
  // YTD real: vendas faturadas acumuladas ÷ OPs vivas em aberto agora (Ticket 110)
  const racioVPYTD = (() => {
    const vendasYTD = s(faturado, mf);
    const opVivas = backlogEntry?.opmanagerf9 != null
      ? backlogEntry.opmanagerf9
      : (totalOpenPipeline || null);
    return opVivas > 0 ? vendasYTD / opVivas * 100 : null;
  })();

  const desvio = Array.from({length:12}, (_,i) => {
    const cumFat = faturado.slice(0,i+1).reduce((s,x)=>s+(x||0),0);
    const cumBP  = bp.slice(0,i+1).reduce((s,x)=>s+(x||0),0);
    if (cumFat===0 && cumBP===0) return null;
    return cumFat - cumBP;
  });

  // Margem %
  const marg = margemMensal || _n12;
  const margemPct = Array.from({length:12}, (_,i) => {
    const fat = faturado[i], m = marg[i];
    return (fat && fat > 0 && m != null) ? m/fat*100 : null;
  });
  const margemPctYTD = (() => {
    const fatTot = s(faturado, mf), margTot = s(marg.map(v=>v||0), mf);
    return fatTot > 0 ? margTot/fatTot*100 : null;
  })();

  // Previsão de Vendas: meses passados=stock do mês × rácio, mês activo=stage90, M+1=OPs quentes, M+2+=pipeline OPEN × rácio
  const stk90 = stage90 || _n12;
  // M+2+ usa o pipeline OPEN da lista (o 1,4 M€), não o stock reconstruído até Dezembro.
  const pipelineExclStar = backlogEntry && backlogEntry.opmanagerf9 != null
    ? backlogEntry.opmanagerf9
    : totalOpenPipeline;
  // OPs opt_top que já estão em stage 90/92 — contariam em M_actual E M+1, corrigir overlap
  const optTopStage90Overlap = (backlogEntry?.ops || [])
    .filter(o => o.opt_top && o.stage_num >= 90)
    .reduce((s, o) => s + (o.val || 0), 0);
  const estrelasM1 = Math.max(0, (backlogEntry?.optTopValor || 0) - optTopStage90Overlap);

  // Redistribuição de stage 90/92 por next_date (opção 2):
  // OPs com next_date num mês futuro saem do mês actual e entram no mês certo
  const stage90Adj = new Array(12).fill(0);
  const _currYear = new Date().getFullYear();
  (backlogEntry?.ops || [])
    .filter(o => o.stage_num >= 90 && o.next_date)
    .forEach(o => {
      const p = (o.next_date || '').split('/'); // MM/DD/YYYY
      if (p.length !== 3) return;
      const ndM = parseInt(p[0]) - 1; // 0-based
      const ndY = parseInt(p[2]);
      if (isNaN(ndM) || isNaN(ndY) || ndY !== _currYear) return;
      if (ndM <= RPC_MES_FECH) return; // próx. contacto já passou ou é este mês → não mover
      stage90Adj[RPC_MES_FECH] -= (o.val || 0); // sai do mês actual
      stage90Adj[ndM]          += (o.val || 0); // entra no mês da next_date
    });

  const previsaoVendas = Array.from({length:12}, (_, i) => {
    if (i < RPC_MES_FECH) {
      // Meses passados: pipelineStock[i] já é "open à data do mês" (corrigido no backend)
      if (racioVPTot == null) return null;
      const pipeBase = stockV[i] || 0;
      return pipeBase > 0 ? pipeBase * racioVPTot / 100 : null;
    }
    if (i === RPC_MES_FECH) {
      const base = stk90.reduce((a,v)=>a+(v||0),0);
      return Math.max(0, base + stage90Adj[i]) || null;
    }
    if (i === RPC_MES_FECH + 1) {
      const v = (estrelasM1 || 0) + (stage90Adj[i] || 0);
      return v || null;
    }
    // M+2 em diante: pipeline OPEN actual × rácio VP YTD + eventuais stage90 redistribuídos
    const pipe = pipelineExclStar != null && racioVPTot != null ? pipelineExclStar * racioVPTot/100 : null;
    const adj  = stage90Adj[i] || 0;
    return pipe != null ? pipe + adj : (adj || null);
  });
  // Coluna YTD: facturado até ao mês anterior + (previsão do mês − facturado no mês) + estrelas + (pipeline aberto − previsão do mês − estrelas) × rácio
  const prevMesAtual = previsaoVendas[RPC_MES_FECH] || 0;
  const fatMesAtual = faturado[RPC_MES_FECH] || 0;
  const estrelasM1Adj = (estrelasM1 || 0) + (stage90Adj[RPC_MES_FECH + 1] || 0);
  const baseM2 = (pipelineExclStar || 0) - prevMesAtual - estrelasM1Adj;
  const previsaoM2 = racioVPTot != null ? baseM2 * racioVPTot / 100 : 0;
  const previsaoTotAll = s(faturado, RPC_MES_FECH) + (prevMesAtual - fatMesAtual) + estrelasM1Adj + previsaoM2;
  const previsaoTot = previsaoTotAll;

  const getObj = (key, def) => objMap?.[key] ?? def;

  const GRP = {
    funil:     'color-mix(in oklch,#3b82f6 11%,transparent)',
    atividade: 'color-mix(in oklch,#f59e0b 10%,transparent)',
    resultado: 'color-mix(in oklch,#22c55e 11%,transparent)',
  };
  const GRP_LABELS = {
    funil:     'Funil & Pipeline',
    atividade: 'Atividade',
    resultado: 'Resultado',
  };

  const objMargemBP = getObj('margem', 35);
  // BP Rating mensal — fórmula por mês: (fat_mes/bp_mes + marg_mes/margBP_mes) / 2
  // margBP_mes = bp_mes × objMargem% (margem esperada em € para o mês)
  // Meses futuros: null (não faz sentido prever margem)
  const bpRatingMensal = Array.from({length:12}, (_,i) => {
    if (i >= mf) return null; // sem dados futuros
    const fatMes  = faturado[i] != null ? faturado[i] : null;
    const bpMes   = bp[i] || 0;
    const margMes = marg[i] != null ? marg[i] : null;
    if (bpMes <= 0 || fatMes == null || margMes == null) return null;
    const vendAch  = fatMes / bpMes * 100;
    const margBPMes = bpMes * objMargemBP / 100;
    const margAch  = margBPMes > 0 ? margMes / margBPMes * 100 : null;
    return margAch != null ? (vendAch + margAch) / 2 : null;
  });
  // BP Rating YTD — fórmula cumulativa: (fatAcum/bpAcum + margAcum/margBPAcum) / 2
  const bpRating = (() => {
    const bpAcum   = s(bp, mf);
    const fatAcum  = s(faturado, mf);
    const margAcum = marg.slice(0,mf).reduce((a,v)=>a+(v||0),0);
    if (bpAcum <= 0 || fatAcum <= 0) return null;
    const vendAch   = fatAcum / bpAcum * 100;
    const margBPAcum = bpAcum * objMargemBP / 100;
    const margAch   = margBPAcum > 0 ? margAcum / margBPAcum * 100 : null;
    return margAch != null ? (vendAch + margAch) / 2 : null;
  })();

  const rows = [
    // ── Funil & Pipeline ──────────────────────────────────────────────────────
    { label:'Leads / OP Mkt',        grp:'funil', vals:optmkt,   tot:s(optmkt,mf),       fmt:rN,    drill:'optmkt',
      tip:'Contagem de oportunidades abertas pelo Marketing (Source: ADS, NEWSLETTER, WEBPAGE) com StartDate neste mes.' },
    { label:'Leads / OP Com',        grp:'funil', vals:optcom,   tot:s(optcom,mf),       fmt:rN,    drill:'optcom',
      tip:'Contagem de oportunidades que não vieram de Marketing (Source_GrupoFilter diferente de MKT), com StartDate neste mês. Inclui comercial, feiras, parceiro e SDR.' },
    { label:'Leads / OP SDR',        grp:'funil', vals:_n12,      tot:null,               fmt:rN,
      tip:'Oportunidades geradas via SDR AI (em implementação). Dados disponíveis brevemente.' },
    { label:'OP Criadas (valor)',     grp:'funil', vals:opmanagerf9, tot:s(opmanagerf9,mf), fmt:rE, drill:'opmanagerf9',
      tip:'Soma do valor (€) de todas as OPs abertas com StartDate neste mes, independente do stage ou source.' },
    { label:'Cobertura de Pipeline', grp:'funil', vals:cobertura, tot:coberturaTot,      fmt:rMult, isMult:true,
      tip:'Pipeline open à data do mês ÷ BP Anual. Responde à pergunta: quantas vezes o meu pipeline cobre o meu objectivo anual? Manter ≥ 4x garante cobertura ao longo de todo o ano.',
      objDef:4, objFmt:rMult,
      resVal:coberturaTot, resFmt:rMult, resOk:(r,o)=>r>=o,
      drill:'cobertura',
      drillExtra: (i) => {
        const isYtd  = i === 'ytd';
        if (isYtd) {
          const pipe      = totalOpen;
          const cumFat    = s(faturado, mf);
          const falta     = bpAnual - cumFat;
          const cob       = pipe > 0 && bpAnual > 0 ? pipe / bpAnual : null;
          const p2026     = stockV[mf-1] || 0;
          const pipeAnt   = Math.max((totalOpen || 0) - p2026, 0);
          return { pipe, bpAnual, cumFat, falta, cob, isYtd: true, pipe2026: p2026, pipeAnterio: pipeAnt };
        }
        // Mês activo e futuros: OPEN real (todos os anos). Passados: stock histórico.
        const cumFat = typeof i === 'number' ? faturado.slice(0, i+1).reduce((a,v)=>a+(v||0),0) : s(faturado, mf);
        const falta  = bpAnual - cumFat;
        if (typeof i === 'number' && i >= RPC_MES_FECH) {
          const pipe = totalOpen || 0;
          const cob  = pipe > 0 && bpAnual > 0 ? pipe / bpAnual : null;
          return { pipe, bpAnual, cumFat, falta, cob, isYtd: false };
        }
        const p2026  = stockV[i] || 0;
        const pipe   = p2026 + pipeAnterioMensal;
        const cob    = pipe > 0 && bpAnual > 0 ? pipe / bpAnual : null;
        return { pipe, pipe2026Mes: p2026, pipeAnterioMensal, bpAnual, cumFat, falta, cob, isYtd: false };
      } },
    { label:'Win Rate',              grp:'funil', vals:winRate, tot:winRateTot, fmt:rPct, isMult:true, drill:'winrate',
      tip:'Win Rate = Valor Won / (Valor Won + Valor Lost). Mede eficácia de fecho pelo valor monetário das oportunidades. Fonte: status das OPs FM.',
      objKey:'winRate', objDef:25, objFmt:v=>v+'%', objEdit:true,
      resVal:winRateTot, resFmt:rPct, resOk:(r,o)=>r>=o },
    { label:'Rácio Vendas/Pipeline', grp:'funil', vals:racioVP, tot:racioVPTot, fmt:rPctK, isMult:true,
      tip:'Em cada mês: vendas faturadas desse mês ÷ pipeline em aberto à data do mês. YTD: média dos rácios mensais de Jan ao mês activo, sem meses sem valor. Obj: 10%.',
      objKey:'racioVP', objDef:10, objFmt:v=>v+'%', objEdit:true,
      resVal:racioVPTot, resFmt:rPctK, resOk:(r,o)=>r>=o,
      drill:'raciovp',
      drillExtra: (i) => {
        const isYtd = i === 'ytd';
        const vendas = isYtd ? s(faturado, mf) : (faturado[i] ?? null);
        const pipe   = isYtd
          ? (backlogEntry?.opmanagerf9 != null ? backlogEntry.opmanagerf9 : (totalOpenPipeline || null))
          : (stockV[i] ?? null);
        const racio  = isYtd ? racioVPTot : racioVP[i];
        return { vendas, pipe, racio, racioYTD: racioVPTot, isYtd, racioVPMeses: isYtd ? racioVP.slice(0, mf) : null };
      } },
    // ── Atividade ─────────────────────────────────────────────────────────────
    { label:'Demos',                 grp:'atividade', vals:_n12, tot:null, fmt:rN,
      blocked:'Campo FM para demo concluida pendente (Armando)',
      tip:'Bloqueado — campo FM para demo concluida com nota a confirmar com Armando',
      objKey:'demos', objDef:10, objFmt:v=>v+'/mes', objEdit:true, resVal:null, resFmt:rN },
    // ── Resultado ─────────────────────────────────────────────────────────────
    { label:'Objectivo BP',          grp:'resultado', vals:bp, tot:s(bp,12), fmt:rE1, isBP:true,
      tip:'Objectivo de vendas (Business Plan) definido no início do ano, por vendedor e mês. Editável. A coluna YTD soma os 12 meses, não só os meses já decorridos.' },
    { label:'BP Desvio',             grp:'resultado', vals:desvio, tot:s(faturado,12)-s(bp,12), fmt:rE, isD:true,
      tip:'Cada mês é o acumulado até esse mês: vendas faturadas acumuladas − BP acumulado. A coluna YTD é o facturado de Janeiro a Dezembro (meses futuros a zero) menos o BP do ano inteiro. Verde = acima; vermelho = abaixo.' },
    { label:'Previsão Vendas',       grp:'resultado', vals:previsaoVendas, tot:previsaoTotAll, fmt:rE,
      starredMes: RPC_MES_FECH + 1,
      drill:'previsao',
      drillExtra: (i) => {
        if (i === 'ytd') {
          return {
            calcOnly: true, formulaAno: true,
            mPassado: s(faturado, RPC_MES_FECH),
            mAtual: prevMesAtual - fatMesAtual,
            mMais1: estrelasM1,
            mMais2plus: previsaoM2,
            pipeline: baseM2,
            winRate: racioVPTot,
            previsaoTotYTD: previsaoTotAll,
          };
        }
        if (i > RPC_MES_FECH + 1) return { totalOpen: pipelineExclStar, winRate: racioVPTot };
        if (typeof i === 'number' && i < RPC_MES_FECH) {
          return { pipeVivoPast: stockV[i] || 0, winRate: racioVPTot };
        }
        return undefined;
      },
      tip:'Coluna YTD: facturado até ao mês anterior + (previsão do mês − já facturado) + estrelas de M+1 + (pipeline aberto − previsão do mês − estrelas) × rácio. Meses fechados: pipeline em aberto × rácio médio. Mês activo: stage 90/92 (excluindo OPs com próx. contacto em mês futuro). M+1: OPs com estrelas + OPs stage 90/92 com próx. contacto em M+1. M+2 em diante: pipeline em aberto × rácio médio.' },
    { label:'Vendas Fact.',          grp:'resultado', vals:faturado, tot:s(faturado,mf), fmt:rE, isV:true, drill:'faturado',
      tip:'Valor faturado ao cliente neste mes (data da fatura Primavera). Tipos Active=1 do layout FMRS_API_ERP_TiposDocumentos.',
      resVal:s(faturado,mf), resFmt:rE },
    { label:'Margem',                grp:'resultado', vals:margemPct, tot:margemPctYTD, fmt:rPct, isMult:true,
      tip:'Margem bruta % = Margem (€) / Vendas Fact. Fonte: campo Margem em W_VendasEntidadeArtigo (Primavera).',
      objKey:'margem', objDef:35, objFmt:v=>v+'%', objEdit:true, drill:'margem',
      resVal:margemPctYTD, resFmt:rPct, resOk:(r,o)=>r>=o },
    { label:'RPC Rating',            grp:'resultado', vals:bpRatingMensal, tot:bpRating, fmt:v=>v==null?'—':v.toFixed(0)+'%', isMult:true,
      tip:'Média do cumprimento de vendas e do cumprimento de margem. Vendas: facturado ÷ BP. Margem: margem em euros ÷ (BP × objectivo de margem). No mês usa esse mês. A coluna YTD acumula de Janeiro até ao mês activo. Objectivo: 100%.',
      resVal:bpRating, resFmt:v=>v==null?'—':v.toFixed(0)+'%', resOk:(r,o)=>r>=o,
      objKey:'bpRating', objDef:100, objFmt:v=>v+'%',
      drill:'bprating',
      drillExtra: (i) => {
        const isYtd = i === 'ytd';
        const fatV  = isYtd ? s(faturado, mf) : (faturado[i] ?? null);
        const bpV   = isYtd ? s(bp, mf)       : (bp[i] || 0);
        const margV = isYtd ? marg.slice(0,mf).reduce((a,v)=>a+(v||0),0) : (marg[i] ?? null);
        const mPct  = (fatV != null && fatV > 0 && margV != null) ? margV/fatV*100 : null;
        const vendAchV = (bpV > 0 && fatV != null && fatV > 0) ? fatV/bpV*100 : null;
        const margBPV  = bpV * objMargemBP / 100;
        const margAchV = (margBPV > 0 && margV != null) ? margV/margBPV*100 : null;
        const rat    = (vendAchV != null && margAchV != null) ? (vendAchV + margAchV)/2 : null;
        return { fatVal:fatV, bpVal:bpV, margVal:margV, margBPVal:margBPV, margPct:mPct, objMargem:objMargemBP, vendAch:vendAchV, margAch:margAchV, bpRat:rat, isYtd };
      } },
  ];

  const C = { base:{padding:'4px 7px',textAlign:'right',fontSize:13,fontFamily:'var(--dgd-font-mono,monospace)',whiteSpace:'nowrap',borderRight:'1px solid var(--dgd-border-1,#e2e8f0)',borderBottom:'1px solid var(--dgd-border-1,#e2e8f0)'} };

  const triggerAI = () => {
    if (aiLoading) return;
    setAiLoading(true);
    setAiData(null);
    const getObj = (key, def) => objMap?.[key] ?? def;
    fetch('/api/rpc/ai-analysis', {
      method: 'POST',
      headers: {'Content-Type':'application/json'},
      body: JSON.stringify({
        nome: com.nome, vendedor: com.vendedor, ano: new Date().getFullYear(),
        mf, mesFechLabel: RPC_MESES[RPC_MES_FECH],
        bpMensal: bp, vendasMensal: faturado,
        bpAnual, ytdVendas: s(faturado,mf), bpAcum: s(bp,mf),
        bpRating,
        cobertura: coberturaTot, winRate: winRateTot, racioVPYTD: racioVPTot,
        racioVP: racioVPTot, margem: margemPctYTD,
        pipelineStock: stockV[mf-1], stage90CurrMes: (stage90||[])[mf],
        optmktYTD: s(optmkt,mf), optcomYTD: s(optcom,mf),
        margemPctMensal: margemPct.slice(0,mf),
        winRateMensal: winRate.slice(0,mf),
        desvioMensal: desvio.slice(0,mf),
        objCobertura: getObj('cobertura', racioVPTot > 0 ? Math.round(100/racioVPTot * 10)/10 : 4), objWinRate: getObj('winRate',25),
        objRacioVP: getObj('racioVP',10), objMargem: getObj('margem',35),
      }),
    })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { setAiData(d); setAiLoading(false); })
      .catch(() => { setAiData({estado:'atencao',resumo:'Erro ao gerar análise.',sugestoes:[]}); setAiLoading(false); });
  };

  const AI_ESTADO = {
    em_risco:   { label:'Em Risco',   bg:'#fef2f2', border:'#fca5a5', color:'#991b1b' },
    atencao:    { label:'Atenção',    bg:'#fffbeb', border:'#fcd34d', color:'#92400e' },
    no_caminho: { label:'No Caminho', bg:'#f0fdf4', border:'#86efac', color:'#166534' },
    destaque:   { label:'Destaque',   bg:'#eff6ff', border:'#93c5fd', color:'#1e40af' },
  };

  return (
    <div style={{marginBottom:20}}>
      <div style={{display:'flex',alignItems:'center',gap:8,padding:'6px 0 4px'}}>
        <div style={{width:26,height:26,borderRadius:7,background:'var(--dgd-bg-surface,#f1f5f9)',border:'1px solid var(--dgd-border-1)',display:'grid',placeItems:'center',fontSize:10,fontWeight:700,color:'var(--dgd-fg-2)',flexShrink:0}}>
          {com.nome.split(' ').map(w=>w[0]).slice(0,2).join('')}
        </div>
        <div style={{fontSize:13,fontWeight:600,color:'var(--dgd-fg-1)'}}>{com.nome}</div>
        {com.vendedor!=='—'&&<div style={{fontSize:10,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)'}}>Primavera: {com.vendedor}</div>}
        {!hideAI && com.vendedor!=='—' && (
          <button onClick={triggerAI} disabled={aiLoading}
            style={{marginLeft:'auto',display:'flex',alignItems:'center',gap:4,padding:'3px 10px',borderRadius:6,border:'1px solid var(--dgd-border-1)',background: aiData ? AI_ESTADO[aiData.estado]?.bg||'white' : 'var(--dgd-bg-surface)',cursor:aiLoading?'wait':'pointer',fontSize:11,fontWeight:600,color: aiData ? AI_ESTADO[aiData.estado]?.color||'var(--dgd-fg-2)' : 'var(--dgd-fg-2)',transition:'all 0.2s'}}>
            <span style={{fontSize:13}}>{aiLoading ? '⏳' : '✶'}</span>
            {aiLoading ? 'A analisar...' : aiData ? AI_ESTADO[aiData.estado]?.label||'Análise IA' : 'Análise IA'}
          </button>
        )}
      </div>
      {aiData && (
        <div style={{margin:'6px 0 8px',padding:'12px 16px',borderRadius:8,border:'1px solid '+(AI_ESTADO[aiData.estado]?.border||'var(--dgd-border-1)'),background:AI_ESTADO[aiData.estado]?.bg||'var(--dgd-bg-surface)',fontSize:12,lineHeight:1.55}}>
          <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:8}}>
            <span style={{fontSize:10,fontWeight:700,letterSpacing:'0.06em',padding:'2px 8px',borderRadius:4,background:AI_ESTADO[aiData.estado]?.border||'#e2e8f0',color:AI_ESTADO[aiData.estado]?.color||'var(--dgd-fg-1)'}}>{AI_ESTADO[aiData.estado]?.label?.toUpperCase()||'IA'}</span>
            <span style={{color:'var(--dgd-fg-2)'}}>{aiData.resumo}</span>
          </div>
          {aiData.sugestoes?.length > 0 && (
            <div style={{display:'flex',flexDirection:'column',gap:5,paddingTop:6,borderTop:'1px solid '+(AI_ESTADO[aiData.estado]?.border||'var(--dgd-border-1)')}}>
              {aiData.sugestoes.map((s,i)=>(
                <div key={i} style={{display:'flex',gap:8,alignItems:'flex-start'}}>
                  <span style={{fontSize:10,fontWeight:700,color:AI_ESTADO[aiData.estado]?.color||'var(--dgd-fg-2)',flexShrink:0,marginTop:1}}>{i+1}.</span>
                  <span><strong style={{color:'var(--dgd-fg-1)'}}>{s.titulo}:</strong> {s.texto}</span>
                </div>
              ))}
            </div>
          )}
        </div>
      )}
      <div style={{overflowX:'auto',borderRadius:8,border:'1px solid var(--dgd-border-1,#e2e8f0)'}}>
        <table style={{borderCollapse:'collapse',width:'100%',minWidth:920}}>
          <thead>
            <tr>
              <th style={{...C.base,textAlign:'left',minWidth:130,position:'sticky',left:0,zIndex:2,background:'var(--dgd-bg-surface,#f8fafc)',fontSize:10,color:'var(--dgd-fg-3)',fontWeight:600,letterSpacing:'0.04em',paddingTop:5,paddingBottom:5}}>—</th>
              <th style={{...C.base,fontSize:10,fontWeight:700,color:'#7c3aed',background:'color-mix(in oklch,#7c3aed 7%,var(--dgd-bg-surface,#f8fafc))',paddingTop:5,paddingBottom:5,minWidth:56}}>Obj.<InfoTip text="Objectivo editavel por comercial para cada linha de KPI. Clica num valor para editar." /></th>
              {RPC_MESES.map((m,i)=>(
                <th key={m} style={{...C.base,fontSize:10,color:i>RPC_MES_FECH?'var(--dgd-fg-3)':'var(--dgd-fg-2)',fontWeight:600,letterSpacing:'0.04em',paddingTop:5,paddingBottom:5,background:i===RPC_MES_FECH?'color-mix(in oklch,#3b82f6 6%,var(--dgd-bg-surface,#f8fafc))':'var(--dgd-bg-surface,#f8fafc)'}}>
                  {m}
                </th>
              ))}
              <th style={{...C.base,fontSize:10,fontWeight:700,color:'var(--dgd-fg-1)',background:'var(--dgd-bg-sunken,#f1f5f9)',paddingTop:5,paddingBottom:5}}>YTD<InfoTip text="A conta depende da linha. Vendas, leads e OPs: soma de Janeiro até ao mês activo, inclusive. Rácio VP: média dos rácios mensais de Jan ao mês activo, sem meses sem valor. Objectivo BP e BP Desvio: o ano inteiro. Cobertura, margem, win rate e rating: acumulado até ao mês activo. Previsão: a conta própria dessa coluna." /></th>
              <th style={{...C.base,fontSize:10,fontWeight:700,color:'#0891b2',background:'color-mix(in oklch,#0891b2 7%,var(--dgd-bg-sunken,#f1f5f9))',paddingTop:5,paddingBottom:5,minWidth:62}}>Result.<InfoTip text="Diferença entre o valor YTD e o objectivo (YTD − Obj.). Verde = acima do objectivo; vermelho = abaixo. O valor YTD aparece por baixo a cinzento para referência." /></th>
              <th style={{...C.base,fontSize:10,fontWeight:700,color:'#7c3aed',background:'color-mix(in oklch,#7c3aed 8%,var(--dgd-bg-sunken,#f1f5f9))',borderRight:'none',paddingTop:5,paddingBottom:5}}>Total<InfoTip text="YTD (meses fechados) + Pipeline OPEN actual. Clica para ver o detalhe completo de OPs em carteira." /></th>
            </tr>
          </thead>
          <tbody>
            {rows.map((row,ri)=>{
              const showGrpHeader = row.grp !== (rows[ri-1]?.grp);
              const bgRow = row.grp==='resultado'?(GRP.resultado):row.isD?'color-mix(in oklch,#1e293b 7%,transparent)':row.isBP?'color-mix(in oklch,#3b82f6 9%,transparent)':row.isV?'color-mix(in oklch,#22c55e 7%,transparent)':row.blocked?'color-mix(in oklch,#f59e0b 8%,transparent)':(GRP[row.grp]||'transparent');
              const bgRowSolid = bgRow.replace('transparent', 'white');
              const labelColor = row.isD?'var(--dgd-fg-1)':row.isBP?'#3b82f6':row.isMult?'#0891b2':row.blocked?'#92400e':'var(--dgd-fg-2)';
              const objVal = row.objKey ? getObj(row.objKey, row.objDef) : row.objDef ?? null;
              const isEditingObj = editObjRow === row.objKey;
              return (
                <React.Fragment key={row.label}>
                {showGrpHeader && (
                  <tr>
                    <td 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,#f8fafc)',borderTop:ri>0?'2px solid var(--dgd-border-1)':undefined,borderBottom:'1px solid var(--dgd-border-1)',position:'sticky',left:0,zIndex:1,whiteSpace:'nowrap'}}>
                      {GRP_LABELS[row.grp]||row.grp}
                    </td>
                    <td colSpan={16} style={{background:'var(--dgd-bg-surface,#f8fafc)',borderTop:ri>0?'2px solid var(--dgd-border-1)':undefined,borderBottom:'1px solid var(--dgd-border-1)'}} />
                  </tr>
                )}
                <tr style={{background:bgRow}}
                  >
                  <td style={{...C.base,textAlign:'left',fontWeight:row.isD||row.isBP||row.isV||row.isMult?700:500,fontSize:10.5,letterSpacing:'0.06em',color:labelColor,background:bgRowSolid,position:'sticky',left:0,zIndex:1,borderRight:'2px solid var(--dgd-border-1)'}}>
                    {row.label}{row.tip&&<InfoTip text={row.tip}/>}{row.blocked&&<span style={{marginLeft:3,fontSize:9,color:'#b45309'}}> ⚠</span>}
                  </td>
                  {(()=>{
                    if (objVal == null) return <td style={{...C.base,color:'var(--dgd-fg-3)',background:'color-mix(in oklch,#7c3aed 3%,transparent)'}}>—</td>;
                    if (!row.objKey && objVal != null) return (
                      <td style={{...C.base,color:'#7c3aed',fontWeight:700,background:'color-mix(in oklch,#7c3aed 5%,transparent)',cursor:row.objInfo?'pointer':'default'}}
                        onClick={row.objInfo?()=>setObjInfoModal(row.objInfo):undefined}
                        title={row.objInfo?'Ver como é calculado o objectivo':''}>
                        {row.objFmt(objVal)}
                      </td>
                    );
                    if (isEditingObj && row.objEdit && canEditBP) return (
                      <td style={{...C.base,background:'color-mix(in oklch,#7c3aed 12%,transparent)',padding:'2px 4px'}}>
                        <input autoFocus defaultValue={objVal}
                          onKeyDown={e=>{if(e.key==='Enter')e.target.blur();if(e.key==='Escape')setEditObjRow(null);}}
                          onBlur={e=>{const v=parseFloat(String(e.target.value).replace(',','.'))||row.objDef;if(onObjEdit)onObjEdit(row.objKey,v);setEditObjRow(null);}}
                          style={{width:'100%',textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontSize:11,border:'none',outline:'none',background:'transparent',color:'#7c3aed',fontWeight:700}}/>
                      </td>
                    );
                    return (
                      <td style={{...C.base,color:'#7c3aed',fontWeight:700,background:'color-mix(in oklch,#7c3aed 5%,transparent)',cursor:(row.objEdit&&canEditBP)||row.objInfo?'pointer':'default'}}
                        onClick={row.objInfo?()=>setObjInfoModal(row.objInfo):row.objEdit&&canEditBP?()=>setEditObjRow(row.objKey):undefined}
                        title={row.objInfo?'Ver como é calculado o objectivo':row.objEdit&&canEditBP?'Clica para editar objectivo':''}>
                        {row.objFmt(objVal)}
                      </td>
                    );
                  })()}
                  {row.vals.map((v,i)=>{
                    const canDrill = !!row.drill && (v!=null || row.drill==='winrate' || row.drill==='opmanagerf9' || row.drill==='raciovp') && (v!==0 || row.drill==='winrate' || row.drill==='opmanagerf9' || row.drill==='raciovp') && onDrilldown && com.vendedor!=='—';
                    const isBPEdit = row.isBP && canEditBP && com.vendedor!=='—';
                    const isEditing = isBPEdit && editCell?.mesIdx===i;
                    if (isEditing) return (
                      <td key={i} style={{...C.base,background:'color-mix(in oklch,#3b82f6 10%,transparent)',padding:'2px 4px'}}>
                        <input autoFocus defaultValue={v||''}
                          onKeyDown={e=>{if(e.key==='Enter')e.target.blur();if(e.key==='Escape')setEditCell(null);}}
                          onBlur={e=>{const raw=e.target.value.replace(/[^0-9.,]/g,'').replace(',','.');const val=parseFloat(raw)||0;if(onBPEdit)onBPEdit(i,val);setEditCell(null);}}
                          style={{width:'100%',textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontSize:11,border:'none',outline:'none',background:'transparent',color:'#1e40af',fontWeight:700}}/>
                      </td>
                    );
                    // M+1 da Previsao Vendas: OPs Opt_Top excluindo as já em stage 90/92
                    if (row.starredMes !== undefined && i === row.starredMes) {
                      const starVal = (backlogEntry?.optTopValor || stage90Adj[RPC_MES_FECH + 1]) ? (estrelasM1Adj || null) : null;
                      return (
                        <td key={i} style={{...C.base, textAlign:'right', background:'transparent', cursor: starVal != null ? 'pointer' : 'default'}}
                          title="Previsao M+1: soma das OPs com estrela vermelha no FileMaker (Opt_Top=1)"
                          onClick={starVal != null && onBacklog ? () => onBacklog('opttop', starVal) : undefined}>
                          {starVal != null
                            ? <span style={{color:'var(--dgd-fg-1)',fontFamily:'var(--dgd-font-mono,monospace)',fontSize:11,textDecoration:'underline dotted'}}>⭐ {rE(starVal)}</span>
                            : <InfoTip text="Previsao M+1: OPs com estrela vermelha no FileMaker. Nenhuma OP marcada ainda." />
                          }
                        </td>
                      );
                    }
                    // Mês activo da Previsão em row de totais (vendedor='—'): abre DrilldownModal com stage90 agregado da equipa
                    if (row.drill === 'previsao' && i === RPC_MES_FECH && com.vendedor === '—' && onDrilldown && comList?.length > 0 && v != null && v !== 0) {
                      return (
                        <td key={i}
                          onClick={() => onDrilldown('previsao', i, { comList })}
                          style={{...C.base, background:'color-mix(in oklch,#3b82f6 4%,transparent)', cursor:'pointer', color:'var(--dgd-fg-1)'}}
                          title="Ver OPs Stage 90/92 da equipa">
                          <span style={{borderBottom:'1px dashed currentColor',paddingBottom:1}}>{row.fmt(v)}</span>
                        </td>
                      );
                    }
                    return (
                      <td key={i}
                        onClick={isBPEdit?()=>setEditCell({mesIdx:i}):canDrill?()=>onDrilldown(row.drill,i,row.drillExtra?row.drillExtra(i):undefined):undefined}
                        style={{...C.base,
                          background:i===RPC_MES_FECH&&!row.isD?'color-mix(in oklch,#3b82f6 4%,transparent)':'transparent',
                          ...(row.blocked?{color:'var(--dgd-fg-3)'}:row.isD?dStyle(v,bp[i]):row.isMult?{color:v==null?'var(--dgd-fg-3)':i>RPC_MES_FECH?'var(--dgd-fg-3)':'#0891b2',fontWeight:v!=null?600:400}:{color:i>RPC_MES_FECH?'var(--dgd-fg-3)':'var(--dgd-fg-1)'}),
                          cursor:isBPEdit?'text':canDrill?'pointer':'default',
                        }}
                        title={row.blocked?row.blocked:isBPEdit?'Clica para editar BP':canDrill?'Ver detalhe':''}>
                        {row.blocked?<span style={{color:'var(--dgd-fg-3)',fontSize:10}}>—</span>
                          :canDrill?<span style={{borderBottom:'1px dashed currentColor',paddingBottom:1}}>{row.fmt(v)}</span>
                          :isBPEdit&&v===0?<span style={{color:'var(--dgd-fg-3)',fontSize:10}}>—</span>
                          :row.fmt(v)}
                      </td>
                    );
                  })}
                  {(()=>{
                    const canDrillYTD = !!row.drill&&(row.tot!=null||row.drill==='opmanagerf9'||row.drill==='raciovp')&&(row.tot!==0||row.drill==='winrate'||row.drill==='opmanagerf9'||row.drill==='raciovp')&&onDrilldown&&com.vendedor!=='—';
                    return (
                      <td onClick={canDrillYTD?()=>onDrilldown(row.drill,'ytd',row.drillExtra?row.drillExtra('ytd'):undefined):undefined}
                        style={{...C.base,fontWeight:700,background:'var(--dgd-bg-sunken,#f1f5f9)',cursor:canDrillYTD?'pointer':'default',...(row.blocked?{color:'var(--dgd-fg-3)'}:row.isD?dStyle(row.tot,s(bp,mf)):row.isMult?{color:row.tot==null?'var(--dgd-fg-3)':'#0891b2'}:{color:'var(--dgd-fg-1)'})}}>
                        {row.blocked?'—':canDrillYTD?<span style={{borderBottom:'1px dashed currentColor',paddingBottom:1}}>{row.fmt(row.tot)}</span>:row.fmt(row.tot)}
                      </td>
                    );
                  })()}
                  {(()=>{
                    const res = row.resVal ?? null;
                    const obj = objVal;
                    const ok  = res!=null&&obj!=null&&row.resOk?row.resOk(res,obj):null;
                    const col = ok===true?'#16a34a':ok===false?'#dc2626':'var(--dgd-fg-3)';
                    const diff = (res!=null&&obj!=null) ? res-obj : null;
                    const diffFmt = diff!=null&&row.resFmt ? (diff>0?'+':'')+row.resFmt(diff) : null;
                    return (
                      <td style={{...C.base,background:'color-mix(in oklch,#0891b2 5%,var(--dgd-bg-sunken,#f1f5f9))'}}>
                        {diffFmt!=null ? (
                          <span style={{fontWeight:700,color:col,fontFamily:'var(--dgd-font-mono,monospace)',fontSize:11}}>{diffFmt}</span>
                        ) : (
                          <span style={{fontWeight:700,color:col}}>{res!=null&&row.resFmt?row.resFmt(res):'—'}</span>
                        )}
                      </td>
                    );
                  })()}
                  {(()=>{
                    const bgCell='color-mix(in oklch,#7c3aed 5%,var(--dgd-bg-sunken,#f1f5f9))';
                    const blankCell=<td style={{...C.base,borderRight:'none',background:bgCell,color:'var(--dgd-fg-3)',fontWeight:400,fontSize:11}}>—</td>;
                    let backlogVal=null, fmt=rE;
                    if(row.drill==='opmanagerf9'){backlogVal=backlogEntry?.opmanagerf9??null;fmt=rE;}
                    else if(row.drill==='optmkt'){backlogVal=backlogEntry?.optmkt??null;fmt=rN;}
                    else if(row.drill==='optcom'){backlogVal=backlogEntry?.optcom??null;fmt=rN;}
                    // Cobertura: mostrar gap vs benchmark 3x em €
                    if(row.drill==='cobertura'){
                      if(pipelineGap4x==null)return blankCell;
                      const canOpen = !!onDrilldown && com.vendedor!=='—';
                      const extra = row.drillExtra ? row.drillExtra('ytd') : undefined;
                      const gapColor = pipelineGap4x >= 0 ? '#166534' : '#991b1b';
                      const gapBg    = pipelineGap4x >= 0 ? 'color-mix(in oklch,#16a34a 8%,var(--dgd-bg-sunken,#f1f5f9))' : 'color-mix(in oklch,#dc2626 8%,var(--dgd-bg-sunken,#f1f5f9))';
                      return(
                        <td onClick={canOpen?()=>onDrilldown('cobertura','ytd',extra):undefined}
                          style={{...C.base,borderRight:'none',fontWeight:700,background:gapBg,cursor:canOpen?'pointer':'default',color:gapColor,fontFamily:'var(--dgd-font-mono,monospace)'}}
                          title={`${pipelineGap4x>=0?'Excesso':'Défice'} face ao benchmark 4x. Clica para detalhe.`}>
                          <span style={{borderBottom:'1px dashed '+gapColor,paddingBottom:1}}>{pipelineGap4x>=0?'+':''}{rE(pipelineGap4x)}</span>
                        </td>
                      );
                    }
                    if(backlogVal===null)return blankCell;
                    const total=backlogVal; // pipeline OPEN actual (sem YTD)
                    const canOpen=!!onBacklog&&com.vendedor!=='—';
                    return(
                      <td onClick={canOpen?()=>onBacklog(row.drill,row.tot||0):undefined}
                        style={{...C.base,borderRight:'none',fontWeight:700,background:'color-mix(in oklch,#7c3aed 8%,var(--dgd-bg-sunken,#f1f5f9))',cursor:canOpen?'pointer':'default',color:'#7c3aed'}}
                        title={canOpen?'Ver OPs OPEN de anos anteriores':''}>
                        {canOpen?<span style={{borderBottom:'1px dashed #7c3aed',paddingBottom:1}}>{fmt(total)}</span>:fmt(total)}
                      </td>
                    );
                  })()}
                </tr>
                </React.Fragment>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
};


// Objectivos editáveis (margem, win rate, rácio, demos). Fonte: comercial_rpc_objetivos.
// localStorage fica como cache e como origem da primeira cópia para o servidor.
const rpcReadLocalObjs = () => {
  try {
    const raw = JSON.parse(localStorage.getItem('rpc_obj_v1') || '{}');
    const cleaned = Object.fromEntries(Object.entries(raw).filter(([k]) => !k.endsWith('__cobertura')));
    if (Object.keys(cleaned).length !== Object.keys(raw).length) localStorage.setItem('rpc_obj_v1', JSON.stringify(cleaned));
    return cleaned;
  } catch { return {}; }
};
const rpcWriteLocalObjs = (next) => {
  try { localStorage.setItem('rpc_obj_v1', JSON.stringify(next)); } catch {}
};
const rpcPostObjetivos = (items) => {
  if (!items.length) return;
  fetch('/api/rpc/objetivos', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ items }),
  }).catch(() => {});
};

const TabMapaRPC = ({ coms, ano, empresaAtiva, canEditBP, excluirNC, setExcluirNC }) => {
  const ytdMap    = React.useContext(YTDCtx);
  const fmMap     = React.useContext(FMCtx);
  const { bpMap, saveBP } = React.useContext(BPCtx);
  const backlogMap = React.useContext(BacklogCtx);
  const [drill, setDrill] = React.useState(null);
  const [backlogDrill, setBacklogDrill] = React.useState(null); // { com, ytdTotal }
  const [objState, setObjState] = React.useState(rpcReadLocalObjs);
  React.useEffect(() => {
    let cancel = false;
    const local = rpcReadLocalObjs();
    fetch('/api/rpc/objetivos')
      .then(r => r.ok ? r.json() : {})
      .then(data => {
        if (cancel) return;
        const serverFlat = {};
        Object.entries(data || {}).forEach(([vend, objs]) => {
          Object.entries(objs || {}).forEach(([chave, valor]) => {
            serverFlat[vend + '__' + chave] = valor;
          });
        });
        const missing = Object.entries(local)
          .filter(([k, v]) => serverFlat[k] == null && v != null)
          .map(([k, valor]) => {
            const i = k.lastIndexOf('__');
            return i > 0 ? { vendedor: k.slice(0, i), chave: k.slice(i + 2), valor } : null;
          })
          .filter(Boolean);
        rpcPostObjetivos(missing);
        setObjState(prev => {
          const next = { ...local, ...serverFlat };
          Object.keys(prev).forEach(k => { if (prev[k] !== local[k]) next[k] = prev[k]; });
          rpcWriteLocalObjs(next);
          return next;
        });
      })
      .catch(() => {});
    return () => { cancel = true; };
  }, []);
  const saveObj = (vendedor, key, val) => {
    const k = vendedor + '__' + key;
    setObjState(prev => {
      const next = { ...prev, [k]: val };
      rpcWriteLocalObjs(next);
      return next;
    });
    rpcPostObjetivos([{ vendedor, chave: key, valor: val }]);
  };
  const getObjMap = (vendedor) => {
    const prefix = vendedor + '__';
    const result = {};
    Object.entries(objState).forEach(([k,v]) => { if (k.startsWith(prefix)) result[k.slice(prefix.length)] = v; });
    return result;
  };
  const margemMap = React.useContext(MargemCtx);
  const mkD = (v, fallback) => {
    // Para vendors com erp_code diferente do código Primavera/FM, usa fallback (gestor_vendedor_id)
    const fv = fmMap[v]    ? v : (fallback && fmMap[fallback]    ? fallback : v);
    const yv = ytdMap[v]   ? v : (fallback && ytdMap[fallback]   ? fallback : v);
    const mv = margemMap[v]? v : (fallback && margemMap[fallback]? fallback : v);
    const fm = fmMap[fv];
    return {
      spin:          _zeros12,                                    // pendente: layout SPINS (Walter)
      optmkt:        fm ? fm.optmkt      : _zeros12,
      optcom:        fm ? fm.optcom      : _zeros12,
      opmanagerf9:   fm ? fm.opmanagerf9 : _null12,
      bp:            bpMap[v] ? bpMap[v] : _zeros12,
      f9:            _null12,                                     // pendente: ForecastDate em FMRS_API_OPORTUNIDADES
      faturado:      ytdMap[yv] || _null12,
      opsWon:        fm ? (fm.wonValor       || _null12) : _null12,
      lostValor:     fm ? (fm.lostValor     || _null12) : _null12,
      pipelineStock: fm ? (fm.pipelineStock || _null12) : _null12,
      totalOpenPipeline: fm ? (fm.totalOpenPipeline || null) : null,
      stage90:       fm ? (fm.stage90       || _null12) : _null12,
      margemMensal:  margemMap[mv] || _null12,
    };
  };
  const mf = RPC_MES_FECH + 1;
  const sortedComs = [...coms].sort((a, b) => {
    const ytdA = (mkD(a.vendedor, a.gestor_vendedor_id).faturado || _null12).slice(0, mf).reduce((s, v) => s + (v || 0), 0);
    const ytdB = (mkD(b.vendedor, b.gestor_vendedor_id).faturado || _null12).slice(0, mf).reduce((s, v) => s + (v || 0), 0);
    return ytdB - ytdA;
  });
  const totaisEquipa = coms.length > 1 ? (() => {
    const tot = { nome:'Totais Equipa', id:'__tot__', vendedor:'—' };
    const agg = k => Array.from({length:12},(_,i)=>coms.every(c=>mkD(c.vendedor, c.gestor_vendedor_id)[k][i]==null)?null:coms.reduce((s,c)=>s+(mkD(c.vendedor, c.gestor_vendedor_id)[k][i]||0),0));
    const totOpen = coms.reduce((s,c) => s+(mkD(c.vendedor, c.gestor_vendedor_id).totalOpenPipeline||0), 0);
    return { com:tot, d:{spin:agg('spin'),optmkt:agg('optmkt'),optcom:agg('optcom'),opmanagerf9:agg('opmanagerf9'),bp:agg('bp'),f9:agg('f9'),faturado:agg('faturado'),opsWon:agg('opsWon'),lostValor:agg('lostValor'),pipelineStock:agg('pipelineStock'),totalOpenPipeline:totOpen,stage90:agg('stage90'),margemMensal:agg('margemMensal')} };
  })() : null;
  // Backlog agregado da equipa: soma optTopValor + todas as ops
  const teamBacklogEntry = React.useMemo(() => {
    if (coms.length <= 1) return null;
    const entries = coms.map(c => backlogMap[c.vendedor]).filter(Boolean);
    if (!entries.length) return null;
    return {
      opmanagerf9: entries.reduce((s, e) => s + (e.opmanagerf9 || 0), 0),
      optmkt:      entries.reduce((s, e) => s + (e.optmkt      || 0), 0),
      optcom:      entries.reduce((s, e) => s + (e.optcom      || 0), 0),
      optTopValor: entries.reduce((s, e) => s + (e.optTopValor || 0), 0),
      ops:         coms.flatMap(c => (backlogMap[c.vendedor]?.ops || []).map(op => ({ ...op, _comercialNome: c.nome }))),
    };
  }, [coms, backlogMap]);

  return (
    <div>
      {/* Filtro NC */}
      <div style={{display:'flex',justifyContent:'flex-end',marginBottom:8}}>
        <button
          onClick={() => setExcluirNC(v => !v)}
          style={{display:'flex',alignItems:'center',gap:6,padding:'4px 10px',borderRadius:6,border:'1px solid var(--dgd-border-1)',background:excluirNC?'color-mix(in oklch,#3b82f6 12%,white)':'var(--dgd-bg-card,white)',cursor:'pointer',fontSize:11,color:excluirNC?'#1d4ed8':'var(--dgd-fg-2)',fontWeight:excluirNC?600:400,transition:'all .15s'}}
          title="Excluir notas de crédito do valor FATURADO"
        >
          <span style={{width:14,height:14,borderRadius:3,border:`1.5px solid ${excluirNC?'#3b82f6':'var(--dgd-border-1)'}`,background:excluirNC?'#3b82f6':'transparent',display:'inline-flex',alignItems:'center',justifyContent:'center',flexShrink:0}}>
            {excluirNC && <span style={{color:'white',fontSize:10,lineHeight:1}}>✓</span>}
          </span>
          Excluir notas de crédito/devoluções
        </button>
      </div>
      {drill && <DrilldownModal drill={drill} onClose={()=>setDrill(null)} excluirNC={excluirNC} />}
      {backlogDrill && <BacklogModal com={backlogDrill.com} backlogEntry={backlogDrill.com.vendedor==='—' ? teamBacklogEntry : backlogMap[backlogDrill.com.vendedor]} tipo={backlogDrill.tipo} ytdTot={backlogDrill.ytdTot} ano={ano} empresa={empresaAtiva} onClose={()=>setBacklogDrill(null)} />}
      {/* Comerciais agrupados por equipa FM — cada equipa com subtotais */}
      {(() => {
        const equipaOf = (c) => {
          const fm = fmMap[c.vendedor] || fmMap[c.gestor_vendedor_id];
          if (fm?.equipa) return fm.equipa;
          if (c.canal_negocio) return c.canal_negocio;
          const empMap = { 'PTDIG': 'MIMAKI PT', 'DECAL_PT': 'DECAL PT', 'DECAL_EX': 'DECAL EX', 'MIMAKI_ES': 'MIMAKI ES', 'NETSCREEN': 'NETSCREEN', 'TAS': 'TAS' };
          return empMap[c.empresa] || '—';
        };
        const equipaOrder = {};
        sortedComs.forEach(c => {
          const eq = equipaOf(c);
          if (!equipaOrder[eq]) equipaOrder[eq] = [];
          equipaOrder[eq].push(c);
        });
        const equipas = Object.entries(equipaOrder);

        // Calcular subtotais por equipa
        const mkSubtotais = (members) => {
          if (members.length <= 1) return null;
          const agg = k => Array.from({length:12},(_,i)=>members.every(c=>mkD(c.vendedor,c.gestor_vendedor_id)[k][i]==null)?null:members.reduce((s,c)=>s+(mkD(c.vendedor,c.gestor_vendedor_id)[k][i]||0),0));
          const totOpen = members.reduce((s,c) => s+(mkD(c.vendedor,c.gestor_vendedor_id).totalOpenPipeline||0), 0);
          return { spin:agg('spin'),optmkt:agg('optmkt'),optcom:agg('optcom'),opmanagerf9:agg('opmanagerf9'),bp:agg('bp'),f9:agg('f9'),faturado:agg('faturado'),opsWon:agg('opsWon'),lostValor:agg('lostValor'),pipelineStock:agg('pipelineStock'),totalOpenPipeline:totOpen,stage90:agg('stage90'),margemMensal:agg('margemMensal') };
        };
        const mkBacklog = (members) => {
          const entries = members.map(c => backlogMap[c.vendedor]).filter(Boolean);
          if (!entries.length) return null;
          return { opmanagerf9: entries.reduce((s,e)=>s+(e.opmanagerf9||0),0), optmkt: entries.reduce((s,e)=>s+(e.optmkt||0),0), optcom: entries.reduce((s,e)=>s+(e.optcom||0),0), optTopValor: entries.reduce((s,e)=>s+(e.optTopValor||0),0), ops: members.flatMap(c => (backlogMap[c.vendedor]?.ops || []).map(op => ({ ...op, _comercialNome: c.nome }))) };
        };

        const renderGroup = (eq, members) => {
          const sub = mkSubtotais(members);
          const subBacklog = mkBacklog(members);
          const subCom = { nome: `Totais ${eq}`, id: `__tot_${eq}__`, vendedor: '—' };
          return (
            <div key={eq}>
              {/* Header de equipa */}
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, margin: '20px 0 10px', padding: '0 4px' }}>
                <div style={{ height: 1, flex: 1, background: 'var(--dgd-border-1)' }} />
                <span style={{ fontFamily: 'var(--dgd-font-mono, monospace)', fontSize: 10, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--dgd-fg-3)', whiteSpace: 'nowrap' }}>{eq} · {members.length} {members.length === 1 ? 'comercial' : 'comerciais'}</span>
                <div style={{ height: 1, flex: 1, background: 'var(--dgd-border-1)' }} />
              </div>
              {/* Subtotais da equipa */}
              {sub && <RPCGrid com={subCom} d={sub} objMap={getObjMap(`__${eq}__`)}
                backlogEntry={subBacklog}
                onBacklog={(tipo, ytdTot) => setBacklogDrill({ com: subCom, tipo, ytdTot })}
                onDrilldown={(tipo, mesIdx, extra) => setDrill({ com: subCom, tipo, mesIdx, ano, empresa: EMPRESA_FM_KEY[empresaAtiva] || empresaAtiva, ...(extra||{}) })}
                comList={members.map(c => ({ vendedor: c.vendedor, nome: c.nome }))}
              />}
              {/* Comerciais individuais */}
              {members.map(c => <RPCGrid key={c.id} com={c} d={mkD(c.vendedor, c.gestor_vendedor_id)}
                onDrilldown={(tipo, mesIdx, extra) => setDrill({ com:c, tipo, mesIdx, ano, empresa: EMPRESA_FM_KEY[empresaAtiva] || empresaAtiva, ...(extra||{}) })}
                canEditBP={canEditBP}
                onBPEdit={(mesIdx, val) => saveBP(c.vendedor, mesIdx, val)}
                backlogEntry={backlogMap[c.vendedor]}
                onBacklog={(tipo, ytdTot) => setBacklogDrill({ com: c, tipo, ytdTot })}
                objMap={getObjMap(c.vendedor)}
                onObjEdit={(key, val) => saveObj(c.vendedor, key, val)}
              />)}
            </div>
          );
        };

        // Se só 1 equipa, mostra totais + individuais sem header separador
        if (equipas.length <= 1) {
          // Usar o totaisEquipa global existente
          return (<>
            {totaisEquipa && <RPCGrid com={totaisEquipa.com} d={totaisEquipa.d} objMap={getObjMap('__equipa')}
              backlogEntry={teamBacklogEntry}
              onBacklog={(tipo, ytdTot) => setBacklogDrill({ com: totaisEquipa.com, tipo, ytdTot })}
              onDrilldown={(tipo, mesIdx, extra) => setDrill({ com: totaisEquipa.com, tipo, mesIdx, ano, empresa: EMPRESA_FM_KEY[empresaAtiva] || empresaAtiva, ...(extra||{}) })}
              comList={sortedComs.map(c => ({ vendedor: c.vendedor, nome: c.nome }))}
            />}
            {sortedComs.map(c => <RPCGrid key={c.id} com={c} d={mkD(c.vendedor, c.gestor_vendedor_id)}
              onDrilldown={(tipo, mesIdx, extra) => setDrill({ com:c, tipo, mesIdx, ano, empresa: EMPRESA_FM_KEY[empresaAtiva] || empresaAtiva, ...(extra||{}) })}
              canEditBP={canEditBP}
              onBPEdit={(mesIdx, val) => saveBP(c.vendedor, mesIdx, val)}
              backlogEntry={backlogMap[c.vendedor]}
              onBacklog={(tipo, ytdTot) => setBacklogDrill({ com: c, tipo, ytdTot })}
              objMap={getObjMap(c.vendedor)}
              onObjEdit={(key, val) => saveObj(c.vendedor, key, val)}
            />)}
          </>);
        }
        return equipas.map(([eq, members]) => renderGroup(eq, members));
      })()}
    </div>
  );
};

// ─── 2. SHOW ME THE MONEY ────────────────────────────────────────────────────
const TabShowMoney = ({ coms }) => {
  const [mesDetalhe, setMesDetalhe] = React.useState(null);
  const [vista, setVista] = React.useState('vendas'); // 'vendas' | 'margem' | 'pct'
  const ytdMap    = React.useContext(YTDCtx);
  const margemMap = React.useContext(MargemCtx);
  const getV = (vendedor) => ytdMap[vendedor]    || _null12;
  const getM = (vendedor) => margemMap[vendedor] || _null12;

  const thS = { padding:'5px 10px', fontSize:10, fontWeight:600, letterSpacing:'0.06em', color:'var(--dgd-fg-3)', fontFamily:'var(--dgd-font-mono,monospace)', borderBottom:'1px solid var(--dgd-border-1)', background:'var(--dgd-bg-surface)', textAlign:'right' };
  const tdS = (fut,cur) => ({ padding:'5px 10px', fontSize:11.5, fontFamily:'var(--dgd-font-mono,monospace)', textAlign:'right', borderBottom:'1px solid var(--dgd-border-1)', color:fut?'var(--dgd-fg-3)':'var(--dgd-fg-1)', background:cur?'color-mix(in oklch,#3b82f6 5%,transparent)':'transparent', cursor:fut?'default':'pointer' });

  const getCellVal = (vendedor, i) => {
    const v = getV(vendedor)[i];
    const m = getM(vendedor)[i];
    if (vista === 'vendas') return rE(v);
    if (vista === 'margem') return rE(m);
    const pct = (v != null && m != null && v > 0) ? m / v * 100 : null;
    return pct != null ? pct.toFixed(1).replace('.',',')+'%' : '—';
  };
  const getYTDVal = (vendedor) => {
    const mf = RPC_MES_FECH + 1;
    const v = getV(vendedor).slice(0, mf).reduce((a,x)=>a+(x||0),0);
    const m = getM(vendedor).slice(0, mf).reduce((a,x)=>a+(x||0),0);
    if (vista === 'vendas') return rE(v);
    if (vista === 'margem') return rE(m);
    return v > 0 ? (m/v*100).toFixed(1).replace('.',',')+'%' : '—';
  };

  const vistaOpts = [
    { id:'vendas', label:'Vendas' },
    { id:'margem', label:'Margem €' },
    { id:'pct',    label:'% Margem' },
  ];

  return (
    <div>
      <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:12,flexWrap:'wrap'}}>
        <div style={{fontSize:12,color:'var(--dgd-fg-2)',flex:1}}>
          Facturação real por comercial · mês a mês. {mesDetalhe!=null&&<button onClick={()=>setMesDetalhe(null)} style={{marginLeft:8,fontSize:11,color:'#3b82f6',background:'none',border:'none',cursor:'pointer'}}>← voltar</button>}
        </div>
        <div style={{display:'flex',gap:4,background:'var(--dgd-bg-surface)',borderRadius:8,padding:3,border:'1px solid var(--dgd-border-1)'}}>
          {vistaOpts.map(opt=>(
            <button key={opt.id} onClick={()=>setVista(opt.id)} style={{padding:'4px 12px',borderRadius:6,border:'none',cursor:'pointer',fontSize:11,fontWeight:600,background:vista===opt.id?'var(--dgd-bg-card,white)':'transparent',color:vista===opt.id?'var(--dgd-fg-1)':'var(--dgd-fg-3)',boxShadow:vista===opt.id?'0 1px 3px rgba(0,0,0,0.1)':'none',transition:'all 0.15s'}}>{opt.label}</button>
          ))}
        </div>
      </div>

      {mesDetalhe==null ? (
        <div style={{overflowX:'auto',borderRadius:8,border:'1px solid var(--dgd-border-1)'}}>
          <table style={{borderCollapse:'collapse',width:'100%',minWidth:760}}>
            <thead>
              <tr>
                <th style={{...thS,textAlign:'left',minWidth:130,position:'sticky',left:0,zIndex:2,background:'var(--dgd-bg-surface)'}}>Comercial</th>
                {RPC_MESES.map((m,i)=><th key={m} style={{...thS,color:i>RPC_MES_FECH?'var(--dgd-fg-3)':'var(--dgd-fg-2)',background:i===RPC_MES_FECH?'color-mix(in oklch,#3b82f6 6%,var(--dgd-bg-surface))':'var(--dgd-bg-surface)'}}>{m}</th>)}
                <th style={{...thS,fontWeight:700,color:'var(--dgd-fg-1)',borderRight:'none'}}>YTD</th>
              </tr>
            </thead>
            <tbody>
              {coms.map((c,ri)=>(
                  <tr key={c.id} style={{background:ri%2===0?'transparent':'color-mix(in oklch,#94a3b8 3%,transparent)'}}>
                    <td style={{...tdS(false,false),textAlign:'left',fontWeight:600,position:'sticky',left:0,background:'var(--dgd-bg-card,white)',borderRight:'2px solid var(--dgd-border-1)'}}>{c.nome}</td>
                    {Array.from({length:12},(_,i)=>(
                      <td key={i} style={tdS(i>RPC_MES_FECH,i===RPC_MES_FECH)} onClick={()=>i<=RPC_MES_FECH&&setMesDetalhe(i)}>{getCellVal(c.vendedor,i)}</td>
                    ))}
                    <td style={{...tdS(false,false),fontWeight:700,background:'var(--dgd-bg-sunken)',borderRight:'none'}}>{getYTDVal(c.vendedor)}</td>
                  </tr>
              ))}
              <tr style={{background:'var(--dgd-bg-sunken)'}}>
                <td style={{...tdS(false,false),textAlign:'left',fontWeight:700,position:'sticky',left:0,background:'var(--dgd-bg-sunken)',borderRight:'2px solid var(--dgd-border-1)',borderTop:'2px solid var(--dgd-border-1)'}}>EQUIPA</td>
                {Array.from({length:12},(_,i)=>{
                  const totV = coms.reduce((s,c)=>s+(getV(c.vendedor)[i]||0),0);
                  const totM = coms.reduce((s,c)=>s+(getM(c.vendedor)[i]||0),0);
                  const cellTot = i>RPC_MES_FECH ? '—'
                    : vista==='vendas' ? rE(totV)
                    : vista==='margem' ? rE(totM)
                    : totV>0 ? (totM/totV*100).toFixed(1).replace('.',',')+'%' : '—';
                  return <td key={i} style={{...tdS(i>RPC_MES_FECH,i===RPC_MES_FECH),fontWeight:700,borderTop:'2px solid var(--dgd-border-1)'}}>{cellTot}</td>;
                })}
                <td style={{...tdS(false,false),fontWeight:700,borderTop:'2px solid var(--dgd-border-1)',borderRight:'none'}}>{(()=>{
                  const mf=RPC_MES_FECH+1;
                  const totV=coms.reduce((s,c)=>s+getV(c.vendedor).slice(0,mf).reduce((a,x)=>a+(x||0),0),0);
                  const totM=coms.reduce((s,c)=>s+getM(c.vendedor).slice(0,mf).reduce((a,x)=>a+(x||0),0),0);
                  return vista==='vendas'?rE(totV):vista==='margem'?rE(totM):totV>0?(totM/totV*100).toFixed(1).replace('.',',')+'%':'—';
                })()}</td>
              </tr>
            </tbody>
          </table>
        </div>
      ) : (
        <div>
          <div style={{fontSize:13,fontWeight:600,marginBottom:10}}>{RPC_MESES[mesDetalhe]} 2026 — Detalhe por comercial</div>
          {coms.map(c=>{
            const val = getV(c.vendedor)[mesDetalhe];
            const bpV = null; // BP pendente: layout OBJECTIVOS (Walter)
            const dev = val!=null&&bpV!=null?val-bpV:null;
            return (
              <div key={c.id} style={{display:'flex',gap:12,alignItems:'center',padding:'10px 14px',borderRadius:8,marginBottom:6,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)'}}>
                <div style={{width:28,height:28,borderRadius:7,background:'var(--dgd-bg-surface)',border:'1px solid var(--dgd-border-1)',display:'grid',placeItems:'center',fontSize:10,fontWeight:700,color:'var(--dgd-fg-2)'}}>{c.nome.split(' ').map(w=>w[0]).slice(0,2).join('')}</div>
                <div style={{flex:1,fontSize:13,fontWeight:500}}>{c.nome}</div>
                <div style={{textAlign:'right'}}>
                  <div style={{fontSize:16,fontWeight:700,fontFamily:'var(--dgd-font-mono,monospace)'}}>{rE(val)}</div>
                  <div style={{fontSize:10,color:'var(--dgd-fg-3)'}}>BP {rE(bpV)}</div>
                </div>
                <div style={{width:64,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontSize:12,...dStyle(dev,bpV)}}>{dev!=null?(dev>=0?'+':'')+rE(dev):'—'}</div>
                <div style={{width:52,textAlign:'right',fontFamily:'var(--dgd-font-mono,monospace)',fontSize:11,color:'var(--dgd-fg-2)'}}>{bpV?rPct(val||0,bpV):''}</div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
};

// ─── 3. FORECAST ─────────────────────────────────────────────────────────────
const FORECAST_OPS = [
  { id:'OP-2026-0441', comercial:'Daniel Silva', cliente:'Gráfica Lusitana',    equip:'Mimaki UCJV300-160',   val:48500,  prob:80, closeEst:'Jul 2026', stage:80 },
  { id:'OP-2026-0389', comercial:'Daniel Silva', cliente:'Print & Go Lda',       equip:'Mimaki JFX200-2513',   val:72000,  prob:65, closeEst:'Jul 2026', stage:65 },
  { id:'OP-2026-0412', comercial:'Carla Ferreira',cliente:'Decor Viseu',         equip:'Mimaki CJV330-160',    val:34800,  prob:90, closeEst:'Jul 2026', stage:90 },
  { id:'OP-2026-0398', comercial:'Carla Ferreira',cliente:'Sintra Digital',      equip:'Mimaki UJV100-160',    val:28400,  prob:50, closeEst:'Ago 2026', stage:65 },
  { id:'OP-2026-0455', comercial:'Javier García', cliente:'Imprenta Málaga S.L.',equip:'Mimaki TS100-1600',    val:41200,  prob:80, closeEst:'Jul 2026', stage:80 },
  { id:'OP-2026-0421', comercial:'Andreia Costa', cliente:'Decal Almada',        equip:'Pack Decal Start',     val:18600,  prob:90, closeEst:'Jul 2026', stage:92 },
  { id:'OP-2026-0438', comercial:'Cláudia Moreira',cliente:'Decorama Lda',       equip:'Pack Decal Pro',       val:24800,  prob:65, closeEst:'Jul 2026', stage:65 },
  { id:'OP-2026-0467', comercial:'Paulo Neves',   cliente:'ClinicaEstetica BeautyX',equip:'Biond Pro 6000',   val:12400,  prob:80, closeEst:'Jul 2026', stage:80 },
  { id:'OP-2026-0444', comercial:'Miguel Teixeira',cliente:'ShoppingParque',     equip:'NetScreen 65" Kit',    val:9800,   prob:65, closeEst:'Ago 2026', stage:65 },
];

const STAGE_LABELS = { 10:'Lead',20:'Demonst. Int.',50:'Evento',65:'Demo',80:'AG Decisão',90:'AG Aprovado',92:'Fecho' };
const stageBadge = (s) => {
  const c = s>=90?'#16a34a':s>=80?'#2563eb':s>=65?'#7c3aed':'#d97706';
  return { display:'inline-block', padding:'2px 7px', borderRadius:4, fontSize:10, fontWeight:600, background:`color-mix(in oklch,${c} 12%,transparent)`, color:c };
};

const STAGE_PROB = { 92: 95, 90: 85, 80: 60 };

const TabForecast = ({ coms: comsArr }) => {
  const [ops, setOps]       = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [sortCol, setSortCol] = React.useState('stage'); // 'stage'|'valor'|'comercial'|'entidade'
  const [sortDir, setSortDir] = React.useState(-1); // -1=desc 1=asc
  const [stageFilter, setStageFilter] = React.useState('9092'); // '9092'|'80'|'all'

  const ano = new Date().getFullYear();

  React.useEffect(() => {
    if (!comsArr.length) { setOps([]); setLoading(false); return; }
    setLoading(true);
    const key = comsArr.map(c=>c.id).join(',');
    Promise.all(
      comsArr.map(c => {
        const dbEmp = (c.empresa === 'MIMAKI_ES' || c.empresa === 'DIGES') ? 'DIGES' : 'PTDIG';
        const tipo  = stageFilter === '80' ? 'pipeline_open' : 'stage90';
        return fetch(`/api/rpc/ops-detail?vendedor=${c.vendedor}&ano=${ano}&tipo=${tipo}&empresa=${dbEmp}`)
          .then(r => r.json())
          .then(d => (d.ops || [])
            .filter(o => {
              const sn = parseInt(o.stage_num) || 0;
              if (stageFilter === '9092') return sn === 90 || sn === 92;
              if (stageFilter === '80')   return sn === 80;
              return sn >= 80;
            })
            .map(o => ({ ...o, comercialNome: c.nome, vendedor: c.vendedor }))
          )
          .catch(() => []);
      })
    ).then(results => {
      setOps(results.flat());
      setLoading(false);
    });
  }, [comsArr.map(c=>c.id).join(','), stageFilter]);

  const sorted = React.useMemo(() => {
    if (!ops) return [];
    return [...ops].sort((a,b) => {
      let av, bv;
      if (sortCol === 'stage')     { av = parseInt(a.stage_num)||0; bv = parseInt(b.stage_num)||0; }
      else if (sortCol === 'valor') { av = a.valor; bv = b.valor; }
      else if (sortCol === 'comercial') { av = a.comercialNome; bv = b.comercialNome; }
      else { av = a.entidade; bv = b.entidade; }
      if (av < bv) return sortDir;
      if (av > bv) return -sortDir;
      return 0;
    });
  }, [ops, sortCol, sortDir]);

  const totalFull = ops ? ops.reduce((s,o) => s + o.valor, 0) : 0;
  const totalWgtd = ops ? ops.reduce((s,o) => s + o.valor * (STAGE_PROB[parseInt(o.stage_num)] || 80) / 100, 0) : 0;

  const thSort = (col, label, align='left') => {
    const active = sortCol === col;
    const arrow = active ? (sortDir === -1 ? ' ↓' : ' ↑') : '';
    return (
      <th onClick={()=>{ if(active){setSortDir(d=>-d);}else{setSortCol(col);setSortDir(-1);} }}
        style={{padding:'6px 10px',fontSize:10,fontWeight:600,color:active?'var(--dgd-fg-1)':'var(--dgd-fg-3)',letterSpacing:'0.06em',fontFamily:'var(--dgd-font-mono,monospace)',borderBottom:'1px solid var(--dgd-border-1)',textAlign:align,cursor:'pointer',userSelect:'none',background:'var(--dgd-bg-surface)',whiteSpace:'nowrap'}}>
        {label}{arrow}
      </th>
    );
  };

  const stageOpts = [
    { id:'9092', label:'Stage 90/92' },
    { id:'80',   label:'Stage 80' },
    { id:'all',  label:'Todos (≥80)' },
  ];

  return (
    <div>
      {/* KPIs + filtro */}
      <div style={{display:'flex',gap:10,marginBottom:14,flexWrap:'wrap',alignItems:'flex-start'}}>
        <div style={{padding:'10px 16px',borderRadius:8,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)',minWidth:90}}>
          <div style={{fontSize:9.5,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.08em',marginBottom:2}}>OPs em curso</div>
          <div className="font-display" style={{fontSize:20,fontWeight:700,color:'var(--dgd-fg-1)',letterSpacing:'-0.02em'}}>{loading?'…':(ops?.length||0)+' ops'}</div>
        </div>
        <div style={{padding:'10px 16px',borderRadius:8,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)',minWidth:110}}>
          <div style={{fontSize:9.5,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.08em',marginBottom:2}}>Valor total</div>
          <div className="font-display" style={{fontSize:20,fontWeight:700,color:'var(--dgd-fg-1)',letterSpacing:'-0.02em'}}>{loading?'…':rE(totalFull)}</div>
        </div>
        <div style={{padding:'10px 16px',borderRadius:8,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)',minWidth:110}}>
          <div style={{fontSize:9.5,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.08em',marginBottom:2}}>Valor ponderado</div>
          <div className="font-display" style={{fontSize:20,fontWeight:700,color:'#2563eb',letterSpacing:'-0.02em'}}>{loading?'…':rE(totalWgtd)}</div>
        </div>
        <div style={{marginLeft:'auto',display:'flex',gap:4,background:'var(--dgd-bg-surface)',borderRadius:8,padding:3,border:'1px solid var(--dgd-border-1)',alignSelf:'center'}}>
          {stageOpts.map(opt=>(
            <button key={opt.id} onClick={()=>setStageFilter(opt.id)} style={{padding:'4px 10px',borderRadius:6,border:'none',cursor:'pointer',fontSize:11,fontWeight:600,background:stageFilter===opt.id?'var(--dgd-bg-card,white)':'transparent',color:stageFilter===opt.id?'var(--dgd-fg-1)':'var(--dgd-fg-3)',boxShadow:stageFilter===opt.id?'0 1px 3px rgba(0,0,0,0.1)':'none',transition:'all 0.15s'}}>{opt.label}</button>
          ))}
        </div>
      </div>

      {loading && <div style={{color:'var(--dgd-fg-3)',fontSize:13,padding:'20px 0'}}>A carregar OPs do pipeline…</div>}

      {!loading && sorted.length === 0 && (
        <div style={{color:'var(--dgd-fg-3)',fontSize:13}}>Sem OPs em pipeline para este filtro.</div>
      )}

      {!loading && sorted.length > 0 && (
        <div style={{overflowX:'auto',borderRadius:8,border:'1px solid var(--dgd-border-1)'}}>
          <table style={{borderCollapse:'collapse',width:'100%',minWidth:700}}>
            <thead>
              <tr>
                <th style={{padding:'6px 10px',fontSize:10,fontWeight:600,color:'var(--dgd-fg-3)',letterSpacing:'0.06em',fontFamily:'var(--dgd-font-mono,monospace)',borderBottom:'1px solid var(--dgd-border-1)',background:'var(--dgd-bg-surface)'}}>OP</th>
                {thSort('comercial','Comercial')}
                {thSort('entidade','Cliente')}
                <th style={{padding:'6px 10px',fontSize:10,fontWeight:600,color:'var(--dgd-fg-3)',letterSpacing:'0.06em',fontFamily:'var(--dgd-font-mono,monospace)',borderBottom:'1px solid var(--dgd-border-1)',textAlign:'left',background:'var(--dgd-bg-surface)'}}>Produto</th>
                {thSort('valor','Valor','right')}
                <th style={{padding:'6px 10px',fontSize:10,fontWeight:600,color:'var(--dgd-fg-3)',letterSpacing:'0.06em',fontFamily:'var(--dgd-font-mono,monospace)',borderBottom:'1px solid var(--dgd-border-1)',textAlign:'right',background:'var(--dgd-bg-surface)'}}>Pond.</th>
                {thSort('stage','Stage','left')}
                <th style={{padding:'6px 10px',fontSize:10,fontWeight:600,color:'var(--dgd-fg-3)',letterSpacing:'0.06em',fontFamily:'var(--dgd-font-mono,monospace)',borderBottom:'1px solid var(--dgd-border-1)',background:'var(--dgd-bg-surface)'}}>Idade</th>
              </tr>
            </thead>
            <tbody>
              {sorted.map((op,i)=>{
                const sn   = parseInt(op.stage_num)||0;
                const prob = STAGE_PROB[sn] || 80;
                const pond = op.valor * prob / 100;
                return (
                  <tr key={op.op_id+i} style={{background:i%2===0?'transparent':'color-mix(in oklch,#94a3b8 3%,transparent)'}}>
                    <td style={{padding:'7px 10px',fontSize:11,fontFamily:'var(--dgd-font-mono,monospace)',color:'#3b82f6',borderBottom:'1px solid var(--dgd-border-1)',whiteSpace:'nowrap'}}>{op.op_id||'—'}</td>
                    <td style={{padding:'7px 10px',fontSize:12,borderBottom:'1px solid var(--dgd-border-1)',whiteSpace:'nowrap'}}>{op.comercialNome}</td>
                    <td style={{padding:'7px 10px',fontSize:12,borderBottom:'1px solid var(--dgd-border-1)',color:'var(--dgd-fg-1)',maxWidth:160,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{op.entidade||'—'}</td>
                    <td style={{padding:'7px 10px',fontSize:11,borderBottom:'1px solid var(--dgd-border-1)',color:'var(--dgd-fg-2)',maxWidth:150,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{op.produto||'—'}</td>
                    <td style={{padding:'7px 10px',fontSize:12,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',fontWeight:600,borderBottom:'1px solid var(--dgd-border-1)'}}>{rE(op.valor)}</td>
                    <td style={{padding:'7px 10px',fontSize:11,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',color:'#2563eb',borderBottom:'1px solid var(--dgd-border-1)'}}>{rE(pond)}</td>
                    <td style={{padding:'7px 10px',borderBottom:'1px solid var(--dgd-border-1)'}}><span style={stageBadge(sn)}>{op.stage||sn}</span></td>
                    <td style={{padding:'7px 10px',fontSize:11,color:'var(--dgd-fg-2)',fontFamily:'var(--dgd-font-mono,monospace)',borderBottom:'1px solid var(--dgd-border-1)'}}>{op.age!=null?op.age+'d':'—'}</td>
                  </tr>
                );
              })}
              <tr style={{background:'var(--dgd-bg-sunken)'}}>
                <td colSpan={4} style={{padding:'7px 10px',fontSize:11,fontWeight:700,borderTop:'2px solid var(--dgd-border-1)'}}>TOTAL</td>
                <td style={{padding:'7px 10px',fontSize:12,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',fontWeight:700,borderTop:'2px solid var(--dgd-border-1)'}}>{rE(totalFull)}</td>
                <td style={{padding:'7px 10px',fontSize:11,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',fontWeight:700,color:'#2563eb',borderTop:'2px solid var(--dgd-border-1)'}}>{rE(totalWgtd)}</td>
                <td colSpan={2} style={{borderTop:'2px solid var(--dgd-border-1)'}}/>
              </tr>
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
};

// ─── 4. PREDITIVIDADE ─────────────────────────────────────────────────────────
const TabPreditividade = ({ coms: comsArr }) => {
  const { bpMap }  = React.useContext(BPCtx);
  const ytdMap     = React.useContext(YTDCtx);
  const fmMap      = React.useContext(FMCtx);
  const margemMap  = React.useContext(MargemCtx);

  const mf   = RPC_MES_FECH + 1;  // meses fechados
  const mes  = RPC_MES_FECH + 1;  // mês activo (Jul = índice 6)
  const ano  = new Date().getFullYear();

  // fallback helper (igual ao mkD mas só para os campos que precisamos)
  const fmV  = v => fmMap[v]    || {};
  const ytdV = (v, fb) => ytdMap[v] || ytdMap[fb] || _null12;
  const bpV  = v => bpMap[v]    || _zeros12;

  const comData = comsArr.map(c => {
    const fb   = c.gestor_vendedor_id;
    const fm   = fmV(c.vendedor);
    const ytd  = ytdV(c.vendedor, fb);
    const bp   = bpV(c.vendedor);

    const ytdTotal   = ytd.slice(0, mf).reduce((a,x)=>a+(x||0), 0);
    const bpAnual    = bp.reduce((a,x)=>a+(x||0), 0);
    const bpAcum     = bp.slice(0, mf).reduce((a,x)=>a+(x||0), 0);
    const falta      = Math.max(bpAnual - ytdTotal, 0);

    // Stage 90/92 do mês activo (soma acumulada criadas no mês activo)
    const s90val     = (fm.stage90 || _zeros12)[mes] || 0;
    const s90pond    = s90val * 0.9;  // média ponderada 85%+95%

    // Pipeline total OPEN (cobertura de pipeline)
    const pipeline   = (fm.pipelineStock || _zeros12)[mes] || 0;

    // Win rate (por valor €)
    const wonT  = (fm.wonValor  || _zeros12).slice(0, mf).reduce((a,x)=>a+(x||0),0);
    const lostT = (fm.lostValor || _zeros12).slice(0, mf).reduce((a,x)=>a+(x||0),0);
    const wr    = (wonT+lostT) > 0 ? wonT/(wonT+lostT)*100 : null;

    // Cobertura do gap: stage90 ponderado / falta
    const cobGap = falta > 0 ? s90pond / falta * 100 : (s90pond > 0 ? 100 : null);

    // Desvio acumulado vs BP acumulado
    const desvio = ytdTotal - bpAcum;

    // Status semáforo
    let status = 'sem_dados';
    if (bpAnual > 0) {
      const ach = ytdTotal / bpAnual * 100;
      if      (ach >= 90)  status = 'destaque';
      else if (ach >= 70)  status = 'no_caminho';
      else if (ach >= 50)  status = 'atencao';
      else                 status = 'em_risco';
    } else if (ytdTotal > 0) {
      status = 'sem_bp';
    }

    return { ...c, ytdTotal, bpAnual, bpAcum, falta, s90val, s90pond, pipeline, wr, cobGap, desvio, status };
  });

  // Totais equipa
  const teamYTD     = comData.reduce((s,c)=>s+c.ytdTotal, 0);
  const teamBP      = comData.reduce((s,c)=>s+c.bpAnual, 0);
  const teamBPAcum  = comData.reduce((s,c)=>s+c.bpAcum, 0);
  const teamS90     = comData.reduce((s,c)=>s+c.s90val, 0);
  const teamS90P    = comData.reduce((s,c)=>s+c.s90pond, 0);
  const teamFalta   = Math.max(teamBP - teamYTD, 0);
  const teamAch     = teamBP > 0 ? teamYTD / teamBP * 100 : null;
  const teamCobGap  = teamFalta > 0 ? teamS90P / teamFalta * 100 : null;

  const STATUS_CFG = {
    destaque:  { label:'Destaque',   bg:'#eff6ff', border:'#93c5fd', dot:'#2563eb' },
    no_caminho:{ label:'No Caminho', bg:'#f0fdf4', border:'#86efac', dot:'#16a34a' },
    atencao:   { label:'Atenção',    bg:'#fffbeb', border:'#fcd34d', dot:'#d97706' },
    em_risco:  { label:'Em Risco',   bg:'#fef2f2', border:'#fca5a5', dot:'#dc2626' },
    sem_bp:    { label:'Sem BP',     bg:'var(--dgd-bg-surface)', border:'var(--dgd-border-1)', dot:'#94a3b8' },
    sem_dados: { label:'Sem dados',  bg:'var(--dgd-bg-surface)', border:'var(--dgd-border-1)', dot:'#cbd5e1' },
  };

  // Ordena: em_risco → atencao → no_caminho → destaque → sem_bp → sem_dados
  const ORDER = { em_risco:0, atencao:1, no_caminho:2, destaque:3, sem_bp:4, sem_dados:5 };
  const sorted = [...comData].sort((a,b) => (ORDER[a.status]||99) - (ORDER[b.status]||99) || b.ytdTotal - a.ytdTotal);

  const Kpi = ({l,v,c,sub}) => (
    <div style={{padding:'10px 16px',borderRadius:8,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)',minWidth:110}}>
      <div style={{fontSize:9.5,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.08em',marginBottom:2}}>{l}</div>
      <div className="font-display" style={{fontSize:20,fontWeight:700,color:c||'var(--dgd-fg-1)',letterSpacing:'-0.02em'}}>{v}</div>
      {sub&&<div style={{fontSize:10,color:'var(--dgd-fg-3)',marginTop:1}}>{sub}</div>}
    </div>
  );

  return (
    <div>
      {/* Team KPIs */}
      <div style={{display:'flex',gap:10,marginBottom:16,flexWrap:'wrap'}}>
        <Kpi l="YTD Equipa"         v={rE(teamYTD)}  c="var(--dgd-fg-1)" sub={teamBPAcum>0?(teamYTD>=teamBPAcum?'+':'')+rE(teamYTD-teamBPAcum)+' vs BP acum':null} />
        <Kpi l="BP Anual Equipa"    v={teamBP>0?rE(teamBP):'—'} />
        <Kpi l="Achiev. BP Anual"   v={teamAch!=null?teamAch.toFixed(0)+'%':'—'} c={teamAch==null?'var(--dgd-fg-3)':teamAch>=90?'#16a34a':teamAch>=70?'#d97706':'#dc2626'} />
        <Kpi l="Stage 90/92 (pond.)" v={rE(teamS90P)} c="#2563eb" sub={teamCobGap!=null?teamCobGap.toFixed(0)+'% do gap restante':null} />
      </div>

      {/* Team progress bar */}
      {teamBP > 0 && (
        <div style={{marginBottom:16,padding:'12px 16px',borderRadius:8,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)'}}>
          <div style={{display:'flex',justifyContent:'space-between',fontSize:11,color:'var(--dgd-fg-2)',marginBottom:6}}>
            <span style={{fontWeight:600}}>Progresso equipa vs BP anual</span>
            <span>{rE(teamYTD)} / {rE(teamBP)} · {teamAch!=null?teamAch.toFixed(1)+'%':'—'}</span>
          </div>
          <div style={{height:10,borderRadius:5,background:'var(--dgd-bg-sunken)',position:'relative',overflow:'hidden'}}>
            {/* YTD */}
            <div style={{position:'absolute',left:0,top:0,height:'100%',width:Math.min(teamAch||0,100)+'%',background:teamAch>=90?'#16a34a':teamAch>=70?'#2563eb':'#d97706',borderRadius:5,transition:'width 0.4s'}} />
            {/* Stage90 potential */}
            {teamFalta>0&&<div style={{position:'absolute',left:Math.min(teamAch||0,100)+'%',top:0,height:'100%',width:Math.min(teamS90P/teamBP*100,100-Math.min(teamAch||0,100))+'%',background:'color-mix(in oklch,#2563eb 30%,transparent)',borderRadius:'0 5px 5px 0'}} />}
          </div>
          <div style={{display:'flex',gap:12,marginTop:5,fontSize:10,color:'var(--dgd-fg-3)'}}>
            <span style={{display:'flex',alignItems:'center',gap:3}}><span style={{width:8,height:8,borderRadius:2,background:'#2563eb',display:'inline-block'}}/> YTD realizado</span>
            <span style={{display:'flex',alignItems:'center',gap:3}}><span style={{width:8,height:8,borderRadius:2,background:'color-mix(in oklch,#2563eb 30%,transparent)',border:'1px solid #93c5fd',display:'inline-block'}}/> Stage 90/92 potencial</span>
          </div>
        </div>
      )}

      {/* Per-comercial cards */}
      <div style={{fontSize:12,fontWeight:600,color:'var(--dgd-fg-2)',marginBottom:10}}>Por comercial</div>
      <div style={{display:'flex',flexDirection:'column',gap:8}}>
        {sorted.map(c => {
          const cfg = STATUS_CFG[c.status];
          const ach = c.bpAnual > 0 ? c.ytdTotal / c.bpAnual * 100 : null;
          return (
            <div key={c.id} style={{padding:'12px 16px',borderRadius:10,background:cfg.bg,border:'1px solid '+cfg.border}}>
              <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:c.bpAnual>0?8:0}}>
                {/* Avatar */}
                <div style={{width:30,height:30,borderRadius:8,background:'var(--dgd-bg-card,white)',border:'1px solid '+cfg.border,display:'grid',placeItems:'center',fontSize:10,fontWeight:700,color:cfg.dot,flexShrink:0}}>
                  {c.nome.split(' ').map(w=>w[0]).slice(0,2).join('')}
                </div>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{fontSize:13,fontWeight:600,display:'flex',alignItems:'center',gap:6}}>
                    {c.nome}
                    <span style={{fontSize:9,fontWeight:700,padding:'2px 6px',borderRadius:3,background:cfg.dot,color:'white',letterSpacing:'0.04em'}}>{cfg.label}</span>
                  </div>
                  <div style={{fontSize:10,color:'var(--dgd-fg-3)',marginTop:1}}>
                    YTD {rE(c.ytdTotal)}{c.bpAnual>0?' · BP '+rE(c.bpAnual):''}
                    {c.s90val>0?' · S90/92 '+rE(c.s90val)+' (pond. '+rE(c.s90pond)+')':''}
                    {c.wr!=null?' · Rácio VP '+c.wr.toFixed(0)+'%':''}
                  </div>
                </div>
                {/* Desvio */}
                {c.bpAcum>0&&(
                  <div style={{textAlign:'right',flexShrink:0}}>
                    <div style={{fontSize:12,fontWeight:700,fontFamily:'var(--dgd-font-mono,monospace)',color:c.desvio>=0?'#16a34a':'#dc2626'}}>{c.desvio>=0?'+':''}{rE(c.desvio)}</div>
                    <div style={{fontSize:9,color:'var(--dgd-fg-3)'}}>vs BP acum.</div>
                  </div>
                )}
              </div>
              {/* Progress bar */}
              {c.bpAnual > 0 && (
                <div>
                  <div style={{height:6,borderRadius:3,background:'color-mix(in oklch,#94a3b8 20%,transparent)',position:'relative',overflow:'hidden'}}>
                    <div style={{position:'absolute',left:0,top:0,height:'100%',width:Math.min(ach,100)+'%',background:cfg.dot,borderRadius:3,transition:'width 0.4s'}} />
                    {c.falta>0&&<div style={{position:'absolute',left:Math.min(ach,100)+'%',top:0,height:'100%',width:Math.min(c.s90pond/c.bpAnual*100, 100-Math.min(ach,100))+'%',background:'color-mix(in oklch,'+cfg.dot+' 30%,transparent)',borderRadius:'0 3px 3px 0'}} />}
                  </div>
                  <div style={{display:'flex',justifyContent:'space-between',fontSize:9,color:'var(--dgd-fg-3)',marginTop:3}}>
                    <span>{ach!=null?ach.toFixed(1)+'% BP':''}</span>
                    {c.cobGap!=null&&<span>Cobre {c.cobGap.toFixed(0)}% do gap</span>}
                  </div>
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
};

// ─── 5. YTD FAMÍLIAS/ARTIGOS ──────────────────────────────────────────────────
const YTD_FAM = [
  { familia:'Mimaki Impressão', artigos:[
    { nome:'Mimaki UCJV300-160', ytdQty:4, ytdVal:194000, bp:280000 },
    { nome:'Mimaki JFX200-2513', ytdQty:2, ytdVal:144000, bp:200000 },
    { nome:'Mimaki CJV330-160',  ytdQty:6, ytdVal:208800, bp:240000 },
    { nome:'Mimaki UJV100-160',  ytdQty:3, ytdVal:85200,  bp:120000 },
    { nome:'Mimaki TS100-1600',  ytdQty:5, ytdVal:206000, bp:250000 },
  ]},
  { familia:'Mimaki Corte', artigos:[
    { nome:'Mimaki CG-130FX',  ytdQty:8,  ytdVal:56000, bp:72000 },
    { nome:'Mimaki CG-60FX',   ytdQty:12, ytdVal:60000, bp:84000 },
  ]},
  { familia:'Decal Consumíveis', artigos:[
    { nome:'Pack Decal Start',   ytdQty:22, ytdVal:408800, bp:528000 },
    { nome:'Pack Decal Pro',     ytdQty:14, ytdVal:347200, bp:432000 },
    { nome:'Pack Decal Premium', ytdQty:8,  ytdVal:288000, bp:336000 },
  ]},
  { familia:'Biond Soluções', artigos:[
    { nome:'Biond Pro 6000',    ytdQty:6,  ytdVal:74400,  bp:96000 },
    { nome:'Biond Starter Kit', ytdQty:10, ytdVal:69000,  bp:90000 },
  ]},
  { familia:'Sensek', artigos:[
    { nome:'Sensek Starter',  ytdQty:8, ytdVal:63200, bp:88000 },
    { nome:'Sensek Advanced', ytdQty:4, ytdVal:54000, bp:72000 },
  ]},
];

const useYTDData = (ano) => {
  const [state, setState] = React.useState({ data: null, loading: true, error: null, isLive: false });
  React.useEffect(() => {
    fetch(`/api/rpc-es/ytd?ano=${ano}`)
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(json => {
        // Agrupar por família para a view
        const byFam = {};
        json.rows.forEach(r => {
          if (!byFam[r.familia]) byFam[r.familia] = { familia: r.familia, artigos: [] };
          byFam[r.familia].artigos.push({ nome: r.artigo, ytdQty: Number(r.qtd), ytdVal: Number(r.valor), bp: 0 });
        });
        const allFams = Object.values(byFam)
          .sort((a, b) => b.artigos.reduce((s, x) => s + x.ytdVal, 0) - a.artigos.reduce((s, x) => s + x.ytdVal, 0));
        // top 20 positivas + todas as negativas (créditos/devoluções)
        const fams = [
          ...allFams.filter(f => f.artigos.reduce((s,x)=>s+x.ytdVal,0) >= 0).slice(0, 20),
          ...allFams.filter(f => f.artigos.reduce((s,x)=>s+x.ytdVal,0) < 0),
        ];
        setState({ data: fams, mensal: json.mensal, loading: false, error: null, isLive: true });
      })
      .catch(() => setState({ data: null, loading: false, error: null, isLive: false }));
  }, [ano]);
  return state;
};

const TabYTD = ({ ano }) => {
  const live = useYTDData(ano);
  const famData = live.isLive ? live.data : YTD_FAM;
  const [expanded, setExpanded] = React.useState({});
  const [sort, setSort] = React.useState({ col: 'ytdVal', dir: 'desc' });

  const toggleSort = (col) => setSort(s => ({ col, dir: s.col === col && s.dir === 'desc' ? 'asc' : 'desc' }));
  const sortIcon = (col) => sort.col !== col ? ' ↕' : sort.dir === 'desc' ? ' ↓' : ' ↑';

  const sorted = [...famData].sort((a, b) => {
    const fYtdV = f => f.artigos.reduce((s,x)=>s+x.ytdVal,0);
    const fYtdQ = f => f.artigos.reduce((s,x)=>s+x.ytdQty,0);
    const fBp   = f => f.artigos.reduce((s,x)=>s+x.bp,0);
    const fPct  = f => { const bp=fBp(f); return bp>0?fYtdV(f)/bp:0; };
    const fDev  = f => fYtdV(f)-fBp(f);
    const getV  = (f) => ({ ytdVal:fYtdV(f), ytdQty:fYtdQ(f), bp:fBp(f), pct:fPct(f), dev:fDev(f), name:f.familia }[sort.col] ?? fYtdV(f));
    return sort.dir === 'desc' ? getV(b) - getV(a) : getV(a) - getV(b);
  });
  return (
    <div>
      {live.isLive && (
        <div style={{marginBottom:10,padding:'6px 12px',borderRadius:6,background:'color-mix(in oklch,#22c55e 10%,transparent)',border:'1px solid color-mix(in oklch,#22c55e 30%,transparent)',fontSize:11,color:'#166534',display:'inline-flex',alignItems:'center',gap:6}}>
          <span style={{width:6,height:6,borderRadius:'50%',background:'#16a34a',display:'inline-block'}} />
          Dados reais Primavera · tipodoc FA/VD/FR (filtro conservador — afinar com Márcio)
        </div>
      )}
      {live.loading && <div style={{color:'var(--dgd-fg-3)',fontSize:13,padding:'20px 0'}}>A carregar dados Primavera...</div>}
      <div style={{overflowX:'auto',borderRadius:8,border:'1px solid var(--dgd-border-1)'}}>
        <table style={{borderCollapse:'collapse',width:'100%',minWidth:560}}>
          <thead>
            <tr style={{background:'var(--dgd-bg-surface)'}}>
              {[['Família / Artigo','name'],['YTD Qtd','ytdQty'],['YTD Valor','ytdVal'],['BP Anual','bp'],['% BP','pct'],['Desvio','dev']].map(([h,col],i)=>(
                <th key={h} onClick={()=>toggleSort(col)} style={{padding:'6px 10px',fontSize:10,fontWeight:600,color:sort.col===col?'var(--dgd-fg-1)':'var(--dgd-fg-3)',letterSpacing:'0.06em',fontFamily:'var(--dgd-font-mono,monospace)',borderBottom:'1px solid var(--dgd-border-1)',textAlign:i===0?'left':'right',cursor:'pointer',userSelect:'none',whiteSpace:'nowrap'}}>
                  {h}{sortIcon(col)}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {sorted.map(f=>{
              const fYtdV = f.artigos.reduce((s,a)=>s+a.ytdVal,0);
              const fBp   = f.artigos.reduce((s,a)=>s+a.bp,0);
              const fDev  = fYtdV-fBp;
              const isExp = expanded[f.familia];
              return (
                <React.Fragment key={f.familia}>
                  <tr style={{background:'color-mix(in oklch,#3b82f6 5%,transparent)',cursor:'pointer'}} onClick={()=>setExpanded(e=>({...e,[f.familia]:!isExp}))}>
                    <td style={{padding:'8px 10px',fontSize:12,fontWeight:700,color:'var(--dgd-fg-1)',borderBottom:'1px solid var(--dgd-border-1)'}}>
                      <span style={{marginRight:6,fontSize:10}}>{isExp?'▼':'▶'}</span>{f.familia}
                    </td>
                    <td style={{padding:'8px 10px',fontSize:12,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',fontWeight:600,borderBottom:'1px solid var(--dgd-border-1)'}}>{f.artigos.reduce((s,a)=>s+a.ytdQty,0)}</td>
                    <td style={{padding:'8px 10px',fontSize:12,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',fontWeight:600,borderBottom:'1px solid var(--dgd-border-1)'}}>{rE(fYtdV)}</td>
                    <td style={{padding:'8px 10px',fontSize:12,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',color:'var(--dgd-fg-2)',borderBottom:'1px solid var(--dgd-border-1)'}}>{rE(fBp)}</td>
                    <td style={{padding:'8px 10px',fontSize:12,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',borderBottom:'1px solid var(--dgd-border-1)',...dStyle(fYtdV,fBp)}}>{rPct(fYtdV,fBp)}</td>
                    <td style={{padding:'8px 10px',fontSize:12,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',borderBottom:'1px solid var(--dgd-border-1)',...dStyle(fDev,fBp)}}>{(fDev>=0?'+':'')+rE(fDev)}</td>
                  </tr>
                  {isExp&&f.artigos.map((a,ai)=>{
                    const dev = a.ytdVal-a.bp;
                    return (
                      <tr key={a.nome} style={{background:ai%2===0?'transparent':'color-mix(in oklch,#94a3b8 2%,transparent)'}}>
                        <td style={{padding:'6px 10px 6px 26px',fontSize:11.5,color:'var(--dgd-fg-2)',borderBottom:'1px solid var(--dgd-border-1)'}}>{a.nome}</td>
                        <td style={{padding:'6px 10px',fontSize:11.5,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',color:'var(--dgd-fg-1)',borderBottom:'1px solid var(--dgd-border-1)'}}>{a.ytdQty}</td>
                        <td style={{padding:'6px 10px',fontSize:11.5,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',color:'var(--dgd-fg-1)',borderBottom:'1px solid var(--dgd-border-1)'}}>{rE(a.ytdVal)}</td>
                        <td style={{padding:'6px 10px',fontSize:11.5,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',color:'var(--dgd-fg-3)',borderBottom:'1px solid var(--dgd-border-1)'}}>{rE(a.bp)}</td>
                        <td style={{padding:'6px 10px',fontSize:11.5,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',borderBottom:'1px solid var(--dgd-border-1)',...dStyle(a.ytdVal,a.bp)}}>{rPct(a.ytdVal,a.bp)}</td>
                        <td style={{padding:'6px 10px',fontSize:11.5,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',borderBottom:'1px solid var(--dgd-border-1)',...dStyle(dev,a.bp)}}>{(dev>=0?'+':'')+rE(dev)}</td>
                      </tr>
                    );
                  })}
                </React.Fragment>
              );
            })}
          </tbody>
          {(()=>{
            const tQty = sorted.reduce((s,f)=>s+f.artigos.reduce((a,x)=>a+x.ytdQty,0),0);
            const tVal = sorted.reduce((s,f)=>s+f.artigos.reduce((a,x)=>a+x.ytdVal,0),0);
            const tBp  = sorted.reduce((s,f)=>s+f.artigos.reduce((a,x)=>a+x.bp,0),0);
            const tDev = tVal-tBp;
            const tPct = tBp>0?tVal/tBp*100:null;
            const monoS = {padding:'8px 10px',fontSize:12,fontFamily:'var(--dgd-font-mono,monospace)',textAlign:'right',fontWeight:700,borderTop:'2px solid var(--dgd-border-1)',background:'var(--dgd-bg-surface)'};
            return (
              <tfoot>
                <tr>
                  <td style={{...monoS,textAlign:'left',color:'var(--dgd-fg-3)'}}>Total</td>
                  <td style={monoS}>{tQty}</td>
                  <td style={monoS}>{rE(tVal)}</td>
                  <td style={{...monoS,color:'var(--dgd-fg-2)'}}>{rE(tBp)}</td>
                  <td style={{...monoS,...dStyle(tVal,tBp)}}>{tPct!=null?tPct.toFixed(0)+'%':'—'}</td>
                  <td style={{...monoS,...dStyle(tDev,tBp)}}>{(tDev>=0?'+':'')+rE(tDev)}</td>
                </tr>
              </tfoot>
            );
          })()}
        </table>
      </div>
    </div>
  );
};

// ─── 6. AI MEDIA SALES ───────────────────────────────────────────────────────
const TIPO_LABELS = { perda_vendas:'Perdas de Vendas', margem_neg:'Margem Negativa', so_tintas:'Só Consumíveis', printplan_desv:'PrintPlan Desvio', digirent_desv:'Digirent Desvio' };
const TIPO_COLORS = { perda_vendas:'#dc2626', margem_neg:'#dc2626', so_tintas:'#d97706', printplan_desv:'#d97706', digirent_desv:'#dc2626' };

const vendedorNomesRPC = {};  // será preenchido por coms

const TabMediaSales = ({ coms: comsArr }) => {
  const [alerts, setAlerts]     = React.useState(null);
  const [loading, setLoading]   = React.useState(true);
  const [filtroTipo, setFiltroTipo] = React.useState('todos');
  const [filtroVend, setFiltroVend] = React.useState('todos');
  const [acaoIdx, setAcaoIdx]   = React.useState(null);

  const ano = new Date().getFullYear();
  const mf  = RPC_MES_FECH + 1;

  // Mapa vendedor → nome
  const vendNomes = React.useMemo(() => {
    const m = {};
    comsArr.forEach(c => { m[c.vendedor] = c.nome; if (c.gestor_vendedor_id) m[c.gestor_vendedor_id] = c.nome; });
    return m;
  }, [comsArr.map(c=>c.id).join(',')]);

  React.useEffect(() => {
    if (!comsArr.length) { setAlerts([]); setLoading(false); return; }
    setLoading(true);
    // Use gestor_vendedor_id when available (ytd_snap uses these codes)
    const codes = [...new Set(comsArr.flatMap(c => [c.vendedor, c.gestor_vendedor_id].filter(Boolean)))];
    fetch(`/api/rpc/media-alerts?ano=${ano}&mf=${mf}&vendedores=${codes.join(',')}`)
      .then(r => r.json())
      .then(d => { setAlerts(d.alerts || []); setLoading(false); })
      .catch(() => { setAlerts([]); setLoading(false); });
  }, [comsArr.map(c=>c.id).join(',')]);

  const tipos = alerts ? ['todos', ...new Set(alerts.map(a=>a.tipo))] : ['todos'];
  const vendedores = ['todos', ...new Set((alerts||[]).map(a=>a.vendedor))];

  const filtrados = (alerts||[]).filter(a =>
    (filtroTipo === 'todos' || a.tipo === filtroTipo) &&
    (filtroVend === 'todos' || a.vendedor === filtroVend)
  );

  return (
    <div>
      {/* KPIs */}
      <div style={{display:'flex',gap:8,marginBottom:14,flexWrap:'wrap'}}>
        {[
          { l:'Alto risco',       v: loading ? '…' : (alerts||[]).filter(a=>a.sev==='alto').length,  c:'#dc2626' },
          { l:'Médio risco',      v: loading ? '…' : (alerts||[]).filter(a=>a.sev==='medio').length, c:'#d97706' },
          { l:'Acções pendentes', v: loading ? '…' : (alerts||[]).length,                            c:'#3b82f6' },
        ].map(k=>(
          <div key={k.l} style={{padding:'8px 14px',borderRadius:8,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)'}}>
            <div style={{fontSize:9.5,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.08em',marginBottom:2}}>{k.l}</div>
            <div className="font-display" style={{fontSize:20,fontWeight:700,color:k.c,letterSpacing:'-0.02em'}}>{k.v}</div>
          </div>
        ))}
      </div>

      {/* Filtros */}
      <div style={{display:'flex',gap:8,marginBottom:12,flexWrap:'wrap',alignItems:'center'}}>
        <div style={{display:'flex',gap:4,flexWrap:'wrap'}}>
          {tipos.map(t=>(
            <button key={t} onClick={()=>setFiltroTipo(t)} style={{padding:'4px 10px',borderRadius:6,fontSize:11,fontWeight:filtroTipo===t?700:400,background:filtroTipo===t?'var(--dgd-fg-1)':'var(--dgd-bg-card,white)',color:filtroTipo===t?'white':'var(--dgd-fg-2)',border:'1px solid '+(filtroTipo===t?'var(--dgd-fg-1)':'var(--dgd-border-1)'),cursor:'pointer'}}>
              {t==='todos'?'Todos':TIPO_LABELS[t]||t}
            </button>
          ))}
        </div>
        {vendedores.length > 2 && (
          <div style={{display:'flex',gap:4,flexWrap:'wrap',marginLeft:8,paddingLeft:8,borderLeft:'1px solid var(--dgd-border-1)'}}>
            {vendedores.map(v=>(
              <button key={v} onClick={()=>setFiltroVend(v)} style={{padding:'4px 10px',borderRadius:6,fontSize:11,fontWeight:filtroVend===v?700:400,background:filtroVend===v?'#7c3aed':'var(--dgd-bg-card,white)',color:filtroVend===v?'white':'var(--dgd-fg-2)',border:'1px solid '+(filtroVend===v?'#7c3aed':'var(--dgd-border-1)'),cursor:'pointer'}}>
                {v==='todos'?'Todos':(vendNomes[v]||v).split(' ')[0]}
              </button>
            ))}
          </div>
        )}
      </div>

      {loading && <div style={{color:'var(--dgd-fg-3)',fontSize:13,padding:'20px 0'}}>A analisar dados de vendas…</div>}
      {!loading && filtrados.length === 0 && <div style={{color:'var(--dgd-fg-3)',fontSize:13}}>Sem alertas para este filtro.</div>}

      {filtrados.map((a,i)=>(
        <div key={i} style={{padding:'12px 14px',borderRadius:8,marginBottom:8,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)',borderLeft:'3px solid '+(TIPO_COLORS[a.tipo]||'#94a3b8')}}>
          <div style={{display:'flex',alignItems:'flex-start',gap:10,flexWrap:'wrap'}}>
            <div style={{flex:1,minWidth:0}}>
              <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:4,flexWrap:'wrap'}}>
                <span style={badgeStyle(a.sev)}>{a.sev.toUpperCase()}</span>
                <span style={{fontSize:10,padding:'2px 7px',borderRadius:4,background:`color-mix(in oklch,${TIPO_COLORS[a.tipo]||'#94a3b8'} 10%,transparent)`,color:TIPO_COLORS[a.tipo]||'#94a3b8',fontWeight:600,letterSpacing:'0.04em'}}>{TIPO_LABELS[a.tipo]||a.tipo}</span>
                <span style={{fontSize:12,fontWeight:600}}>{a.cliente}</span>
                <span style={{fontSize:11,color:'var(--dgd-fg-3)'}}>· {vendNomes[a.vendedor]||a.vendedor}</span>
              </div>
              <div style={{fontSize:12,color:'var(--dgd-fg-2)',lineHeight:1.5}}>{a.msg}</div>
            </div>
            <button onClick={()=>setAcaoIdx(acaoIdx===i?null:i)} style={{padding:'5px 12px',borderRadius:6,fontSize:11,fontWeight:600,flexShrink:0,background:acaoIdx===i?'var(--dgd-fg-1)':'color-mix(in oklch,#3b82f6 12%,transparent)',color:acaoIdx===i?'white':'#3b82f6',border:'1px solid '+(acaoIdx===i?'var(--dgd-fg-1)':'color-mix(in oklch,#3b82f6 30%,transparent)'),cursor:'pointer'}}>
              {a.action}
            </button>
          </div>
          {acaoIdx===i&&(
            <div style={{marginTop:10,paddingTop:10,borderTop:'1px solid var(--dgd-border-1)',background:'color-mix(in oklch,#3b82f6 4%,transparent)',borderRadius:6,padding:'10px 12px',fontSize:12,color:'var(--dgd-fg-2)',fontStyle:'italic'}}>
              Acção AI — <strong style={{color:'var(--dgd-fg-1)'}}>TODO:</strong> {a.tipo==='perda_vendas'?'Gerar email de follow-up comercial com contexto de queda de vendas e proposta de reunião.':a.tipo==='margem_neg'?'Analisar descontos aplicados e gerar proposta de estratégia de pricing.':'Identificar equipamento compatível com o perfil do cliente e abrir OP com NextStep gerado por AI.'}
            </div>
          )}
        </div>
      ))}
    </div>
  );
};

// ─── 7. SUGESTÕES AI ─────────────────────────────────────────────────────────
const TabSugestoes = ({ coms: comsArr }) => {
  const { bpMap }  = React.useContext(BPCtx);
  const ytdMap     = React.useContext(YTDCtx);
  const fmMap      = React.useContext(FMCtx);
  const margemMap  = React.useContext(MargemCtx);

  const [results, setResults]   = React.useState({});  // vendedor → {estado, resumo, sugestoes, loading}
  const [filtro, setFiltro]     = React.useState('todos');
  const [started, setStarted]   = React.useState(false);

  const mf  = RPC_MES_FECH + 1;
  const ano = new Date().getFullYear();

  const s = (arr, n) => (arr||[]).slice(0,n).reduce((a,x)=>a+(x||0),0);

  const buildPayload = (c) => {
    const fb   = c.gestor_vendedor_id;
    const fm   = fmMap[c.vendedor] || {};
    const ytd  = ytdMap[c.vendedor] || ytdMap[fb] || _null12;
    const marg = margemMap[c.vendedor] || margemMap[fb] || _null12;
    const bp   = bpMap[c.vendedor] || _zeros12;

    const faturado     = ytd;
    const stockV       = fm.pipelineStock || _zeros12;
    const stage90      = fm.stage90       || _zeros12;
    const wonValor     = fm.wonValor      || _zeros12;
    const lostValor    = fm.lostValor     || _zeros12;
    const optmkt       = fm.optmkt        || _zeros12;
    const optcom       = fm.optcom        || _zeros12;

    const bpAnual      = s(bp, 12);
    const ytdVendas    = s(faturado, mf);
    const bpAcum       = s(bp, mf);
    const pipe         = stockV[mf-1] || 0;
    const falta        = Math.max(bpAnual - ytdVendas, 0);
    const cobertura    = falta > 0 ? pipe / falta : (pipe > 0 ? 99 : null);
    const wonT         = s(wonValor, mf);
    const lostT        = s(lostValor, mf);
    const winRate      = (wonT+lostT) > 0 ? wonT/(wonT+lostT)*100 : null;
    const racioVP      = stockV[mf-1] > 0 ? ytdVendas / stockV[mf-1] * 100 : null;
    const margT        = s(marg, mf);
    const margemPct    = ytdVendas > 0 ? margT / ytdVendas * 100 : null;

    return {
      nome: c.nome, vendedor: c.vendedor, ano, mf,
      mesFechLabel: RPC_MESES[RPC_MES_FECH],
      bpMensal: bp, vendasMensal: faturado,
      bpAnual, ytdVendas, bpAcum,
      cobertura, winRate, racioVP, margem: margemPct,
      pipelineStock: pipe, stage90CurrMes: stage90[mf] || 0,
      optmktYTD: s(optmkt, mf), optcomYTD: s(optcom, mf),
      objCobertura: racioVP > 0 ? Math.round(100/racioVP * 10)/10 : 4, objWinRate: 25, objRacioVP: 10, objMargem: 35,
    };
  };

  const runAll = React.useCallback(() => {
    setStarted(true);
    comsArr.forEach(c => {
      setResults(prev => ({ ...prev, [c.vendedor]: { loading: true } }));
      fetch('/api/rpc/ai-analysis', {
        method: 'POST',
        headers: {'Content-Type':'application/json'},
        body: JSON.stringify(buildPayload(c)),
      })
        .then(r => r.ok ? r.json() : Promise.reject())
        .then(d => setResults(prev => ({ ...prev, [c.vendedor]: { ...d, loading: false } })))
        .catch(() => setResults(prev => ({ ...prev, [c.vendedor]: { estado:'atencao', resumo:'Erro ao gerar análise.', sugestoes:[], loading:false } })));
    });
  }, [comsArr.map(c=>c.id).join(',')]);

  const AI_ESTADO = {
    em_risco:   { label:'Em Risco',   bg:'#fef2f2', border:'#fca5a5', color:'#991b1b', dot:'#dc2626' },
    atencao:    { label:'Atenção',    bg:'#fffbeb', border:'#fcd34d', color:'#92400e', dot:'#d97706' },
    no_caminho: { label:'No Caminho', bg:'#f0fdf4', border:'#86efac', color:'#166534', dot:'#16a34a' },
    destaque:   { label:'Destaque',   bg:'#eff6ff', border:'#93c5fd', color:'#1e40af', dot:'#2563eb' },
  };
  const ESTADO_ORDER = { em_risco:0, atencao:1, no_caminho:2, destaque:3 };

  const comsFiltrados = filtro === 'todos' ? comsArr
    : comsArr.filter(c => results[c.vendedor]?.estado === filtro);

  const anyLoading = Object.values(results).some(r => r.loading);
  const done = started && !anyLoading && Object.keys(results).length === comsArr.length;

  // Sort by estado severity when done
  const comsSorted = done
    ? [...comsFiltrados].sort((a,b) => (ESTADO_ORDER[results[a.vendedor]?.estado]||99) - (ESTADO_ORDER[results[b.vendedor]?.estado]||99))
    : comsFiltrados;

  return (
    <div>
      {/* Header */}
      <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:16,flexWrap:'wrap'}}>
        <div style={{flex:1}}>
          <div style={{fontSize:13,fontWeight:600}}>Análise IA por comercial</div>
          <div style={{fontSize:11,color:'var(--dgd-fg-3)',marginTop:2}}>Gerada pelo Claude com base em vendas, pipeline, margem e BP. {done&&<span style={{color:'#16a34a'}}>✓ {Object.keys(results).length} análises concluídas</span>}</div>
        </div>
        {!started && (
          <button onClick={runAll} style={{padding:'8px 18px',borderRadius:8,fontSize:12,fontWeight:700,background:'linear-gradient(135deg,#7c3aed,#3b82f6)',color:'white',border:'none',cursor:'pointer',boxShadow:'0 2px 8px rgba(124,58,237,0.3)'}}>
            ✦ Gerar análises IA
          </button>
        )}
        {started && anyLoading && (
          <div style={{fontSize:12,color:'var(--dgd-fg-3)'}}>A analisar {Object.values(results).filter(r=>r.loading).length} comerciais…</div>
        )}
        {done && (
          <button onClick={()=>{setResults({});setStarted(false);}} style={{padding:'6px 14px',borderRadius:7,fontSize:11,fontWeight:600,background:'transparent',color:'var(--dgd-fg-3)',border:'1px solid var(--dgd-border-1)',cursor:'pointer'}}>
            Regenerar
          </button>
        )}
      </div>

      {/* Filtro estado */}
      {done && (
        <div style={{display:'flex',gap:4,marginBottom:14,flexWrap:'wrap'}}>
          {['todos','em_risco','atencao','no_caminho','destaque'].map(e=>{
            const cfg = AI_ESTADO[e];
            const count = e==='todos' ? comsArr.length : comsArr.filter(c=>results[c.vendedor]?.estado===e).length;
            if (e!=='todos' && count===0) return null;
            return (
              <button key={e} onClick={()=>setFiltro(e)} style={{padding:'4px 12px',borderRadius:6,fontSize:11,fontWeight:filtro===e?700:400,background:filtro===e?(cfg?.dot||'var(--dgd-fg-1)'):'var(--dgd-bg-card,white)',color:filtro===e?'white':(cfg?.dot||'var(--dgd-fg-2)'),border:'1px solid '+(filtro===e?(cfg?.dot||'var(--dgd-fg-1)'):'var(--dgd-border-1)'),cursor:'pointer'}}>
                {e==='todos'?'Todos':cfg?.label} {count > 0 && <span style={{opacity:0.8}}>({count})</span>}
              </button>
            );
          })}
        </div>
      )}

      {/* Cards por comercial */}
      <div style={{display:'flex',flexDirection:'column',gap:10}}>
        {comsSorted.map(c => {
          const r = results[c.vendedor];
          const cfg = r?.estado ? AI_ESTADO[r.estado] : null;
          return (
            <div key={c.id} style={{borderRadius:10,border:'1px solid '+(cfg?.border||'var(--dgd-border-1)'),background:cfg?.bg||'var(--dgd-bg-card,white)',overflow:'hidden'}}>
              {/* Header comercial */}
              <div style={{display:'flex',alignItems:'center',gap:10,padding:'12px 16px',borderBottom:r&&!r.loading?'1px solid '+(cfg?.border||'var(--dgd-border-1)'):'none'}}>
                <div style={{width:32,height:32,borderRadius:8,background:'var(--dgd-bg-surface)',border:'1px solid '+(cfg?.border||'var(--dgd-border-1)'),display:'grid',placeItems:'center',fontSize:11,fontWeight:700,color:cfg?.dot||'var(--dgd-fg-2)',flexShrink:0}}>
                  {c.nome.split(' ').map(w=>w[0]).slice(0,2).join('')}
                </div>
                <div style={{flex:1,fontSize:13,fontWeight:600}}>{c.nome}</div>
                {r?.loading && <div style={{fontSize:11,color:'var(--dgd-fg-3)'}}>A analisar…</div>}
                {r && !r.loading && cfg && (
                  <span style={{fontSize:10,fontWeight:700,padding:'3px 8px',borderRadius:4,background:cfg.dot,color:'white',letterSpacing:'0.04em'}}>{cfg.label}</span>
                )}
                {!r && !started && <div style={{fontSize:11,color:'var(--dgd-fg-3)',opacity:0.5}}>pendente</div>}
              </div>
              {/* Análise */}
              {r && !r.loading && (
                <div style={{padding:'12px 16px'}}>
                  <div style={{fontSize:12.5,color:cfg?.color||'var(--dgd-fg-2)',lineHeight:1.6,marginBottom:r.sugestoes?.length?10:0}}>{r.resumo}</div>
                  {r.sugestoes?.length > 0 && (
                    <div style={{display:'flex',flexDirection:'column',gap:6}}>
                      {r.sugestoes.map((sg,j)=>(
                        <div key={j} style={{display:'flex',gap:8,padding:'8px 10px',borderRadius:7,background:'color-mix(in oklch,white 70%,transparent)',border:'1px solid '+(cfg?.border||'var(--dgd-border-1)')}}>
                          <span style={{fontSize:11,fontWeight:700,color:cfg?.dot||'#3b82f6',flexShrink:0,marginTop:1}}>{j+1}.</span>
                          <div>
                            {sg.titulo && <div style={{fontSize:11,fontWeight:700,color:'var(--dgd-fg-1)',marginBottom:2}}>{sg.titulo}</div>}
                            <div style={{fontSize:12,color:'var(--dgd-fg-2)',lineHeight:1.5}}>{sg.texto}</div>
                          </div>
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
};




// ─── Componente principal ─────────────────────────────────────────────────────

// ─── Tab Previsão AI ──────────────────────────────────────────────────────────
const TabPrevisaoAI = ({ coms, ano }) => {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [modeloSel, setModeloSel] = React.useState(null);
  const [vendedorSel, setVendedorSel] = React.useState('EQUIPA');
  const ytdByV = React.useContext(YTDCtx);
  const meses = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];

  React.useEffect(() => {
    setLoading(true);
    fetch(`/api/rpc/previsao-ai?ano=${ano}`)
      .then(r => r.json())
      .then(d => {
        setData(d);
        // seleccionar primeiro modelo disponível
        const modelos = Object.keys(Object.values(d.byVendedor || {})[0] || {});
        if (modelos.length && !modeloSel) setModeloSel(modelos[0]);
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }, [ano]);

  if (loading) return <div style={{padding:'24px 0',color:'var(--dgd-fg-3)',fontSize:13}}>A carregar previsões AI...</div>;

  const byVendedor = data?.byVendedor || {};
  const todosModelos = [...new Set(Object.values(byVendedor).flatMap(Object.keys))];
  const temDados = Object.keys(byVendedor).length > 0;

  // Dados reais de vendas do vendedor seleccionado
  const getReal = (v) => {
    const ytd = ytdByV[v] || Array(12).fill(null);
    return ytd;
  };

  // Previsão do modelo seleccionado para o vendedor seleccionado
  const getPrevisao = () => {
    if (!modeloSel) return null;
    const v = vendedorSel === 'EQUIPA' ? 'EQUIPA' : vendedorSel;
    return byVendedor[v]?.[modeloSel] || null;
  };

  const prev = getPrevisao();
  const real = getReal(vendedorSel === 'EQUIPA' ? (coms[0]?.vendedor || '') : vendedorSel);

  // Calcular MAPE para meses com dados reais
  const calcMape = (prev, real) => {
    if (!prev) return null;
    const pairs = real.map((r,i) => ({ r, p: prev.previsao[i] })).filter(x => x.r && x.p && x.r > 0);
    if (!pairs.length) return null;
    return pairs.reduce((s,x) => s + Math.abs(x.r - x.p) / x.r * 100, 0) / pairs.length;
  };
  const mape = calcMape(prev, real);

  // Mini chart SVG — previsão vs real
  const ChartPrevReal = ({ prev, real }) => {
    const vals = [...(prev?.previsao || []).filter(Boolean), ...real.filter(Boolean)];
    if (!vals.length) return null;
    const maxV = Math.max(...vals);
    const w = 600, h = 90, pad = 8;
    const xOf = i => pad + i * ((w - pad*2) / 11);
    const yOf = v => h - pad - (v / maxV) * (h - pad*2);
    const prevPts = (prev?.previsao || []).map((v,i) => v ? `${xOf(i)},${yOf(v)}` : null).filter(Boolean).join(' ');
    const realPts = real.map((v,i) => v ? `${xOf(i)},${yOf(v)}` : null).filter(Boolean).join(' ');
    const bandPts = [
      ...(prev?.conf_alta || []).map((v,i) => v ? `${xOf(i)},${yOf(v)}` : null).filter(Boolean),
      ...(prev?.conf_baixa || []).map((v,i) => v ? `${xOf(i)},${yOf(v)}` : null).filter(Boolean).reverse(),
    ].join(' ');
    return (
      <svg width={w} height={h+20} style={{display:'block',maxWidth:'100%'}}>
        {bandPts && <polygon points={bandPts} fill="#3b82f6" opacity={0.08}/>}
        {prevPts && <polyline points={prevPts} fill="none" stroke="#3b82f6" strokeWidth="2" strokeDasharray="5 3"/>}
        {realPts && <polyline points={realPts} fill="none" stroke="#16a34a" strokeWidth="2.5"/>}
        {real.map((v,i) => v ? <circle key={i} cx={xOf(i)} cy={yOf(v)} r="3" fill="#16a34a"/> : null)}
        {(prev?.previsao||[]).map((v,i) => v ? <circle key={i} cx={xOf(i)} cy={yOf(v)} r="2.5" fill="#3b82f6" fillOpacity={0.7}/> : null)}
        {meses.map((m,i) => <text key={i} x={xOf(i)} y={h+14} textAnchor="middle" fontSize={9} fill="var(--dgd-fg-3)">{m}</text>)}
      </svg>
    );
  };

  // Tabela comparativa mensal
  const TabelaComparativa = ({ prev, real }) => {
    const rE = v => v == null ? '—' : v >= 1000000 ? (v/1000000).toFixed(1).replace('.',',')+'M' : v >= 1000 ? Math.round(v/1000)+'k' : Math.round(v).toLocaleString('pt-PT');
    return (
      <table style={{width:'100%',borderCollapse:'collapse',fontSize:11,marginTop:12}}>
        <thead>
          <tr style={{background:'var(--dgd-bg-app)'}}>
            <th style={{padding:'5px 10px',textAlign:'left',color:'var(--dgd-fg-3)',fontWeight:600,fontSize:10}}>MÊS</th>
            {meses.map(m => <th key={m} style={{padding:'5px 6px',textAlign:'center',color:'var(--dgd-fg-3)',fontWeight:600,fontSize:10}}>{m}</th>)}
          </tr>
        </thead>
        <tbody>
          <tr style={{borderTop:'1px solid var(--dgd-border-1)'}}>
            <td style={{padding:'5px 10px',color:'var(--dgd-fg-2)',fontWeight:600,fontSize:10}}>Previsão AI</td>
            {(prev?.previsao||Array(12).fill(null)).map((v,i) => (
              <td key={i} style={{padding:'5px 6px',textAlign:'center',color:'#3b82f6',fontWeight:v?500:400}}>{rE(v)}</td>
            ))}
          </tr>
          <tr style={{borderTop:'1px solid var(--dgd-border-1)'}}>
            <td style={{padding:'5px 10px',color:'var(--dgd-fg-2)',fontWeight:600,fontSize:10}}>Real</td>
            {real.map((v,i) => <td key={i} style={{padding:'5px 6px',textAlign:'center',color:'#16a34a',fontWeight:v?500:400}}>{rE(v)}</td>)}
          </tr>
          <tr style={{borderTop:'1px solid var(--dgd-border-1)',background:'var(--dgd-bg-app)'}}>
            <td style={{padding:'5px 10px',color:'var(--dgd-fg-2)',fontWeight:600,fontSize:10}}>Desvio</td>
            {(prev?.previsao||Array(12).fill(null)).map((v,i) => {
              const r = real[i], desvio = r && v ? r - v : null;
              return <td key={i} style={{padding:'5px 6px',textAlign:'center',color: desvio == null ? 'var(--dgd-fg-3)' : desvio >= 0 ? '#16a34a' : '#dc2626',fontWeight:desvio!=null?500:400}}>{desvio == null ? '—' : (desvio>=0?'+':'')+rE(desvio)}</td>;
            })}
          </tr>
        </tbody>
      </table>
    );
  };

  const algColor = { ARIMA:'#3b82f6', Prophet:'#8b5cf6', LinearRegression:'#10b981' };

  return (
    <div style={{marginTop:12}}>
      {/* Estado — sem dados */}
      {!temDados && (
        <div style={{padding:'32px 24px',borderRadius:10,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)',textAlign:'center'}}>
          <div style={{fontSize:32,marginBottom:12}}>🤖</div>
          <div style={{fontSize:15,fontWeight:600,color:'var(--dgd-fg-1)',marginBottom:8}}>Modelos ainda não treinados</div>
          <div style={{fontSize:13,color:'var(--dgd-fg-3)',lineHeight:1.6,maxWidth:400,margin:'0 auto'}}>
            Os modelos preditivos ainda não correram. Vai a <strong>Sistema → Modelos Preditivos</strong> e clica "▶ Correr" para iniciar o primeiro treino.
            Após o treino, as previsões aparecem aqui.
          </div>
        </div>
      )}

      {temDados && (
        <>
          {/* Controlos */}
          <div style={{display:'flex',gap:8,marginBottom:16,flexWrap:'wrap',alignItems:'center'}}>
            {/* Selector modelo */}
            <div style={{display:'flex',gap:4}}>
              {todosModelos.map(m => {
                const info = Object.values(byVendedor)[0]?.[m];
                return (
                  <button key={m} onClick={() => setModeloSel(m)}
                    style={{padding:'5px 12px',fontSize:11,fontWeight:modeloSel===m?700:400,borderRadius:6,border:'1px solid var(--dgd-border-1)',background:modeloSel===m?(algColor[info?.algoritmo]||'#3b82f6'):'var(--dgd-bg-card)',color:modeloSel===m?'white':'var(--dgd-fg-2)',cursor:'pointer'}}>
                    {info?.modelo_nome || m}
                  </button>
                );
              })}
            </div>
            {/* Selector vendedor */}
            <select value={vendedorSel} onChange={e => setVendedorSel(e.target.value)}
              style={{padding:'5px 10px',fontSize:11,borderRadius:6,border:'1px solid var(--dgd-border-1)',background:'var(--dgd-bg-card)',color:'var(--dgd-fg-1)',cursor:'pointer'}}>
              <option value="EQUIPA">Equipa</option>
              {coms.map(c => <option key={c.vendedor} value={c.vendedor}>{c.nome || c.vendedor}</option>)}
            </select>
            {/* MAPE badge */}
            {mape != null && (
              <div style={{marginLeft:'auto',padding:'5px 12px',borderRadius:6,background: mape<10?'#dcfce7':mape<20?'#fef9c3':'#fee2e2',color:mape<10?'#16a34a':mape<20?'#92400e':'#dc2626',fontSize:11,fontWeight:700}}>
                MAPE {mape.toFixed(1)}% {mape<10?'✅':mape<20?'🟡':'🔴'}
              </div>
            )}
          </div>

          {/* Gráfico */}
          <div style={{background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)',borderRadius:10,padding:'16px 20px',marginBottom:12}}>
            <div style={{display:'flex',gap:16,alignItems:'center',marginBottom:12,fontSize:11,color:'var(--dgd-fg-3)'}}>
              <span><span style={{display:'inline-block',width:20,height:2,background:'#3b82f6',verticalAlign:'middle',marginRight:4,borderTop:'2px dashed #3b82f6'}}></span>Previsão AI</span>
              <span><span style={{display:'inline-block',width:20,height:2,background:'#16a34a',verticalAlign:'middle',marginRight:4}}></span>Real</span>
              <span style={{opacity:0.6}}>Banda azul = intervalo de confiança</span>
            </div>
            <ChartPrevReal prev={prev} real={real} />
          </div>

          {/* Tabela */}
          <div style={{background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)',borderRadius:10,padding:'14px 16px',marginBottom:12,overflowX:'auto'}}>
            <TabelaComparativa prev={prev} real={real} />
          </div>

          {/* Aviso de promoção */}
          {mape != null && mape < 10 && (
            <div style={{padding:'10px 16px',borderRadius:8,background:'#dcfce7',border:'1px solid #86efac',fontSize:12,color:'#166534'}}>
              ✅ <strong>MAPE abaixo de 10%</strong> — este modelo está a funcionar bem. Considera promovê-lo ao tab Forecast principal.
            </div>
          )}
          {mape != null && mape >= 10 && (
            <div style={{padding:'10px 16px',borderRadius:8,background:'#fffbeb',border:'1px solid #fde68a',fontSize:12,color:'#92400e'}}>
              🟡 Modelo em validação — MAPE de {mape.toFixed(1)}%. Continua a monitorizar antes de promover ao Forecast.
            </div>
          )}
        </>
      )}
    </div>
  );
};


const RPC_TABS_ALL   = [
  { 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 RPC_TABS_BOARD = [
  { id:'rpc',        label:'Mapa RPC' },
  { id:'money',      label:'Show me the Money' },
];

// Permissões — resolve com base no userTipo
const rpcPermissions = (userTipo) => {
  const t = (userTipo || '').toLowerCase();
  if (t === 'admin' || t === 'manager')                    return { level: 'manager', tabs: RPC_TABS_ALL };
  if (t === 'c_suite' || t === 'chairman' || t === 'director') return { level: 'board',   tabs: RPC_TABS_BOARD };
  return { level: 'none', tabs: [] };
};

const ScreenComercialRPC = ({ vendedorId, userTipo, userName, user }) => {
  const perm = rpcPermissions(userTipo);
  const userModos = user?.modos || user?.modos_acesso || [];
  const canEditBP = userModos.includes('dev') || userModos.includes('board');

  // Todos os hooks têm de ser chamados incondicionalmente (React rules of hooks)
  const [tab, setTab]   = React.useState(perm.tabs[0]?.id || 'rpc');
  const [ano, setAno]   = React.useState(RPC_ANO_DEF);
  const [excluirNC, setExcluirNC] = React.useState(false);
  const [marca, setMarca] = React.useState('Mimaki PT');
  const isManager  = !!vendedorId;
  const empresaAtiva = MARCA_EMPRESA[marca] || 'MIMAKI_ES';
  const teamState  = useTeam(isManager ? user?.id : null);
  const comerciaisState = useComerciais(!isManager ? empresaAtiva : null);
  const ytdState   = useYTDMensal(ano, excluirNC);
  const margemByV  = ytdState.margemMap || {};
  const { bpMap, saveBP } = useBPTargets(ano);
  // FM pipeline — carrega todos os tenants em paralelo (independente da marca activa)
  // NSC e TAS só carregam quando a marca correspondente está activa (evitar lentidão no arranque)
  const fmKeyAtivo = EMPRESA_FM_KEY[MARCA_EMPRESA[marca]] || 'DIGES';
  const fmPT  = { byVendedor: null, totalOps: 0, loading: false, fromPostgres: false }; // ES: só DIGES
  const fmES  = useFMPipeline('DIGES', ano);
  const fmNSC = useFMPipeline(fmKeyAtivo === 'NSC' ? 'NSC' : null, ano);
  const fmTAS = useFMPipeline(fmKeyAtivo === 'TAS' ? 'TAS' : null, ano);
  // Backlog — só carrega PT e ES sempre; NSC e TAS apenas quando activos
  const backlogPT  = {}; // ES: não usa PTDIG backlog
  const backlogES  = useBacklog('DIGES', ano);
  const backlogNSC = useBacklog(fmKeyAtivo === 'NSC' ? 'NSC' : null, ano);
  const backlogTAS = useBacklog(fmKeyAtivo === 'TAS' ? 'TAS' : null, ano);

  // Sem acesso (após todos os hooks)
  if (perm.level === 'none') {
    return (
      <div style={{height:'100%',display:'flex',alignItems:'center',justifyContent:'center',background:'var(--dgd-bg-app,#f8fafc)'}}>
        <div style={{textAlign:'center',maxWidth:380,padding:'40px 32px',borderRadius:12,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)'}}>
          <div style={{fontSize:32,marginBottom:12}}>🔒</div>
          <div style={{fontSize:16,fontWeight:600,color:'var(--dgd-fg-1)',marginBottom:8}}>Acesso restrito</div>
          <div style={{fontSize:13,color:'var(--dgd-fg-3)',lineHeight:1.6}}>
            O Relatório de Produção Comercial está disponível para gestores e administradores.<br/>
            Contacta o administrador para acesso.
          </div>
        </div>
      </div>
    );
  }

  // activeComs: equipa BD (manager) ou comerciais BD por empresa (admin)
  const marcasVisiveis = isManager ? [] : MARCAS;
  const marcasComTab   = ['rpc','money','forecast','predit','previsao'].includes(tab);

  const activeComs = (isManager && teamState.team)
    ? teamState.team
    : (comerciaisState.comerciais || []);

  // Merge todos os tenants FM num único mapa byVendedor (PT < ES < NSC < TAS)
  const fmByV = { ...(fmPT.byVendedor || {}), ...(fmES.byVendedor || {}), ...(fmNSC.byVendedor || {}), ...(fmTAS.byVendedor || {}) };

  // ytdByV: começa com dados Primavera (PT); usa FM wonValor para tenants sem Primavera (ES/NSC/TAS)
  const ytdByV = { ...(ytdState.byVendedor || {}) };
  [fmES, fmNSC, fmTAS].forEach(fm => {
    Object.entries(fm.byVendedor || {}).forEach(([v, d]) => {
      if (!ytdByV[v]) ytdByV[v] = d.wonValor;
    });
  });

  const getVendas = (vendedor, fallback) => {
    const v = ytdByV[vendedor] ? vendedor : (fallback && ytdByV[fallback] ? fallback : vendedor);
    return ytdByV[v] || (fmByV[v] ? fmByV[v].wonValor : _null12);
  };
  const coms = activeComs;
  const ytdV = coms.reduce((s,c)=>s+getVendas(c.vendedor, c.gestor_vendedor_id).slice(0,RPC_MES_FECH+1).reduce((a,v)=>a+(v||0),0),0);
  const ytdB = coms.reduce((s,c)=>s+(bpMap[c.vendedor]||[]).slice(0,RPC_MES_FECH+1).reduce((a,v)=>a+(v||0),0),0);
  const hasFMData = !!(fmPT.byVendedor || fmES.byVendedor || fmNSC.byVendedor || fmTAS.byVendedor);
  const hasRealData = !!ytdState.byVendedor || hasFMData;

  const backlogByV = { ...backlogPT, ...backlogES, ...backlogNSC, ...backlogTAS };

  return (
    <BacklogCtx.Provider value={backlogByV}>
    <MargemCtx.Provider value={margemByV}>
    <FMCtx.Provider value={fmByV}>
    <YTDCtx.Provider value={ytdByV}>
    <BPCtx.Provider value={{ bpMap, saveBP }}>
    <div className="scrollbar" style={{height:'100%',overflowY:'auto',background:'var(--dgd-bg-app,#f8fafc)'}}>
      <div style={{padding:'18px 24px 80px'}}>

        {/* Banner board */}
        {perm.level === 'board' && (
          <div style={{marginBottom:12,padding:'6px 12px',borderRadius:6,background:'color-mix(in oklch,#3b82f6 8%,transparent)',border:'1px solid color-mix(in oklch,#3b82f6 25%,transparent)',fontSize:11,color:'#1e40af',display:'flex',alignItems:'center',gap:6}}>
            <span style={{fontWeight:700}}>Visão executiva</span> · leitura apenas · Mapa RPC e facturação real
          </div>
        )}

        {/* Banner FM indisponivel — fallback PostgreSQL */}        {(fmPT.fromPostgres || fmES.fromPostgres || fmNSC.fromPostgres || fmTAS.fromPostgres) && (          <div style={{marginBottom:12,padding:"8px 14px",borderRadius:6,background:"color-mix(in oklch,#f59e0b 10%,transparent)",border:"1px solid color-mix(in oklch,#f59e0b 30%,transparent)",fontSize:12,color:"#92400e",display:"flex",alignItems:"center",gap:8}}>            <span style={{fontSize:16}}>⚠</span>            <span><strong>Fonte: PostgreSQL local</strong> — FileMaker indisponivel. Dados de pipeline podem estar desactualizados.</span>          </div>        )}

        {/* Header */}
        <div style={{display:'flex',alignItems:'flex-start',justifyContent:'space-between',flexWrap:'wrap',gap:10,marginBottom:12}}>
          <div>
            <div style={{fontSize:10,color:'var(--dgd-fg-3)',fontFamily:'var(--dgd-font-mono,monospace)',letterSpacing:'0.1em',marginBottom:2}}>COMERCIAL · PRODUÇÃO COMERCIAL · {RPC_ANO_DEF}</div>
            <h2 className="font-display" style={{margin:0,fontSize:21,fontWeight:600,letterSpacing:'-0.01em',color:'var(--dgd-fg-1)'}}>
              {perm.tabs.find(t=>t.id===tab)?.label}
              {marcasComTab&&<span style={{fontSize:13,fontWeight:400,color:'var(--dgd-fg-3)',marginLeft:8}}>· {marca}</span>}
              {hasRealData
                ? <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)'}}>MOCK</span>
              }
            </h2>
          </div>
          <div style={{display:'flex',alignItems:'center',gap:8,flexWrap:'wrap'}}>
            {marcasComTab && (isManager
              ? <span style={{fontSize:11,color:'var(--dgd-fg-2)',padding:'4px 10px',borderRadius:6,background:'var(--dgd-bg-card,white)',border:'1px solid var(--dgd-border-1)'}}>{userName || 'A minha equipa'}</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>
                ))
            )}
            <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>
        </div>

        {/* KPIs rápidos (só quando há marca) */}
        {marcasComTab&&(
          <div style={{display:'flex',gap:8,marginBottom:14,flexWrap:'wrap'}}>
            {(()=>{
              const mLabel = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'][RPC_MES_FECH] || '';
              return [
                { l:'VENDAS YTD', v:rE(ytdV), c:ytdV>=ytdB?'#16a34a':'#dc2626', d:'Jan–'+mLabel+' · Primavera' },
                { l:'BP YTD', v:rE(ytdB), c:'var(--dgd-fg-2)', d:'Objectivo acumulado Jan–'+mLabel },
                { l:'DESVIO', v:(ytdV-ytdB>=0?'+':'')+rE(ytdV-ytdB), c:ytdV>=ytdB?'#16a34a':'#dc2626', d:'Vendas − BP acumulado' },
                { l:'% BP', v:rPct(ytdV,ytdB), c:ytdV/ytdB>=1?'#16a34a':ytdV/ytdB>=0.85?'#d97706':'#dc2626', d:'Realizado vs objectivo' },
              ].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'}}>
          {perm.tabs.filter(t => !isManager || 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>

        {/* Conteúdo */}
        {teamState.loading && isManager && <div style={{padding:'20px 0',color:'var(--dgd-fg-3)',fontSize:12}}>A carregar equipa...</div>}
        {comerciaisState.loading && !isManager && <div style={{padding:'20px 0',color:'var(--dgd-fg-3)',fontSize:12}}>A carregar comerciais {empresaAtiva}...</div>}
        {tab==='rpc'        && <TabMapaRPC coms={activeComs} ano={ano} empresaAtiva={empresaAtiva} canEditBP={canEditBP} excluirNC={excluirNC} setExcluirNC={setExcluirNC} />}
        {tab==='money'      && <TabShowMoney coms={activeComs} />}
        {tab==='forecast'   && <TabForecast coms={activeComs} />}
        {tab==='predit'     && <TabPreditividade coms={activeComs} />}
        {tab==='ytd'        && <TabYTD ano={ano} />}
        {tab==='mediasales' && <TabMediaSales coms={activeComs} />}
        {tab==='sugestoes'  && <TabSugestoes  coms={activeComs} />}
        {tab==='previsao'   && <TabPrevisaoAI coms={activeComs} ano={ano} />}

        {/* Nota dados */}
        {hasRealData
          ? <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> —
              {` Comerciais: BD PostgreSQL (${activeComs.length} em ${empresaAtiva || 'equipa'}).`}
              {ytdState.byVendedor ? ' VENDAS PT: Primavera (32 tipos Active=1).' : ''}
              {fmPT.byVendedor ? ` FM PT: ${fmPT.totalOps||0} OPs ${ano} (OPMANAGERF9/OPTMKT/OPTCOM).` : ''}
              {fmES.byVendedor ? ` FM ES: ${fmES.totalOps||0} OPs ${ano} (OPMANAGERF9/OPTMKT/OPTCOM/VENDAS ES).` : ''}
              {' '}SPIN/BP/F9 pendente layouts Walter (SPINS, OBJECTIVOS, ForecastDate).
            </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> — FM GestorConnect + Primavera sync.
            </div>
        }
      </div>
    </div>
    </BPCtx.Provider>
    </YTDCtx.Provider>
    </FMCtx.Provider>
    </MargemCtx.Provider>
    </BacklogCtx.Provider>
  );
};

window.ScreenComercialRPCES = ScreenComercialRPC;
window.DrilldownModal = DrilldownModal;
window.RPCGrid = RPCGrid;
window.BacklogModal = BacklogModal;

// ─── PV Embed: Mapa RPC Equipamentos embebido na Previsão de Vendas ────────────
const _pvEquipaMatchV = (equipa, v) => {
  if (!equipa || !v) return true;
  if (equipa === 'mimaki_gc')  return v === 'EQ1';
  if (equipa === 'equip')      return /^EQ/.test(v) && v !== 'EQ1';
  if (equipa === 'mimaki_es')  return v === 'EX2';
  if (equipa === 'mimaki_tec') return v === 'J';
  if (equipa === 'sede')       return v === '1' || v === 'MKS';
  return true;
};

const PVEquipMapaEmbed = ({ empresa, fmEmpresa, equipa, selVendedor, ano, userTipo }) => {
  const canEditBP = ['admin', 'board', 'dev'].includes(userTipo);
  const { bpMap, saveBP }    = useBPTargets(ano);
  const fmState              = useFMPipeline(fmEmpresa, ano);
  const ytdState             = useYTDMensal(ano, false);
  const backlogByV           = useBacklog(fmEmpresa, ano);
  const { comerciais, loading: comsLoading } = useComerciais(empresa);

  const [drill, setDrill]               = React.useState(null);
  const [backlogDrill, setBacklogDrill] = React.useState(null);

  const [objState, setObjState] = React.useState(rpcReadLocalObjs);
  React.useEffect(() => {
    let cancel = false;
    const local = rpcReadLocalObjs();
    fetch('/api/rpc/objetivos')
      .then(r => r.ok ? r.json() : {})
      .then(data => {
        if (cancel) return;
        const serverFlat = {};
        Object.entries(data || {}).forEach(([vend, objs]) => {
          Object.entries(objs || {}).forEach(([chave, valor]) => {
            serverFlat[vend + '__' + chave] = parseFloat(valor);
          });
        });
        const missing = Object.entries(local)
          .filter(([k, v]) => serverFlat[k] == null && v != null)
          .map(([k, valor]) => {
            const i = k.lastIndexOf('__');
            return i > 0 ? { vendedor: k.slice(0, i), chave: k.slice(i + 2), valor } : null;
          })
          .filter(Boolean);
        rpcPostObjetivos(missing);
        setObjState(prev => {
          const next = { ...local, ...serverFlat };
          Object.keys(prev).forEach(k => { if (prev[k] !== local[k]) next[k] = prev[k]; });
          rpcWriteLocalObjs(next);
          return next;
        });
      })
      .catch(() => {});
    return () => { cancel = true; };
  }, []);
  const saveObj = (vendedor, key, val) => {
    const k = vendedor + '__' + key;
    setObjState(prev => {
      const next = { ...prev, [k]: val };
      rpcWriteLocalObjs(next);
      return next;
    });
    rpcPostObjetivos([{ vendedor, chave: key, valor: val }]);
  };
  const getObjMap = (vendedor) => {
    const prefix = vendedor + '__';
    const result = {};
    Object.entries(objState).forEach(([k, v]) => { if (k.startsWith(prefix)) result[k.slice(prefix.length)] = v; });
    return result;
  };

  const fmByV    = fmState.byVendedor || {};
  const ytdByV   = ytdState.byVendedor || {};
  const margemByV = ytdState.margemMap || {};

  const comsInEquipa = React.useMemo(() => {
    const all = comerciais || [];
    return all.filter(c => _pvEquipaMatchV(equipa, c.vendedor));
  }, [comerciais, equipa]);

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

  const mkD = (v, fallback) => {
    const fv = fmByV[v] ? v : (fallback && fmByV[fallback] ? fallback : v);
    const yv = ytdByV[v] ? v : (fallback && ytdByV[fallback] ? fallback : v);
    const mv = margemByV[v] ? v : (fallback && margemByV[fallback] ? fallback : v);
    const fm = fmByV[fv];
    return {
      spin:              _zeros12,
      optmkt:            fm ? fm.optmkt           : _zeros12,
      optcom:            fm ? fm.optcom           : _zeros12,
      opmanagerf9:       fm ? fm.opmanagerf9      : _null12,
      bp:                bpMap[v]                  ? bpMap[v]              : _zeros12,
      f9:                _null12,
      faturado:          ytdByV[yv]                || _null12,
      opsWon:            fm ? (fm.wonValor         || _null12) : _null12,
      lostValor:         fm ? (fm.lostValor        || _null12) : _null12,
      pipelineStock:     fm ? (fm.pipelineStock    || _null12) : _null12,
      totalOpenPipeline: fm ? (fm.totalOpenPipeline || null)   : null,
      stage90:           fm ? (fm.stage90          || _null12) : _null12,
      margemMensal:      margemByV[mv]             || _null12,
    };
  };

  const mkAgg = (coms) => {
    const agg = k => Array.from({length:12}, (_, i) =>
      coms.every(c => mkD(c.vendedor, c.gestor_vendedor_id)[k][i] == null) ? null
        : coms.reduce((s, c) => s + (mkD(c.vendedor, c.gestor_vendedor_id)[k][i] || 0), 0));
    const totOpen = coms.reduce((s,c) => s + (mkD(c.vendedor, c.gestor_vendedor_id).totalOpenPipeline || 0), 0);
    return { spin:agg('spin'), optmkt:agg('optmkt'), optcom:agg('optcom'), opmanagerf9:agg('opmanagerf9'), bp:agg('bp'), f9:agg('f9'), faturado:agg('faturado'), opsWon:agg('opsWon'), lostValor:agg('lostValor'), pipelineStock:agg('pipelineStock'), totalOpenPipeline:totOpen, stage90:agg('stage90'), margemMensal:agg('margemMensal') };
  };

  const teamBacklog = React.useMemo(() => {
    const entries = comsInEquipa.map(c => backlogByV[c.vendedor]).filter(Boolean);
    if (!entries.length) return null;
    return {
      opmanagerf9: entries.reduce((s,e) => s + (e.opmanagerf9 || 0), 0),
      optmkt:      entries.reduce((s,e) => s + (e.optmkt      || 0), 0),
      optcom:      entries.reduce((s,e) => s + (e.optcom      || 0), 0),
      optTopValor: entries.reduce((s,e) => s + (e.optTopValor || 0), 0),
      ops:         comsInEquipa.flatMap(c => (backlogByV[c.vendedor]?.ops || []).map(op => ({ ...op, _comercialNome: c.nome }))),
    };
  }, [comsInEquipa, backlogByV]);

  if (comsLoading || fmState.loading || ytdState.loading) {
    return <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>A carregar RPC Equipamentos...</div>;
  }

  const renderComs = selVendedor ? comsToShow : comsInEquipa;
  if (!renderComs.length) {
    return <div style={{ padding:'20px 0', color:'var(--dgd-fg-3)', fontSize:12 }}>Sem comerciais de equipamentos para esta equipa.</div>;
  }

  const aggCom = !selVendedor && renderComs.length > 1
    ? { nome: 'Totais Equipa', id: '__tot__', vendedor: '—', gestor_vendedor_id: null }
    : null;
  const aggD = (!selVendedor && renderComs.length > 0) ? mkAgg(renderComs) : null;

  return (
    <BacklogCtx.Provider value={backlogByV}>
    <MargemCtx.Provider value={margemByV}>
    <FMCtx.Provider value={fmByV}>
    <YTDCtx.Provider value={ytdByV}>
    <BPCtx.Provider value={{ bpMap, saveBP }}>
      <div>
        {drill && <DrilldownModal drill={drill} onClose={() => setDrill(null)} excluirNC={false} />}
        {backlogDrill && (
          <BacklogModal
            com={backlogDrill.com}
            backlogEntry={backlogDrill.com.vendedor === '—' ? teamBacklog : backlogByV[backlogDrill.com.vendedor]}
            tipo={backlogDrill.tipo}
            ytdTot={backlogDrill.ytdTot}
            ano={ano}
            empresa={fmEmpresa}
            onClose={() => setBacklogDrill(null)}
          />
        )}
        {aggCom && aggD && (
          <div style={{ marginBottom: 16 }}>
            <RPCGrid
              com={aggCom}
              d={aggD}
              onDrilldown={(tipo, mesIdx, extra) => setDrill({ com: aggCom, tipo, mesIdx, ano, empresa: fmEmpresa, ...(extra||{}) })}
              canEditBP={false}
              onBPEdit={() => {}}
              backlogEntry={teamBacklog}
              onBacklog={(tipo, ytdTot) => setBacklogDrill({ com: aggCom, tipo, ytdTot })}
              objMap={getObjMap('__equipa_' + equipa)}
              onObjEdit={(key, val) => saveObj('__equipa_' + equipa, key, val)}
              hideAI={true}
              comList={renderComs.map(c => ({ vendedor: c.vendedor, nome: c.nome }))}
            />
          </div>
        )}
        {selVendedor && renderComs.map(com => (
          <div key={com.vendedor} style={{ marginBottom: 16 }}>
            <RPCGrid
              com={com}
              d={mkD(com.vendedor, com.gestor_vendedor_id)}
              onDrilldown={(tipo, mesIdx, extra) => setDrill({ com, tipo, mesIdx, ano, empresa: fmEmpresa, ...(extra||{}) })}
              canEditBP={canEditBP}
              onBPEdit={(mesIdx, val) => saveBP(com.vendedor, mesIdx, val)}
              backlogEntry={backlogByV[com.vendedor]}
              onBacklog={(tipo, ytdTot) => setBacklogDrill({ com, tipo, ytdTot })}
              objMap={getObjMap(com.vendedor)}
              onObjEdit={(key, val) => saveObj(com.vendedor, key, val)}
              hideAI={true}
            />
          </div>
        ))}
      </div>
    </BPCtx.Provider>
    </YTDCtx.Provider>
    </FMCtx.Provider>
    </MargemCtx.Provider>
    </BacklogCtx.Provider>
  );
};
window.PVEquipMapaEmbedES = PVEquipMapaEmbed;

})(); // fim IIFE
