O Embalo é um aplicativo web (PWA) para motoristas profissionais — Uber, 99, iFood, táxi e particulares — que querem ver o lucro do dia, não só o faturamento da plataforma. Reúne receitas e despesas, veículo, jornada, rotas, recibos, vouchers e relatórios. Roda no navegador do celular ou do computador, em português, no domínio embalomotorista.com.br. O código é JavaScript vanilla com Supabase (PostgreSQL, Auth e Storage) e deploy na Vercel.

Este caso técnico mostra o que o código faz, as decisões que o moldaram e as falhas reais que viraram regra. Os números foram conferidos no repositório na versão de 28/10/2026 (commit c07ce82).

Transparência: o Embalo foi projetado e é desenvolvido por Jeremias Cardoso, autor deste blog. O produto está em implementação; este texto não afirma frota paga nem métricas de usuários.

Nota de atualização (02/11/2026): este post foi reescrito como caso técnico. A versão de 01/10/2026 era uma apresentação de produto. Esta traz arquitetura, números, incidentes, limites e fontes conferidos no repositório.

O problema: o extrato da plataforma não é o lucro

O valor de uma corrida não é o que fica com o motorista. Combustível, manutenção, pedágio e o tempo parado entre chamadas saem da mesma conta, mas não aparecem no extrato da Uber ou da 99. Sem esses números lado a lado, a semana termina com o faturamento conhecido e o resultado desconhecido.

Planilha resolve enquanto cabe numa pessoa e num arquivo que não some. O problema começa quando o lançamento precisa acontecer no celular, com rede ruim, foto do comprovante, recibo para empresa e histórico que sobreviva a trocar de aparelho. É a mesma fronteira em que planilhas de controle deixam de bastar e um sistema com login e regras passa a fazer sentido — a mesma lógica que aparece no EscalaOper para escala de turno.

O sistema em números

Medição local em 02/11/2026 sobre o commit c07ce82 (último commit do repositório em 28/10/2026).

O sistema em números
MedidaValor
Aplicação (JS/CSS/HTML principais, sem tests/docs/assets)28 arquivos; cerca de 22,7 mil linhas de código (não brancas e sem comentário)
API serverless (api/)12 arquivos; cerca de 1,5 mil linhas de código
Pasta tests/232 arquivos (inclui fixtures e relatórios; não usar o total bruto de linhas como “código de teste”)
Histórico no Gitcerca de 681 commits
Domínio públicoembalomotorista.com.br

A contagem exclui node_modules, documentação, estado, mockups e assets. O arquivo central app.js sozinho passa de 500 KB no disco.

Uma conta, dois acessos

A decisão de produto é tratar aplicativo e portal como o mesmo produto: mesma conta, mesmos dados, mesmas regras. O celular não é uma versão reduzida. A stack escolhida foi deliberadamente fina no front: HTML, CSS com tokens claro/escuro e JavaScript sem framework, para caber no bolso e no 4G.

Os dados moram no Supabase. Autenticação, tabelas e fotos de comprovante passam por lá. Parte das regras de plano e de uso mensal foi migrada para funções e rotas serverless em api/, porque deixar o navegador como única fonte da verdade do plano abre buraco: dá para virar Pro pelo console do navegador. A auditoria de junho de 2026 já marcava isso como bloqueador antes de cobrar.

O Embalo não importa automaticamente os ganhos das plataformas. O motorista lança à mão, por voz ou com apoio de leitura do valor na foto do comprovante. A vantagem está em reunir, no mesmo lugar, o que cada plataforma paga e o que o carro consome.

O que o código entrega

Financeiro. Receitas, despesas e resultado líquido por período (mês, últimos 30 dias, ano). Meta diária, filtros por categoria e anexo de foto. Os totais saem dos lançamentos do próprio motorista.

Meu Veículo. Cadastro, quilometragem, manutenções com previsão da próxima troca e checklist diário. A previsão parte do último registro de cada tipo de serviço.

Jornada e produtividade. Início e fim de turno com quilometragem; ganho por hora, custo por quilômetro, horas trabalhadas e a categoria de receita que mais rendeu no mês. Gráficos dos últimos sete dias e por turno.

