feat: extrair docencias PPG e simplificar blocos de pontuacao

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
This commit is contained in:
Frederico Castro
2025-12-23 04:27:36 -03:00
parent d33695ed65
commit 8799a68c30
11 changed files with 154 additions and 123 deletions

View File

@@ -214,6 +214,23 @@ class ProcessarRankingJob:
} }
for m in consultor.membros_banca 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, "pontuacao": pontuacao,
} }
@@ -289,7 +306,7 @@ class ProcessarRankingJob:
if limpar_antes: if limpar_antes:
self.ranking_oracle_repo.limpar_tabela() self.ranking_oracle_repo.limpar_tabela()
batch_size = 500 batch_size = 2000
for i in range(0, len(consultores), batch_size): for i in range(0, len(consultores), batch_size):
batch = consultores[i:i + batch_size] batch = consultores[i:i + batch_size]
self.ranking_oracle_repo.inserir_batch(batch) self.ranking_oracle_repo.inserir_batch(batch)

View File

@@ -53,6 +53,7 @@ class RankingMapper:
participacoes = None participacoes = None
orientacoes = None orientacoes = None
membros_banca = None membros_banca = None
docencias = None
pontuacao = None pontuacao = None
tipos_atuacao = [] tipos_atuacao = []
@@ -68,6 +69,7 @@ class RankingMapper:
participacoes = jd.get("participacoes") participacoes = jd.get("participacoes")
orientacoes = jd.get("orientacoes") orientacoes = jd.get("orientacoes")
membros_banca = jd.get("membros_banca") membros_banca = jd.get("membros_banca")
docencias = jd.get("docencias")
pontuacao = jd.get("pontuacao") pontuacao = jd.get("pontuacao")
tipos_atuacao = RankingMapper._extrair_tipos_atuacao(jd) tipos_atuacao = RankingMapper._extrair_tipos_atuacao(jd)
except (json.JSONDecodeError, TypeError) as e: except (json.JSONDecodeError, TypeError) as e:
@@ -104,6 +106,7 @@ class RankingMapper:
participacoes=participacoes, participacoes=participacoes,
orientacoes=orientacoes, orientacoes=orientacoes,
membros_banca=membros_banca, membros_banca=membros_banca,
docencias=docencias,
pontuacao=pontuacao_ajustada if pontuacao_ajustada else None, pontuacao=pontuacao_ajustada if pontuacao_ajustada else None,
) )

View File

@@ -105,6 +105,20 @@ class MembroBanca:
ano: Optional[int] = None 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 @dataclass
class Consultor: class Consultor:
id_pessoa: int id_pessoa: int
@@ -120,6 +134,7 @@ class Consultor:
participacoes: List[Participacao] = field(default_factory=list) participacoes: List[Participacao] = field(default_factory=list)
orientacoes: List[Orientacao] = field(default_factory=list) orientacoes: List[Orientacao] = field(default_factory=list)
membros_banca: List[MembroBanca] = field(default_factory=list) membros_banca: List[MembroBanca] = field(default_factory=list)
docencias: List[DocenciaPPG] = field(default_factory=list)
pontuacao: Optional[PontuacaoCompleta] = None pontuacao: Optional[PontuacaoCompleta] = None
@property @property

View File

