import React, { useState, useRef, useEffect, useMemo, memo } from 'react';
import './ConsultorCard.css';
const SELOS = {
PRESID_CAMARA: { label: 'Presidente Camara', cor: 'selo-camara', icone: '👑' },
COORD_PPG: { label: 'Coord. PPG', cor: 'selo-coord', icone: '🎓' },
BPQ: { label: 'BPQ', cor: 'selo-bpq', icone: '🏅' },
AUTOR_GP: { label: 'Autor GP', cor: 'selo-gp', icone: '🏆' },
AUTOR_PREMIO: { label: 'Autor Premio', cor: 'selo-premio', icone: '🥇' },
AUTOR_MENCAO: { label: 'Autor Mencao', cor: 'selo-mencao', icone: '🥈' },
ORIENT_GP: { label: 'Orient. GP', cor: 'selo-gp', icone: '🏆' },
ORIENT_PREMIO: { label: 'Orient. Premio', cor: 'selo-orient-premio', icone: '🎖️' },
ORIENT_MENCAO: { label: 'Orient. Mencao', cor: 'selo-orient-mencao', icone: '📜' },
COORIENT_GP: { label: 'Coorient. GP', cor: 'selo-gp', icone: '🏆' },
COORIENT_PREMIO: { label: 'Coorient. Premio', cor: 'selo-coorient-premio', icone: '🎖️' },
COORIENT_MENCAO: { label: 'Coorient. Mencao', cor: 'selo-coorient-mencao', icone: '📜' },
ORIENT_TESE: { label: 'Orient. Tese', cor: 'selo-orient', icone: '📚' },
ORIENT_DISS: { label: 'Orient. Diss.', cor: 'selo-orient', icone: '📄' },
ORIENT_POS_DOC: { label: 'Orient. Pos-Doc', cor: 'selo-orient', icone: '🔬' },
CO_ORIENT_TESE: { label: 'Coorient. Tese', cor: 'selo-coorient', icone: '📚' },
CO_ORIENT_DISS: { label: 'Coorient. Diss.', cor: 'selo-coorient', icone: '📄' },
CO_ORIENT_POS_DOC: { label: 'Coorient. Pos-Doc', cor: 'selo-coorient', icone: '🔬' },
};
const gerarSelos = (consultor) => {
const selos = [];
const isPresidCamaraVigente = consultor.coordenacoes_capes?.some(
(c) => c.codigo === 'CAM' && c.presidente && (c.ativo ?? !c.fim)
);
if (isPresidCamaraVigente) {
selos.push({ ...SELOS.PRESID_CAMARA, qtd: 1, hint: 'Presidente Câmara Temática' });
}
if (consultor.coordenador_ppg) {
selos.push({ ...SELOS.COORD_PPG, qtd: 1, hint: 'Coordenador de PPG' });
}
const bolsas = Array.isArray(consultor.bolsas_cnpq) ? consultor.bolsas_cnpq : [];
if (bolsas.length > 0) {
const porNivel = {};
for (const b of bolsas) {
const nivel = (b.nivel || 'N/A').toString().trim();
porNivel[nivel] = (porNivel[nivel] || 0) + 1;
}
const niveis = Object.keys(porNivel).sort();
const label = niveis.length === 1 ? `BPQ ${niveis[0]}` : 'BPQ';
const niveisStr = niveis.join(', ');
selos.push({ ...SELOS.BPQ, label, qtd: bolsas.length, hint: `BPQ NIVEL ${niveisStr}` });
}
const premiacoes = Array.isArray(consultor.premiacoes) ? consultor.premiacoes : [];
const gerarSelosPorPapel = (papel, seloGP, seloPremio, seloMencao, hintPrefix) => {
const lista = premiacoes.filter((p) => (p.papel || '').toString().toLowerCase() === papel.toLowerCase());
const gp = lista.filter((p) => p.codigo === 'PREMIACAO').length;
const premio = lista.filter((p) => p.codigo === 'PREMIACAO_GP').length;
const mencao = lista.filter((p) => p.codigo === 'MENCAO').length;
if (gp > 0) selos.push({ ...seloGP, qtd: gp, hint: `${hintPrefix} - Grande Prêmio` });
if (premio > 0) selos.push({ ...seloPremio, qtd: premio, hint: `${hintPrefix} - Prêmio` });
if (mencao > 0) selos.push({ ...seloMencao, qtd: mencao, hint: `${hintPrefix} - Menção Honrosa` });
};
gerarSelosPorPapel('autor', SELOS.AUTOR_GP, SELOS.AUTOR_PREMIO, SELOS.AUTOR_MENCAO, 'Autor');
gerarSelosPorPapel('orientador', SELOS.ORIENT_GP, SELOS.ORIENT_PREMIO, SELOS.ORIENT_MENCAO, 'Orientador');
gerarSelosPorPapel('coorientador', SELOS.COORIENT_GP, SELOS.COORIENT_PREMIO, SELOS.COORIENT_MENCAO, 'Coorientador');
const orientacoes = Array.isArray(consultor.orientacoes) ? consultor.orientacoes : [];
const gerarSelosOrientacaoContagem = (codigo, isCoorientacao, seloBase) => {
const lista = orientacoes.filter((o) => o.codigo === codigo && (isCoorientacao ? o.coorientacao : !o.coorientacao));
if (lista.length > 0) {
selos.push({ ...seloBase, qtd: lista.length, hint: `${seloBase.label} (${lista.length}x)` });
}
};
gerarSelosOrientacaoContagem('ORIENT_POS_DOC', false, SELOS.ORIENT_POS_DOC);
gerarSelosOrientacaoContagem('ORIENT_TESE', false, SELOS.ORIENT_TESE);
gerarSelosOrientacaoContagem('ORIENT_DISS', false, SELOS.ORIENT_DISS);
gerarSelosOrientacaoContagem('CO_ORIENT_POS_DOC', true, SELOS.CO_ORIENT_POS_DOC);
gerarSelosOrientacaoContagem('CO_ORIENT_TESE', true, SELOS.CO_ORIENT_TESE);
gerarSelosOrientacaoContagem('CO_ORIENT_DISS', true, SELOS.CO_ORIENT_DISS);
return selos;
};
const SelosBadges = ({ selos, compacto = false }) => {
if (!selos || selos.length === 0) return null;
const selosExibidos = compacto ? selos.slice(0, 4) : selos;
const selosOcultos = compacto && selos.length > 4 ? selos.length - 4 : 0;
return (
{selosExibidos.map((selo, idx) => (
1 ? ` (${selo.qtd}x)` : ''}`}
>
{selo.icone}
{!compacto && {selo.label}}
{selo.qtd > 1 && {selo.qtd}}
))}
{selosOcultos > 0 && (
+{selosOcultos}
)}
);
};
const FORMULAS = {
bloco_a: {
titulo: 'Coordenacao CAPES',
descricao: 'CA=200 | CAJ=150 | CAJ_MP=120 | CAM=100\nTempo: multiplicador por ano (anos completos)\nBônus atualidade (mandato vigente) + Retorno (mandato anterior)',
},
bloco_b: {
titulo: 'Coordenacao PPG',
descricao: 'Reservado no V1: PPG_COORD base=0 | teto=0 (dados incompletos no ATUACAPES para pontuar).',
},
bloco_c: {
titulo: 'Consultoria',
descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade: 3a=+5, 5a=+10, 8a+=+20 (escalonado)\nRetorno (reativação): +15 (uma vez)',
},
bloco_d: {
titulo: 'Premiacoes/Avaliacoes',
descricao: 'Premiações: GP=100 (teto 180) | Prêmio=50 (teto 60) | Menção=30 (teto 30)\nBolsas: BPQ_SUP=30 (teto 60) | BPQ_INT=50 (teto 100)\nInscrições/Avaliações/Comissões/Participações/Orientações/Bancas (com tetos por código)',
},
};
const PONTOS_BASE = {
CA: 200, CAJ: 150, CAJ_MP: 120, CAM: 100,
CONS_ATIVO: 150, CONS_HIST: 100, CONS_FALECIDO: 100,
INSC_AUTOR: 10, INSC_INST: 30,
AVAL_COMIS_PREMIO: 30, AVAL_COMIS_GP: 50,
COORD_COMIS_PREMIO: 50, COORD_COMIS_GP: 60,
PREMIACAO: 100, PREMIACAO_GP: 50, MENCAO: 30,
BOL_BPQ_SUP: 30, BOL_BPQ_INT: 50,
BOL_BPQ_SUPERIOR: 30, BOL_BPQ_INTERMEDIARIO: 50,
EVENTO: 1, PROJ: 10,
ORIENT_POS_DOC: 15, ORIENT_TESE: 10, ORIENT_DISS: 5,
CO_ORIENT_POS_DOC: 7, CO_ORIENT_TESE: 5, CO_ORIENT_DISS: 3,
MB_BANCA_POS_DOC: 3, MB_BANCA_TESE: 3, MB_BANCA_DISS: 2,
};
const TETOS = {
INSC_AUTOR: { teto: 20, doc: '3.3 Inscrições' },
INSC_INST: { teto: 60, doc: '3.3 Inscrições' },
AVAL_COMIS_PREMIO: { teto: 60, doc: '3.4 Avaliação/Comissão', bonus: '+2/ano (max 15)' },
AVAL_COMIS_GP: { teto: 80, doc: '3.4 Avaliação/Comissão', bonus: '+3/ano (max 20)' },
COORD_COMIS_PREMIO: { teto: 100, doc: '3.4 Avaliação/Comissão', bonus: '+4/ano (max 20)' },
COORD_COMIS_GP: { teto: 120, doc: '3.4 Avaliação/Comissão', bonus: '+6/ano (max 20)' },
PREMIACAO: { teto: 180, doc: '3.4 Premiações e Bolsas' },
PREMIACAO_GP: { teto: 60, doc: '3.4 Premiações e Bolsas' },
MENCAO: { teto: 30, doc: '3.4 Premiações e Bolsas' },
EVENTO: { teto: 5, doc: '3.5 Participações Acadêmicas' },
PROJ: { teto: 40, doc: '3.5 Participações Acadêmicas' },
BOL_BPQ_SUP: { teto: 60, doc: '3.4 Premiações e Bolsas' },
BOL_BPQ_INT: { teto: 100, doc: '3.4 Premiações e Bolsas' },
BOL_BPQ_SUPERIOR: { teto: 60, doc: '3.4 Premiações e Bolsas' },
BOL_BPQ_INTERMEDIARIO: { teto: 100, doc: '3.4 Premiações e Bolsas' },
ORIENT_POS_DOC: { teto: 100, doc: '3.5 Participações Acadêmicas' },
ORIENT_TESE: { teto: 50, doc: '3.5 Participações Acadêmicas' },
ORIENT_DISS: { teto: 25, doc: '3.5 Participações Acadêmicas' },
CO_ORIENT_POS_DOC: { teto: 35, doc: '3.5 Participações Acadêmicas' },
CO_ORIENT_TESE: { teto: 25, doc: '3.5 Participações Acadêmicas' },
CO_ORIENT_DISS: { teto: 15, doc: '3.5 Participações Acadêmicas' },
MB_BANCA_POS_DOC: { teto: 15, doc: '3.5 Participações Acadêmicas' },
MB_BANCA_TESE: { teto: 15, doc: '3.5 Participações Acadêmicas' },
MB_BANCA_DISS: { teto: 10, doc: '3.5 Participações Acadêmicas' },
};
const ScoreItemWithTooltip = ({ value, label, formula, style }) => (
);
const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecionado }) => {
const [expanded, setExpanded] = useState(false);
const cardRef = useRef(null);
useEffect(() => {
if (highlight && cardRef.current) {
const timeoutId = setTimeout(() => {
cardRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 100);
return () => clearTimeout(timeoutId);
}
}, [highlight]);
const getRankClass = (rank) => {
if (rank === 1) return 'rank-1';
if (rank === 2) return 'rank-2';
if (rank === 3) return 'rank-3';
return '';
};
const formatDate = (dateStr) => {
if (!dateStr) return 'Atual';
return new Date(dateStr).toLocaleDateString('pt-BR');
};
const handleCheckboxClick = (e) => {
e.stopPropagation();
onToggleSelecionado(consultor);
};
const { consultoria, pontuacao } = consultor;
const blocoA = pontuacao?.bloco_a || { total: consultor.bloco_a || 0 };
const blocoB = pontuacao?.bloco_b || { total: consultor.bloco_b || 0 };
const blocoC = pontuacao?.bloco_c || { total: consultor.bloco_c || 0 };
const blocoD = pontuacao?.bloco_d || { total: consultor.bloco_d || 0 };
const pontuacaoTotal = (blocoA.total || 0) + (blocoB.total || 0) + (blocoC.total || 0) + (blocoD.total || 0);
const selos = useMemo(() => gerarSelos(consultor), [consultor]);
return (
setExpanded(!expanded)}>
{}}
/>
#{consultor.posicao || consultor.rank}
{consultor.anos_atuacao} anos de atuacao
{consultoria?.inicio && ` | Desde ${formatDate(consultoria.inicio)}`}
{consultoria && (
<>
{consultoria.codigo?.replace('CONS_', '')}
Status
{consultoria.anos_consecutivos || 0}
Anos Consec.
>
)}
{expanded ? '▲' : '▼'}
{expanded && (
Pontuacao Total
0 ? 'var(--accent-2)' : 'var(--muted)' }}
/>
0 ? 'var(--accent)' : 'var(--muted)' }}
/>
0 ? 'var(--gold)' : 'var(--muted)' }}
/>
0 ? 'var(--bronze)' : 'var(--muted)' }}
/>
Bloco A + Bloco B + Bloco C + Bloco D
{blocoA.atuacoes && blocoA.atuacoes.length > 0 && (
)}
{(blocoB.total > 0 || (blocoB.atuacoes && blocoB.atuacoes.length > 0)) && (
)}
{blocoC.atuacoes && blocoC.atuacoes.length > 0 && (
)}
{blocoD.atuacoes && blocoD.atuacoes.length > 0 && (
)}
{selos.length > 0 && (
Selos e Reconhecimentos
)}
{consultor.coordenacoes_capes?.length > 0 && (
Coordenacoes CAPES
{consultor.coordenacoes_capes.map((coord, idx) => (
{coord.codigo || coord.tipo}
{PONTOS_BASE[coord.codigo] || 0} pts
{coord.area_avaliacao}
{formatDate(coord.inicio || coord.periodo?.inicio)} - {formatDate(coord.fim || coord.periodo?.fim)}
))}
)}
{consultor.premiacoes?.length > 0 && (
Premiacoes
{consultor.premiacoes.map((prem, idx) => (
{prem.codigo}
{PONTOS_BASE[prem.codigo] || 0} pts
{prem.nome_premio}
{prem.ano}
))}
)}
{consultor.avaliacoes_comissao?.length > 0 && (
Avaliacoes de Comissao
{consultor.avaliacoes_comissao.map((aval, idx) => (
{aval.codigo}
{PONTOS_BASE[aval.codigo] || 0} pts
{aval.nome_comissao || aval.premio}
{aval.ano}
))}
)}
{consultor.inscricoes?.length > 0 && (
Inscricoes
{consultor.inscricoes.map((insc, idx) => (
{insc.codigo}
{PONTOS_BASE[insc.codigo] || 0} pts
{insc.premio}
{insc.ano}
))}
)}
{consultor.participacoes?.length > 0 && (
Participacoes (Eventos/Projetos)
{consultor.participacoes.slice(0, 10).map((part, idx) => (
{part.codigo}
{PONTOS_BASE[part.codigo] || 0} pts
{part.descricao || part.tipo}
{part.ano}
))}
{consultor.participacoes.length > 10 && (
... e mais {consultor.participacoes.length - 10} participacoes
)}
)}
)}
);
});
ConsultorCard.displayName = 'ConsultorCard';
const BlocoDetalhes = memo(({ titulo, bloco, cor }) => (
{titulo}
{bloco.atuacoes?.map((at, idx) => (
{(() => {
const base = at.base || 0;
const tempo = at.tempo || 0;
const bonus = at.bonus || 0;
const bruto = base + tempo + bonus;
const meta = TETOS[at.codigo];
const hasTeto = meta && meta.teto > 0;
const capped = hasTeto && bruto > meta.teto;
const unidade = at.quantidade > 1 ? Math.round(base / at.quantidade) : null;
const partes = [];
partes.push(
unidade
? `Base ${unidade} x ${at.quantidade} = ${base}`
: `Base ${base}`
);
if (tempo) partes.push(`Tempo ${tempo}`);
if (bonus) partes.push(`Bônus ${bonus}`);
if (hasTeto) partes.push(`Teto ${meta.teto}`);
if (meta && meta.bonus) partes.push(meta.bonus);
partes.push(capped ? `Total ${at.total} (teto)` : `Total ${at.total}`);
return partes.join(" | ");
})()}
))}
));
BlocoDetalhes.displayName = 'BlocoDetalhes';
export default ConsultorCard;