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:
| Doc | Conteúdo |
|---|---|
| Visão Geral (este documento) | Arquitetura, rota, ciclo de vida do rascunho |
| Cabeçalho da Venda | Formulário de cabeçalho, defaults de layout |
| Carrinho e Itens | Adicionar/editar/remover item, sincronização de snapshot |
| Descontos | Desconto por item, total e progressivo |
| Validação Comercial | Estoque, desconto máximo, alertas de carrinho |
| Separação por Tipo de Operação | vinculoId, drafts vinculados, propagação |
| Salvar e Enviar | Persistência, envio, offline, retry automático |
| Compartilhamento e Importação | Export/import de pedido criptografado |
| Edição de Pedido Existente | Editar 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 emhooks/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
| Arquivo | Responsabilidade |
|---|---|
app/nova-venda.tsx | Rota Expo Router, sempre renderiza NewSaleScreen |
features/new-sale/new-sale.tsx | Screen: monta tabs (Cabeçalho/Produtos/Carrinho), modais e bottom sheets |
features/new-sale/new-sale.controller.ts | Estado de UI (aba ativa, bottom sheets, modo de visualização), navegação |
hooks/new-sale/useNewSaleDraftController.ts → useSaleDraftHeader.ts | Resolve saleDraftId inicial e carrega snapshot de cabeçalho/cliente |
hooks/new-sale/useSaleDraftCart.ts | Estado do carrinho para a tela |
services/sale/sale-draft/sale-draft.service.ts | Fachada de todos os serviços de rascunho |
services/sale/sale-draft/sale-draft.service.instance.ts | Instância singleton usada pela UI |
repositories/sale-draft/sale-draft.repository.ts | Persistência SQLite do cabeçalho/registro do rascunho |
repositories/sale-draft-cart/sale-draft-cart.repository.ts | Persistência SQLite dos itens do carrinho |
types/new-sale/sale-draft.type.ts | Tipos: 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.