Pular para conteúdo

Anúncio vs Produto

A distinção entre produto e anúncio é central na modelagem Gubee. Confundir os dois leva a erros como atualizar preço no produto quando o desejado era no anúncio, ou esperar que uma edição no catálogo reflita instantaneamente na publicação.

O que é um produto?

Um produto é a entidade de catálogo. Tem:

  • Um externalId (também chamado productId), definido pelo seller.
  • Metadados (título, descrição, marca, dimensões, NBM).
  • N variações, cada uma com seu próprio sku (do seller) e skuId (Gubee).
  • Imagens e vídeos por variação.
  • Atributos e categorias.
  • EANs por variação: a lista eans, com um principal (ean, depreciado, devolve o principal).

Rotas canônicas: /integration/products/*.

O produto não está associado a um marketplace específico. Ele é a "fonte da verdade" do catálogo.

O que é um anúncio?

Um anúncio é a publicação do produto em um marketplace específico. Tem:

  • Um adId (gerado pela Gubee).
  • Um originSkuId que aponta para a variação do produto que o originou.
  • Um platform (MERCADOLIVRE, SHOPEE, etc.).
  • Título e descrição enriquecidos por template (podem diferir do produto).
  • Preço, estoque e imagens específicos da publicação.
  • Status (ACTIVE, INACTIVE, PAUSED).

Rotas canônicas: /integration/ads/* e /bffweb/ads/*.

Relação: 1 produto → N anúncios

Para um produto com N variações publicado em M marketplaces, existem até N×M anúncios. Cada anúncio é independente: preço e estoque podem variar por marketplace, mesmo que o produto subjacente seja o mesmo.

                  PRODUTO (productId=PROD-001)
                 /          |           \
          var skuId-A   var skuId-B   var skuId-C
              |             |             |
       +------+------+ +----+----+ +------+------+
       |      |      | |         | |      |      |
      ML     SHOPEE  B2W  ML    SHOPEE  ML    SHOPEE  B2W
       |      |      |   |        |       |     |      |
     ad#1   ad#2  ad#3 ad#4    ad#5    ad#6  ad#7  ad#8

Cada ad#N tem seu próprio adId e seu próprio preço/estoque.

Replicação produto → anúncio

Quais campos do produto acompanham o anúncio depois de criado é controlado pelo campo updateOptions do anúncio. Cada opção cobre um campo ou um grupo de campos:

Opção Campos do anúncio atualizados
TITLE título
DESCRIPTION descrição
DIMENSIONS altura, largura, profundidade e peso
HANDLING_TIME prazo de manuseio
IMAGES imagens da variação
VIDEOS vídeos da variação
STATUS ativo/inativo
PRICE preço
SPECIFICATION especificações/atributos
EAN EAN/GTIN: ean e a lista eans, sempre os dois juntos
BRAND marca — usa o nome do de/para da marca no marketplace do anúncio; sem de/para, o nome da marca
FISCAL NCM, origem fiscal e país de origem (sempre os três juntos)
WARRANTY garantia: tempo e tipo (SELLER_WARRANTY / FACTORY_WARRANTY), sempre os dois juntos; tipo não informado no produto não altera o do anúncio
CONDITION condição (novo/usado)

Quando a opção está no set, a mudança no produto chega ao anúncio via plataforma de streaming de eventos. Renomear uma marca, ou alterar o de/para dela com um marketplace, atualiza todos os anúncios com BRAND dos produtos daquela marca. Sem a opção, o anúncio mantém o valor próprio.

O anúncio só leva o campo ao marketplace se a integração daquele canal publica esse campo.

Clientes devem ignorar valores desconhecidos de updateOptions: novas opções podem ser acrescentadas sem aviso de versão.

Ligar ou desligar opções

Atenção: este endpoint está em /bffweb (superfície web), não em /integration. Parceiros ERP/hub raramente precisam tocá-lo — a replicação é normalmente configurada via painel.

curl -X PUT "$GUBEE_API/bffweb/ads/update/updateOptions" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "adIds": ["ad#1", "ad#2"],
    "eventOptions": ["BRAND", "FISCAL"],
    "action": "ENABLE"
  }'

action aceita ENABLE ou DISABLE. Resposta (202 Accepted): lista de adIds que serão processados em segundo plano.

["ad#1", "ad#2"]

Ligar uma opção não copia o valor atual do produto. Para isso, o painel chama em seguida POST /bffweb/ads/sync com {"adIds": [...], "eventOptions": [...]}, que reemite os eventos do produto para esses anúncios.

EANs do anúncio

O anúncio tem os mesmos dois campos da variação do produto: eans, a lista de itens { "value", "main" } (até 20, exatamente um principal), e ean, depreciado e sem data de remoção, que devolve o EAN principal.

{
  "ean": "7891234567890",
  "eans": [
    { "value": "7891234567890", "main": true },
    { "value": "17891234567897", "main": false }
  ]
}

Origem da lista. O anúncio criado a partir do produto copia a lista da variação. Depois disso, a lista só acompanha o produto se o anúncio tem EAN em updateOptions; sem a opção, a lista é do anúncio.

Leitura. As respostas de anúncio (GET /integration/ads/full/{id}, GET /integration/ads/list/byOriginSkuId e as consultas de /bffweb/ads) trazem ean e eans. eans é omitido quando nulo e vem [] quando a lista está vazia.

Escrita. Criar (POST /bffweb/ads), atualizar (PUT /bffweb/ads/{id}) e editar em lote (PUT /bffweb/ads/bulk-edit) seguem as mesmas regras do produto, descritas em Lista de EANs:

  • ean e eans ausentes ou null: os EANs do anúncio não são alterados.
  • Só ean com valor: vira o principal e os secundários são mantidos.
  • Só ean: "": remove todos.
  • Só eans: a lista enviada substitui a gravada; eans: [] remove todos. Com a gravação da lista desabilitada, eans sozinho é ignorado no anúncio, inclusive eans: []: nada muda.
  • ean com valor e eans, em anúncio que já existe: cada campo é comparado com o gravado.
    • Lista igual à gravada: vale o ean (se mudou, vira o principal; a lista é o eco do que foi lido e não é validada).
    • Lista diferente da gravada: vale a lista, e o par tem de ser coerente — ean está em eans e, se há item com main: true, é ele. Senão, 400.
  • ean: "" (vazio ou só com espaços) e eans, em anúncio que já existe:
    • eans: [] ou lista igual à gravada: remove todos os EANs. É o caso de quem lê o anúncio, esvazia ean e devolve o corpo.
    • Lista com itens e diferente da gravada: 400 (EAN_NOT_MAIN_OF_EANS), sem gravar nada.
    • Com a gravação da lista desabilitada: eans é ignorado e o ean: "" remove todos, qualquer que seja a lista.
  • ean: "" e eans com itens, na criação: a lista é gravada.
  • Valor repetido em eans não é erro: os repetidos viram um item só.
  • 400 (chave de erro invalid.ean.list), sem gravar nada: mais de um main: true (MULTIPLE_MAIN); mais de 20 itens depois de juntar os repetidos (TOO_MANY); ean fora da lista (EAN_NOT_IN_EANS); ean na lista, mas o item marcado é outro, ou ean: "" com eans com itens (EAN_NOT_MAIN_OF_EANS). Os dois últimos, na atualização, só quando a lista difere da gravada.

Trecho do corpo de PUT /bffweb/ads/{id} que acrescenta um secundário, com o par coerente (ean é o item principal de eans):

{
  "ean": "7891234567890",
  "eans": [
    { "value": "7891234567890", "main": true },
    { "value": "17891234567897", "main": false },
    { "value": "27891234567894", "main": false }
  ]
}

Um par coerente pode ser reenviado sem mudar o resultado. Reenviar um par incoerente que passou porque a lista era a gravada (ean novo + lista lida) devolve 400 na segunda vez; o EAN gravado pela primeira continua lá. Reenviar ean: "" com a lista lida devolve 400 (EAN_NOT_MAIN_OF_EANS) na segunda vez — a lista já difere da gravada, vazia — e os EANs continuam removidos (com a gravação da lista desabilitada, a repetição é aceita e nada muda). Para remover de forma que possa ser repetida sem erro, envie só ean: "".

Envie sempre ean junto com eans

A gravação da lista é habilitada por ambiente. Enquanto não estiver, eans é ignorado na escrita e só ean é gravado — um corpo só com eans não grava os EANs enviados. Com os dois campos coerentes o resultado é o esperado nos dois casos. Diferença para o produto: lá, eans: [] sem ean ainda limpa em POST e PUT com a lista desabilitada; no anúncio não limpa em nenhuma operação.

Mudança de comportamento

Em criar, atualizar e editar em lote, ean nulo ou omitido não apaga mais o EAN do anúncio. ean: "" remove; eans: [] só remove com a gravação da lista habilitada.

Busca e associação. O filtro ean da busca de anúncios (POST /bffweb/ads/list/search/{platform}) e a associação de anúncio a produto por EAN casam com qualquer EAN da lista, principal ou secundário.

Trocar o EAN principal não chega a todo canal: o Mercado Livre não atualiza o GTIN de um anúncio já publicado e a Amazon só envia o EAN na criação. Já é assim ao trocar o ean.

Endpoints de anúncio (superfície /integration)

Listar anúncios por originSkuId

curl -X GET "$GUBEE_API/integration/ads/list/byOriginSkuId?originSkuIds=SKU-DEMO-001,SKU-DEMO-002" \
  -H "Authorization: Bearer $JWT"
[
  {
    "id": "ad#1",
    "sellerId": "SELLER123",
    "originSkuId": "SKU-DEMO-001",
    "platform": "MERCADOLIVRE",
    "status": "ACTIVE",
    "name": "Tênis Runner Preto 42 - ML",
    "ean": "7891234567890",
    "eans": [
      { "value": "7891234567890", "main": true },
      { "value": "17891234567897", "main": false }
    ],
    "defaultPrice": { "value": 209.90 }
  },
  {
    "id": "ad#5",
    "sellerId": "SELLER123",
    "originSkuId": "SKU-DEMO-002",
    "platform": "SHOPEE",
    "status": "ACTIVE",
    "name": "Tênis Runner Preto 43 - SHOPEE"
  }
]

Limite de 10 originSkuIds por chamada. Acima disso, retorna 400.

Detalhe completo (com template enriquecido)

curl -X GET "$GUBEE_API/integration/ads/full/ad%231" \
  -H "Authorization: Bearer $JWT"

Retorna AdGroupApiDTO com:

  • Título e descrição finais (após aplicação do template).
  • Todas as variações do grupo (se o anúncio for pai de variações).
  • Preço, estoque, imagens, vídeos, atributos, especificações.

Buscar originSkuIds por critério

curl -X POST "$GUBEE_API/integration/ads/search/originSkuIds/MERCADOLIVRE" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "ACTIVE",
    "onlySimple": true
  }'

Retorna lista de originSkuIds cujos anúncios na plataforma informada casam com o critério. Útil para sincronização inicial de catálogo.

Mapear adId → originSkuId

curl -X POST "$GUBEE_API/integration/ads/map/originSkuIds" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '["ad#1", "ad#2", "ad#999"]'
{
  "ad#1": "SKU-DEMO-001",
  "ad#2": "SKU-DEMO-001"
}

ad#999 foi omitido da resposta porque não existe ou não tem originSkuId. Esta é a semântica do endpoint: IDs inválidos são silenciosamente descartados.

Expandir adIds pais para filhos

curl -X POST "$GUBEE_API/integration/ads/obtain/originSkuIds" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '["ad-parent#1"]'

Retorna Set<String> com todos os originSkuIds das variações filhas. Útil quando o parceiro recebe um adId pai (que não é publicável individualmente) e precisa dos itens filhos.

Quando usar endpoint de produto vs anúncio

Cenário Use
Criar/atualizar dados de catálogo (título, descrição, dimensões, EANs) /integration/products/*
Sincronizar preço/estoque "default" do produto /integration/prices/* e /integration/stocks/* (domínio PRODUCT)
Sincronizar preço/estoque por marketplace /integration/prices/platforms/{itemId} com adId como itemId
Ler como o anúncio está publicado (com template aplicado) /integration/ads/full/{id}
Descobrir quais anúncios existem para um SKU /integration/ads/list/byOriginSkuId
Ligar/desligar replicação de campos (imagens, vídeos, marca, fiscal, garantia…) /bffweb/ads/update/updateOptions
Atualizar título/descrição específicos do anúncio /integration/ads/titledescription/byOriginSkuId/{skuId}

itemId: skuId vs adId

Em endpoints que aceitam {itemId} (preço, estoque), o significado depende do domínio:

  • domainType=PRODUCT (default): itemId é o skuId da variação.
  • domainType=AD: itemId é o adId.
# preço de um produto (default)
curl -X GET "$GUBEE_API/integration/prices/MERCADOLIVRE/SKUID-123" \
  -H "Authorization: Bearer $JWT"

# preço de um anúncio específico
curl -X PUT "$GUBEE_API/integration/prices/platforms/ad%231?domainType=AD" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '[{ "platform": "MERCADOLIVRE", "value": 219.90, "priceType": "DEFAULT" }]'

Consistência entre produto e anúncio

Após atualizar preço/estoque no produto, a propagação para os anúncios derivados acontece via plataforma de streaming de eventos e pode levar até 30s (e mais se a replicação estiver com updateOptions desativada — nesse caso, o anúncio mantém valor próprio). Veja CQRS e consistência eventual.

Anúncios são derivados do produto por um job interno; não tente criar anúncios manualmente para refletir uma mudança de produto. A criação de anúncio é um fluxo de publicação separado (normalmente via painel web).

Resumo

  • Produto = catálogo, fonte da verdade, identificado por productId / sku / skuId.
  • Anúncio = publicação em marketplace, identificado por adId, ligado ao produto via originSkuId.
  • Preço/estoque podem estar em qualquer dos dois domínios — escolha pelo domainType ou pelo endpoint (/prices/bysku vs /prices/platforms/{itemId}).
  • Replicação é controlada por updateOptions (TITLE … VIDEOS, BRAND, FISCAL, WARRANTY, CONDITION).
  • Latência entre produto e anúncio é medida em segundos, não milissegundos.

Próximos passos