Revisão da visualização do Balancete

This commit is contained in:
Gabriel 2026-09-18 09:29:16 -03:00
parent 530e0e85e3
commit a8db64d949
7 changed files with 235 additions and 67 deletions

View File

@ -82,7 +82,11 @@
"Bash(\"./.venv/Scripts/python.exe\" manage.py makemigrations --check --dry-run)", "Bash(\"./.venv/Scripts/python.exe\" manage.py makemigrations --check --dry-run)",
"Bash(\"./.venv/Scripts/pip.exe\" install *)", "Bash(\"./.venv/Scripts/pip.exe\" install *)",
"PowerShell(node --check \"static/js/dashboard-contabil.js\")", "PowerShell(node --check \"static/js/dashboard-contabil.js\")",
"Bash(.venv/Scripts/python.exe -m py_compile portal_api/dashboard_contabil/regras.py)" "Bash(.venv/Scripts/python.exe -m py_compile portal_api/dashboard_contabil/regras.py)",
"Bash(node -e \"require\\('vm'\\).Script\\(require\\('fs'\\).readFileSync\\('static/js/dashboard-contabil.js','utf8'\\)\\)\")",
"Bash(.venv/Scripts/python.exe \"C:/Users/Depaula/AppData/Local/Temp/claude/c--Users-Depaula-Documents-Portal/cf4a6caf-9798-42a6-bbc8-a8ffbb3debf6/scratchpad/sim_destaque.py\")",
"Bash(PYTHONPATH=\"C:\\\\Users\\\\Depaula\\\\Documents\\\\Portal\" .venv/Scripts/python.exe \"C:/Users/Depaula/AppData/Local/Temp/claude/c--Users-Depaula-Documents-Portal/cf4a6caf-9798-42a6-bbc8-a8ffbb3debf6/scratchpad/sim_destaque.py\")",
"Bash(PYTHONPATH=\"C:\\\\Users\\\\Depaula\\\\Documents\\\\Portal\" .venv/Scripts/python.exe \"C:/Users/Depaula/AppData/Local/Temp/claude/c--Users-Depaula-Documents-Portal/cf4a6caf-9798-42a6-bbc8-a8ffbb3debf6/scratchpad/sim_expandir_tudo.py\")"
] ]
} }
} }

View File

