Varejo de veículos elétricos

PDV Jaguar
PDV que emite NF-e, controla estoque por chassi e espelha o catálogo na loja WooCommerce
- Cliente
- Jaguar Mobilidade Elétrica
- Categoria
- PDV com integração fiscal e e-commerce
- Período
- abr–ago 2026
- Estado
- Em produção
O problema
A Jaguar vende autopropelidos, patinetes e bikes elétricas no balcão e no site. Cada unidade tem chassi e sai com NF-e modelo 55 no nome do comprador, e não havia sistema que amarrasse chassi a cliente nem emitisse a nota na hora da venda. O catálogo do site era mantido à parte do estoque físico: produto cadastrado duas vezes, estoque desencontrado. Recibo de sinal e termo de responsabilidade eram preenchidos no papel, e os XMLs do mês eram garimpados à mão para o contador.
O que foi construído
PDV em Flutter Web sobre Firebase cobrindo catálogo, venda, caixa, orçamento, ordem de serviço e documentos fiscais, com emissão de NF-e 55 via Focus NFe através de um backend Node/Express. O app nunca chama a API do WooCommerce: fala com Firestore, com callables e com o backend fiscal. A propagação de catálogo e estoque para a loja online roda em Cloud Functions, disparada por trigger do Firestore. A escolha por Firebase veio de uma restrição do cliente: não existe equipe de operação, e a loja online fora do ar não pode travar a venda no balcão.
Decisões de engenharia
Venda no balcão baixa o estoque do site
Finalizar a venda grava, num único batch do Firestore, a venda e o novo status das unidades vendidas por chassi. A trigger onProductUpdated recalcula o stock_quantity do WooCommerce como a contagem de unidades com status disponivel e faz upsert por SKU. O acoplamento com a loja online mora inteiramente no servidor: se o site cair, o caixa continua vendendo.
lib/data/mock_repository.dart:235-245 (batch venda + unidades) · functions-wc/index.js:151-156 e :219 (stock_quantity) · :410-426 (trigger)
Rateio de desconto e juros por item no XML da NF-e
A SEFAZ rejeita a nota quando a soma dos vDesc por item difere do desconto total. O payload distribui o desconto proporcionalmente ao valor bruto de cada item e joga o drift de centavos no último; o juros do crediário entra embutido no preço unitário para vProd fechar com vNF, e a última linha de pagamento é fechada contra o vNF. É o tipo de detalhe que só aparece depois de apanhar da validação da SEFAZ.
lib/services/nfe_service.dart:268-345 (fator de juros, rateio, drift e fechamento dos pagamentos)
Credenciais do Woo fora do app, em codebase próprio
A primeira versão chamava a REST do WooCommerce direto do Flutter, o que embarcava consumer key e secret em qualquer build web e esbarrava em CORS; virou callable mais Secret Manager, num codebase Firebase separado com deploy independente. As triggers classificam a falha: 4xx exceto 429 é erro permanente e é engolido com log, enquanto 5xx, timeout e rede levantam para o retry automático do Firebase.
firebase.json:19-31 (dois codebases) · functions-wc/index.js:12-14 (defineSecret) e :257-280 (4xx vs transitório) · git show b076f30 (versão antiga com Basic Auth no app)
Assinatura vira snapshot dentro do documento
A assinatura da empresa é configurada uma vez e copiada como snapshot para dentro de cada recibo de sinal e termo, então trocar a assinatura depois não altera documento já assinado. O PDF é gerado no navegador e hospedado no Cloudinary; se o upload falhar, a assinatura do cliente já foi gravada e o PDF é regerado depois, sem pedir nova assinatura.
lib/models/assinatura_empresa.dart:6-13 · lib/services/sinal_service.dart:62-77 (grava a assinatura mesmo com o upload falhando) e :110 (regerarPdf)
Stack
Telas







