diff --git a/backend/scripts/popular_selos.py b/backend/scripts/popular_selos.py new file mode 100644 index 0000000..fd7a90b --- /dev/null +++ b/backend/scripts/popular_selos.py @@ -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() diff --git a/backend/sql/migrations/v1.2_add_selos_column.sql b/backend/sql/migrations/v1.2_add_selos_column.sql new file mode 100644 index 0000000..476bb06 --- /dev/null +++ b/backend/sql/migrations/v1.2_add_selos_column.sql @@ -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'; diff --git a/backend/src/infrastructure/oracle/ranking_repository.py b/backend/src/infrastructure/oracle/ranking_repository.py index 25f6d93..3fd5766 100644 --- a/backend/src/infrastructure/oracle/ranking_repository.py +++ b/backend/src/infrastructure/oracle/ranking_repository.py @@ -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) diff --git a/backend/src/infrastructure/ranking_store.py b/backend/src/infrastructure/ranking_store.py index fd2d67e..07b806d 100644 --- a/backend/src/infrastructure/ranking_store.py +++ b/backend/src/infrastructure/ranking_store.py @@ -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 diff --git a/backend/src/interface/api/routes.py b/backend/src/interface/api/routes.py index cab1546..9854a60 100644 --- a/backend/src/interface/api/routes.py +++ b/backend/src/interface/api/routes.py @@ -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 = [] diff --git a/frontend/src/components/ConsultorCard.css b/frontend/src/components/ConsultorCard.css index 00aaa83..a192b2c 100644 --- a/frontend/src/components/ConsultorCard.css +++ b/frontend/src/components/ConsultorCard.css @@ -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); diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index 7d5db6b..c76b89f 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -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 }) => { diff --git a/frontend/src/components/FiltroSelos.jsx b/frontend/src/components/FiltroSelos.jsx index 3d63f50..fb1a996 100644 --- a/frontend/src/components/FiltroSelos.jsx +++ b/frontend/src/components/FiltroSelos.jsx @@ -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: '🌐' }, ], }, };