From abfd11d4de63402473d08e60b42911983e84a911 Mon Sep 17 00:00:00 2001 From: Frederico Castro Date: Sat, 13 Dec 2025 08:15:44 -0300 Subject: [PATCH] Ajusta detalhamento do componente B no frontend --- frontend/src/components/CalculoDetalhado.css | 295 +++++++++++++++++++ frontend/src/components/CalculoDetalhado.jsx | 280 ++++++++++++++++++ frontend/src/components/ConsultorCard.jsx | 157 ++++++---- frontend/src/services/api.js | 62 +++- 4 files changed, 742 insertions(+), 52 deletions(-) create mode 100644 frontend/src/components/CalculoDetalhado.css create mode 100644 frontend/src/components/CalculoDetalhado.jsx diff --git a/frontend/src/components/CalculoDetalhado.css b/frontend/src/components/CalculoDetalhado.css new file mode 100644 index 0000000..75ae264 --- /dev/null +++ b/frontend/src/components/CalculoDetalhado.css @@ -0,0 +1,295 @@ +.calculo-detalhado { + margin-top: 1.5rem; + padding-top: 1.5rem; + border-top: 1px dashed var(--stroke); +} + +.calculo-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.25rem; +} + +.calculo-header h3 { + font-size: 0.9rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + color: var(--text); + margin: 0; +} + +.calculo-total { + font-size: 0.85rem; + color: var(--muted); +} + +.calculo-total strong { + font-size: 1.2rem; + background: linear-gradient(120deg, var(--accent), var(--accent-2)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.componentes-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1rem; +} + +.componente-card { + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--stroke); + border-radius: 12px; + overflow: hidden; + transition: all 200ms ease; + cursor: pointer; +} + +.componente-card:hover { + border-color: rgba(255, 255, 255, 0.15); + transform: translateY(-2px); +} + +.componente-card.ativo { + border-color: rgba(79, 70, 229, 0.4); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); +} + +.componente-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + color: white; +} + +.componente-info { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.componente-letra { + width: 24px; + height: 24px; + display: grid; + place-items: center; + background: rgba(255, 255, 255, 0.2); + border-radius: 6px; + font-weight: 700; + font-size: 0.8rem; +} + +.componente-nome { + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.componente-pontos { + display: flex; + align-items: baseline; +} + +.pontos-valor { + font-size: 1.3rem; + font-weight: 800; +} + +.pontos-max { + font-size: 0.75rem; + opacity: 0.7; +} + +.componente-barra-container { + position: relative; + height: 4px; + background: rgba(255, 255, 255, 0.1); +} + +.componente-barra { + height: 100%; + transition: width 500ms ease; +} + +.teto-badge { + position: absolute; + right: 4px; + top: -18px; + font-size: 0.6rem; + font-weight: 700; + padding: 2px 6px; + background: var(--success); + color: white; + border-radius: 4px; + letter-spacing: 0.5px; +} + +.componente-desc { + padding: 0.75rem 1rem 0.5rem; + font-size: 0.75rem; + color: var(--muted); + margin: 0; + line-height: 1.4; +} + +.componente-toggle { + padding: 0.5rem 1rem 0.75rem; + font-size: 0.7rem; + color: var(--accent-2); + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 600; +} + +.componente-detalhes { + padding: 0 1rem 0.75rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + animation: slideDown 200ms ease; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.detalhe-item { + background: rgba(0, 0, 0, 0.2); + border-radius: 8px; + padding: 0.65rem 0.75rem; +} + +.detalhe-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.4rem; +} + +.detalhe-label { + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text); +} + +.detalhe-valor { + font-size: 0.9rem; + font-weight: 700; +} + +.detalhe-barra-container { + height: 3px; + background: rgba(255, 255, 255, 0.1); + border-radius: 2px; + overflow: hidden; + margin-bottom: 0.5rem; +} + +.detalhe-barra { + height: 100%; + border-radius: 2px; + transition: width 400ms ease; +} + +.detalhe-formula { + font-size: 0.72rem; + color: var(--text); + font-family: 'SF Mono', 'Fira Code', monospace; + background: rgba(255, 255, 255, 0.05); + padding: 0.35rem 0.5rem; + border-radius: 4px; + margin-bottom: 0.35rem; +} + +.detalhe-explicacao { + font-size: 0.68rem; + color: var(--muted); + line-height: 1.4; +} + +.formula-resumo { + margin-top: 1.25rem; + padding: 1rem; + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--stroke); + border-radius: 10px; +} + +.formula-linha { + display: flex; + align-items: center; + justify-content: center; + gap: 0.75rem; + flex-wrap: wrap; +} + +.formula-comp { + font-size: 0.85rem; + font-weight: 600; + padding: 0.35rem 0.65rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 6px; +} + +.formula-op { + font-size: 1rem; + color: var(--muted); + font-weight: 300; +} + +.formula-total { + font-size: 1.4rem; + font-weight: 800; + background: linear-gradient(120deg, var(--accent), var(--accent-2)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + padding: 0.25rem 0.75rem; + border: 2px solid rgba(79, 70, 229, 0.4); + border-radius: 8px; +} + +@media (max-width: 1100px) { + .componentes-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 640px) { + .componentes-grid { + grid-template-columns: 1fr; + } + + .calculo-header { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + + .formula-linha { + gap: 0.5rem; + } + + .formula-comp { + font-size: 0.75rem; + padding: 0.25rem 0.5rem; + } + + .formula-total { + font-size: 1.1rem; + } +} diff --git a/frontend/src/components/CalculoDetalhado.jsx b/frontend/src/components/CalculoDetalhado.jsx new file mode 100644 index 0000000..f093cfe --- /dev/null +++ b/frontend/src/components/CalculoDetalhado.jsx @@ -0,0 +1,280 @@ +import React, { useState } from 'react'; +import './CalculoDetalhado.css'; + +const CalculoDetalhado = ({ consultor }) => { + const [componenteAtivo, setComponenteAtivo] = useState(null); + const { pontuacao } = consultor; + + const toggleComponente = (comp, e) => { + e.stopPropagation(); + setComponenteAtivo(componenteAtivo === comp ? null : comp); + }; + + const calcularProgresso = (valor, maximo) => Math.min((valor / maximo) * 100, 100); + + const componentes = [ + { + id: 'a', + nome: 'Coordenação CAPES', + dados: pontuacao.componente_a, + max: 450, + cor: 'var(--accent-2)', + corGradient: 'linear-gradient(135deg, #6366f1, #4f46e5)', + descricao: 'Funções de coordenação exercidas na CAPES', + detalhes: [ + { + label: 'Base', + valor: pontuacao.componente_a.base, + max: 200, + formula: inferirFormulaBase(pontuacao.componente_a.base), + explicacao: 'Pontuação pelo tipo de coordenação: CA=200, CAJ=150, CAJ-MP=120, CAM=100' + }, + { + label: 'Tempo', + valor: pontuacao.componente_a.tempo, + max: 100, + formula: inferirFormulaTempo(pontuacao.componente_a.tempo, pontuacao.componente_a.base), + explicacao: 'Anos completos × multiplicador (CA=10, CAJ=8, CAJ-MP=6, CAM=5)' + }, + { + label: 'Áreas', + valor: pontuacao.componente_a.extras, + max: 100, + formula: pontuacao.componente_a.extras > 0 ? `${pontuacao.componente_a.extras / 20} áreas extras × 20 pts` : 'Nenhuma área adicional', + explicacao: 'Cada área adicional coordenada vale 20 pts (máx 100)' + }, + { + label: 'Bônus Ativo', + valor: pontuacao.componente_a.bonus, + max: 30, + formula: pontuacao.componente_a.bonus > 0 ? `Coordenador ativo → +${pontuacao.componente_a.bonus} pts` : 'Não está ativo', + explicacao: 'Bônus por exercer função atualmente: CA=30, CAJ=20, CAJ-MP=15, CAM=10' + }, + { + label: 'Retorno', + valor: pontuacao.componente_a.retorno || 0, + max: 20, + formula: (pontuacao.componente_a.retorno || 0) > 0 ? 'Retornou à função → +20 pts' : 'Sem retorno registrado', + explicacao: 'Bônus único por retornar a exercer coordenação' + } + ] + }, + { + id: 'b', + nome: 'Coordenação PPG', + dados: pontuacao.componente_b, + max: 180, + cor: 'var(--success)', + corGradient: 'linear-gradient(135deg, #22c55e, #16a34a)', + descricao: 'Coordenação de programas de pós-graduação', + detalhes: [ + { + label: 'Base', + valor: pontuacao.componente_b.base, + max: 70, + formula: pontuacao.componente_b.base > 0 ? 'Coordenador de PPG → 70 pts' : 'Não é coordenador', + explicacao: 'Pontuação base por ser coordenador de programa' + }, + { + label: 'Tempo', + valor: pontuacao.componente_b.tempo, + max: 50, + formula: pontuacao.componente_b.tempo > 0 ? `${pontuacao.componente_b.tempo / 5} anos × 5 pts` : 'Sem tempo registrado', + explicacao: 'Cada ano completo de coordenação vale 5 pts (máx 50)' + }, + { + label: 'Programas', + valor: pontuacao.componente_b.extras, + max: 40, + formula: pontuacao.componente_b.extras > 0 ? `${pontuacao.componente_b.extras / 20} programas extras × 20 pts` : 'Apenas 1 programa', + explicacao: 'Cada programa adicional coordenado vale 20 pts (máx 40)' + }, + { + label: 'Nota PPG', + valor: pontuacao.componente_b.bonus, + max: 20, + formula: inferirFormulaNota(pontuacao.componente_b.bonus), + explicacao: 'Maior nota CAPES: 7=20pts, 6=15pts, 5=10pts, 4=5pts, 3=0pts' + } + ] + }, + { + id: 'c', + nome: 'Consultoria', + dados: pontuacao.componente_c, + max: 230, + cor: 'var(--gold)', + corGradient: 'linear-gradient(135deg, #fbbf24, #f59e0b)', + descricao: 'Atuação como consultor CAPES', + detalhes: [ + { + label: 'Base', + valor: pontuacao.componente_c.base, + max: 150, + formula: pontuacao.componente_c.base === 150 ? 'Consultor ativo → 150 pts' : pontuacao.componente_c.base === 100 ? 'Consultor histórico → 100 pts' : 'Não é consultor', + explicacao: 'Ativo (eventos recentes) = 150 pts, Histórico = 100 pts' + }, + { + label: 'Tempo', + valor: pontuacao.componente_c.tempo, + max: 50, + formula: pontuacao.componente_c.tempo > 0 ? `${pontuacao.componente_c.tempo / 5} anos × 5 pts` : 'Sem tempo registrado', + explicacao: 'Anos desde primeiro evento × 5 pts (máx 50)' + }, + { + label: 'Bônus', + valor: pontuacao.componente_c.bonus, + max: 30, + formula: inferirFormulaBonusConsultoria(pontuacao.componente_c.bonus), + explicacao: 'Continuidade (3a=5, 5a=10, 8a+=15) + Retorno (15 pts)' + } + ] + }, + { + id: 'd', + nome: 'Premiações', + dados: pontuacao.componente_d, + max: 180, + cor: 'var(--bronze)', + corGradient: 'linear-gradient(135deg, #fb923c, #ea580c)', + descricao: 'Premiações, avaliações e inscrições em prêmios', + detalhes: [ + { + label: 'Total', + valor: pontuacao.componente_d.base, + max: 180, + formula: inferirFormulaPremiacoes(pontuacao.componente_d.base, consultor.premiacoes), + explicacao: 'Premiação=60pts, Avaliação=40pts (máx 20), Inscrição=20pts' + } + ] + } + ]; + + return ( +
+
+

Detalhamento do Cálculo

+ + Total: {pontuacao.pontuacao_total} pts + +
+ +
+ {componentes.map((comp) => ( +
toggleComponente(comp.id, e)} + > +
+
+ {comp.id.toUpperCase()} + {comp.nome} +
+
+ {comp.dados.total} + /{comp.max} +
+
+ +
+
+ {comp.dados.total >= comp.max && ( + TETO + )} +
+ +

{comp.descricao}

+ + {componenteAtivo === comp.id && ( +
+ {comp.detalhes.map((det, idx) => ( +
+
+ {det.label} + + {det.valor > 0 ? `+${det.valor}` : det.valor} + +
+
+
+
+
{det.formula}
+
{det.explicacao}
+
+ ))} +
+ )} + +
+ {componenteAtivo === comp.id ? 'Ocultar detalhes' : 'Ver detalhes'} +
+
+ ))} +
+ +
+
+ A: {pontuacao.componente_a.total} + + + B: {pontuacao.componente_b.total} + + + C: {pontuacao.componente_c.total} + + + D: {pontuacao.componente_d.total} + = + {pontuacao.pontuacao_total} +
+
+
+ ); +}; + +function inferirFormulaBase(base) { + const tipos = { 200: 'CA', 150: 'CAJ', 120: 'CAJ-MP', 100: 'CAM' }; + return tipos[base] ? `Tipo ${tipos[base]} → ${base} pts` : 'Sem coordenação'; +} + +function inferirFormulaTempo(tempo, base) { + if (tempo === 0) return 'Menos de 1 ano completo'; + const mults = { 200: 10, 150: 8, 120: 6, 100: 5 }; + const mult = mults[base] || 10; + const anos = Math.round(tempo / mult); + return `${anos} ano${anos > 1 ? 's' : ''} × ${mult} pts = ${tempo} pts`; +} + +function inferirFormulaNota(bonus) { + const notas = { 20: '7', 15: '6', 10: '5', 5: '4', 0: '3 ou sem nota' }; + return bonus > 0 ? `Nota ${notas[bonus]} → +${bonus} pts` : 'Sem nota ou nota 3'; +} + +function inferirFormulaBonusConsultoria(bonus) { + if (bonus === 0) return 'Sem bônus de continuidade/retorno'; + if (bonus === 30) return 'Continuidade 8+ anos (15) + Retorno (15)'; + if (bonus === 25) return 'Continuidade 5+ anos (10) + Retorno (15)'; + if (bonus === 20) return 'Continuidade 3+ anos (5) + Retorno (15)'; + if (bonus === 15) return 'Continuidade 8+ anos ou Retorno'; + if (bonus === 10) return 'Continuidade 5+ anos'; + if (bonus === 5) return 'Continuidade 3+ anos'; + return `Bônus total: ${bonus} pts`; +} + +function inferirFormulaPremiacoes(total, premiacoes) { + if (!premiacoes || premiacoes.length === 0) return 'Nenhuma premiação registrada'; + const qtd = premiacoes.length; + return `${qtd} premiação${qtd > 1 ? 'ões' : ''} → ${total} pts`; +} + +export default CalculoDetalhado; diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index 2c3506e..cd5df47 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -1,6 +1,55 @@ import React, { useState, useRef, useEffect } from 'react'; import './ConsultorCard.css'; +const FORMULAS = { + componente_a: { + titulo: 'Coordenação CAPES', + base: 'CA=200 | CAJ=150 | CAJ-MP=120 | CAM=100', + tempo: 'CA: 10pts/ano (máx 100)\nCAJ: 8pts/ano (máx 80)\nCAJ-MP: 6pts/ano (máx 60)\nCAM: 5pts/ano (máx 50)', + extras: '20 pts por área adicional (máx 100)', + bonus: 'Bônus atualidade:\nCA=30 | CAJ=20 | CAJ-MP=15 | CAM=10', + retorno: '+20 pts se retornou à coordenação', + total: 'Base + Tempo + Extras + Bônus + Retorno\n(máx 450 pts)', + }, + componente_b: { + titulo: 'Coordenação PPG', + base: '70 pts por ser coordenador de programa', + tempo: '5 pts por ano completo (máx 50)', + extras: '20 pts por programa adicional (máx 40)', + bonus: 'Nota CAPES do PPG (máx 20): 7=20 | 6=15 | 5=10 | 4=5 | 3=0', + retorno: '', + total: 'Base + Tempo + Extras + Nota (máx 180 pts)', + }, + componente_c: { + titulo: 'Consultoria', + base: 'Ativo (recente): 150 pts | Histórico/Falecido: 100 pts', + tempo: '5 pts por ano de consultoria (máx 50)', + extras: 'Extras: não se aplicam (0)', + bonus: 'Continuidade: 3 anos=+5 | 5 anos=+10 | 8+ anos=+15', + retorno: '+15 pts se retornou à consultoria', + total: 'Base + Tempo + Bônus + Retorno (máx 230 pts)', + }, + componente_d: { + titulo: 'Premiações', + base: 'Soma dos pontos das premiações', + tempo: '', + extras: 'Avaliação (avaliador) soma até 20 pts; demais pontos somam à base', + bonus: '', + retorno: '', + total: 'Pontos totais das premiações (teto 180 pts)', + }, +}; + +const ScoreItemWithTooltip = ({ value, label, formula, style }) => ( +
+
+
{value}
+
{label}
+
+ {formula &&
{formula}
} +
+); + const ConsultorCard = ({ consultor, highlight }) => { const [expanded, setExpanded] = useState(false); const cardRef = useRef(null); @@ -27,6 +76,19 @@ const ConsultorCard = ({ consultor, highlight }) => { const { pontuacao } = consultor; const { consultoria } = consultor; + const temPPGDetalhado = (consultor.coordenacoes_programas || []).length > 0; + + const formulasB = temPPGDetalhado + ? FORMULAS.componente_b + : { + titulo: 'Coordenação PPG', + base: `Total apurado (sem detalhamento): ${pontuacao.componente_b.total} pts`, + tempo: '', + extras: '', + bonus: '', + retorno: '', + total: `Total ${pontuacao.componente_b.total} pts`, + }; return (
setExpanded(!expanded)}> @@ -77,33 +139,36 @@ const ConsultorCard = ({ consultor, highlight }) => {

Pontuação Total

-
-
0 ? 'var(--accent-2)' : 'var(--muted)' }}> - {pontuacao.componente_a.total} + 0 ? 'var(--accent-2)' : 'var(--muted)' }} + /> + 0 ? 'var(--success)' : 'var(--muted)' }} + /> + 0 ? 'var(--gold)' : 'var(--muted)' }} + /> + 0 ? 'var(--bronze)' : 'var(--muted)' }} + /> +
+
+
{pontuacao.pontuacao_total}
+
TOTAL
-
COMP A
-
-
-
0 ? 'var(--success)' : 'var(--muted)' }}> - {pontuacao.componente_b.total} -
-
COMP B
-
-
-
0 ? 'var(--gold)' : 'var(--muted)' }}> - {pontuacao.componente_c.total} -
-
COMP C
-
-
-
0 ? 'var(--bronze)' : 'var(--muted)' }}> - {pontuacao.componente_d.total} -
-
COMP D
-
-
-
{pontuacao.pontuacao_total}
-
TOTAL
+
Comp A + Comp B + Comp C + Comp D
@@ -112,24 +177,28 @@ const ConsultorCard = ({ consultor, highlight }) => { titulo="A - Coordenação CAPES" componente={pontuacao.componente_a} cor="var(--accent-2)" + formulas={FORMULAS.componente_a} />
@@ -188,35 +257,23 @@ const ConsultorCard = ({ consultor, highlight }) => { ); }; -const ComponenteDetalhes = ({ titulo, componente, cor }) => ( +const ComponenteDetalhes = ({ titulo, componente, cor, formulas }) => (

{titulo}

-
-
{componente.base}
-
BASE
-
-
-
{componente.tempo}
-
TEMPO
-
-
-
{componente.extras}
-
EXTRAS
-
-
-
{componente.bonus}
-
BÔNUS
-
+ + + + {componente.retorno > 0 && ( -
-
{componente.retorno}
-
RETORNO
-
+ )} -
-
{componente.total}
-
TOTAL
+
+
+
{componente.total}
+
TOTAL
+
+ {formulas?.total &&
{formulas.total}
}
diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 51bf888..0c43d22 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -8,6 +8,43 @@ const api = axios.create({ timeout: 180000, }); +const calcularComponenteB = (coordenacoesProgramas = []) => { + if (!coordenacoesProgramas.length) { + return { base: 0, tempo: 0, extras: 0, bonus: 0, total: 0 }; + } + + const agora = new Date(); + const base = 70; + + let anosTotais = 0; + coordenacoesProgramas.forEach((coord) => { + const inicio = coord.periodo?.inicio ? new Date(coord.periodo.inicio) : null; + const fim = coord.periodo?.fim ? new Date(coord.periodo.fim) : agora; + if (inicio && fim >= inicio) { + const diffAnos = Math.floor((fim - inicio) / (365 * 24 * 60 * 60 * 1000)); + anosTotais += diffAnos; + } + }); + const tempo = Math.min(anosTotais * 5, 50); + + const programasDistintos = new Set( + coordenacoesProgramas.map((c) => c.id_programa || c.codigo_programa || c.nome_programa) + ).size; + const extras = programasDistintos > 1 ? Math.min((programasDistintos - 1) * 20, 40) : 0; + + let maiorNota = 0; + coordenacoesProgramas.forEach((coord) => { + const n = String(coord.nota_ppg || '').trim(); + if (['7', '6', '5', '4', '3'].includes(n)) { + maiorNota = Math.max(maiorNota, parseInt(n, 10)); + } + }); + const bonus = ({ 7: 20, 6: 15, 5: 10, 4: 5, 3: 0 }[maiorNota] ?? 0); + + const total = base + tempo + extras + bonus; + return { base, tempo, extras, bonus, total }; +}; + export const rankingService = { async getRanking(page = 1, size = 100) { // Usa ranking paginado (Oracle) para percorrer os 350k @@ -43,6 +80,20 @@ export const rankingService = { periodo: mapPeriodo(coord), })); + let compB; + if (coordenacoesProgramas.length > 0) { + compB = calcularComponenteB(coordenacoesProgramas); + } else { + const totalB = Number(c.componente_b || 0); + compB = { + base: totalB > 0 ? totalB : 0, + tempo: 0, + extras: 0, + bonus: 0, + total: totalB, + }; + } + return { id_pessoa: c.id_pessoa, nome: c.nome, @@ -50,7 +101,7 @@ export const rankingService = { posicao: c.posicao, pontuacao_total: c.pontuacao_total, componente_a: c.componente_a, - componente_b: c.componente_b, + componente_b: compB.total, componente_c: c.componente_c, componente_d: c.componente_d, ativo: c.ativo, @@ -59,7 +110,14 @@ export const rankingService = { pontuacao: { pontuacao_total: c.pontuacao_total, componente_a: { base: c.componente_a, tempo: 0, extras: 0, bonus: 0, retorno: 0, total: c.componente_a }, - componente_b: { base: c.componente_b, tempo: 0, extras: 0, bonus: 0, retorno: 0, total: c.componente_b }, + componente_b: { + base: compB.base, + tempo: compB.tempo, + extras: compB.extras, + bonus: compB.bonus, + retorno: 0, + total: compB.total, + }, componente_c: { base: c.componente_c, tempo: 0, extras: 0, bonus: 0, retorno: 0, total: c.componente_c }, componente_d: { base: c.componente_d, tempo: 0, extras: 0, bonus: 0, retorno: 0, total: c.componente_d }, },