Promoções

Por Administrador 2026-07-14

Manual de Integração — Promoções (API do Concentrador)

Guia para ERPs que criam/editam promoções chamando a API do Concentrador diretamente (a mesma API que a tela de cadastro do Concentrador usa, porém sem passar por ela).

⚠️ Leitura obrigatória — a API não valida regra de negócio. O backend só valida formato (campos obrigatórios de estrutura, enums, tamanhos). Ele não verifica se o payload faz sentido para o tipo escolhido (ex.: aceita PRECO_FIXO sem preço, LEVA_PAGA com operador >=, Scanntech com operador errado). Todas as regras de negócio moram na tela do Concentrador — quem integra direto precisa replicá-las (ver seção 7). Um JSON aceito pela API pode gerar uma promoção que não desconta nada no PDV.


1. As duas famílias de tipos

As promoções se dividem em duas famílias, distinguidas pelo campo origem:

Família origem tipo Comportamento
Scanntech SCANNTECH os 6 tipos SCANNTECH_* Homologado/fixo. O ERP envia as promoções vindas da Scanntech. Não use os campos "nossos" (limite por produto, considerar produtos diferentes). Operador sempre =.
Manual (nossa) TILLIT, CONCENTRADOR, ADAPTIVE, CONTROLLER_PLUS LEVA_PAGA, PRECO_FIXO, DESCONTO_FIXO, DESCONTO_VARIAVEL Cadastro manual, com particularidades próprias (seção 5). Semântica por unidade.

Tipo e origem são acoplados: tipos SCANNTECH_* só com origem = SCANNTECH; os 4 tipos manuais só com origem não-Scanntech. Não misture (a API aceita, mas o comportamento fica indefinido).

Valores exatos dos enums (string em JSON):

  • tipo: SCANNTECH_LEVA_PAGA, SCANNTECH_PRODUTO_BRINDE, SCANNTECH_PRECO_FIXO, SCANNTECH_PRODUTO_DESCONTO, SCANNTECH_DESCONTO_VARIAVEL, SCANNTECH_DESCONTO_FIXO, LEVA_PAGA, PRECO_FIXO, DESCONTO_FIXO, DESCONTO_VARIAVEL.
  • origem: TILLIT, CONCENTRADOR, SCANNTECH, ADAPTIVE, CONTROLLER_PLUS.
  • operador (na condição-item): = ou >=.

2. Estrutura do payload

Árvore enviada em POST/PUT (ver seção 8):

Promocao
├─ tipo, origem, titulo, inicioVigencia, fimVigencia, limiteAplicacaoPorCupom, ...
└─ detalhePromocao
   ├─ preco / desconto / quantidadePagar        ← valor dos tipos SCANNTECH (nível detalhe)
   ├─ condicao
   │  └─ itens[]  (condicaoPromocaoItem)
   │     ├─ operador, quantidade (gatilho)
   │     ├─ preco / desconto / quantidadePagar  ← valor dos tipos MANUAIS (nível item)
   │     ├─ limiteAplicacaoPorProduto           ← só manuais
   │     ├─ considerarProdutosDiferentes        ← só manuais
   │     └─ idProdutos[]
   └─ beneficio (opcional)
      └─ itens[]  ├─ quantidade └─ idProdutos[]  ← só SCANNTECH_PRODUTO_BRINDE / _PRODUTO_DESCONTO

Onde vai o "valor" da promoção — o ponto que mais confunde:

Família Nível do preco/desconto/quantidadePagar
Scanntech em detalhePromocao (nível detalhe)
Manual em cada detalhePromocao.condicao.itens[] (nível item de condição)

quantidade (o gatilho — "levando N") vai sempre no item de condição, nas duas famílias.


3. Tabela-resumo por tipo

= obrigatório · = não usar · detalhe / item = nível onde vai o valor.