@ -318,3 +318,26 @@ Só vale pra achados gerados dali em diante — um achado já persistido mantém
Usuário reportou, com print de uma árvore de 3 níveis (Balancete), que uma conta "mãe" (ex. "CAIXA E EQUIVALENTES DE CAIXA") não ficava verde mesmo depois de toda conta-folha dentro dela ("netos", ex. os 4 bancos dentro de "DEPÓSITOS BANCÁRIOS A VISTA") já estarem validadas uma a uma — mesmo a conta "filha" intermediária ("DEPÓSITOS BANCÁRIOS A VISTA") já aparecendo corretamente verde. Causa raiz: `dcEstadoValidacaoGrupo()` (`dashboard-contabil.js`, tri-state do botão "validado" de uma sintética, ver rodada de "Tri-state numa conta/linha sintética") decidia o estado "completo" checando `descendentes.every(d => d.validado)` sobre **todos** os descendentes (filhos, netos, bisnetos), não só os de folha — quando os netos são marcados diretamente (sem clicar na própria "filha" intermediária pra cascatear), o campo `validado` da "filha" no banco continua `False` mesmo que ela já apareça "completa" visualmente (calculada a cada render); a "mãe", ao olhar todos os descendentes, incluía essa "filha" com `validado=False` e nunca fechava o grupo. Usuário reportou, com print de uma árvore de 3 níveis (Balancete), que uma conta "mãe" (ex. "CAIXA E EQUIVALENTES DE CAIXA") não ficava verde mesmo depois de toda conta-folha dentro dela ("netos", ex. os 4 bancos dentro de "DEPÓSITOS BANCÁRIOS A VISTA") já estarem validadas uma a uma — mesmo a conta "filha" intermediária ("DEPÓSITOS BANCÁRIOS A VISTA") já aparecendo corretamente verde. Causa raiz: `dcEstadoValidacaoGrupo()` (`dashboard-contabil.js`, tri-state do botão "validado" de uma sintética, ver rodada de "Tri-state numa conta/linha sintética") decidia o estado "completo" checando `descendentes.every(d => d.validado)` sobre **todos** os descendentes (filhos, netos, bisnetos), não só os de folha — quando os netos são marcados diretamente (sem clicar na própria "filha" intermediária pra cascatear), o campo `validado` da "filha" no banco continua `False` mesmo que ela já apareça "completa" visualmente (calculada a cada render); a "mãe", ao olhar todos os descendentes, incluía essa "filha" com `validado=False` e nunca fechava o grupo.
Corrigido restringindo o critério de "completo" só aos descendentes **folha** (sem filhos) — `dcDescendentes()` ganhou um quarto parâmetro opcional (`somenteFolhas`), reaproveitando o mesmo cálculo de "tem filho" já usado por `temFilhos`; `dcEstadoValidacaoGrupo(item, descendentes, descendentesFolhas)` passou a checar `descendentesFolhas.every(d => d.validado)` pro estado "completo", mantendo `descendentes` (todos) pro "parcial" (uma sintética marcada sozinha, sem cascatear, ainda deve sinalizar "em andamento" num ancestral). Os 4 pontos que calculam o estado (`dcValidadoInfo()` + os 3 handlers de clique — Balancete/D.R.E./Análise Vertical) foram atualizados pra passar os dois conjuntos. Puramente lógica de exibição/cálculo no frontend — nenhuma mudança de model/endpoint. Corrigido restringindo o critério de "completo" só aos descendentes **folha** (sem filhos) — `dcDescendentes()` ganhou um quarto parâmetro opcional (`somenteFolhas`), reaproveitando o mesmo cálculo de "tem filho" já usado por `temFilhos`; `dcEstadoValidacaoGrupo(item, descendentes, descendentesFolhas)` passou a checar `descendentesFolhas.every(d => d.validado)` pro estado "completo", mantendo `descendentes` (todos) pro "parcial" (uma sintética marcada sozinha, sem cascatear, ainda deve sinalizar "em andamento" num ancestral). Os 4 pontos que calculam o estado (`dcValidadoInfo()` + os 3 handlers de clique — Balancete/D.R.E./Análise Vertical) foram atualizados pra passar os dois conjuntos. Puramente lógica de exibição/cálculo no frontend — nenhuma mudança de model/endpoint.
### 140. Bug real: destaque de um grupo já expandido desaparecia ao expandir outro
Usuário reportou, com print de uma árvore de 3 níveis (Balancete), que expandir uma segunda conta apagava visualmente o destaque dourado da primeira já expandida — o esperado era as duas ficarem destacadas ao mesmo tempo, pra facilitar a validação em sequência de várias contas abertas de uma vez. Causa: o destaque das linhas recém-reveladas ao expandir (ver rodada "Destaque acompanha o último grupo expandido") era desenhado pra **substituir** a leva anterior por design — cada expandir trocava `dcContasDestaque`/`dcDreDestaque`/`dcAvDestaque` pelos filhos diretos do grupo recém-aberto, empilhando o destaque anterior numa pilha (`dc*DestaqueHistorico`) só restaurada ao recolher o mesmo grupo.
**Duas tentativas falhas antes de acertar** (as duas reportadas pelo usuário testando em produção — sempre com hard refresh e restart do servidor, então nunca foi cache):
1. **Somar os filhos revelados ao `Set` de destaque já existente**: não mudou nada visualmente. Causa raiz, só encontrada depois de simular o algoritmo em Python contra as contas reais da apuração do usuário: o destaque inicial (`dcUltimaLevaVisivel`) já marca **praticamente toda conta de nível ≥ 2** — todas nascem recolhidas pelo limiar padrão, então cada uma conta como "fim de ramo visível". Somar os filhos revelados a essa base deixava a tabela inteira destacada, e com tudo destacado nada se destaca. O comportamento original só parecia funcionar porque o "substituir" limpava todo o resto da tabela junto.
2. **Destacar a própria conta expandida** em vez dos filhos: o usuário esclareceu que destacar os filhos sempre esteve certo — a única mudança pedida era acumular mais de um grupo.
**Correção final**: `dcDestaqueGrupos(itens, nivelFn, colapsadas, expandidos)` (nova) passa a derivar o destaque sempre do zero, a partir de um `Set` novo por árvore (`dcContasExpandidos`/`dcDreExpandidos`/`dcAvExpandidos`) com os grupos que o contador expandiu e que continuam abertos: sem nenhum grupo aberto, vale a leva padrão de sempre (`dcUltimaLevaVisivel`); com algum aberto, o destaque é **só** a união dos filhos diretos desses grupos, descartando a leva padrão — é esse descarte que devolve o contraste. Recolher um grupo remove ele **e todos os descendentes** dele do conjunto (um grupo aninhado que estava aberto dentro dele deixa de contar), garantindo que fechar tudo devolva exatamente o estado inicial. As três pilhas de histórico (`dc*DestaqueHistorico`) foram removidas — recolher não precisa mais restaurar nada de uma pilha.
Validado simulando o algoritmo em Python contra as contas reais da apuração `2021`/`08-2026`, reproduzindo o roteiro exato descrito pelo usuário: expandir "CAIXA E EQUIVALENTES DE CAIXA" destaca só seus 2 filhos (resto da tabela limpo); expandir "CLIENTES" em seguida mantém os 2 e soma "DUPLICATAS A RECEBER"; fechar "CLIENTES" volta aos 2 primeiros; fechar "CAIXA" devolve exatamente o conjunto de destaque inicial (igualdade entre os dois conjuntos conferida). Mesmo ajuste replicado em `pidDcrArvore()` (`dashboard-contabil-relatorio.html`), que reimplementa esta lógica em JS puro pro relatório "Gerar Dashboard" — os dois lados são mantidos manualmente em sincronia, sem fonte única (um é SPA, o outro HTML estático renderizado uma vez). Puramente lógica de exibição no frontend, sem mudança de model/endpoint. Detalhe técnico completo no `CLAUDE.md` desta pasta.
**Lição registrada na memória do projeto**: nas duas primeiras tentativas a resposta ao "não funcionou" incluiu a hipótese de cache do navegador/JS antigo em memória — o usuário deixou claro que sempre testa com hard refresh e restart do servidor antes de reportar. A causa era sempre bug real, e só apareceu quando o algoritmo foi **simulado com os dados reais** (script Python ad-hoc lendo as contas da apuração pelo ORM) em vez de analisado estaticamente.
### 141. Botão "+" (expandir tudo) no cabeçalho das tabelas da revisão
Pedido explícito do usuário: um botão pequeno no canto esquerdo do cabeçalho da primeira coluna que expande todas as contas até o último nível de uma vez. A ideia inicial tinha também um "−" que recolheria um nível por vez, descartada pelo próprio usuário na mesma conversa ("poderia apenas um botão, o de +").
`.dc-expandir-tudo-btn` (`data-dc-expandir-tudo`) nas 3 tabelas da revisão (Balancete, D.R.E. e Análise Vertical — cada uma com o seu, já que cada árvore tem estado de colapso próprio, mesmo motivo do botão "Restaurar formatação" que já existia), dentro do novo modificador `.dc-th-linha--inicio` (só troca o `justify-content` pra `flex-start`, já que aqui o botão vem antes do rótulo, e não colado na borda direita como o de restaurar). Não é um toggle: o caminho de volta é o próprio "Restaurar formatação padrão", na última coluna da mesma tabela.
Detalhe não-óbvio da implementação: expandir tudo **zera** o conjunto de grupos expandidos (`dc*Expandidos`) em vez de populá-lo com todos os grupos — com ele vazio, o destaque volta a sair de `dcUltimaLevaVisivel()`, que sem nada recolhido marca exatamente as folhas (as contas analíticas finais). Conferido contra a apuração real `2021`/`08-2026`: 133 contas visíveis e 74 destacadas, exatamente as 74 contas sem filhos. Popular o conjunto com todos os grupos destacaria quase toda linha da tabela, recaindo no mesmo "tudo destacado, nada se destaca" da rodada 140. Só frontend (HTML/CSS/JS), sem mudança de model/endpoint; o relatório "Gerar Dashboard" não ganhou esse botão nesta rodada.

View File

