Correção visual na aplicação

This commit is contained in:
Gabriel 2026-09-14 14:11:36 -03:00
parent 03af149104
commit affeabc061
5 changed files with 119 additions and 48 deletions

View File

@ -278,3 +278,15 @@ Pedido explícito do usuário, complementando a rodada 130: um botão "Limpar fo
Dois pedidos explícitos do usuário na mesma rodada. (1) Removido o gráfico "Evolução do Resultado Líquido" da aba Resumo — "temos a análise vertical para esta visualização", a aba Análise Vertical já cobre esse tipo de comparação mês a mês. Saiu o `<script>` do Chart.js (CDN), o `<canvas>`, `inicializaGrafico()`, o `{{ evolucao|json_script }}` e o loop que montava essa lista em `dashboard()` (`views.py`) — `_contabil_monta_historico_completo()` continua existindo, só perdeu esse consumidor (o cálculo de Depreciação/Amortização do EBITDA continua usando).
(2) Novo botão "Exportar PDF" na aba Resumo (`ContabilApuracaoViewSet.resumo_pdf()`, `GET /api/contabil-apuracoes/{id}/resumo-pdf/`) gera um PDF avulso com Resumo do Fechamento (texto rico convertido em flowables do reportlab, incluindo listas e imagens embutidas) + Indicadores (uma tabela por grupo) + Observações da Análise (mesmo conteúdo/agrupamento de "Todas as Observações da Análise") — sem o resto do relatório. Identidade visual do escritório (banner roxo/dourado com `logo-branco.png`), mesmo padrão de `custo_contratacao/pdf.py`/`indicadores/recibo.py`. `_contabil_dados_resumo()` (nova função em `views.py`) foi extraída de dentro de `dashboard()` pra ser reaproveitada pelos dois caminhos (indicadores + observações visíveis). Novo módulo `portal_api/dashboard_contabil/resumo_pdf.py` — HTML→reportlab via BeautifulSoup (já era dependência transitiva do projeto, nenhuma adicionada), cobrindo só o allowlist fechado de tags que o editor do Resumo já permite (`RICHTEXT_ALLOWED_TAGS`). Validado com apuração real via `Client.force_login()` e testes unitários isolados (negrito/itálico/lista/imagem, resumo vazio, zero observações) — todos os PDFs conferidos com `pdfplumber`. Detalhe completo no `CLAUDE.md` desta pasta.
### 133. Limiar de `variacao_atipica_dre` ajustado pra 65% + severidade rebaixada pra baixa
Pedido explícito do usuário: variação atípica na DRE (Análise Vertical, ver rodada 129) só deveria disparar acima de **65%** de variação relativa (antes 50%) e, quando disparar, entrar como prioridade **baixa** (antes média). `VARIACAO_LIMIAR_PERCENTUAL` (`regras.py`) foi de `Decimal("0.5")` pra `Decimal("0.65")`, e `regra_variacao_atipica_dre` passou a usar `SEVERIDADE_BAIXA` em vez de `SEVERIDADE_MEDIA`. `VARIACAO_AV_PONTOS_PERCENTUAIS_MINIMO` (piso de 1 ponto percentual) não mudou. Só constante+severidade — nenhuma mudança de estrutura, endpoint ou frontend (a regra continua com a mesma chave `variacao_atipica_dre`, mesmo grupo "Variações e Indicadores" em `PID_DC_GRUPOS`/`dashboard-contabil.js`, e a ordenação por severidade da aba Observações já lida com qualquer severidade automaticamente). Efeito prático: menos achados disparam (limiar mais alto) e os que disparam aparecem por último na lista ordenada por severidade (rodada 96) e contam pro terço "Baixa" do donut do resumo (rodada 98), não mais pro terço "Média".
### 134. Relatório do cliente: "Limpar formatação" virou ícone dentro do cabeçalho da tabela
Pedido explícito do usuário: o botão "Limpar formatação" (rodada 131), até então um botão com texto solto dentro do `<h2>` de cada seção (Balancete/D.R.E./Análise Vertical), passou a ser um ícone pequeno dentro da própria célula de cabeçalho "Observação" da tabela — mesmo padrão do botão "Restaurar formatação padrão" que já existe na tela de revisão (`icon-btn dc-reset-formatacao-btn` dentro de `.dc-th-linha`, `dashboard-contabil.html`/`.css`). `dashboard-contabil-relatorio.html` ganhou as classes equivalentes (`.dcr-th-linha`/`.dcr-th-reset-btn`) e o `<th>Observação</th>` das três tabelas virou `<th><span class="dcr-th-linha">Observação<button class="dcr-th-reset-btn" data-dcr-reset="...">...</button></span></th>`. `data-dcr-reset` (o atributo que o JS já usava, `document.querySelectorAll("[data-dcr-reset]")`) foi mantido, então nenhuma linha de script mudou — só HTML/CSS. `.dcr-reset-btn` (a classe antiga, com texto+borda+padding de pílula) foi removida por completo, sem consumidor restante. Detalhe completo no `CLAUDE.md` desta pasta.
**Bug real, reportado pelo usuário testando a mudança acima**: na aba Análise Vertical, a coluna "Observação" (e o ícone dentro dela) aparecia cortada — não dava nem pra ler "Observação" por completo, nem pra ver o botão. Causa: `.dcr-page` (o container do relatório) tem `max-width:1140px`, e a tabela de Análise Vertical pode ter bem mais colunas que Balancete/D.R.E. (Descrição + 2 por mês + Observação, normalmente 8 no total pra 3 meses) — com `.dcr-tabela-wrap { overflow: hidden }`, o navegador espremia cada coluna até quebrar o texto do cabeçalho em 2-3 linhas e cortar a última coluna pra fora da área visível. Corrigido trocando pra `overflow-x: auto` (mantendo `overflow-y: hidden`, mesmo comportamento vertical de sempre) e `white-space: nowrap` em `table.dcr-tabela th` — agora a tabela cresce além do container quando precisa (ativando rolagem horizontal) em vez de espremer/cortar colunas. Balancete/D.R.E., que já cabiam sem aperto, não mudaram de aparência.
**Pedido explícito do usuário, ainda na mesma rodada**: a rolagem horizontal resolvia o corte, mas o usuário preferiu que o texto diminuísse o bastante pra caber tudo sem precisar arrastar a tabela, pelo menos no caso comum (3 meses). Nova classe `dcr-tabela--compacta`, só na tabela de Análise Vertical (Balancete/D.R.E. mantidos do tamanho original — já cabiam sem aperto): padding menor (`9px 14px` → `5px 7px`), fonte menor (corpo `0.85rem` → `0.74rem`, cabeçalho `0.72rem` → `0.6rem`, `letter-spacing` reduzido) e os ícones de dentro da tabela (toggle de expandir, "Limpar formatação", observação) encolhidos proporcionalmente. Novo filtro de template `mes_curto` (`contabil_extras.py`) corta o ano do mês pra 2 dígitos (`"mai/2026"` → `"mai/26"`) só no cabeçalho dessa tabela — o texto repetido "— Valor"/"— Variação" em cada uma das colunas por mês era o maior consumidor de largura. `overflow-x: auto` do ajuste anterior continua como rede de segurança (uma apuração com mais de 3 meses ainda pode precisar rolar), mas o caso comum (3 meses) passa a caber inteiro sem rolagem nenhuma. Detalhe completo no `CLAUDE.md` desta pasta.

