require 'rails_helper' RSpec.describe Analytics::OperacaoMetricas do # Linhas simuladas (mesma forma do retorno de select_all) — evita depender das # tabelas externas no teste. Stubamos #registros para focar nas agregações. let(:rows) do [ row(status: 'completed', driver: 'Carlos', sts: 'STS PERUS', gade: 'RECORRENTE', checkout: '2026-06-22 10:00:00', clat: '-23.5', clng: '-46.6'), row(status: 'completed', driver: 'Carlos', sts: 'STS PERUS', gade: 'RECORRENTE', checkout: '2026-06-22 11:00:00', lat: '-23.6', lng: '-46.7'), row(status: 'completed', driver: 'Pedro', sts: 'STS PIRITUBA', gade: 'RECORRENTE', checkout: '2026-06-23 09:00:00', clat: '-23.4', clng: '-46.5'), row(status: 'failed', driver: 'Pedro', sts: 'STS PIRITUBA', gade: 'NOVO', obs: 'ÓBITO', checkout: '2026-06-23 12:00:00'), row(status: 'failed', driver: 'Carlos', sts: 'STS PERUS', gade: 'NOVO', obs: 'ÓBITO', checkout: '2026-06-22 13:00:00'), row(status: 'pending', driver: 'Marcos', sts: 'STS PERUS', gade: 'NOVO', planned: '2026-06-24') ] end subject(:metricas) do described_class.new(tabelas: [], inicio: Date.new(2026, 6, 1), fim: Date.new(2026, 6, 30)) end before do # Isola das tabelas externas: não toca o catálogo nem db_reem_simplerout_2026. allow(Operacao).to receive(:sanitizar).and_return([]) allow(metricas).to receive(:registros).and_return(rows) end it 'conta KPIs (total/sucesso/recusas/pendentes)' do expect(metricas.total).to eq(6) expect(metricas.sucesso).to eq(3) expect(metricas.recusas).to eq(2) expect(metricas.pendentes).to eq(1) end it 'calcula insucessos %' do ins = metricas.insucessos_pct expect(ins[:completed]).to eq(3) expect(ins[:failed]).to eq(2) expect(ins[:pct_completed]).to eq(50.0) expect(ins[:pct_failed]).to eq(33.33) end it 'agrupa índices de falha por observação' do expect(metricas.indices_falha).to eq([{ observation: 'ÓBITO', total: 2, pct: 100.0 }]) end it 'agrupa por status_gade' do expect(metricas.por_status_gade).to contain_exactly( { status: 'RECORRENTE', total: 3 }, { status: 'NOVO', total: 3 } ) end it 'conta NFs por motorista (todas), desc' do expect(metricas.por_motorista.first).to eq({ nome: 'Carlos', total: 3 }) expect(metricas.por_motorista.map { |h| h[:nome] }).to eq(%w[Carlos Pedro Marcos]) end it 'conta entregas concluídas por STS (contact_name)' do expect(metricas.por_sts).to eq([ { nome: 'STS PERUS', total: 2 }, { nome: 'STS PIRITUBA', total: 1 } ]) end it 'agrupa por dia (completas x falhas)' do pd = metricas.por_dia expect(pd[:labels]).to eq(['22/06/2026', '23/06/2026']) expect(pd[:completed]).to eq([2, 1]) expect(pd[:failed]).to eq([1, 1]) end it 'monta pontos do mapa (prefere checkout, cai p/ checkin)' do expect(metricas.pontos_mapa).to eq([[-23.5, -46.6], [-23.6, -46.7], [-23.4, -46.5]]) end context 'sem registros' do before { allow(metricas).to receive(:registros).and_return([]) } it 'não divide por zero' do expect(metricas.total).to eq(0) expect(metricas.insucessos_pct[:pct_failed]).to eq(0.0) expect(metricas.indices_falha).to eq([]) expect(metricas.pontos_mapa).to eq([]) expect(metricas.centro_mapa).to eq(described_class::SP_CENTRO) end end def row(status:, driver:, sts:, gade:, obs: nil, checkout: nil, planned: nil, lat: nil, lng: nil, clat: nil, clng: nil) { 'status' => status, 'driver' => driver, 'contact_name' => sts, 'status_gade' => gade, 'observation' => obs, 'checkout' => checkout, 'planned_date' => planned, 'latitude' => lat, 'longitude' => lng, 'checkout_latitude' => clat, 'checkout_longitude' => clng } end end