Pular para conteúdo

Formação de Preço

Formação de Preço é o motor de pricing dinâmico da Gubee. Em vez de o seller (ou o ERP) enviar um preço fixo por SKU, ele define uma regra que recalcula o preço final a partir do custo unitário, comissão do marketplace, frete e demais custos variáveis. O motor executa a regra em um job assíncrono e atualiza os preços em todos os anúncios elegíveis.

Esta página explica o modelo para que parceiros e integradores entendam por que veem alterações de preço que não dispararam — e saibam quando usar o motor em vez de um simples PUT /prices/bysku.

Por que isto existe?

Sellers com milhares de SKUs em múltiplos marketplaces precisam ajustar preços quando custos mudam. Atualizar SKU por SKU é inviável. A Formação de Preço permite recalcular tudo em lote a partir de um único insumo: o custo unitário.

Por que isto importa para integradores

Mesmo que você não vá usar a Formação de Preço no seu ERP, ela afeta o que você observa:

  • Você faz PUT /integration/prices/bysku com value: 199.90.
  • O motor tem uma regra ACTIVE cujo resultado é 209.90.
  • Após alguns segundos, GET /integration/prices/bysku?sku=X retorna 209.90, não 199.90. Não é bug — a regra sobrescreveu seu preço.

Se você precisa de preço "exato" para reconciliação, leia sempre via GET /integration/prices/... após o lag de consistência (~30s), nunca confie no valor que acabou de escrever. Veja CQRS e consistência eventual.

Conceitos-chave

Rule (Regra)

Uma PriceFormationRule é a unidade de configuração. Tem:

  • Domínio (domain): PRODUCT (todos os SKUs de um produto) ou AD (anúncio específico).
  • Base (baseValue): COST (custo unitário do SKU) ou SALE_PRICE (preço de venda atual).
  • Elegibilidade (eligibility): filtros que determinam quais SKUs/anúncios a regra atinge (por categoria, marca, plataforma, etc.).
  • Estrutura de custos (costStructure): ver seção abaixo.
  • Objetivo (objective): CONTRIBUTION_MARGIN ou MARKUP.
  • Status (status): ciclo de vida (ver abaixo).
  • Datas (startDate, endDate): janela de vigência.

Status lifecycle

INACTIVE  ──(startDate)──>  SCHEDULED  ──(clock tick)──>  ACTIVE  ──(endDate)──>  FINISHED
   ^                                                                                     |
   |                                                                                     |
   +----------------------------------(manual)-------------------------------------------+
Status Significado
INACTIVE Regra criada mas não ativa. Pode ser editada.
SCHEDULED Atingiu startDate mas ainda não foi aplicada (job pendente).
ACTIVE Aplicada; preços refletindo a regra.
FINISHED Atingiu endDate ou foi finalizada manualmente.

restorePricesOnAutoFinish

Ao finalizar a regra (auto ou manual), o campo restorePricesOnAutoFinish controla se os preços voltam ao valor pré-regra (true) ou ficam no último valor calculado (false). Padrão em UI admin: true.

CostStructure (Estrutura de Custos)

A estrutura de custos tem duas camadas:

1. Channel costs (custos de canal)

Custos específicos do marketplace onde o anúncio está publicado:

Campo Descrição
commission Comissão percentual do marketplace (ex.: 0.16 = 16%).
freight Frete pago pelo canal.
tax Tributos sobre a venda no canal (ex.: ICMS, ISS).

2. Product costs (custos de produto)

Custos associados ao SKU, independentes do canal:

Campo Descrição
inboundFreight Frete de entrada (do fornecedor até o CD).
packaging Custo de embalagem.
royalty Royalties (para marcas franqueadas, licenciadas).

O toggle embedProductCosts (booleano) controla se os custos de produto são incluídos no cálculo (true) ou ignorados (false, default histórico).

Objective (objetivo)

O objetivo define o que a regra busca. Há dois drivers suportados:

Driver Fórmula Quando usar
CONTRIBUTION_MARGIN preço = (custo + margem_desejada) / (1 - comissão%) Recomendado: lucro por unidade após custos variáveis.
MARKUP preço = custo × markup Quando você pensa em multiplicador sobre custo.

Não há driver PROFIT

Um driver PROFIT (lucro líquido final) foi intencionalmente removido do modelo. Motivo: lucro real exige conhecer custos fixos da empresa (aluguel, salários, marketing), que estão fora do escopo da Gubee. CONTRIBUTION_MARGIN é o substituto correto — mede lucro por unidade após custos variáveis (comissão, frete, imposto).