@@ -12,7 +12,7 @@ class RankingOracleRepository:
def inserir_batch(self, consultores: List[Dict[str, Any]]) -> int: 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. Retorna o número de registros processados.
""" """
import oracledb import oracledb
@@ -20,68 +20,39 @@ class RankingOracleRepository:
if not consultores: if not consultores:
return 0 return 0
merge_sql = """ insert_sql = """
MERGE INTO TB_RANKING_CONSULTOR t INSERT INTO TB_RANKING_CONSULTOR (
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, ID_PESSOA, NOME, PONTUACAO_TOTAL,
COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D, COMPONENTE_A, COMPONENTE_B, COMPONENTE_C, COMPONENTE_D,
ATIVO, ANOS_ATUACAO, JSON_DETALHES, DT_CALCULO ATIVO, ANOS_ATUACAO, JSON_DETALHES, DT_CALCULO
) ) VALUES (
VALUES ( :id_pessoa, :nome, :pontuacao_total,
s.ID_PESSOA, s.NOME, s.PONTUACAO_TOTAL, :componente_a, :componente_b, :componente_c, :componente_d,
s.COMPONENTE_A, s.COMPONENTE_B, s.COMPONENTE_C, s.COMPONENTE_D, :ativo, :anos_atuacao, :json_detalhes, CURRENT_TIMESTAMP
s.ATIVO, s.ANOS_ATUACAO, s.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: with self.client.get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
try: try:
for consultor in consultores:
json_str = json.dumps(consultor, ensure_ascii=False)
cursor.setinputsizes(json_detalhes=oracledb.DB_TYPE_CLOB) cursor.setinputsizes(json_detalhes=oracledb.DB_TYPE_CLOB)
params = { cursor.executemany(insert_sql, batch_data)
"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)
conn.commit() conn.commit()
return len(consultores) return len(consultores)
except Exception as e: except Exception as e:
@@ -372,14 +343,18 @@ class RankingOracleRepository:
""" """
Limpa todos os registros da tabela de ranking. Limpa todos os registros da tabela de ranking.
Usar apenas quando for reprocessar do zero. Usar apenas quando for reprocessar do zero.
TRUNCATE é muito mais rápido que DELETE para grandes volumes.
""" """
with self.client.get_connection() as conn: with self.client.get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
try:
cursor.execute("TRUNCATE TABLE TB_RANKING_CONSULTOR")
except Exception as e:
try: try:
cursor.execute("DELETE FROM TB_RANKING_CONSULTOR") cursor.execute("DELETE FROM TB_RANKING_CONSULTOR")
conn.commit() conn.commit()
except Exception as e: except Exception as e2:
conn.rollback() conn.rollback()
raise RuntimeError(f"Erro ao limpar tabela: {e}") raise RuntimeError(f"Erro ao limpar tabela: {e2}")
finally: finally:
cursor.close() cursor.close()

View File

@@ -17,6 +17,7 @@ from ...domain.entities.consultor import (
Participacao, Participacao,
Orientacao, Orientacao,
MembroBanca, MembroBanca,
DocenciaPPG,
) )
from ...domain.repositories.consultor_repository import ConsultorRepository from ...domain.repositories.consultor_repository import ConsultorRepository
from ...domain.services.calculador_pontuacao import CalculadorPontuacao from ...domain.services.calculador_pontuacao import CalculadorPontuacao
@@ -554,6 +555,51 @@ class ConsultorRepositoryImpl(ConsultorRepository):
return membros 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: async def _construir_consultor(self, doc: Dict[str, Any]) -> Consultor:
id_pessoa = doc["id"] id_pessoa = doc["id"]
dados_pessoais = doc.get("dadosPessoais", {}) dados_pessoais = doc.get("dadosPessoais", {})
@@ -569,6 +615,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
orientacoes = self._extrair_orientacoes(atuacoes) orientacoes = self._extrair_orientacoes(atuacoes)
coorientacoes = self._extrair_coorientacoes(atuacoes) coorientacoes = self._extrair_coorientacoes(atuacoes)
membros_banca = self._extrair_membros_banca(atuacoes) membros_banca = self._extrair_membros_banca(atuacoes)
docencias = self._extrair_docencias(atuacoes)
coordenador_ppg = self._tem_coordenacao_ppg(atuacoes) coordenador_ppg = self._tem_coordenacao_ppg(atuacoes)
consultor = Consultor( consultor = Consultor(
@@ -585,6 +632,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
participacoes=participacoes, participacoes=participacoes,
orientacoes=orientacoes + coorientacoes, orientacoes=orientacoes + coorientacoes,
membros_banca=membros_banca, membros_banca=membros_banca,
docencias=docencias,
) )
consultor.pontuacao = self.calculador.calcular_pontuacao_completa(consultor) consultor.pontuacao = self.calculador.calcular_pontuacao_completa(consultor)

View File

@@ -253,6 +253,7 @@ async def ranking_paginado(
participacoes=d.get("participacoes"), participacoes=d.get("participacoes"),
orientacoes=d.get("orientacoes"), orientacoes=d.get("orientacoes"),
membros_banca=d.get("membros_banca"), membros_banca=d.get("membros_banca"),
docencias=d.get("docencias"),
pontuacao=d.get("pontuacao"), pontuacao=d.get("pontuacao"),
) )
) )

