Go to file
2026-06-18 20:21:58 -03:00
2026-06-18 20:21:58 -03:00
2026-06-18 20:03:14 -03:00
2026-06-10 23:37:41 -03:00
2026-06-17 19:23:50 -03:00
2026-06-10 20:12:20 -03:00
2026-06-18 20:21:58 -03:00
2026-06-10 20:12:20 -03:00

🚛 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

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 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 inexistentesEntrega.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:

# 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::UsuariosControllertoggle_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 zero20260610_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 GadeDashboardController 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:

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).
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_atPG::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_motoristasusers.email passa a permitir NULL; e-mails "" existentes viram NULL.
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.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/600text-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.

📌 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.
Description
No description provided
Readme 27 MiB
Languages
HTML 89.3%
Ruby 9.5%
JavaScript 1.1%
Shell 0.1%