tipo origem operador valor (nível/campo) quantidadePagar benefício limiteAplicacaoPorProduto considerarProdutosDiferentes limiteAplicacaoPorCupom
LEVA_PAGA manual = item ✔ opcional opcional
PRECO_FIXO manual = ou >= item · preco opcional opcional
DESCONTO_FIXO manual = ou >= item · desconto (R$) ✔ opcional opcional
DESCONTO_VARIAVEL manual = ou >= item · desconto (%) ✔ opcional opcional
SCANNTECH_LEVA_PAGA SCANNTECH = detalhe ✔
SCANNTECH_PRECO_FIXO SCANNTECH = detalhe · preco
SCANNTECH_DESCONTO_FIXO SCANNTECH = detalhe · desconto (R$) ✔
SCANNTECH_DESCONTO_VARIAVEL SCANNTECH = detalhe · desconto (%) ✔
SCANNTECH_PRODUTO_BRINDE SCANNTECH = ✔ (itens)
SCANNTECH_PRODUTO_DESCONTO SCANNTECH = detalhe · desconto ✔ (itens)

Sempre obrigatórios (todos os tipos): titulo, inicioVigencia, fimVigencia, tipo, origem, ao menos 1 item de condição com produto (idProdutos), e por item o operador e a quantidade. Para SCANNTECH_PRODUTO_BRINDE/_PRODUTO_DESCONTO exige-se, além da condição, ao menos 1 item de benefício com produto.


4. Família Scanntech (resumo)

Comportamento homologado, não mexer. O ERP envia as promoções da Scanntech como estão:

  • Valor no detalhePromocao (preco/desconto/quantidadePagar), conforme a tabela da seção 3.
  • Operador sempre =.
  • limiteAplicacaoPorCupom (nível Promocao): limite de aplicações por cupom/venda — na tela do Concentrador é obrigatório para Scanntech. 0 = ilimitado.
  • SCANNTECH_PRODUTO_BRINDE e SCANNTECH_PRODUTO_DESCONTO usam o bloco beneficio.itens (o produto que ganha brinde/desconto) além da condicao.itens (a condição de compra).
  • Não envie limiteAplicacaoPorProduto nem considerarProdutosDiferentes (são da família manual).

5. Particularidades da família manual (o núcleo)

Os 4 tipos manuais têm semântica por unidade e três alavancas próprias: operador, limiteAplicacaoPorProduto e considerarProdutosDiferentes. Entender isso é o que separa uma promoção que funciona bem de uma que não desconta o esperado.

5.1 O que cada tipo faz (por unidade)

  • PRECO_FIXO — cada unidade beneficiada passa a custar preco (por unidade). Nunca cobra acima do preço de prateleira (desconto trava em zero).
  • DESCONTO_FIXO — cada unidade beneficiada recebe desconto reais de abatimento.
  • DESCONTO_VARIAVEL — cada unidade beneficiada recebe desconto% de abatimento.
  • LEVA_PAGA — a cada quantidade (gatilho) unidades, quantidade − quantidadePagar saem "grátis". O valor das unidades grátis é rateado (diluído) entre as quantidade unidades do pacote (todas saem com um desconto proporcional; o total do pacote equivale às unidades grátis).

5.2 Operador = vs >= — muda como o gatilho e o limite funcionam

  • = (pacotes): a venda é fragmentada em pacotes de quantidade unidades. Cada pacote completo é 1 aplicação que recebe o benefício inteiro. Unidades que não fecham um pacote ficam no preço normal até completar o próximo. limiteAplicacaoPorProduto = nº máximo de aplicações (pacotes).
  • >= (a partir de N): atingido o gatilho quantidade, todas as unidades recebem o benefício por unidade. limiteAplicacaoPorProduto = nº máximo de unidades com desconto.
    • LEVA_PAGA não aceita >= (só =).

Exemplos:

  • PRECO_FIXO, =, quantidade=3, preco=5, limite=2, 6 un → 2 pacotes de 3 → as 6 a R$5 (2 aplicações). Com 9 un → só 6 a R$5 (teto de 2 aplicações), 3 no preço normal.
  • DESCONTO_FIXO, >=, quantidade=2, desconto=1, limite=10, 13 un → 10 unidades com R$1 de desconto, 3 no preço normal.

