From 04b78bc7de0f190b1823957d1d9f4a326bd4086e90b4b7564e3639fbed6d2b56 Mon Sep 17 00:00:00 2001
From: victor
+ visitas atendidas · <%= @notas_atendidas %> notas fiscais
+
+ <%= icone :parcial, cor: 'text-amber-400', espaco: false %>
+ Notas fora da operação
+
+ Entregues em <%= inicio.strftime('%d/%m/%Y') %> – <%= fim.strftime('%d/%m/%Y') %>
+ sem constar em nenhuma planilha
+ Nenhuma nota fora das planilhas de operação neste período.
+
+ Mostrando as <%= Analytics::NotasForaOperacao::LIMITE %> mais recentes de <%= fora.total %>.
+ Total de Entregas <%= metricas.total %> notas fiscais da operação
<%= icone :sucesso, cor: 'text-green-500', tamanho: 'w-4 h-4' %> <%= metricas.sucesso %> · <%= icone :ponto, cor: 'text-blue-500', tamanho: 'w-4 h-4' %> <%= metricas.recusas %> · <%= icone :parcial, cor: 'text-amber-400', tamanho: 'w-4 h-4' %> <%= metricas.pendentes %>
Visitas ao local
+ O dashboard financeiro conta VISITAS (o motorista recebe por ida); esta tela conta NOTAS.
+ A diferença são as <%= metricas.retentativas %> retentativas acima.
+ 🔢 Dashboard × Operações: por que os números não batiam — notas x visitas + painel de avulsas (24/08/2026)
+
+> ⚠️ **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
+```
+
+gade_entregas_* — planos avulsos e de inclusão.
+
+
+
+
+
+
+
+ <% fora.listagem.each do |linha| %>
+ <% sucesso = linha['status'] == 'completed' %>
+ NF
+ Plano / título
+ Motorista
+ Unidade
+ Data
+ Resultado
+ Motivo
+
+
+ <% end %>
+
+ <%= linha['reference_id'] %>
+ <%= fora.plano(linha) %>
+ <%= linha['driver'].presence || '—' %>
+ <%= linha['contact_name'].presence || '—' %>
+
+ <%= (linha['checkout'].presence || linha['planned_date'].presence)&.to_date&.strftime('%d/%m/%Y') || '—' %>
+
+
+ <%= sucesso ? 'Entregue' : (linha['status'].presence || '—') %>
+
+ <%= linha['observation'].presence || '—' %>
+
+
+