Se você vê código-fonte antigo referenciando PROFIT, é legacy morto.

Execution Job

A aplicação de uma regra não é síncrona. Ao chamar POST /rules/{id}/apply, o BFF agenda um RuleExecutionJob que percorre todos os SKUs elegíveis e recalcula cada preço. Você recebe um jobId para acompanhar progresso.

curl -X POST "$GUBEE_API/bffweb/price-formation/rules/RULE-123/apply" \
  -H "Authorization: Bearer $JWT"
{
  "jobId": "JOB-abc-456",
  "ruleId": "RULE-123",
  "status": "PENDING"
}

Acompanhe o progresso:

curl -X GET "$GUBEE_API/bffweb/price-formation/rules/RULE-123/jobs/JOB-abc-456" \
  -H "Authorization: Bearer $JWT"
{
  "jobId": "JOB-abc-456",
  "status": "RUNNING",
  "progressPct": 42,
  "processedSkus": 1250,
  "totalSkus": 2976,
  "errors": []
}

Matriz de decisão: quando usar Formação de Preço

Cenário Use
Ajustar preço de 1 SKU para um valor específico PUT /integration/prices/bysku
Sincronizar preços do ERP para todos os SKUs (preço = preço ERP) PUT /integration/prices/bysku (loop)
Promoção temporária com janela de validade PROMOTION via /integration/prices/list/bysku
Recalcular preços quando custo unitário muda (margem alvo) Formação de Preço
Diferenciar preço por marketplace (mesma margem, custos diferentes) Formação de Preço (calcula por canal)
Ajustar preços sazonalmente (Black Friday, festas) com restauração Formação de Preço com endDate e restorePricesOnAutoFinish: true
Campanha complexa com cupom, desconto fixo, bundle Promoção nativa do marketplace (não Gubee)

Regra de ouro

Use Formação de Preço quando o preço é função de custo + margem. Use PUT /prices/bysku quando o preço é entrada (vem do ERP pronto). Misturar os dois leva a resultados imprevisíveis (cada write sobrescreve o outro).

Fluxo de execução

  1. Criação: seller define a regra no painel admin (UI Angular) ou via POST /bffweb/price-formation/rules. Status inicial: INACTIVE.
  2. Agendamento: ao atingir startDate ou ao chamar /apply, status vira SCHEDULED e o RuleExecutionJob é enfileirado.
  3. Execução: o job percorre SKUs elegíveis. Para cada um:
  4. Lê custo unitário (costStructure.cost).
  5. Lê custos de canal por marketplace (commission, freight, tax).
  6. Aplica a fórmula do objective (CONTRIBUTION_MARGIN ou MARKUP).
  7. Grava novo preço no modelo de escrita (evento de domínio).
  8. Convergência: modelo de leitura converge em ~30s. Preço visível em GET /integration/prices/... reflete a regra.
  9. Notificação: webhook price.updated é disparado para cada SKU afetado.
  10. Conclusão: ao atingir endDate, status vira FINISHED. Se restorePricesOnAutoFinish: true, outro job restaura os preços pré-regra.
  [CREATE] POST /rules              [APPLY] POST /rules/{id}/apply
       |                                     |
       v                                     v
  +----------+                          +----------+
  | INACTIVE |  ─── startDate ──>       | SCHEDULED|
  +----------+                          +----------+
                                             |
                                             v
                                     +----------------+
                                     | RuleExecutionJob|  ── async
                                     +----------------+
                                             |
                                             v
                                     +----------+
                                     |  ACTIVE  |  ── preços recalculados
                                     +----------+
                                             |
                                             | endDate
                                             v
                                     +----------+
                                     | FINISHED |  ── restorePricesOnAutoFinish?
                                     +----------+      sim -> volta ao pré-regra

Cobertura de dados de custo

A regra só pode calcular preço para SKUs que têm custo unitário cadastrado. Se muitos SKUs da elegibilidade não têm custo, a regra pula esses itens. O campo costDataCoveragePct no summary da regra mostra o percentual coberto:

curl -X GET "$GUBEE_API/bffweb/price-formation/rules/RULE-123/summary" \
  -H "Authorization: Bearer $JWT"
{
  "ruleId": "RULE-123",
  "status": "ACTIVE",
  "eligibleSkus": 5000,
  "costDataCoveragePct": 78,
  "lastJobId": "JOB-abc-456",
  "lastJobStatus": "SUCCESS"
}