5.3 limiteAplicacaoPorProduto — significado depende do operador

Campo escalar no item de condição. Vazio, ausente ou 0 = ilimitado.

operador o limite conta
= aplicações (pacotes de quantidade unidades)
>= unidades com desconto

5.4 considerarProdutosDiferentes — considerar produtos diferentes vs. produto único

Só importa quando o mesmo item de condição tem 2+ produtos (idProdutos com vários). Boolean, padrão false.

  • false (produto único, padrão): cada pacote/grupo é de um só produto — produtos diferentes não se misturam num mesmo pacote. O limite conta por produto (cada produto tem seu próprio teto).
  • true (considerar produtos diferentes): produtos diferentes da condição contam juntos para formar o pacote (útil para variações intercambiáveis — mesmo produto em sabores/fragrâncias). O limite conta total por condição (não por produto).

⚠️ LEVA_PAGA com considerarProdutosDiferentes = true: o rateio da unidade grátis usa o preço do primeiro produto do pacote. Ative essa opção no leva-paga apenas com produtos de mesmo preço de venda (o caso de variações). Com preços diferentes, o valor descontado distorce.

5.5 permitirAcumularDescontos e exibirNoDescontoTotal

  • permitirAcumularDescontos (nível Promocao, padrão false): se true, o desconto desta promoção soma a outros descontos do item em vez de sobrescrever.
  • exibirNoDescontoTotal (padrão true): controla exibição no "sua economia".

6. Como escolher a configuração (guia rápido)

  • "Leve 3, pague 2"LEVA_PAGA, operador="=", quantidade=3, quantidadePagar=2.
  • "Leve 4, pague 3, no máximo 2 vezes" → idem, limiteAplicacaoPorProduto=2.
  • "Qualquer unidade a R$5"PRECO_FIXO, operador=">=", quantidade=1, preco=5.
  • "A partir de 3, cada uma R$5, até 10 unidades"PRECO_FIXO, >=, quantidade=3, preco=5, limiteAplicacaoPorProduto=10.
  • "10% em qualquer quantidade"DESCONTO_VARIAVEL, >=, quantidade=1, desconto=10.
  • "Leve 3 de qualquer sabor (mesmo preço), pague 2"LEVA_PAGA, =, quantidade=3, quantidadePagar=2, considerarProdutosDiferentes=true, idProdutos com todos os sabores.

7. Validações a replicar

O backend não faz nada disto — replique no ERP antes de enviar:

  • origem coerente com tipo (Scanntech ↔ SCANNTECH_*; manual ↔ os 4 tipos).
  • operador = "=" para todos os Scanntech e para LEVA_PAGA. Só PRECO_FIXO/DESCONTO_FIXO/ DESCONTO_VARIAVEL podem usar >=.
  • Valor no nível certo: detalhe para Scanntech, item de condição para manuais.
  • Campo de valor presente conforme o tipo (seção 3): preco (PRECO_FIXO), desconto (DESCONTO_FIXO/VARIAVEL e Scanntech desconto), quantidadePagar (LEVA_PAGA).
  • quantidade (gatilho) > 0 em cada item de condição.
  • Ao menos 1 item de condição com produto. Para SCANNTECH_PRODUTO_BRINDE/_PRODUTO_DESCONTO, ao menos 1 item de benefício com produto.
  • Não enviar limiteAplicacaoPorProduto/considerarProdutosDiferentes em Scanntech.
  • inicioVigencia < fimVigencia (o backend não checa).
  • idProdutos existentes — IDs inexistentes são descartados silenciosamente (sem erro), o que pode gerar item/promoção sem produto.

8. Exemplos de payload (POST/PUT)

Campos de infra (id, idUsuarioAlteracao) são opcionais; datas em ISO-8601.

8.1 LEVA_PAGA — leve 3 pague 2 (manual, =)

