Pesquisa — CRM Bonus: Geração de bônus em pedidos sem resgate
Data: 2026-05-28 (atualizado 2026-05-29) Contexto: Task #193232 — análise complementar à task-06 Status: Insumo para conversa com PO — fluxo esperado não confirmado
Divergência Portal vs App relevante para o domínio Upsell: o handler de criação de pedido via Portal (zzportal) não popula
CrmBonus.Total, fazendo o CRM não gerar bônus. Esta divergência pode afetar outras features do Upsell que dependem doCrmBonuspopulado corretamente. Considerar pesquisa separada no domínio Upsell para mapear o caminho completo de criação via Portal. Arquivos relacionados:
- task-193232-checkout-crm.md — overview da task
- task-193232-checkout-crm-requisitos.md — requisitos EARS
- pesquisa-crm-bonus.md — visão consolidada
- pesquisa-crm-bonus-analise-codigo.md — análise de código
- pesquisa-crm-bonus-documentacao-parceiro.md — API oficial CRM&Bonus
- pesquisa-crm-bonus-docs-internos.md — docs internos + Wiki
- pesquisa-desconto-pix-crm-bonus.md — combinação PIX + CRM
1. O cenário em discussão (refinado 2026-05-29)
Cenário confirmado pelo usuário:
- Vendas com origem Portal (zzportal):
finaliza_compraé chamado comvalor_bruto = 0→ CRM não gera bônus → cliente perde benefício - Vendas com origem App (zzapp): geram bônus corretamente — o App envia
objeto
CrmBonuscomtotalpreenchido na criação do carrinho →CrmBonus.Totalé persistido com o valor real →finaliza_compraenviavalor_brutocorreto → CRM gera bônus
Hipótese de negócio a validar com PO:
"Toda compra em loja que tenha CRM Bonus deveria finalizar a compra para gerar bônus, mesmo quando o cliente não usou bônus como desconto. O CRM gera novo bônus (cashback) com base no total final pago."
Esta pesquisa investiga o gap específico do Portal, identifica a correção mínima e levanta pendências para aprovação do PO.
2. Comportamento atual confirmado em código
2.1 Guarda do PosCaptureService
- Arquivo:
Application/Services/PosCaptureService.cs - Condição:
cart.CrmBonus != null && !string.IsNullOrEmpty(cart.CrmBonus.IdsBonus) - Fonte: pesquisa-crm-bonus-analise-codigo.md:113, 172
- Análise de logs (2026-05-29): o valor no banco para
CrmBonusIdsBonustem default"0"(string), nãoNULL/"" !string.IsNullOrEmpty("0")→true→ a guarda não bloqueia pedidos sem resgate- Conclusão revisada:
finaliza_comprajá é chamado mesmo sem resgate, porém com valores default:valor_bruto = cart.CrmBonus.Total = 0ids_bonus = "0"bonus_resgatado = cart.CrmBonus.RescuedBonus = 0
2.2 Guarda do CreatePaymentCommandHandler
- Condição:
cart.CrmBonus != null+ internamentebonus_resgatado > 0 - Fonte: pesquisa-crm-bonus.md:57, pesquisa-crm-bonus-analise-codigo.md:91, 171
- Implicação: sem resgate →
baixa_bonusnunca é chamado → CRM não recebebaixa_bonuspara registrar oticketantes dofinaliza_compra
2.3 O que é efetivamente enviado ao CRM em pedidos sem resgate (análise de logs)
Com base na análise de logs (2026-05-29), o PosCaptureService envia no finaliza_compra:
{
"user_id": <valor real do cliente>,
"valor_bruto": 0, // ← cart.CrmBonus.Total default (0)
"bonus_resgatado": 0, // ← cart.CrmBonus.RescuedBonus default (0)
"ids_bonus": "0", // ← cart.CrmBonus.IdsBonus default ("0")
"ticket": "<paymentIdComplex>",
"campanha": "0" // ← useCRM derivado de GenerateCrmBonus via transformação invertida (ver 2.7)
}
O problema não está na guarda — está no valor enviado:
valor_bruto = 0→ CRM recebe0como base de cálculo → provavelmente gera bônus de0ou recusa a requisiçãoids_bonus = "0"→ não é um ID de bônus real; comportamento não documentado pelo parceiro- O
cart.ItemsTotal(valor real dos itens) não é usado comovalor_bruto campanha = "0"— deriva deGenerateCrmBonus = trueapós transformação dupla (bool → int → string invertida); ver seção 2.7
Escopo do problema (confirmado pelo usuário, 2026-05-29): este cenário
(valor_bruto = 0 no finaliza_compra) ocorre exclusivamente em vendas com
origem Portal (zzportal). Vendas com origem App (zzapp) enviam objeto
CrmBonus com total preenchido na criação → CrmBonus.Total persistido com
valor real → finaliza_compra envia valor_bruto correto → CRM gera bônus
normalmente.
2.4 Flag GenerateCrmBonus
- Existe em
cart.GenerateCrmBonus, passado aoCrmBonutDTO.CreateFromCrmBonusviaConvert.ToInt32(cart.GenerateCrmBonus)(parâmetro nomeadocampanha). Internamente sofre transformação invertida:useCRM = (campanha == 0) ? "-1" : "0". Ver detalhes na seção 2.7. - Fonte: pesquisa-crm-bonus-analise-codigo.md:131
- Default no banco (CartMapping):
true— mapeado comoIsRequired(false).HasDefaultValue(true) - Default no vendas_app:
true(cart_dto.dart:110) - UI (vendendo loja): vendedora pode marcar "Não gerar CRM Bônus para esta venda" (
toggleGenerateCrmBonus, check_sell_crm_values.dart:67) - Regra de habilitação da UI:
customer.cellPhone != null && saleEcommerce == false(cart_dto.dart:74) - Estado atual: com a confirmação de que a guarda do
PosCaptureServicenão bloqueia (IdsBonus ="0"permite a passagem), o flagGenerateCrmBonusjá chega ao CRM como campocampanha(após transformação invertida — ver 2.7) em todos os pedidos. O gap não está no flag — está novalor_bruto = 0.
2.5 Endpoint finaliza_compra — resposta de sucesso inclui bonus_id
Conforme doc oficial (pesquisa-crm-bonus-documentacao-parceiro.md:145-161):
{
"status": true,
"message": "sucesso",
"data": {
"bonus_id": 136473654,
"order_id": 129898024,
"ticket": "CRM-TESTE-001"
}
}
O campo bonus_id comprova que finaliza_compra é o endpoint que registra a venda e gera bônus no CRM. Nenhum outro endpoint tem essa semântica no fluxo legado.
2.6 Diferença por origem do pedido (Portal vs App)
O CartMapping mapeia o campo Origin (enum OriginType) com default App:
builder.Property(x => x.Origin) .IsRequired(true) .HasDefaultValue(OriginType.App);
Comportamento confirmado (2026-05-29):
| Origem | CrmBonus na criação | CrmBonus.Total persistido | valor_bruto enviado | CRM gera bônus? |
|---|---|---|---|---|
| App | Objeto completo com total | Valor real do carrinho | Real | SIM |
| Portal | Ausente/vazio | 0 | 0 | NÃO |
Divergência de domínio (registrada também para o Upsell): o handler de
criação de pedido via Portal não popula CrmBonus.Total. Esta divergência
provavelmente afeta outras features que dependem de dados do CrmBonus
calculados na criação. Ver referência no topo deste documento e considerar
pesquisa separada no domínio Upsell.
A investigar:
- Qual handler/comando cria pedidos com origem Portal?
- O zzportal envia
crmBonusno payload de criação ou deixa para o checkout calcular? - Por que o caminho Portal diverge do App na população do
CrmBonus?
2.7 Mapeamento GenerateCrmBonus → campanha no DTO (achado 2026-05-29)
O CrmBonutDTO.CreateFromCrmBonus aplica uma transformação invertida no
flag GenerateCrmBonus antes de enviar ao CRM:
public static CrmBonutDTO CreateFromCrmBonus(int? userId, decimal? total,
decimal? rescuedBonus, string? idsBonus, string? ticket, int? campanha)
{
string useCRM;
useCRM = (campanha == 0) ? "-1" : "0";
// ...
}
Mapeamento final:
cart.GenerateCrmBonus | Convert.ToInt32 | useCRM enviado | Campo JSON campanha |
|---|---|---|---|
true (gerar bônus) | 1 | "0" | "campanha": "0" |
false (não gerar) | 0 | "-1" | "campanha": "-1" |
Problemas:
-
Campo
campanhanão está na documentação oficial do parceiro (pesquisa-crm-bonus-documentacao-parceiro.md:95-162 — schema dofinaliza_compra). Não há referência aos valores"0"/"-1"nem ao significado deste campo. -
Inversão e mudança de tipo:
bool→int (0/1)→string ("-1"/"0"). A inversão (truevira"0",falsevira"-1") é contraintuitiva. -
Nome do parâmetro enganoso: no
CreateFromCrmBonuso parâmetrocampanhasugere "ID de campanha", mas o valor recebido éConvert.ToInt32(cart.GenerateCrmBonus)— um booleano. Internamente, é usado como flag booleana invertida (useCRM). -
Origem desconhecida: sem
git blamenem histórico do parceiro, não é possível confirmar se é:- (a) Definição antiga do CRM que mudou semântica sem atualizar a doc
- (b) Bug de implementação com valores inventados pelo dev na integração
- (c) Acordo informal não documentado com o parceiro
Ações (registradas nas seções 7 e 8):
- Perguntar ao parceiro CRM o que o campo
campanhasignifica git blameemCrmBonutDTO.CreateFromCrmBonuspara identificar autor, data e PR/commit que introduziu a transformação- Buscar documentação interna (Wiki ZZAPPS, Confluence) por referências
a
campanha,useCRM,"-1","0"no contexto CRM
Arquivo inspecionado: Core.OrgDB.Data.Mappings.CartMapping
3.1 Mapeamento real de CrmBonus (OwnsOne)
builder.OwnsOne(c => c.CrmBonus, crmBonus =>
{
crmBonus.WithOwner();
crmBonus.Property(a => a.UserId)
.HasColumnName("CrmBonusUserId");
crmBonus.Property(a => a.Total)
.HasColumnName("CrmBonusTotal")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
crmBonus.Property(a => a.RescuedBonus)
.HasColumnName("CrmBonusRescuedBonus")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
crmBonus.Property(a => a.RescuedBonus) // ← linha duplicada (bug cosmético)
.HasColumnName("CrmBonusRescuedBonus")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
crmBonus.Property(a => a.IdsBonus)
.HasColumnType("varchar(150)")
.HasColumnName("CrmBonusIdsBonus");
crmBonus.Property(a => a.Ticket)
.HasColumnName("CrmBonusTicket");
});
Achados:
- Nenhum
IsRequired()declarado noOwnsOnenem em nenhumaPropertyinterna CrmBonusTotal,CrmBonusRescuedBonus:decimal(18,2)NOT NULL (value type)CrmBonusUserId: tipo padrão (provavelmenteint NOT NULL)CrmBonusIdsBonus:varchar(150)— semIsRequired()no mapeamento, mas valor default no banco é"0"(confirmado por análise de logs)CrmBonusTicket: tipo padrão (provavelmentenvarchar(max) NULL)- Bug cosmético:
Property(a => a.RescuedBonus)aparece 2x consecutivas com mapeamento idêntico. Sem impacto funcional (idempotente), mas sinal de descuido. A ser corrigido em janela de housekeeping.
3.2 GenerateCrmBonus está no root CartModel (confirmado)
builder.Property(x => x.GenerateCrmBonus)
.IsRequired(false)
.HasDefaultValue(true);
- Default no banco:
true→ pedidos legados sem explicitar têm geração habilitada - Acessível independente de
cart.CrmBonussernullou instanciado - Cenário C (flag inacessível) descartado
3.3 Comportamento esperado de materialização (a confirmar em runtime)
Heurística do EF Core para OwnsOne sem IsRequired:
- Todas colunas NOT NULL com default (
0/"") E todas colunas nullable comNULL→ materializanull - Qualquer coluna nullable com valor não-null → instancia o owned type
- Atenção: heurística mudou entre EF Core 6/7/8 — confirmar versão do EF Core no projeto
| Caso | UserId | Total | RescuedBonus | IdsBonus | Ticket | cart.CrmBonus | Guarda atual (!= null && !IsNullOrEmpty(IdsBonus)) |
|---|---|---|---|---|---|---|---|
| Pedido sem CRM (cliente sem telefone) | 0 | 0 | 0 | NULL | NULL | prov. null | não passa — != null retorna false |
| Perfil CRM, sem resgate (cenário do usuário) | 27056298 | 499.80 | 0 | "0" (default do banco) | "CRM-..." | != null | sim — passa ("0" não é null nem vazio) |
| Perfil CRM, com resgate | 27056298 | 499.80 | 23.78 | "7444497" | "CRM-..." | != null | sim — passa |
Conclusão prática (revisada com análise de logs — 2026-05-29):
CrmBonusIdsBonustem default"0"no banco → a guarda doPosCaptureServicenão bloqueia pedidos sem resgatefinaliza_comprajá é chamado em todos os pedidos com perfil CRM, mesmo sem resgate, mas com:valor_bruto = cart.CrmBonus.Total = 0→ CRM não tem base para calcular bônus de cashbackids_bonus = "0"→ não é um ID de bônus real (comportamento do CRM desconhecido)
- O gap real identificado não é a guarda — é a fonte do
valor_bruto: deve-se usarcart.ItemsTotalem vez decart.CrmBonus.Total(que é0ou congelado) tanto no cenário com resgate (RF-3 da task-06) quanto no sem resgate
3.4 Investigação pendente para confirmar em runtime
- Validar comportamento de materialização:
- Habilitar logging EF (
LogTo) em ambiente de testes/homolog - Confirmar quando
cart.CrmBonusénull(cliente sem telefone) vs instanciado (perfil CRM, mesmo sem resgate) - A análise de logs já confirmou que
IdsBonus = "0"(default) para pedidos sem resgate — resta confirmarUserId,TotaleTicketnesse cenário
- Habilitar logging EF (
- Verificar versão do EF Core no
*.csprojdo projeto — heurística mudou entre 6/7/8 - Amostra de produção (query na tabela
Carts):SELECT CrmBonusUserId, CrmBonusTotal, CrmBonusIdsBonus, CrmBonusRescuedBonus FROM Carts WHERE ...- Confirmar distribuição real dos casos: quantos % têm
IdsBonus = "0"vsIdsBonus NULLvsIdsBonuscom valor real
- Inspecionar
CreatePaymentCommandHandler:- Como o objeto
CrmBonusé construído quandobonus_resgatado = 0? - O DTO de entrada do
createByZzAppenviauser_idmesmo sem resgate? - De onde vem o default
"0"deIdsBonus? Migration? Seed? EF Core convention paravarchar?
- Como o objeto
4. Caminhos técnicos possíveis (a discutir com PO + parceiro CRM)
Informação revisada (análise de logs, 2026-05-29)
Com a confirmação de que IdsBonus tem default "0" no banco e a guarda do PosCaptureService já permite a chamada de finaliza_compra em pedidos sem resgate, as opções abaixo consideram o estado real: finaliza_compra já é chamado, mas com valor_bruto = 0.
Opção 1 — Corrigir valor_bruto no finaliza_compra (única opção viável)
- Manter o fluxo atual —
finaliza_comprajá é chamado para todo pedido com perfil CRM - Única mudança:
PosCaptureServiceusarcart.ItemsTotalcomovalor_brutoem vez decart.CrmBonus.Total ids_bonus = "0"continuará sendo enviado (ou mudar para""/nullse parceiro preferir)bonus_resgatado = 0permanece- Vantagens: menor esforço, não quebra contrato, não adiciona chamada nova
- Pendência: validar com parceiro se
ids_bonus = "0"é tratado corretamente (gera bônus baseado novalor_bruto? ou rejeita?)
Escopo do impacto:
- Vendas Portal: passam a gerar bônus (corrige o gap)
- Vendas App: idempotente —
cart.ItemsTotal == CrmBonus.Totalquando setado corretamente na criação - Vendas com upsell: refletem o valor pós-upsell (alinha com RF-3)
- Correção: 1 linha no
PosCaptureService—valor_brutodecart.CrmBonus.Totalparacart.ItemsTotal
Opção 2 — Migrar para /bonus/execute (alternativa de modernização)
5. Definição de valor_bruto e combinação com upsell
5.1 Definição confirmada (PO)
valor_bruto a enviar ao CRM = Σ(item.Price) - Σ(desconto_aplicado_no_item)
Em termos de domínio:
- Sem frete (
ShipmentValue) - Sem desconto PIX (desconto de meio de pagamento, aplicado fora do ciclo de
cart.Total) - Sem desconto CRM (
RescuedBonus— resgate de bônus, valor já deduzido no checkout) - Apenas o líquido dos itens depois de descontos aplicados nos próprios itens (
DiscountValue,DiscountType,DiscountMarkdown)
Esta definição vale para AMBOS os cenários: com resgate e sem resgate.
5.2 Candidato no CartModel: cart.ItemsTotal
// CartModel.CalculateTotal() (pesquisa-desconto-pix-crm-bonus.md:80)
ItemsTotal = CartItems.Sum(p => p.CalculateTotal());
Se CartItem.CalculateTotal() retorna UnitPrice * Quantity já com desconto de item aplicado:
valor_bruto (definição PO) == cart.ItemsTotal
A confirmar via análise de CartItemModel / CartItemMapping:
CartItem.CalculateTotal()aplicaDiscountValue/DiscountType?DiscountMarkdown?ItemsTotalé recalculado e persistido após upsell? (ADR-007 trataPayment.Total; checar seItemsTotaltambém é atualizado)- Inclui ou exclui desconto de item via CRM? (esperado: exclui, porque
RescuedBonusé deduzido apenas emCalculateTotal()doCartModel, não emCartItem.CalculateTotal())
5.3 NÃO usar PdvIntegrationValue como valor_bruto
Conforme esclarecimento do usuário e análise do CartMapping:
- Comentário do mapping: "Campo utilizado pelo payment para integrar com PDVs. Campo calculado na Entidade (ItensTotal - CRmBonus)"
- Definição confirmada:
valor que o cliente vai pagar - frete - Divergência aparente: comentário fala só de
ItemsTotal - CRmBonus; definição do usuário sugere que também deduz PIX. Pendência: confirmar qual é o cálculo real em runtime. - Em qualquer caso:
PdvIntegrationValuededuz o resgate (RescuedBonus) → não serve comovalor_bruto, que por definição deve ser antes de descontos de bônus.
5.4 Evolução de valores com upsell (cenário sem resgate)
Seja:
T0=ItemsTotalno momento da criação (sem resgate,B = 0)ΔItem= valor líquido do item de upsell (com desconto de item já aplicado)F=ShipmentValueP%=PixDiscountPercentageda loja
| Momento | ItemsTotal | Total | PdvIntegrationValue | valor_bruto correto p/ CRM |
|---|---|---|---|---|
| Criação | T0 | T0 + F | T0 | T0 |
| Após upsell | T0 + ΔItem | T0 + ΔItem + F | (recalculado?) | T0 + ΔItem |
| Captura PIX | T0 + ΔItem | T0 + ΔItem + F - PIX (via fila) | T0 + ΔItem - PIX? | T0 + ΔItem |
Conclusão: o valor_bruto a enviar ao CRM deve ser cart.ItemsTotal no momento da chamada (pós-upsell, pré-deduções de pagamento e bônus). Nem cart.Total (inclui frete e deduz resgate) nem cart.CrmBonus.Total (congelado) atendem à definição.
5.5 Impacto na RF-3 da task-06 (caso COM resgate)
A RF-3 atual da task-06 discute substituir cart.CrmBonus.Total (congelado na criação) por cart.Total no finaliza_compra quando há resgate + upsell.
Com a definição agora confirmada de valor_bruto (Σ Price - descontos de item):
- Nem
cart.Totalnemcart.CrmBonus.Totalsão corretos para o caso COM resgate:cart.Totalinclui frete e deduzRescuedBonuscart.CrmBonus.Totalé congelado e tem semântica ambígua
- A resposta correta para AMBOS os cenários (com e sem resgate) é
cart.ItemsTotalpós-upsell - A RF-3 precisa ser revista para considerar
cart.ItemsTotalcomo alternativa acart.Total
6. Perguntas para o PO
-
Fluxo esperado para pedido loja física SEM desconto CRM:
- O cliente DEVE receber bônus de cashback dessa compra? Sim, deve receber o bonus.
- Em qual momento do fluxo isso deve acontecer (após captura? agendado? imediato?)? No pos captura, como deveria ja estar ocorrendo
-
Base de cálculo do bônus
- Definição confirmada:
valor_bruto = Σ(item.Price) - Σ(desconto_aplicado_no_item) - Equivalente provável:
cart.ItemsTotal - Vale para ambos os cenários (com e sem resgate)
- Desconto PIX não deve ser deduzido —
valor_brutoé sempre antes de descontos de meio de pagamento
Sub-perguntas remanescentes (validação técnica, não PO): 2a. Confirmar que
CartItem.CalculateTotal()aplicaDiscountValue/DiscountType/DiscountMarkdowncorretamente 2b.ItemsTotalé recalculado e persistido após upsell? (ADR-007 trataPayment.Total; checarItemsTotal) 2c. No caso COM resgate:valor_brutocontinua sendoItemsTotal(sem deduzirRescuedBonus)? A semântica histórica de "bruto" no CRM é "antes do desconto de bônus" — confirmar alinhamento com a nova definição do PO - Definição confirmada:
-
Flag "Não gerar CRM Bônus para esta venda" (UI no zzapp):
- Hoje este flag está na UI e tem default
true(gerar). - Se o cliente não tem celular cadastrado, o flag some da UI — está correto que o cliente só ganha bônus se tiver perfil CRM.
- Hoje este flag está na UI e tem default
-
Escopo da correção (origem):
- Confirmado: vendas Portal hoje têm
valor_bruto = 0; vendas App funcionam corretamente - Escopo do produto: loja física via App e Portal (sem e-commerce)
- Correção proposta no
PosCaptureService(cart.ItemsTotalem vez decart.CrmBonus.Total) é genérica — afeta todas as origens - Para vendas App é idempotente (já enviam valor correto via
CrmBonus.Total) - PO confirma: aplicar correção genérica, sem condicional por origem. Devemos validar o valor a ser usado?
- Confirmado: vendas Portal hoje têm
-
SLA de geração:
- Há expectativa de prazo entre captura do pagamento e disponibilidade do bônus?
- Se
finaliza_compracombonus_resgatado=0falhar, precisa de retry? Ou aceita perder a geração (comportamento atual —FinishCrmBonusAsyncnão lança exceção)?
-
cart.CrmBonus.RescuedBonus = 0masGenerateCrmBonus = true:- Este cenário deve gerar bônus? (resposta esperada: sim — é exatamente o cenário em discussão)
- E se
RescuedBonus > 0EGenerateCrmBonus = false? Apenas efetiva resgate e não gera? Ou o flag afeta algo diferente?
7. Perguntas para o parceiro CRM
-
/finaliza_compracombonus_resgatado = 0eids_bonusvazio:- É aceito pelo endpoint?
- Gera bônus de cashback normalmente? Qual a regra de cálculo?
- O
ticketprecisa ter sido previamente registrado viabaixa_bonus? Ou aceitaticketinédito?
-
/bonus/executeapenas comgenerated_bonus(semredeemed_bonus):- Gera bônus corretamente? Qual contrato mínimo exigido?
generated_bonus.expiration_date_start/endegenerated_bonus.valuesão obrigatórios ou calculados automaticamente pelo CRM com base nogross_value?
-
Regra de cálculo do bônus gerado:
- Há um campo "campanha_id" no schema? Como ele se relaciona com o
campo
campanhaque o checkout envia hoje (ver pergunta 7)?
- Há um campo "campanha_id" no schema? Como ele se relaciona com o
campo
-
Validação entre chamadas:
- O CRM valida se o
valor_brutodofinaliza_compracoincide com o dobaixa_bonus? - Se forem diferentes (ex: upsell alterou o valor entre a criação e a captura), rejeita? Aceita silenciosamente?
- O CRM valida se o
-
Rate limit / idempotência:
finaliza_compraé idempotente? Se for chamado 2x para o mesmoticket, o que acontece?- Há rate limit por loja ou por
ticket?
-
Endpoint
/bonus/executevsfinaliza_compra:- Qual a recomendação do parceiro para o cenário "gerar bônus para uma venda que não usou bônus"?
/bonus/executeé o substituto moderno do fluxobaixa_bonus+finaliza_compra? Ou são complementares?
-
Campo
campanhanofinaliza_compra(achado 2026-05-29):- O campo
campanhanão está documentado no schema oficial dofinaliza_compra(pesquisa-crm-bonus-documentacao-parceiro.md:95-162). - O checkout envia
"campanha": "0"quando o flag internoGenerateCrmBonus = true, e"campanha": "-1"quandofalse(ver seção 2.7 para detalhes da transformação). - Perguntas:
- O CRM lê/processa esse campo? Qual o efeito de cada valor?
- É uma definição legada (antes de se tornar
campanha_id)? Foi descontinuada? Foi substituída por outro mecanismo? - Existe documentação interna do parceiro sobre os valores
"-1"e"0"neste campo? - Devemos deixar de enviar este campo ou enviar com outro valor?
- O campo
8. Próximos passos
- Conversa com PO (perguntas seção 6) → decisão de escopo e regras de negócio
- Conversa com parceiro CRM (perguntas seção 7) → viabilidade técnica e contrato
- Investigação técnica (a executar antes de implementar):
a. RESPONDIDO PELO USUÁRIO (2026-05-29): CRM não gera bônus com
valor_bruto = 0. Problema afeta vendas com origem Portal. Vendas App funcionam corretamente porque enviam objetoCrmBonuscomtotalpreenchido na criação. b. Validar materialização doCrmBonusem runtime (seção 3.4) — confirmar quando énullvs instanciado c. Confirmar versão do EF Core nocoezzion-service-checkout/*.csprojd. InspecionarCreatePaymentCommandHandler— comoCrmBonusé construído quandobonus_resgatado = 0e. InspecionarCartItemModeleCartItem.CalculateTotal()— confirmar queItemsTotalatende à definição devalor_brutof. Confirmar seItemsTotalé recalculado e persistido após upsell (ADR-007 fala dePayment.Total; checarItemsTotal) g. Corrigir bug cosmético doRescuedBonusmapeado 2x emCartMapping(janela de housekeeping — não bloqueia) h. Revisar RF-3 da task-06 — com a definição confirmada devalor_bruto,cart.ItemsTotalpode substituircart.CrmBonus.Totalno caso COM resgate i. ITEM (a) CONFIRMADO: CRM não gera bônus comvalor_bruto = 0→ implementar correção doPosCaptureServicepara usarcart.ItemsTotal— mudança mínima, sem nova chamada, sem quebra de contrato. Ver Opção 1 na seção 4. j. Investigar handler de criação de pedidos com origem Portal:- Localizar comando/handler responsável por pedidos Portal (provavelmente diferente
do
CreatePaymentCommandHandlerusado pelo App) - Identificar por que
CrmBonus.Totalnão é populado - Avaliar se a divergência afeta outras features (cashback, perfil CRM)
k. Confirmar amostra em produção: distribuição de
CrmBonus.TotalporOrigin(esperado — Portal:0; App: valor real do carrinho) l. Investigar histórico do campocampanha(achado 2026-05-29): git blameemCrmBonutDTO.CreateFromCrmBonuspara identificar autor/data da transformação(campanha == 0) ? "-1" : "0"- Buscar PRs/commits que mencionem "campanha", "useCRM" no checkout
- Consultar Wiki/Confluence por documentação interna
- Confirmar com parceiro CRM (ver seção 7, pergunta 7) se o campo é processado e qual a semântica esperada
- Localizar comando/handler responsável por pedidos Portal (provavelmente diferente
do
- Se decisão for implementar: criar novas RFs + ADR na task-06
9. Referências cruzadas
| Fonte | Trecho relevante |
|---|---|
Core.OrgDB.Data.Mappings.CartMapping | OwnsOne(CrmBonus) — sem IsRequired(); GenerateCrmBonus no root com default true; colunas CrmBonus* sem IsRequired(); bug cosmético RescuedBonus duplicado; PdvIntegrationValue com comentário ItensTotal - CRmBonus |
CrmBonutDTO.CreateFromCrmBonus | Transformação invertida GenerateCrmBonus → campanha (true → "0", false → "-1"); campo não documentado pelo parceiro (ver seção 2.7) |
CartModel.CalculateTotal() | ItemsTotal = CartItems.Sum(p => p.CalculateTotal()); Total = ItemsTotal + ShipmentValue - RescuedBonus |
CartItem.CalculateTotal() | A inspecionar — confirma se aplica DiscountValue/DiscountType/DiscountMarkdown |
| task-193232-checkout-crm-requisitos.md RF-3 | Divergência cart.Total vs cart.CrmBonus.Total no finaliza_compra — precisa ser revista à luz da definição confirmada de valor_bruto (seção 5.5) |
| pesquisa-crm-bonus.md:48-67 | Callers de baixa_bonus, finaliza_compra, cancelarBonusEcom |
| pesquisa-crm-bonus.md:134 | IdsBonus como condição extra no Finish |
| pesquisa-crm-bonus-analise-codigo.md:113,131,172 | Guarda PosCaptureService + DTO completo + condição DebitCrmBonusAsync |
| pesquisa-crm-bonus-documentacao-parceiro.md:95-162 | Schema finaliza_compra (resposta com bonus_id) |
| pesquisa-crm-bonus-documentacao-parceiro.md:205-267 | Schema /bonus/execute (unificado) |
| pesquisa-crm-bonus-documentacao-parceiro.md:275-280 | Fluxo oficial: "bonus_resgatado > 0? SIM → baixa_bonus; NÃO → nenhuma ação CRM" |
| pesquisa-crm-bonus-docs-internos.md:59 | tokenCrm null → objeto crmBonus deve ser null |
| pesquisa-crm-bonus-docs-internos.md:80,84-90 | Gatilhos documentados para finaliza_compra |
| pesquisa-desconto-pix-crm-bonus.md:80-91 | CalculateTotal() — desconto PIX fora do ciclo, RescuedBonus deduzido |
| pesquisa-desconto-pix-crm-bonus.md:122-152 | Tabela de evolução de valores com upsell + PIX |
| task-193232-checkout-crm.md:45 | CrmBonus como record OwnsOne |
| cart_dto.dart:41,74,110 | generateCrmBonus default true, regra de habilitação |
| cart_controller.dart:151,177 | shouldNotGenerateCrmBonus, toggleGenerateCrmBonus |
| check_sell_crm_values.dart:67 | Checkbox "Não gerar CRM Bônus" |