View File

@ -37,7 +37,7 @@ Cada `regra_*` é uma função pura: `(ResultadoExtracao da apuração atual, li
6. **`conta_transitoria_com_saldo`** (média) — descrição contém "TRANSIT" com `saldo_atual != 0`, **exceto** a palavra isolada "TRANSITO" (`\bTRANSITO\b`, "dinheiro em trânsito" — conceito diferente de conta transitória/de compensação, excluído numa rodada seguinte). A exclusão é por palavra isolada, não um trecho maior como "TRANSITOR": testando contra `1751 - Balancete 07.2026.pdf` antes de decidir, a fonte embutida corrompe o acento de "TRANSITÓRIA" num caractere ilegível (não recuperável) na extração — "TRANSITOR" nunca bateria com essa conta (que tem saldo real), então a mudança pra um trecho positivo mais longo foi descartada a favor de manter "TRANSIT" e só excluir o falso positivo conhecido.
7. **`conta_deveria_zerar`** (média) — `TRECHOS_CONTA_DEVERIA_ZERAR` (lista curta e deliberadamente restrita — só "ADIANTAMENTOS DE SALÁRIOS", que o ITD confirma dever ficar zerada todo mês; **não** inclui "Adiantamento de Férias"/"13º Salário", que legitimamente carregam saldo entre meses).
8. **`descricao_generica`** (baixa) — descrição exatamente `"DIVERSOS"` com saldo relevante (o ITD cita esse caso especificamente: "o contador deverá realocar estes lançamentos a conta pertinente").
9. **`variacao_atipica_dre`** (média, **reescrita numa rodada seguinte** — pedido explícito do usuário, "utilizar a análise vertical") — não depende mais de `historico`/apurações anteriores do Portal. Usa a própria seção "Demonstração Mensal (Análise Vertical)" do PDF (`atual.linhas_analise_vertical`, quando presente): compara só os **2 meses mais recentes** dessa tabela (ex. jun → jul), pelo `percentual` (já isolado por mês, não acumulado) de cada linha sobre a Receita Operacional Bruta. Dispara quando o salto entre os 2 meses é de pelo menos `VARIACAO_AV_PONTOS_PERCENTUAIS_MINIMO` (1 ponto percentual — piso pra não disparar em saltos %-mente grandes só porque a base de comparação já era perto de zero) **e**, quando o percentual anterior não é zero, `VARIACAO_LIMIAR_PERCENTUAL` (50% de variação relativa sobre ele). Roda mesmo na 1ª apuração de uma empresa nova, desde que o PDF traga essa seção — validado contra os 2 balancetes reais (16 e 15 achados respectivamente; ambos com bastante movimento real entre os 2 meses do próprio PDF).
9. **`variacao_atipica_dre`** (baixa — era média até uma rodada seguinte, ver abaixo; **reescrita numa rodada anterior** — pedido explícito do usuário, "utilizar a análise vertical") — não depende mais de `historico`/apurações anteriores do Portal. Usa a própria seção "Demonstração Mensal (Análise Vertical)" do PDF (`atual.linhas_analise_vertical`, quando presente): compara só os **2 meses mais recentes** dessa tabela (ex. jun → jul), pelo `percentual` (já isolado por mês, não acumulado) de cada linha sobre a Receita Operacional Bruta. Dispara quando o salto entre os 2 meses é de pelo menos `VARIACAO_AV_PONTOS_PERCENTUAIS_MINIMO` (1 ponto percentual — piso pra não disparar em saltos %-mente grandes só porque a base de comparação já era perto de zero) **e**, quando o percentual anterior não é zero, `VARIACAO_LIMIAR_PERCENTUAL` (**65%** de variação relativa sobre ele — era 50%, ajustado a pedido explícito do usuário numa rodada seguinte, junto da troca de severidade pra baixa: "variações acima de 65%... como prioridade baixa"). Roda mesmo na 1ª apuração de uma empresa nova, desde que o PDF traga essa seção — validado contra os 2 balancetes reais (16 e 15 achados respectivamente; ambos com bastante movimento real entre os 2 meses do próprio PDF) no limiar original de 50%, antes do ajuste pra 65%.
**Duas regras removidas na mesma rodada** (pedido explícito do usuário, "utilizar a análise vertical" no lugar delas): `variacao_atipica_saldo` (variação de saldo de conta do Balancete contra a apuração anterior) — a Análise Vertical do PDF só cobre linhas da DRE, não contas do Balancete, então não tinha como reaproveitar a mesma fonte pra ela, e a alternativa de mantê-la como estava (usando `historico`) foi descartada a favor de simplificar o motor; `percentual_custo_receita_atipico` (razão Custos/Receita Líquida do mês contra o mês anterior, via `historico`) — a granularidade maior de `variacao_atipica_dre` sobre a Análise Vertical já cobre esse caso (e qualquer outra linha da DRE) sem precisar de uma regra dedicada.
@ -411,7 +411,18 @@ Três decisões de escopo confirmadas por `AskUserQuestion` **antes** de impleme
Validado de duas formas: (1) ponta a ponta com `Client.force_login()` em transação com rollback forçado — observação com conta real ganhou `data-dcr-obs-ir` correto, observação "órfã" (chave sem conta correspondente nesta apuração) não ganhou âncora nem classe clicável, e as duas seções aparecem antes da tabela no HTML gerado; (2) **clique de verdade num Chromium headless (Playwright)**, contra o relatório real de uma apuração já em produção (empresa `2017`) — confirmou que `expandeAte()` de fato revela a linha e a página rola até ela, sem erro de JS. Essa segunda rodada de teste só aconteceu **depois** de o usuário reportar "não funciona" já em produção: a causa real não era um bug de código, e sim que `dashboard()` (a mudança que calcula `ancora`) mora em `views.py` — o `runserver` do usuário precisa reiniciar (ou o autoreload do Django precisa pegar a mudança) pra passar a computar isso; a reordenação de HTML (mudança só de template) já aparecia sem reiniciar nada, o que mascarou o diagnóstico por um tempo. Lição: uma mudança em `.py` exige reiniciar o `runserver` pra valer pro usuário testar; mudança só em `.html`/`.css` não exige.
**Botão "Limpar formatação" (um por tabela) e "Voltar ao topo"** (pedido explícito do usuário, mesma rodada): `resetar()` (novo, dentro do fechamento de `pidDcrArvore`) devolve `colapsadas`/`destaque`/`historico` pro estado inicial do servidor (mesmo critério de `data-dcr-colapsado-padrao` usado na primeira carga) e limpa qualquer `.dcr-row-flash` que tenha sobrado; `pidDcrObs(tbodyId)` também passou a devolver `{ fecharTudo }`, que fecha todo painel de observação inline aberto na tabela. O botão (`data-dcr-reset="dcr-{balancete,dre,av}-body"`, no cabeçalho de cada seção, ao lado do de exportar XLSX quando existe) chama os dois de uma vez, via dois `dict`s (`dcrArvores`/`dcrObsControles`) que casam o `tbodyId` do atributo com a `arvore`/painel certos — não desfaz o que está **fora** daquela tabela (cada botão só afeta a própria seção). "Voltar ao topo" (`#dcr-scroll-top-btn`, canto inferior direito, sempre presente no DOM) aparece (`.is-visible`, `opacity`/`transform` com `transition` normal — não é um elemento que entra a partir de `hidden`/`display:none`, então não precisa do padrão `animation` do resto do documento) só depois de `window.scrollY` passar de `PID_DCR_SCROLL_TOP_LIMIAR` (320px), e faz `window.scrollTo({top:0, behavior:"smooth"})` ao clicar. Os dois somem em `@media print` (`.dcr-reset-btn`/`.dcr-scroll-top-btn { display: none; }`), mesmo padrão de `.dcr-print-btn`/`.dcr-export-btn`.
**Botão "Limpar formatação" (um por tabela) e "Voltar ao topo"** (pedido explícito do usuário, mesma rodada): `resetar()` (novo, dentro do fechamento de `pidDcrArvore`) devolve `colapsadas`/`destaque`/`historico` pro estado inicial do servidor (mesmo critério de `data-dcr-colapsado-padrao` usado na primeira carga) e limpa qualquer `.dcr-row-flash` que tenha sobrado; `pidDcrObs(tbodyId)` também passou a devolver `{ fecharTudo }`, que fecha todo painel de observação inline aberto na tabela. O botão (`data-dcr-reset="dcr-{balancete,dre,av}-body"`) chama os dois de uma vez, via dois `dict`s (`dcrArvores`/`dcrObsControles`) que casam o `tbodyId` do atributo com a `arvore`/painel certos — não desfaz o que está **fora** daquela tabela (cada botão só afeta a própria seção). "Voltar ao topo" (`#dcr-scroll-top-btn`, canto inferior direito, sempre presente no DOM) aparece (`.is-visible`, `opacity`/`transform` com `transition` normal — não é um elemento que entra a partir de `hidden`/`display:none`, então não precisa do padrão `animation` do resto do documento) só depois de `window.scrollY` passar de `PID_DCR_SCROLL_TOP_LIMIAR` (320px), e faz `window.scrollTo({top:0, behavior:"smooth"})` ao clicar. Os dois somem em `@media print` (`.dcr-th-reset-btn`/`.dcr-scroll-top-btn { display: none; }`), mesmo padrão de `.dcr-print-btn`/`.dcr-export-btn`.
**"Limpar formatação" virou ícone dentro do cabeçalho "Observação"** (rodada seguinte, pedido explícito do usuário): nasceu como um botão com texto solto (borda, padding de pílula, `.dcr-reset-btn`) dentro do `<h2>` de cada seção — passou a ser um ícone só, dentro da própria célula `<th>Observação</th>` de cada tabela, mesmo padrão do botão "Restaurar formatação padrão" que já existe no `<thead>` de Balancete/D.R.E. da tela de revisão (`icon-btn dc-reset-formatacao-btn` dentro de `.dc-th-linha`, ver `dashboard-contabil.html`/`.css`). `.dcr-th-linha` (flex, rótulo à esquerda/ícone à direita) e `.dcr-th-reset-btn` (botão circular 20px, cor `--roxo-escuro`, sem borda) são as classes novas em `dashboard-contabil-relatorio.html`; `.dcr-reset-btn` foi removida por completo (zero consumidor restante). `data-dcr-reset` (o atributo lido pelo JS) não mudou de lugar conceitualmente — só migrou de dentro do `<h2>` pra dentro do `<th>` — então nenhuma linha de `<script>` precisou mudar.
**Bug real, achado testando essa mudança na Análise Vertical**: essa tabela pode ter bem mais colunas que Balancete/D.R.E. (Descrição + 2 por mês + Observação — normalmente 8 pra 3 meses), e `.dcr-page` (container do relatório inteiro) tem `max-width:1140px`. Com `.dcr-tabela-wrap { overflow: hidden }` (valor original, pensado só pra arredondar os cantos), o navegador espremia cada coluna até quebrar o texto do cabeçalho em 2-3 linhas e cortar a última coluna (Observação, com o ícone novo) pra fora da área visível — o ícone simplesmente não aparecia. Corrigido com duas mudanças em `table.dcr-tabela`/`.dcr-tabela-wrap`: `overflow-x: auto` (era `overflow: hidden`, que zerava os dois eixos — `overflow-y` continua `hidden`, mesmo comportamento vertical de sempre) permite rolagem horizontal quando a tabela precisa de mais espaço do que o container tem; `white-space: nowrap` em `table.dcr-tabela th` impede o cabeçalho de quebrar linha, fazendo a tabela realmente crescer além do container (e então rolar) em vez de espremer as colunas até ficarem ilegíveis. Balancete/D.R.E. (poucas colunas, sempre cabiam sem aperto) não mudam de aparência com isso.
**Análise Vertical: texto compacto pra caber sem rolagem (pedido explícito do usuário, mesma rodada)**: a rolagem horizontal resolvia o corte, mas o usuário preferiu texto/espaçamento menores pra caber tudo sem precisar arrastar, pelo menos no caso comum de 3 meses. Nova classe `dcr-tabela--compacta` (só no `<table>` da Análise Vertical, `dashboard-contabil-relatorio.html`):
- `table.dcr-tabela.dcr-tabela--compacta th, td { padding: 5px 7px; font-size: 0.74rem; }` e `th { font-size: 0.6rem; letter-spacing: 0.01em; }` (era `9px 14px`/`0.85rem`/`0.72rem`/`0.03em`, os valores padrão que Balancete/D.R.E. continuam usando).
- `.dcr-th-reset-btn`/`.dcr-toggle`/`.dcr-toggle-spacer`/`.dcr-obs-btn` ganham tamanhos menores (`16px`/`15px`/`15px`/`20px`) só dentro de `.dcr-tabela--compacta` — os ícones precisavam encolher junto com o texto, senão dominariam uma célula já estreita.
- **Novo filtro `mes_curto`** (`portal_api/templatetags/contabil_extras.py`) corta o ano do mês pra 2 dígitos (`"mai/2026"` → `"mai/26"`) — usado só no cabeçalho desta tabela (`{{ mes|mes_curto|capfirst }} — Valor`/`— Variação`), já que "— Valor"/"— Variação" repetido em cada coluna por mês era o maior consumidor de largura do cabeçalho (`table.dcr-tabela th { white-space: nowrap }`, ver ajuste anterior). Não usado em nenhum outro lugar do relatório (a lista de observações, por exemplo, continua mostrando a competência por extenso via `{{ ...|competencia }}`, formato diferente — `MM/AAAA` a partir de um `date`, não de uma string "mês/ano" já formatada).
`overflow-x: auto` (ajuste anterior) continua como rede de segurança — uma apuração com mais de 3 meses ainda pode precisar de rolagem horizontal, já que não há como garantir que qualquer quantidade de colunas caiba num container de largura fixa só encolhendo texto até um certo ponto (ilegibilidade é o limite). O objetivo desta mudança é só o caso comum (3 meses, o normal do PDF Questor) caber inteiro sem arrastar nada.
**Frontend** (`dashboard-contabil.js`): as observações são carregadas à parte da apuração (`dcCarregarObservacoes()`, chamada ao abrir/criar uma análise) e indexadas por chave natural (`dcObsIndice`), porque não pertencem ao payload da apuração. O editor inline virou uma thread (`dcObsPainelHtml()`/`dcObsItemHtml()`): histórico em cima (autor, data, competência de origem, selos "Histórico"/"Encerrada"/"Editada"/"Aparece ao cliente"/"Interna" e ações de olho, ver histórico de edições (só quando `editada`), editar, encerrar/reativar), campo de observação nova embaixo. **Sem botão de excluir** (removido numa rodada seguinte, pedido explícito do usuário) — a exclusão de uma observação vigente fica limitada ao fluxo de "ocultar das próximas competências" (`encerrar`), nunca um apagar definitivo pela tela; o `DELETE` do backend (`ContabilObservacaoViewSet`, ver "Imutabilidade"/"Endpoints" acima) continua existindo e reachável via API, só não tem mais consumidor no frontend. Um handler único (`dcTrataCliqueObservacao()`) atende as três tabelas e as quatro listas de resumo, e cada mutação refaz o fetch e re-renderiza tudo (`dcRenderObservacoesTudo()`) — a mesma observação pode estar visível em mais de um lugar ao mesmo tempo. O botão da coluna "Observação" ganhou um contador (`.dc-obs-contador`), já que uma conta pode ter várias. Os chips "Todas / Visíveis ao cliente / Internas" (`dcObsFiltro`) existem nas quatro listas e compartilham a mesma variável: filtrar numa aba filtra em todas.