O papel da CG Mixart
Arquitetura, full-stack e design. A CG Mixart levantou o produto do zero: mapa de telas e fluxos validado com o cliente, identidade visual derivada do logo oficial com paleta própria (JaguarColors) e dark mode como padrão, o app Flutter inteiro, as duas camadas de backend, a integração WooCommerce e todo o caminho fiscal (modelagem do payload NF-e, certificado A1, credenciamento na Focus NFe e o cadastro UPD-PR que destravou a rejeição 974 da SEFAZ-PR). Desenvolvimento praticamente solo, com participação pontual de um segundo autor.
Ressalvas e notas técnicas, verificadas no códigoabrir
Cliente: Jaguar Mobilidade Elétrica LTDA, loja de veículos elétricos em São José dos Pinhais/PR, com e-commerce em jaguarmob.com.br. Os quatro serviços em produção responderam 200 na conferência de hoje (curl): o PDV, o micro-app público de termos, o backend e a loja WooCommerce. Os dois apps web saem do mesmo codebase Flutter, por entrypoints diferentes (lib/main.dart e lib/main_termo.dart), publicados em dois targets de hosting. O app está na v1.93 (lib/version.dart, bump manual a cada deploy). O .planning/NEXT.md registra a primeira NF-e autorizada em produção em 08/05/2026 (nº 3, série 1, protocolo 141260177556151); NFC-e ficou fora de escopo por decisão do cliente. Não existe no repositório métrica de volume de vendas, usuários ou uptime. Nada disso foi afirmado acima. Nota de nomenclatura: lib/data/mock_repository.dart herdou o nome do esqueleto, mas é o repositório real: abre os listeners do Firestore e escreve em batch. Não há mock nem fixture no caminho de produção do app. Duas pendências técnicas registradas com as palavras certas: 1. Regra de Firestore aberta demais. Hoje /termos/{id} é `allow read, write: if true` (create e delete inclusive) e, como `read` em Firestore cobre get e list, dá para listar as coleções /sinais e /termos inteiras e ler todos os recibos (nome, documento, valores, assinaturas). Antes de qualquer publicação: trocar `read` por `get` nas duas e restringir create/delete de /termos a request.auth != null, como já está em /sinais (firestore.rules:5-16). 2. Exportação de XMLs pro contador ainda aponta pra homologação. A callable exportarXmlsContador (functions/index.js:1041) consulta as vendas do mês na janela de Brasília (UTC-3 fixo), baixa os XMLs da Focus, zipa em memória com archiver e envia por e-mail. Só que a base da Focus está fixada em homologação (functions/index.js:1074), e é essa função que a tela chama (exportar_xmls_screen.dart:26). Como as notas reais foram emitidas em produção, o download falha hoje; virar é trocar a constante. A versão mais rígida, com intervalo de datas e descarte de notas de homologação pelo marcador /arquivos_development/, está no backend do Render (contadorController.js), um repositório separado que não está nesta pasta. Essa versão ainda não está ligada ao app. Há também código herdado que não roda para a Jaguar: o codebase functions/ nasceu do esqueleto de um PDV anterior e trouxe endpoints de Mercado Pago e gatilhos de agregados que disparam em coleções que este app não tem. Os documentos que eles escreveriam não são lidos em lugar nenhum de lib/. O total do caixa é incrementado no cliente, no mesmo batch da sangria (mock_repository.dart:227-231). A integração com a loja online foi feita do zero, em codebase separado (functions-wc). A sincronia é unidirecional (PDV para loja online); o webhook do WordPress de volta está documentado como backlog em INTEGRACAO_WOOCOMMERCE.md, seção 13 ("Limitações e backlog"). Seguem abertos e marcados como tal no código: importação de produtos por planilha e criação automática do usuário de Auth ao cadastrar vendedor (o vendedor cadastrado não consegue logar até isso ser feito à mão). Parte do trabalho de jun–ago/2026 foi deployada mas ainda não versionada no repositório: o módulo de recibo de sinal, a assinatura da empresa, a exportação de XMLs e a configuração de juros vivem só na cópia de trabalho. Vale acertar isso antes de mostrar o repo a alguém.