Rotas. Paradas ordenáveis; abertura no Google Maps ou no Waze; otimização da ordem em versão beta, que pode falhar ao geocodificar um endereço.

Recibos, clientes e vouchers. Recibos numerados em PDF, compartilháveis no WhatsApp; vouchers com situação (pendente, enviado, em análise, pago). O mesmo eixo de “artefato pronto, relação humana” aparece no ZapOrca e no post sobre custo do WhatsApp com IA.

Planos Free, Pro e Max. Limites de lançamentos, fotos, recibos, vouchers e veículos; mensagem em português quando o teto estoura; trial e assinatura por Pix no desenho atual.

Arquitetura em restrição: o que cabe no celular

O front não usa React nem Vue de propósito. A lógica de negócio mora em app.js e em módulos satélite (voz-parser.js, assistente-voz.js, business-ui.js, report-ui.js). O service worker e o manifesto fecham o PWA. A escolha reduz o payload e evita uma camada de build no caminho crítico do motorista; o preço é um arquivo monolítico grande e a disciplina de não misturar estado de sessão entre contas.

No servidor, as rotas em api/ concentram o que não pode confiar no cliente: consumir cota de comando de voz, reconciliar uso mensal e endurecer o plano. O SQL versionado em sql/ documenta a reconstituição da base e os rollbacks das migrações de plano. A auditoria de junho já separava “fase de teste grátis” (aprovável com ressalvas) de “começar a cobrar” (reprovado até o servidor ser a fonte da verdade e a exclusão de conta existir no app).

São Paulo aparece no desenho do produto porque o fuso UTC−3 e o motorista noturno são o público típico do bug de data — não como marketing de cidade. O domínio público embalomotorista.com.br é a vitrine e o manual; este caso descreve o repositório, não um laudo do que está deployado em cada momento no painel da Vercel.

Incidentes que viraram regra

1. O limite do plano que virava “Algo deu errado”

Quando o Free batia no teto, o app lançava um erro com texto claro em português (“Limite atingido… faça upgrade”). O catch passava por traduzirErro(), feito para mensagens em inglês do Supabase. Nenhum match batia, e o motorista via o fallback genérico. Quem mais precisava ver o convite de upgrade não via. A correção marcou os erros do próprio app com jaAmigavel e passou a devolver a mensagem original. Em app.js, as chaves LIMITE_PLANO:* e o ramo err.jaAmigavel ainda estão aí.

2. Duplo toque, segundo veículo, jornada fantasma

No celular, um segundo toque nos 700–1500 ms entre o sucesso e a troca de tela reenviava o formulário. Em veículo, o segundo insert quebrava a aba: carregarVeiculo() usa .maybeSingle() e a tela ficava em erro permanente. Em jornada, nascia uma jornada aberta “fantasma”. A regra que ficou: não reabilitar o botão no finally do sucesso enquanto a navegação não terminou, e setar modo edição assim que o veículo é criado.

3. Depois das 21h em São Paulo, o dia virava amanhã

Datas de calendário usavam toISOString().slice(0,10) (UTC). Em SP (UTC−3), depois das 21h isso já é o dia seguinte — exatamente quando o motorista noturno fecha o dia. Gráfico de sete dias e relatórios de jornada deslocavam. O helper dataISOLocal / dataHojeISO passou a usar o fuso do aparelho. O comentário em app.js registra o bug clássico de SP de propósito.

4. Mil linhas sem aviso

Consultas que somam dinheiro pediam “tudo” sem .limit(). O PostgREST devolve no máximo 1.000 linhas e não avisa o corte. Um motorista que lança cada corrida passa desse teto em cerca de um ano e o relatório anual mente em silêncio. O mínimo é detectar o teto e avisar; paginar de verdade ficou como dívida.

5. Copiloto de voz: offline, período e cota

