Pular para o conteúdo principal

Nova Venda / Edição de Venda — Visão Geral

Última atualização: 14 de julho de 2026

O que é este fluxo

É o fluxo em que o vendedor monta um pedido de venda em campo, normalmente sem internet estável. Cobre: cabeçalho da venda (cliente, empresa, condição de pagamento, tipo de operação), inclusão de produtos no carrinho, descontos, validação comercial (estoque/desconto), separação do pedido por tipo de operação, salvar/enviar (com suporte offline) e compartilhamento/importação de pedidos entre dispositivos.

Esta seção documenta esse fluxo em 9 partes, uma por sub-fluxo:

DocConteúdo
Visão Geral (este documento)Arquitetura, rota, ciclo de vida do rascunho
Cabeçalho da VendaFormulário de cabeçalho, defaults de layout
Carrinho e ItensAdicionar/editar/remover item, sincronização de snapshot
DescontosDesconto por item, total e progressivo
Validação ComercialEstoque, desconto máximo, alertas de carrinho
Separação por Tipo de OperaçãovinculoId, drafts vinculados, propagação
Salvar e EnviarPersistência, envio, offline, retry automático
Compartilhamento e ImportaçãoExport/import de pedido criptografado
Edição de Pedido ExistenteEditar rascunho salvo ou pedido remoto

O rascunho de venda é a fonte de verdade

Toda venda em andamento, seja nova ou em edição, existe como um rascunho de venda (SaleDraft), persistido localmente em SQLite com um snapshot em storage local. Enquanto o vendedor navega entre as abas Cabeçalho, Produtos e Carrinho, nenhum dado fica só em estado de componente React: cabeçalho, itens do carrinho e totais são lidos e escritos no rascunho a cada alteração. Isso é o que permite que o app funcione sem internet e sobreviva a fechar o app no meio de um pedido.

A skill do projeto, em .agents/skills/sales-flow/SKILL.md, documenta uma regra crítica aqui: nunca inventar estado paralelo para dados críticos do rascunho. Qualquer alteração precisa sempre passar por SaleDraftService e pelos repositórios.

Arquitetura em camadas

SaleDraftService (services/sale/sale-draft/sale-draft.service.ts) não implementa regra de negócio diretamente: ele apenas monta e expõe os serviços especializados de header, cart, pricing, split, validation, persistence, transfer e edit como uma fachada única. Cada serviço especializado recebe suas dependências via injeção manual no construtor do SaleDraftService, por exemplo:

this.cartCore = new SaleDraftCartCoreService({
draftRepo: this.draftRepo,
cartRepo: this.cartRepo,
getHeaderRelatedData: (input) => this.header.getHeaderRelatedData(input),
calculatePriceByProduct: (params) =>
this.pricing.calculatePriceByProduct(params),
propagateItemUpsert: (id, item) =>
this.split.propagateItemUpsertToLinkedDrafts(id, item),
propagateItemRemoval: (id, params) =>
this.split.propagateItemRemovalToLinkedDrafts(id, params),
});

Por isso, ao ler qualquer sub-serviço isoladamente, é normal ver chamadas para callbacks injetados em vez de imports diretos de outro serviço. É assim que o projeto evita dependência circular entre os módulos de sale-draft-*.

Na camada de UI, o padrão observado, e normativo para novo código segundo a skill do projeto, é o seguinte:

  • Screen monta só UI (features/new-sale/new-sale.tsx).
  • Controller concentra estado de UI, handlers e navegação (new-sale.controller.ts, new-sale-cart.controller.ts, hooks em hooks/new-sale/*).
  • Service / Repository concentram persistência e regra de negócio (services/sale/sale-draft/*, repositories/sale-draft*).

Rota única para criar e editar

Não existem duas telas separadas para "nova venda" e "editar venda". A mesma rota renderiza o mesmo componente nos dois casos:

// app/nova-venda.tsx → renderiza features/new-sale/new-sale.tsx (NewSaleScreen)

A tela lê os parâmetros de rota:

const params = useLocalSearchParams<{
saleDraftId?: string | string[];
returnTo?: string | string[];
}>();

Na nova venda, a navegação vai para /nova-venda sem saleDraftId, e um novo rascunho é criado ou retomado por startOrResumeDraft. Na edição, a navegação vai para /nova-venda com o saleDraftId do rascunho a editar, o que dispara a restauração do rascunho:

useEffect(() => {
if (!initialSaleDraftId) return;
if (hasInitialHeaderSnapshot || hasInitialClientSnapshot) return;
void saleDraftService.prepareDraftForEditing(initialSaleDraftId);
}, [hasInitialClientSnapshot, hasInitialHeaderSnapshot, initialSaleDraftId]);

Editar um pedido remoto, ou seja, um OrderHeader já existente no servidor e não um rascunho local, passa antes por uma etapa de adaptação, detalhada em Edição de Pedido Existente.

Ciclo de vida do rascunho

Arquivos-chave

ArquivoResponsabilidade
app/nova-venda.tsxRota Expo Router, sempre renderiza NewSaleScreen
features/new-sale/new-sale.tsxScreen: monta tabs (Cabeçalho/Produtos/Carrinho), modais e bottom sheets
features/new-sale/new-sale.controller.tsEstado de UI (aba ativa, bottom sheets, modo de visualização), navegação
hooks/new-sale/useNewSaleDraftController.tsuseSaleDraftHeader.tsResolve saleDraftId inicial e carrega snapshot de cabeçalho/cliente
hooks/new-sale/useSaleDraftCart.tsEstado do carrinho para a tela
services/sale/sale-draft/sale-draft.service.tsFachada de todos os serviços de rascunho
services/sale/sale-draft/sale-draft.service.instance.tsInstância singleton usada pela UI
repositories/sale-draft/sale-draft.repository.tsPersistência SQLite do cabeçalho/registro do rascunho
repositories/sale-draft-cart/sale-draft-cart.repository.tsPersistência SQLite dos itens do carrinho
types/new-sale/sale-draft.type.tsTipos: SaleDraftRecord, SaleDraftItem, SaleDraftTotals, etc.

Tipos centrais

De types/new-sale/sale-draft.type.ts:

export type SaleDraftRecord = {
saleDraftId: string;
status: SaleDraftStatus; // "Rascunho"
client: Client;
clientIdExterno: string;
clientName: string;
companyIdExterno: string;
companyName: string;
paymentIdExterno: string;
paymentName: string;
observacao: string;
log: string;
items: SaleDraftItem[];
itemsCount: number;
totalValue: number;
headerData: SaleDraftHeaderData;
vinculoId?: string | null;
/** Preenchido apenas em edição de venda online. ID remoto do OrderHeader de origem. */
orderHeaderId?: string | null;
/** Preenchido apenas em edição de venda online. idExterno do OrderHeader de origem. */
idExterno?: string | null;
organizationId: string;
createdAt: string;
updatedAt: string;
};

vinculoId é o campo que liga múltiplos rascunhos gerados por separação por tipo de operação, detalhada em Separação por Tipo de Operação.

Regras críticas do fluxo (não violar)

  • Nunca apagar rascunho/pedido sem confirmação explícita do usuário e sem regra clara.
  • Nunca perder cliente, cabeçalho ou itens ao recalcular preço.
  • Nunca sobrescrever o contexto de organização ao operar sobre uma venda.
  • Nunca quebrar a propagação entre rascunhos vinculados por tipo de operação.
  • Toda alteração de item deve terminar em sincronização do snapshot do carrinho, como descrito em Carrinho e Itens. Pular esse passo quebra totais, badges e validações.