{
  "tipo": "LEVA_PAGA",
  "origem": "CONCENTRADOR",
  "titulo": "Leve 3 Pague 2 - Refrigerante",
  "inicioVigencia": "2026-07-01T00:00:00Z",
  "fimVigencia": "2026-07-31T23:59:59Z",
  "detalhePromocao": {
    "condicao": {
      "itens": [
        {
          "operador": "=",
          "quantidade": 3,
          "quantidadePagar": 2,
          "limiteAplicacaoPorProduto": 0,
          "considerarProdutosDiferentes": false,
          "idProdutos": ["11111111-1111-1111-1111-111111111111"]
        }
      ]
    }
  }
}

8.2 PRECO_FIXO — pacote de 3 a R$5, no máx 2 aplicações, considerar produtos diferentes (=)

{
  "tipo": "PRECO_FIXO",
  "origem": "CONCENTRADOR",
  "titulo": "3 Sabores por R$5 cada",
  "inicioVigencia": "2026-07-01T00:00:00Z",
  "fimVigencia": "2026-07-31T23:59:59Z",
  "detalhePromocao": {
    "condicao": {
      "itens": [
        {
          "operador": "=",
          "quantidade": 3,
          "preco": 5.00,
          "limiteAplicacaoPorProduto": 2,
          "considerarProdutosDiferentes": true,
          "idProdutos": [
            "22222222-2222-2222-2222-222222222222",
            "33333333-3333-3333-3333-333333333333"
          ]
        }
      ]
    }
  }
}

8.3 DESCONTO_VARIAVEL — 10% a partir de 2, teto de 10 unidades (>=)

{
  "tipo": "DESCONTO_VARIAVEL",
  "origem": "CONCENTRADOR",
  "titulo": "10% acima de 2 unidades",
  "inicioVigencia": "2026-07-01T00:00:00Z",
  "fimVigencia": "2026-07-31T23:59:59Z",
  "detalhePromocao": {
    "condicao": {
      "itens": [
        {
          "operador": ">=",
          "quantidade": 2,
          "desconto": 10,
          "limiteAplicacaoPorProduto": 10,
          "considerarProdutosDiferentes": false,
          "idProdutos": ["44444444-4444-4444-4444-444444444444"]
        }
      ]
    }
  }
}

8.4 SCANNTECH_LEVA_PAGA — valor no detalhe, limite por cupom

{
  "tipo": "SCANNTECH_LEVA_PAGA",
  "origem": "SCANNTECH",
  "titulo": "Scanntech Leve 3 Pague 2",
  "inicioVigencia": "2026-07-01T00:00:00Z",
  "fimVigencia": "2026-07-31T23:59:59Z",
  "limiteAplicacaoPorCupom": 1,
  "detalhePromocao": {
    "quantidadePagar": 2,
    "condicao": {
      "itens": [
        { "operador": "=", "quantidade": 3, "idProdutos": ["55555555-5555-5555-5555-555555555555"] }
      ]
    }
  }
}

8.5 SCANNTECH_PRODUTO_BRINDE — condição + benefício

{
  "tipo": "SCANNTECH_PRODUTO_BRINDE",
  "origem": "SCANNTECH",
  "titulo": "Compre A, ganhe B",
  "inicioVigencia": "2026-07-01T00:00:00Z",
  "fimVigencia": "2026-07-31T23:59:59Z",
  "limiteAplicacaoPorCupom": 1,
  "detalhePromocao": {
    "condicao": {
      "itens": [
        { "operador": "=", "quantidade": 2, "idProdutos": ["66666666-6666-6666-6666-666666666666"] }
      ]
    },
    "beneficio": {
      "itens": [
        { "quantidade": 1, "idProdutos": ["77777777-7777-7777-7777-777777777777"] }
      ]
    }
  }
}

9. Resumo de armadilhas

  • A API aceita regra de negócio inválida — valide no ERP (seção 7).
  • Valor no nível errado (detalhe x item) = promoção sem efeito.
  • LEVA_PAGA com >= não é suportado; use =.
  • limiteAplicacaoPorProduto significa aplicações no = e unidades no >=.
  • considerarProdutosDiferentes=true no LEVA_PAGA só com produtos de mesmo preço.
  • idProdutos inexistente é descartado sem aviso.