View File

@@ -25,6 +25,7 @@ class ConsultorRankingResumoSchema(BaseModel):
participacoes: Optional[list] = None participacoes: Optional[list] = None
orientacoes: Optional[list] = None orientacoes: Optional[list] = None
membros_banca: Optional[list] = None membros_banca: Optional[list] = None
docencias: Optional[list] = None
pontuacao: Optional[dict] = None pontuacao: Optional[dict] = None

View File

@@ -107,15 +107,13 @@ const CompararModal = ({ consultor1, consultor2, onClose }) => {
const blocoA1 = p1.bloco_a || { total: consultor1.bloco_a || 0 }; const blocoA1 = p1.bloco_a || { total: consultor1.bloco_a || 0 };
const blocoA2 = p2.bloco_a || { total: consultor2.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 blocoC1 = p1.bloco_c || { total: consultor1.bloco_c || 0 };
const blocoC2 = p2.bloco_c || { total: consultor2.bloco_c || 0 }; const blocoC2 = p2.bloco_c || { total: consultor2.bloco_c || 0 };
const blocoD1 = p1.bloco_d || { total: consultor1.bloco_d || 0 }; const blocoD1 = p1.bloco_d || { total: consultor1.bloco_d || 0 };
const blocoD2 = p2.bloco_d || { total: consultor2.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 total1 = Number(consultor1.pontuacao_total ?? 0);
const total2 = (blocoA2.total || 0) + (blocoB2.total || 0) + (blocoC2.total || 0) + (blocoD2.total || 0); const total2 = Number(consultor2.pontuacao_total ?? 0);
const c1 = consultor1.consultoria; const c1 = consultor1.consultoria;
const c2 = consultor2.consultoria; const c2 = consultor2.consultoria;
@@ -193,11 +191,6 @@ const CompararModal = ({ consultor1, consultor2, onClose }) => {
)} )}
</div> </div>
<div className="comparacao-secao">
<h3 style={{ color: 'var(--accent)' }}>B - Coordenacao PPG</h3>
{renderLinhaComparacao('Total', blocoB1.total, blocoB2.total, 'var(--accent)')}
</div>
<div className="comparacao-secao"> <div className="comparacao-secao">
<h3 style={{ color: 'var(--gold)' }}>C - Consultoria</h3> <h3 style={{ color: 'var(--gold)' }}>C - Consultoria</h3>
{renderLinhaComparacao('Total', blocoC1.total, blocoC2.total, 'var(--gold)')} {renderLinhaComparacao('Total', blocoC1.total, blocoC2.total, 'var(--gold)')}

View File

@@ -1010,6 +1010,12 @@
color: #f9a8d4; 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 { .tipo-default {
background: rgba(148, 163, 184, 0.12); background: rgba(148, 163, 184, 0.12);
border-color: rgba(148, 163, 184, 0.25); border-color: rgba(148, 163, 184, 0.25);

View File

