diff --git a/README.md b/README.md index 8f66115..945be02 100644 --- a/README.md +++ b/README.md @@ -1638,3 +1638,117 @@ app/javascript/controllers/validacao_controller.js (chip usa SVG, não emoji) +--- + +
+🔁 NF com mais de um lançamento + varredura de responsividade (21–22/07/2026) + +## Parte 1 — Editar Lançamento: quando a mesma NF tem 2 visitas + +### 🎯 O problema real +Quando um plano é **duplicado** no SimpliRoute, nasce uma **visita nova** (outro `tracking_id`) com a +**mesma NF**, e a antiga continua existindo. Caso que motivou tudo: **NF 82891** com visita em +**17/07** (pendente, motorista Thiago Rabello Bittencourt) e outra em **21/07** (sucesso, sem +motorista). A tela mostrava **uma só** — e depois, por regressão, **nenhuma**. + +### 🔴 Causas corrigidas (foram 7, em camadas) +1. **`Entrega.por_nf(nf).first`** — pegava uma linha só, sem `ORDER BY`. Agora carrega todas e a + tela lista as ocorrências para o ADM escolher qual editar. +2. **`SimpliRoute::Client#resolver_id` abortava na ambiguidade** (`"Mais de uma visita para a NF…"`) + em vez de deixar escolher. A tela de edição não passa mais por ele. +3. **As datas vinham só do espelho local.** Como a API **não busca NF por intervalo**, a visita de + 17/07 era inalcançável se o painel só conhecia a de 21/07. Entraram campos **De/Até** + botão + **"Últimos 30 dias"** (varredura dia a dia, teto de 62 dias). +4. **Um dia com falha derrubava a busca inteira** → agora é *fail-soft* por dia: registra em + `falhas` e segue. A tela informa o período consultado e quais dias falharam. +5. **`carregar?` faltando em `EdicaoLancamentoPolicy`** → o Pundit levantava `NoMethodError` (500), + o `fetch` recebia HTML e o JS reportava "erro de conexão". Falhou **fechado** (negou acesso), + sem brecha de segurança. +6. **Tela em branco:** `renderOcorrencias()` montava a lista inteira mas **nunca removia a classe + `hidden`** do container. O conteúdo estava no DOM (contador já dizia "2 lançamentos"), invisível. +7. **Fuso horário:** `new Date('2026-07-15')` é meia-noite **UTC** e, em UTC−3, o `toLocale` + devolvia **14/07**. Datas puras agora são formatadas direto do texto ISO; horários de checkout + continuam convertendo (correto para instante). + +### 🔑 Descobertas sobre a API do SimpliRoute (medidas contra a API real, 21/07/2026) +| Parâmetro | Resultado | +|---|---| +| **`&search=`** | ✅ **Funciona e não é documentado.** Um dia cai de **3,8 MB / ~9 s** (1879 visitas) para **~1 KB / ~0,6 s**. Varredura de 31 dias em lotes paralelos: **3,4 s / 3,2 KB** | +| `reference`, `reference_id`, `q`, `title`, `reference__*` | ❌ **Ignorados em silêncio** — devolvem 200 com o dia inteiro | +| `planned_date_from/to`, `since/until`, `__gte/__lte`, `date_from/to` | ❌ **Não existe filtro de intervalo** — todos devolveram 2469, idêntico ao controle **sem parâmetro nenhum** | +| `GET /v1/routes/visits/` sem `planned_date` | ⚠️ Devolve um **conjunto padrão (~2469)** que **não cobre o histórico** — para a NF 82891 voltava só a visita de 21/07 e escondia a de 17/07 | + +> ⚠️ **Lição:** parâmetro não registrado no backend Django é **ignorado sem erro**. "Voltou 200 com +> resultados" **não** prova que filtrou — a prova é a **contagem diminuir** em relação ao dia sem o +> filtro. Ferramenta: **`bin/sondar_busca_nf --nf --data [--sem-data]`** (só GET). + +--- + +## Parte 2 — Responsividade + +### 🎯 A raiz comum +Os breakpoints do Tailwind (`md:`, `lg:`, `xl:`) enxergam a **janela**, mas o conteúdo perde +**256px** para a sidebar (`main.md:ml-64`). **A mesma janela de 1280px tem duas larguras** conforme +o menu esteja aberto ou recolhido — e grades de colunas fixas não ficam sabendo. Daí "quando a barra +de menu é acionada, quebra o layout". Solução: **`flex-wrap` / `auto-fit + minmax`**, que reagem ao +espaço **real**. + +### 🔧 Corrigido +| Tela | Antes | Sintoma | Depois | +|---|---|---|---| +| `consolidacoes/index` | `md:grid-cols-6` | Campos a ~150px; texto do botão **"Filtrar" vazava** para fora do fundo | `flex-wrap` + `basis-*` | +| `dashboard/index` | `xl:grid-cols-5` | **"R$ 82.692,"** — valor cortado pelo `overflow-hidden` do card | `auto-fit,minmax(13rem,1fr)` | +| `consolidacao_entregas/revisar` | `md:grid-cols-7` | 7 cards de **~55px**, destruindo "Extraordinária"/"Termo Especial" | `auto-fit,minmax(9rem,1fr)` | +| `configuracoes` + `admin/configuracoes` | `lg:grid-cols-4` | ~128px úteis para valores em moeda | `auto-fit,minmax(14rem,1fr)` | + +**Mantidos de propósito:** `validar.html.erb` (`lg:grid-cols-4` é a divisão 3:1 lista/Resumo, não +grade de cards) e os `xl:grid-cols-3` de gráficos — nesses o conteúdo encolhe sem cortar. + +### 📐 Passo 2 (validar) — cabeçalho fixo +Medido a 1280×577: **442px de cabeçalho contra 67px de lista** (menos que um card). + +- Voltar + título + progresso passaram a **uma faixa só**; o card de progresso (70px de moldura para + uma barra de 12px) virou linha de 8px. +- **"Adicionar lançamento"** entrou na barra de ações via `order` do flex — sem mover os ~170 linhas + de modais que vivem dentro do `data-controller`. +- **Pilares (Normal/Retirada/Bônus/Desconto/Extra) continuam SEMPRE visíveis**, apenas **esmaecidos** + (`opacity-40`) enquanto não há seleção, com contador e "limpar" aparecendo ao selecionar. + ⚠️ Uma versão intermediária os **escondia** até haver seleção — revertido: são a ação principal da + tela e sumir com eles esconde o que dá para fazer de quem ainda não sabe que precisa selecionar. +- Legibilidade preservada: título `text-2xl`, subtítulo e progresso `text-sm`, números em branco/negrito. + +**Resultado:** cabeçalho **442px → 210px**, lista **67px → 299px**. + +### 📂 Arquivos +``` +app/controllers/admin/edicao_lancamentos_controller.rb # lista ocorrências, período, fail-soft, carregar +app/policies/edicao_lancamento_policy.rb # + carregar? (era o 500) +app/services/simpli_route/client.rb # visitas_da_data(data, busca:) → &search= +app/views/admin/edicao_lancamentos/show.html.erb # lista de ocorrências, De/Até, avisos, fuso +config/routes.rb # + get :carregar +bin/sondar_busca_nf # NOVO — sondagem de filtros da API (só GET) +app/views/consolidacao_entregas/validar.html.erb # cabeçalho compacto + barra de ações +app/javascript/controllers/validacao_controller.js # atualizarSelecao / limparSelecao +app/views/consolidacao_entregas/revisar.html.erb # grid-cols-7 → auto-fit +app/views/consolidacoes/index.html.erb # filtros → flex-wrap +app/views/dashboard/index.html.erb # KPIs → auto-fit +app/views/configuracoes/index.html.erb # preços → auto-fit +app/views/admin/configuracoes/index.html.erb # idem (arquivo distinto, também vivo) +``` + +### ⚠️ Pendências e alertas +- **Tailwind vem do Play CDN** (`cdn.tailwindcss.com`, em `application.html.erb:15`), que gera CSS no + navegador em tempo real. A documentação oficial **desaconselha em produção** (~380KB bloqueando a + renderização, recompilação a cada carregamento). O **`tailwind.config.js` do repositório não está + sendo usado** — a config real está embutida no layout (linha 17), e a gem `tailwindcss-rails` está + no Gemfile sem servir CSS. +- **Não existe teste para `Admin::EdicaoLancamentosController`** (`spec/` não tem nada de + `edicao_lancamento`). Duas das quebras acima — policy faltando e `hidden` não removido — seriam + pegas por um teste de request/sistema em segundos, sem custar deploy. +- **Token do SimpliRoute:** se passou por chat/e-mail, **rotacione** em `app2.simpliroute.com`. + +> **Sem migration e sem gem nova** — controller, policy, service, views e JS. +> ⚠️ Reiniciar o Puma após o deploy (cache de classes/views). + +
+ diff --git a/app/javascript/controllers/validacao_controller.js b/app/javascript/controllers/validacao_controller.js index 87708b1..2f54e20 100644 --- a/app/javascript/controllers/validacao_controller.js +++ b/app/javascript/controllers/validacao_controller.js @@ -7,7 +7,7 @@ import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = [ "linha", "checkbox", "selecionarTodos", "busca", "buscaVazia", - "barraSelecao", "contadorSelecao", + "grupoPilares", "chipSelecao", "contadorSelecao", "btnLimpar", "resumo", "listaCol", "btnResumoLabel", "toggles", "barra", "barraManuais", "percentual", "chipVeiculo", "consolidarBtn", "consolidarHint", @@ -102,13 +102,23 @@ export default class extends Controller { this.atualizarSelecao() } - // ── Barra de ações em massa: só existe quando há seleção ── - // Sem isto os 5 botões de pilar ficavam fixos no topo o tempo todo, comendo - // a altura da lista para não fazer nada (clicar sem seleção só alertava). + // ── Estado da seleção ── + // Os pilares ficam SEMPRE na tela; sem seleção apenas esmaecem. Esconder e + // remostrar mexia na altura do cabeçalho a cada clique e escondia a ação + // principal justamente de quem ainda não sabe que precisa selecionar antes. atualizarSelecao() { const n = this.checkboxTargets.filter(c => c.checked).length + if (this.hasContadorSelecaoTarget) this.contadorSelecaoTarget.textContent = n - if (this.hasBarraSelecaoTarget) this.barraSelecaoTarget.classList.toggle("hidden", n === 0) + if (this.hasChipSelecaoTarget) this.chipSelecaoTarget.classList.toggle("hidden", n === 0) + if (this.hasBtnLimparTarget) this.btnLimparTarget.classList.toggle("hidden", n === 0) + + if (this.hasGrupoPilaresTarget) { + this.grupoPilaresTarget.classList.toggle("opacity-40", n === 0) + this.grupoPilaresTarget.title = n === 0 + ? "Marque ao menos uma entrega para aplicar em massa" + : `Aplicar em ${n} entrega(s) — clique de novo no pilar para remover` + } } limparSelecao() { diff --git a/app/views/admin/configuracoes/index.html.erb b/app/views/admin/configuracoes/index.html.erb index f90d925..8fbfee7 100644 --- a/app/views/admin/configuracoes/index.html.erb +++ b/app/views/admin/configuracoes/index.html.erb @@ -11,7 +11,10 @@ <%# Pilares de preço %>

<%= icone :dinheiro, espaco: false %> Pilares de Preço

-
+ <%# auto-fit: `lg:grid-cols-4` mede a JANELA, mas a sidebar come 256px do + conteúdo — os cards de preço ficavam com ~128px úteis e o valor em + moeda espremia. Aqui o número de colunas segue a largura REAL. %> +
<% @configuracoes.each do |cfg| %>
diff --git a/app/views/configuracoes/index.html.erb b/app/views/configuracoes/index.html.erb index f10cf9b..1567874 100644 --- a/app/views/configuracoes/index.html.erb +++ b/app/views/configuracoes/index.html.erb @@ -11,7 +11,10 @@ <%# Pilares de preço %>

<%= icone :dinheiro, espaco: false %> Pilares de Preço

-
+ <%# auto-fit: `lg:grid-cols-4` mede a JANELA, mas a sidebar come 256px do + conteúdo — os cards de preço ficavam com ~128px úteis e o valor em + moeda espremia. Aqui o número de colunas segue a largura REAL. %> +
<% @configuracoes.each do |cfg| %>
diff --git a/app/views/consolidacao_entregas/revisar.html.erb b/app/views/consolidacao_entregas/revisar.html.erb index 011077b..b4c93d9 100644 --- a/app/views/consolidacao_entregas/revisar.html.erb +++ b/app/views/consolidacao_entregas/revisar.html.erb @@ -11,8 +11,11 @@

<%= @consolidacao.nome %>

- <%# Resumo por tipo %> -
+ <%# Resumo por tipo — auto-fit em vez de `md:grid-cols-7`. + Sete colunas fixas a partir de 768px dão ~55px cada quando a sidebar está + aberta (768 − 256 de menu − padding), destruindo rótulos como + "Extraordinária" e "Termo Especial". O mínimo de 9rem cabe o maior rótulo. %> +
<% [ ['entrega_normal', 'Normal', 'bg-orange-500 text-black'], ['retirada', 'Retirada', 'bg-orange-800 text-white'], diff --git a/app/views/consolidacao_entregas/validar.html.erb b/app/views/consolidacao_entregas/validar.html.erb index 5440890..31eb1b9 100644 --- a/app/views/consolidacao_entregas/validar.html.erb +++ b/app/views/consolidacao_entregas/validar.html.erb @@ -147,14 +147,39 @@ abaixo, que carrega os modais) entra NESTA mesma faixa via flex `order`, sem precisar mover os modais de lugar. %>
-
<%# Fim do cabeçalho fixo — abaixo só a lista de NFs rola. %>