Correção de alguns valores no dash principal

This commit is contained in:
2026-08-24 12:07:56 -03:00
parent 7b3ec3e407
commit 04b78bc7de
12 changed files with 690 additions and 50 deletions

118
README.md
View File

@@ -2113,3 +2113,121 @@ docker compose exec app bundle exec rspec \
```
</details>
---
<details>
<summary><strong>🔢 Dashboard × Operações: por que os números não batiam — notas x visitas + painel de avulsas (24/08/2026)</strong></summary>
> ⚠️ **STATUS: implementado, ainda NÃO executado.** Não há Ruby/Bundler nem Postgres na máquina de
> desenvolvimento — foi conferida a sintaxe de todos os `.rb` e `.erb` alterados. **A suíte e a
> validação com dado real continuam pendentes** — roteiro no fim desta seção.
### 🎯 O problema
Mesmo período filtrado, dois números diferentes:
| | Dashboard financeiro | Dashboard de Operações |
|---|---|---|
| Total | 4977 | 4973 |
| Entregues / Sucesso | 4852 | 4851 |
| Falhadas / Recusas | 124 | 122 |
| Pendentes | 1 | 0 |
Não era arredondamento: **as duas telas contam coisas diferentes**, e nada na interface dizia isso.
- O **financeiro** conta **visitas** (idas ao local). É o recorte certo lá, porque é por ida que o
motorista recebe — `Entrega.contar_atendidas` conta linhas, e a consolidação paga em cima disso.
- **Operações** conta **notas fiscais** (último status de cada NF). É o recorte certo aqui, porque
é o que o cliente paga e o que confere nos documentos físicos — o mesmo critério da aba ENTREGAS
da planilha entregue (`Analytics::PlanilhaEntregas`).
Uma NF que falhou dia 10 e foi entregue dia 12 vale **2 no financeiro e 1 em Operações**. Correto
nos dois — mas invisível.
### 🐛 Três defeitos reais por trás disso
**1. O dedup rodava sobre a tabela inteira, não sobre o período.** O `ROW_NUMBER() ... rn = 1`
ficava numa CTE **antes** do filtro de data. Se o último checkout de uma NF era **posterior** ao fim
do período, a visita que aconteceu **dentro** do período sumia da contagem do mês. Subcontagem
silenciosa em todo fechamento. Agora o dedup acontece em `#linhas`, **depois** do período e do
cross-filter.
**2. O dedup escondia todo o insucesso reentregue.** NF que falhou duas vezes antes de entregar
aparecia como 100% de sucesso, e o motivo sumia do "Índices de falha".
**3. O período nem era aplicado no modo Operação.** `montar([@operacao], ...)` era chamado **sem
`inicio:`/`fim:`** e o seletor de data só aparecia no modo Global — comparar as duas telas "no mesmo
período" era literalmente impossível.
### 🆕 O que mudou na tela
**Operações — camada "Visitas ao local"** (abaixo dos 4 cards): visitas realizadas, retentativas,
insucessos por visita e "entregues na 2ª ida ou mais". Os 4 cards de cima seguem contando **notas**.
**Operações — painel "Notas fora da operação"**: NFs entregues no período que **não estão em nenhuma
planilha `gade_entregas_*`** — os planos avulsos e de inclusão. Contavam no financeiro e o
`INNER JOIN` com a tabela da operação as descartava aqui. Agora aparecem com NF, plano/título,
motorista, unidade, data, resultado e motivo, com quebra por plano de origem.
**Operações — período no modo Operação**: o seletor passa a valer também aqui, mas **só quando o
operador escolhe uma faixa** (`inicio`/`fim` na URL). Sem escolha, o recorte segue sendo a operação
inteira — senão abrir uma operação de meses atrás cairia no mês corrente e mostraria zero. Botão
**"Operação inteira"** volta ao recorte natural.
**Financeiro — linha "N notas fiscais"** no card Total Entregas, ao lado de "visitas atendidas".
### ⚙️ Pontos não-óbvios
**Duas camadas no mesmo objeto.** `OperacaoMetricas#visitas` = uma linha por ida; `#linhas` = uma
linha por NF (a última visita). Todos os KPIs, o donut, os motivos, o mapa e a tabela espelho
continuam saindo de `#linhas` — ou seja, **a tela segue batendo com a planilha do cliente**. Só a
faixa nova lê `#visitas`.
**`uniq` por `tracking_id`.** Sem a CTE, se a mesma `nota_fiscal` estiver repetida dentro de uma
tabela de operação (ou em duas tabelas do UNION global), o `INNER JOIN` devolvia a **mesma visita**
mais de uma vez. Agora colapsa.
**Filtro de conta unificado.** `Entrega.condicao_conta_sql` nasceu para as queries cruas de
`Analytics` usarem exatamente o mesmo recorte de `DB_EXISTING_ACCOUNT_ID` do scope
`da_conta_gade` — antes o dashboard filtrava conta e Operações não.
**`NOT IN` com `NULL` devolve zero linhas.** Cada `SELECT` da união em `NotasForaOperacao` filtra
`nota_fiscal IS NOT NULL`; sem isso um único NULL numa planilha deixaria o painel vazio para sempre.
Tem spec para isso.
### 📂 Arquivos
```
app/models/entrega.rb (contas_gade + condicao_conta_sql)
app/services/analytics/operacao_metricas.rb (visitas x linhas; dedup pós-filtro; conta)
app/services/analytics/notas_fora_operacao.rb (NOVO — avulsas/inclusão)
app/controllers/operacoes_dashboard_controller.rb (período no modo Operação; @fora_operacao)
app/controllers/dashboard_controller.rb (@notas_atendidas)
app/views/operacoes_dashboard/_painel.html.erb (faixa "Visitas ao local")
app/views/operacoes_dashboard/_fora_operacao.html.erb (NOVO — painel de avulsas)
app/views/operacoes_dashboard/index.html.erb (seletor de período + render do painel)
app/views/dashboard/index.html.erb (linha "N notas fiscais")
spec/services/analytics/operacao_metricas_spec.rb (+ NF com retentativa)
spec/services/analytics/notas_fora_operacao_spec.rb (NOVO)
```
> **Sem migration e sem gem nova** — model, services, controllers, views e specs.
### ⏳ Pendente — roteiro
```bash
# 1. Suíte (não pôde ser executada aqui — sem Ruby/Bundler local)
docker compose exec app bundle exec rspec \
spec/services/analytics/operacao_metricas_spec.rb \
spec/services/analytics/notas_fora_operacao_spec.rb \
spec/models/entrega_spec.rb
# 2. Conferir a coluna que carrega o nome do PLANO ("(Avulsa)", "INCLUSÃO").
# NotasForaOperacao::COLUNAS_PLANO tenta title, notes, comments, route_id nessa ordem.
# Se o painel mostrar "SEM PLANO IDENTIFICADO", a coluna certa é outra:
docker compose exec app bin/rails runner 'puts Entrega.column_names.sort'
# 3. Com dado real, no mesmo período nas duas telas:
# financeiro "N notas fiscais" == Operações Global "Total de Entregas"
# financeiro "Total Entregas" == Operações "Visitas ao local" + notas fora da operação
```
</details>