import React, { useState, useRef, useEffect, useMemo, memo } from 'react'; import { createPortal } from 'react-dom'; import './ConsultorCard.css'; import RawDataModal from './RawDataModal'; import { rankingService } from '../services/api'; const SELOS = { PRESID_CAMARA: { codigo: 'PRESID_CAMARA', label: 'Presidente Camara', cor: 'selo-camara', icone: '👑' }, COORD_PPG: { codigo: 'COORD_PPG', label: 'Coord. PPG', cor: 'selo-coord', icone: '🎓' }, BPQ: { codigo: 'BPQ', label: 'BPQ', cor: 'selo-bpq', icone: '🏅' }, AUTOR_GP: { codigo: 'AUTOR_GP', label: 'Autor GP', cor: 'selo-gp', icone: '🏆' }, AUTOR_PREMIO: { codigo: 'AUTOR_PREMIO', label: 'Autor Premio', cor: 'selo-premio', icone: '🥇' }, AUTOR_MENCAO: { codigo: 'AUTOR_MENCAO', label: 'Autor Mencao', cor: 'selo-mencao', icone: '🥈' }, ORIENT_GP: { codigo: 'ORIENT_GP', label: 'Orient. GP', cor: 'selo-gp', icone: '🏆' }, ORIENT_PREMIO: { codigo: 'ORIENT_PREMIO', label: 'Orient. Premio', cor: 'selo-orient-premio', icone: '🎖️' }, ORIENT_MENCAO: { codigo: 'ORIENT_MENCAO', label: 'Orient. Mencao', cor: 'selo-orient-mencao', icone: '📜' }, COORIENT_GP: { codigo: 'COORIENT_GP', label: 'Coorient. GP', cor: 'selo-gp', icone: '🏆' }, COORIENT_PREMIO: { codigo: 'COORIENT_PREMIO', label: 'Coorient. Premio', cor: 'selo-coorient-premio', icone: '🎖️' }, COORIENT_MENCAO: { codigo: 'COORIENT_MENCAO', label: 'Coorient. Mencao', cor: 'selo-coorient-mencao', icone: '📜' }, ORIENT_TESE: { codigo: 'ORIENT_TESE', label: 'Orient. Tese', cor: 'selo-orient', icone: '📚' }, ORIENT_DISS: { codigo: 'ORIENT_DISS', label: 'Orient. Diss.', cor: 'selo-orient', icone: '📄' }, ORIENT_POS_DOC: { codigo: 'ORIENT_POS_DOC', label: 'Orient. Pos-Doc', cor: 'selo-orient', icone: '🔬' }, CO_ORIENT_TESE: { codigo: 'CO_ORIENT_TESE', label: 'Coorient. Tese', cor: 'selo-coorient', icone: '📚' }, CO_ORIENT_DISS: { codigo: 'CO_ORIENT_DISS', label: 'Coorient. Diss.', cor: 'selo-coorient', icone: '📄' }, CO_ORIENT_POS_DOC: { codigo: 'CO_ORIENT_POS_DOC', label: 'Coorient. Pos-Doc', cor: 'selo-coorient', icone: '🔬' }, MB_BANCA_POS_DOC: { codigo: 'MB_BANCA_POS_DOC', label: 'Banca Pos-Doc', cor: 'selo-banca', icone: '🔬' }, MB_BANCA_TESE: { codigo: 'MB_BANCA_TESE', label: 'Banca Tese', cor: 'selo-banca', icone: '📚' }, MB_BANCA_DISS: { codigo: 'MB_BANCA_DISS', label: 'Banca Diss.', cor: 'selo-banca', icone: '📄' }, EVENTO: { codigo: 'EVENTO', label: 'Evento', cor: 'selo-evento', icone: '📅' }, PROJ: { codigo: 'PROJ', label: 'Projeto', cor: 'selo-proj', icone: '📁' }, IDIOMA_BILINGUE: { codigo: 'IDIOMA_BILINGUE', label: 'Bilingue', cor: 'selo-idioma', icone: '🌍' }, IDIOMA_MULTILINGUE: { codigo: 'IDIOMA_MULTILINGUE', label: 'Multilingue', cor: 'selo-idioma', icone: '🌐' }, TITULACAO_MESTRE: { codigo: 'TITULACAO_MESTRE', label: 'Mestre', cor: 'selo-titulacao', icone: '🎓' }, TITULACAO_DOUTOR: { codigo: 'TITULACAO_DOUTOR', label: 'Doutor', cor: 'selo-titulacao', icone: '🎓' }, TITULACAO_POS_DOUTOR: { codigo: 'TITULACAO_POS_DOUTOR', label: 'Pos-Doutor', cor: 'selo-titulacao', icone: '🎓' }, }; const TIPOS_ATUACAO_CONFIG = { 'Coordenador': { cor: 'tipo-coordenador', icone: '🎯' }, 'Consultor': { cor: 'tipo-consultor', icone: '💼' }, 'Avaliador': { cor: 'tipo-avaliador', icone: '📋' }, 'Premiado': { cor: 'tipo-premiado', icone: '🏆' }, 'Orientador': { cor: 'tipo-orientador', icone: '🎓' }, 'Bolsista CNPq': { cor: 'tipo-bolsista', icone: '🔬' }, 'Inscrito Premio': { cor: 'tipo-inscrito', icone: '📝' }, 'Projeto': { cor: 'tipo-projeto', icone: '📊' }, 'Evento': { cor: 'tipo-evento', 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_GP_AUTOR').length; const premio = lista.filter((p) => p.codigo === 'PREMIACAO_AUTOR').length; const mencao = lista.filter((p) => p.codigo === 'MENCAO_AUTOR').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); const membrosBanca = Array.isArray(consultor.membros_banca) ? consultor.membros_banca : []; const bancaPosDoc = membrosBanca.filter((m) => m.codigo === 'MB_BANCA_POS_DOC'); const bancaTese = membrosBanca.filter((m) => m.codigo === 'MB_BANCA_TESE'); const bancaDiss = membrosBanca.filter((m) => m.codigo === 'MB_BANCA_DISS'); if (bancaPosDoc.length > 0) { selos.push({ ...SELOS.MB_BANCA_POS_DOC, qtd: bancaPosDoc.length, hint: `Banca Pos-Doc (${bancaPosDoc.length}x)` }); } if (bancaTese.length > 0) { selos.push({ ...SELOS.MB_BANCA_TESE, qtd: bancaTese.length, hint: `Banca Tese (${bancaTese.length}x)` }); } if (bancaDiss.length > 0) { selos.push({ ...SELOS.MB_BANCA_DISS, qtd: bancaDiss.length, hint: `Banca Dissertacao (${bancaDiss.length}x)` }); } const participacoes = Array.isArray(consultor.participacoes) ? consultor.participacoes : []; const eventos = participacoes.filter((p) => p.codigo === 'EVENTO'); const projetos = participacoes.filter((p) => p.codigo === 'PROJ'); if (eventos.length > 0) { selos.push({ ...SELOS.EVENTO, qtd: eventos.length, hint: `Eventos (${eventos.length}x)` }); } if (projetos.length > 0) { selos.push({ ...SELOS.PROJ, qtd: projetos.length, hint: `Projetos (${projetos.length}x)` }); } return selos; }; const SELOS_COM_DADOS = [ 'PRESID_CAMARA', 'COORD_PPG', 'BPQ', 'AUTOR_GP', 'AUTOR_PREMIO', 'AUTOR_MENCAO', 'ORIENT_GP', 'ORIENT_PREMIO', 'ORIENT_MENCAO', 'COORIENT_GP', 'COORIENT_PREMIO', 'COORIENT_MENCAO', 'ORIENT_TESE', 'ORIENT_DISS', 'ORIENT_POS_DOC', 'CO_ORIENT_TESE', 'CO_ORIENT_DISS', 'CO_ORIENT_POS_DOC', 'MB_BANCA_POS_DOC', 'MB_BANCA_TESE', 'MB_BANCA_DISS', 'EVENTO', 'PROJ', ]; const SelosBadges = ({ selos, compacto = false, onSeloClick }) => { if (!selos || selos.length === 0) return null; const handleClick = (e, selo) => { if (onSeloClick && SELOS_COM_DADOS.includes(selo.codigo)) { e.preventDefault(); e.stopPropagation(); onSeloClick(selo); } }; return (
{selos.map((selo, idx) => { const temDados = SELOS_COM_DADOS.includes(selo.codigo); return ( 1 ? ` (${selo.qtd}x)` : ''}`)} onMouseDown={(e) => onSeloClick && temDados && e.stopPropagation()} onClick={(e) => handleClick(e, selo)} > {selo.icone} {!compacto && {selo.label}} {!compacto && {selo.qtd || 1}} ); })}
); }; const TiposAtuacaoBadges = ({ tipos, exibirTodos = false, onBadgeClick, consultor }) => { if (!tipos || tipos.length === 0) return null; const tiposExibidos = exibirTodos ? tipos : tipos.slice(0, 4); const tiposOcultos = !exibirTodos && tipos.length > 4 ? tipos.length - 4 : 0; const handleClick = (e, tipo) => { if (onBadgeClick) { e.preventDefault(); e.stopPropagation(); onBadgeClick(tipo); } }; return (
{tiposExibidos.map((tipo, idx) => { const config = TIPOS_ATUACAO_CONFIG[tipo] || { cor: 'tipo-default', icone: '📌' }; return ( onBadgeClick && e.stopPropagation()} onClick={(e) => handleClick(e, tipo)} > {config.icone} {tipo} ); })} {tiposOcultos > 0 && ( +{tiposOcultos} )}
); }; const TipoAtuacaoModal = ({ tipo, consultor, onClose }) => { if (!tipo || !consultor) return null; const formatDate = (dateStr) => { if (!dateStr) return 'Atual'; return new Date(dateStr).toLocaleDateString('pt-BR'); }; const renderContent = () => { switch (tipo) { case 'Coordenador': { const coords = consultor.coordenacoes_capes || []; if (coords.length === 0) return

Sem dados de coordenação

; return (
{[...coords].sort((a, b) => new Date(b.inicio || 0) - new Date(a.inicio || 0)).map((c, i) => (
{c.codigo} {c.area_avaliacao} {c.presidente && 👑 Presidente} {formatDate(c.inicio)} - {formatDate(c.fim)}
))}
); } case 'Consultor': { const cons = consultor.consultoria; if (!cons) return

Sem dados de consultoria

; const vinculos = cons.vinculos || []; return (
Anos consecutivos: {cons.anos_consecutivos || 0} Início: {formatDate(cons.inicio)}
{vinculos.length > 0 && ( <>
Vínculos ({vinculos.length})
{[...vinculos].sort((a, b) => new Date(b.periodo?.inicio || 0) - new Date(a.periodo?.inicio || 0)).map((v, i) => { const isAtivo = v.periodo?.ativo ?? !v.periodo?.fim; return (
{isAtivo ? 'ATIVO' : 'ENCERRADO'} {v.ies ? (v.ies.sigla ? `${v.ies.sigla} - ${v.ies.nome || ''}` : v.ies.nome) : 'IES não informada'} {formatDate(v.periodo?.inicio)} - {formatDate(v.periodo?.fim)}
); })} )}
); } case 'Avaliador': { const avals = consultor.avaliacoes_comissao || []; if (avals.length === 0) return

Sem avaliações de comissão

; return (
{[...avals].sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((a, i) => (
{a.codigo} {a.nome_comissao || a.premio || a.descricao || '-'} {a.ano || '-'}
))}
); } case 'Premiado': { const prems = consultor.premiacoes || []; if (prems.length === 0) return

Sem premiações

; return (
{[...prems].sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => (
{p.codigo} {p.nome_premio || p.premio || '-'} {p.papel || ''} {p.ano || '-'}
))}
); } case 'Orientador': { const orients = consultor.orientacoes || []; if (orients.length === 0) return

Sem orientações

; const contagem = {}; orients.forEach(o => { contagem[o.codigo] = (contagem[o.codigo] || 0) + 1; }); const labels = { ORIENT_POS_DOC: '🔬 Pós-Doutorado', ORIENT_TESE: '📚 Tese (Doutorado)', ORIENT_DISS: '📄 Dissertação (Mestrado)', CO_ORIENT_POS_DOC: '🔬 Coorient. Pós-Doc', CO_ORIENT_TESE: '📚 Coorient. Tese', CO_ORIENT_DISS: '📄 Coorient. Diss.' }; return (
Total: {orients.length} orientações
{Object.entries(contagem).map(([cod, qtd]) => (
{labels[cod] || cod} {qtd}x
))}
); } case 'Bolsista CNPq': { const bolsas = consultor.bolsas_cnpq || []; if (bolsas.length === 0) return

Sem bolsas CNPq

; return (
{bolsas.map((b, i) => (
BPQ Nível {b.nivel || 'N/A'}
))}
); } case 'Inscrito Premio': { const inscs = consultor.inscricoes || []; if (inscs.length === 0) return

Sem inscrições em prêmios

; return (
{[...inscs].sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((ins, i) => (
{ins.codigo} {ins.premio || ins.descricao || '-'} {ins.ano || '-'}
))}
); } case 'Projeto': { const parts = (consultor.participacoes || []).filter(p => p.codigo === 'PROJ'); if (parts.length === 0) return

Sem projetos

; return (
{[...parts].sort((a, b) => (b.ano || 0) - (a.ano || 0)).slice(0, 20).map((p, i) => (
PROJ {p.descricao || p.tipo || '-'} {p.ano || '-'}
))} {parts.length > 20 &&

... e mais {parts.length - 20} projetos

}
); } case 'Evento': { const parts = (consultor.participacoes || []).filter(p => p.codigo === 'EVENTO'); if (parts.length === 0) return

Sem eventos

; return (
{[...parts].sort((a, b) => (b.ano || 0) - (a.ano || 0)).slice(0, 20).map((p, i) => (
EVENTO {p.descricao || p.tipo || '-'} {p.ano || '-'}
))} {parts.length > 20 &&

... e mais {parts.length - 20} eventos

}
); } default: return

Tipo não reconhecido

; } }; const config = TIPOS_ATUACAO_CONFIG[tipo] || { cor: 'tipo-default', icone: '📌' }; return createPortal(
e.stopPropagation()}>
{config.icone} {tipo}
{renderContent()}
, document.body ); }; const SeloModal = ({ selo, consultor, onClose }) => { if (!selo || !consultor) return null; const formatDate = (dateStr) => { if (!dateStr) return 'Atual'; return new Date(dateStr).toLocaleDateString('pt-BR'); }; const renderContent = () => { switch (selo.codigo) { case 'PRESID_CAMARA': { const coords = (consultor.coordenacoes_capes || []).filter(c => c.presidente); if (coords.length === 0) return

Sem dados

; return (
{coords.map((c, i) => (
{c.codigo} {c.area_avaliacao} {formatDate(c.inicio)} - {formatDate(c.fim)}
))}
); } case 'COORD_PPG': return

Coordenador de Programa de Pós-Graduação

; case 'BPQ': { const bolsas = consultor.bolsas_cnpq || []; if (bolsas.length === 0) return

Sem bolsas

; return (
Total: {bolsas.length} bolsa(s) BPQ
{bolsas.map((b, i) => (
BPQ Nível {b.nivel || 'N/A'}
))}
); } case 'AUTOR_GP': case 'AUTOR_PREMIO': case 'AUTOR_MENCAO': { const codMap = { AUTOR_GP: 'PREMIACAO_GP_AUTOR', AUTOR_PREMIO: 'PREMIACAO_AUTOR', AUTOR_MENCAO: 'MENCAO_AUTOR' }; const prems = (consultor.premiacoes || []).filter(p => p.codigo === codMap[selo.codigo] && (p.papel || '').toLowerCase() === 'autor'); if (prems.length === 0) return

Sem premiações

; return (
{prems.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => (
{p.codigo} {p.nome_premio || p.premio || '-'} {p.ano || '-'}
))}
); } case 'ORIENT_GP': case 'ORIENT_PREMIO': case 'ORIENT_MENCAO': { const codMap = { ORIENT_GP: 'PREMIACAO_GP_AUTOR', ORIENT_PREMIO: 'PREMIACAO_AUTOR', ORIENT_MENCAO: 'MENCAO_AUTOR' }; const prems = (consultor.premiacoes || []).filter(p => p.codigo === codMap[selo.codigo] && (p.papel || '').toLowerCase() === 'orientador'); if (prems.length === 0) return

Sem premiações como orientador

; return (
{prems.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => (
{p.codigo} {p.nome_premio || p.premio || '-'} {p.ano || '-'}
))}
); } case 'COORIENT_GP': case 'COORIENT_PREMIO': case 'COORIENT_MENCAO': { const codMap = { COORIENT_GP: 'PREMIACAO_GP_AUTOR', COORIENT_PREMIO: 'PREMIACAO_AUTOR', COORIENT_MENCAO: 'MENCAO_AUTOR' }; const prems = (consultor.premiacoes || []).filter(p => p.codigo === codMap[selo.codigo] && (p.papel || '').toLowerCase() === 'coorientador'); if (prems.length === 0) return

Sem premiações como coorientador

; return (
{prems.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => (
{p.codigo} {p.nome_premio || p.premio || '-'} {p.ano || '-'}
))}
); } case 'ORIENT_TESE': case 'ORIENT_DISS': case 'ORIENT_POS_DOC': case 'CO_ORIENT_TESE': case 'CO_ORIENT_DISS': case 'CO_ORIENT_POS_DOC': { const orients = consultor.orientacoes || []; const lista = orients.filter(o => o.codigo === selo.codigo); const isCoorient = selo.codigo.startsWith('CO_'); const tipoLabel = { ORIENT_TESE: 'Teses de Doutorado', ORIENT_DISS: 'Dissertações de Mestrado', ORIENT_POS_DOC: 'Pós-Doutorados', CO_ORIENT_TESE: 'Coorientações de Tese', CO_ORIENT_DISS: 'Coorientações de Dissertação', CO_ORIENT_POS_DOC: 'Coorientações de Pós-Doc' }; const premiadas = lista.filter(o => o.premiada); return (
Total: {lista.length} {tipoLabel[selo.codigo]?.toLowerCase() || 'orientações'} {premiadas.length > 0 && Premiadas: {premiadas.length} 🏆}

{isCoorient ? 'Coorientações' : 'Orientações'} de {selo.codigo.includes('TESE') ? 'Doutorado' : selo.codigo.includes('DISS') ? 'Mestrado' : 'Pós-Doutorado'} concluídas.

{premiadas.length > 0 && (

🏆 {premiadas.length} orientação(ões) premiada(s) no Prêmio CAPES de Tese

)}

Dados agregados do currículo Lattes via ATUACAPES.

); } case 'MB_BANCA_POS_DOC': case 'MB_BANCA_TESE': case 'MB_BANCA_DISS': { const bancas = consultor.membros_banca || []; const lista = bancas.filter(b => b.codigo === selo.codigo); const tipoLabel = { MB_BANCA_POS_DOC: 'Bancas de Pós-Doutorado', MB_BANCA_TESE: 'Bancas de Doutorado', MB_BANCA_DISS: 'Bancas de Mestrado' }; if (lista.length === 0) return

Sem dados de bancas

; return (
Total: {lista.length} {tipoLabel[selo.codigo]?.toLowerCase() || 'bancas'}
{lista.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((b, i) => (
{b.nivel || b.tipo || selo.codigo} {b.tipo || tipoLabel[selo.codigo]} {b.ano || '-'}
))}
); } case 'EVENTO': { const participacoes = consultor.participacoes || []; const eventos = participacoes.filter(p => p.codigo === 'EVENTO'); if (eventos.length === 0) return

Sem eventos

; return (
Total: {eventos.length} participação(ões) em eventos
{eventos.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((e, i) => (
EVENTO {e.descricao || e.tipo || 'Evento'} {e.ano || '-'}
))}
); } case 'PROJ': { const participacoes = consultor.participacoes || []; const projetos = participacoes.filter(p => p.codigo === 'PROJ'); if (projetos.length === 0) return

Sem projetos

; return (
Total: {projetos.length} participação(ões) em projetos
{projetos.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => (
PROJ {p.descricao || p.tipo || 'Projeto'} {p.ano || '-'}
))}
); } default: return

Sem dados detalhados para este selo

; } }; return createPortal(
e.stopPropagation()}>
{selo.icone} {selo.label} {selo.qtd}
{renderContent()}
, document.body ); }; const ItemDetalheModal = ({ item, tipo, onClose }) => { if (!item || !tipo) return null; const formatDate = (dateStr) => { if (!dateStr) return 'N/A'; return new Date(dateStr).toLocaleDateString('pt-BR'); }; const getTitulo = () => { switch (tipo) { case 'vinculo': return 'Vínculo de Consultoria'; case 'coordenacao': return 'Coordenação CAPES'; case 'premiacao': return 'Premiação'; case 'avaliacao': return 'Avaliação de Comissão'; case 'inscricao': return 'Inscrição em Prêmio'; case 'participacao': return item.codigo === 'PROJ' ? 'Projeto' : 'Evento'; case 'orientacao': return 'Orientação'; default: return 'Detalhes'; } }; const getIcone = () => { switch (tipo) { case 'vinculo': return '💼'; case 'coordenacao': return '🎯'; case 'premiacao': return '🏆'; case 'avaliacao': return '📋'; case 'inscricao': return '📝'; case 'participacao': return item.codigo === 'PROJ' ? '📊' : '📅'; case 'orientacao': return '🎓'; default: return '📄'; } }; const renderContent = () => { switch (tipo) { case 'vinculo': { const periodo = item.periodo || {}; const isAtivo = periodo.ativo ?? !periodo.fim; return (
Status {isAtivo ? 'ATIVO' : 'ENCERRADO'}
Instituição {item.ies ? (item.ies.sigla ? `${item.ies.sigla} - ${item.ies.nome || ''}` : item.ies.nome) : 'Não informada'}
Início {formatDate(periodo.inicio)}
Fim {isAtivo ? 'Em andamento' : formatDate(periodo.fim)}
{item.situacao && (
Situação {item.situacao}
)}
); } case 'coordenacao': { const isAtivo = item.ativo ?? !item.fim; return (
Tipo {item.codigo || item.tipo}
Área de Avaliação {item.area_avaliacao || 'N/A'}
Status {isAtivo ? 'VIGENTE' : 'ENCERRADO'}
Início {formatDate(item.inicio || item.periodo?.inicio)}
Fim {isAtivo ? 'Em andamento' : formatDate(item.fim || item.periodo?.fim)}
{item.presidente && (
Função 👑 Presidente de Câmara
)}
Pontuação Base {PONTOS_BASE[item.codigo] || 0} pts
); } case 'premiacao': return (
Código {item.codigo}
Prêmio {item.nome_premio || item.premio || 'N/A'}
Ano {item.ano || 'N/A'}
{item.papel && (
Papel {item.papel}
)} {item.tipo && (
Tipo {item.tipo}
)}
Pontuação Base {PONTOS_BASE[item.codigo] || 0} pts
); case 'avaliacao': return (
Código {item.codigo}
Prêmio {item.premio || 'N/A'}
{item.nome_comissao && (
Comissão {item.nome_comissao}
)} {item.comissao_tipo && (
Tipo Comissão {item.comissao_tipo}
)} {item.tipo && (
Função {item.tipo}
)}
Ano {item.ano || 'N/A'}
Pontuação Base {PONTOS_BASE[item.codigo] || 0} pts
); case 'inscricao': return (
Código {item.codigo}
Prêmio {item.premio || 'N/A'}
{item.tipo && (
Tipo Inscrição {item.tipo}
)} {item.situacao && (
Situação {item.situacao}
)}
Ano {item.ano || 'N/A'}
Pontuação Base {PONTOS_BASE[item.codigo] || 0} pts
); case 'participacao': return (
Tipo {item.codigo}
Descrição {item.descricao || item.tipo || 'N/A'}
Ano {item.ano || 'N/A'}
Pontuação Base {PONTOS_BASE[item.codigo] || 0} pts
); case 'orientacao': return (
Tipo {item.codigo}
Categoria {item.codigo?.includes('TESE') ? 'Doutorado' : item.codigo?.includes('DISS') ? 'Mestrado' : 'Pós-Doutorado'}
Função {item.coorientacao || item.codigo?.startsWith('CO_') ? 'Coorientador' : 'Orientador'}
{item.premiada && (
Destaque 🏆 Premiada
)}
Pontuação Base {PONTOS_BASE[item.codigo] || 0} pts
); default: return

Sem detalhes disponíveis

; } }; return createPortal(
e.stopPropagation()}>
{getIcone()} {getTitulo()}
{renderContent()}
, document.body ); }; 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: 'Consultoria', descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade: 8a+=+20 (escalonado)\nRetorno (reativação): +15 (uma vez)', }, bloco_c: { titulo: 'Avaliacoes/Premiacoes', descricao: 'Premiações: GP=100 | Prêmio=50 | Menção=30\nAvaliações de Comissão e Coordenação\nInscrições em Prêmios\nOrientações e Bancas de trabalhos premiados', }, bloco_d: { titulo: 'Indicadores', descricao: 'Bolsas CNPq: BPQ Nível Superior/Intermediário\nParticipações: Eventos e Projetos\nOrientações e Bancas (sem premiação)', }, }; 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_AUTOR: 20, AVAL_COMIS_PREMIO: 30, AVAL_COMIS_GP: 40, COORD_COMIS_PREMIO: 40, COORD_COMIS_GP: 50, PREMIACAO_GP_AUTOR: 100, PREMIACAO_AUTOR: 50, MENCAO_AUTOR: 30, BOL_BPQ_NIVEL: 30, EVENTO: 1, PROJ: 10, }; const gerarInsights = (consultor, totalConsultores) => { const insights = []; const posicao = consultor.posicao || consultor.rank || 0; const pontuacao = consultor.pontuacao_total || 0; const blocoA = consultor.bloco_a || 0; const blocoB = consultor.bloco_b || 0; const blocoC = consultor.bloco_c || 0; const blocoD = consultor.bloco_d || 0; if (totalConsultores > 0 && posicao > 0) { const percentil = ((totalConsultores - posicao) / totalConsultores * 100); if (percentil >= 99) { insights.push({ icone: '🏆', texto: `Top 1% - Elite dos ${totalConsultores.toLocaleString('pt-BR')} consultores` }); } else if (percentil >= 95) { insights.push({ icone: '⭐', texto: `Top 5% entre ${totalConsultores.toLocaleString('pt-BR')} consultores` }); } else if (percentil >= 90) { insights.push({ icone: '📈', texto: `Top 10% no ranking geral` }); } else if (percentil >= 75) { insights.push({ icone: '✓', texto: `Acima de 75% dos consultores` }); } else if (percentil >= 50) { insights.push({ icone: '→', texto: `Metade superior do ranking` }); } } const blocos = [ { nome: 'Coordenação CAPES', letra: 'A', valor: blocoA }, { nome: 'Consultoria', letra: 'B', valor: blocoB }, { nome: 'Avaliações/Premiações', letra: 'C', valor: blocoC }, { nome: 'Indicadores', letra: 'D', valor: blocoD }, ]; const blocosAtivos = blocos.filter(b => b.valor > 0); if (blocosAtivos.length > 0 && pontuacao > 0) { const maiorBloco = blocosAtivos.reduce((a, b) => a.valor > b.valor ? a : b); const pct = Math.round(maiorBloco.valor / pontuacao * 100); if (pct >= 50) { insights.push({ icone: '📊', texto: `${pct}% da pontuação vem de ${maiorBloco.nome}` }); } } const coords = consultor.coordenacoes_capes || []; const coordAtiva = coords.find(c => c.ativo ?? !c.fim); if (coordAtiva) { const labels = { CA: 'Coordenador de Área', CAJ: 'Coordenador Adjunto', CAJ_MP: 'Coord. Adjunto MP', CAM: 'Câmara Temática' }; const bonusAtual = { CA: 30, CAJ: 20, CAJ_MP: 15, CAM: 20 }; insights.push({ icone: '🎯', texto: `${labels[coordAtiva.codigo] || coordAtiva.codigo} em exercício (+${bonusAtual[coordAtiva.codigo] || 0} bônus atualidade)` }); } else if (coords.length > 0) { insights.push({ icone: '📜', texto: `Histórico de ${coords.length} coordenação(ões) CAPES` }); } const consultoria = consultor.consultoria || {}; if (consultor.ativo && consultoria.anos_consecutivos >= 8) { insights.push({ icone: '💎', texto: `${consultoria.anos_consecutivos} anos consecutivos (+20 bônus continuidade)` }); } else if (consultoria.anos_consecutivos >= 3) { insights.push({ icone: '🔷', texto: `${consultoria.anos_consecutivos} anos consecutivos de consultoria` }); } if (consultoria.retornos > 0) { const bonusRetorno = consultor.ativo ? 15 : 20; insights.push({ icone: '🔄', texto: `Retorno à consultoria (+${bonusRetorno} bônus reativação)` }); } const premiacoes = consultor.premiacoes || []; const gps = premiacoes.filter(p => p.codigo === 'PREMIACAO_GP_AUTOR'); const premios = premiacoes.filter(p => p.codigo === 'PREMIACAO_AUTOR'); const mencoes = premiacoes.filter(p => p.codigo === 'MENCAO_AUTOR'); if (gps.length > 0) { insights.push({ icone: '🏆', texto: `${gps.length}x Grande Prêmio CAPES (base 100 pts cada)` }); } if (premios.length > 0) { insights.push({ icone: '🥇', texto: `${premios.length}x Prêmio CAPES (base 50 pts cada)` }); } if (mencoes.length > 0) { insights.push({ icone: '🎖️', texto: `${mencoes.length}x Menção Honrosa (base 30 pts cada)` }); } const anos = consultor.anos_atuacao || 0; if (anos >= 15) { insights.push({ icone: '👑', texto: `${anos} anos de contribuição ao SNPG` }); } else if (anos >= 10) { insights.push({ icone: '🏅', texto: `Veterano com ${anos} anos de atuação` }); } if (blocoA > 0 && blocoB > 0 && blocoC > 0) { insights.push({ icone: '🌟', texto: 'Perfil diversificado: coordenação + consultoria + avaliações' }); } if (insights.length === 0) { insights.push({ icone: '📋', texto: `Pontuação total: ${pontuacao} pontos` }); if (posicao > 0) { insights.push({ icone: '📍', texto: `Posição #${posicao.toLocaleString('pt-BR')} no ranking` }); } } return insights; }; const InsightsModal = ({ consultor, totalConsultores, onClose }) => { const insights = useMemo(() => gerarInsights(consultor, totalConsultores), [consultor, totalConsultores]); const posicao = consultor.posicao || consultor.rank || 0; return createPortal(
e.stopPropagation()}>
💡 Por que estou na posição #{posicao}?
{insights.map((insight, idx) => (
{insight.icone} {insight.texto}
))}
Análise baseada nos critérios oficiais do Ranking CAPES V1.0
, document.body ); }; const TETOS = { CA: { teto: 450, doc: 'Bloco A - Coordenação CAPES', bonus: 'Tempo: 10pts/ano (max 100) | Atualidade: +30 | Retorno: +20' }, CAJ: { teto: 370, doc: 'Bloco A - Coordenação CAPES', bonus: 'Tempo: 8pts/ano (max 80) | Atualidade: +20 | Retorno: +15' }, CAJ_MP: { teto: 315, doc: 'Bloco A - Coordenação CAPES', bonus: 'Tempo: 6pts/ano (max 60) | Atualidade: +15 | Retorno: +10' }, CAM: { teto: 280, doc: 'Bloco A - Coordenação CAPES', bonus: 'Tempo: 5pts/ano (max 50) | Atualidade: +20 | Retorno: +10' }, CONS_ATIVO: { teto: 230, doc: 'Bloco B - Consultoria', bonus: 'Tempo: 5pts/ano (max 50) | Atualidade: +20 | Continuidade 8a+: +20 | Retorno: +15' }, CONS_HIST: { teto: 230, doc: 'Bloco B - Consultoria', bonus: 'Tempo: 5pts/ano (max 50) | Continuidade 8a+: +20 | Retorno: +20' }, CONS_FALECIDO: { teto: 230, doc: 'Bloco B - Consultoria', bonus: 'Tempo: 5pts/ano (max 50) | Continuidade 8a+: +20' }, INSC_AUTOR: { teto: 20, doc: 'Bloco C - Inscrições', bonus: '+2/participação (max 10)' }, INSC_INST_AUTOR: { teto: 50, doc: 'Bloco C - Inscrições', bonus: '+5/participação (max 10)' }, AVAL_COMIS_PREMIO: { teto: 60, doc: 'Bloco C - Avaliação/Comissão', bonus: '+2/ano (max 15)' }, AVAL_COMIS_GP: { teto: 80, doc: 'Bloco C - Avaliação/Comissão', bonus: '+3/ano (max 20)' }, COORD_COMIS_PREMIO: { teto: 100, doc: 'Bloco C - Avaliação/Comissão', bonus: '+4/ano (max 20)' }, COORD_COMIS_GP: { teto: 120, doc: 'Bloco C - Avaliação/Comissão', bonus: '+6/ano (max 20)' }, PREMIACAO_GP_AUTOR: { teto: 300, doc: 'Bloco C - Premiações' }, PREMIACAO_AUTOR: { teto: 150, doc: 'Bloco C - Premiações' }, MENCAO_AUTOR: { teto: 90, doc: 'Bloco C - Premiações' }, EVENTO: { teto: 5, doc: 'Bloco D - Participações', bonus: '+1/participação (max 10)' }, PROJ: { teto: 30, doc: 'Bloco D - Participações', bonus: '+2/participação (max 10)' }, BOL_BPQ_NIVEL: { teto: 60, doc: 'Bloco D - Bolsas CNPq' }, }; const PontuacaoModal = ({ dados, onClose }) => { if (!dados) return null; const { tipo, label, value, formula, atuacao, bloco } = dados; const getTitulo = () => { if (tipo === 'bloco') return `${label} - Detalhes`; if (tipo === 'atuacao') return `${atuacao?.codigo || label} - Cálculo`; if (tipo === 'total') return 'Pontuação Total'; return label; }; const getIcone = () => { if (label?.includes('BLOCO A') || label === 'A') return '🎯'; if (label?.includes('BLOCO B') || label === 'B') return '💼'; if (label?.includes('BLOCO C') || label === 'C') return '🏆'; if (label?.includes('BLOCO D') || label === 'D') return '📊'; if (tipo === 'total') return '📈'; return '📌'; }; const renderBlocoContent = () => { const formulaLines = formula ? formula.split('\n') : []; return (
Pontuação {value} pts
{formulaLines.length > 0 && (
Fórmula de Cálculo
{formulaLines.map((line, idx) => (
{line}
))}
)} {bloco?.atuacoes?.length > 0 && (
Composição
{bloco.atuacoes.map((at, idx) => (
{at.codigo} {at.total} pts {at.quantidade > 1 ? `(${at.quantidade}x)` : ''}
))}
)}
); }; const renderAtuacaoContent = () => { if (!atuacao) return null; const base = atuacao.base || 0; const tempo = atuacao.tempo || 0; const bonus = atuacao.bonus || 0; const bruto = base + tempo + bonus; const meta = TETOS[atuacao.codigo]; const hasTeto = meta && meta.teto > 0; const capped = hasTeto && bruto > meta.teto; const unidade = atuacao.quantidade > 1 ? Math.round(base / atuacao.quantidade) : null; return (
Código {atuacao.codigo}
Pontuação Final {atuacao.total} pts
Cálculo Detalhado
{unidade ? (
Base: {unidade} × {atuacao.quantidade} = {base}
) : (
Base: {base}
)} {tempo > 0 && (
Tempo: +{tempo}
)} {bonus > 0 && (
Bônus: +{bonus}
)}
Subtotal: {bruto}
{hasTeto && (
Teto: {meta.teto}
)}
Total: {atuacao.total} {capped ? '(limitado pelo teto)' : ''}
{meta?.doc && (
Referência {meta.doc}
)} {meta?.bonus && (
Regra de Bônus {meta.bonus}
)}
); }; const renderTotalContent = () => (
Pontuação Total {value} pts
Fórmula
Bloco A + Bloco B + Bloco C + Bloco D
A: Coordenação CAPES | B: Consultoria | C: Avaliações/Premiações | D: Indicadores
); return createPortal(
e.stopPropagation()}>
{getIcone()} {getTitulo()}
{tipo === 'bloco' && renderBlocoContent()} {tipo === 'atuacao' && renderAtuacaoContent()} {tipo === 'total' && renderTotalContent()}
, document.body ); }; const ScoreItemClickable = ({ value, label, formula, style, onClick }) => (
{value}
{label}
); const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecionado, totalConsultores = 0 }) => { const [expanded, setExpanded] = useState(false); const [showRawModal, setShowRawModal] = useState(false); const [tipoAtuacaoModal, setTipoAtuacaoModal] = useState(null); const [seloModal, setSeloModal] = useState(null); const [itemDetalhe, setItemDetalhe] = useState(null); const [pontuacaoModal, setPontuacaoModal] = useState(null); const [showInsights, setShowInsights] = 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 handleRawDataClick = (e) => { e.stopPropagation(); setShowRawModal(true); }; const handleInsightsClick = (e) => { e.stopPropagation(); setShowInsights(true); }; 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 = Number(consultor.pontuacao_total ?? 0); const selos = useMemo(() => gerarSelos(consultor), [consultor]); return (
setExpanded(!expanded)}>
{}} />
#{consultor.posicao || consultor.rank}
{import.meta.env.VITE_HOST_ATUACAPES && consultor.id_pessoa && ( e.stopPropagation()} title="Ver perfil no ATUACAPES" > ↗ )} {consultor.nome} {consultor.ativo && ATIVO} {!consultor.ativo && HISTORICO} {consultor.veterano && VETERANO}
{consultor.anos_atuacao} anos de atuacao {consultoria?.inicio && ` | Desde ${formatDate(consultoria.inicio)}`}
{selos.length > 0 && (
)}
{ const coords = consultor.coordenacoes_capes || []; if (coords.length === 0) return 'Sem coordenação CAPES'; const prioridade = { CA: 1, CAJ: 2, CAJ_MP: 3, CAM: 4 }; const sorted = [...coords].sort((a, b) => { const aAtivo = a.ativo ?? !a.fim; const bAtivo = b.ativo ?? !b.fim; if (aAtivo !== bAtivo) return bAtivo ? 1 : -1; return (prioridade[a.codigo] || 99) - (prioridade[b.codigo] || 99); }); return sorted[0]?.area_avaliacao || 'Coordenação CAPES'; })()}>
{(() => { const coords = consultor.coordenacoes_capes || []; if (coords.length === 0) return '-'; const prioridade = { CA: 1, CAJ: 2, CAJ_MP: 3, CAM: 4 }; const sorted = [...coords].sort((a, b) => { const aAtivo = a.ativo ?? !a.fim; const bAtivo = b.ativo ?? !b.fim; if (aAtivo !== bAtivo) return bAtivo ? 1 : -1; return (prioridade[a.codigo] || 99) - (prioridade[b.codigo] || 99); }); return sorted[0]?.codigo || '-'; })()}
Coord.
{consultoria?.anos_consecutivos || 0}
Anos Consec.
{pontuacaoTotal}
Score
{expanded ? '▲' : '▼'}
{expanded && (
e.stopPropagation()}>

Pontuacao Total

0 ? 'var(--accent-2)' : 'var(--muted)' }} onClick={() => setPontuacaoModal({ tipo: 'bloco', label: 'BLOCO A - Coordenação CAPES', value: blocoA.total, formula: FORMULAS.bloco_a.descricao, bloco: blocoA })} /> 0 ? 'var(--gold)' : 'var(--muted)' }} onClick={() => setPontuacaoModal({ tipo: 'bloco', label: 'BLOCO B - Consultoria', value: blocoB.total, formula: FORMULAS.bloco_b.descricao, bloco: blocoB })} /> 0 ? 'var(--bronze)' : 'var(--muted)' }} onClick={() => setPontuacaoModal({ tipo: 'bloco', label: 'BLOCO C - Avaliações/Premiações', value: blocoC.total, formula: FORMULAS.bloco_c.descricao, bloco: blocoC })} /> 0 ? 'var(--silver)' : 'var(--muted)' }} onClick={() => setPontuacaoModal({ tipo: 'bloco', label: 'BLOCO D - Indicadores', value: blocoD.total, formula: FORMULAS.bloco_d.descricao, bloco: blocoD })} /> setPontuacaoModal({ tipo: 'total', label: 'TOTAL', value: pontuacaoTotal })} />
{blocoA.atuacoes && blocoA.atuacoes.length > 0 && ( )} {blocoB.atuacoes && blocoB.atuacoes.length > 0 && ( )} {blocoC.atuacoes && blocoC.atuacoes.length > 0 && ( )} {blocoD.atuacoes && blocoD.atuacoes.length > 0 && ( )}
{consultor.tipos_atuacao?.length > 0 && (

Tipos de Atuacao

)} {selos.length > 0 && (

Selos e Reconhecimentos

)} {consultoria?.vinculos?.length > 0 && (

Vinculos de Consultoria

{[...consultoria.vinculos] .sort((a, b) => new Date(b.periodo?.inicio || 0) - new Date(a.periodo?.inicio || 0)) .map((vinculo, idx) => { const periodo = vinculo.periodo || {}; const isAtivo = periodo.ativo ?? !periodo.fim; return (
setItemDetalhe({ item: vinculo, tipo: 'vinculo' })} > {isAtivo ? 'ATIVO' : 'ENCERRADO'} {vinculo.ies ? vinculo.ies.sigla && vinculo.ies.nome ? `${vinculo.ies.sigla} - ${vinculo.ies.nome}` : vinculo.ies.sigla || vinculo.ies.nome : 'IES nao informada'} {formatDate(periodo.inicio)} - {isAtivo ? 'Atual' : formatDate(periodo.fim)}
); })}
)} {consultor.coordenacoes_capes?.length > 0 && (

Coordenacoes CAPES

{[...consultor.coordenacoes_capes] .sort((a, b) => new Date(b.inicio || b.periodo?.inicio || 0) - new Date(a.inicio || a.periodo?.inicio || 0)) .map((coord, idx) => (
setItemDetalhe({ item: coord, tipo: 'coordenacao' })} > {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] .sort((a, b) => (b.ano || 0) - (a.ano || 0)) .map((prem, idx) => (
setItemDetalhe({ item: prem, tipo: 'premiacao' })} > {prem.codigo} {PONTOS_BASE[prem.codigo] || 0} pts {prem.nome_premio} {prem.ano}
))}
)} {consultor.avaliacoes_comissao?.length > 0 && (

Avaliacoes de Comissao

{[...consultor.avaliacoes_comissao] .sort((a, b) => (b.ano || 0) - (a.ano || 0)) .map((aval, idx) => (
setItemDetalhe({ item: aval, tipo: 'avaliacao' })} > {aval.codigo} {PONTOS_BASE[aval.codigo] || 0} pts {aval.nome_comissao || aval.premio} {aval.ano}
))}
)} {consultor.inscricoes?.length > 0 && (

Inscricoes

{[...consultor.inscricoes] .sort((a, b) => (b.ano || 0) - (a.ano || 0)) .map((insc, idx) => (
setItemDetalhe({ item: insc, tipo: 'inscricao' })} > {insc.codigo} {PONTOS_BASE[insc.codigo] || 0} pts {insc.premio} {insc.ano}
))}
)} {consultor.participacoes?.length > 0 && (

Participacoes (Eventos/Projetos)

{[...consultor.participacoes] .sort((a, b) => (b.ano || 0) - (a.ano || 0)) .slice(0, 10) .map((part, idx) => (
setItemDetalhe({ item: part, tipo: 'participacao' })} > {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
)}
)} {consultor.orientacoes?.length > 0 && (() => { const orientacoes = consultor.orientacoes; const contagem = {}; const premiadas = {}; orientacoes.forEach(o => { contagem[o.codigo] = (contagem[o.codigo] || 0) + 1; if (o.premiada) premiadas[o.codigo] = (premiadas[o.codigo] || 0) + 1; }); const config = { ORIENT_POS_DOC: { label: 'Pós-Doutorado', icone: '🔬', cor: 'tipo-orientador' }, ORIENT_TESE: { label: 'Tese (Doutorado)', icone: '📚', cor: 'tipo-orientador' }, ORIENT_DISS: { label: 'Dissertação (Mestrado)', icone: '📄', cor: 'tipo-orientador' }, CO_ORIENT_POS_DOC: { label: 'Coorient. Pós-Doc', icone: '🔬', cor: 'tipo-avaliador' }, CO_ORIENT_TESE: { label: 'Coorient. Tese', icone: '📚', cor: 'tipo-avaliador' }, CO_ORIENT_DISS: { label: 'Coorient. Diss.', icone: '📄', cor: 'tipo-avaliador' }, }; const ordem = ['ORIENT_POS_DOC', 'ORIENT_TESE', 'ORIENT_DISS', 'CO_ORIENT_POS_DOC', 'CO_ORIENT_TESE', 'CO_ORIENT_DISS']; return (

Orientacoes ({orientacoes.length} total)

{ordem.filter(cod => contagem[cod] > 0).map(cod => { const cfg = config[cod]; const qtd = contagem[cod]; const prem = premiadas[cod] || 0; return (
{cfg.icone} {cfg.label} {qtd}x {prem > 0 && ( 🏆 {prem} )}
); })}
); })()}
)} {showRawModal && ( setShowRawModal(false)} /> )} {tipoAtuacaoModal && ( setTipoAtuacaoModal(null)} /> )} {seloModal && ( setSeloModal(null)} /> )} {itemDetalhe && ( setItemDetalhe(null)} /> )} {pontuacaoModal && ( setPontuacaoModal(null)} /> )} {showInsights && ( setShowInsights(false)} /> )}
); }); ConsultorCard.displayName = 'ConsultorCard'; const CODIGOS_APENAS_SELO = [ 'ORIENT_POS_DOC', 'ORIENT_TESE', 'ORIENT_DISS', 'CO_ORIENT_POS_DOC', 'CO_ORIENT_TESE', 'CO_ORIENT_DISS', 'MB_BANCA_POS_DOC', 'MB_BANCA_TESE', 'MB_BANCA_DISS', 'ORIENT_POS_DOC_PREM', 'ORIENT_TESE_PREM', 'ORIENT_DISS_PREM', 'CO_ORIENT_POS_DOC_PREM', 'CO_ORIENT_TESE_PREM', 'CO_ORIENT_DISS_PREM', 'MB_BANCA_POS_DOC_PREM', 'MB_BANCA_TESE_PREM', 'MB_BANCA_DISS_PREM', ]; const BlocoDetalhes = memo(({ titulo, bloco, cor, onItemClick }) => { const atuacoesFiltradas = bloco.atuacoes?.filter(at => !CODIGOS_APENAS_SELO.includes(at.codigo)) || []; return (

{titulo}

{atuacoesFiltradas.map((at, idx) => (
onItemClick && onItemClick({ tipo: 'atuacao', label: at.codigo, value: at.total, atuacao: at })} >
{at.total}
{at.codigo}
))}
onItemClick && onItemClick({ tipo: 'bloco', label: titulo, value: bloco.total, bloco: bloco })} >
{bloco.total}
TOTAL
); }); BlocoDetalhes.displayName = 'BlocoDetalhes'; export default ConsultorCard;