View File

@ -46,7 +46,7 @@ TRECHOS_CONTA_DEVERIA_ZERAR = ["ADIANTAMENTOS DE SALÁRIOS", "ADIANTAMENTO DE SA
# saldo desta linha agregadora, não de uma das duas filhas específicas.
CODIGO_LUCRO_PREJUIZO_EXERCICIO = "2.04.13.002"
VARIACAO_LIMIAR_PERCENTUAL = Decimal("0.5") # 50%
VARIACAO_LIMIAR_PERCENTUAL = Decimal("0.65") # 65%
VARIACAO_VALOR_MINIMO = Decimal("1000") # ignora variações abaixo disso, mesmo que %-mente grandes
# Piso em pontos percentuais pra regra_variacao_atipica_dre (Análise Vertical)
# — mesmo espírito de VARIACAO_VALOR_MINIMO acima, adaptado pro domínio de
@ -261,7 +261,7 @@ def regra_variacao_atipica_dre(atual: ResultadoExtracao, historico: list[Snapsho
achados.append(
AchadoDetectado(
regra="variacao_atipica_dre",
severidade=SEVERIDADE_MEDIA,
severidade=SEVERIDADE_BAIXA,
titulo="Variação atípica na DRE",
mensagem=(
f'Linha "{linha.descricao}" da DRE foi de {mes_anterior.percentual:.2f}% da Receita Bruta em '

View File

@ -68,6 +68,19 @@ def percentual_av(valor: str | None) -> str:
return f"{_agrupa_milhar(f'{Decimal(valor):,.2f}')}%"
@register.filter
def mes_curto(valor: str | None) -> str:
""""mai/2026" -> "mai/26" — só pro cabeçalho da tabela de Análise
Vertical (`analise_vertical_meses`), que repete "mês — Valor"/"mês —
Variação" por coluna: o ano com 4 dígitos empurrava o cabeçalho largo
demais pra caber sem rolagem horizontal (ver "Análise Vertical: texto
compacto" no CLAUDE.md do pacote)."""
if not valor or "/" not in valor:
return valor or ""
mes, ano = valor.split("/", 1)
return f"{mes}/{ano[-2:]}"
@register.filter
def competencia(valor: date | None) -> str:
return valor.strftime("%m/%Y") if valor else "—"

View File

@ -175,30 +175,6 @@
.dcr-export-btn:hover { background: var(--dourado-claro); transform: translateY(-1px); }
.dcr-export-btn:active { transform: translateY(0); }
/* Botão "Limpar formatação" — mesmo estilo do de exportar (outline
discreto), mas em roxo pra não parecer uma segunda ação de exportação.
Fica ANTES do de exportar dentro do `<h2>` (quando os dois existem, só
Balancete/D.R.E.) e é quem carrega o `margin-left:auto` que empurra os
dois pro fim da linha — a Análise Vertical não tem exportar XLSX, então
esse botão sozinho já resolve sem precisar de wrapper. */
.dcr-reset-btn {
margin-left: auto;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: 999px;
border: 1.5px solid var(--roxo-medio);
color: var(--roxo-escuro);
background: #fff;
font-family: inherit;
font-size: 0.78rem;
font-weight: 700;
cursor: pointer;
transition: background 160ms ease, transform 160ms ease;
}
.dcr-reset-btn:hover { background: var(--roxo-claro); transform: translateY(-1px); }
.dcr-reset-btn:active { transform: translateY(0); }
/* Botão flutuante "Voltar ao topo" — canto inferior direito, sempre
presente no DOM (nunca via `hidden`, então `opacity`/`transform` com
@ -389,11 +365,22 @@
}
/* ---- Tabelas ---- */
/* `overflow-x:auto` (era `hidden`) — necessário pra Análise Vertical, que
pode ter várias colunas (Descrição + 2 por mês + Observação): com o
container limitado a `.dcr-page` (max-width:1140px), `hidden` deixava o
navegador espremer cada coluna até quebrar o texto do cabeçalho em 2-3
linhas e cortar a última coluna (Observação, com o ícone de "Limpar
formatação") pra fora da área visível. Com scroll horizontal + cabeçalho
sem quebra de linha (`white-space:nowrap` abaixo), a tabela cresce além
do container quando necessário e o usuário rola pro lado, em vez de
perder conteúdo. `overflow-y:hidden` preserva o cantos arredondados
(Balancete/D.R.E. continuam cabendo sem precisar de scroll nenhum). */
.dcr-tabela-wrap {
background: #fff;
border: 1px solid var(--borda);
border-radius: 14px;
overflow: hidden;
overflow-x: auto;
overflow-y: hidden;
box-shadow: var(--sombra);
animation: dcrScaleIn 380ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
@ -406,10 +393,52 @@
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
white-space: nowrap;
position: sticky;
top: 0;
}
table.dcr-tabela td.dcr-valor { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
/* Análise Vertical pode ter bem mais colunas que Balancete/D.R.E.
(Descrição + 2 por mês + Observação — normalmente 8 pra 3 meses,
ver `analise_vertical_meses`) — texto/espaçamento menores pra caber
tudo em `.dcr-page` (max-width:1140px) sem depender da rolagem
horizontal de `.dcr-tabela-wrap` (mantida como rede de segurança pra
um PDF com mais de 3 meses, mas o objetivo é o caso comum caber sem
precisar arrastar). Só a Análise Vertical usa esta classe — Balancete/
D.R.E. já cabem confortavelmente no tamanho padrão. */
table.dcr-tabela.dcr-tabela--compacta th,
table.dcr-tabela.dcr-tabela--compacta td { padding: 5px 7px; font-size: 0.74rem; }
table.dcr-tabela.dcr-tabela--compacta th { font-size: 0.6rem; letter-spacing: 0.01em; }
.dcr-tabela--compacta .dcr-th-reset-btn { width: 16px; height: 16px; }
.dcr-tabela--compacta .dcr-toggle,
.dcr-tabela--compacta .dcr-toggle-spacer { width: 15px; height: 15px; }
.dcr-tabela--compacta .dcr-obs-btn { width: 20px; height: 20px; }
/* "Limpar formatação" virou um ícone dentro do próprio cabeçalho "Observação"
(mesmo padrão de `.dc-th-linha`/`.dc-reset-formatacao-btn` da tela de
revisão do Portal, `dashboard-contabil.html`/`.css`) — antes era um botão
com texto solto dentro do `<h2>` da seção, pedido explícito do usuário pra
aproximar do visual da tela de validação. `.dcr-th-linha` empurra o rótulo
"Observação" e o ícone pras duas pontas da própria célula de cabeçalho. */
.dcr-th-linha { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.dcr-th-reset-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
flex-shrink: 0;
border-radius: 50%;
border: none;
background: none;
color: var(--roxo-escuro);
opacity: 0.65;
cursor: pointer;
padding: 0;
transition: background 160ms ease, opacity 160ms ease;
}
.dcr-th-reset-btn:hover { background: #fff; opacity: 1; }
table.dcr-tabela tbody tr { transition: background 120ms ease; }
tr.dcr-linha-total td { font-weight: 700; }
.dcr-row-in { animation: dcrFadeIn 220ms ease; }
@ -592,7 +621,7 @@
.dcr-print-btn { display: none; }
.dcr-export-btn { display: none; }
.dcr-resumo-toolbar { display: none; }
.dcr-reset-btn { display: none; }
.dcr-th-reset-btn { display: none; }
.dcr-scroll-top-btn { display: none; }
.dcr-header { margin: 0 0 24px; }
.dcr-tabs { display: none; }
@ -662,10 +691,6 @@
<h2>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
Balancete
<button type="button" class="dcr-reset-btn" data-dcr-reset="dcr-balancete-body">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 2.64-6.36"/><path d="M3 3v6h6"/></svg>
Limpar formatação
</button>
<a class="dcr-export-btn" href="/api/contabil-apuracoes/{{ apuracao.id }}/exportar-xlsx/?parte=balancete">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
Exportar XLSX
@ -681,7 +706,13 @@
<th class="dcr-valor">Débito</th>
<th class="dcr-valor">Crédito</th>
<th class="dcr-valor">Saldo Atual</th>
<th>Observação</th>
<th>
<span class="dcr-th-linha">Observação
<button type="button" class="dcr-th-reset-btn" data-dcr-reset="dcr-balancete-body" aria-label="Restaurar formatação padrão do Balancete" title="Restaurar formatação padrão (recolher grupos como no início)">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 2.64-6.36"/><path d="M3 3v6h6"/></svg>
</button>
</span>
</th>
</tr>
</thead>
<tbody id="dcr-balancete-body">
@ -759,10 +790,6 @@
<h2>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V5a2 2 0 0 1 2-2h8l6 6v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2Z"/><path d="M14 3v6h6M9 13h6M9 17h6"/></svg>
D.R.E. — Demonstração do Resultado do Exercício
<button type="button" class="dcr-reset-btn" data-dcr-reset="dcr-dre-body">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 2.64-6.36"/><path d="M3 3v6h6"/></svg>
Limpar formatação
</button>
<a class="dcr-export-btn" href="/api/contabil-apuracoes/{{ apuracao.id }}/exportar-xlsx/?parte=dre">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
Exportar XLSX
@ -770,7 +797,13 @@
</h2>
<div class="dcr-tabela-wrap">
<table class="dcr-tabela">
<thead><tr><th>Descrição</th><th class="dcr-valor">Valor</th><th>Observação</th></tr></thead>
<thead><tr><th>Descrição</th><th class="dcr-valor">Valor</th><th>
<span class="dcr-th-linha">Observação
<button type="button" class="dcr-th-reset-btn" data-dcr-reset="dcr-dre-body" aria-label="Restaurar formatação padrão da DRE" title="Restaurar formatação padrão (recolher grupos como no início)">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 2.64-6.36"/><path d="M3 3v6h6"/></svg>
</button>
</span>
</th></tr></thead>
<tbody id="dcr-dre-body">
{% for item in linhas_dre %}
<tr{% if item.linha.totalizador %} class="dcr-linha-total"{% endif %} data-dcr-nivel="{{ item.nivel }}" data-dcr-id="linha-{{ item.linha.id }}" data-dcr-tem-filhos="{% if item.tem_filhos %}1{% else %}0{% endif %}" data-dcr-colapsado-padrao="{% if item.colapsado_padrao %}1{% else %}0{% endif %}">
@ -843,21 +876,23 @@
<h2>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M18 17V9M13 17V5M8 17v-3"/></svg>
Demonstração Mensal (Análise Vertical)
<button type="button" class="dcr-reset-btn" data-dcr-reset="dcr-av-body">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 2.64-6.36"/><path d="M3 3v6h6"/></svg>
Limpar formatação
</button>
</h2>
<div class="dcr-tabela-wrap">
<table class="dcr-tabela">
<table class="dcr-tabela dcr-tabela--compacta">
<thead>
<tr>
<th>Descrição</th>
{% for mes in analise_vertical_meses %}
<th class="dcr-valor">{{ mes|capfirst }} — Valor</th>
<th class="dcr-valor">{{ mes|capfirst }} — Variação</th>
<th class="dcr-valor">{{ mes|mes_curto|capfirst }} — Valor</th>
<th class="dcr-valor">{{ mes|mes_curto|capfirst }} — Variação</th>
{% endfor %}
<th>Observação</th>
<th>
<span class="dcr-th-linha">Observação
<button type="button" class="dcr-th-reset-btn" data-dcr-reset="dcr-av-body" aria-label="Restaurar formatação padrão da Análise Vertical" title="Restaurar formatação padrão (recolher grupos como no início)">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 2.64-6.36"/><path d="M3 3v6h6"/></svg>
</button>
</span>
</th>
</tr>
</thead>
<tbody id="dcr-av-body">