36 KiB
🚛 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'ANDcheckout 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.
🔧 Correções + funcionalidades (16/06/2026)
🔴 Erros de runtime corrigidos (reportados em prints)
- Logout dava
No route matches [GET] /auth/logout— o link usavamethod: :delete(rails-ujs), ignorado pelo Turbo. Agora usadata-turbo-method. - /admin/usuarios e /admin/configuracoes estouravam
NoMethodError: admin_ou_gerente?— helpers de papel (admin?,admin_ou_gerente?) movidos para aApplicationPolicy. - Listagem de usuários chamava o helper inexistente
badge_role— criado emApplicationHelper. - Nova consolidação estourava
NoMethodError: new?—ApplicationPolicyagora definenew? = create?eedit? = update?. - Login do motorista (PIN) derrubava
/motoristacomPG::UndefinedTable: relation "consolidacao_motorista" does not exist— o inflector pt-BR resolvia o composto no singular.self.table_namefixado explicitamente emConsolidacao,ConsolidacaoMotorista,ConsolidacaoEntregaeConfiguracao. - Listagem de usuários faltava o helper
badge_status_usuarioe a rotatoggle_ativoestava sem o prefixoadmin_(verbo errado também) — corrigidos. - Passo 3 (revisão) da consolidação ficava em branco:
GROUP BY tipocombinado comORDER BY created_até inválido no PostgreSQL (PG::GroupingError, vira 500/tela branca em produção). Corrigido comreorder(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.pagasagora éstatus='completed' AND checkout IS NOT NULL(antes eracheckin). 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 trocaconsolidacoes.route_idsporvehicle_ids. - Filtro de conta configurável:
account_idna tabela externa é texto e vinha vazio nos registros recentes (o dado de junho estava 100% em conta vazia, e o filtro fixo em95907zerava o dashboard).DB_EXISTING_ACCOUNT_IDagora aceita lista de contas ouall/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 rodarails db:prepareantes do servidor — não precisa mais rodardb:migratena mão após oup.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).
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. Use95907,(vírgula no fim) para incluir também registros com conta vazia/NULL. O scopeEntrega.da_conta_gadeagora incluiNULLquando 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-cachena página e o gráfico Chart.js virou idempotente (Chart.getChart(ctx)?.destroy()). - 500 ao criar motorista:
users.emaileraNOT NULLcom índice único e default""— o 2º motorista sem e-mail colidia em""(RecordNotUnique). Agora a coluna permiteNULLe e-mail em branco viranil(User#normalizar_email_em_branco); no Postgres váriosNULLconvivem no índice único. - 500 ao gerar relatório PDF (
gerar_pdf_relatorio):@entregas.group(:tipo).countcom.order(:created_at)geravaGROUP BY ... ORDER BY created_at→PG::GroupingError. Corrigido comreorder(nil). Também removidas as opçõescolor:passadas aoPrawn#text(não suportadas — uso defill_color). - Motorista não baixava o próprio holerite ("sem permissão"):
ConsolidacaoPolicy#show?exigepode_consolidar?(falso para motorista). Novoautorizar_holerite!noConsolidacoesController— 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). Novourl_acessorespeita o esquema doAPP_HOST(sem esquema = http). DefinaAPP_HOSTcom 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_massaaceitaacao: 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.emailpassa a permitirNULL; e-mails""existentes viramNULL.
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)
# 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
🆕 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: colunaspago_em,pago_por,forma_pagamentoemconsolidacao_motoristas(pagamento é por motorista). ConsolidacaoMotorista:pago?,marcar_pago!(user, forma:),cancelar_pagamento!, scopespagos/pendentes,belongs_to :pagador.Consolidacao#status_pagamento(:pendente/:parcial/:pago),valor_pago,valor_pendente, scopespagamento_pago/pagamento_pendente/pagamento_parcial(filtro da lista).- Tela show: marcar motorista individual ou a operação inteira (com
<select>de forma); estorno. Açõesregistrar_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-mailConsolidacaoMailer#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 viainformation_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) emconsolidacoes;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(defaultgade_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ícioe< 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_pagese faltar espaço — o Prawn não pagina imagem sozinho).
🎨 UI / Identidade visual
- Fonte base maior para leitura:
html { font-size: 17px }(desktop) e18px(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]), commix-blend-mode: screenpara "derrubar" o fundo preto do vídeo. rack-mini-profilerdesativado emconfig/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(verOperacao). - 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) ebadge_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:BasePdfagora 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.ttfeDejaVuSans-Bold.ttf(a imagem Dockerruby:3.2.2-slimnão traz fontes do sistema; oCOPY . .do Dockerfile e o volume.:/appgarantem que estejam disponíveis em runtime).
- Fontes versionadas em
- 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_downera frágil e, com a fonte nova, fazia o QR se sobrepor ao texto/assinaturas. ✅ Corrigido:desenhar_qrcodeagora 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.
📌 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.