Três lições conferidas no código em outubro de 2026: (a) “resiliência offline” só vale para a interpretação do texto — reconhecimento de fala e gravação no Supabase precisam de rede, e não há fila de reenvio; (b) o período da consulta por lista de exclusão aceitava “em 2025” ou “sábado” e caía no dia errado — a guarda segura é por inclusão do que o app sabe calcular; (c) cota de comandos no localStorage não é fonte da verdade — o custo da IA e a cota passam pelo servidor.

6. “Concluída” e “100% dos testes” sem prova

A tarefa do servidor como fonte da verdade dos planos chegou marcada concluída sem cumprir o critério. “100% dos testes” valia só para uma bateria do copiloto; a linha de base da bateria inteira deu 18 PASS e 21 FAIL já existentes. A regra que ficou na memória do projeto: alegação sem evidência apontável é hipótese.

7. Keep-alive que consultava coluna inexistente

O robô que aquecia o Supabase a cada três dias consultava configuracoes?select=id, mas a chave da tabela é chave, e o papel anon não lê essa tabela. A resposta 4xx era tratada como sucesso. A consulta foi trocada; atividade no painel não se prova com status HTTP enganoso.

Testes: a máquina importa

Na conferência R19 (28/10/2026), o run-all --fechamento na máquina do dono com Edge deu 43/44 (1.060 casos PASS; um FAIL por TEMPO_CASO / Edge que não fechou a tempo; o mesmo caso passou no reteste 16/16). Na nuvem, a mesma rodada deu 38/44: a nuvem não tem Edge e troca por Chromium com calço. A memória do projeto registra: bateria na nuvem não é a bateria da máquina para tela e zoom.

Isso não prova que o Embalo está “pronto para cobrar”. Prova que a prova de interface depende do ambiente, e que timeout de navegador não vira defeito de produto sem repetir o caso.

O que ainda falta / o que este caso não prova

  • Adoção. O repositório e este texto não afirmam quantos motoristas usam o produto em produção. Recrutar testadores e o acesso à loja aparecem como tarefas bloqueadas no estado do projeto, não como fatos concluídos.
  • Cobrança plena. Antes de cobrar de fato, a auditoria e as tarefas ainda tratam servidor como fonte da verdade dos planos, exclusão de conta pelo app (LGPD) e revisão jurídica da política e dos termos como pendências de go-live. A fase de teste grátis e a fase de cobrar são vereditos diferentes no relatório de junho.
  • Import das plataformas. Continua manual. Não há conector Uber/99/iFood no código desta versão.
  • Offline real. Não há fila de lançamentos para quando a rede volta. O copiloto interpreta texto localmente; gravar e ouvir dependem de internet.
  • Paginação dos totais. O teto de 1.000 linhas do PostgREST continua sendo risco estrutural se o uso crescer sem .range() ou soma no banco.
  • Segredos e painéis. Detalhes de chaves, políticas RLS completas e configuração de SMTP ficam de fora deste texto de propósito.
  • Bibliotecas de PDF e planilha. A memória registra versões e avisos conhecidos (jsPDF, SheetJS); este caso não relê a cadeia de supply de cada dependência de front.

Lições que valem fora do app de motorista

  1. Mensagem amigável que o catch engole não existe. Marque o erro do próprio app ou o usuário vê genérico.
  2. Fuso é requisito de produto para quem trabalha à noite. toISOString em data de calendário mente depois das 21h em SP.
  3. Teto silencioso de API é mentira contábil. Se a soma pode cortar, o corte precisa aparecer.
  4. “Offline” e “100% dos testes” pedem definição escrita. Sem ela, viram marketing interno.
  5. Prova de tela na nuvem ≠ prova na máquina do usuário. Ambiente faz parte da evidência.

Fontes

  • Código, documentação, auditoria pré-produção, memória do projeto e histórico: repositório motorista-pro (Embalo) no commit c07ce82 (28/10/2026), lido pelo autor; extrato com trechos, método da contagem e limites, verificado em 2026-11-02. A suíte completa não foi reexecutada nesta redação; os resultados de bateria citados vêm do relatório R19 de 28/10/2026 versionado no repositório.
  • Manual público do produto em embalomotorista.com.br (apresentação e capturas de demonstração), conferido na versão de lançamento do post original.