feat(selos): implementar sistema completo de selos para consultores

- Adicionar coluna SELOS na tabela TB_RANKING_CONSULTOR (migration v1.2)
- Criar script popular_selos.py para popular/atualizar selos em batch
- Simplificar para 22 selos com dados reais (remover selos sem dados)
- Adicionar selo IDIOMA_MULTILINGUE para consultores com 3+ idiomas
- Corrigir filtro de selos com query LIKE exata (evitar matches parciais)
- Alinhar ícones entre FiltroSelos e ConsultorCard
- Reorganizar filtro em 7 categorias: Coordenação, Consultoria, Avaliações,
  Premiações, Orientações, Participações, Características

Selos disponíveis: CA, CAJ, CAJ_MP, CAM, PRESID_CAMARA, CONS_ATIVO,
AVAL_COMIS, COORD_COMIS, AUTOR_GP, AUTOR_PREMIO, AUTOR_MENCAO,
ORIENT_GP, ORIENT_PREMIO, ORIENT_MENCAO, COORIENT_GP, COORIENT_PREMIO,
COORIENT_MENCAO, ORIENT_TESE, ORIENT_DISS, EVENTO, PROJ, IDIOMA_MULTILINGUE
This commit is contained in:
Frederico Castro
2025-12-27 21:08:22 -03:00
parent edb4e00880
commit 13ccfb02d3
8 changed files with 361 additions and 130 deletions

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""
Script para popular a coluna SELOS na TB_RANKING_CONSULTOR.
Lê JSON_DETALHES, extrai selos e atualiza a coluna.
"""
import os
import sys
import json
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from dotenv import load_dotenv
load_dotenv(os.path.join(os.path.dirname(__file__), "..", ".env"))
from infrastructure.oracle.client import OracleClient
from infrastructure.ranking_store import extrair_selos_entry
def main():
user = os.getenv("ORACLE_LOCAL_USER")
password = os.getenv("ORACLE_LOCAL_PASSWORD")
dsn = os.getenv("ORACLE_LOCAL_DSN", "localhost:1521/XEPDB1")
print(f"Conectando ao Oracle: {dsn}")
client = OracleClient(user=user, password=password, dsn=dsn)
client.connect()
recalcular_todos = "--all" in sys.argv
if recalcular_todos:
query = """
SELECT ID_PESSOA, JSON_DETALHES
FROM TB_RANKING_CONSULTOR
"""
print("Re-calculando TODOS os selos...")
else:
query = """
SELECT ID_PESSOA, JSON_DETALHES
FROM TB_RANKING_CONSULTOR
WHERE SELOS IS NULL
"""
print("Buscando consultores sem selos...")
results = client.executar_query(query)
print(f"Encontrados {len(results)} registros para atualizar")
batch_size = 500
batch = []
updated = 0
for r in results:
id_pessoa = r["ID_PESSOA"]
json_det = r["JSON_DETALHES"]
if hasattr(json_det, "read"):
json_det = json_det.read()
try:
detalhes = json.loads(json_det) if json_det else {}
except (json.JSONDecodeError, TypeError):
detalhes = {}
selos_set = extrair_selos_entry(detalhes)
selos_str = ",".join(sorted(selos_set)) if selos_set else None
batch.append({"id_pessoa": id_pessoa, "selos": selos_str})
if len(batch) >= batch_size:
_update_batch(client, batch)
updated += len(batch)
print(f" Atualizados: {updated}")
batch = []
if batch:
_update_batch(client, batch)
updated += len(batch)
print(f" Atualizados: {updated}")
print(f"Total de registros atualizados: {updated}")
def _update_batch(client, batch):
update_sql = """
UPDATE TB_RANKING_CONSULTOR
SET SELOS = :selos
WHERE ID_PESSOA = :id_pessoa
"""
with client.get_connection() as conn:
cursor = conn.cursor()
try:
cursor.executemany(update_sql, batch)
conn.commit()
finally:
cursor.close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,10 @@
-- Migration v1.2: Adicionar coluna SELOS para filtragem
-- Data: 2025-12-27
ALTER TABLE TB_RANKING_CONSULTOR ADD (
SELOS VARCHAR2(500) DEFAULT NULL
);
CREATE INDEX IDX_RANKING_SELOS ON TB_RANKING_CONSULTOR(SELOS);
COMMENT ON COLUMN TB_RANKING_CONSULTOR.SELOS IS 'Lista de selos do consultor separados por virgula para filtragem';

View File

@@ -1,9 +1,10 @@
from typing import List, Optional, Dict, Any
from typing import List, Optional, Dict, Any, Set
from datetime import datetime
import json
from ...domain.entities.consultor_ranking import ConsultorRanking
from .client import OracleClient
from ..ranking_store import extrair_selos_entry
class RankingOracleRepository:
@@ -24,17 +25,19 @@ class RankingOracleRepository:
INSERT INTO TB_RANKING_CONSULTOR (
ID_PESSOA, NOME, PONTUACAO_TOTAL,
COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D, COMPONENTE_E,
ATIVO, ANOS_ATUACAO, JSON_DETALHES, DT_CALCULO
ATIVO, ANOS_ATUACAO, JSON_DETALHES, SELOS, DT_CALCULO
) VALUES (
:id_pessoa, :nome, :pontuacao_total,
:componente_a, :componente_b, :componente_c, :componente_d, :componente_e,
:ativo, :anos_atuacao, :json_detalhes, CURRENT_TIMESTAMP
:ativo, :anos_atuacao, :json_detalhes, :selos, CURRENT_TIMESTAMP
)
"""
batch_data = []
for consultor in consultores:
json_str = json.dumps(consultor, ensure_ascii=False)
selos_set = extrair_selos_entry(consultor)
selos_str = ",".join(sorted(selos_set)) if selos_set else None
batch_data.append({
"id_pessoa": int(consultor["id_pessoa"]),
"nome": str(consultor.get("nome", ""))[:500],
@@ -46,7 +49,8 @@ class RankingOracleRepository:
"componente_e": int(consultor.get("bloco_e") or consultor.get("componente_e") or 0),
"ativo": "S" if consultor.get("ativo") else "N",
"anos_atuacao": float(consultor.get("anos_atuacao") or 0),
"json_detalhes": json_str
"json_detalhes": json_str,
"selos": selos_str
})
with self.client.get_connection() as conn:
@@ -66,7 +70,8 @@ class RankingOracleRepository:
self,
page: int = 1,
size: int = 50,
filtro_ativo: Optional[bool] = None
filtro_ativo: Optional[bool] = None,
filtro_selos: Optional[List[str]] = None
) -> List[ConsultorRanking]:
"""
Busca ranking paginado ordenado por posição.
@@ -79,16 +84,26 @@ class RankingOracleRepository:
offset = (page - 1) * size
limit_end = offset + size
where_clause = ""
where_clauses = []
params = {
"offset": offset,
"limit_end": limit_end,
}
if filtro_ativo is not None:
where_clause = "AND ATIVO = :ativo"
where_clauses.append("ATIVO = :ativo")
params["ativo"] = "S" if filtro_ativo else "N"
if filtro_selos:
for i, selo in enumerate(filtro_selos):
param_name = f"selo_{i}"
where_clauses.append(f"((',' || SELOS || ',') LIKE '%,' || :{param_name} || ',%')")
params[param_name] = selo
where_clause = ""
if where_clauses:
where_clause = "AND " + " AND ".join(where_clauses)
query = f"""
SELECT * FROM (
SELECT
@@ -142,17 +157,31 @@ class RankingOracleRepository:
return consultores
def contar_total(self, filtro_ativo: Optional[bool] = None) -> int:
def contar_total(
self,
filtro_ativo: Optional[bool] = None,
filtro_selos: Optional[List[str]] = None
) -> int:
"""
Conta total de consultores no ranking.
"""
where_clause = ""
where_clauses = []
params = {}
if filtro_ativo is not None:
where_clause = "WHERE ATIVO = :ativo"
where_clauses.append("ATIVO = :ativo")
params["ativo"] = "S" if filtro_ativo else "N"
if filtro_selos:
for i, selo in enumerate(filtro_selos):
param_name = f"selo_{i}"
where_clauses.append(f"((',' || SELOS || ',') LIKE '%,' || :{param_name} || ',%')")
params[param_name] = selo
where_clause = ""
if where_clauses:
where_clause = "WHERE " + " AND ".join(where_clauses)
query = f"SELECT COUNT(*) AS TOTAL FROM TB_RANKING_CONSULTOR {where_clause}"
results = self.client.executar_query(query, params)

View File

@@ -7,9 +7,14 @@ from typing import Any, Dict, List, Optional, Set, Tuple
SELOS_DISPONIVEIS = [
"CA",
"CAJ",
"CAJ_MP",
"CAM",
"PRESID_CAMARA",
"COORD_PPG",
"BPQ",
"CONS_ATIVO",
"AVAL_COMIS",
"COORD_COMIS",
"AUTOR_GP",
"AUTOR_PREMIO",
"AUTOR_MENCAO",
@@ -19,12 +24,11 @@ SELOS_DISPONIVEIS = [
"COORIENT_GP",
"COORIENT_PREMIO",
"COORIENT_MENCAO",
"ORIENT_POS_DOC",
"ORIENT_TESE",
"ORIENT_DISS",
"CO_ORIENT_POS_DOC",
"CO_ORIENT_TESE",
"CO_ORIENT_DISS",
"EVENTO",
"PROJ",
"IDIOMA_MULTILINGUE",
]
@@ -32,46 +36,75 @@ def extrair_selos_entry(detalhes: Dict[str, Any]) -> Set[str]:
selos = set()
for c in detalhes.get("coordenacoes_capes", []):
codigo = c.get("codigo", "")
if codigo == "CA":
selos.add("CA")
elif codigo == "CAJ":
selos.add("CAJ")
elif codigo == "CAJ_MP":
selos.add("CAJ_MP")
elif codigo == "CAM":
selos.add("CAM")
if c.get("presidente"):
selos.add("PRESID_CAMARA")
if detalhes.get("coordenador_ppg"):
selos.add("COORD_PPG")
consultoria = detalhes.get("consultoria")
if consultoria and consultoria.get("codigo") == "CONS_ATIVO":
selos.add("CONS_ATIVO")
if detalhes.get("bolsas_cnpq"):
selos.add("BPQ")
for aval in detalhes.get("avaliacoes_comissao", []):
codigo = aval.get("codigo", "")
if "COORD" in codigo:
selos.add("COORD_COMIS")
elif "AVAL" in codigo:
selos.add("AVAL_COMIS")
for part in detalhes.get("participacoes", []):
codigo = part.get("codigo", "")
if codigo == "EVENTO":
selos.add("EVENTO")
elif codigo == "PROJ":
selos.add("PROJ")
for prem in detalhes.get("premiacoes", []):
codigo = prem.get("codigo", "")
papel = prem.get("papel", "").lower() if prem.get("papel") else ""
is_orientador = "orientador" in papel
is_coorientador = "coorientador" in papel or "co-orientador" in papel
if codigo == "PREMIACAO_GP_AUTOR":
selos.add("AUTOR_GP")
if is_coorientador:
selos.add("COORIENT_GP")
elif is_orientador:
selos.add("ORIENT_GP")
else:
selos.add("AUTOR_GP")
elif codigo == "PREMIACAO_AUTOR":
selos.add("AUTOR_PREMIO")
if is_coorientador:
selos.add("COORIENT_PREMIO")
elif is_orientador:
selos.add("ORIENT_PREMIO")
else:
selos.add("AUTOR_PREMIO")
elif codigo == "MENCAO_AUTOR":
selos.add("AUTOR_MENCAO")
if is_coorientador:
selos.add("COORIENT_MENCAO")
elif is_orientador:
selos.add("ORIENT_MENCAO")
else:
selos.add("AUTOR_MENCAO")
for orient in detalhes.get("orientacoes", []):
codigo = orient.get("codigo", "")
is_coorient = orient.get("coorientacao", False)
if is_coorient:
if codigo in ("CO_ORIENT_POS_DOC", "CO_ORIENT_POS_DOC_PREM"):
selos.add("CO_ORIENT_POS_DOC")
elif codigo in ("CO_ORIENT_TESE", "CO_ORIENT_TESE_PREM"):
selos.add("CO_ORIENT_TESE")
elif codigo in ("CO_ORIENT_DISS", "CO_ORIENT_DISS_PREM"):
selos.add("CO_ORIENT_DISS")
else:
if codigo in ("ORIENT_POS_DOC", "ORIENT_POS_DOC_PREM"):
selos.add("ORIENT_POS_DOC")
elif codigo in ("ORIENT_TESE", "ORIENT_TESE_PREM"):
selos.add("ORIENT_TESE")
elif codigo in ("ORIENT_DISS", "ORIENT_DISS_PREM"):
selos.add("ORIENT_DISS")
if codigo in ("ORIENT_TESE", "ORIENT_TESE_PREM"):
selos.add("ORIENT_TESE")
elif codigo in ("ORIENT_DISS", "ORIENT_DISS_PREM"):
selos.add("ORIENT_DISS")
if orient.get("premiada") or "_PREM" in codigo:
prem_tipo = orient.get("premiacao_tipo", "")
is_coorient = orient.get("coorientacao", False)
if is_coorient:
if prem_tipo == "GP":
selos.add("COORIENT_GP")
@@ -87,6 +120,10 @@ def extrair_selos_entry(detalhes: Dict[str, Any]) -> Set[str]:
elif prem_tipo == "MENCAO":
selos.add("ORIENT_MENCAO")
idiomas = detalhes.get("idiomas", [])
if len(idiomas) >= 3:
selos.add("IDIOMA_MULTILINGUE")
return selos

View File

@@ -214,14 +214,20 @@ async def ranking_paginado(
if not oracle_repo:
raise HTTPException(status_code=503, detail="Oracle não configurado")
total = oracle_repo.contar_total(filtro_ativo=ativo)
selos_lista = [s.strip() for s in selos.split(",") if s.strip()] if selos else None
total = oracle_repo.contar_total(filtro_ativo=ativo, filtro_selos=selos_lista)
if total == 0:
if selos_lista:
return RankingPaginadoResponseSchema(
total=0, page=page, size=size, total_pages=0, consultores=[]
)
raise HTTPException(
status_code=503,
detail="Ranking ainda não foi processado. Execute POST /api/v1/ranking/processar.",
)
consultores = oracle_repo.buscar_paginado(page=page, size=size, filtro_ativo=ativo)
consultores = oracle_repo.buscar_paginado(page=page, size=size, filtro_ativo=ativo, filtro_selos=selos_lista)
total_pages = (total + size - 1) // size
consultores_schema = []

View File

@@ -856,6 +856,54 @@
padding: 0.1rem 0.2rem;
}
.selo-ca {
background: linear-gradient(135deg, rgba(220, 38, 38, 0.3), rgba(220, 38, 38, 0.15));
border-color: rgba(220, 38, 38, 0.5);
color: #fca5a5;
}
.selo-caj {
background: linear-gradient(135deg, rgba(234, 88, 12, 0.3), rgba(234, 88, 12, 0.15));
border-color: rgba(234, 88, 12, 0.5);
color: #fdba74;
}
.selo-caj-mp {
background: linear-gradient(135deg, rgba(202, 138, 4, 0.3), rgba(202, 138, 4, 0.15));
border-color: rgba(202, 138, 4, 0.5);
color: #fcd34d;
}
.selo-cam {
background: linear-gradient(135deg, rgba(147, 51, 234, 0.3), rgba(147, 51, 234, 0.15));
border-color: rgba(147, 51, 234, 0.5);
color: #d8b4fe;
}
.selo-cons-ativo {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.3), rgba(34, 197, 94, 0.15));
border-color: rgba(34, 197, 94, 0.5);
color: #86efac;
}
.selo-cons-hist {
background: linear-gradient(135deg, rgba(100, 116, 139, 0.3), rgba(100, 116, 139, 0.15));
border-color: rgba(100, 116, 139, 0.5);
color: #cbd5e1;
}
.selo-aval {
background: linear-gradient(135deg, rgba(6, 182, 212, 0.3), rgba(6, 182, 212, 0.15));
border-color: rgba(6, 182, 212, 0.5);
color: #67e8f9;
}
.selo-coord-comis {
background: linear-gradient(135deg, rgba(79, 70, 229, 0.3), rgba(79, 70, 229, 0.15));
border-color: rgba(79, 70, 229, 0.5);
color: #a5b4fc;
}
.selo-coord {
background: linear-gradient(135deg, rgba(139, 92, 246, 0.25), rgba(139, 92, 246, 0.1));
border-color: rgba(139, 92, 246, 0.4);

View File

@@ -5,33 +5,28 @@ 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: '🏅' },
CA: { codigo: 'CA', label: 'Coord. Área', cor: 'selo-ca', icone: '🎯' },
CAJ: { codigo: 'CAJ', label: 'Coord. Adjunto', cor: 'selo-caj', icone: '📋' },
CAJ_MP: { codigo: 'CAJ_MP', label: 'Coord. Adj. MP', cor: 'selo-caj-mp', icone: '📋' },
CAM: { codigo: 'CAM', label: 'Câmara Temática', cor: 'selo-cam', icone: '🏛️' },
PRESID_CAMARA: { codigo: 'PRESID_CAMARA', label: 'Presidente Câmara', cor: 'selo-camara', icone: '👑' },
CONS_ATIVO: { codigo: 'CONS_ATIVO', label: 'Consultor', cor: 'selo-cons-ativo', icone: '✅' },
AVAL_COMIS: { codigo: 'AVAL_COMIS', label: 'Avaliador', cor: 'selo-aval', icone: '⚖️' },
COORD_COMIS: { codigo: 'COORD_COMIS', label: 'Coord. Comissão', cor: 'selo-coord-comis', 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: '🥈' },
AUTOR_PREMIO: { codigo: 'AUTOR_PREMIO', label: 'Autor Prêmio', cor: 'selo-premio', icone: '🥇' },
AUTOR_MENCAO: { codigo: 'AUTOR_MENCAO', label: 'Autor Menção', 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: '📜' },
ORIENT_PREMIO: { codigo: 'ORIENT_PREMIO', label: 'Orient. Prêmio', cor: 'selo-orient-premio', icone: '🎖️' },
ORIENT_MENCAO: { codigo: 'ORIENT_MENCAO', label: 'Orient. Menção', 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: '📜' },
COORIENT_PREMIO: { codigo: 'COORIENT_PREMIO', label: 'Coorient. Prêmio', cor: 'selo-coorient-premio', icone: '🎖️' },
COORIENT_MENCAO: { codigo: 'COORIENT_MENCAO', label: 'Coorient. Menção', 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_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: '🎓' },
EVENTO: { codigo: 'EVENTO', label: 'Evento', cor: 'selo-evento', icone: '🎪' },
PROJ: { codigo: 'PROJ', label: 'Projeto', cor: 'selo-proj', icone: '🔧' },
IDIOMA_MULTILINGUE: { codigo: 'IDIOMA_MULTILINGUE', label: 'Multilíngue', cor: 'selo-idioma', icone: '🌐' },
};
const TIPOS_ATUACAO_CONFIG = {
@@ -54,30 +49,46 @@ const gerarSelos = (consultor) => {
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.trim();
const normalizarCodigoIdioma = (valor) => normalizarIdioma(valor).replace(/[^a-z0-9]/g, '');
const isPresidCamaraVigente = consultor.coordenacoes_capes?.some(
const coordenacoes = Array.isArray(consultor.coordenacoes_capes) ? consultor.coordenacoes_capes : [];
const coordCA = coordenacoes.filter((c) => c.codigo === 'CA');
const coordCAJ = coordenacoes.filter((c) => c.codigo === 'CAJ');
const coordCAJ_MP = coordenacoes.filter((c) => c.codigo === 'CAJ_MP');
const coordCAM = coordenacoes.filter((c) => c.codigo === 'CAM');
if (coordCA.length > 0) {
selos.push({ ...SELOS.CA, qtd: coordCA.length, hint: `Coordenador de Área (${coordCA.length}x)` });
}
if (coordCAJ.length > 0) {
selos.push({ ...SELOS.CAJ, qtd: coordCAJ.length, hint: `Coordenador Adjunto (${coordCAJ.length}x)` });
}
if (coordCAJ_MP.length > 0) {
selos.push({ ...SELOS.CAJ_MP, qtd: coordCAJ_MP.length, hint: `Coord. Adjunto MP (${coordCAJ_MP.length}x)` });
}
if (coordCAM.length > 0) {
selos.push({ ...SELOS.CAM, qtd: coordCAM.length, hint: `Câmara Temática (${coordCAM.length}x)` });
}
const isPresidCamaraVigente = coordenacoes.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 consultoria = consultor.consultoria;
if (consultoria && consultoria.codigo === 'CONS_ATIVO') {
selos.push({ ...SELOS.CONS_ATIVO, qtd: 1, hint: 'Consultor Ativo' });
}
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 avaliacoes = Array.isArray(consultor.avaliacoes_comissao) ? consultor.avaliacoes_comissao : [];
const coordComissao = avaliacoes.filter((a) => (a.codigo || '').includes('COORD'));
const avalComissao = avaliacoes.filter((a) => (a.codigo || '').includes('AVAL'));
if (coordComissao.length > 0) {
selos.push({ ...SELOS.COORD_COMIS, qtd: coordComissao.length, hint: `Coord. Comissão (${coordComissao.length}x)` });
}
if (avalComissao.length > 0) {
selos.push({ ...SELOS.AVAL_COMIS, qtd: avalComissao.length, hint: `Avaliador Comissão (${avalComissao.length}x)` });
}
const premiacoes = Array.isArray(consultor.premiacoes) ? consultor.premiacoes : [];
@@ -98,34 +109,13 @@ const gerarSelos = (consultor) => {
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)` });
const orientTese = orientacoes.filter((o) => o.codigo === 'ORIENT_TESE' || o.codigo === 'ORIENT_TESE_PREM');
const orientDiss = orientacoes.filter((o) => o.codigo === 'ORIENT_DISS' || o.codigo === 'ORIENT_DISS_PREM');
if (orientTese.length > 0) {
selos.push({ ...SELOS.ORIENT_TESE, qtd: orientTese.length, hint: `Orient. Tese (${orientTese.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)` });
if (orientDiss.length > 0) {
selos.push({ ...SELOS.ORIENT_DISS, qtd: orientDiss.length, hint: `Orient. Dissertação (${orientDiss.length}x)` });
}
const participacoes = Array.isArray(consultor.participacoes) ? consultor.participacoes : [];
@@ -162,30 +152,19 @@ const gerarSelos = (consultor) => {
});
}
const titulacao = consultor.titulacao || '';
const titulacaoLower = titulacao.toLowerCase();
if (titulacaoLower.includes('pós-doutorado') || titulacaoLower.includes('pos-doutorado') || titulacaoLower.includes('posdoc') || titulacaoLower.includes('pós-doc')) {
selos.push({ ...SELOS.TITULACAO_POS_DOUTOR, qtd: 1, hint: 'Pós-Doutorado' });
} else if (titulacaoLower.includes('doutorado') || titulacaoLower.includes('doutor')) {
selos.push({ ...SELOS.TITULACAO_DOUTOR, qtd: 1, hint: 'Doutorado' });
} else if (titulacaoLower.includes('mestrado') || titulacaoLower.includes('mestre')) {
selos.push({ ...SELOS.TITULACAO_MESTRE, qtd: 1, hint: 'Mestrado' });
}
return selos;
};
const SELOS_COM_DADOS = [
'PRESID_CAMARA', 'COORD_PPG', 'BPQ',
'CA', 'CAJ', 'CAJ_MP', 'CAM', 'PRESID_CAMARA',
'CONS_ATIVO',
'AVAL_COMIS', 'COORD_COMIS',
'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',
'ORIENT_TESE', 'ORIENT_DISS',
'EVENTO', 'PROJ',
'IDIOMA_MULTILINGUE',
'TITULACAO_MESTRE', 'TITULACAO_DOUTOR', 'TITULACAO_POS_DOUTOR',
];
const SelosBadges = ({ selos, compacto = false, onSeloClick }) => {

View File

@@ -2,12 +2,27 @@ import { useState, useRef, useEffect } from 'react';
import './FiltroSelos.css';
const SELOS_CONFIG = {
funcoes: {
label: 'Funções',
coordenacao: {
label: 'Coordenação CAPES',
selos: [
{ codigo: 'CA', label: 'Coord. Área', icone: '🎯' },
{ codigo: 'CAJ', label: 'Coord. Adjunto', icone: '📋' },
{ codigo: 'CAJ_MP', label: 'Coord. Adj. MP', icone: '📋' },
{ codigo: 'CAM', label: 'Câmara Temática', icone: '🏛️' },
{ codigo: 'PRESID_CAMARA', label: 'Presidente Câmara', icone: '👑' },
{ codigo: 'COORD_PPG', label: 'Coord. PPG', icone: '🎓' },
{ codigo: 'BPQ', label: 'Bolsista PQ', icone: '🏅' },
],
},
consultoria: {
label: 'Consultoria',
selos: [
{ codigo: 'CONS_ATIVO', label: 'Consultor Ativo', icone: '✅' },
],
},
avaliacoes: {
label: 'Avaliações',
selos: [
{ codigo: 'AVAL_COMIS', label: 'Avaliador Comissão', icone: '⚖️' },
{ codigo: 'COORD_COMIS', label: 'Coord. Comissão', icone: '📊' },
],
},
premiacoes: {
@@ -27,12 +42,21 @@ const SELOS_CONFIG = {
orientacoes: {
label: 'Orientações',
selos: [
{ codigo: 'ORIENT_POS_DOC', label: 'Pós-Doc', icone: '🔬' },
{ codigo: 'ORIENT_TESE', label: 'Tese', icone: '📚' },
{ codigo: 'ORIENT_DISS', label: 'Dissertação', icone: '📄' },
{ codigo: 'CO_ORIENT_POS_DOC', label: 'Co-orient. Pós-Doc', icone: '🔬' },
{ codigo: 'CO_ORIENT_TESE', label: 'Co-orient. Tese', icone: '📚' },
{ codigo: 'CO_ORIENT_DISS', label: 'Co-orient. Diss.', icone: '📄' },
{ codigo: 'ORIENT_TESE', label: 'Orient. Tese', icone: '📚' },
{ codigo: 'ORIENT_DISS', label: 'Orient. Dissertação', icone: '📄' },
],
},
participacoes: {
label: 'Participações',
selos: [
{ codigo: 'EVENTO', label: 'Evento', icone: '🎪' },
{ codigo: 'PROJ', label: 'Projeto', icone: '🔧' },
],
},
caracteristicas: {
label: 'Características',
selos: [
{ codigo: 'IDIOMA_MULTILINGUE', label: 'Multilíngue (3+)', icone: '🌐' },
],
},
};