@@ -818,8 +818,8 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => {
</div> </div>
)} )}
<div className="modal-detalhe-row"> <div className="modal-detalhe-row">
<span className="modal-detalhe-label">Pontuação</span> <span className="modal-detalhe-label">Pontuação Base</span>
<span className="modal-detalhe-value muted">Apenas selo (sem pontuação)</span> <span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
</div> </div>
</div> </div>
); );
@@ -853,17 +853,13 @@ const FORMULAS = {
titulo: 'Coordenacao CAPES', 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)', 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: { bloco_c: {
titulo: 'Consultoria', 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)', 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: { bloco_d: {
titulo: 'Premiacoes/Avaliacoes', 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, PREMIACAO_GP_AUTOR: 100, PREMIACAO_AUTOR: 50, MENCAO_AUTOR: 30,
BOL_BPQ_NIVEL: 30, BOL_BPQ_NIVEL: 30,
EVENTO: 1, PROJ: 10, 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 = { const TETOS = {
@@ -894,15 +887,6 @@ const TETOS = {
EVENTO: { teto: 5, doc: '3.5 Participações Acadêmicas', bonus: '+1/participação (max 10)' }, 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)' }, 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' }, 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 }) => { const PontuacaoModal = ({ dados, onClose }) => {
@@ -1054,7 +1038,7 @@ const PontuacaoModal = ({ dados, onClose }) => {
<div className="modal-formula-section"> <div className="modal-formula-section">
<span className="modal-detalhe-label">Fórmula</span> <span className="modal-detalhe-label">Fórmula</span>
<div className="modal-formula-box"> <div className="modal-formula-box">
<div className="modal-formula-line">Bloco A + Bloco B + Bloco C + Bloco D</div> <div className="modal-formula-line">Bloco A + Bloco C + Bloco D</div>
</div> </div>
</div> </div>
</div> </div>
@@ -1132,10 +1116,9 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
const { consultoria, pontuacao } = consultor; const { consultoria, pontuacao } = consultor;
const blocoA = pontuacao?.bloco_a || { total: consultor.bloco_a || 0 }; 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 blocoC = pontuacao?.bloco_c || { total: consultor.bloco_c || 0 };
const blocoD = pontuacao?.bloco_d || { total: consultor.bloco_d || 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]); const selos = useMemo(() => gerarSelos(consultor), [consultor]);
@@ -1246,18 +1229,6 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
bloco: blocoA bloco: blocoA
})} })}
/> />
<ScoreItemClickable
value={blocoB.total}
label="BLOCO B"
style={{ color: blocoB.total > 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
})}
/>
<ScoreItemClickable <ScoreItemClickable
value={blocoC.total} value={blocoC.total}
label="BLOCO C" label="BLOCO C"
@@ -1299,10 +1270,6 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
<BlocoDetalhes titulo="A - Coordenacao CAPES" bloco={blocoA} cor="var(--accent-2)" onItemClick={setPontuacaoModal} /> <BlocoDetalhes titulo="A - Coordenacao CAPES" bloco={blocoA} cor="var(--accent-2)" onItemClick={setPontuacaoModal} />
)} )}
{(blocoB.total > 0 || (blocoB.atuacoes && blocoB.atuacoes.length > 0)) && (
<BlocoDetalhes titulo="B - Coordenacao PPG" bloco={blocoB} cor="var(--accent)" onItemClick={setPontuacaoModal} />
)}
{blocoC.atuacoes && blocoC.atuacoes.length > 0 && ( {blocoC.atuacoes && blocoC.atuacoes.length > 0 && (
<BlocoDetalhes titulo="C - Consultoria" bloco={blocoC} cor="var(--gold)" onItemClick={setPontuacaoModal} /> <BlocoDetalhes titulo="C - Consultoria" bloco={blocoC} cor="var(--gold)" onItemClick={setPontuacaoModal} />
)} )}
@@ -1571,11 +1538,23 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
ConsultorCard.displayName = 'ConsultorCard'; 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 (
<div className="detail-section"> <div className="detail-section">
<h4 style={{ color: cor }}>{titulo}</h4> <h4 style={{ color: cor }}>{titulo}</h4>
<div className="score-breakdown"> <div className="score-breakdown">
{bloco.atuacoes?.map((at, idx) => ( {atuacoesFiltradas.map((at, idx) => (
<div <div
key={idx} key={idx}
className="score-item-wrapper score-item-clicavel" className="score-item-wrapper score-item-clicavel"
@@ -1608,7 +1587,8 @@ const BlocoDetalhes = memo(({ titulo, bloco, cor, onItemClick }) => (
</div> </div>
</div> </div>
</div> </div>
)); );
});
BlocoDetalhes.displayName = 'BlocoDetalhes'; BlocoDetalhes.displayName = 'BlocoDetalhes';

View File

@@ -64,17 +64,9 @@ const Header = ({ total }) => {
<div className="criteria-section bloco-e"> <div className="criteria-section bloco-e">
<div className="section-header"> <div className="section-header">
<h4>E - Coord. PPG</h4> <h4>E - Coord. PPG</h4>
<span className="max-pts">V2</span> <span className="max-pts">indicador</span>
</div> </div>
<table className="criteria-table"> <div className="criteria-note">Nao entra no score (apenas selo/indicador no perfil)</div>
<thead>
<tr><th>Cod</th><th>Base</th><th>Teto</th></tr>
</thead>
<tbody>
<tr><td>PPG_COORD</td><td>0</td><td>0</td></tr>
</tbody>
</table>
<div className="criteria-note">Dados incompletos no V1</div>
</div> </div>
</div> </div>