O inflector pt-BR (inflections.rb) resolvia o composto ConsolidacaoMotorista para o singular "consolidacao_motorista", tabela que não existe (migration cria "consolidacao_motoristas"). Login do motorista por PIN derrubava /motorista com PG::UndefinedTable. Fixa self.table_name explicitamente nos models pt-BR (Consolidacao, ConsolidacaoMotorista, ConsolidacaoEntrega, Configuracao), eliminando a dependência do inflector. Spec trava os nomes de tabela. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🚛 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,DELETEou 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
git clone https://git.xenserver.com.br/Cludio-code/logistica-controle-custos.git
cd logistica-controle-custos
2. Configure o .env
cp .env.example .env
nano .env # preencha DB_HOST, DB_PASSWORD, etc.
3. Gere o SECRET_KEY_BASE
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
docker-compose up -d
5. Banco + seeds
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
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
ativoque já existia → erro de coluna duplicada - Criava
pin_acessoduplicando opin_codeexistente - ✅ 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_codeem 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 paraapp/views/admin/+ paths atualizados (admin_usuarios_pathetc.)
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_mese métodomapa_de_precosrestaurados nos models, junto comtipo_label/iconeusados 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::AuditoriaLogsControllertinha rota mas não existia → ✅ criado com view de listagem e filtrostoggle_temaetoggle_ativoexistiam nos controllers mas sem rota → ✅ rotas adicionadas- Controller de configurações usava campo
tipoque não existe (schema usachave) → ✅ corrigido
🟡 Duplicações removidas (arquivos órfãos)
app/controllers/motorista_controller.rb+app/views/motorista/index.html.erb— a rota/motoristausaMotorista::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'ANDcheckin 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:
- WhatsApp: preencha
TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKENeTWILIO_WHATSAPP_FROMno.enve mude a configuraçãonotificacao_whatsappparatrueno sistema. Motorista precisa tertelefonecadastrado (formato+5511999998888). - Email: preencha as variáveis
SMTP_*no.enve mudenotificacao_emailparatrue. Motorista precisa teremail. - 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
- Login admin → criar consolidação (nome + período + motoristas)
- Wizard Passo 1 → escolher motorista → Passo 2: classificar entregas com os toggles
- Usar "Marcar todos como Normal" → Passo 3: revisar → próximo motorista
- Com tudo classificado → Finalizar (motoristas notificados)
- Na tela da consolidação → "Ver antes de gerar" → Confirmar → baixar Holerite PDF
- Logout →
/motorista/login→ entrar com PIN do motorista - Conferir cards de valores → baixar próprio holerite → sair
▶️ Próximos passos (deploy)
O projeto está completo. Para colocar em produção:
# 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_massaaceitavamtracking_id/motoristaarbitrá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:
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_2026stubamEntrega— o banco de teste não contém essa tabela externa.
🗄️ 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 paga |
checkin |
NULL = motorista não registrou chegada |
route_id |
UUID da operação (UBS SUL, EMAD, etc.) |
contact_name |
Nome do local (UBS SACOMA, EMAD VELEIROS…) |
Regra de pagamento: status = 'completed' AND checkin 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)
# 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.keyou qualquer arquivo com senha - Repositório PRIVADO em git.xenserver.com.br
- Use sempre
ENV['VARIAVEL']no código Ruby