Skip to main content

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 do CrmBonus populado corretamente. Considerar pesquisa separada no domínio Upsell para mapear o caminho completo de criação via Portal. Arquivos relacionados:


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 com valor_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 CrmBonus com total preenchido na criação do carrinho → CrmBonus.Total é persistido com o valor real → finaliza_compra envia valor_bruto correto → 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 CrmBonusIdsBonus tem default "0" (string), não NULL/""
  • !string.IsNullOrEmpty("0")true → a guarda não bloqueia pedidos sem resgate
  • Conclusão revisada: finaliza_compra já é chamado mesmo sem resgate, porém com valores default:
    • valor_bruto = cart.CrmBonus.Total = 0
    • ids_bonus = "0"
    • bonus_resgatado = cart.CrmBonus.RescuedBonus = 0

2.2 Guarda do CreatePaymentCommandHandler

  • Condição: cart.CrmBonus != null + internamente bonus_resgatado > 0
  • Fonte: pesquisa-crm-bonus.md:57, pesquisa-crm-bonus-analise-codigo.md:91, 171
  • Implicação: sem resgate → baixa_bonus nunca é chamado → CRM não recebe baixa_bonus para registrar o ticket antes do finaliza_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 recebe 0 como base de cálculo → provavelmente gera bônus de 0 ou recusa a requisição
  • ids_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 como valor_bruto
  • campanha = "0" — deriva de GenerateCrmBonus = true apó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 ao CrmBonutDTO.CreateFromCrmBonus via Convert.ToInt32(cart.GenerateCrmBonus) (parâmetro nomeado campanha). 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 como IsRequired(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 PosCaptureService não bloqueia (IdsBonus = "0" permite a passagem), o flag GenerateCrmBonus já chega ao CRM como campo campanha (após transformação invertida — ver 2.7) em todos os pedidos. O gap não está no flag — está no valor_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):

OrigemCrmBonus na criaçãoCrmBonus.Total persistidovalor_bruto enviadoCRM gera bônus?
AppObjeto completo com totalValor real do carrinhoRealSIM
PortalAusente/vazio00NÃ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 crmBonus no 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 GenerateCrmBonuscampanha 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.GenerateCrmBonusConvert.ToInt32useCRM enviadoCampo JSON campanha
true (gerar bônus)1"0""campanha": "0"
false (não gerar)0"-1""campanha": "-1"

