require 'rails_helper' RSpec.describe 'Dashboard', type: :request do it 'redireciona usuário não autenticado para o login' do get dashboard_path expect(response).to redirect_to(new_user_session_path) end it 'redireciona motorista para o painel exclusivo de motorista' do sign_in create(:motorista) get dashboard_path expect(response).to redirect_to(motorista_dashboard_path) end end # Trava a CONTABILIZAÇÃO DAS FALHAS no dashboard principal. # # A base financeira do dashboard é `atendidas` (concluídas + falhadas, com # checkout) — o MESMO conjunto que a consolidação considera elegível e paga. # Antes usava só `pagas` (concluídas) e o valor da tela ficava menor que o do # fechamento; e as falhas eram filtradas por `planned_date`, caindo no período # errado. Os dois casos estão fixados aqui. # # ⚠️ Falhada TEM checkout: o motorista foi ao local e fechou a visita com motivo # de insucesso. É o que torna `checkout` o eixo correto também para elas. RSpec.describe 'Dashboard — contabilização de falhas', type: :request do include_context 'espelho de rastreio' # Preço redondo para os valores esperados serem óbvios na leitura do spec. let(:preco) { 10.0 } let(:inicio) { '2026-08-01' } let(:fim) { '2026-08-14' } before do create(:configuracao, chave: 'preco_entrega', valor: preco.to_s) sign_in create(:gerente) end def abrir_dashboard get dashboard_path, params: { inicio: inicio, fim: fim } expect(response).to have_http_status(:ok) response.body end it 'soma a falhada ao valor estimado e ao total de atendidas' do semear([ { driver: 'JOSE DA SILVA', vehicle: 'GADE_001', status: 'completed', planned_date: Time.zone.local(2026, 8, 5, 8, 0), checkout: Time.zone.local(2026, 8, 5, 10, 0) }, { driver: 'JOSE DA SILVA', vehicle: 'GADE_001', status: 'failed', planned_date: Time.zone.local(2026, 8, 6, 8, 0), checkout: Time.zone.local(2026, 8, 6, 11, 0) } ]) corpo = abrir_dashboard # 2 atendidas × R$ 10,00 — com a base antiga (`pagas`) seria 1 e R$ 10,00. expect(corpo).to include('2 entregas atendidas') expect(corpo).to include('1 concluídas · 1 falhadas') expect(corpo).to include('R$ 20,00') end it 'conta a falhada pela data do CHECKOUT, não pela planejada' do # Planejada em 31/07 (fora), entregue em 01/08 (dentro) → entra. semear([ { driver: 'JOSE DA SILVA', vehicle: 'GADE_002', status: 'failed', planned_date: Time.zone.local(2026, 7, 31, 8, 0), checkout: Time.zone.local(2026, 8, 1, 9, 0) } ]) corpo = abrir_dashboard expect(corpo).to include('1 entregas atendidas') expect(corpo).to include('0 concluídas · 1 falhadas') end it 'exclui a falhada planejada dentro do período mas com checkout fora' do # O espelho do caso acima: planejada em 10/08 (dentro), entregue em 01/09 # (fora) → não entra. Com o eixo antigo (planned_date) entrava. semear([ { driver: 'JOSE DA SILVA', vehicle: 'GADE_003', status: 'failed', planned_date: Time.zone.local(2026, 8, 10, 8, 0), checkout: Time.zone.local(2026, 9, 1, 9, 0) } ]) corpo = abrir_dashboard expect(corpo).to include('0 entregas atendidas') expect(corpo).to include('0 concluídas · 0 falhadas') end it 'mantém a pendente fora do valor, contando-a só como pendente' do # Pendente não tem checkout: continua no eixo planned_date e não vira R$. semear([ { driver: 'JOSE DA SILVA', vehicle: 'GADE_004', status: 'pending', planned_date: Time.zone.local(2026, 8, 7, 8, 0), checkout: nil } ]) corpo = abrir_dashboard expect(corpo).to include('0 entregas atendidas') expect(corpo).to include('1 pendentes') end it 'inclui a falhada no ranking por motorista' do semear([ # JOSE: 1 concluída + 1 falhada = 2 atendidas → R$ 20,00 { driver: 'JOSE DA SILVA', vehicle: 'GADE_005', status: 'completed', planned_date: Time.zone.local(2026, 8, 4, 8, 0), checkout: Time.zone.local(2026, 8, 4, 10, 0) }, { driver: 'JOSE DA SILVA', vehicle: 'GADE_005', status: 'failed', planned_date: Time.zone.local(2026, 8, 4, 8, 0), checkout: Time.zone.local(2026, 8, 4, 15, 0) }, # MARIA: 1 concluída = 1 atendida → R$ 10,00 { driver: 'MARIA SOUZA', vehicle: 'GADE_006', status: 'completed', planned_date: Time.zone.local(2026, 8, 5, 8, 0), checkout: Time.zone.local(2026, 8, 5, 10, 0) } ]) corpo = abrir_dashboard expect(corpo).to include('3 entregas atendidas') expect(corpo).to include('JOSE DA SILVA') # R$ 20,00 do JOSE só existe se a falhada dele entrou na conta do ranking # (com a base antiga ele apareceria com R$ 10,00, igual à MARIA). expect(corpo).to include('R$ 20,00') expect(corpo).to include('R$ 30,00') # total estimado do período end end # Trava a ABA "CONSOLIDADO" do card de motoristas. # # O card tem duas abas sobre o mesmo ranking e elas mostram números diferentes de # propósito: "Estimado" = atendidas × preço; "Consolidado" = o que realmente foi # fechado (com bônus/desconto/retirada) + a quantidade de entregas do fechamento. # Confundir as duas é justamente o que este spec impede. RSpec.describe 'Dashboard — aba Consolidado do ranking de motoristas', type: :request do include_context 'espelho de rastreio' let(:inicio) { '2026-08-01' } let(:fim) { '2026-08-14' } before do create(:configuracao, chave: 'preco_entrega', valor: '10.0') sign_in create(:gerente) end # Só o conteúdo da aba consolidada: a aba estimada renderiza o MESMO markup # (moeda + "N entregas"), então asserção no body inteiro passaria por acidente. def painel_consolidado get dashboard_path, params: { inicio: inicio, fim: fim } expect(response).to have_http_status(:ok) Nokogiri::HTML(response.body).at_css('#ranking-painel-consolidado').text end # cria as entregas ANTES do motorista: ConsolidacaoMotorista#recalcular_valor # (before_save) soma as consolidacao_entregas do mesmo nome — sem elas o # valor_total nasceria zerado. def consolidar!(nome, linhas, consolidacao) linhas.each { |l| create(:consolidacao_entrega, { consolidacao: consolidacao, motorista_nome: nome }.merge(l)) } create(:consolidacao_motorista, consolidacao: consolidacao, motorista_nome: nome) end let(:consolidacao) do create(:consolidacao, status: :finalizada, data_inicio: Date.new(2026, 8, 1), data_fim: Date.new(2026, 8, 14)) end it 'mostra o valor fechado e a quantidade exata de entregas de cada motorista' do # JOSE: 2 entregas (TRK-A e TRK-B). TRK-A tem DOIS pilares (normal + bônus), # então são 3 linhas para 2 entregas — é o caso que quebra se alguém contar # linhas em vez de tracking_id distinto. consolidar!('JOSE DA SILVA', [ { tracking_id: 'TRK-A', tipo: :entrega_normal, valor_aplicado: 10.0 }, { tracking_id: 'TRK-A', tipo: :bonus, valor_aplicado: 5.0 }, { tracking_id: 'TRK-B', tipo: :entrega_normal, valor_aplicado: 10.0 } ], consolidacao) consolidar!('MARIA SOUZA', [ { tracking_id: 'TRK-C', tipo: :entrega_normal, valor_aplicado: 10.0 } ], consolidacao) texto = painel_consolidado expect(texto).to include('JOSE DA SILVA') expect(texto).to include('R$ 25,00') # 10 + 5 + 10, valor REAL do fechamento expect(texto).to include('2 entregas') # e NÃO 3: TRK-A conta uma vez só expect(texto).to include('MARIA SOUZA') expect(texto).to include('R$ 10,00') expect(texto).to include('1 entregas') end it 'subtrai o desconto do valor fechado' do consolidar!('JOSE DA SILVA', [ { tracking_id: 'TRK-A', tipo: :entrega_normal, valor_aplicado: 10.0 }, { tracking_id: 'TRK-A', tipo: :desconto, valor_aplicado: 4.0 } ], consolidacao) expect(painel_consolidado).to include('R$ 6,00') end it 'não lista motorista arquivado, que saiu do fechamento' do consolidar!('JOSE DA SILVA', [ { tracking_id: 'TRK-A', tipo: :entrega_normal, valor_aplicado: 10.0 } ], consolidacao) cm = consolidar!('MARIA SOUZA', [ { tracking_id: 'TRK-C', tipo: :entrega_normal, valor_aplicado: 10.0 } ], consolidacao) cm.arquivar!(nil) texto = painel_consolidado expect(texto).to include('JOSE DA SILVA') expect(texto).not_to include('MARIA SOUZA') end it 'ignora consolidação em rascunho — só entra o que foi finalizado' do rascunho = create(:consolidacao, status: :rascunho, data_inicio: Date.new(2026, 8, 1), data_fim: Date.new(2026, 8, 14)) consolidar!('JOSE DA SILVA', [ { tracking_id: 'TRK-A', tipo: :entrega_normal, valor_aplicado: 10.0 } ], rascunho) expect(painel_consolidado).to include('Nenhuma consolidação finalizada neste período') end end