Alerta de cobertura baixa

O dashboard alerta quando costDataCoveragePct < 50. Nesse caso, mais da metade dos SKUs elegíveis não terá preço recalculado — a regra pode dar resultados enganosos (alguns preços desatualizados misturados com atualizados). Antes de ativar uma regra em produção, sincronize os custos no ERP via PUT /integration/products/* ou via planilha.

Onde a Formação de Preço vive na API

A Formação de Preço não está na superfície /integration. Ela vive em:

/bffweb/price-formation/*

Recursos principais:

Método Path Descrição
POST /bffweb/price-formation/rules Cria regra.
GET /bffweb/price-formation/rules Lista regras (filtros por status).
GET /bffweb/price-formation/rules/{ruleId} Detalhe de uma regra.
PUT /bffweb/price-formation/rules/{ruleId} Atualiza regra.
DELETE /bffweb/price-formation/rules/{ruleId} Remove regra.
POST /bffweb/price-formation/rules/{ruleId}/apply Aplica regra (agenda job).
POST /bffweb/price-formation/rules/{ruleId}/clone Clona regra.
POST /bffweb/price-formation/rules/{ruleId}/finish Finaliza regra manualmente.
POST /bffweb/price-formation/rules/{ruleId}/restore-prices Restaura preços pré-regra.
GET /bffweb/price-formation/rules/{ruleId}/summary Summary com cobertura.
GET /bffweb/price-formation/rules/{ruleId}/results Resultados detalhados por SKU.
GET /bffweb/price-formation/rules/{ruleId}/jobs/{jobId} Status de um job.
POST /bffweb/price-formation/rules/{ruleId}/simulate Simula regra sem gravar (dry-run).
POST /bffweb/price-formation/simulate Simulação ad-hoc.

Existem também endpoints de templates reutilizáveis:

Método Path Descrição
POST /bffweb/price-formation/templates Cria template.
GET /bffweb/price-formation/templates Lista templates.
POST /bffweb/price-formation/templates/{id}/copy Copia template (para outro seller).

Superfície /bffweb vs /integration

Estes endpoints estão em /bffweb (superfície web). Parceiros ERP/hub normalmente não consomem a Formação de Preço diretamente — eles apenas leem o preço resultante via GET /integration/prices/.... Esta página existe para que você entenda por que vê preços mudando sem ter enviado update.

Se você precisa automatizar a criação de regras, fale com o time Gubee para validar se o token de parceiro tem escopo bffweb:price-formation.

O que o integrador deve fazer

Na prática, um ERP/hub integrando com a Gubee:

  1. Não cria regras de Formação de Preço (essa é uma decisão comercial do seller, feita no painel admin).
  2. Sincroniza custos — se você fornece custo unitário ao produto, a regra consegue calcular. Veja Sincronizar Catálogo.
  3. Lê preços efetivos via GET /integration/prices/bysku?sku=X (ou /platforms/{platform}/{itemId}).
  4. Reage a webhooks price.updated se precisar manter espelho no ERP.

Para o fluxo simples de atualização de preço (sem Formation), veja Estoque e preço por SKU.

Simulação antes de aplicar

Antes de ativar uma regra em produção, use o endpoint de simulação para ver quais seriam os preços resultantes sem gravar:

curl -X POST "$GUBEE_API/bffweb/price-formation/simulate" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "PRODUCT",
    "baseValue": "COST",
    "costStructure": {
      "channelCosts": { "commission": 0.16, "freight": 5.00, "tax": 0.18 },
      "embedProductCosts": true
    },
    "objective": {
      "driver": "CONTRIBUTION_MARGIN",
      "value": 25.00
    },
    "eligibility": { "platforms": ["MERCADOLIVRE"] }
  }'

Resposta (SimulatedResultApiDTO):

{
  "totalSkus": 1250,
  "simulatedPrices": [
    {
      "skuId": "SKU-001",
      "platform": "MERCADOLIVRE",
      "currentPrice": 209.90,
      "simulatedPrice": 219.50,
      "delta": 9.60
    }
  ]
}

Use a simulação para validar com o time comercial antes de POST /apply.

Erros comuns

Preço no ERP não reflete no marketplace

Sintoma: ERP envia PUT /prices/bysku com 199.90, mas GET /integration/prices/MERCADOLIVRE/ITEM retorna 209.90.

Causa: existe regra ACTIVE cujo resultado é 209.90. Toda vez que você grava um preço, o motor pode regravar com base na regra.

Solução: verifique regras ativas com GET /bffweb/price-formation/rules?status=ACTIVE. Se houver, pause-a (POST /rules/{id}/finish) ou ajuste a regra para respeitar override manual.

costDataCoveragePct baixo

Sintoma: regra ACTIVE mas só 20% dos SKUs têm preço atualizado.

Causa: 80% dos SKUs elegíveis não têm custo unitário cadastrado.

Solução: sincronize custos via PUT /integration/products/* ou via planilha admin, e re-apply a regra.

PROFIT em código legado

Sintoma: ao ler regras antigas, algumas têm objective.driver: "PROFIT".

Causa: modelo anterior. Não é mais aceito em criação/edição.

Solução: migre para CONTRIBUTION_MARGIN. O BFF rejeita PROFIT em POST/PUT /rules com 400 Bad Request.

Próximos passos


Apêndice: API de Promoções / Ajustes de Preço

A Formação de Preço é o motor de precificação dinâmica (calcula preço a partir de custo + margem desejada). Quando você precisa de ajustes pontuais de preço (campanhas sazonais, Black Friday, queima de estoque), a Gubee expõe o CRUD de Promoções em /integration/promotions.

Diferença entre Formation, Promotion e PUT /prices/bysku

Recurso Quando usar Efeito
PUT /integration/prices/bysku Ajuste direto de 1 SKU, sem regra Sobrescreve o preço com valor fixo
Formação de Preço (/bffweb/price-formation/*) Regra durável de cálculo de preço Recalcula preço de N SKUs sempre que custo muda
Promotion (/integration/promotions/*) Campanha temporária com data de início/fim Aplica desconto/ajuste durante janela configurada

A Promotion é a única das três que é totalmente exposta na API pública de integração (a Formação fica apenas no /bffweb porque é operada via UI interna; o resultado dela é lido pelos parceiros via GET /prices/...).

Endpoints disponíveis

Todos sob /integration/promotions (HAL+JSON, autenticação JWT):

Método Path Descrição
GET /{promotionId} Recupera 1 promoção por ID
GET /list/all?page=&size=&sort= Lista paginada (todas do seller)
POST /list/search Busca paginada por filtro (status, datas, tipo, marketplace)
POST /list/search/ids Mesma busca, mas retorna só IDs (sync incremental)
POST (root) Cria nova promoção (201 Created)
PUT /update/{promotionId} Atualiza promoção existente
DELETE /{promotionId} Remove promoção
PUT /activate Ativa lote de promoções (transição p/ ACTIVE)
PUT /finish Finaliza lote (transição p/ FINISHED)
POST /{id}/clone Clona promoção (novo ID, status INACTIVE)

Ciclo de vida de uma promoção

   INACTIVE  ──activate──>  ACTIVE  ──finish (manual ou endDate)──>  FINISHED
       │                        │
       │                        └── se restorePricesOnAutoFinish=true,
       │                            preços voltam ao valor anterior
       └── criada via POST ou via clone

Exemplo: criar uma campanha de Black Friday

curl -X POST "$GUBEE_API/integration/promotions" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Black Friday 2026 - Eletrônicos",
    "status": "INACTIVE",
    "startDate": "2026-11-24T00:00:00Z",
    "endDate": "2026-11-30T23:59:59Z",
    "restorePricesOnAutoFinish": true,
    "eligibility": {
      "domain": "PRODUCT",
      "categories": ["Eletrônicos"]
    },
    "ruleType": "PERCENTAGE_DISCOUNT",
    "value": 15.0
  }' | jq

Ative em lote próximo ao início:

curl -X PUT "$GUBEE_API/integration/promotions/activate" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '["<promotionId-retornado>"]' -w "\nHTTP %{http_code}\n"

Para finalizar manualmente antes do endDate:

curl -X PUT "$GUBEE_API/integration/promotions/finish" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '["<promotionId>"]'

Modelo de dados (resumo)

A PromotionDTO tem campos principais:

  • id, name, status (INACTIVE / ACTIVE / FINISHED)
  • startDate, endDate (ISO 8601)
  • restorePricesOnAutoFinish — se true, restaura preços pré-promoção ao expirar
  • eligibility — árvore de filtros (PRODUCT ou AD, categorias, SKUs, plataformas)
  • ruleType — tipo de ajuste (PERCENTAGE_DISCOUNT, FIXED_DISCOUNT, FIXED_PRICE, etc)
  • value — valor do ajuste (percentual ou absoluto conforme ruleType)

Para schema completo, consulte a Referência da API na árvore lateral.