Atualização READ.me
This commit is contained in:
198
README.md
198
README.md
@@ -3737,3 +3737,201 @@ a contagem e o motivo camada por camada.
|
|||||||
3. Atualizar este README (marcar fases entregues) e o perfil "Cliente" nos seeds.
|
3. Atualizar este README (marcar fases entregues) e o perfil "Cliente" nos seeds.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>🧾 28/08/2026 (sessão 2) — Financeiro, seleção em massa, plano por nome e o 404 que fingia ser outra coisa</strong></summary>
|
||||||
|
|
||||||
|
| Sintoma | Causa real | Onde se resolveu |
|
||||||
|
|---|---|---|
|
||||||
|
| Custo total e "N entregas" do mesmo card discordavam | `ces` incluía motorista arquivado; `cms` não | `dashboard_controller.rb` (EXISTS por par) |
|
||||||
|
| Rosca de composição não fechava com o Custo total | desconto entrava como fatia **positiva** | desconto sai do gráfico + linha "já abatidos" |
|
||||||
|
| Ticket médio alto demais | lote de termo contava como **1** entrega | `#contar_entregas` |
|
||||||
|
| "Entrega de Termo" com a cor de "Entrega Normal" | 5 cores fixas para 7 tipos | `DashboardController::CORES_TIPO` |
|
||||||
|
| Arquivar consolidação uma a uma | não havia seleção múltipla | `arquivar_em_lote` + linha de seleção |
|
||||||
|
| Ter que acertar a DATA do plano | a API expõe os planos por NOME | seletor + filtro de período |
|
||||||
|
| "Trocar logo" → `404 (Not Found)` | `before_action` rodando em rota de coleção | `except:` do `carregar_romaneio` |
|
||||||
|
| Checkbox destoando do site | `accent-color` = checkbox do sistema | CSS global no layout |
|
||||||
|
|
||||||
|
## 1. Financeiro: quatro números que discordavam
|
||||||
|
|
||||||
|
O ponto comum das três primeiras: **`cms` (o dinheiro) filtrava motorista
|
||||||
|
arquivado e `ces` (as entregas) não**. `arquivar_motorista` é reversível e
|
||||||
|
**mantém** as linhas em `consolidacao_entregas` — quem apaga é o
|
||||||
|
`excluir_motorista`. Sem espelhar o recorte, o mesmo card mostrava R$ de um
|
||||||
|
conjunto e contagem de outro.
|
||||||
|
|
||||||
|
O filtro é por **par** `(consolidacao_id, motorista_nome)`, não por nome: o mesmo
|
||||||
|
motorista pode estar ativo numa consolidação do período e arquivado em outra.
|
||||||
|
|
||||||
|
**O desconto não é fatia.** No dinheiro ele SUBTRAI; como fatia positiva de uma
|
||||||
|
rosca — que se lê como partes de um todo — a soma das fatias ficava maior que o
|
||||||
|
Custo total logo acima. Saiu do gráfico e virou a linha *"− R$ X em descontos, já
|
||||||
|
abatidos no Custo total"*, que só aparece quando há desconto: a divergência é
|
||||||
|
legítima, então é explicada onde ela aparece (diretriz 1).
|
||||||
|
|
||||||
|
**Lote de termo vale N, não 1.** Um lote é UMA linha com `quantidade = N` e
|
||||||
|
`valor_aplicado = N × preço`. Contado por `DISTINCT tracking_id`, um lote de 20
|
||||||
|
entrava como 1 no denominador e o ticket médio saía 20× maior. A conta agora é
|
||||||
|
`DISTINCT tracking_id` (para os pilares múltiplos, que já estava certo) **mais**
|
||||||
|
`SUM(quantidade)` dos tipos em lote — a mesma unidade que
|
||||||
|
`Analytics::TotaisPorMotorista` sempre usou.
|
||||||
|
|
||||||
|
> ⚠️ **Os números do dashboard MUDAM depois disso, para melhor.** "Entregas
|
||||||
|
> classificadas" pode subir e o ticket médio cair. Se alguém já conhecia o número
|
||||||
|
> antigo, avise — número que muda sozinho vira desconfiança do sistema inteiro.
|
||||||
|
|
||||||
|
## 2. Consolidações: arquivamento em lote
|
||||||
|
|
||||||
|
Linha de seleção sempre visível acima da lista (some para quem não tem
|
||||||
|
`consolidacao.arquivar`): *Selecionar todas* · contador · *Arquivar selecionadas*.
|
||||||
|
|
||||||
|
- O card deixou de ser **um `<a>` só**: com o link envolvendo tudo, clicar no
|
||||||
|
checkbox navegava em vez de marcar. A caixinha virou irmã do link.
|
||||||
|
- Os checkboxes usam `form="form-arquivar-lote"`, então o formulário não precisa
|
||||||
|
envolver a lista nem o carrossel.
|
||||||
|
- **Sem JS o essencial continua**: marcar na mão e enviar funciona, e o botão
|
||||||
|
nasce habilitado de propósito — quem desabilita é o Stimulus. Dado o histórico
|
||||||
|
de asset 404 em produção, o contrário deixaria a função inalcançável.
|
||||||
|
- Arquivar é reversível, então a confirmação aponta para "Arquivadas".
|
||||||
|
|
||||||
|
## 3. Romaneio: escolher o plano pelo NOME
|
||||||
|
|
||||||
|
Ver também a seção corrigida *"Sobre buscar o plano por nome na API"*. A tela
|
||||||
|
trocou o campo **"Data do plano"** por um seletor dos planos, mais um filtro de
|
||||||
|
período.
|
||||||
|
|
||||||
|
**Por que a data era impossível de acertar** — o plano tem uma JANELA, e o dia
|
||||||
|
das rotas ora é o começo dela, ora o fim:
|
||||||
|
|
||||||
|
| plano | janela | dia real das rotas |
|
||||||
|
|---|---|---|
|
||||||
|
| EMAD SETEMBRO 2026 | 31/08 → 08/09 | **31/08** (início) |
|
||||||
|
| UBS OESTE AGOSTO 2026 | 18/08 → 20/08 | **20/08** (fim) |
|
||||||
|
| UBS NORTE AGOSTO 2026 | 17/08 → 20/08 | **20/08** (fim) |
|
||||||
|
|
||||||
|
Agora quem descobre o dia é a API (uma chamada — todas as rotas de um plano
|
||||||
|
dividem o mesmo dia). A importação são **3 chamadas**, e o filtro pelas rotas do
|
||||||
|
plano não é otimização: dois planos podem cair no mesmo dia, e sem ele o romaneio
|
||||||
|
de um sairia com as paradas do outro.
|
||||||
|
|
||||||
|
Sem período, os **5 mais recentes** (a ferramenta é usada logo depois de lançar
|
||||||
|
uma operação). Com período, **todos** os da faixa — critério é a janela **cruzar**
|
||||||
|
o período, como `Consolidacao.cruzando_periodo`. Medido: agosto/2026 = 8 planos,
|
||||||
|
março/2026 = 13. Os planos "Automático" ficam na lista.
|
||||||
|
|
||||||
|
Conferido ponta a ponta contra a API real: EMAD SETEMBRO 2026 → 31/08/2026, 2047
|
||||||
|
visitas, 73 veículos, GADE_001 com 29 paradas, primeira NF 90158 — igual à tela.
|
||||||
|
|
||||||
|
## 4. "Trocar logo" dava 404 — e não era rota
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
before_action :carregar_romaneio, except: %i[index create importar_planilha]
|
||||||
|
```
|
||||||
|
|
||||||
|
`atualizar_logo` foi adicionada ao `collection` do `routes.rb` e **esquecida
|
||||||
|
aqui**. `carregar_romaneio` faz `Romaneio.find(params[:id])`, e em rota de coleção
|
||||||
|
não existe `:id` → `RecordNotFound` → **404**.
|
||||||
|
|
||||||
|
Como 404 parece "rota errada", a procura foi para todo lado menos para o
|
||||||
|
`before_action`. A prova de que a rota estava certa é um POST sem sessão:
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /admin/romaneios/atualizar_logo -> 422 (existe, barrou no CSRF)
|
||||||
|
POST /admin/romaneios/rota_que_nao_existe -> 404 (essa sim não existe)
|
||||||
|
```
|
||||||
|
|
||||||
|
E fecha o sintoma anterior — *"subi outro logo e o PDF não mudou"*: não mudou
|
||||||
|
porque **o upload nunca acontecia**.
|
||||||
|
|
||||||
|
Junto vieram as redes que faltavam nesse caminho: formato detectado pela
|
||||||
|
**assinatura do arquivo** (o `content_type` do navegador manda
|
||||||
|
`application/octet-stream` para PNG válido, e a validação antiga recusava um PNG
|
||||||
|
dizendo "precisa ser PNG"); gravação com `rescue SystemCallError` que nomeia a
|
||||||
|
falha em vez de página 500; `require 'fileutils'` explícito; e o token CSRF
|
||||||
|
global, que era o único formulário da tela sem ele.
|
||||||
|
|
||||||
|
> O Prawn embute **só PNG e JPEG**. WebP/SVG/GIF são reconhecidos para a mensagem
|
||||||
|
> poder dizer *"o arquivo é WebP, salve como PNG"* — suportá-los de verdade exige
|
||||||
|
> gem de conversão (o `libvips` já está no Dockerfile, falta o driver).
|
||||||
|
|
||||||
|
## 5. Checkbox do sistema, no site inteiro
|
||||||
|
|
||||||
|
O nativo com `accent-color` continua sendo o do sistema operacional: quadrado
|
||||||
|
duro e azul-acinzentado no hover, o único elemento denunciando "formulário cru"
|
||||||
|
numa tela toda de cantos 12px e laranja.
|
||||||
|
|
||||||
|
Vale por **seletor de elemento** no `<style>` do layout, não por classe: são ~20
|
||||||
|
checkboxes em 17 telas, e classe nova só pega quem lembrar de usá-la. Não há nada
|
||||||
|
para aplicar em cada tela.
|
||||||
|
|
||||||
|
- `:not(.sr-only)` protege os **interruptores** (padrão `sr-only peer`, em que o
|
||||||
|
checkbox é invisível e quem desenha é o irmão) — estilizá-los faria aparecer uma
|
||||||
|
caixinha em cima de cada switch.
|
||||||
|
- `min-width/min-height` em vez de `width/height`: o Tailwind vem de CDN e injeta
|
||||||
|
o `w-4 h-4` em ordem imprevisível; `min-*` vence sem depender de quem carregou
|
||||||
|
primeiro, então nenhum checkbox encolhe.
|
||||||
|
- A marca é `clip-path`, não imagem: nada para carregar e não depende do `img-src`
|
||||||
|
do CSP. Estado **indeterminado** incluído (a seleção em massa usa).
|
||||||
|
- O roxo da tela Validar é semântico (extraordinária) e continua roxo.
|
||||||
|
|
||||||
|
## 6. Produção fora do ar: `Blocked hosts`
|
||||||
|
|
||||||
|
```
|
||||||
|
ActionDispatch::HostAuthorization: Blocked hosts: controle.reemtransportes.com.br
|
||||||
|
```
|
||||||
|
|
||||||
|
Proteção contra DNS rebinding. Lista `config.hosts` vazia aceita tudo; **com
|
||||||
|
qualquer item, só o que está nela entra** — e o `docker-compose.yml` tem
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
APP_HOST: ${APP_HOST:-teste.reemtransportes.com.br}
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ **O padrão é o host de TESTE.** Um `.env` que não chega transforma a produção
|
||||||
|
em "teste" **em silêncio** — e não só na lista de hosts: `APP_HOST` monta os links
|
||||||
|
de e-mail e WhatsApp (`https://#{APP_HOST}/motorista`), então todo link mandado a
|
||||||
|
motorista passaria a apontar para o ambiente de teste, sem erro nenhum.
|
||||||
|
|
||||||
|
Diagnóstico, em um comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec app printenv APP_HOST | cat -A # ^M revela CR do Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
- `teste.…$` → o `.env` não chegou. `docker compose restart` **não relê** o
|
||||||
|
`.env`; só `up -d` recria. E `environment:` tem precedência sobre `env_file:`.
|
||||||
|
- `controle.…^M$` → CR no arquivo; a comparação de host é exata e nunca casa.
|
||||||
|
- Escotilha de emergência: `RAILS_ALLOW_ALL_HOSTS=true` (minutos, não dias).
|
||||||
|
|
||||||
|
**E 403 nem sempre é seu.** Resposta que passou pelo Rails carrega `x-request-id`
|
||||||
|
e HSTS. Sem eles, com corpo vazio e só `cf-ray`, o bloqueio é do **Cloudflare** e
|
||||||
|
nada no código resolve — é `Security → Events` no painel.
|
||||||
|
|
||||||
|
> `Puma::HttpParserError: Are you trying to open an SSL connection to a non-SSL
|
||||||
|
> Puma?` é coisa diferente: o proxy reverso está configurado com **HTTPS** como
|
||||||
|
> protocolo de destino. O Puma serve HTTP puro na 3000 e não tem certificado —
|
||||||
|
> quem termina o TLS é o Cloudflare/DSM. E health check deve apontar para
|
||||||
|
> `/health`, não para `/` (que exige login e responde 401).
|
||||||
|
|
||||||
|
## 📂 Arquivos
|
||||||
|
|
||||||
|
```
|
||||||
|
app/controllers/dashboard_controller.rb # recorte de `ces`, contagem, cores
|
||||||
|
app/views/dashboard/index.html.erb # linha do desconto + cores do controller
|
||||||
|
app/services/analytics/totais_por_motorista.rb # mesmo vazamento nos lançamentos
|
||||||
|
app/controllers/consolidacoes_controller.rb # arquivar_em_lote
|
||||||
|
app/views/consolidacoes/_lista.html.erb # linha de seleção + card sem <a> envolvente
|
||||||
|
app/javascript/controllers/selecao_lote_controller.js # marcar todas/contador — NOVO
|
||||||
|
app/services/simpli_route/client.rb # planos, plano(id), rota(id)
|
||||||
|
app/services/romaneios/plano_do_dia.rb # caminho do plano escolhido
|
||||||
|
app/controllers/admin/romaneios_controller.rb # plano_id, período, logo, `except:` do 404
|
||||||
|
app/views/admin/romaneios/index.html.erb # seletor de plano + filtro de período
|
||||||
|
app/services/pdf/romaneio_pdf.rb # logo ausente deixa de ser mudo
|
||||||
|
app/views/layouts/application.html.erb # checkbox global
|
||||||
|
bin/sondar_planos # a sondagem da API — NOVO
|
||||||
|
config/routes.rb # POST arquivar_em_lote
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|||||||
Reference in New Issue
Block a user