# 🚛 Gade 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 `checkin 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. --- ## 🗄️ 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) ```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