@ -70,7 +70,23 @@ Diferente de `IndicadorApuracaoViewSet`/`ImportacaoPlanoSaudeViewSet` (onde a ch
**Nasce recolhida a partir do 3º segmento do código** (pedido explícito do usuário, pra reduzir a poluição visual de uma apuração com muitas contas analíticas — limiar ajustado numa rodada seguinte, ver abaixo): em vez de `renderRevisao()` reiniciar os dois `Set()` vazios (tudo expandido), `dcColapsoPadrao(itens, nivelFn)` os pré-popula com os ids de todo item que tem filhos **e** está no nível `PID_DC_NIVEL_ABERTO_PADRAO` (`2`) ou além — ex. a conta `1.01.01` (3 segmentos, nível 2) aparece aberta, mas seus filhos (`1.01.01.001`, nível 3) ficam ocultos até o contador clicar pra expandir; se expandido, um filho de nível 3 que também tenha netos nasce recolhido de novo pelo mesmo critério, então "nível 3 em diante" precisa sempre de um clique a mais, não só a primeira camada. Mesmo limiar aplicado à DRE, sobre `Math.max(0, linha.nivel)` — decisão confirmada com o usuário (a princípio o pedido citava só o Balancete, mas como a DRE reaproveita o mesmo algoritmo, o mesmo comportamento faz sentido nela também). O relatório "Gerar Dashboard" replica esse mesmo estado inicial (ver abaixo) — não é só a tela de revisão. `PID_DC_NIVEL_ABERTO_PADRAO` (JS) e `_CONTABIL_NIVEL_ABERTO_PADRAO` (`views.py`) são a mesma constante conceitual duplicada nos dois lados (um é SPA, o outro HTML renderizado uma vez) — mudar o limiar exige ajustar os dois. **Nasce recolhida a partir do 3º segmento do código** (pedido explícito do usuário, pra reduzir a poluição visual de uma apuração com muitas contas analíticas — limiar ajustado numa rodada seguinte, ver abaixo): em vez de `renderRevisao()` reiniciar os dois `Set()` vazios (tudo expandido), `dcColapsoPadrao(itens, nivelFn)` os pré-popula com os ids de todo item que tem filhos **e** está no nível `PID_DC_NIVEL_ABERTO_PADRAO` (`2`) ou além — ex. a conta `1.01.01` (3 segmentos, nível 2) aparece aberta, mas seus filhos (`1.01.01.001`, nível 3) ficam ocultos até o contador clicar pra expandir; se expandido, um filho de nível 3 que também tenha netos nasce recolhido de novo pelo mesmo critério, então "nível 3 em diante" precisa sempre de um clique a mais, não só a primeira camada. Mesmo limiar aplicado à DRE, sobre `Math.max(0, linha.nivel)` — decisão confirmada com o usuário (a princípio o pedido citava só o Balancete, mas como a DRE reaproveita o mesmo algoritmo, o mesmo comportamento faz sentido nela também). O relatório "Gerar Dashboard" replica esse mesmo estado inicial (ver abaixo) — não é só a tela de revisão. `PID_DC_NIVEL_ABERTO_PADRAO` (JS) e `_CONTABIL_NIVEL_ABERTO_PADRAO` (`views.py`) são a mesma constante conceitual duplicada nos dois lados (um é SPA, o outro HTML renderizado uma vez) — mudar o limiar exige ajustar os dois.
**Destaque acompanha o último grupo expandido** (pedido explícito do usuário, mesma rodada do ajuste de limiar acima): ao expandir uma conta/linha, as linhas que acabaram de ficar visíveis (só os filhos **diretos**, não os netos — que continuam recolhidos pelo limiar acima) ganham um realce dourado (`.dc-conta-row--destaque`); se o contador expandir uma dessas linhas em seguida, o realce se move pros filhos dela, e o grupo destacado antes volta pra cor padrão — nunca acumula mais de uma "leva" destacada ao mesmo tempo. `dcContasDestaque`/`dcDreDestaque` (dois `Set()`, um por árvore, resetados em `renderRevisao()`) guardam só a leva mais recente; `dcFilhosDiretos(itens, nivelFn, id)` (função genérica, reaproveitada pelas duas árvores) varre a lista plana a partir do índice do item expandido e para no primeiro item de nível igual ou menor (fim do grupo), coletando só os de nível exatamente `pai+1`. O handler de clique do toggle decide o destino do destaque **antes** de checar se a ação foi expandir ou recolher (`estavaColapsada = colapsadas.has(id)` guardado antes de mutar o `Set`): expandir substitui `dcContasDestaque`/`dcDreDestaque` pelos filhos diretos recém-revelados; recolher só limpa (`new Set()`), já que nada novo foi exposto. Puramente visual, sem persistência — reseta a cada apuração aberta, sem afetar nenhum dado gravado. **Destaque acompanha todo grupo já expandido** (pedido explícito do usuário, mesma rodada do ajuste de limiar acima, com o mecanismo revisado na rodada 140 abaixo): ao expandir uma conta/linha, as linhas que acabaram de ficar visíveis (só os filhos **diretos**, não os netos — que continuam recolhidos pelo limiar acima) ganham um realce dourado (`.dc-conta-row--destaque`), e **mais nada** fica destacado enquanto houver grupo aberto. Dois `Set()` por árvore (os 6 resetados em `renderRevisao()` e no botão "Restaurar formatação"): `dcContasExpandidos`/`dcDreExpandidos`/`dcAvExpandidos` guardam os ids dos grupos que o contador expandiu manualmente e que continuam expandidos, e `dcContasDestaque`/`dcDreDestaque`/`dcAvDestaque` guardam o resultado **derivado** (as linhas de fato destacadas), sempre recalculado por `dcDestaqueGrupos(itens, nivelFn, colapsadas, expandidos)`:
- `expandidos` vazio (nada aberto ainda) → vale a leva padrão, `dcUltimaLevaVisivel(itens, nivelFn, colapsadas)`, mesma coisa do estado inicial de uma apuração recém-aberta.
- `expandidos` com algo → destaque é **só** a união de `dcFilhosDiretos(itens, nivelFn, id)` de cada grupo aberto (função genérica reaproveitada pelas 3 árvores: varre a lista plana a partir do índice do item e para no primeiro item de nível igual ou menor, coletando só os de nível exatamente `pai+1`), descartando a leva padrão por completo.
O handler de clique do toggle guarda `estavaColapsada = colapsadas.has(id)` **antes** de mutar o `Set` de colapso, e então: expandir adiciona o `id` a `expandidos`; recolher remove o `id` **e todo descendente dele** (`dcDescendentes()`) de `expandidos` — um grupo aninhado que estava aberto dentro do que acabou de fechar deixa de contar pro destaque, senão o destaque apontaria pra linhas agora invisíveis e a árvore nunca voltaria ao estado padrão. Puramente visual, sem persistência — reseta a cada apuração aberta, sem afetar nenhum dado gravado.
**Bug real, rodada 140 — destaque de um grupo sumia ao expandir outro, e as duas primeiras correções não resolveram**: na versão original, expandir SUBSTITUÍA o `Set` de destaque pelos filhos diretos do grupo recém-aberto (empilhando o anterior numa pilha, restaurada ao recolher) — usuário reportou, com print de uma árvore de 3 níveis, que expandir uma segunda conta "apagava" o destaque da primeira, quando o esperado era as duas conviverem pra facilitar a validação em sequência. Duas tentativas falhas antes da correção final, ambas reportadas pelo usuário testando em produção (sempre com hard refresh + restart do servidor, então nunca foi cache):
1. **Somar os filhos revelados ao `Set` já existente** (mantendo o que estava): não mudou nada visualmente. Causa, confirmada simulando o algoritmo em Python contra as contas reais da apuração do usuário: o destaque inicial (`dcUltimaLevaVisivel`) já marca **praticamente toda conta de nível ≥ 2** — todas nascem recolhidas pelo limiar padrão, então cada uma é "fim de ramo visível" por si só. Somar os filhos revelados a essa base deixava a tabela inteira destacada, e com tudo destacado nada se destaca. O comportamento original só parecia funcionar porque o "substituir" limpava todo o resto da tabela.
2. **Destacar a própria conta expandida** em vez dos filhos: o usuário esclareceu que destacar os filhos estava certo desde o começo — a única mudança pedida era acumular mais de um grupo.
Correção final é a descrita acima (`dcDestaqueGrupos()` com a base padrão **descartada** enquanto houver grupo aberto). Validada simulando o algoritmo em Python contra as contas reais da apuração `2021`/`08-2026`, reproduzindo o roteiro exato do usuário: expandir "CAIXA E EQUIVALENTES DE CAIXA" destaca só seus 2 filhos (resto da tabela limpo); expandir "CLIENTES" em seguida mantém os 2 primeiros e soma "DUPLICATAS A RECEBER"; fechar "CLIENTES" volta aos 2 primeiros; fechar "CAIXA" devolve exatamente o `Set` de destaque inicial (comparação de igualdade entre os dois conjuntos bateu). Mesmo ajuste replicado em `pidDcrArvore()` (`dashboard-contabil-relatorio.html`, réplica em JS puro desta mesma lógica no relatório "Gerar Dashboard", com `calculaDestaque()`/`expandidos`/`descendentes()` espelhando as funções do JS) — os dois lados são mantidos manualmente em sincronia, ver "Balancete e D.R.E. têm árvore recolhível igual à tela de revisão" abaixo.
**Botão "+" (expandir tudo) no cabeçalho da primeira coluna** (pedido explícito do usuário, rodada seguinte — a ideia inicial incluía um "−" que recolhesse um nível por vez, descartada pelo próprio usuário na mesma conversa, "poderia apenas um botão"): `.dc-expandir-tudo-btn` (`data-dc-expandir-tudo="balancete|dre|analise-vertical"`), dentro de um `.dc-th-linha.dc-th-linha--inicio` (modificador novo, só troca o `justify-content` pra `flex-start` — o botão vem **antes** do rótulo, ao contrário do "Restaurar formatação", que fica colado na borda direita da última coluna). Abre a árvore inteira de uma vez (`dc*Colapsadas = new Set()`), até a conta analítica mais funda, nas 3 tabelas (cada uma com o seu botão, mesmo motivo do botão de restaurar: estados de colapso independentes). **Não é um toggle** — o caminho de volta é o botão "Restaurar formatação padrão" que já existe na coluna "Observação" da mesma tabela.
Ponto não-óbvio: expandir tudo **zera** `dc*Expandidos` em vez de populá-lo com todos os grupos. Com o conjunto vazio, `dcDestaqueGrupos()` cai na leva padrão (`dcUltimaLevaVisivel`), que sem nada recolhido marca exatamente as **folhas** — validado contra a apuração real `2021`/`08-2026`: 133 contas visíveis, 74 destacadas, exatamente as 74 contas sem filhos (igualdade de conjuntos conferida). Popular `dc*Expandidos` com todos os grupos destacaria quase toda linha da tabela, que é o mesmo problema de "tudo destacado, nada se destaca" da tentativa falha nº 1 acima. O cabeçalho da Análise Vertical é montado em JS (`renderAnaliseVerticalHead()`, número de colunas varia com os meses), então lá o botão nasce no template string — nos outros dois é HTML fixo em `dashboard-contabil.html`; os listeners são delegados no `<thead>`, então sobrevivem ao `innerHTML` ser refeito a cada render.
**Card de achado expande a conta usada no apontamento** (`renderAchados()`, pedido explícito do usuário): `achado.conta` (id, já vem no payload de `/api/contabil-apuracoes/{id}/`, junto de `conta_codigo`/`conta_descricao`) é resolvido pra objeto completo procurando em `apuracaoAtual.contas` (`.find((c) => c.id === achado.conta)`) — sem chamada de API extra, já que a apuração inteira (contas + linhas de DRE + achados) já vem de uma vez só nesse endpoint. Só achados vinculados a uma conta específica ganham o botão "Ver conta usada no apontamento" (`.dc-achado-card__toggle-conta`) — as duas regras gerais (`balanceamento_ativo_passivo`/`debito_credito_divergente`, `conta` nulo no model) não têm uma conta única por trás, então não mostram o toggle. Expandido, mostra código/descrição/saldo anterior/débito/crédito/saldo atual da conta (`.dc-achado-card__conta`, um `<dl>` em grid). Estado de expansão (`dcAchadosContaExpandida`, um `Set()` de ids de achado) segue o mesmo padrão de `dcContasColapsadas`/`dcDreColapsadas` — reiniciado em `renderRevisao()`. **Card de achado expande a conta usada no apontamento** (`renderAchados()`, pedido explícito do usuário): `achado.conta` (id, já vem no payload de `/api/contabil-apuracoes/{id}/`, junto de `conta_codigo`/`conta_descricao`) é resolvido pra objeto completo procurando em `apuracaoAtual.contas` (`.find((c) => c.id === achado.conta)`) — sem chamada de API extra, já que a apuração inteira (contas + linhas de DRE + achados) já vem de uma vez só nesse endpoint. Só achados vinculados a uma conta específica ganham o botão "Ver conta usada no apontamento" (`.dc-achado-card__toggle-conta`) — as duas regras gerais (`balanceamento_ativo_passivo`/`debito_credito_divergente`, `conta` nulo no model) não têm uma conta única por trás, então não mostram o toggle. Expandido, mostra código/descrição/saldo anterior/débito/crédito/saldo atual da conta (`.dc-achado-card__conta`, um `<dl>` em grid). Estado de expansão (`dcAchadosContaExpandida`, um `Set()` de ids de achado) segue o mesmo padrão de `dcContasColapsadas`/`dcDreColapsadas` — reiniciado em `renderRevisao()`.

View File

@ -1835,9 +1835,17 @@
gap: var(--space-2); gap: var(--space-2);
} }
/* "Expandir tudo" fica na PRIMEIRA coluna, antes do rótulo (o rótulo é que
vai pra direita do botão), ao contrário de "Restaurar formatação", que
fica na última coluna com o botão empurrado pra borda direita. */
.dc-th-linha--inicio {
justify-content: flex-start;
}
/* Menor que o .icon-btn padrão (38px) — vive dentro de uma célula de /* Menor que o .icon-btn padrão (38px) — vive dentro de uma célula de
cabeçalho de tabela, não pode competir em tamanho com o texto da coluna. */ cabeçalho de tabela, não pode competir em tamanho com o texto da coluna. */
.dc-reset-formatacao-btn { .dc-reset-formatacao-btn,
.dc-expandir-tudo-btn {
width: 22px; width: 22px;
height: 22px; height: 22px;
flex-shrink: 0; flex-shrink: 0;

View File

@ -916,8 +916,8 @@ document.addEventListener("DOMContentLoaded", async () => {
// do usuário pra não abrir a tela inteira na cor de grupo/total. // do usuário pra não abrir a tela inteira na cor de grupo/total.
dcContasDestaque = dcUltimaLevaVisivel(apuracao.contas, dcContaNivel, dcContasColapsadas); dcContasDestaque = dcUltimaLevaVisivel(apuracao.contas, dcContaNivel, dcContasColapsadas);
dcDreDestaque = dcUltimaLevaVisivel(apuracao.linhas_dre, (linha) => Math.max(0, linha.nivel), dcDreColapsadas); dcDreDestaque = dcUltimaLevaVisivel(apuracao.linhas_dre, (linha) => Math.max(0, linha.nivel), dcDreColapsadas);
dcContasDestaqueHistorico = []; dcContasExpandidos = new Set();
dcDreDestaqueHistorico = []; dcDreExpandidos = new Set();
dcObsPainelAberto = { conta: null, dre: null, analise_vertical: null }; dcObsPainelAberto = { conta: null, dre: null, analise_vertical: null };
dcObsEditandoId = null; dcObsEditandoId = null;
dcAchadosContaExpandida = new Set(); dcAchadosContaExpandida = new Set();
@ -946,7 +946,7 @@ document.addEventListener("DOMContentLoaded", async () => {
(linha) => Math.max(0, linha.nivel), (linha) => Math.max(0, linha.nivel),
dcAvColapsadas dcAvColapsadas
); );
dcAvDestaqueHistorico = []; dcAvExpandidos = new Set();
renderAchados(); renderAchados();
renderContas(); renderContas();
@ -1259,22 +1259,43 @@ document.addEventListener("DOMContentLoaded", async () => {
return destaque; return destaque;
} }
// Ids da última "leva" de linhas recém-reveladas por um clique de // Ids das linhas de fato destacadas agora (`.dc-conta-row--destaque`) —
// expandir — pedido explícito do usuário: destacar as contas que // derivado, nunca editado à mão: sai sempre de dcDestaqueGrupos() abaixo.
// aparecem ao expandir um grupo, e mover o destaque (não acumular) pra a
// leva mais recente conforme o contador vai expandindo mais fundo. Cada
// clique de expandir SUBSTITUI esse conjunto, empilhando o que estava
// destacado antes em dc*DestaqueHistorico; um clique de recolher desempilha
// e restaura o destaque anterior (pedido explícito do usuário — fechar uma
// leva revela de novo o destaque de onde se estava antes de abri-la, em vez
// de simplesmente apagar o destaque). Um par conjunto+pilha por árvore, já
// que Balancete e DRE são independentes.
let dcContasDestaque = new Set(); let dcContasDestaque = new Set();
let dcDreDestaque = new Set(); let dcDreDestaque = new Set();
let dcAvDestaque = new Set(); let dcAvDestaque = new Set();
let dcContasDestaqueHistorico = [];
let dcDreDestaqueHistorico = []; // Ids dos grupos que o contador expandiu manualmente e que continuam
let dcAvDestaqueHistorico = []; // expandidos — entram no conjunto ao expandir, saem ao recolher (junto com
// qualquer descendente do grupo recolhido, que volta a ficar invisível).
// Um conjunto por árvore, já que Balancete/DRE/Análise Vertical são
// independentes.
let dcContasExpandidos = new Set();
let dcDreExpandidos = new Set();
let dcAvExpandidos = new Set();
// Destaque atual de uma árvore. Enquanto o contador não tiver expandido
// nada, vale a leva padrão (dcUltimaLevaVisivel — toda linha que esconde
// algo ou é folha genuína). A partir do primeiro clique de expandir, o
// destaque passa a ser **só** a união dos filhos diretos dos grupos
// expandidos manualmente, e mais nada.
//
// Esse "e mais nada" é o ponto: a leva padrão já marca praticamente toda
// conta de nível ≥ 2 (nascem recolhidas, então toda uma é "fim de ramo"),
// então somar os filhos revelados a ela deixava a tabela inteira destacada
// — com tudo destacado, nada se destaca, que foi exatamente o efeito
// reportado pelo usuário ("os filhos não ficaram em destaque"). Limpando o
// resto, sobra o contraste de sempre; a diferença pro comportamento antigo
// é que agora vários grupos abertos convivem, em vez de o mais recente
// substituir o anterior.
function dcDestaqueGrupos(itens, nivelFn, colapsadas, expandidos) {
if (!expandidos.size) return dcUltimaLevaVisivel(itens, nivelFn, colapsadas);
const destaque = new Set();
expandidos.forEach((id) => {
dcFilhosDiretos(itens, nivelFn, id).forEach((filhoId) => destaque.add(filhoId));
});
return destaque;
}
// Filhos DIRETOS de um item (mesmo próximo nível, não os netos) — usado // Filhos DIRETOS de um item (mesmo próximo nível, não os netos) — usado
// pra saber quais linhas acabaram de ficar visíveis ao expandir `id`. // pra saber quais linhas acabaram de ficar visíveis ao expandir `id`.
@ -2132,16 +2153,17 @@ document.addEventListener("DOMContentLoaded", async () => {
if (toggleBtn) { if (toggleBtn) {
const id = Number(toggleBtn.getAttribute("data-dc-conta-toggle")); const id = Number(toggleBtn.getAttribute("data-dc-conta-toggle"));
const estavaColapsada = dcContasColapsadas.has(id); const estavaColapsada = dcContasColapsadas.has(id);
if (estavaColapsada) dcContasColapsadas.delete(id);
else dcContasColapsadas.add(id);
// Expandir empilha o destaque atual e troca pros filhos recém-revelados;
// recolher desempilha e restaura o destaque de antes — ver dcContasDestaque acima.
if (estavaColapsada) { if (estavaColapsada) {
dcContasDestaqueHistorico.push(dcContasDestaque); dcContasColapsadas.delete(id);
dcContasDestaque = dcFilhosDiretos(apuracaoAtual.contas, dcContaNivel, id); dcContasExpandidos.add(id);
} else { } else {
dcContasDestaque = dcContasDestaqueHistorico.pop() || new Set(); dcContasColapsadas.add(id);
dcContasExpandidos.delete(id);
// Um grupo aninhado que estava aberto dentro deste some junto —
// deixa de valer pro destaque enquanto o pai estiver recolhido.
dcDescendentes(apuracaoAtual.contas, dcContaNivel, id).forEach((d) => dcContasExpandidos.delete(d.id));
} }
dcContasDestaque = dcDestaqueGrupos(apuracaoAtual.contas, dcContaNivel, dcContasColapsadas, dcContasExpandidos);
renderContas(); renderContas();
return; return;
} }
@ -2242,15 +2264,16 @@ document.addEventListener("DOMContentLoaded", async () => {
if (toggleBtn) { if (toggleBtn) {
const id = Number(toggleBtn.getAttribute("data-dc-dre-toggle")); const id = Number(toggleBtn.getAttribute("data-dc-dre-toggle"));
const estavaColapsada = dcDreColapsadas.has(id); const estavaColapsada = dcDreColapsadas.has(id);
if (estavaColapsada) dcDreColapsadas.delete(id);
else dcDreColapsadas.add(id);
const nivelFn = (linha) => Math.max(0, linha.nivel); const nivelFn = (linha) => Math.max(0, linha.nivel);
if (estavaColapsada) { if (estavaColapsada) {
dcDreDestaqueHistorico.push(dcDreDestaque); dcDreColapsadas.delete(id);
dcDreDestaque = dcFilhosDiretos(apuracaoAtual.linhas_dre, nivelFn, id); dcDreExpandidos.add(id);
} else { } else {
dcDreDestaque = dcDreDestaqueHistorico.pop() || new Set(); dcDreColapsadas.add(id);
dcDreExpandidos.delete(id);
dcDescendentes(apuracaoAtual.linhas_dre, nivelFn, id).forEach((d) => dcDreExpandidos.delete(d.id));
} }
dcDreDestaque = dcDestaqueGrupos(apuracaoAtual.linhas_dre, nivelFn, dcDreColapsadas, dcDreExpandidos);
renderDre(); renderDre();
return; return;
} }
@ -2281,7 +2304,14 @@ document.addEventListener("DOMContentLoaded", async () => {
const meses = apuracaoAtual.analise_vertical_meses || []; const meses = apuracaoAtual.analise_vertical_meses || [];
const row = document.getElementById("dc-av-head-row"); const row = document.getElementById("dc-av-head-row");
row.innerHTML = ` row.innerHTML = `
<th>Descrição</th> <th>
<span class="dc-th-linha dc-th-linha--inicio">
<button type="button" class="icon-btn dc-expandir-tudo-btn" data-dc-expandir-tudo="analise-vertical" aria-label="Expandir todas as linhas da Análise Vertical" title="Expandir todas as linhas até o último nível">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
</button>
Descrição
</span>
</th>
${meses ${meses
.map((mes) => { .map((mes) => {
const label = pidDcEscapeHtml(pidDcFormatMesAnaliseVertical(mes)); const label = pidDcEscapeHtml(pidDcFormatMesAnaliseVertical(mes));
@ -2379,15 +2409,21 @@ document.addEventListener("DOMContentLoaded", async () => {
if (toggleBtn) { if (toggleBtn) {
const id = Number(toggleBtn.getAttribute("data-dc-av-toggle")); const id = Number(toggleBtn.getAttribute("data-dc-av-toggle"));
const estavaColapsada = dcAvColapsadas.has(id); const estavaColapsada = dcAvColapsadas.has(id);
if (estavaColapsada) dcAvColapsadas.delete(id);
else dcAvColapsadas.add(id);
const nivelFn = (linha) => Math.max(0, linha.nivel); const nivelFn = (linha) => Math.max(0, linha.nivel);
if (estavaColapsada) { if (estavaColapsada) {
dcAvDestaqueHistorico.push(dcAvDestaque); dcAvColapsadas.delete(id);
dcAvDestaque = dcFilhosDiretos(apuracaoAtual.linhas_analise_vertical, nivelFn, id); dcAvExpandidos.add(id);
} else { } else {
dcAvDestaque = dcAvDestaqueHistorico.pop() || new Set(); dcAvColapsadas.add(id);
dcAvExpandidos.delete(id);
dcDescendentes(apuracaoAtual.linhas_analise_vertical, nivelFn, id).forEach((d) => dcAvExpandidos.delete(d.id));
} }
dcAvDestaque = dcDestaqueGrupos(
apuracaoAtual.linhas_analise_vertical,
nivelFn,
dcAvColapsadas,
dcAvExpandidos
);
renderAnaliseVertical(); renderAnaliseVertical();
return; return;
} }
@ -2423,36 +2459,67 @@ document.addEventListener("DOMContentLoaded", async () => {
if (tab === "dashboard") await renderDashboardTab(); if (tab === "dashboard") await renderDashboardTab();
}); });
// ---- Restaurar formatação padrão (Balancete/DRE/Análise Vertical) ---- // ---- Cabeçalho: "Expandir tudo" e "Restaurar formatação padrão" ----
// Botão no canto direito da linha de cabeçalho de cada tabela — descarta // Dois botões por tabela, em pontas opostas da linha de cabeçalho: "+" na
// todo expandir/recolher manual feito pelo contador e recalcula o mesmo // primeira coluna abre a árvore inteira (nada recolhido, até a conta
// estado inicial de dcColapsoPadrao()/dcUltimaLevaVisivel() usado quando a // analítica mais funda), e o de restaurar, na coluna "Observação", descarta
// apuração é aberta (ver renderRevisao). Cada árvore (Balancete/DRE/Análise // todo expandir/recolher manual e recalcula o mesmo estado inicial de
// Vertical) tem seu próprio botão porque cada uma tem seu próprio estado de // dcColapsoPadrao()/dcUltimaLevaVisivel() usado quando a apuração é aberta
// colapso/destaque, independente das demais. // (ver renderRevisao) — é o caminho de volta do "+", já que ele não é um
// toggle. Cada árvore (Balancete/DRE/Análise Vertical) tem o seu par,
// porque cada uma tem seu próprio estado de colapso/destaque.
//
// Expandir tudo zera `dc*Expandidos` junto (não popula com todos os grupos):
// com o conjunto vazio o destaque volta a sair de dcUltimaLevaVisivel(), que
// sem nada recolhido marca exatamente as folhas — as contas analíticas do
// último nível, que é o que interessa destacar com a árvore toda aberta.
// Popular `dc*Expandidos` com todos os grupos destacaria quase toda linha da
// tabela, o que na prática é o mesmo que não destacar nada (ver
// dcDestaqueGrupos acima).
document.getElementById("dc-contas-thead").addEventListener("click", (event) => { document.getElementById("dc-contas-thead").addEventListener("click", (event) => {
if (event.target.closest("[data-dc-expandir-tudo]")) {
dcContasColapsadas = new Set();
dcContasExpandidos = new Set();
dcContasDestaque = dcUltimaLevaVisivel(apuracaoAtual.contas, dcContaNivel, dcContasColapsadas);
renderContas();
return;
}
if (!event.target.closest("[data-dc-reset-formatacao]")) return; if (!event.target.closest("[data-dc-reset-formatacao]")) return;
dcContasColapsadas = dcColapsoPadrao(apuracaoAtual.contas, dcContaNivel); dcContasColapsadas = dcColapsoPadrao(apuracaoAtual.contas, dcContaNivel);
dcContasExpandidos = new Set();
dcContasDestaque = dcUltimaLevaVisivel(apuracaoAtual.contas, dcContaNivel, dcContasColapsadas); dcContasDestaque = dcUltimaLevaVisivel(apuracaoAtual.contas, dcContaNivel, dcContasColapsadas);
dcContasDestaqueHistorico = [];
renderContas(); renderContas();
}); });
document.getElementById("dc-dre-thead").addEventListener("click", (event) => { document.getElementById("dc-dre-thead").addEventListener("click", (event) => {
if (!event.target.closest("[data-dc-reset-formatacao]")) return;
const nivelFn = (linha) => Math.max(0, linha.nivel); const nivelFn = (linha) => Math.max(0, linha.nivel);
dcDreColapsadas = dcColapsoPadrao(apuracaoAtual.linhas_dre, nivelFn); if (event.target.closest("[data-dc-expandir-tudo]")) {
dcDreColapsadas = new Set();
dcDreExpandidos = new Set();
dcDreDestaque = dcUltimaLevaVisivel(apuracaoAtual.linhas_dre, nivelFn, dcDreColapsadas);
renderDre();
return;
}
if (!event.target.closest("[data-dc-reset-formatacao]")) return;
dcDreColapsadas = dcColapsoPadrao(apuracaoAtual.linhas_dre, nivelFn);
dcDreExpandidos = new Set();
dcDreDestaque = dcUltimaLevaVisivel(apuracaoAtual.linhas_dre, nivelFn, dcDreColapsadas); dcDreDestaque = dcUltimaLevaVisivel(apuracaoAtual.linhas_dre, nivelFn, dcDreColapsadas);
dcDreDestaqueHistorico = [];
renderDre(); renderDre();
}); });
document.getElementById("dc-av-thead").addEventListener("click", (event) => { document.getElementById("dc-av-thead").addEventListener("click", (event) => {
if (!event.target.closest("[data-dc-reset-formatacao]")) return;
const nivelFn = (linha) => Math.max(0, linha.nivel); const nivelFn = (linha) => Math.max(0, linha.nivel);
dcAvColapsadas = dcColapsoPadrao(apuracaoAtual.linhas_analise_vertical, nivelFn); if (event.target.closest("[data-dc-expandir-tudo]")) {
dcAvColapsadas = new Set();
dcAvExpandidos = new Set();
dcAvDestaque = dcUltimaLevaVisivel(apuracaoAtual.linhas_analise_vertical, nivelFn, dcAvColapsadas);
renderAnaliseVertical();
return;
}
if (!event.target.closest("[data-dc-reset-formatacao]")) return;
dcAvColapsadas = dcColapsoPadrao(apuracaoAtual.linhas_analise_vertical, nivelFn);
dcAvExpandidos = new Set();
dcAvDestaque = dcUltimaLevaVisivel(apuracaoAtual.linhas_analise_vertical, nivelFn, dcAvColapsadas); dcAvDestaque = dcUltimaLevaVisivel(apuracaoAtual.linhas_analise_vertical, nivelFn, dcAvColapsadas);
dcAvDestaqueHistorico = [];
renderAnaliseVertical(); renderAnaliseVertical();
}); });

View File

@ -443,11 +443,11 @@
tr.dcr-linha-total td { font-weight: 700; } tr.dcr-linha-total td { font-weight: 700; }
.dcr-row-in { animation: dcrFadeIn 220ms ease; } .dcr-row-in { animation: dcrFadeIn 220ms ease; }
/* Destaque temporário nas linhas recém-reveladas ao expandir um grupo — /* Destaque nas linhas recém-reveladas ao expandir um grupo — mesmo
mesmo mecanismo de dc-conta-row--destaque em dashboard-contabil.js/css mecanismo de dc-conta-row--destaque em dashboard-contabil.js/css do
do Portal (ver ali o porquê: dá pra acompanhar visualmente o que acabou Portal (ver ali o porquê: dá pra acompanhar visualmente o que acabou
de aparecer ao expandir, e ao recolher de novo o destaque volta pra de aparecer ao expandir; expandir mais de um grupo acumula o destaque
leva anterior, ver pidDcrArvore() mais abaixo). Este documento não tem de todos, ver pidDcrArvore() mais abaixo). Este documento não tem
alternância de tema (é sempre a marca clara do escritório), então só alternância de tema (é sempre a marca clara do escritório), então só
existe a variante "clara": linhas fora do destaque ganham um leve existe a variante "clara": linhas fora do destaque ganham um leve
tingimento roxo (substitui o zebra por linha par de antes, que ficava tingimento roxo (substitui o zebra por linha par de antes, que ficava
@ -1126,12 +1126,6 @@
} }
}); });
// Histórico do destaque — mesmo espírito de dcContasDestaqueHistorico
// em dashboard-contabil.js: cada expandir empilha o destaque atual e
// troca pros filhos diretos recém-revelados; cada recolher desempilha
// e restaura o destaque de antes, em vez de simplesmente apagar.
var historico = [];
function filhosDiretos(id) { function filhosDiretos(id) {
var idx = linhas.findIndex(function (tr) { var idx = linhas.findIndex(function (tr) {
return tr.getAttribute("data-dcr-id") === id; return tr.getAttribute("data-dcr-id") === id;
@ -1147,6 +1141,27 @@
return filhos; return filhos;
} }
// Todos os descendentes (filhos, netos...) de uma linha — usado pra
// tirar do destaque um grupo aninhado que estava aberto dentro de um
// grupo que acabou de ser recolhido.
function descendentes(id) {
var idx = linhas.findIndex(function (tr) {
return tr.getAttribute("data-dcr-id") === id;
});
if (idx === -1) return [];
var nivelPai = Number(linhas[idx].getAttribute("data-dcr-nivel"));
var resultado = [];
for (var j = idx + 1; j < linhas.length; j++) {
if (Number(linhas[j].getAttribute("data-dcr-nivel")) <= nivelPai) break;
resultado.push(linhas[j].getAttribute("data-dcr-id"));
}
return resultado;
}
// Ids dos grupos expandidos manualmente e ainda abertos — ver
// dcContasExpandidos/dcDestaqueGrupos em dashboard-contabil.js.
var expandidos = {};
// Destaque inicial (antes de qualquer clique) — pedido explícito do // Destaque inicial (antes de qualquer clique) — pedido explícito do
// usuário depois de notar que folha genuína (linha sem filho // usuário depois de notar que folha genuína (linha sem filho
// nenhum, ex. "(-) SIMPLES NACIONAL") ficava de fora do destaque // nenhum, ex. "(-) SIMPLES NACIONAL") ficava de fora do destaque
@ -1190,7 +1205,24 @@
return resultado; return resultado;
} }
var destaque = calculaDestaqueInicial(); // Enquanto nada tiver sido expandido, vale a leva padrão acima; a
// partir do primeiro clique de expandir, o destaque é só a união dos
// filhos diretos dos grupos abertos (vários convivem) — mesma regra
// de dcDestaqueGrupos() em dashboard-contabil.js, inclusive o porquê
// de descartar a leva padrão nesse caso (ver comentário lá).
function calculaDestaque() {
var abertos = Object.keys(expandidos);
if (!abertos.length) return calculaDestaqueInicial();
var resultado = {};
abertos.forEach(function (id) {
Object.keys(filhosDiretos(id)).forEach(function (filhoId) {
resultado[filhoId] = true;
});
});
return resultado;
}
var destaque = calculaDestaque();
function aplicaDestaque() { function aplicaDestaque() {
linhas.forEach(function (tr) { linhas.forEach(function (tr) {
@ -1226,11 +1258,15 @@
colapsadas[id] = !estavaColapsada; colapsadas[id] = !estavaColapsada;
btn.classList.toggle("is-expanded", !colapsadas[id]); btn.classList.toggle("is-expanded", !colapsadas[id]);
if (estavaColapsada) { if (estavaColapsada) {
historico.push(destaque); expandidos[id] = true;
destaque = filhosDiretos(id);
} else { } else {
destaque = historico.pop() || {}; delete expandidos[id];
// Um grupo aninhado aberto dentro deste some junto do destaque.
descendentes(id).forEach(function (descendenteId) {
delete expandidos[descendenteId];
});
} }
destaque = calculaDestaque();
aplicaDestaque(); aplicaDestaque();
atualiza(); atualiza();
}); });
@ -1295,7 +1331,7 @@
if (toggleBtn) toggleBtn.classList.toggle("is-expanded", !padrao); if (toggleBtn) toggleBtn.classList.toggle("is-expanded", !padrao);
tr.classList.remove("dcr-row-flash"); tr.classList.remove("dcr-row-flash");
}); });
historico = []; expandidos = {};
destaque = calculaDestaqueInicial(); destaque = calculaDestaqueInicial();
aplicaDestaque(); aplicaDestaque();
atualiza(); atualiza();

View File

@ -460,7 +460,14 @@
<table class="pa-table dc-contas-table"> <table class="pa-table dc-contas-table">
<thead id="dc-contas-thead"> <thead id="dc-contas-thead">
<tr> <tr>
<th>Conta</th> <th>
<span class="dc-th-linha dc-th-linha--inicio">
<button type="button" class="icon-btn dc-expandir-tudo-btn" data-dc-expandir-tudo="balancete" aria-label="Expandir todas as contas do Balancete" title="Expandir todas as contas até o último nível">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
</button>
Conta
</span>
</th>
<th>Classificação</th> <th>Classificação</th>
<th>Descrição</th> <th>Descrição</th>
<th>Saldo Anterior</th> <th>Saldo Anterior</th>
@ -496,7 +503,14 @@
<table class="pa-table dc-dre-table"> <table class="pa-table dc-dre-table">
<thead id="dc-dre-thead"> <thead id="dc-dre-thead">
<tr> <tr>
<th>Descrição</th> <th>
<span class="dc-th-linha dc-th-linha--inicio">
<button type="button" class="icon-btn dc-expandir-tudo-btn" data-dc-expandir-tudo="dre" aria-label="Expandir todas as linhas da DRE" title="Expandir todas as linhas até o último nível">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
</button>
Descrição
</span>
</th>
<th>Valor</th> <th>Valor</th>
<th> <th>
<span class="dc-th-linha">Observação <span class="dc-th-linha">Observação