From 99ce6e30d8f89c2fb321ad8eca6fdf0190c77c21 Mon Sep 17 00:00:00 2001 From: Frederico Castro Date: Wed, 17 Dec 2025 20:48:50 -0300 Subject: [PATCH] =?UTF-8?q?feat(vinculos):=20adicionar=20v=C3=ADnculos=20d?= =?UTF-8?q?e=20consultoria=20com=20IES=20e=20ordena=C3=A7=C3=A3o=20cronol?= =?UTF-8?q?=C3=B3gica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adicionar entidades e DTOs para vínculos de consultoria (IES, período, situação) - Extrair vínculos do Elasticsearch com datas e informações da IES - Exibir vínculos no card do consultor com sigla e nome completo da IES - Ordenar todas as listas do detalhe por data/ano decrescente (mais recente primeiro) --- backend/src/application/dtos/consultor_dto.py | 19 +++++++ .../src/application/jobs/processar_ranking.py | 16 +++++- .../application/use_cases/obter_ranking.py | 19 +++++++ backend/src/domain/entities/consultor.py | 15 ++++++ .../repositories/consultor_repository_impl.py | 24 ++++++++- .../src/interface/schemas/consultor_schema.py | 13 +++++ frontend/src/components/ConsultorCard.css | 7 +++ frontend/src/components/ConsultorCard.jsx | 52 +++++++++++++++++-- frontend/src/services/api.js | 9 ++++ 9 files changed, 167 insertions(+), 7 deletions(-) diff --git a/backend/src/application/dtos/consultor_dto.py b/backend/src/application/dtos/consultor_dto.py index 3f9d664..e2e759c 100644 --- a/backend/src/application/dtos/consultor_dto.py +++ b/backend/src/application/dtos/consultor_dto.py @@ -21,6 +21,20 @@ class CoordenacaoCapesDTO: presidente: bool +@dataclass +class IESDTO: + id: str + nome: str + sigla: Optional[str] = None + + +@dataclass +class VinculoConsultoriaDTO: + periodo: PeriodoDTO + ies: Optional[IESDTO] = None + situacao: str = "" + + @dataclass class ConsultoriaDTO: codigo: str @@ -29,6 +43,11 @@ class ConsultoriaDTO: areas: List[str] anos_consecutivos: int retornos: int + vinculos: List[VinculoConsultoriaDTO] = None + + def __post_init__(self): + if self.vinculos is None: + self.vinculos = [] @dataclass diff --git a/backend/src/application/jobs/processar_ranking.py b/backend/src/application/jobs/processar_ranking.py index cd29a7b..913173c 100644 --- a/backend/src/application/jobs/processar_ranking.py +++ b/backend/src/application/jobs/processar_ranking.py @@ -131,7 +131,21 @@ class ProcessarRankingJob: "fim": consultor.consultoria.periodo.fim.isoformat() if consultor.consultoria.periodo.fim else None, "areas": consultor.consultoria.areas, "anos_consecutivos": consultor.consultoria.anos_consecutivos, - "retornos": consultor.consultoria.retornos + "retornos": consultor.consultoria.retornos, + "vinculos": [ + { + "inicio": v.periodo.inicio.isoformat() if v.periodo.inicio else None, + "fim": v.periodo.fim.isoformat() if v.periodo.fim else None, + "ativo": v.periodo.ativo, + "situacao": v.situacao, + "ies": { + "id": v.ies.id, + "nome": v.ies.nome, + "sigla": v.ies.sigla, + } if v.ies else None, + } + for v in consultor.consultoria.vinculos + ], } if consultor.consultoria else None, "inscricoes": [ { diff --git a/backend/src/application/use_cases/obter_ranking.py b/backend/src/application/use_cases/obter_ranking.py index 2856686..20a2d86 100644 --- a/backend/src/application/use_cases/obter_ranking.py +++ b/backend/src/application/use_cases/obter_ranking.py @@ -8,6 +8,8 @@ from ..dtos.consultor_dto import ( PeriodoDTO, CoordenacaoCapesDTO, ConsultoriaDTO, + IESDTO, + VinculoConsultoriaDTO, InscricaoDTO, AvaliacaoComissaoDTO, PremiacaoDTO, @@ -91,6 +93,23 @@ class ObterRankingUseCase: areas=consultor.consultoria.areas, anos_consecutivos=consultor.consultoria.anos_consecutivos, retornos=consultor.consultoria.retornos, + vinculos=[ + VinculoConsultoriaDTO( + periodo=PeriodoDTO( + inicio=v.periodo.inicio.isoformat() if v.periodo.inicio else "", + fim=v.periodo.fim.isoformat() if v.periodo.fim else None, + ativo=v.periodo.ativo, + anos_decorridos=v.periodo.anos_decorridos, + ), + ies=IESDTO( + id=v.ies.id, + nome=v.ies.nome, + sigla=v.ies.sigla, + ) if v.ies else None, + situacao=v.situacao, + ) + for v in consultor.consultoria.vinculos + ], ) if consultor.consultoria else None, inscricoes=[ InscricaoDTO( diff --git a/backend/src/domain/entities/consultor.py b/backend/src/domain/entities/consultor.py index 9afc453..bc770e5 100644 --- a/backend/src/domain/entities/consultor.py +++ b/backend/src/domain/entities/consultor.py @@ -17,12 +17,27 @@ class CoordenacaoCapes: presidente: bool = False +@dataclass +class IES: + id: str + nome: str + sigla: Optional[str] = None + + +@dataclass +class VinculoConsultoria: + periodo: Periodo + ies: Optional[IES] = None + situacao: str = "" + + @dataclass class Consultoria: codigo: str situacao: str periodo: Periodo periodos: List[Periodo] = field(default_factory=list) + vinculos: List[VinculoConsultoria] = field(default_factory=list) areas: List[str] = field(default_factory=list) anos_consecutivos: int = 0 retornos: int = 0 diff --git a/backend/src/infrastructure/repositories/consultor_repository_impl.py b/backend/src/infrastructure/repositories/consultor_repository_impl.py index 0287a83..4766793 100644 --- a/backend/src/infrastructure/repositories/consultor_repository_impl.py +++ b/backend/src/infrastructure/repositories/consultor_repository_impl.py @@ -8,6 +8,8 @@ from ...domain.entities.consultor import ( Consultor, CoordenacaoCapes, Consultoria, + IES, + VinculoConsultoria, Inscricao, AvaliacaoComissao, Premiacao, @@ -140,6 +142,7 @@ class ConsultorRepositoryImpl(ConsultorRepository): return None periodos: List[Periodo] = [] + vinculos: List[VinculoConsultoria] = [] situacoes: List[str] = [] areas: List[str] = [] @@ -162,9 +165,25 @@ class ConsultorRepositoryImpl(ConsultorRepository): if inicio and fim and fim < inicio: fim = None + + ies_data = dc.get("ies", {}) or {} + ies = None + if ies_data and (ies_data.get("id") or ies_data.get("nome")): + ies = IES( + id=str(ies_data.get("id", "")), + nome=ies_data.get("nome", ""), + sigla=ies_data.get("sigla"), + ) + if inicio: try: - periodos.append(Periodo(inicio=inicio, fim=fim)) + periodo = Periodo(inicio=inicio, fim=fim) + periodos.append(periodo) + vinculos.append(VinculoConsultoria( + periodo=periodo, + ies=ies, + situacao=situacao or "", + )) except ValueError: continue @@ -176,6 +195,8 @@ class ConsultorRepositoryImpl(ConsultorRepository): if not periodos: return None + vinculos.sort(key=lambda v: v.periodo.inicio, reverse=True) + mesclados = mesclar_periodos(periodos) periodo_ativo = next((p for p in mesclados if p.ativo), None) anos_consecutivos = periodo_ativo.anos_completos(datetime.now()) if periodo_ativo else 0 @@ -205,6 +226,7 @@ class ConsultorRepositoryImpl(ConsultorRepository): situacao=situacao_final, periodo=Periodo(inicio=primeiro_evento, fim=fim_final), periodos=mesclados, + vinculos=vinculos, areas=areas, anos_consecutivos=anos_consecutivos, retornos=retornos, diff --git a/backend/src/interface/schemas/consultor_schema.py b/backend/src/interface/schemas/consultor_schema.py index d9fb9bc..bf91590 100644 --- a/backend/src/interface/schemas/consultor_schema.py +++ b/backend/src/interface/schemas/consultor_schema.py @@ -9,6 +9,18 @@ class PeriodoSchema(BaseModel): anos_decorridos: float +class IESSchema(BaseModel): + id: str + nome: str + sigla: Optional[str] = None + + +class VinculoConsultoriaSchema(BaseModel): + periodo: PeriodoSchema + ies: Optional[IESSchema] = None + situacao: str = "" + + class CoordenacaoCapesSchema(BaseModel): codigo: str tipo: str @@ -23,6 +35,7 @@ class ConsultoriaSchema(BaseModel): codigo: str situacao: str periodo: PeriodoSchema + vinculos: List[VinculoConsultoriaSchema] = [] areas: List[str] anos_consecutivos: int retornos: int diff --git a/frontend/src/components/ConsultorCard.css b/frontend/src/components/ConsultorCard.css index 9283dcb..2ed7660 100644 --- a/frontend/src/components/ConsultorCard.css +++ b/frontend/src/components/ConsultorCard.css @@ -458,6 +458,13 @@ min-width: 50px; } +.list-item .ies-nome { + flex: 1; + color: var(--text); + font-weight: 500; + font-size: 0.85rem; +} + @media (max-width: 1200px) { .details-grid { grid-template-columns: repeat(3, 1fr); diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index 1c931f4..42a6945 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -346,11 +346,44 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio )} + {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 ( +
+ + {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.map((coord, idx) => ( + {[...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) => (
{coord.codigo || coord.tipo} {PONTOS_BASE[coord.codigo] || 0} pts @@ -368,7 +401,9 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio

Premiacoes

- {consultor.premiacoes.map((prem, idx) => ( + {[...consultor.premiacoes] + .sort((a, b) => (b.ano || 0) - (a.ano || 0)) + .map((prem, idx) => (
{prem.codigo} {PONTOS_BASE[prem.codigo] || 0} pts @@ -384,7 +419,9 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio

Avaliacoes de Comissao

- {consultor.avaliacoes_comissao.map((aval, idx) => ( + {[...consultor.avaliacoes_comissao] + .sort((a, b) => (b.ano || 0) - (a.ano || 0)) + .map((aval, idx) => (
{aval.codigo} {PONTOS_BASE[aval.codigo] || 0} pts @@ -400,7 +437,9 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio

Inscricoes

- {consultor.inscricoes.map((insc, idx) => ( + {[...consultor.inscricoes] + .sort((a, b) => (b.ano || 0) - (a.ano || 0)) + .map((insc, idx) => (
{insc.codigo} {PONTOS_BASE[insc.codigo] || 0} pts @@ -416,7 +455,10 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio

Participacoes (Eventos/Projetos)

- {consultor.participacoes.slice(0, 10).map((part, idx) => ( + {[...consultor.participacoes] + .sort((a, b) => (b.ano || 0) - (a.ano || 0)) + .slice(0, 10) + .map((part, idx) => (
{part.codigo} {PONTOS_BASE[part.codigo] || 0} pts diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 0d91235..5da7572 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -69,6 +69,15 @@ export const rankingService = { areas: consultoria.areas || [], anos_consecutivos: consultoria.anos_consecutivos || 0, retornos: consultoria.retornos || 0, + vinculos: (consultoria.vinculos || []).map((v) => ({ + periodo: { + inicio: v.inicio || v.periodo?.inicio || null, + fim: v.fim || v.periodo?.fim || null, + ativo: v.ativo ?? v.periodo?.ativo ?? !v.fim, + }, + ies: v.ies || null, + situacao: v.situacao || '', + })), }, coordenacoes_capes: coordenacoesCapes, inscricoes: c.inscricoes || [],