diff --git a/backend/src/application/mappers/ranking_mapper.py b/backend/src/application/mappers/ranking_mapper.py
index 455078f..70cde22 100644
--- a/backend/src/application/mappers/ranking_mapper.py
+++ b/backend/src/application/mappers/ranking_mapper.py
@@ -1,14 +1,47 @@
import json
import logging
-from typing import Any, Dict, Optional
+from typing import Any, Dict, List, Optional
from ...domain.entities.consultor_ranking import ConsultorRanking
from ...interface.schemas.ranking_schema import ConsultorRankingResumoSchema
logger = logging.getLogger(__name__)
+TIPOS_ATUACAO_ORDEM = [
+ ("Coordenador", "coordenacoes_capes"),
+ ("Consultor", "consultoria"),
+ ("Avaliador", "avaliacoes_comissao"),
+ ("Premiado", "premiacoes"),
+ ("Orientador", "orientacoes"),
+ ("Bolsista CNPq", "bolsas_cnpq"),
+ ("Inscrito Premio", "inscricoes"),
+ ("Projeto", "participacoes_projeto"),
+ ("Evento", "participacoes_evento"),
+]
+
class RankingMapper:
+ @staticmethod
+ def _extrair_tipos_atuacao(jd: Dict[str, Any]) -> List[str]:
+ tipos = []
+ for label, campo in TIPOS_ATUACAO_ORDEM:
+ if campo == "consultoria":
+ if jd.get("consultoria"):
+ tipos.append(label)
+ elif campo == "participacoes_projeto":
+ participacoes = jd.get("participacoes") or []
+ if any(p.get("codigo") == "PROJ" for p in participacoes):
+ tipos.append(label)
+ elif campo == "participacoes_evento":
+ participacoes = jd.get("participacoes") or []
+ if any(p.get("codigo") == "EVENTO" for p in participacoes):
+ tipos.append(label)
+ else:
+ dados = jd.get(campo)
+ if dados and (isinstance(dados, list) and len(dados) > 0):
+ tipos.append(label)
+ return tipos
+
@staticmethod
def consultor_ranking_to_schema(c: ConsultorRanking) -> ConsultorRankingResumoSchema:
consultoria = None
@@ -21,6 +54,7 @@ class RankingMapper:
orientacoes = None
membros_banca = None
pontuacao = None
+ tipos_atuacao = []
try:
jd = json.loads(c.json_detalhes) if c.json_detalhes else {}
@@ -35,6 +69,7 @@ class RankingMapper:
orientacoes = jd.get("orientacoes")
membros_banca = jd.get("membros_banca")
pontuacao = jd.get("pontuacao")
+ tipos_atuacao = RankingMapper._extrair_tipos_atuacao(jd)
except (json.JSONDecodeError, TypeError) as e:
logger.warning(f"Erro ao parsear json_detalhes do consultor {c.id_pessoa}: {e}")
@@ -59,6 +94,7 @@ class RankingMapper:
bloco_d=bloco_d,
ativo=c.ativo,
anos_atuacao=c.anos_atuacao,
+ tipos_atuacao=tipos_atuacao,
consultoria=consultoria,
coordenacoes_capes=coordenacoes_capes,
inscricoes=inscricoes,
diff --git a/backend/src/interface/api/routes.py b/backend/src/interface/api/routes.py
index 332c8c2..0c9d8eb 100644
--- a/backend/src/interface/api/routes.py
+++ b/backend/src/interface/api/routes.py
@@ -21,6 +21,7 @@ def normalizar_texto(texto: str) -> str:
from ...application.use_cases.obter_ranking import ObterRankingUseCase
from ...application.use_cases.obter_consultor import ObterConsultorUseCase
from ...infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
+from ...application.mappers.ranking_mapper import RankingMapper
from ..schemas.consultor_schema import (
RankingResponseSchema,
RankingDetalhadoResponseSchema,
@@ -183,6 +184,7 @@ async def ranking_paginado(
consultores_schema = []
for e in entries:
d = e.detalhes
+ tipos_atuacao = RankingMapper._extrair_tipos_atuacao(d)
consultores_schema.append(
ConsultorRankingResumoSchema(
id_pessoa=e.id_pessoa,
@@ -195,6 +197,7 @@ async def ranking_paginado(
bloco_d=float(e.bloco_d),
ativo=e.ativo,
anos_atuacao=float(e.anos_atuacao),
+ tipos_atuacao=tipos_atuacao,
coordenador_ppg=bool(d.get("coordenador_ppg", False)),
consultoria=d.get("consultoria"),
coordenacoes_capes=d.get("coordenacoes_capes"),
diff --git a/backend/src/interface/schemas/ranking_schema.py b/backend/src/interface/schemas/ranking_schema.py
index 32689da..faead5c 100644
--- a/backend/src/interface/schemas/ranking_schema.py
+++ b/backend/src/interface/schemas/ranking_schema.py
@@ -14,6 +14,7 @@ class ConsultorRankingResumoSchema(BaseModel):
bloco_d: float
ativo: bool
anos_atuacao: float
+ tipos_atuacao: List[str] = []
coordenador_ppg: Optional[bool] = None
consultoria: Optional[dict] = None
coordenacoes_capes: Optional[list] = None
diff --git a/frontend/src/components/ConsultorCard.css b/frontend/src/components/ConsultorCard.css
index 8e97c07..53c3e96 100644
--- a/frontend/src/components/ConsultorCard.css
+++ b/frontend/src/components/ConsultorCard.css
@@ -124,6 +124,8 @@
font-size: 1.35rem;
white-space: nowrap;
line-height: 1;
+ color: #ffffff;
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
.rank-number.rank-digits-4 {
@@ -578,13 +580,17 @@
.selos-container.selos-compacto {
display: inline-flex;
- margin-left: 0.3rem;
+ margin-left: 0.25rem;
+ gap: 0.2rem;
}
.selos-compacto .selo {
background: rgba(148, 163, 184, 0.12);
border-color: rgba(148, 163, 184, 0.25);
color: #94a3b8;
+ padding: 0.1rem 0.25rem;
+ gap: 0.15rem;
+ font-size: 0.6rem;
}
.selos-compacto .selo:hover {
@@ -596,11 +602,14 @@
.selos-compacto .selo-icone {
filter: grayscale(100%) brightness(1.2);
opacity: 0.85;
+ font-size: 0.65rem;
}
.selos-compacto .selo-qtd {
background: rgba(148, 163, 184, 0.25);
color: #cbd5e1;
+ font-size: 0.5rem;
+ padding: 0.05rem 0.2rem;
}
.selo {
@@ -644,8 +653,9 @@
.selo-mais {
background: rgba(255, 255, 255, 0.05);
border-style: dashed;
- font-size: 0.65rem;
+ font-size: 0.55rem;
color: var(--muted);
+ padding: 0.1rem 0.2rem;
}
.selo-coord {
@@ -722,6 +732,32 @@
color: #e2e8f0;
}
+.tipos-section {
+ grid-column: 1 / -1;
+ margin-top: 1rem;
+}
+
+.tipos-section h4 {
+ color: var(--accent-2);
+}
+
+.tipos-expandido {
+ gap: 0.5rem;
+}
+
+.tipos-expandido .tipo-atuacao {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.7rem;
+}
+
+.tipos-expandido .tipo-icone {
+ font-size: 0.8rem;
+}
+
+.tipos-expandido .tipo-label {
+ font-size: 0.65rem;
+}
+
.selos-section {
grid-column: 1 / -1;
margin-top: 1rem;
@@ -771,3 +807,123 @@
border-color: rgba(79, 70, 229, 0.4);
transform: scale(1.1);
}
+
+.tipos-atuacao-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem;
+ margin-top: 0.4rem;
+}
+
+.tipo-atuacao {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.2rem;
+ font-size: 0.65rem;
+ padding: 0.15rem 0.4rem;
+ border-radius: 4px;
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ background: rgba(255, 255, 255, 0.05);
+ color: var(--muted);
+ font-weight: 500;
+ transition: all 150ms ease;
+}
+
+.tipo-atuacao:hover {
+ transform: translateY(-1px);
+ border-color: rgba(255, 255, 255, 0.25);
+}
+
+.tipo-icone {
+ font-size: 0.7rem;
+ line-height: 1;
+ opacity: 0.9;
+}
+
+.tipo-label {
+ font-size: 0.6rem;
+ letter-spacing: 0.2px;
+ text-transform: uppercase;
+}
+
+.tipo-mais {
+ background: rgba(255, 255, 255, 0.03);
+ border-style: dashed;
+ font-size: 0.55rem;
+ padding: 0.1rem 0.25rem;
+}
+
+.tipo-coordenador {
+ background: linear-gradient(135deg, rgba(239, 68, 68, 0.2), rgba(239, 68, 68, 0.08));
+ border-color: rgba(239, 68, 68, 0.35);
+ color: #fca5a5;
+}
+
+.tipo-consultor {
+ background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(59, 130, 246, 0.08));
+ border-color: rgba(59, 130, 246, 0.35);
+ color: #93c5fd;
+}
+
+.tipo-avaliador {
+ background: linear-gradient(135deg, rgba(168, 85, 247, 0.2), rgba(168, 85, 247, 0.08));
+ border-color: rgba(168, 85, 247, 0.35);
+ color: #d8b4fe;
+}
+
+.tipo-premiado {
+ background: linear-gradient(135deg, rgba(251, 191, 36, 0.25), rgba(251, 191, 36, 0.1));
+ border-color: rgba(251, 191, 36, 0.4);
+ color: #fcd34d;
+}
+
+.tipo-orientador {
+ background: linear-gradient(135deg, rgba(34, 197, 94, 0.2), rgba(34, 197, 94, 0.08));
+ border-color: rgba(34, 197, 94, 0.35);
+ color: #86efac;
+}
+
+.tipo-bolsista {
+ background: linear-gradient(135deg, rgba(14, 165, 233, 0.2), rgba(14, 165, 233, 0.08));
+ border-color: rgba(14, 165, 233, 0.35);
+ color: #7dd3fc;
+}
+
+.tipo-inscrito {
+ background: linear-gradient(135deg, rgba(249, 115, 22, 0.2), rgba(249, 115, 22, 0.08));
+ border-color: rgba(249, 115, 22, 0.35);
+ color: #fdba74;
+}
+
+.tipo-projeto {
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(99, 102, 241, 0.08));
+ border-color: rgba(99, 102, 241, 0.35);
+ color: #a5b4fc;
+}
+
+.tipo-evento {
+ background: linear-gradient(135deg, rgba(236, 72, 153, 0.2), rgba(236, 72, 153, 0.08));
+ border-color: rgba(236, 72, 153, 0.35);
+ color: #f9a8d4;
+}
+
+.tipo-default {
+ background: rgba(148, 163, 184, 0.12);
+ border-color: rgba(148, 163, 184, 0.25);
+ color: #94a3b8;
+}
+
+@media (max-width: 900px) {
+ .tipos-atuacao-container {
+ margin-top: 0.3rem;
+ }
+
+ .tipo-atuacao {
+ font-size: 0.6rem;
+ padding: 0.1rem 0.3rem;
+ }
+
+ .tipo-label {
+ font-size: 0.55rem;
+ }
+}
diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx
index e86d587..5864d23 100644
--- a/frontend/src/components/ConsultorCard.jsx
+++ b/frontend/src/components/ConsultorCard.jsx
@@ -24,6 +24,18 @@ const SELOS = {
CO_ORIENT_POS_DOC: { label: 'Coorient. Pos-Doc', cor: 'selo-coorient', 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 = [];
@@ -114,6 +126,32 @@ const SelosBadges = ({ selos, compacto = false }) => {
);
};
+const TiposAtuacaoBadges = ({ tipos, exibirTodos = false }) => {
+ 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;
+
+ return (
+
+ {tiposExibidos.map((tipo, idx) => {
+ const config = TIPOS_ATUACAO_CONFIG[tipo] || { cor: 'tipo-default', icone: '📌' };
+ return (
+
+ {config.icone}
+ {tipo}
+
+ );
+ })}
+ {tiposOcultos > 0 && (
+
+ +{tiposOcultos}
+
+ )}
+
+ );
+};
+
const FORMULAS = {
bloco_a: {
titulo: 'Coordenacao CAPES',
@@ -372,6 +410,13 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
)}
+ {consultor.tipos_atuacao?.length > 0 && (
+
+
Tipos de Atuacao
+
+
+ )}
+
{selos.length > 0 && (
Selos e Reconhecimentos
diff --git a/frontend/src/components/Header.css b/frontend/src/components/Header.css
index 884a371..8cedabb 100644
--- a/frontend/src/components/Header.css
+++ b/frontend/src/components/Header.css
@@ -359,6 +359,96 @@
color: #c4b5fd;
}
+/* Selos na Legenda */
+.selos-table th:last-child,
+.selos-table td:last-child {
+ text-align: center;
+ width: 2.5rem;
+}
+
+.selo-legenda {
+ display: inline-block;
+ font-size: 0.75rem;
+ padding: 0.1rem 0.3rem;
+ border-radius: 4px;
+ line-height: 1;
+}
+
+.selo-legenda.selo-orient {
+ background: rgba(34, 211, 238, 0.2);
+ border: 1px solid rgba(34, 211, 238, 0.4);
+}
+
+.selo-legenda.selo-coorient {
+ background: rgba(96, 165, 250, 0.2);
+ border: 1px solid rgba(96, 165, 250, 0.4);
+}
+
+.selo-legenda.selo-banca {
+ background: rgba(167, 139, 250, 0.2);
+ border: 1px solid rgba(167, 139, 250, 0.4);
+}
+
+.selo-legenda.selo-bpq {
+ background: rgba(59, 130, 246, 0.2);
+ border: 1px solid rgba(59, 130, 246, 0.4);
+}
+
+.selo-legenda.selo-proj {
+ background: rgba(16, 185, 129, 0.2);
+ border: 1px solid rgba(16, 185, 129, 0.4);
+}
+
+.selo-legenda.selo-evento {
+ background: rgba(251, 146, 60, 0.2);
+ border: 1px solid rgba(251, 146, 60, 0.4);
+}
+
+.selo-legenda.selo-coord {
+ background: rgba(139, 92, 246, 0.2);
+ border: 1px solid rgba(139, 92, 246, 0.4);
+}
+
+.selo-legenda.selo-camara {
+ background: rgba(234, 179, 8, 0.2);
+ border: 1px solid rgba(234, 179, 8, 0.4);
+}
+
+.selo-legenda.selo-gp {
+ background: rgba(234, 179, 8, 0.25);
+ border: 1px solid rgba(234, 179, 8, 0.5);
+}
+
+.selo-legenda.selo-premio {
+ background: rgba(16, 185, 129, 0.2);
+ border: 1px solid rgba(16, 185, 129, 0.4);
+}
+
+.selo-legenda.selo-mencao {
+ background: rgba(156, 163, 175, 0.2);
+ border: 1px solid rgba(156, 163, 175, 0.4);
+}
+
+.selo-legenda.selo-orient-premio {
+ background: rgba(236, 72, 153, 0.2);
+ border: 1px solid rgba(236, 72, 153, 0.4);
+}
+
+.selo-legenda.selo-orient-mencao {
+ background: rgba(148, 163, 184, 0.2);
+ border: 1px solid rgba(148, 163, 184, 0.4);
+}
+
+.selo-legenda.selo-coorient-premio {
+ background: rgba(168, 85, 247, 0.2);
+ border: 1px solid rgba(168, 85, 247, 0.4);
+}
+
+.selo-legenda.selo-coorient-mencao {
+ background: rgba(107, 114, 128, 0.2);
+ border: 1px solid rgba(107, 114, 128, 0.4);
+}
+
@media (max-width: 900px) {
.criteria-row {
flex-wrap: wrap;
diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx
index 802178c..12129b3 100644
--- a/frontend/src/components/Header.jsx
+++ b/frontend/src/components/Header.jsx
@@ -85,14 +85,29 @@ const Header = ({ total }) => {
max 300
-
+
- | Premiacoes | Base | Teto |
+ | Premiacoes | B | T | Autor | Orient | Coorient |
- | PREMIACAO_GP | 100 | 300 |
- | PREMIACAO | 50 | 150 |
- | MENCAO | 30 | 90 |
+
+ | GP | 100 | 300 |
+ 🏆 |
+ 🏆 |
+ 🏆 |
+
+
+ | PREMIO | 50 | 150 |
+ 🥇 |
+ 🎖️ |
+ 🎖️ |
+
+
+ | MENCAO | 30 | 90 |
+ 🥈 |
+ 📜 |
+ 📜 |
+
@@ -120,61 +135,59 @@ const Header = ({ total }) => {
-
+
C - Orientacoes
selos
-
- | Orientacao | B | T |
+
+ | Orientacao | Selo |
- | POS_DOC | 0 | 0 |
- | TESE | 0 | 0 |
- | DISS | 0 | 0 |
+ | POS_DOC | 🔬 |
+ | TESE | 📚 |
+ | DISS | 📄 |
-
- | Co-Orient | B | T |
+
+ | Co-Orient | Selo |
- | POS_DOC | 0 | 0 |
- | TESE | 0 | 0 |
- | DISS | 0 | 0 |
+ | POS_DOC | 🔬 |
+ | TESE | 📚 |
+ | DISS | 📄 |
- +versoes _PREM | Selos no V1
-
+
C - Bancas
selos
-
- | Membro Banca | Base | Teto |
+
+ | Membro Banca | Selo |
- | MB_POS_DOC | 0 | 0 |
- | MB_TESE | 0 | 0 |
- | MB_DISS | 0 | 0 |
+ | MB_POS_DOC | 🔬 |
+ | MB_TESE | 📚 |
+ | MB_DISS | 📄 |
- +versoes _PREM | Selos no V1
-
+
D - Indicadores
selos
-
- | Codigo | Base | Teto |
+
+ | Codigo | B | T | Selo |
- | BOL_BPQ | 30 | 60 |
- | PROJ | 10 | 30 |
- | EVENTO | 1 | 5 |
- | IDIOMA | 0 | 0 |
- | TITULACAO | 0 | 0 |
+ | BOL_BPQ | 30 | 60 | 🏅 |
+ | PROJ | 10 | 30 | 📁 |
+ | EVENTO | 1 | 5 | 📅 |
+ | PPG_COORD | - | - | 🎓 |
+ | PRES_CAM | - | - | 👑 |
diff --git a/frontend/src/components/SugerirConsultores.css b/frontend/src/components/SugerirConsultores.css
index 145e98e..7eb02cc 100644
--- a/frontend/src/components/SugerirConsultores.css
+++ b/frontend/src/components/SugerirConsultores.css
@@ -277,7 +277,7 @@
.sugerir-rank {
font-size: 0.8rem;
font-weight: 700;
- color: var(--accent);
+ color: #ebeaff;
min-width: 2rem;
}
@@ -286,6 +286,31 @@
font-weight: 600;
color: var(--white);
font-size: 0.95rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.sugerir-nome .link-atuacapes {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ font-size: 0.8rem;
+ color: #94a3b8;
+ background: rgba(255, 255, 255, 0.06);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 5px;
+ text-decoration: none;
+ transition: all 200ms ease;
+}
+
+.sugerir-nome .link-atuacapes:hover {
+ color: #a5b4fc;
+ background: rgba(79, 70, 229, 0.2);
+ border-color: rgba(165, 180, 252, 0.4);
+ transform: scale(1.1);
}
.sugerir-badges {
@@ -338,12 +363,12 @@
align-items: center;
gap: 0.3rem;
font-size: 0.8rem;
- color: #22d3ee;
+ color: #67e8f9;
font-weight: 600;
- background: rgba(34, 211, 238, 0.15);
+ background: rgba(34, 211, 238, 0.2);
padding: 0.25rem 0.6rem;
border-radius: 6px;
- border: 1px solid rgba(34, 211, 238, 0.3);
+ border: 1px solid rgba(103, 232, 249, 0.4);
}
.stat-icon {
diff --git a/frontend/src/components/SugerirConsultores.jsx b/frontend/src/components/SugerirConsultores.jsx
index 02181ec..5b358a8 100644
--- a/frontend/src/components/SugerirConsultores.jsx
+++ b/frontend/src/components/SugerirConsultores.jsx
@@ -245,7 +245,21 @@ const SugerirConsultores = ({ onClose, onSelectConsultor }) => {
/>
#{index + 1}
- {c.nome}
+
+ {c.nome}
+ {import.meta.env.VITE_HOST_ATUACAPES && c.id_pessoa && (
+ e.stopPropagation()}
+ title="Ver perfil no ATUACAPES"
+ >
+ ↗
+
+ )}
+
{c.foi_coordenador && CA}
{c.foi_premiado && P}
diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js
index 2ba5c6c..45eedc8 100644
--- a/frontend/src/services/api.js
+++ b/frontend/src/services/api.js
@@ -53,6 +53,7 @@ export const rankingService = {
ativo: c.ativo,
anos_atuacao: anos,
veterano: anos >= 10,
+ tipos_atuacao: c.tipos_atuacao || [],
coordenador_ppg: Boolean(c.coordenador_ppg),
pontuacao: c.pontuacao || {
pontuacao_total: c.pontuacao_total,