695 lines
40 KiB
Markdown
695 lines
40 KiB
Markdown
# 🚛 Reem Logística — Sistema de Controle de Custos
|
||
|
||
Sistema web para controle de custos de entregas hospitalares da **Gade Hospitalar**.
|
||
|
||
**Stack:** Ruby on Rails 7+ · PostgreSQL 15 · Docker · Tailwind CSS · Hotwire (Turbo + Stimulus)
|
||
**Repositório:** https://git.xenserver.com.br/Cludio-code/logistica-controle-custos
|
||
|
||
---
|
||
|
||
## 📋 CONTEXTO DO PROJETO (leia antes de tudo)
|
||
|
||
A **Gade Hospitalar** precisa controlar o custo de cada operação de entrega
|
||
(UBS SUL, UBS LESTE, EMAD, STS, SAD, etc.).
|
||
|
||
O banco **já existe** e é atualizado a cada 1 hora por outro sistema externo:
|
||
- Tabela: `public.db_reem_simplerout_2026`
|
||
- Account: Gade Hospitalar (account_id: 95907)
|
||
- **NUNCA** fazer `DROP TABLE`, `TRUNCATE`, `DELETE` ou migration nessa tabela.
|
||
|
||
O sistema novo cria suas próprias tabelas no mesmo PostgreSQL (schema separado ou prefixo)
|
||
e lê a tabela existente apenas para exibir e consolidar entregas.
|
||
|
||
---
|
||
|
||
## 🏗️ Estado atual — O que já foi feito (Fase 1 ✅)
|
||
|
||
```
|
||
logistica-controle-custos/
|
||
├── Gemfile # Todas as gems (Devise, Pundit, Prawn, Whenever, Tailwind…)
|
||
├── Dockerfile # Ruby 3.2.2-slim
|
||
├── docker-compose.yml # app + db (postgres:15)
|
||
├── tailwind.config.js # Paleta preto #0a0a0a / laranja #f97316 / branco
|
||
├── .env.example # Template de variáveis — copiar para .env
|
||
├── .gitignore # Protege .env, master.key, credenciais
|
||
│
|
||
├── config/
|
||
│ ├── database.yml # 100% via ENV — nunca hardcode
|
||
│ └── routes.rb # Rotas completas para todas as 8 fases
|
||
│
|
||
├── app/
|
||
│ ├── models/
|
||
│ │ ├── entrega.rb # ⚠️ READ-ONLY — tabela existente db_reem_simplerout_2026
|
||
│ │ ├── user.rb # Devise + roles (admin/gerente/operador/motorista) + PIN
|
||
│ │ ├── configuracao.rb # Preços configuráveis (entrega, retirada, bonus, desconto)
|
||
│ │ ├── consolidacao.rb # Soft delete, enum status, cálculo de progresso
|
||
│ │ ├── consolidacao_motorista.rb
|
||
│ │ ├── consolidacao_entrega.rb # Enum tipo: normal/retirada/bonus/desconto
|
||
│ │ ├── historico_estimado.rb # Job 1h + cálculo ao vivo
|
||
│ │ └── auditoria_log.rb # Log de ações críticas
|
||
│ │
|
||
│ ├── controllers/
|
||
│ │ └── application_controller.rb # Auth + Pundit + tema
|
||
│ ├── policies/
|
||
│ │ └── application_policy.rb # Base Pundit
|
||
│ ├── helpers/
|
||
│ │ └── application_helper.rb # nav_link_to, moeda(), badge_status(), progress_bar()
|
||
│ └── views/layouts/
|
||
│ ├── application.html.erb # Layout base dark theme laranja/preto/branco
|
||
│ └── _navbar.html.erb # Sidebar responsiva com hamburguer mobile
|
||
│
|
||
└── db/
|
||
├── seeds.rb # Admin: admin@gade.com / Gade@2026! + configs de preço
|
||
└── migrate/
|
||
├── ..._create_users.rb
|
||
├── ..._create_configuracoes.rb
|
||
├── ..._create_consolidacoes.rb
|
||
├── ..._create_consolidacao_motoristas.rb
|
||
├── ..._create_consolidacao_entregas.rb
|
||
├── ..._create_historico_estimados.rb
|
||
└── ..._create_auditoria_logs.rb
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 Setup do zero (próximo dev)
|
||
|
||
### 1. Clone
|
||
```bash
|
||
git clone https://git.xenserver.com.br/Cludio-code/logistica-controle-custos.git
|
||
cd logistica-controle-custos
|
||
```
|
||
|
||
### 2. Configure o .env
|
||
```bash
|
||
cp .env.example .env
|
||
nano .env # preencha DB_HOST, DB_PASSWORD, etc.
|
||
```
|
||
|
||
### 3. Gere o SECRET_KEY_BASE
|
||
```bash
|
||
docker run --rm ruby:3.2.2-slim bash -c "gem install rails --no-doc -q && rails secret"
|
||
# Cole o resultado no .env → SECRET_KEY_BASE=...
|
||
```
|
||
|
||
### 4. Suba os containers
|
||
```bash
|
||
docker-compose up -d
|
||
```
|
||
|
||
### 5. Banco + seeds
|
||
```bash
|
||
docker-compose exec app bundle exec rails db:create db:migrate db:seed
|
||
```
|
||
|
||
### 6. Acesse
|
||
```
|
||
http://localhost:3000
|
||
Login: admin@gade.com
|
||
Senha: Gade@2026! ← ALTERE NO PRIMEIRO ACESSO
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 Comandos do dia a dia
|
||
|
||
```bash
|
||
docker-compose exec app bundle exec rails db:migrate # nova migration
|
||
docker-compose exec app bundle exec rails console # console Rails
|
||
docker-compose logs -f app # logs em tempo real
|
||
docker-compose down # parar tudo
|
||
```
|
||
|
||
---
|
||
|
||
## 🗺️ Fases de implementação
|
||
|
||
| Fase | Status | O que faz |
|
||
|------|--------|-----------|
|
||
| **1** | ✅ Concluída | Setup + Docker + Models + Migrations + Seeds + Layout base |
|
||
| **2** | ✅ Concluída *(corrigida)* | Login e-mail/PIN + CRUD usuários + pilares de preço + toggle tema |
|
||
| **3** | ✅ Concluída *(corrigida)* | Dashboard: cards + gráfico Chart.js + ranking motoristas + navegação por mês |
|
||
| **4** | ✅ Concluída | Job Whenever (1h) + HistoricoEstimado + AuditoriaLog + API métricas |
|
||
| **5** | ✅ Concluída | Nova Consolidação + filtros + Wizard Passo 1 |
|
||
| **6** | ✅ Concluída | Toggles coloridos + ações em massa + Wizard Passos 2 e 3 (Stimulus) |
|
||
| **7** | ✅ Concluída | PDFs Prawn: relatório + holerite com QR code + modal preview |
|
||
| **8** | ✅ Concluída | Painel motorista + login PIN/QR + notificações WhatsApp/email |
|
||
|
||
**🎉 PROJETO: 8 de 8 fases implementadas.**
|
||
|
||
---
|
||
|
||
## 🔧 Análise de integração (11/06/2026) — Correções aplicadas
|
||
|
||
As Fases 2/3 (commit `4724847`) foram desenvolvidas em paralelo às Fases 4-8 (commits `90ed705` e `8b3d4bc`), e a mesclagem gerou **conflitos que quebravam a aplicação**. Análise dos commits e correções:
|
||
|
||
### 🔴 Problemas críticos encontrados e corrigidos
|
||
|
||
**1. Migration da Fase 2 quebrada** (`20260610_add_fase2_fields_to_users.rb`)
|
||
- Timestamp curto (8 dígitos) fazia ela rodar ANTES da `create_users` → erro "table does not exist"
|
||
- Adicionava coluna `ativo` que já existia → erro de coluna duplicada
|
||
- Criava `pin_acesso` duplicando o `pin_code` existente
|
||
- ✅ **Corrigida:** reescrita como `20260101000010_add_devise_trackable_to_users.rb` — só trackable/lockable, timestamp correto
|
||
|
||
**2. Campo de PIN inconsistente** — Fase 2 usava `pin_acesso`, o model e as Fases 7/8 usam `pin_code`. PINs cadastrados no CRUD não funcionariam no login.
|
||
- ✅ **Corrigido:** padronizado `pin_code` em todos os arquivos
|
||
|
||
**3. Campo de nome inconsistente** — CRUD de usuários usava `:name`, o schema usa `nome` → o CRUD quebrava ao salvar.
|
||
- ✅ **Corrigido:** padronizado `nome`
|
||
|
||
**4. Controllers fora do namespace** — As rotas esperam `Admin::UsuariosController` e `Admin::ConfiguracoesController`, mas os controllers foram criados na raiz → `/admin/usuarios` dava "uninitialized constant" e os controllers ficavam inacessíveis.
|
||
- ✅ **Corrigido:** movidos para `app/controllers/admin/` + views para `app/views/admin/` + paths atualizados (`admin_usuarios_path` etc.)
|
||
|
||
**5. Dashboard (Fase 3) chamava métodos inexistentes** — `Entrega.do_mes` e `Configuracao.mapa_de_precos` foram sobrescritos pelo commit das Fases 4-6 → **a raiz do site quebrava** (root = dashboard#index).
|
||
- ✅ **Corrigido:** scope `do_mes` e método `mapa_de_precos` restaurados nos models, junto com `tipo_label`/`icone` usados pelas views de configurações
|
||
|
||
**6. AuditoriaLog com 2 assinaturas** — Fase 2 chamava `registrar(user, 'acao', ip)` posicional; o model usa keyword args → `ArgumentError` em login PIN, CRUD de usuários e configurações.
|
||
- ✅ **Corrigido:** todas as chamadas convertidas para a assinatura do model
|
||
|
||
**7. Rotas/controllers ausentes**
|
||
- `Admin::AuditoriaLogsController` tinha rota mas não existia → ✅ criado com view de listagem e filtros
|
||
- `toggle_tema` e `toggle_ativo` existiam nos controllers mas sem rota → ✅ rotas adicionadas
|
||
- Controller de configurações usava campo `tipo` que não existe (schema usa `chave`) → ✅ corrigido
|
||
|
||
### 🟡 Duplicações removidas (arquivos órfãos)
|
||
- `app/controllers/motorista_controller.rb` + `app/views/motorista/index.html.erb` — a rota `/motorista` usa `Motorista::DashboardController` (Fase 8)
|
||
- `app/views/layouts/_sidebar.html.erb` — o layout renderiza `_navbar.html.erb`
|
||
- Seeds dos 3 motoristas de teste (João/1234, Maria/5678, Carlos/9012) restaurados — tinham sido sobrescritos
|
||
|
||
### ⚠️ Observação sobre login PIN duplicado
|
||
Existem **dois fluxos de login por PIN** funcionais: a aba PIN na tela Devise (`/auth/login`, Fase 2) e a tela dedicada (`/motorista/login`, Fase 8 — usada pelo QR Code do holerite). Ambos usam `pin_code` e funcionam; recomenda-se manter os dois (a tela dedicada é melhor para mobile) ou unificar futuramente.
|
||
|
||
### 📂 Arquivos das Fases 4, 5 e 6
|
||
|
||
**Fase 4 — Job 1h + Auditoria:**
|
||
```
|
||
config/schedule.rb # Whenever — roda a cada 1h
|
||
lib/tasks/historico.rake # rake historico:atualizar
|
||
app/jobs/application_job.rb
|
||
app/jobs/atualizar_historico_estimado_job.rb # conta entregas pagas × preco_entrega
|
||
app/controllers/api/v1/dashboard_controller.rb # GET /api/v1/dashboard/metricas
|
||
app/controllers/concerns/auditavel.rb # auditar!(:acao, registro) nos controllers
|
||
app/policies/dashboard_policy.rb
|
||
```
|
||
Ativar o cron no servidor: `bundle exec whenever --update-crontab`
|
||
Testar manualmente: `docker-compose exec app bundle exec rake historico:atualizar`
|
||
|
||
**Fase 5 — Consolidação + Wizard Passo 1:**
|
||
```
|
||
db/migrate/20260101000008_add_route_ids_to_consolidacoes.rb # rodar db:migrate!
|
||
app/controllers/consolidacoes_controller.rb # index/new/create/show/wizard/finalizar/arquivar
|
||
app/policies/consolidacao_policy.rb
|
||
app/views/consolidacoes/index.html.erb # lista com filtros (status/nome/período/motorista/rota)
|
||
app/views/consolidacoes/new.html.erb # form: nome + 2 date pickers + multi-select motoristas/rotas
|
||
app/views/consolidacoes/wizard.html.erb # Passo 1: lista motoristas com progresso
|
||
```
|
||
|
||
**Fase 6 — Validação + Toggles + Massa:**
|
||
```
|
||
app/controllers/consolidacao_entregas_controller.rb # validar/classificar/classificar_em_massa/revisar
|
||
app/views/consolidacao_entregas/validar.html.erb # Passo 2: toggles coloridos + checkbox massa + resumo lateral
|
||
app/views/consolidacao_entregas/revisar.html.erb # Passo 3: resumo + próximo motorista / salvar rascunho
|
||
app/javascript/controllers/validacao_controller.js # Stimulus: tempo real (progresso, resumo, toggles)
|
||
config/routes.rb # ATUALIZADO — rotas do wizard
|
||
```
|
||
|
||
**Cores dos toggles (padrão do projeto):**
|
||
🟧 Laranja = Entrega Normal · 🟫 Laranja escuro = Retirada · ⬜ Branco = Bônus · ⬛ Preto = Desconto
|
||
|
||
**Regras implementadas:**
|
||
- Entregas elegíveis: `status='completed'` AND `checkout IS NOT NULL` (somente leitura da tabela existente)
|
||
- Não permite finalizar consolidação com entregas não classificadas (botão desabilitado + validação server-side)
|
||
- Consolidação finalizada não pode ser editada por operadores (Pundit)
|
||
- Todas ações críticas registradas em `auditoria_logs`
|
||
|
||
**Fase 7 — PDFs (Prawn + QR Code):**
|
||
```
|
||
Gemfile # + rqrcode, chunky_png
|
||
db/migrate/20260101000009_add_login_token_to_users.rb # token para QR — rodar db:migrate!
|
||
app/services/pdf/base_pdf.rb # layout base: cabeçalho preto/laranja + rodapé
|
||
app/services/pdf/relatorio_motorista_pdf.rb # tabela detalhada de entregas + resumo + total
|
||
app/services/pdf/holerite_pdf.rb # estilo contracheque + QR Code de login rápido
|
||
app/controllers/consolidacoes_controller.rb # ATUALIZADO: gerar_pdf_relatorio/gerar_pdf_holerite/preview_holerite
|
||
app/views/consolidacoes/show.html.erb # botões por motorista + modal de preview
|
||
app/views/consolidacoes/preview_holerite.html.erb # preview HTML antes de confirmar o PDF
|
||
app/models/user.rb # ATUALIZADO: regenerar_login_token!
|
||
```
|
||
Soft delete já implementado: consolidações finalizadas vão para "arquivada", nunca são excluídas.
|
||
|
||
**Fase 8 — Painel Motorista + Notificações:**
|
||
```
|
||
Gemfile # + twilio-ruby
|
||
app/controllers/motorista/sessoes_controller.rb # login PIN 4 dígitos + acesso via QR (/motorista/acesso/:token)
|
||
app/controllers/motorista/dashboard_controller.rb # 2 cards: estimado (mês) + consolidado (finalizadas)
|
||
app/views/motorista/sessoes/new.html.erb # teclado numérico gigante mobile-first (envia no 4º dígito)
|
||
app/views/motorista/dashboard/index.html.erb # cards laranja/branco + lista com download do próprio holerite
|
||
app/services/notificacao_service.rb # WhatsApp (Twilio) + email — falha nunca quebra a finalização
|
||
app/mailers/application_mailer.rb
|
||
app/mailers/consolidacao_mailer.rb # pagamento_fechado
|
||
app/views/consolidacao_mailer/pagamento_fechado.html.erb # email com identidade visual da marca
|
||
app/views/layouts/mailer.html.erb
|
||
config/initializers/smtp.rb # SMTP via ENV (só ativa se SMTP_USERNAME existir)
|
||
config/routes.rb # ATUALIZADO: /motorista/login + /motorista/acesso/:token
|
||
```
|
||
|
||
**Notificações — como ativar:**
|
||
1. WhatsApp: preencha `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN` e `TWILIO_WHATSAPP_FROM` no `.env` e mude a configuração `notificacao_whatsapp` para `true` no sistema. Motorista precisa ter `telefone` cadastrado (formato `+5511999998888`).
|
||
2. Email: preencha as variáveis `SMTP_*` no `.env` e mude `notificacao_email` para `true`. Motorista precisa ter `email`.
|
||
3. Ao finalizar uma consolidação, cada motorista recebe automaticamente a mensagem com o valor e o link do painel.
|
||
|
||
**Fluxo do motorista (público de baixo nível técnico):**
|
||
- Acessa `/motorista/login` → digita PIN de 4 números num teclado gigante (envia sozinho no 4º dígito)
|
||
- OU escaneia o QR Code impresso no holerite → entra direto sem digitar nada
|
||
- Vê 2 cards: 🟧 valor estimado do mês ("aproximado") e ⬜ valor fechado para pagamento
|
||
- Baixa o próprio holerite das consolidações finalizadas
|
||
|
||
### 🧪 Teste end-to-end sugerido
|
||
1. Login admin → criar consolidação (nome + período + motoristas)
|
||
2. Wizard Passo 1 → escolher motorista → Passo 2: classificar entregas com os toggles
|
||
3. Usar "Marcar todos como Normal" → Passo 3: revisar → próximo motorista
|
||
4. Com tudo classificado → Finalizar (motoristas notificados)
|
||
5. Na tela da consolidação → "Ver antes de gerar" → Confirmar → baixar Holerite PDF
|
||
6. Logout → `/motorista/login` → entrar com PIN do motorista
|
||
7. Conferir cards de valores → baixar próprio holerite → sair
|
||
|
||
### ▶️ Próximos passos (deploy)
|
||
|
||
O projeto está completo. Para colocar em produção:
|
||
|
||
```bash
|
||
# 1. Configurar .env com credenciais reais (DB_HOST do PostgreSQL existente)
|
||
cp .env.example .env && nano .env
|
||
|
||
# 2. Subir e migrar
|
||
docker-compose up -d --build
|
||
docker-compose exec app bundle exec rails db:create db:migrate db:seed
|
||
|
||
# 3. Ativar o cron do job de 1h
|
||
docker-compose exec app bundle exec whenever --update-crontab
|
||
|
||
# 4. Testar o fluxo end-to-end (seção de teste acima)
|
||
```
|
||
|
||
**Credenciais de teste (seeds):**
|
||
|
||
| Usuário | Login | Senha/PIN |
|
||
|---------|-------|-----------|
|
||
| Admin | admin@gade.com | Gade@2026! |
|
||
| Gerente | gerente@gade.com | Gade@2026! |
|
||
| Motorista João Silva | PIN | 1234 |
|
||
| Motorista Maria Santos | PIN | 5678 |
|
||
| Motorista Carlos Souza | PIN | 9012 |
|
||
|
||
⚠️ Altere as senhas padrão no primeiro acesso em produção.
|
||
|
||
---
|
||
|
||
## 🧪 Auditoria + Suíte de testes (15/06/2026)
|
||
|
||
Revisão de código + criação da suíte RSpec (o projeto tinha `rspec-rails`/`factory_bot`/`capybara` no Gemfile, mas **nenhum teste** e **nenhum `spec/`**).
|
||
|
||
### 🔴 Bugs corrigidos
|
||
|
||
**1. Erro de sintaxe em `Admin::UsuariosController`** — `toggle_ativo` tinha `entidade: \'User\'` (aspas escapadas com barra). O arquivo não carregava → qualquer acesso a `/admin/usuarios` dava 500. ✅ corrigido.
|
||
|
||
**2. Migration quebrava o setup do zero** — `20260610_add_fase2_fields_to_users.rb` readicionava a coluna `ativo` (já criada em `create_users`) sem guard → `PG::DuplicateColumn` num `db:migrate` limpo. ✅ guard `unless column_exists?` em `ativo` e `pin_acesso`.
|
||
|
||
**3. `db:seed` falhava no 2º motorista** — motoristas sem e-mail colidiam no índice único de `email` (`default ""`). ✅ seeds agora geram placeholder único (`nome.parameterize@motorista.local`).
|
||
|
||
**4. Dashboard ignorava o filtro de conta Gade** — `DashboardController` e `HistoricoEstimado.{atualizar!,calcular_ao_vivo}` contavam entregas de **todas** as contas. ✅ aplicado `.da_conta_gade`.
|
||
|
||
**5. `valor_total` com duas fontes de verdade** — o controller somava `valor_aplicado` (capturado na classificação) e o callback do model recalculava pelos **preços atuais** de `Configuracao` → divergência quando os preços mudavam. ✅ `ConsolidacaoMotorista#recalcular_valor` agora usa `valor_aplicado` (fonte única).
|
||
|
||
**6. Gate de finalização + classificação sem validação**
|
||
- `todas_entregas_classificadas?` usava soma agregada com `>=`, mascarando motoristas pendentes quando outro tinha entregas de sobra. ✅ agora exige cada motorista completo (`all?`) e respeita o filtro de rotas.
|
||
- `classificar`/`classificar_em_massa` aceitavam `tracking_id`/`motorista` arbitrários. ✅ validam que o motorista pertence à consolidação e que a entrega é elegível no período/rota.
|
||
|
||
**7. Coluna órfã `pin_acesso`** — nunca usada (o sistema usa `pin_code`). ✅ removida via migration.
|
||
|
||
**Minors:** motorista logado conseguia abrir `/dashboard` (✅ redireciona ao painel do motorista); `Date.parse(params[:data])` sem rescue dava 500 (✅ cai em `Date.current`); `arquivar!` não registrava autor (✅ novas colunas `arquivado_por`/`arquivado_em` + `belongs_to :arquivador`); `MotoristaController` + `app/views/motorista/index.html.erb` órfãos e quebrados (`current_user.name`, `where(user:)`, status inexistentes) — ✅ removidos.
|
||
|
||
### 🆕 Migrations adicionadas (rodar `db:migrate`)
|
||
```
|
||
db/migrate/20260615000001_remove_pin_acesso_from_users.rb
|
||
db/migrate/20260615000002_add_arquivamento_to_consolidacoes.rb # arquivado_por / arquivado_em
|
||
```
|
||
|
||
### ✅ Suíte de testes
|
||
```
|
||
.rspec
|
||
config/database.yml # ENV-driven, com banco de teste separado (<DB_NAME>_test)
|
||
spec/spec_helper.rb · spec/rails_helper.rb
|
||
spec/support/{pundit,devise}.rb
|
||
spec/factories.rb
|
||
spec/models/{user,configuracao,consolidacao,consolidacao_motorista,consolidacao_entrega,auditoria_log}_spec.rb
|
||
spec/policies/consolidacao_policy_spec.rb
|
||
spec/requests/dashboard_spec.rb
|
||
```
|
||
|
||
Como rodar:
|
||
```bash
|
||
docker-compose up -d
|
||
docker-compose exec app env RAILS_ENV=test bundle exec rails db:create db:migrate
|
||
docker-compose exec app bundle exec rspec
|
||
```
|
||
|
||
> Os specs que dependem da tabela read-only `db_reem_simplerout_2026` stubam `Entrega` — o banco de teste não contém essa tabela externa.
|
||
|
||
---
|
||
|
||
## 🔧 Correções + funcionalidades (16/06/2026)
|
||
|
||
### 🔴 Erros de runtime corrigidos (reportados em prints)
|
||
- **Logout** dava `No route matches [GET] /auth/logout` — o link usava `method: :delete` (rails-ujs), ignorado pelo Turbo. Agora usa `data-turbo-method`.
|
||
- **/admin/usuarios** e **/admin/configuracoes** estouravam `NoMethodError: admin_ou_gerente?` — helpers de papel (`admin?`, `admin_ou_gerente?`) movidos para a `ApplicationPolicy`.
|
||
- **Listagem de usuários** chamava o helper inexistente `badge_role` — criado em `ApplicationHelper`.
|
||
- **Nova consolidação** estourava `NoMethodError: new?` — `ApplicationPolicy` agora define `new? = create?` e `edit? = update?`.
|
||
- **Login do motorista (PIN)** derrubava `/motorista` com `PG::UndefinedTable: relation "consolidacao_motorista" does not exist` — o inflector pt-BR resolvia o composto no singular. `self.table_name` fixado explicitamente em `Consolidacao`, `ConsolidacaoMotorista`, `ConsolidacaoEntrega` e `Configuracao`.
|
||
- **Listagem de usuários** faltava o helper `badge_status_usuario` e a rota `toggle_ativo` estava sem o prefixo `admin_` (verbo errado também) — corrigidos.
|
||
- **Passo 3 (revisão) da consolidação** ficava em branco: `GROUP BY tipo` combinado com `ORDER BY created_at` é inválido no PostgreSQL (`PG::GroupingError`, vira 500/tela branca em produção). Corrigido com `reorder(nil)` no count agrupado.
|
||
|
||
### 🆕 Funcionalidades
|
||
- **Dashboard com filtro de faixa de datas** (Flatpickr, modo range) no lugar da navegação por mês; o controller passou a consultar por período e a respeitar `.da_conta_gade`.
|
||
- **Regra de pagamento por checkout:** `Entrega.pagas` agora é `status='completed' AND checkout IS NOT NULL` (antes era `checkin`). Vale para dashboard, painel do motorista, job de 1h e elegibilidade das consolidações.
|
||
- **Consolidação por veículo:** o filtro/identificação passou de rota (`route_id`, UUID) para **veículo** (`vehicle`). Migration troca `consolidacoes.route_ids` por `vehicle_ids`.
|
||
- **Filtro de conta configurável:** `account_id` na tabela externa é texto e vinha vazio nos registros recentes (o dado de junho estava 100% em conta vazia, e o filtro fixo em `95907` zerava o dashboard). `DB_EXISTING_ACCOUNT_ID` agora aceita lista de contas ou `all`/vazio (sem filtro). Comparação feita como texto.
|
||
- **Múltiplos pilares por entrega:** na validação da consolidação cada entrega pode receber mais de um pilar ao mesmo tempo (ex.: Normal + Bônus + Retirada), somando os valores. Os toggles viraram multi-seleção (ligam/desligam por pilar); a unicidade passou a ser `[consolidacao_id, tracking_id, tipo]` e o progresso conta entregas distintas.
|
||
- **Tela de "Pilares de Preço"** passou a listar só os pilares de preço (Entrega/Retirada/Bônus/Desconto); Nome da Empresa e Notificações saíram dali (não são preço).
|
||
|
||
### ⚙️ Automação / diagnóstico
|
||
- `docker-compose.yml`: o boot roda `rails db:prepare` antes do servidor — **não precisa mais rodar `db:migrate` na mão** após o `up`.
|
||
- `rake reem:diagnostico`: checa a tabela externa por conta/período/status/checkout e o intervalo de datas disponível (útil para depurar dashboard vazio).
|
||
|
||
```bash
|
||
docker-compose exec app bundle exec rake reem:diagnostico
|
||
docker-compose exec app bundle exec rake reem:diagnostico INICIO=2026-02-01 FIM=2026-06-30
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 Correções + funcionalidades (16/06/2026 — sessão 2)
|
||
|
||
### 🔴 Bugs corrigidos
|
||
- **Dashboard zerado por filtro de conta:** `DB_EXISTING_ACCOUNT_ID` é uma **lista separada por vírgula**, não um trecho de SQL. Valor errado (ex.: `account_id IN ('95907', '')`) zerava tudo. Use `95907,` (vírgula no fim) para incluir também registros com conta vazia/NULL. O scope `Entrega.da_conta_gade` agora inclui `NULL` quando há token vazio na lista.
|
||
- **Dashboard travava ao voltar (só com F5):** era o cache de preview do Turbo servindo um snapshot congelado. Adicionado `turbo-cache-control: no-cache` na página e o gráfico Chart.js virou idempotente (`Chart.getChart(ctx)?.destroy()`).
|
||
- **500 ao criar motorista:** `users.email` era `NOT NULL` com índice único e default `""` — o 2º motorista sem e-mail colidia em `""` (`RecordNotUnique`). Agora a coluna permite `NULL` e e-mail em branco vira `nil` (`User#normalizar_email_em_branco`); no Postgres vários `NULL` convivem no índice único.
|
||
- **500 ao gerar relatório PDF** (`gerar_pdf_relatorio`): `@entregas.group(:tipo).count` com `.order(:created_at)` gerava `GROUP BY ... ORDER BY created_at` → `PG::GroupingError`. Corrigido com `reorder(nil)`. Também removidas as opções `color:` passadas ao `Prawn#text` (não suportadas — uso de `fill_color`).
|
||
- **Motorista não baixava o próprio holerite** ("sem permissão"): `ConsolidacaoPolicy#show?` exige `pode_consolidar?` (falso para motorista). Novo `autorizar_holerite!` no `ConsolidacoesController` — staff baixa qualquer um; motorista baixa só o próprio (PDF já filtrado pelo nome dele).
|
||
- **QR code do holerite não abria:** URL estava fixa em `https://`, mas o servidor roda em **http** (IP:porta). Novo `url_acesso` respeita o esquema do `APP_HOST` (sem esquema = http). Defina `APP_HOST` com o IP/host acessível pelo celular.
|
||
|
||
### 🆕 Funcionalidades
|
||
- **NF + Endereço no relatório PDF:** colunas separadas; a coluna **Tracking** foi substituída por **Veículo** (relatório e tela de revisão / passo 3).
|
||
- **Endereço nas telas da consolidação:** linha de endereço nos cards do passo 2 (validação) e coluna Endereço no passo 3 (revisão, paginado 20/50/100).
|
||
- **Campos de assinatura nos PDFs:** bloco com assinatura do **Motorista** e do **Administrador** (Reem Transporte) no relatório e no holerite (`BasePdf#assinaturas`).
|
||
- **Selecionar todos** no passo 2: checkbox que marca/desmarca todas as entregas de uma vez.
|
||
- **Desselecionar em massa:** toggle "Modo remover" — os botões Normal/Retirada/Bônus/Desconto (e "marcar todos") passam a **remover** o pilar das entregas selecionadas (`classificar_em_massa` aceita `acao: remover`).
|
||
- **Botões de voltar** entre os passos do wizard (2→1, 3→2) viraram botões visíveis (antes eram texto cinza discreto).
|
||
|
||
### 🆕 Migration adicionada (rodar `db:migrate`)
|
||
- `20260616000003_allow_null_email_for_motoristas` — `users.email` passa a permitir `NULL`; e-mails `""` existentes viram `NULL`.
|
||
|
||
```bash
|
||
docker-compose exec app bundle exec rails db:migrate
|
||
```
|
||
|
||
---
|
||
|
||
## 🗄️ Banco existente — referência rápida
|
||
|
||
| Campo | Uso |
|
||
|-------|-----|
|
||
| `tracking_id` | PK lógico da entrega |
|
||
| `reference_id` | Número da NF |
|
||
| `planned_date` | Data da entrega (filtro principal) |
|
||
| `driver` | Nome do motorista |
|
||
| `status` | `'completed'` = entrega concluída |
|
||
| `checkout` | NULL = motorista não finalizou (define elegibilidade de pagamento) |
|
||
| `vehicle` | Veículo da entrega (usado no filtro de consolidação) |
|
||
| `route_id` | UUID da operação (não usado no app — substituído por `vehicle`) |
|
||
| `contact_name` | Nome do local (UBS SACOMA, EMAD VELEIROS…) |
|
||
|
||
**Regra de pagamento:** `status = 'completed'` AND `checkout IS NOT NULL`
|
||
|
||
---
|
||
|
||
## 🎨 Design — padrão da marca
|
||
|
||
| Elemento | Cor | Hex |
|
||
|----------|-----|-----|
|
||
| Fundo | Preto | `#0a0a0a` |
|
||
| Cards | Cinza escuro | `#1a1a1a` |
|
||
| Destaque / botões | Laranja | `#f97316` |
|
||
| Textos | Branco | `#ffffff` |
|
||
| Sucesso | Verde | `#22c55e` |
|
||
| Erro/desconto | Vermelho | `#ef4444` |
|
||
|
||
- Fonte mínima 16px desktop / 18px mobile
|
||
- Botões mínimo 48px altura (acessibilidade touch)
|
||
- Tema escuro como padrão
|
||
|
||
---
|
||
|
||
## 👥 Perfis de acesso
|
||
|
||
| Perfil | Login | Acesso |
|
||
|--------|-------|--------|
|
||
| Admin | email + senha | Tudo |
|
||
| Gerente | email + senha | Dashboard + Consolidações + PDFs |
|
||
| Operador | email + senha | Consolidações (criar + validar) |
|
||
| Motorista | PIN 4 dígitos ou QR | Painel pessoal + baixar próprio PDF |
|
||
|
||
---
|
||
|
||
|
||
## 🩺 Correção (11/06): container caía mostrando ajuda do "rails new"
|
||
|
||
**Sintoma:** `docker-compose up` subia e o log mostrava o help do `rails new` em loop.
|
||
**Causa:** o repositório tinha os arquivos da aplicação (models/controllers/views) mas **não tinha o esqueleto de boot do Rails** — sem `config/application.rb` e `config.ru`, o comando `rails s` não reconhece a pasta como um app Rails e cai no modo "criar app novo".
|
||
|
||
**Arquivos adicionados nesta correção:**
|
||
```
|
||
config.ru # entrada Rack
|
||
config/boot.rb, environment.rb # boot do Rails
|
||
config/application.rb # módulo GadeLogistica + timezone SP + pt-BR
|
||
config/puma.rb # servidor web
|
||
config/environments/{development,production,test}.rb
|
||
config/importmap.rb # pins do Stimulus/Turbo
|
||
config/initializers/devise.rb # obrigatório para o devise_for das rotas
|
||
config/locales/pt-BR.yml # formatos de data brasileiros
|
||
bin/rails, bin/rake # executáveis
|
||
app/javascript/application.js # bootstrap Turbo + Stimulus
|
||
app/javascript/controllers/{application,index}.js
|
||
Gemfile # + gem propshaft (servir o JS via importmap)
|
||
app/views/layouts/application.html.erb # + javascript_importmap_tags (Stimulus não carregava!)
|
||
config/routes.rb # devise_for agora aponta para users/sessions (login PIN)
|
||
```
|
||
|
||
### Como subir agora (passo a passo completo)
|
||
|
||
```bash
|
||
# 1. Criar o .env (você NÃO tem ele ainda — é cópia do exemplo):
|
||
cp .env.example .env
|
||
|
||
# 2. Gerar a SECRET_KEY_BASE e colar no .env:
|
||
openssl rand -hex 64
|
||
nano .env # cole em SECRET_KEY_BASE= e preencha DB_HOST/DB_USER/DB_PASSWORD
|
||
|
||
# 3. Rebuild (Gemfile mudou — gem propshaft):
|
||
docker-compose build --no-cache
|
||
docker-compose up -d
|
||
|
||
# 4. Criar banco do sistema + migrar + popular:
|
||
docker-compose exec app bundle exec rails db:create db:migrate db:seed
|
||
|
||
# 5. Conferir:
|
||
docker-compose logs -f app # deve mostrar "Listening on http://0.0.0.0:3000"
|
||
# Acesse http://SEU_IP:3000 → admin@gade.com / Gade@2026!
|
||
```
|
||
|
||
## 🔐 Segurança
|
||
|
||
- **NUNCA** versione `.env`, `config/master.key` ou qualquer arquivo com senha
|
||
- Repositório **PRIVADO** em git.xenserver.com.br
|
||
- Use sempre `ENV['VARIAVEL']` no código Ruby
|
||
|
||
---
|
||
|
||
## 🆕 Atualização 17/06/2026 — Pagamentos, Operações, Dashboard financeiro e UI
|
||
|
||
Conjunto de melhorias para deixar o sistema pronto para entrega final.
|
||
|
||
### 💳 Status de pagamento (Pago / Parcial / Pendente)
|
||
- Migration `add_pagamento_to_consolidacao_motoristas`: colunas **`pago_em`**, **`pago_por`**,
|
||
**`forma_pagamento`** em `consolidacao_motoristas` (pagamento é **por motorista**).
|
||
- `ConsolidacaoMotorista`: `pago?`, `marcar_pago!(user, forma:)`, `cancelar_pagamento!`,
|
||
scopes `pagos`/`pendentes`, `belongs_to :pagador`.
|
||
- `Consolidacao#status_pagamento` (`:pendente`/`:parcial`/`:pago`), `valor_pago`, `valor_pendente`,
|
||
scopes `pagamento_pago`/`pagamento_pendente`/`pagamento_parcial` (filtro da lista).
|
||
- Tela **show**: marcar motorista individual **ou a operação inteira** (com `<select>` de forma);
|
||
estorno. Ações `registrar_pagamento`/`cancelar_pagamento` (admin/gerente).
|
||
- Helper `badge_pagamento`; filtro pago/pendente na **index**; selo no **holerite**.
|
||
- **Notificação de pagamento**: `NotificacaoService.notificar_pagamento` (WhatsApp + e-mail
|
||
`ConsolidacaoMailer#pagamento_efetuado`) — falhas só logam.
|
||
- **Aviso in-app no painel do motorista**: faixa "Pagamento confirmado" (últimos 7 dias) + selo
|
||
PAGO por consolidação.
|
||
|
||
### 🗂️ Consolidações por OPERAÇÃO (tabelas externas `gade_entregas_*`)
|
||
- PORO **`app/models/operacao.rb`**: descobre as tabelas via `information_schema`, com
|
||
**whitelist anti-injection** (`Operacao.sanitizar` + `quote_table_name`) — nunca interpolar
|
||
nome de tabela cru. `Operacao.todas`, `dados(tabelas)`, `agrupadas_por_mes`.
|
||
- `Entrega.da_operacoes(tabelas)`: junta por NF (`reference_id::text = nota_fiscal`).
|
||
- Coluna **`operacoes` (jsonb)** em `consolidacoes`; `Consolidacao.com_operacoes(ops)`.
|
||
- **Nova consolidação**: multi-seleção de operação **pré-preenche** motoristas/veículos/período
|
||
e **filtra** as entregas por NF; liberdade de marcar **um ou todos** os motoristas da operação.
|
||
- ENV opcional `OPERACOES_TABLE_PREFIX` (default `gade_entregas_`).
|
||
|
||
### 📊 Dashboard financeiro
|
||
- **Filtro de operação** no topo (multi-seleção, agrupada por mês, com "Limpar") aplicado a tudo.
|
||
- KPIs: **Custo total**, **Ticket médio/entrega**, **Pago**, **A pagar**.
|
||
- Gráficos (Chart.js): **custo por operação**, **composição por tipo**, **custo por motorista**,
|
||
**rosca pago × a pagar** + tabelas (pagos por data de pagamento, pendentes).
|
||
|
||
### 📅 Correção de data — usar `checkout` (data real), não `planned_date`
|
||
- `Entrega.no_periodo_checkout(inicio, fim)`: filtro pela **data real da conclusão**, com
|
||
**comparação naïve** (intervalo meio-aberto `>= início` e `< fim+1`), **sem conversão de fuso**
|
||
— igual à análise feita direto no banco.
|
||
- Aplicado em `contar_pagas`, elegibilidade do wizard, gráfico principal (`DATE(checkout)`) e na
|
||
exibição da data das entregas.
|
||
|
||
### 🧾 PDFs (Relatório e Holerite)
|
||
- **Relatório**: endereço completo com quebra de linha, larguras de coluna fixas, quebra de
|
||
página antes do RESUMO. **Rodapé via `canvas`** (na margem inferior — não sobrepõe mais o
|
||
conteúdo); margem inferior 72.
|
||
- **Holerite**: coluna **Unit.**, os 4 pilares sempre exibidos, **total de entregas + veículos**,
|
||
**selo de pagamento** (PAGO/PENDENTE) e **garantia de espaço para o QR Code**
|
||
(`start_new_page` se faltar espaço — o Prawn não pagina imagem sozinho).
|
||
|
||
### 🎨 UI / Identidade visual
|
||
- **Fonte base maior** para leitura: `html { font-size: 17px }` (desktop) e `18px` (mobile).
|
||
- **Contraste** elevado no dashboard (`text-gray-500/600` → `text-gray-400`).
|
||
- **Painel do motorista** redesenhado mobile-first (fontes maiores, botões full-width, alvos ≥48px).
|
||
- **Favicon** (van laranja): `public/favicon.png`.
|
||
- **Logo da Reem** na barra (sidebar + topo mobile): `public/logo-reem.png`.
|
||
- **Animação de abertura (splash) após login**: vídeo `public/login-animacao.{webm,mp4}` (WebM +
|
||
MP4 fallback), exibido **uma vez** via flag de sessão (`session[:mostrar_splash]`), com
|
||
`mix-blend-mode: screen` para "derrubar" o fundo preto do vídeo.
|
||
- `rack-mini-profiler` desativado em `config/initializers/rack_mini_profiler.rb`.
|
||
|
||
### 📌 Padrões a seguir (novos)
|
||
- **Tabelas externas / read-only**: acesso só por SELECT; nome de tabela em SQL **sempre** via
|
||
whitelist + `quote_table_name` (ver `Operacao`).
|
||
- **Datas financeiras**: usar **`checkout`** (`no_periodo_checkout`), comparação naïve sem fuso.
|
||
- **Gráficos**: Chart.js via CDN, padrão **IIFE** + `Chart.getChart(ctx)?.destroy()` (evita
|
||
"Canvas already in use" com Turbo).
|
||
- **Badges**: `badge_status` (consolidação) e `badge_pagamento` (pagamento).
|
||
- **Assets estáticos** (favicon, logo, vídeos) ficam em `public/` e são referenciados por caminho
|
||
absoluto (`/arquivo.ext`).
|
||
|
||
### Migrations adicionadas hoje
|
||
```
|
||
20260617000001_add_pagamento_to_consolidacao_motoristas.rb
|
||
20260617000002_add_operacoes_to_consolidacoes.rb
|
||
```
|
||
Aplicar com: `docker-compose exec app bundle exec rails db:migrate`
|
||
|
||
---
|
||
|
||
## 🔧 Correções — Holerite PDF (18/06/2026)
|
||
|
||
### 🔴 Bugs corrigidos
|
||
- **500 ao gerar holerite** (`gerar_pdf_holerite`): `Prawn::Errors::IncompatibleStringEncoding`
|
||
(`Encoding::UndefinedConversionError`). Os PDFs usavam a fonte **embutida** do Prawn
|
||
(Helvetica/AFM), que só aceita o charset **Windows-1252** — o caractere **`✓`** do selo
|
||
"✓ PAGO" não existe nesse charset e derrubava a geração.
|
||
✅ **Corrigido:** `BasePdf` agora registra a família **DejaVu Sans (TTF)** e a define como
|
||
fonte padrão (`registrar_fonte_utf8`), com suporte total a **UTF-8**. Vale para todos os PDFs
|
||
(holerite e relatório) — qualquer caractere Unicode passa a funcionar.
|
||
- Fontes versionadas em `app/assets/fonts/DejaVuSans.ttf` e `DejaVuSans-Bold.ttf`
|
||
(a imagem Docker `ruby:3.2.2-slim` não traz fontes do sistema; o `COPY . .` do Dockerfile e
|
||
o volume `.:/app` garantem que estejam disponíveis em runtime).
|
||
- **Assinaturas do holerite caíam na 2ª página:** após o QR Code o cursor ficava abaixo do
|
||
limite de quebra (`start_new_page if cursor < 120`), empurrando o bloco de assinaturas para
|
||
uma página nova. ✅ **Corrigido:** espaçamentos compactados com segurança (padding das
|
||
tabelas `[4,8]`, QR Code menor) + folga antes das assinaturas reduzida (`BasePdf#assinaturas`,
|
||
60 → 36, limite de quebra 120 → 100) — **o holerite agora cabe em uma única página**.
|
||
- **QR Code sobrepondo / sumindo:** o posicionamento por `move_up`/`move_down` era frágil e,
|
||
com a fonte nova, fazia o QR se sobrepor ao texto/assinaturas. ✅ **Corrigido:**
|
||
`desenhar_qrcode` agora desenha a imagem com **posição absoluta (`at:`)** — que não move o
|
||
cursor — e avança o cursor manualmente pela altura da imagem. Também garantido que cada caixa
|
||
(selo de pagamento, total) tenha folga **≥ a própria altura** para não sobrepor o conteúdo
|
||
seguinte.
|
||
|
||
- **QR Code não aparecia para alguns motoristas:** o QR aponta para o painel do motorista
|
||
(`/motorista/acesso/:token`), que **exige uma conta de usuário** (`User` motorista com
|
||
`login_token`). Sem conta correspondente, `user_motorista` ficava `nil` e a seção do QR era
|
||
**omitida silenciosamente**. Duas causas tratadas:
|
||
- **Nome não batia** (espaços/maiúsculas) entre `consolidacao_motoristas.motorista_nome`
|
||
(dado externo) e `users.nome` (digitado no admin). ✅ `ConsolidacoesController#buscar_user_motorista`
|
||
agora tenta o match exato e, se falhar, compara nomes **normalizados** (sem espaços
|
||
extras/duplos, case-insensitive).
|
||
- **Motorista sem conta:** ✅ em vez de omitir, o holerite agora desenha um **aviso**
|
||
("Cadastro de acesso ainda não disponível… solicite ao administrador") no lugar do QR
|
||
(`HoleritePdf#desenhar_aviso_sem_acesso`). O QR só existe para motoristas **com cadastro**
|
||
no site — comportamento esperado, já que sem conta não há painel para acessar.
|
||
|
||
### 📌 Padrão (novo)
|
||
- **PDFs com Prawn**: usar **sempre** a fonte TTF registrada no `BasePdf` (DejaVu, UTF-8). Nunca
|
||
contar com a fonte embutida — ela quebra em qualquer caractere fora do Windows-1252.
|
||
|
||
---
|
||
|
||
## 🆕 Atualização 19/06/2026 — Apontamento manual / Nota avulsa
|
||
|
||
Permite incluir numa consolidação uma **entrega que chegou por fora** (ex.: nota que entrou
|
||
depois e não está no banco mensal das operações). O usuário digita o **número da nota (NF =
|
||
`reference_id`)**, o sistema busca os dados direto na `db_reem_simplerout_2026` (atualizada a cada
|
||
hora pela SimpleRoute) e adiciona a entrega ao fechamento do motorista.
|
||
|
||
> A tabela externa continua **read-only** — o apontamento só **lê** dela (`Entrega.por_nf`,
|
||
> sem filtro de período, restrito à conta Gade via `da_conta_gade`).
|
||
|
||
### 🆕 Funcionalidades
|
||
- **Apontamento dentro de uma consolidação existente** (wizard Passo 1): botão **"➕ Apontamento
|
||
manual"** → modal busca a NF → confere os dados (motorista/veículo/data) → escolhe **um ou
|
||
vários pilares** (Normal/Retirada/Bônus/Desconto) → adiciona.
|
||
- **"Nota avulsa"** (tela de Consolidações): botão **"➕ Nota avulsa"** → busca a NF → ao
|
||
prosseguir **cria já a consolidação** (nome `Avulsa nf<NF>`, período = data da entrega → fim do
|
||
mês, motorista vindo do `driver` da entrega) com o apontamento, e abre a tela da consolidação.
|
||
- **Múltiplas classificações** por apontamento: cria um pilar (`ConsolidacaoEntrega`) por tipo
|
||
escolhido, somando os valores (mesmo modelo multi-pilar das entregas normais).
|
||
- **Motorista automático**: vem do `driver` da entrega; se ainda não estiver na consolidação, é
|
||
incluído.
|
||
- **Gestão na tela da consolidação (show)**: seção **"➕ Apontamentos manuais"** lista NF,
|
||
motorista, pilares e valor, com botão de **remover** (a nota inteira). Na revisão (Passo 3) os
|
||
apontamentos aparecem com o selo **"➕ Apontamento"** e botão de remover (por pilar).
|
||
|
||
### ⚙️ Como funciona (técnico)
|
||
- Coluna **`manual` (boolean, default false)** em `consolidacao_entregas` distingue apontamento de
|
||
entrega elegível do período. `Consolidacao#classificadas_count` ignora os `manual: true` para
|
||
**não estourar a barra de progresso** nem o gate de finalização (`>= 100%`).
|
||
- Lógica centralizada em **`Consolidacao#adicionar_apontamento!(entrega, tipos, user)`** e
|
||
**`Consolidacao#recalcular_motorista!`** — reusadas pelos dois fluxos (wizard e avulsa). Preço por
|
||
pilar via `ConsolidacaoEntrega.valor_para(tipo)` (fonte única). Dados da entrega via
|
||
`Entrega#resumo_apontamento`.
|
||
- Rotas novas:
|
||
- `consolidacoes` (coleção): `GET buscar_nf` (JSON), `POST avulsa`.
|
||
- `consolidacao_entregas` (coleção): `GET buscar_nf`, `POST apontar`, `DELETE remover_apontamento`
|
||
(aceita `?id=` para um pilar ou `?tracking_id=&motorista=` para a nota toda).
|
||
- Stimulus: `apontamento_controller.js` (modal do wizard) e `nota_avulsa_controller.js` (modal da
|
||
index). ⚠️ Identificador do Stimulus segue o nome do arquivo em **dash-case**: `nota_avulsa_controller.js`
|
||
→ `data-controller="nota-avulsa"` / `data-action="nota-avulsa#..."` (e o elemento da ação precisa
|
||
estar **dentro** da `div` do controller).
|
||
|
||
### 🆕 Migration adicionada (rodar `db:migrate`)
|
||
```
|
||
20260619000001_add_manual_to_consolidacao_entregas.rb
|
||
```
|
||
|
||
```bash
|
||
docker-compose exec app bundle exec rails db:migrate
|
||
```
|
||
|
||
### 🧪 Testes
|
||
- `spec/models/consolidacao_spec.rb`: `classificadas_count` ignora apontamentos manuais e
|
||
`adicionar_apontamento!` (multi-tipo, idempotente, inclui o motorista e soma o valor).
|