Problemas:

  1. Campo campanha não está na documentação oficial do parceiro (pesquisa-crm-bonus-documentacao-parceiro.md:95-162 — schema do finaliza_compra). Não há referência aos valores "0"/"-1" nem ao significado deste campo.

  2. Inversão e mudança de tipo: boolint (0/1)string ("-1"/"0"). A inversão (true vira "0", false vira "-1") é contraintuitiva.

  3. Nome do parâmetro enganoso: no CreateFromCrmBonus o parâmetro campanha sugere "ID de campanha", mas o valor recebido é Convert.ToInt32(cart.GenerateCrmBonus) — um booleano. Internamente, é usado como flag booleana invertida (useCRM).

  4. Origem desconhecida: sem git blame nem 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 campanha significa
  • git blame em CrmBonutDTO.CreateFromCrmBonus para 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 no OwnsOne nem em nenhuma Property interna
  • CrmBonusTotal, CrmBonusRescuedBonus: decimal(18,2) NOT NULL (value type)
  • CrmBonusUserId: tipo padrão (provavelmente int NOT NULL)
  • CrmBonusIdsBonus: varchar(150) — sem IsRequired() no mapeamento, mas valor default no banco é "0" (confirmado por análise de logs)
  • CrmBonusTicket: tipo padrão (provavelmente nvarchar(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.CrmBonus ser null ou 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 com NULL → materializa null
  • 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
CasoUserIdTotalRescuedBonusIdsBonusTicketcart.CrmBonusGuarda atual (!= null && !IsNullOrEmpty(IdsBonus))
Pedido sem CRM (cliente sem telefone)000NULLNULLprov. nullnão passa — != null retorna false
Perfil CRM, sem resgate (cenário do usuário)27056298499.800"0" (default do banco)"CRM-..."!= nullsim — passa ("0" não é null nem vazio)
Perfil CRM, com resgate27056298499.8023.78"7444497""CRM-..."!= nullsim — passa

Conclusão prática (revisada com análise de logs — 2026-05-29):

  • CrmBonusIdsBonus tem default "0" no banco → a guarda do PosCaptureService não bloqueia pedidos sem resgate
  • finaliza_compra já é 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 cashback
    • ids_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 usar cart.ItemsTotal em vez de cart.CrmBonus.Total (que é 0 ou 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

  1. 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 confirmar UserId, Total e Ticket nesse cenário
  2. Verificar versão do EF Core no *.csproj do projeto — heurística mudou entre 6/7/8
  3. 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" vs IdsBonus NULL vs IdsBonus com valor real
  4. Inspecionar CreatePaymentCommandHandler:
    • Como o objeto CrmBonus é construído quando bonus_resgatado = 0?
    • O DTO de entrada do createByZzApp envia user_id mesmo sem resgate?
    • De onde vem o default "0" de IdsBonus? Migration? Seed? EF Core convention para varchar?

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_compra já é chamado para todo pedido com perfil CRM
  • Única mudança: PosCaptureService usar cart.ItemsTotal como valor_bruto em vez de cart.CrmBonus.Total
  • ids_bonus = "0" continuará sendo enviado (ou mudar para ""/null se parceiro preferir)
  • bonus_resgatado = 0 permanece
  • 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 no valor_bruto? ou rejeita?)

Escopo do impacto:

  • Vendas Portal: passam a gerar bônus (corrige o gap)
  • Vendas App: idempotente — cart.ItemsTotal == CrmBonus.Total quando setado corretamente na criação
  • Vendas com upsell: refletem o valor pós-upsell (alinha com RF-3)
  • Correção: 1 linha no PosCaptureServicevalor_bruto de cart.CrmBonus.Total para cart.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() aplica DiscountValue/DiscountType? DiscountMarkdown?
  • ItemsTotal é recalculado e persistido após upsell? (ADR-007 trata Payment.Total; checar se ItemsTotal também é atualizado)
  • Inclui ou exclui desconto de item via CRM? (esperado: exclui, porque RescuedBonus é deduzido apenas em CalculateTotal() do CartModel, não em CartItem.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: PdvIntegrationValue deduz o resgate (RescuedBonus) → não serve como valor_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 = ItemsTotal no momento da criação (sem resgate, B = 0)
  • ΔItem = valor líquido do item de upsell (com desconto de item já aplicado)
  • F = ShipmentValue
  • P% = PixDiscountPercentage da loja
MomentoItemsTotalTotalPdvIntegrationValuevalor_bruto correto p/ CRM
CriaçãoT0T0 + FT0T0
Após upsellT0 + ΔItemT0 + ΔItem + F(recalculado?)T0 + ΔItem
Captura PIXT0 + ΔItemT0 + Δ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.Total nem cart.CrmBonus.Total são corretos para o caso COM resgate:
    • cart.Total inclui frete e deduz RescuedBonus
    • cart.CrmBonus.Total é congelado e tem semântica ambígua
  • A resposta correta para AMBOS os cenários (com e sem resgate) é cart.ItemsTotal pós-upsell
  • A RF-3 precisa ser revista para considerar cart.ItemsTotal como alternativa a cart.Total

6. Perguntas para o PO

  1. 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
  2. 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 deduzidovalor_bruto é sempre antes de descontos de meio de pagamento

    Sub-perguntas remanescentes (validação técnica, não PO): 2a. Confirmar que CartItem.CalculateTotal() aplica DiscountValue/DiscountType/DiscountMarkdown corretamente 2b. ItemsTotal é recalculado e persistido após upsell? (ADR-007 trata Payment.Total; checar ItemsTotal) 2c. No caso COM resgate: valor_bruto continua sendo ItemsTotal (sem deduzir RescuedBonus)? A semântica histórica de "bruto" no CRM é "antes do desconto de bônus" — confirmar alinhamento com a nova definição do PO

  3. 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.
  4. 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.ItemsTotal em vez de cart.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?
  5. SLA de geração:

    • Há expectativa de prazo entre captura do pagamento e disponibilidade do bônus?
    • Se finaliza_compra com bonus_resgatado=0 falhar, precisa de retry? Ou aceita perder a geração (comportamento atual — FinishCrmBonusAsync não lança exceção)?
  6. cart.CrmBonus.RescuedBonus = 0 mas GenerateCrmBonus = true:

    • Este cenário deve gerar bônus? (resposta esperada: sim — é exatamente o cenário em discussão)
    • E se RescuedBonus > 0 E GenerateCrmBonus = false? Apenas efetiva resgate e não gera? Ou o flag afeta algo diferente?

7. Perguntas para o parceiro CRM

  1. /finaliza_compra com bonus_resgatado = 0 e ids_bonus vazio:

    • É aceito pelo endpoint?
    • Gera bônus de cashback normalmente? Qual a regra de cálculo?
    • O ticket precisa ter sido previamente registrado via baixa_bonus? Ou aceita ticket inédito?
  2. /bonus/execute apenas com generated_bonus (sem redeemed_bonus):

    • Gera bônus corretamente? Qual contrato mínimo exigido?
    • generated_bonus.expiration_date_start/end e generated_bonus.value são obrigatórios ou calculados automaticamente pelo CRM com base no gross_value?
  3. Regra de cálculo do bônus gerado:

    • Há um campo "campanha_id" no schema? Como ele se relaciona com o campo campanha que o checkout envia hoje (ver pergunta 7)?
  4. Validação entre chamadas:

    • O CRM valida se o valor_bruto do finaliza_compra coincide com o do baixa_bonus?
    • Se forem diferentes (ex: upsell alterou o valor entre a criação e a captura), rejeita? Aceita silenciosamente?
  5. Rate limit / idempotência:

    • finaliza_compra é idempotente? Se for chamado 2x para o mesmo ticket, o que acontece?
    • Há rate limit por loja ou por ticket?
  6. Endpoint /bonus/execute vs finaliza_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 fluxo baixa_bonus + finaliza_compra? Ou são complementares?
  7. Campo campanha no finaliza_compra (achado 2026-05-29):

    • O campo campanha não está documentado no schema oficial do finaliza_compra (pesquisa-crm-bonus-documentacao-parceiro.md:95-162).
    • O checkout envia "campanha": "0" quando o flag interno GenerateCrmBonus = true, e "campanha": "-1" quando false (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?

8. Próximos passos

  1. Conversa com PO (perguntas seção 6) → decisão de escopo e regras de negócio
  2. Conversa com parceiro CRM (perguntas seção 7) → viabilidade técnica e contrato
  3. 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 objeto CrmBonus com total preenchido na criação. b. Validar materialização do CrmBonus em runtime (seção 3.4) — confirmar quando é null vs instanciado c. Confirmar versão do EF Core no coezzion-service-checkout/*.csproj d. Inspecionar CreatePaymentCommandHandler — como CrmBonus é construído quando bonus_resgatado = 0 e. Inspecionar CartItemModel e CartItem.CalculateTotal() — confirmar que ItemsTotal atende à definição de valor_bruto f. Confirmar se ItemsTotal é recalculado e persistido após upsell (ADR-007 fala de Payment.Total; checar ItemsTotal) g. Corrigir bug cosmético do RescuedBonus mapeado 2x em CartMapping (janela de housekeeping — não bloqueia) h. Revisar RF-3 da task-06 — com a definição confirmada de valor_bruto, cart.ItemsTotal pode substituir cart.CrmBonus.Total no caso COM resgate i. ITEM (a) CONFIRMADO: CRM não gera bônus com valor_bruto = 0 → implementar correção do PosCaptureService para usar cart.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 CreatePaymentCommandHandler usado pelo App)
    • Identificar por que CrmBonus.Total não é populado
    • Avaliar se a divergência afeta outras features (cashback, perfil CRM) k. Confirmar amostra em produção: distribuição de CrmBonus.Total por Origin (esperado — Portal: 0; App: valor real do carrinho) l. Investigar histórico do campo campanha (achado 2026-05-29):
    • git blame em CrmBonutDTO.CreateFromCrmBonus para 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
  4. Se decisão for implementar: criar novas RFs + ADR na task-06

9. Referências cruzadas

FonteTrecho relevante
Core.OrgDB.Data.Mappings.CartMappingOwnsOne(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.CreateFromCrmBonusTransformação invertida GenerateCrmBonuscampanha (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-3Divergência cart.Total vs cart.CrmBonus.Total no finaliza_compraprecisa ser revista à luz da definição confirmada de valor_bruto (seção 5.5)
pesquisa-crm-bonus.md:48-67Callers de baixa_bonus, finaliza_compra, cancelarBonusEcom
pesquisa-crm-bonus.md:134IdsBonus como condição extra no Finish
pesquisa-crm-bonus-analise-codigo.md:113,131,172Guarda PosCaptureService + DTO completo + condição DebitCrmBonusAsync
pesquisa-crm-bonus-documentacao-parceiro.md:95-162Schema finaliza_compra (resposta com bonus_id)
pesquisa-crm-bonus-documentacao-parceiro.md:205-267Schema /bonus/execute (unificado)
pesquisa-crm-bonus-documentacao-parceiro.md:275-280Fluxo oficial: "bonus_resgatado > 0? SIM → baixa_bonus; NÃO → nenhuma ação CRM"
pesquisa-crm-bonus-docs-internos.md:59tokenCrm null → objeto crmBonus deve ser null
pesquisa-crm-bonus-docs-internos.md:80,84-90Gatilhos documentados para finaliza_compra
pesquisa-desconto-pix-crm-bonus.md:80-91CalculateTotal() — desconto PIX fora do ciclo, RescuedBonus deduzido
pesquisa-desconto-pix-crm-bonus.md:122-152Tabela de evolução de valores com upsell + PIX
task-193232-checkout-crm.md:45CrmBonus como record OwnsOne
cart_dto.dart:41,74,110generateCrmBonus default true, regra de habilitação
cart_controller.dart:151,177shouldNotGenerateCrmBonus, toggleGenerateCrmBonus
check_sell_crm_values.dart:67Checkbox "Não gerar CRM Bônus"