From 8799a68c3019b5f102b62cf86fe4ef714453e975 Mon Sep 17 00:00:00 2001 From: Frederico Castro Date: Tue, 23 Dec 2025 04:27:36 -0300 Subject: [PATCH] feat: extrair docencias PPG e simplificar blocos de pontuacao MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: - Adicionar entidade DocenciaPPG para dados de docencia - Extrair docencias do Elasticsearch (tipo "Docência") - Serializar docencias no JSON de detalhes do consultor - Aumentar batch size de 500 para 2000 para melhor performance Frontend: - Remover Bloco B (Coord. PPG) - reservado para V2 - Simplificar formula para: Bloco A + Bloco C + Bloco D - Filtrar orientacoes/bancas da listagem (sao apenas selos) - Atualizar Header com nota que PPG_COORD e apenas indicador - Exibir pontuacao base nos modais de orientacao/banca --- .../src/application/jobs/processar_ranking.py | 19 +++- .../src/application/mappers/ranking_mapper.py | 3 + backend/src/domain/entities/consultor.py | 15 +++ .../oracle/ranking_repository.py | 99 +++++++------------ .../repositories/consultor_repository_impl.py | 48 +++++++++ backend/src/interface/api/routes.py | 1 + .../src/interface/schemas/ranking_schema.py | 1 + frontend/src/components/CompararModal.jsx | 11 +-- frontend/src/components/ConsultorCard.css | 6 ++ frontend/src/components/ConsultorCard.jsx | 62 ++++-------- frontend/src/components/Header.jsx | 12 +-- 11 files changed, 154 insertions(+), 123 deletions(-) diff --git a/backend/src/application/jobs/processar_ranking.py b/backend/src/application/jobs/processar_ranking.py index 31d66e0..54f52e8 100644 --- a/backend/src/application/jobs/processar_ranking.py +++ b/backend/src/application/jobs/processar_ranking.py @@ -214,6 +214,23 @@ class ProcessarRankingJob: } for m in consultor.membros_banca ], + "docencias": [ + { + "programa": d.programa, + "codigo_programa": d.codigo_programa, + "ies_sigla": d.ies_sigla, + "ies_nome": d.ies_nome, + "categoria": d.categoria, + "area_avaliacao": d.area_avaliacao, + "modalidade": d.modalidade, + "inicio": d.periodo.inicio.isoformat() if d.periodo.inicio else None, + "fim": d.periodo.fim.isoformat() if d.periodo.fim else None, + "ativo": d.periodo.ativo, + "carga_horaria": d.carga_horaria, + "linhas_pesquisa": d.linhas_pesquisa, + } + for d in consultor.docencias + ], "pontuacao": pontuacao, } @@ -289,7 +306,7 @@ class ProcessarRankingJob: if limpar_antes: self.ranking_oracle_repo.limpar_tabela() - batch_size = 500 + batch_size = 2000 for i in range(0, len(consultores), batch_size): batch = consultores[i:i + batch_size] self.ranking_oracle_repo.inserir_batch(batch) diff --git a/backend/src/application/mappers/ranking_mapper.py b/backend/src/application/mappers/ranking_mapper.py index 70cde22..7d35572 100644 --- a/backend/src/application/mappers/ranking_mapper.py +++ b/backend/src/application/mappers/ranking_mapper.py @@ -53,6 +53,7 @@ class RankingMapper: participacoes = None orientacoes = None membros_banca = None + docencias = None pontuacao = None tipos_atuacao = [] @@ -68,6 +69,7 @@ class RankingMapper: participacoes = jd.get("participacoes") orientacoes = jd.get("orientacoes") membros_banca = jd.get("membros_banca") + docencias = jd.get("docencias") pontuacao = jd.get("pontuacao") tipos_atuacao = RankingMapper._extrair_tipos_atuacao(jd) except (json.JSONDecodeError, TypeError) as e: @@ -104,6 +106,7 @@ class RankingMapper: participacoes=participacoes, orientacoes=orientacoes, membros_banca=membros_banca, + docencias=docencias, pontuacao=pontuacao_ajustada if pontuacao_ajustada else None, ) diff --git a/backend/src/domain/entities/consultor.py b/backend/src/domain/entities/consultor.py index bc770e5..6194780 100644 --- a/backend/src/domain/entities/consultor.py +++ b/backend/src/domain/entities/consultor.py @@ -105,6 +105,20 @@ class MembroBanca: ano: Optional[int] = None +@dataclass +class DocenciaPPG: + programa: str + codigo_programa: str + ies_sigla: str + ies_nome: str + categoria: str + area_avaliacao: str + modalidade: str + periodo: Periodo + carga_horaria: Optional[int] = None + linhas_pesquisa: List[str] = field(default_factory=list) + + @dataclass class Consultor: id_pessoa: int @@ -120,6 +134,7 @@ class Consultor: participacoes: List[Participacao] = field(default_factory=list) orientacoes: List[Orientacao] = field(default_factory=list) membros_banca: List[MembroBanca] = field(default_factory=list) + docencias: List[DocenciaPPG] = field(default_factory=list) pontuacao: Optional[PontuacaoCompleta] = None @property diff --git a/backend/src/infrastructure/oracle/ranking_repository.py b/backend/src/infrastructure/oracle/ranking_repository.py index 412378a..8393ac9 100644 --- a/backend/src/infrastructure/oracle/ranking_repository.py +++ b/backend/src/infrastructure/oracle/ranking_repository.py @@ -12,7 +12,7 @@ class RankingOracleRepository: def inserir_batch(self, consultores: List[Dict[str, Any]]) -> int: """ - Insere ou atualiza um batch de consultores usando MERGE. + Insere batch de consultores usando executemany (muito mais rápido). Retorna o número de registros processados. """ import oracledb @@ -20,68 +20,39 @@ class RankingOracleRepository: if not consultores: return 0 - merge_sql = """ - MERGE INTO TB_RANKING_CONSULTOR t - USING ( - SELECT - :id_pessoa AS ID_PESSOA, - :nome AS NOME, - :pontuacao_total AS PONTUACAO_TOTAL, - :componente_a AS COMPONENTE_A, - :componente_b AS COMPONENTE_B, - :componente_c AS COMPONENTE_C, - :componente_d AS COMPONENTE_D, - :ativo AS ATIVO, - :anos_atuacao AS ANOS_ATUACAO, - TO_CLOB(:json_detalhes) AS JSON_DETALHES - FROM DUAL - ) s - ON (t.ID_PESSOA = s.ID_PESSOA) - WHEN MATCHED THEN - UPDATE SET - t.NOME = s.NOME, - t.PONTUACAO_TOTAL = s.PONTUACAO_TOTAL, - t.COMPONENTE_A = s.COMPONENTE_A, - t.COMPONENTE_B = s.COMPONENTE_B, - t.COMPONENTE_C = s.COMPONENTE_C, - t.COMPONENTE_D = s.COMPONENTE_D, - t.ATIVO = s.ATIVO, - t.ANOS_ATUACAO = s.ANOS_ATUACAO, - t.DT_CALCULO = CURRENT_TIMESTAMP, - t.JSON_DETALHES = s.JSON_DETALHES - WHEN NOT MATCHED THEN - INSERT ( - ID_PESSOA, NOME, PONTUACAO_TOTAL, - COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D, - ATIVO, ANOS_ATUACAO, JSON_DETALHES, DT_CALCULO - ) - VALUES ( - s.ID_PESSOA, s.NOME, s.PONTUACAO_TOTAL, - s.COMPONENTE_A, s.COMPONENTE_B, s.COMPONENTE_C, s.COMPONENTE_D, - s.ATIVO, s.ANOS_ATUACAO, s.JSON_DETALHES, CURRENT_TIMESTAMP - ) + insert_sql = """ + INSERT INTO TB_RANKING_CONSULTOR ( + ID_PESSOA, NOME, PONTUACAO_TOTAL, + COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D, + ATIVO, ANOS_ATUACAO, JSON_DETALHES, DT_CALCULO + ) VALUES ( + :id_pessoa, :nome, :pontuacao_total, + :componente_a, :componente_b, :componente_c, :componente_d, + :ativo, :anos_atuacao, :json_detalhes, CURRENT_TIMESTAMP + ) """ + batch_data = [] + for consultor in consultores: + json_str = json.dumps(consultor, ensure_ascii=False) + batch_data.append({ + "id_pessoa": int(consultor["id_pessoa"]), + "nome": str(consultor.get("nome", ""))[:500], + "pontuacao_total": int(consultor.get("pontuacao_total") or 0), + "componente_a": int(consultor.get("bloco_a") or consultor.get("componente_a") or 0), + "componente_b": int(consultor.get("bloco_b") or consultor.get("componente_b") or 0), + "componente_c": int(consultor.get("bloco_c") or consultor.get("componente_c") or 0), + "componente_d": int(consultor.get("bloco_d") or consultor.get("componente_d") or 0), + "ativo": "S" if consultor.get("ativo") else "N", + "anos_atuacao": float(consultor.get("anos_atuacao") or 0), + "json_detalhes": json_str + }) + with self.client.get_connection() as conn: cursor = conn.cursor() try: - for consultor in consultores: - json_str = json.dumps(consultor, ensure_ascii=False) - cursor.setinputsizes(json_detalhes=oracledb.DB_TYPE_CLOB) - params = { - "id_pessoa": consultor["id_pessoa"], - "nome": consultor["nome"], - "pontuacao_total": consultor["pontuacao_total"], - "componente_a": consultor.get("bloco_a") or consultor.get("componente_a", 0), - "componente_b": consultor.get("bloco_b") or consultor.get("componente_b", 0), - "componente_c": consultor.get("bloco_c") or consultor.get("componente_c", 0), - "componente_d": consultor.get("bloco_d") or consultor.get("componente_d", 0), - "ativo": "S" if consultor.get("ativo") else "N", - "anos_atuacao": consultor.get("anos_atuacao", 0), - "json_detalhes": json_str - } - cursor.execute(merge_sql, params) - + cursor.setinputsizes(json_detalhes=oracledb.DB_TYPE_CLOB) + cursor.executemany(insert_sql, batch_data) conn.commit() return len(consultores) except Exception as e: @@ -372,14 +343,18 @@ class RankingOracleRepository: """ Limpa todos os registros da tabela de ranking. Usar apenas quando for reprocessar do zero. + TRUNCATE é muito mais rápido que DELETE para grandes volumes. """ with self.client.get_connection() as conn: cursor = conn.cursor() try: - cursor.execute("DELETE FROM TB_RANKING_CONSULTOR") - conn.commit() + cursor.execute("TRUNCATE TABLE TB_RANKING_CONSULTOR") except Exception as e: - conn.rollback() - raise RuntimeError(f"Erro ao limpar tabela: {e}") + try: + cursor.execute("DELETE FROM TB_RANKING_CONSULTOR") + conn.commit() + except Exception as e2: + conn.rollback() + raise RuntimeError(f"Erro ao limpar tabela: {e2}") finally: cursor.close() diff --git a/backend/src/infrastructure/repositories/consultor_repository_impl.py b/backend/src/infrastructure/repositories/consultor_repository_impl.py index 4b50234..0d6dbb8 100644 --- a/backend/src/infrastructure/repositories/consultor_repository_impl.py +++ b/backend/src/infrastructure/repositories/consultor_repository_impl.py @@ -17,6 +17,7 @@ from ...domain.entities.consultor import ( Participacao, Orientacao, MembroBanca, + DocenciaPPG, ) from ...domain.repositories.consultor_repository import ConsultorRepository from ...domain.services.calculador_pontuacao import CalculadorPontuacao @@ -554,6 +555,51 @@ class ConsultorRepositoryImpl(ConsultorRepository): return membros + def _extrair_docencias(self, atuacoes: List[Dict[str, Any]]) -> List[DocenciaPPG]: + docencias = [] + + for a in atuacoes: + if a.get("tipo") != "Docência": + continue + + dados = a.get("dadosDocencia", {}) or {} + if not dados: + continue + + programa_data = dados.get("programa", {}) or {} + ies_data = dados.get("ies", {}) or {} + area_data = dados.get("areaConhecimento", {}) or {} + area_aval_data = area_data.get("areaAvaliacao", {}) or {} + + programa = programa_data.get("nome", "") or a.get("descricao", "") + codigo_programa = programa_data.get("codigo", "") + if not programa and not codigo_programa: + continue + + inicio = self._parse_date(a.get("inicio")) + fim = self._parse_date(a.get("fim")) + + linhas = [] + for lp in dados.get("linhaPesquisa", []) or []: + if lp.get("nome"): + linhas.append(lp["nome"]) + + docencias.append(DocenciaPPG( + programa=programa, + codigo_programa=codigo_programa, + ies_sigla=ies_data.get("sigla", ""), + ies_nome=ies_data.get("nome", ""), + categoria=dados.get("categoria", ""), + area_avaliacao=area_aval_data.get("nome", "") or area_data.get("nome", ""), + modalidade=programa_data.get("modalidade", ""), + periodo=Periodo(inicio=inicio, fim=fim), + carga_horaria=dados.get("cargaHoraria"), + linhas_pesquisa=linhas[:3], + )) + + docencias.sort(key=lambda d: (d.periodo.fim is not None, d.periodo.inicio or datetime.min), reverse=True) + return docencias + async def _construir_consultor(self, doc: Dict[str, Any]) -> Consultor: id_pessoa = doc["id"] dados_pessoais = doc.get("dadosPessoais", {}) @@ -569,6 +615,7 @@ class ConsultorRepositoryImpl(ConsultorRepository): orientacoes = self._extrair_orientacoes(atuacoes) coorientacoes = self._extrair_coorientacoes(atuacoes) membros_banca = self._extrair_membros_banca(atuacoes) + docencias = self._extrair_docencias(atuacoes) coordenador_ppg = self._tem_coordenacao_ppg(atuacoes) consultor = Consultor( @@ -585,6 +632,7 @@ class ConsultorRepositoryImpl(ConsultorRepository): participacoes=participacoes, orientacoes=orientacoes + coorientacoes, membros_banca=membros_banca, + docencias=docencias, ) consultor.pontuacao = self.calculador.calcular_pontuacao_completa(consultor) diff --git a/backend/src/interface/api/routes.py b/backend/src/interface/api/routes.py index 6510795..63cc603 100644 --- a/backend/src/interface/api/routes.py +++ b/backend/src/interface/api/routes.py @@ -253,6 +253,7 @@ async def ranking_paginado( participacoes=d.get("participacoes"), orientacoes=d.get("orientacoes"), membros_banca=d.get("membros_banca"), + docencias=d.get("docencias"), pontuacao=d.get("pontuacao"), ) ) diff --git a/backend/src/interface/schemas/ranking_schema.py b/backend/src/interface/schemas/ranking_schema.py index faead5c..1b419df 100644 --- a/backend/src/interface/schemas/ranking_schema.py +++ b/backend/src/interface/schemas/ranking_schema.py @@ -25,6 +25,7 @@ class ConsultorRankingResumoSchema(BaseModel): participacoes: Optional[list] = None orientacoes: Optional[list] = None membros_banca: Optional[list] = None + docencias: Optional[list] = None pontuacao: Optional[dict] = None diff --git a/frontend/src/components/CompararModal.jsx b/frontend/src/components/CompararModal.jsx index 74c8dac..fe20060 100644 --- a/frontend/src/components/CompararModal.jsx +++ b/frontend/src/components/CompararModal.jsx @@ -107,15 +107,13 @@ const CompararModal = ({ consultor1, consultor2, onClose }) => { const blocoA1 = p1.bloco_a || { total: consultor1.bloco_a || 0 }; const blocoA2 = p2.bloco_a || { total: consultor2.bloco_a || 0 }; - const blocoB1 = p1.bloco_b || { total: consultor1.bloco_b || 0 }; - const blocoB2 = p2.bloco_b || { total: consultor2.bloco_b || 0 }; const blocoC1 = p1.bloco_c || { total: consultor1.bloco_c || 0 }; const blocoC2 = p2.bloco_c || { total: consultor2.bloco_c || 0 }; const blocoD1 = p1.bloco_d || { total: consultor1.bloco_d || 0 }; const blocoD2 = p2.bloco_d || { total: consultor2.bloco_d || 0 }; - const total1 = (blocoA1.total || 0) + (blocoB1.total || 0) + (blocoC1.total || 0) + (blocoD1.total || 0); - const total2 = (blocoA2.total || 0) + (blocoB2.total || 0) + (blocoC2.total || 0) + (blocoD2.total || 0); + const total1 = Number(consultor1.pontuacao_total ?? 0); + const total2 = Number(consultor2.pontuacao_total ?? 0); const c1 = consultor1.consultoria; const c2 = consultor2.consultoria; @@ -193,11 +191,6 @@ const CompararModal = ({ consultor1, consultor2, onClose }) => { )} -
-

B - Coordenacao PPG

- {renderLinhaComparacao('Total', blocoB1.total, blocoB2.total, 'var(--accent)')} -
-

C - Consultoria

{renderLinhaComparacao('Total', blocoC1.total, blocoC2.total, 'var(--gold)')} diff --git a/frontend/src/components/ConsultorCard.css b/frontend/src/components/ConsultorCard.css index 73aac5a..a41c01c 100644 --- a/frontend/src/components/ConsultorCard.css +++ b/frontend/src/components/ConsultorCard.css @@ -1010,6 +1010,12 @@ color: #f9a8d4; } +.tipo-docente { + background: linear-gradient(135deg, rgba(20, 184, 166, 0.2), rgba(20, 184, 166, 0.08)); + border-color: rgba(20, 184, 166, 0.35); + color: #5eead4; +} + .tipo-default { background: rgba(148, 163, 184, 0.12); border-color: rgba(148, 163, 184, 0.25); diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index 431ab1e..dc28805 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -818,8 +818,8 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => {
)}
- Pontuação - Apenas selo (sem pontuação) + Pontuação Base + {PONTOS_BASE[item.codigo] || 0} pts
); @@ -853,17 +853,13 @@ const FORMULAS = { 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: 'Coordenacao PPG', - descricao: 'Reservado no V1: PPG_COORD base=0 | teto=0 (dados incompletos no ATUACAPES para pontuar).', - }, bloco_c: { titulo: 'Consultoria', descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade: 3a=+5, 5a=+10, 8a+=+20 (escalonado)\nRetorno (reativação): +15 (uma vez)', }, bloco_d: { titulo: 'Premiacoes/Avaliacoes', - descricao: 'Premiações: GP=100 (teto 300) | Prêmio=50 (teto 150) | Menção=30 (teto 90)\nBolsas: BPQ=30 (teto 60)\nInscrições/Avaliações/Comissões/Participações (com tetos por código)\nOrientações/Bancas: apenas selos (0 pts)', + descricao: 'Premiações: GP=100 (teto 300) | Prêmio=50 (teto 150) | Menção=30 (teto 90)\nBolsas: BPQ=30 (teto 60)\nInscrições/Avaliações/Comissões/Participações (com tetos por código)\nOrientações e Bancas: apenas selos (sem pontuação)', }, }; @@ -876,9 +872,6 @@ const PONTOS_BASE = { PREMIACAO_GP_AUTOR: 100, PREMIACAO_AUTOR: 50, MENCAO_AUTOR: 30, BOL_BPQ_NIVEL: 30, EVENTO: 1, PROJ: 10, - ORIENT_POS_DOC: 0, ORIENT_TESE: 0, ORIENT_DISS: 0, - CO_ORIENT_POS_DOC: 0, CO_ORIENT_TESE: 0, CO_ORIENT_DISS: 0, - MB_BANCA_POS_DOC: 0, MB_BANCA_TESE: 0, MB_BANCA_DISS: 0, }; const TETOS = { @@ -894,15 +887,6 @@ const TETOS = { EVENTO: { teto: 5, doc: '3.5 Participações Acadêmicas', bonus: '+1/participação (max 10)' }, PROJ: { teto: 30, doc: '3.5 Participações Acadêmicas', bonus: '+2/participação (max 10)' }, BOL_BPQ_NIVEL: { teto: 60, doc: '3.4 Premiações e Bolsas' }, - ORIENT_POS_DOC: { teto: 0, doc: 'Selo (sem pontuação)' }, - ORIENT_TESE: { teto: 0, doc: 'Selo (sem pontuação)' }, - ORIENT_DISS: { teto: 0, doc: 'Selo (sem pontuação)' }, - CO_ORIENT_POS_DOC: { teto: 0, doc: 'Selo (sem pontuação)' }, - CO_ORIENT_TESE: { teto: 0, doc: 'Selo (sem pontuação)' }, - CO_ORIENT_DISS: { teto: 0, doc: 'Selo (sem pontuação)' }, - MB_BANCA_POS_DOC: { teto: 0, doc: 'Selo (sem pontuação)' }, - MB_BANCA_TESE: { teto: 0, doc: 'Selo (sem pontuação)' }, - MB_BANCA_DISS: { teto: 0, doc: 'Selo (sem pontuação)' }, }; const PontuacaoModal = ({ dados, onClose }) => { @@ -1054,7 +1038,7 @@ const PontuacaoModal = ({ dados, onClose }) => {
Fórmula
-
Bloco A + Bloco B + Bloco C + Bloco D
+
Bloco A + Bloco C + Bloco D
@@ -1132,10 +1116,9 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio 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 = (blocoA.total || 0) + (blocoB.total || 0) + (blocoC.total || 0) + (blocoD.total || 0); + const pontuacaoTotal = Number(consultor.pontuacao_total ?? 0); const selos = useMemo(() => gerarSelos(consultor), [consultor]); @@ -1246,18 +1229,6 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio bloco: blocoA })} /> - 0 ? 'var(--accent)' : 'var(--muted)' }} - onClick={() => setPontuacaoModal({ - tipo: 'bloco', - label: 'BLOCO B - Coordenação PPG', - value: blocoB.total, - formula: FORMULAS.bloco_b.descricao, - bloco: blocoB - })} - /> )} - {(blocoB.total > 0 || (blocoB.atuacoes && blocoB.atuacoes.length > 0)) && ( - - )} - {blocoC.atuacoes && blocoC.atuacoes.length > 0 && ( )} @@ -1571,11 +1538,23 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio ConsultorCard.displayName = 'ConsultorCard'; -const BlocoDetalhes = memo(({ titulo, bloco, cor, onItemClick }) => ( +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}

- {bloco.atuacoes?.map((at, idx) => ( + {atuacoesFiltradas.map((at, idx) => (
(
-)); + ); +}); BlocoDetalhes.displayName = 'BlocoDetalhes'; diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 12129b3..7676c75 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -64,17 +64,9 @@ const Header = ({ total }) => {

E - Coord. PPG

- V2 + indicador
- - - - - - - -
CodBaseTeto
PPG_COORD00
-
Dados incompletos no V1
+
Nao entra no score (apenas selo/indicador no perfil)