DocumentaçãoDocumentationDocumentaciónOne Conecta
ÍndiceIndexÍndice
Baixar .mdDownload .mdBajar .md
Feature (ecossistema) · PromoçõesFeature (ecosystem) · PromotionsFeature (ecosistema) · Promociones

PromoçõesPromotionsPromociones

Não é uma tela — é um ecossistema: um motor de avaliação puro (elegibilidade, alvo, recompensa, conflito, escalonamento de níveis) que roda dentro do carrinho, mais os pontos onde a promoção aparece (vitrine de produtos, detalhe da promoção, revisão do carrinho) e a marcação na transação do pedido (prefixo Promo_). Este doc cobre o motor inteiro e todas as áreas de atuação. Not a screen — an ecosystem: a pure evaluation engine (eligibility, target, reward, conflict, level escalation) that runs inside the cart, plus the places a promotion surfaces (product showcase, promotion detail, cart review) and the transaction marking on the order (the Promo_ prefix). This doc covers the whole engine and every area of action. No es una pantalla — es un ecosistema: un motor de evaluación puro (elegibilidad, objetivo, recompensa, conflicto, escalonamiento de niveles) que corre dentro del carrito, más los lugares donde aparece la promoción (vitrina de productos, detalle de la promoción, revisión del carrito) y la marca en la transacción del pedido (prefijo Promo_). Este doc cubre todo el motor y todas las áreas de actuación.

PúblicoAudiencePúblico
Representante · QA · Suporte · DevRep · QA · Support · DevRepresentante · QA · Soporte · Dev
Onde ficaWhereDónde
Vitrine de produtos · Detalhe da promoção · CarrinhoProduct showcase · Promotion detail · CartVitrina · Detalle de la promoción · Carrito
RelacionadoRelatedRelacionado
AtualizadoUpdatedActualizado
22/07/20262026-07-22
Disponível emAvailable inDisponible en BR CL ZA
01

O que é e para que serveWhat it is and what it's forQué es y para qué sirve

Uma promoção é um acordo comercial: "se o varejo atingir um alvo (comprar tanto de tal produto, alcançar um valor de pedido, etc.), ele ganha uma recompensa (bonificação de produto, desconto, dias de crédito extras)". O ConectaRep não tem uma "tela de promoções" única — a promoção vive espalhada pelo fluxo de criação de pedido, calculada em tempo real por um motor conforme o representante monta o carrinho. A promotion is a commercial deal: "if the retail hits a target (buys so much of a product, reaches an order value, etc.), it earns a reward (free product, discount, extra credit days)". ConectaRep has no single "promotions screen" — a promotion lives spread across the order-creation flow, computed in real time by an engine as the rep builds the cart. Una promoción es un acuerdo comercial: "si el punto de venta alcanza un objetivo (compra tanto de un producto, llega a un valor de pedido, etc.), gana una recompensa (producto bonificado, descuento, días de crédito extra)". ConectaRep no tiene una "pantalla de promociones" única — la promoción vive repartida por el flujo de creación de pedido, calculada en tiempo real por un motor mientras el rep arma el carrito.

AlvoTargetObjetivo

A condição a cumprir: produto, grupo, categoria, valor ou SOQ do pedido. Pode ter vários níveis (quanto mais compra, maior o nível).The condition to meet: product, group, category, order value or SOQ. It can have several levels (buy more, reach a higher level).La condición a cumplir: producto, grupo, categoría, valor o SOQ del pedido. Puede tener varios niveles.

RecompensaRewardRecompensa

O que o varejo ganha ao atingir o alvo: produto grátis (bonificação/FoC), desconto no pedido ou no produto, ou dias de crédito.What the retail earns on hitting the target: free product (FoC), order or product discount, or credit days.Lo que gana al alcanzar el objetivo: producto gratis (FoC), descuento en pedido o producto, o días de crédito.

MotorEngineMotor

A cada mudança no carrinho, o motor recalcula: é elegível? o alvo foi atingido? qual recompensa aplicar? Sem tocar no backend.On every cart change the engine recomputes: eligible? target hit? which reward to apply? All offline.Ante cada cambio del carrito el motor recalcula: ¿elegible? ¿objetivo alcanzado? ¿qué recompensa aplicar? Sin tocar el backend.

Regular vs SpotRegular vs SpotRegular vs Spot dois tipos: a promoção regular (com níveis, alvos e recompensas avaliados pelo motor) e a spot promotion (um brinde pré-definido por conta, sem avaliação — só ligar/desligar). Spot só existe no Brasil (spotPromotionsEnabled). There are two types: the regular promotion (levels, targets and rewards evaluated by the engine) and the spot promotion (a pre-defined per-account giveaway, no evaluation — just on/off). Spot exists only in Brazil (spotPromotionsEnabled). Hay dos tipos: la promoción regular (niveles, objetivos y recompensas evaluados por el motor) y la spot promotion (un obsequio predefinido por cuenta, sin evaluación — solo activar/desactivar). Spot solo existe en Brasil (spotPromotionsEnabled).

02

Onde a promoção apareceWhere the promotion shows upDónde aparece la promoción

A promoção não tem um acesso único; ela surge em quatro pontos do fluxo de pedido:A promotion has no single entry point; it surfaces at four points of the order flow:La promoción no tiene un acceso único; aparece en cuatro puntos del flujo de pedido:

  1. Aba Promoções da vitrineShowcase Promotions tabPestaña Promociones de la vitrinaNa vitrine de produtos (fluxo de novo pedido) há uma aba Promoções listando todas as promoções da conta, com um botão "acessar promoção".In the product showcase (new-order flow) a Promotions tab lists every promotion for the account, with an "access promotion" button.En la vitrina de productos (flujo de nuevo pedido) hay una pestaña Promociones que lista todas las promociones de la cuenta, con un botón "acceder a la promoción".
  2. Detalhe da promoçãoPromotion detailDetalle de la promociónUma tela própria (PromotionDetailPage) com título, regras, produtos de alvo, os níveis e as recompensas — tudo avaliado ao vivo contra o carrinho atual.Its own screen (PromotionDetailPage) with title, rules, target products, the levels and rewards — all evaluated live against the current cart.Una pantalla propia (PromotionDetailPage) con título, reglas, productos de objetivo, los niveles y las recompensas — todo evaluado en vivo contra el carrito actual.
  3. Card do produtoProduct cardTarjeta del productoUm produto que participa de promoções (promotionsForProduct) sinaliza isso na vitrine.A product taking part in promotions (promotionsForProduct) flags that in the showcase.Un producto que participa en promociones (promotionsForProduct) lo señala en la vitrina.
  4. Revisão do carrinho e pedidoCart review & orderRevisión del carrito y pedidoAs bonificações concedidas aparecem na seção "Bonificações" da revisão; ao enviar, o pedido é marcado com o prefixo Promo_ na transação.Granted free-of-charge lines show in the "Free of charge" section of the review; on submit, the order transaction is marked with the Promo_ prefix.Las bonificaciones concedidas aparecen en la sección "Bonificaciones" de la revisión; al enviar, la transacción del pedido se marca con el prefijo Promo_.
03

O ecossistema em uma olhadaThe ecosystem at a glanceEl ecosistema de un vistazo

Uma promoção regular tem uma estrutura fixa. É útil entendê-la para ler o detalhe da promoção:A regular promotion has a fixed structure. Understanding it helps you read the promotion detail:Una promoción regular tiene una estructura fija. Entenderla ayuda a leer el detalle:

PromoçãoPromotionPromoción
Título, descrição, regulamento, instruções ao representante, período (início/fim) e o tipo (ON_ORDER, BY_PERIOD, NUMBER_OF_ORDERS).Title, description, regulation, rep instructions, period (start/end) and the type (ON_ORDER, BY_PERIOD, NUMBER_OF_ORDERS).Título, descripción, reglamento, instrucciones al rep, período (inicio/fin) y el tipo.
NíveisLevelsNiveles
Uma promoção pode ter vários níveis; cada nível tem sua lista de alvos e de recompensas. O motor resolve o maior nível atingido (não soma níveis).A promotion can have several levels; each level has its own targets and rewards. The engine resolves the highest level reached (levels don't stack).Una promoción puede tener varios niveles; cada nivel tiene sus objetivos y recompensas. El motor resuelve el nivel más alto alcanzado (no suma niveles).
Alvo / RecompensaTarget / RewardObjetivo / Recompensa
Cada um é uma lista de detalhes combinados por operador (AND/OR). O detalhe aponta a variável (produto, grupo, valor, SOQ…) e o valor a atingir ou a conceder.Each is a list of details combined by an operator (AND/OR). The detail points to the variable (product, group, value, SOQ…) and the amount to reach or grant.Cada uno es una lista de detalles combinados por operador (AND/OR). El detalle apunta a la variable y al valor a alcanzar o conceder.
Dados por contaPer-account dataDatos por cuenta
Cada conta tem seu bloco: histórico de execução, frequência (dias da semana / limite) e mapeamento customizado. É o que decide se a promoção está elegível para aquele varejo.Each account carries its block: execution history, frequency (weekdays / limit) and custom mapping. It's what decides whether the promotion is eligible for that retail.Cada cuenta lleva su bloque: historial de ejecución, frecuencia (días de la semana / límite) y mapeo personalizado. Decide si la promoción es elegible para ese punto de venta.
Spot promotionSpot promotionSpot promotion
Estrutura mais simples (BR): título, conta, lista de produtos de brinde e o pedido em que foi aplicada. Sem níveis nem motor de avaliação.A simpler structure (BR): title, account, a list of giveaway products and the order it was applied on. No levels, no evaluation engine.Estructura más simple (BR): título, cuenta, lista de productos de obsequio y el pedido donde se aplicó. Sin niveles ni motor.
04

Estados de uma promoçãoPromotion statesEstados de una promoción

O motor classifica cada promoção em relação ao carrinho atual. Os principais estados que o representante percebe:The engine classifies each promotion against the current cart. The main states the rep perceives:El motor clasifica cada promoción contra el carrito actual. Los principales estados que el rep percibe:

Alvo atingidoTarget reachedObjetivo alcanzado Em progressoIn progressEn progreso InelegívelIneligibleInelegible Não aplicadaNot appliedNo aplicada
Em progresso / AtingidoIn progress / ReachedEn progreso / Alcanzado
Cada linha de alvo mostra o quanto falta; ao cruzar o limite, o nível é "atingido" e a recompensa daquele nível fica disponível.Each target line shows how much is missing; on crossing the threshold the level is "reached" and that level's reward becomes available.Cada línea de objetivo muestra cuánto falta; al cruzar el umbral el nivel queda "alcanzado" y su recompensa disponible.
Elegível vs InelegívelEligible vs IneligibleElegible vs Inelegible
A elegibilidade é uma cascata de 9 verificações (produto disponível, recompensa disponível, inadimplência, frequência, status, limite de crédito, período, teto de recompensa, teto de desconto). Se qualquer uma falha na criação de pedido, a promoção não é aplicada ao pedido.Eligibility is a 9-check cascade (products available, reward available, overdue, frequency, status, credit limit, date range, max reward, discount cap). If any fails during order creation, the promotion isn't applied to the order.La elegibilidad es una cascada de 9 verificaciones. Si alguna falla en la creación de pedido, la promoción no se aplica al pedido.
Aplicada / Não aplicadaApplied / Not appliedAplicada / No aplicada
O representante pode ligar/desligar a promoção no carrinho. Só as aplicadas e com nível atingido entram no pedido.The rep can toggle the promotion on/off in the cart. Only applied promotions with a reached level enter the order.El rep puede activar/desactivar la promoción en el carrito. Solo las aplicadas con nivel alcanzado entran en el pedido.

Exibição ≠ elegibilidadeDisplay ≠ eligibilityExhibición ≠ elegibilidad A lista e o detalhe da promoção mostram todas as promoções da conta — não são filtrados por elegibilidade. A elegibilidade só decide o que entra no pedido enviado. Um alvo pode aparecer "verde" enquanto a recompensa está inelegível. The promotion list and detail show all of the account's promotions — they are not filtered by eligibility. Eligibility only decides what enters the submitted order. A target may look "green" while the reward is ineligible. La lista y el detalle muestran todas las promociones de la cuenta — no se filtran por elegibilidad. La elegibilidad solo decide qué entra en el pedido enviado.

05

Ações do representanteRep actionsAcciones del representante

Ver a promoçãoView the promotionVer la promoción
Abrir o detalhe para entender alvo, níveis e recompensa, e acompanhar o progresso conforme adiciona produtos.Open the detail to understand target, levels and reward, and track progress as products are added.Abrir el detalle para entender objetivo, niveles y recompensa, y seguir el progreso al agregar productos.
Adicionar produtosAdd productsAgregar productos
Comprar os produtos de alvo aproxima do nível; o motor recalcula a cada mudança e revela a recompensa quando o alvo é cruzado.Buying target products moves toward the level; the engine recomputes on every change and reveals the reward when the target is crossed.Comprar productos de objetivo acerca al nivel; el motor recalcula en cada cambio y revela la recompensa al cruzar el objetivo.
Aplicar / RetirarApply / UnapplyAplicar / Quitar
Ligar ou desligar a promoção no carrinho (ApplyPromotionIntent/UnapplyPromotionIntent).Toggle the promotion in the cart (ApplyPromotionIntent/UnapplyPromotionIntent).Activar o desactivar la promoción en el carrito (ApplyPromotionIntent/UnapplyPromotionIntent).
Escolher a recompensaSelect the rewardElegir la recompensa
Quando um nível oferece recompensas alternativas (operador OR), o representante escolhe uma (SelectPromotionRewardIntent).When a level offers alternative rewards (OR operator), the rep picks one (SelectPromotionRewardIntent).Cuando un nivel ofrece recompensas alternativas (operador OR), el rep elige una (SelectPromotionRewardIntent).

Sem envio próprioNo own submitSin envío propio Promoções não têm transação própria: elas viajam dentro do pedido. Quando o pedido tem qualquer promoção aplicada, o envio usa o serviço com o prefixo Promo_ (ver o motor e Order detail). Promotions have no transaction of their own: they ride inside the order. When the order has any applied promotion, the submit uses the Promo_-prefixed service (see the engine and Order detail). Las promociones no tienen transacción propia: viajan dentro del pedido. Cuando el pedido tiene alguna promoción aplicada, el envío usa el servicio con prefijo Promo_.

06

Arquitetura e fluxo de dadosArchitecture & data flowArquitectura y flujo de datos

Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. Há dois fluxos: a obtenção do catálogo (dado persistido, cache + remoto) e a avaliação (o motor puro, sem I/O, que roda dentro do carrinho a cada recálculo).Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. There are two flows: catalog fetch (persisted data, cache + remote) and evaluation (the pure engine, no I/O, running inside the cart on each recompute).Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. Hay dos flujos: la obtención del catálogo (dato persistido, caché + remoto) y la evaluación (el motor puro, sin I/O, que corre dentro del carrito en cada recálculo).

Catálogo · cache + remotoCatalog · cache + remoteCatálogo · caché + remoto

Dois RPCs unários (promoções + spot) num único fetch; o resultado vira PromotionCatalogEntity e é gravado no ObjectBox (write-through). O detalhe/vitrine lê do catálogo em cache:Two unary RPCs (promotions + spot) in a single fetch; the result becomes PromotionCatalogEntity and is written to ObjectBox (write-through). Detail/showcase read the cached catalog:Dos RPCs unarios (promociones + spot) en un solo fetch; el resultado es PromotionCatalogEntity y se graba en ObjectBox (write-through). El detalle/vitrina leen del catálogo en caché:

  • PromotionReply + SpotPromotionReplygRPC proto
    • toDTOPromotionCatalogDTODTO · Freezed
      • toDomainPromotionCatalogEntitydomain
        • toModelPromotionCatalogModelObjectBox
          • toDomainPromotionCatalogEntitydomain · cache
            • getPromotionCatalogGetPromotionCatalogUseCase
              • → UIPromotionDetailNotifier · ProductShowcaseNotifier

Avaliação · o motor puroEvaluation · the pure engineEvaluación · el motor puro

Nenhum I/O aqui — só domínio. A cada recálculo do carrinho, o CartOrchestrator monta o contexto e chama o PromotionOrchestrator.recalculate, que itera as promoções: para cada uma checa elegibilidade, avalia o alvo (por nível), aplica a recompensa do maior nível atingido e mescla o efeito num PromotionRewardEffectEntity. O resultado (carrinho + efeito + elegibilidade) volta ao CartOrchestrator para os totais:No I/O here — domain only. On each cart recompute, CartOrchestrator builds the context and calls PromotionOrchestrator.recalculate, which iterates the promotions: for each it checks eligibility, evaluates the target (per level), applies the reward of the highest reached level and merges the effect into a PromotionRewardEffectEntity. The result (cart + effect + eligibility) returns to CartOrchestrator for totals:Sin I/O aquí — solo dominio. En cada recálculo del carrito, CartOrchestrator arma el contexto y llama PromotionOrchestrator.recalculate, que itera las promociones: para cada una revisa elegibilidad, evalúa el objetivo (por nivel), aplica la recompensa del nivel más alto alcanzado y mezcla el efecto en un PromotionRewardEffectEntity. El resultado vuelve a CartOrchestrator para los totales:

  • CartOrchestratorrecompute
    • recalculate(cart, promotions, context)PromotionOrchestrator
      • per promotionEvaluatePromotionEligibilityUseCase9 checks → isEligible
        • aggregatesComputeOrderAggregatesUseCase
          • rewardEvaluatePromotionRewardUseCase→ ApplyLevelRewards → target_evaluators + reward_appliers
            • mergePromotionRecalculationResultEntitycart + rewardEffect + eligibility

Efeito imutável, não mutaçãoImmutable effect, not mutationEfecto inmutable, no mutación Os aplicadores de recompensa não mutam o carrinho (diferente do legado): retornam um value object domain-only PromotionRewardEffectEntity (FoC grants, descontos por produto/pedido, dias de crédito). 0.14c/0.14d compõem e mesclam esses efeitos — nunca recalculam do zero. FoC compartilha estoque de van entre promoções via grantedFocByProduct (compra tem prioridade sobre bonificação). Reward appliers don't mutate the cart (unlike legacy): they return a domain-only value object PromotionRewardEffectEntity (FoC grants, product/order discounts, credit days). 0.14c/0.14d compose and merge these effects — never recompute from scratch. FoC shares van stock across promotions via grantedFocByProduct (purchase has priority over free-of-charge). Los aplicadores de recompensa no mutan el carrito: retornan un value object domain-only PromotionRewardEffectEntity. 0.14c/0.14d componen y mezclan esos efectos — nunca recalculan desde cero. FoC comparte stock de van entre promociones vía grantedFocByProduct.

07

Modelo de dadosData modelModelo de datos

O catálogo de promoções persistido existe em quatro representações ao longo das camadas — Proto (wire gRPC) → DTO (Freezed) → Model (ObjectBox) → Entity (domínio) — cada fronteira atravessada por um mapper, com cache write-through. Os nomes se mantêm; muda pouco (enums tipados na Entity, relações no Model). Importante: os value objects do motor (efeitos de recompensa, linhas de alvo, avaliações) são domain-only — não têm proto/DTO/Model e ficam na seção O motor.The persisted promotion catalog exists in four representations across the layers — Proto (gRPC wire) → DTO (Freezed) → Model (ObjectBox) → Entity (domain) — each boundary crossed by a mapper, with cache write-through. Names stay the same; little changes (enums typed in the Entity, relations in the Model). Note: the engine's value objects (reward effects, target lines, evaluations) are domain-only — no proto/DTO/Model, they live in The engine.El catálogo persistido existe en cuatro representacionesProtoDTOModelEntity — cada frontera cruzada por un mapper, con caché write-through. Los nombres se mantienen; cambia poco (enums tipados en la Entity, relaciones en el Model). Nota: los value objects del motor son domain-only — sin proto/DTO/Model, viven en El motor.

O dado chega num container PromotionCatalog (lastSyncAt gerado no mapper + promotions[] + spotPromotions[]). Cada Promotion (21 campos) aninha níveis → alvos/recompensas → detalhes → variável/categoria/grupo, mais os dados por conta (execução, frequência, custom mapping) e as escolhas de pedido (order choice). A SpotPromotion é uma árvore paralela e menor. A seguir: o proto, as estruturas campo-a-campo, e os mappers.Data arrives in a PromotionCatalog container (lastSyncAt generated in the mapper + promotions[] + spotPromotions[]). Each Promotion (21 fields) nests levels → targets/rewards → details → variable/category/group, plus per-account data (execution, frequency, custom mapping) and order choices. SpotPromotion is a smaller parallel tree. Next: the proto, the field-by-field structures, and the mappers.El dato llega en un container PromotionCatalog (lastSyncAt + promotions[] + spotPromotions[]). Cada Promotion (21 campos) anida niveles → objetivos/recompensas → detalles → variable/categoría/grupo, más datos por cuenta y order choices. SpotPromotion es un árbol paralelo menor. A continuación: el proto, las estructuras, y los mappers.

Proto

PromotionConectaRep.proto · proto3 · package mn.bat.conectarep.streambridge. Um serviço (PromotionConectaRepService), dois métodos unários (o de spot só é chamado quando spotPromotionsEnabled):One service (PromotionConectaRepService), two unary methods (spot is only called when spotPromotionsEnabled):Un servicio (PromotionConectaRepService), dos métodos unarios (spot solo se llama cuando spotPromotionsEnabled):

getPromotionListunary
MétodoMethodMétodo

rpc getPromotionList(PromotionRequest) returns (PromotionReply)

path /mn.bat.conectarep.streambridge.PromotionConectaRepService/getPromotionList

Request · PromotionRequest
locationHierarchySfid
string · #1 · hierarquia do representante de vendas (resolvida no repository via currentResourceProvider)sales rep hierarchy (resolved in the repository via currentResourceProvider)jerarquía del representante de ventas (resuelta en el repository)
dateReference
string · #2 · optional
lastModifiedDate
string · #3 · optional (não usado hoje)optional (not used today)optional (no usado hoy)
Reply · PromotionReply

repeated Promotion promotionListos campos de Promotion estão nas Estruturas abaixo.Promotion's fields are in Structures below.los campos de Promotion están en Estructuras abajo.

getSpotPromotionListunary · BR
MétodoMethodMétodo

rpc getSpotPromotionList(PromotionRequest) returns (SpotPromotionReply)

mesmo request; só é disparado quando marketConfiguration.promotionConfig.spotPromotionsEnabled (hoje só BR).same request; only fired when marketConfiguration.promotionConfig.spotPromotionsEnabled (today BR only).mismo request; solo se dispara cuando spotPromotionsEnabled (hoy solo BR).

Reply · SpotPromotionReply

repeated SpotPromotion spotPromotionList

Estruturas de dadosData structuresEstructuras de datos

Um dropdown por estrutura, aninhados pela hierarquia. Cada tabela tem uma coluna por camada — Proto · DTO · Model · Entity; o delta (texto azul) marca onde o tipo primeiro muda (relação ToMany/ToOne no Model, enum na Entity). ¹ = optional no proto.One dropdown per structure, nested by hierarchy. Each table has one column per layer — Proto · DTO · Model · Entity; the delta (blue text) marks where the type first changes (ToMany/ToOne relation in the Model, enum in the Entity). ¹ = optional in the proto.Un dropdown por estructura, anidados por jerarquía. Cada tabla tiene una columna por capa — Proto · DTO · Model · Entity; el delta (texto azul) marca dónde primero cambia el tipo. ¹ = optional en el proto.

  • PromotionCatalog raiz 3 campos
    CampoProtoDTOModelEntity
    lastSyncAtDateTimeDateTime
    promotionsrepeated PromotionList<…DTO>ToMany<…Model>List<…Entity>
    spotPromotionsrepeated SpotPromotionList<…DTO>ToMany<…Model>List<…Entity>
    • Promotion PromotionCatalog.promotions[] 21 campos
      CampoProtoDTOModelEntity
      idint64intintint
      namestringStringStringString
      titlestringStringStringString
      cartTitlestringStringStringString
      descriptionstringStringStringString
      mobileImageUrlstringStringStringString
      regulationstringStringStringString
      repInstructionsstringStringStringString
      typestringStringStringString
      statusstringStringStringString
      startDatestringStringStringString
      endDatestringStringStringString
      deactivationDatestringStringStringString
      deactivationCriteriastringStringStringString
      discountLimitdoubledoubledoubledouble
      maxRewardQuantityint32intintint
      hasCustomTargetboolboolboolbool
      hasCustomRewardboolboolboolbool
      levelListrepeated PromotionLevelList<…DTO>ToMany<…Model>List<…Entity>
      accountDataListrepeated PromotionAccountDataList<…DTO>ToMany<…Model>List<…Entity>
      orderChoiceListrepeated OrderChoiceList<…DTO>ToMany<…Model>List<…Entity>
      • PromotionLevel Promotion.levelList[] 4 campos
        CampoProtoDTOModelEntity
        idint64intintint
        namestringStringStringString
        targetListrepeated PromotionTargetOrRewardList<…DTO>ToMany<…Model>List<…Entity>
        rewardListrepeated PromotionTargetOrRewardList<…DTO>ToMany<…Model>List<…Entity>
        • PromotionTargetOrReward Level.targetList[] · rewardList[] 6 campos
          CampoProtoDTOModelEntity
          targetOrRewardIndexint32intintint
          operationTypestringStringStringPromotionOperationType
          labelstring¹String?String?String?
          categoryPromotionCategory…DTO?ToOne<…Model>…Entity?
          detailListrepeated PromotionDetailList<…DTO>ToMany<…Model>List<…Entity>
          selectedChannelsrepeated stringList<String>List<String>List<String>
          • PromotionCategory TargetOrReward.category · Detail.category 2 campos
            CampoProtoDTOModelEntity
            codestringStringStringPromotionCategoryCode
            typestringStringStringPromotionCategoryType
          • PromotionDetail TargetOrReward.detailList[] 22 campos
            CampoProtoDTOModelEntity
            idstringStringStringString
            detailIndexint32intintint
            operationTypestringStringStringPromotionOperationType
            variablePromotionVariable…DTO?ToOne<…Model>…Entity?
            categoryPromotionCategory…DTO?ToOne<…Model>…Entity?
            productGroupPromotionProductGroup…DTO?ToOne<…Model>…Entity?
            productIdstringStringStringString
            quantitydoubledoubledoubledouble
            valuedoubledoubledoubledouble
            percentagedoubledoubledoubledouble
            rangeFromValuedoubledoubledoubledouble
            rangeToValuedoubledoubledoubledouble
            productFamilystringStringStringString
            productCategorystringStringStringString
            productGroupTotalValuedoubledoubledoubledouble
            promoTargetValuedoubledoubledoubledouble
            promoOriginalRealizedValuedoubledoubledoubledouble
            remainingRewardQuantitydoubledoubledoubledouble
            maxDiscountdoubledoubledoubledouble
            productQuantityLimitdoubledoubledoubledouble
            maxTargetdoubledoubledoubledouble
            quotientdoubledoubledoubledouble
            • PromotionVariable Detail.variable 3 campos
              CampoProtoDTOModelEntity
              codestringStringStringPromotionVariableCode
              typestringStringStringPromotionVariableType
              dynamicDescriptionstringStringStringString
            • PromotionProductGroup Detail.productGroup 3 campos
              CampoProtoDTOModelEntity
              typestringStringStringPromotionProductGroupType
              groupOperatorstringStringStringString
              detailListrepeated PromotionProductGroupDetailList<…DTO>ToMany<…Model>List<…Entity>
              • PromotionProductGroupDetail ProductGroup.detailList[] 2 campos
                CampoProtoDTOModelEntity
                productIdstringStringStringString
                valuedoubledoubledoubledouble
      • PromotionAccountData Promotion.accountDataList[] 4 campos
        CampoProtoDTOModelEntity
        accountSfidstringStringStringString
        executionPromotionExecution¹…DTO?ToOne<…Model>…Entity?
        frequencyPromotionFrequency¹…DTO?ToOne<…Model>…Entity?
        customMappingPromotionCustomMapping¹…DTO?ToOne<…Model>…Entity?
        • PromotionExecution AccountData.execution 6 campos
          CampoProtoDTOModelEntity
          lastUpdatedstringStringStringString
          lastApplicationstringStringStringString
          totalExecutionsint32intintint
          discountEarneddoubledoubledoubledouble
          rewardQuantityEarneddoubledoubledoubledouble
          praOrderListrepeated stringList<String>List<String>List<String>
        • PromotionFrequency AccountData.frequency 10 campos
          CampoProtoDTOModelEntity
          typestringStringStringString
          repetitionNumberint32intintint
          daysBetweenActivationsint32intintint
          monboolboolboolbool
          tueboolboolboolbool
          wedboolboolboolbool
          thuboolboolboolbool
          friboolboolboolbool
          satboolboolboolbool
          sunboolboolboolbool
        • PromotionCustomMapping AccountData.customMapping 2 campos
          CampoProtoDTOModelEntity
          targetDetailListrepeated CustomMappingDetailList<…DTO>ToMany<…Model>List<…Entity>
          rewardDetailListrepeated CustomMappingDetailList<…DTO>ToMany<…Model>List<…Entity>
          • CustomMappingDetail CustomMapping.targetDetailList[] · rewardDetailList[] 2 campos
            CampoProtoDTOModelEntity
            detailIdstringStringStringString
            valuedoubledoubledoubledouble
      • OrderChoice Promotion.orderChoiceList[] 3 campos
        CampoProtoDTOModelEntity
        poNumberstringStringStringString
        levelIdint64intintint
        rewardListrepeated OrderChoiceRewardList<…DTO>ToMany<…Model>List<…Entity>
        • OrderChoiceReward OrderChoice.rewardList[] 2 campos
          CampoProtoDTOModelEntity
          rewardIdstringStringStringString
          detailListrepeated OrderChoiceRewardDetailList<…DTO>ToMany<…Model>List<…Entity>
          • OrderChoiceRewardDetail OrderChoiceReward.detailList[] 2 campos
            CampoProtoDTOModelEntity
            rewardDetailIdstringStringStringString
            productListrepeated OrderChoiceProductList<…DTO>ToMany<…Model>List<…Entity>
            • OrderChoiceProduct OrderChoiceRewardDetail.productList[] 2 campos
              CampoProtoDTOModelEntity
              productSfidstringStringStringString
              quantitydoubledoubledoubledouble
    • SpotPromotion PromotionCatalog.spotPromotions[] · BR 9 campos
      CampoProtoDTOModelEntity
      idint64intintint
      promotionTypestringStringStringString
      promotionTitlestringStringStringString
      promotionCartTitlestringStringStringString
      promotionRepInstructionsstringStringStringString
      statusstringStringStringString
      accountSfidstringStringStringString
      appliedOnOrderstringStringStringString
      productListrepeated SpotPromotionProductList<…DTO>ToMany<…Model>List<…Entity>
      • SpotPromotionProduct SpotPromotion.productList[] 2 campos
        CampoProtoDTOModelEntity
        productSfidstringStringStringString
        quantityint32intintint

Mappers

Uma extension por tipo, 5 direções — replicadas para cada uma das 20 estruturas do catálogo:One extension per type, 5 directions — replicated for each of the catalog's 20 structures:Una extension por tipo, 5 direcciones — replicadas para cada una de las 20 estructuras:

DireçãoDirectionDirecciónMétodoMethodMétodo
JSON → DTOstatic fromMap(Map)
Proto → DTOtoDTO()
DTO → EntitytoDomain() (resolve enums: fromString)(resolves enums: fromString)(resuelve enums: fromString)
Entity → ModeltoModel() (enums → .value; popula ToMany/ToOne)(enums → .value; fills relations)(enums → .value; llena relaciones)
Model → EntitytoDomain()

Os únicos deltasThe only deltasLos únicos deltas

  • enums tipados só na Entity — operationType, category.code/type, variable.code/type, productGroup.type (String nas outras camadas)enums typed only in the Entity — operationType, category.code/type, variable.code/type, productGroup.type (String elsewhere)enums tipados solo en la Entity — operationType, category.code/type, variable.code/type, productGroup.type
  • Promotion.type / status / deactivationCriteria permanecem String em todas as camadas (comparados por .value no motor, não tipados)stay String in all layers (compared by .value in the engine, not typed)permanecen String en todas las capas (comparados por .value en el motor)
  • datas (startDate/endDate/deactivationDate) permanecem String — não são parseadas para DateTimedates (startDate/endDate/deactivationDate) stay String — not parsed to DateTimefechas permanecen String — no se parsean a DateTime
  • relações viram ToMany/ToOne no Modelrelations become ToMany/ToOne in the Modelrelaciones pasan a ToMany/ToOne en el Model
  • PromotionCatalog.lastSyncAt gerado no mapper (não vem do proto)generated in the mapper (not from proto)generado en el mapper (no viene del proto)
  • a estrutura productMap/PromotionProduct do legado foi removida — produtos resolvem por variable.code (PRODUCT → productId; PRODUCT_GROUP → productGroup.detailList)the legacy productMap/PromotionProduct was removed — products resolve by variable.code (PRODUCT → productId; PRODUCT_GROUP → productGroup.detailList)la estructura legada productMap/PromotionProduct fue removida — productos resuelven por variable.code
08

Repository

PromotionRepositoryImpl implementaimplementsimplementa PromotionRepositoryInterface e injeta os 3 datasources (mock/local/remote) + ConnectivityService + a flag useMockData + Ref. Provider keepAlive. Método a método:and injects the 3 datasources (mock/local/remote) + ConnectivityService + the useMockData flag + Ref. keepAlive provider. Method by method:e inyecta los 3 datasources (mock/local/remote) + ConnectivityService + la flag useMockData + Ref. Provider keepAlive. Método a método:

getPromotionCatalog({source}) mock / local / remote

RetornaReturnsDevuelve Result<PromotionCatalogEntity, Failure>

Ponto de entrada do catálogo (default source: local). Decide a fonte e grava no cache (write-through).Catalog entry point (default source: local). Picks the source and writes to cache (write-through).Punto de entrada del catálogo (default source: local). Elige la fuente y graba en caché.

Árvore de decisão de fonteSource decision treeÁrbol de decisión de fuente

  1. useMockData ouoro source == mock_fetchFromMock(): lê os assets JSON (promotions/promotion + promotions/spot_promotion, por mercado, real vs sintético), mapeia via toDomain(). Não grava cache._fetchFromMock(): reads JSON assets (promotions/promotion + promotions/spot_promotion, per market, real vs synthetic), maps via toDomain(). No cache write._fetchFromMock(): lee los assets JSON, mapea vía toDomain(). No graba caché.
  2. source == local ou offlineor offlineu offline_fetchFromCacheOrFail(): cache; se vazio, Error(NetworkFailure)._fetchFromCacheOrFail(): cache; if empty, Error(NetworkFailure)._fetchFromCacheOrFail(): caché; si vacío, Error(NetworkFailure).
  3. senão (remoto + conectado)otherwise (remote + connected)si no (remoto + conectado)_fetchFromRemoteWithFallback(): lê currentResourceProvider (null → cache) e marketConfigurationProvider (spotPromotionsEnabled decide o RPC de spot), chama o remoto com locationHierarchyId, mapeia, grava no cache; em erro, fallback pro cache._fetchFromRemoteWithFallback(): reads currentResourceProvider (null → cache) and marketConfigurationProvider (spotPromotionsEnabled decides the spot RPC), calls remote with locationHierarchyId, maps, writes cache; on error, falls back to cache._fetchFromRemoteWithFallback(): lee currentResourceProvider (null → caché) y marketConfigurationProvider, llama al remoto con locationHierarchyId, mapea, graba caché; en error, fallback al caché.
getCachedPromotionCatalog() local

RetornaReturnsDevuelve Result<PromotionCatalogEntity?, Failure>

Só cache; null vira Success(null). Base dos lookups por id abaixo.Cache only; null becomes Success(null). Base for the by-id lookups below.Solo caché; null es Success(null). Base de los lookups por id.

getCachedPromotionCatalogLastSyncAt() local

RetornaReturnsDevuelve DateTime? (sem Result)(no Result)(sin Result)

Timestamp da última sync do container, para o DataLoadInfo do detalhe.The container's last-sync timestamp, for the detail's DataLoadInfo.Timestamp de última sincronización del container, para el DataLoadInfo.

getCachedPromotionById({promotionId}) local · §28 A

RetornaReturnsDevuelve Result<PromotionEntity?, Failure>

Lê o catálogo em cache e filtra pela id. Cache-only (§28 cat. A) — nunca dispara remoto.Reads the cached catalog and filters by id. Cache-only (§28 cat. A) — never triggers remote.Lee el catálogo en caché y filtra por id. Cache-only (§28 cat. A).

getCachedSpotPromotionById({spotPromotionId}) local · §28 A

RetornaReturnsDevuelve Result<SpotPromotionEntity?, Failure>

Igual ao anterior, sobre spotPromotions.Same as above, over spotPromotions.Igual al anterior, sobre spotPromotions.

savePromotionCatalog({promotionCatalog}) local

RetornaReturnsDevuelve Result<void, Failure>

Destrutivo: clearPromotionCatalog() (limpa as ~20 boxes filhas → raiz) + regrava o modelo. Cache-writer após cada fetch remoto.Destructive: clearPromotionCatalog() (clears the ~20 child boxes → root) + rewrites the model. Cache-writer after each remote fetch.Destructivo: clearPromotionCatalog() + regraba el modelo. Cache-writer tras cada fetch remoto.

09

Datasources

Um card por datasource (dropdown). No corpo: método, envio, retorno, fluxo de uso e tratamento de erro.One card per datasource (dropdown). In the body: method, what it sends, return, usage flow and error handling.Un card por datasource (dropdown). En el cuerpo: método, envío, retorno, flujo de uso y manejo de errores.

Remote PromotionRemoteDataSource gRPC
getPromotionCatalog({locationHierarchySfid, dateReference?, includeSpotPromotions})
EnvioSendsEnvío
monta PromotionRequest e dispara getPromotionList + (se includeSpotPromotions) getSpotPromotionList em paralelo, no PromotionConectaRepServiceClient.builds PromotionRequest and fires getPromotionList + (if includeSpotPromotions) getSpotPromotionList in parallel on PromotionConectaRepServiceClient.arma PromotionRequest y dispara getPromotionList + (si includeSpotPromotions) getSpotPromotionList en paralelo.
RetornoReturnRetorno
PromotionCatalogDTO (via reply.toDTO() nas duas listas)(via reply.toDTO() on both lists)(vía reply.toDTO())
Fluxo de usoUsage flowFlujo de uso
caminho remoto do repository quando online e sem mock; o resultado é gravado no cache.repository's remote path when online and not mocking; the result is written to cache.camino remoto del repository online y sin mock; el resultado se graba en caché.
Tratamento de erroError handlingManejo de errores
GrpcErrorGrpcExceptionHandler; outros → ServerException. O repository faz fallback pro cache.GrpcErrorGrpcExceptionHandler; others → ServerException. The repository falls back to cache.GrpcErrorGrpcExceptionHandler; otros → ServerException. El repository hace fallback al caché.
Local PromotionLocalDataSource ObjectBox

Envio / fluxo: persistência local via ObjectBox — box raiz PromotionCatalogModel + ~20 boxes filhas. Alimenta os caminhos cache do repository. Erro: falhas propagam como CacheException.Sends / flow: local persistence via ObjectBox — PromotionCatalogModel root box + ~20 child boxes. Feeds the repository's cache paths. Error: failures propagate as CacheException.Envío / flujo: persistencia local vía ObjectBox — box raíz PromotionCatalogModel + ~20 boxes hijas. Error: propagan como CacheException.

getPromotionCatalog()
RetornoReturnRetorno
PromotionCatalogEntity?
ComportamentoBehaviorComportamiento
models.first.toDomain() ou null se vazio.or null if empty.o null si vacío.
getPromotionCatalogLastSyncAt()
RetornoReturnRetorno
DateTime?
ComportamentoBehaviorComportamiento
models.first.lastSyncAt
savePromotionCatalog({entity})
RetornoReturnRetorno
void
ComportamentoBehaviorComportamiento
destrutivo: clearPromotionCatalog() + _catalogBox.put(entity.toModel()) (cascata das filhas).destructive: clearPromotionCatalog() + _catalogBox.put(entity.toModel()) (child cascade).destructivo: clearPromotionCatalog() + _catalogBox.put(entity.toModel()).
mergeAdhocPromotionCatalog({incomingPromotions, incomingSpotPromotions, accountSfid})
RetornoReturnRetorno
void
ComportamentoBehaviorComportamiento
merge aditivo do reply Ad Hoc por conta (via AdhocPromotionCatalogMerge). Não exposto na interface — usado pelo fluxo Ad Hoc.additive merge of the Ad Hoc reply per account (via AdhocPromotionCatalogMerge). Not on the interface — used by the Ad Hoc flow.merge aditivo del reply Ad Hoc por cuenta. No expuesto en la interfaz.
clearPromotionCatalog()
RetornoReturnRetorno
void
ComportamentoBehaviorComportamiento
remove todas as boxes na ordem filhas→raiz.removes all boxes children→root.remueve todas las boxes hijas→raíz.
Mock PromotionMockDataSource JSON
getPromotionCatalog()
EnvioSendsEnvío
carrega dois assets JSON (promotions/promotion + promotions/spot_promotion, por mercado, real vs sintético via useRealMockData), combina num mapa e mapeia via PromotionCatalogDTOMapper.fromMap. Resultado é memoizado (_cached).loads two JSON assets (promotions/promotion + promotions/spot_promotion, per market, real vs synthetic via useRealMockData), combines into a map and maps via PromotionCatalogDTOMapper.fromMap. Memoized (_cached).carga dos assets JSON, los combina y mapea vía PromotionCatalogDTOMapper.fromMap. Memoizado (_cached).
RetornoReturnRetorno
PromotionCatalogDTO
Fluxo de usoUsage flowFlujo de uso
usado quando useMockData ou source == mock.used when useMockData or source == mock.usado cuando useMockData o source == mock.
Tratamento de erroError handlingManejo de errores
asset ausente ou JSON inválido → CacheException (reseta _cached).missing asset or invalid JSON → CacheException (resets _cached).asset ausente o JSON inválido → CacheException.
10

Enums e labelsEnums & labelsEnums y labels

Os enums só existem tipados na Entity (em DTO/Model/Proto trafegam como String). fromString resolve case-insensitive, com fallback unknown. Lista completa de valores:Enums are only typed in the Entity (in DTO/Model/Proto they travel as String). fromString resolves case-insensitive, with an unknown fallback. Full value list:Los enums solo están tipados en la Entity. fromString resuelve case-insensitive, con fallback unknown. Lista completa:

PromotionType 4
casevalue
onOrder"ON_ORDER"
byPeriod"BY_PERIOD"
numberOfOrders"NUMBER_OF_ORDERS"
unknown""
PromotionStatus 3
casevalue
published"PUBLISHED"
available"AVAILABLE"
unknown""
PromotionCategoryCode 9
casevalue
product"PRODUCT"
none"NONE"
target"TARGET"
soq"SOQ"
noTarget"NO_TARGET"
discount"DISCOUNT"
creditDays"CREDIT_DAYS"
coin"COIN"
unknown"unknown"
PromotionCategoryType 11
casevalue
product"PRODUCT"
quotient"QUOTIENT"
order"ORDER"
onTimePayment"ON_TIME_PAYMENT"
paymentTerms"PAYMENT_TERMS"
noTarget"NO_TARGET"
discount"DISCOUNT"
creditDays"CREDIT_DAYS"
coin"COIN"
foc"FoC"
unknown"unknown"
PromotionVariableCode 14
casevalue
product"PRODUCT"
productGroup"PRODUCT_GROUP"
productCategory"PRODUCT_CATEGORY"
productFamily"PRODUCT_FAMILY"
order"ORDER"
orderDetailed"ORDER_DETAILED"
orderValue"ORDER_VALUE"
target"TARGET"
onTimePayment"ON_TIME_PAYMENT"
paymentTerms"PAYMENT_TERMS"
noTarget"NO_TARGET"
orderCreditDays"ORDER_CREDIT_DAYS"
absoluteOrderCreditDays"ABSOLUTE_ORDER_CREDIT_DAYS"
unknown"unknown"
PromotionVariableType 5
casevalue
quantity"QUANTITY"
valueType"VALUE"
soq"SOQ"
percentage"PERCENTAGE"
unknown"unknown"
PromotionProductGroupType 3
casevalue
allTogether"ALL_TOGETHER"
each"EACH"
unknown"unknown"
PromotionOperationType 3
casevalue
and"AND"
or"OR"
unknown"unknown"
PromotionFrequencyType 3
casevalue
once"ONCE"
limited"LIMITED"
unknown""
PromotionDeactivationCriteria 2
casevalue
overdueRetail"OVERDUE_RETAIL"
none""
PromotionChannel 2
casevalue
salesRep"SALES REP"
b2b"B2B"
PromotionEligibilityReason 9 · sem value (motivos de falha)9 · no value (failure reasons)9 · sin value (motivos de fallo)
case
productsUnavailable
rewardUnavailable
deactivationCriteriaOverdue
frequencyExhausted
promotionStatus
creditLimit
dateRange
maxRewardQuantity
discountLimit
11

O motor (UseCases)The engine (UseCases)El motor (UseCases)

O motor é ~40 UseCases puros (domínio, sem I/O) sob usecases/promotion/**, coordenados pelo PromotionOrchestrator. Por ser um ecossistema, agrupamos um dropdown por sub-área; dentro, uma linha por UseCase (classe · retorna · papel). O GetPromotionCatalogUseCase é o único que toca dados (delegado ao repository).The engine is ~40 pure UseCases (domain, no I/O) under usecases/promotion/**, coordinated by PromotionOrchestrator. As an ecosystem, we group one dropdown per sub-area; inside, one row per UseCase (class · returns · role). GetPromotionCatalogUseCase is the only one that touches data (delegated to the repository).El motor son ~40 UseCases puros (dominio, sin I/O) bajo usecases/promotion/**, coordinados por PromotionOrchestrator. Como es un ecosistema, agrupamos un dropdown por sub-área; dentro, una fila por UseCase.

PromotionOrchestrator coordenadorcoordinatorcoordinador
MétodoMethodMétodoRetornaReturnsDevuelvePapelRolePapel
recalculate({cart, promotions, context})PromotionRecalculationResultEntityItera as promoções: elegibilidade → aggregates → reward; mescla efeitos, acumula runningCreditDays e grantedFocByProduct, monta o carrinho com focItems + appliedPromotions. Chamado pelo CartOrchestrator.Iterates promotions: eligibility → aggregates → reward; merges effects, accumulates runningCreditDays and grantedFocByProduct, assembles the cart with focItems + appliedPromotions. Called by CartOrchestrator.Itera las promociones: elegibilidad → aggregates → reward; mezcla efectos, acumula runningCreditDays y grantedFocByProduct. Llamado por CartOrchestrator.
catalog 1 · dadosdatadatos
Classe / métodoClass / methodClase / métodoRetornaReturnsDevuelvePapelRolePapel
GetPromotionCatalogUseCase.execute({source})Result<PromotionCatalogEntity, Failure>Catálogo (roteia fonte → repository). Também getCached()Result<…?> e getCachedLastSyncAt()DateTime?. keepAlive.Catalog (routes source → repository). Also getCached()Result<…?> and getCachedLastSyncAt()DateTime?. keepAlive.Catálogo (rutea fuente → repository). También getCached() y getCachedLastSyncAt(). keepAlive.
aggregation 1
UseCaseRetornaReturnsDevuelvePapelRolePapel
ComputeOrderAggregatesUseCasePromotionOrderAggregatesEntitySoma quantidade e valor dos itens comprados (por produto e total do pedido) — insumo dos target evaluators de ORDER/SOQ/VALUE.Sums quantity and value of purchased items (per product and order total) — input to the ORDER/SOQ/VALUE target evaluators.Suma cantidad y valor de los ítems comprados — insumo de los target evaluators de ORDER/SOQ/VALUE.
eligibility 9 → 1 cascata1 cascade1 cascada
UseCaseRetornaReturnsDevuelvePapelRolePapel
EvaluatePromotionEligibilityUseCasePromotionEligibilityResultEntityCascata das 9 checagens abaixo; acumula os failedReasons e devolve isEligible + lista de motivos.Cascade of the 9 checks below; accumulates failedReasons and returns isEligible + reason list.Cascada de las 9 verificaciones; acumula failedReasons y devuelve isEligible + motivos.
ValidatePromotionProductsAvailableUseCaseboolos produtos de alvo existem/estão disponíveis.target products exist/are available.los productos de objetivo están disponibles.
ValidateRewardAvailableUseCaseboolos produtos de recompensa estão disponíveis.reward products are available.los productos de recompensa están disponibles.
ValidateDeactivationCriteriaOverdueUseCaseboolinadimplência: OVERDUE_RETAIL desativa a promoção na criação de pedido se a conta está overdue.overdue: OVERDUE_RETAIL disables the promotion on order creation if the account is overdue.morosidad: OVERDUE_RETAIL desactiva la promoción si la cuenta está en mora.
ValidatePromotionFrequencyUseCaseboolfrequência: dia da semana (currentWeekDay), limite (ONCE/LIMITED) e execução.frequency: weekday (currentWeekDay), limit (ONCE/LIMITED) and execution.frecuencia: día de la semana, límite (ONCE/LIMITED) y ejecución.
ValidatePromotionStatusUseCaseboolstatus PUBLISHED ou AVAILABLE (regular vs spot).status PUBLISHED or AVAILABLE (regular vs spot).status PUBLISHED o AVAILABLE.
ValidatePromotionCreditLimitUseCaseboolquando gatesByCreditLimit (CL), bloqueia se o limite de crédito foi atingido.when gatesByCreditLimit (CL), blocks if credit limit reached.cuando gatesByCreditLimit (CL), bloquea si se alcanzó el límite.
ValidatePromotionDateRangeUseCaseboola data corrente está entre startDate e endDate.current date is within startDate/endDate.la fecha está entre startDate y endDate.
ValidateMaxRewardQuantityGateUseCaseboolteto maxRewardQuantity (soma detailList) vs rewardQuantityEarned.maxRewardQuantity cap (sums detailList) vs rewardQuantityEarned.tope maxRewardQuantity vs rewardQuantityEarned.
ValidateDiscountLimitGateUseCaseboolteto discountLimit vs executionDiscountEarned.discountLimit cap vs executionDiscountEarned.tope discountLimit vs executionDiscountEarned.
resolution 2
UseCaseRetornaReturnsDevuelvePapelRolePapel
ResolvePromotionTargetLinesUseCaseList<PromotionTargetLineEntity>resolve os produtos do alvo por variable.code (PRODUCT → productId; PRODUCT_GROUP → productGroup.detailList) e cruza com o carrinho.resolves target products by variable.code and crosses with the cart.resuelve los productos del objetivo por variable.code y cruza con el carrito.
ResolveRewardProductLinesUseCaseList<RewardProductLineEntity>resolve os produtos de recompensa (incluindo reward-only, via rewardProducts) para o aplicador.resolves reward products (including reward-only, via rewardProducts) for the applier.resuelve los productos de recompensa (incluyendo reward-only) para el aplicador.
target_evaluators 11
UseCaseRetornaReturnsDevuelvePapelRolePapel
EvaluateTargetDetailUseCasebooldispatcher por variable.code → chama o evaluator específico.dispatcher by variable.code → calls the specific evaluator.dispatcher por variable.code.
CombineTargetResultsUseCaseboolcombina os detalhes por operador AND/OR.combines details by AND/OR operator.combina los detalles por operador AND/OR.
EvaluateLevelTargetUseCaseboolavalia o alvo de um nível inteiro.evaluates a whole level's target.evalúa el objetivo de un nivel.
EvaluateProductGroupTargetUseCaseboolPRODUCT_GROUP (ALL_TOGETHER / EACH).
EvaluateProductCategoryFamilyTargetUseCaseboolPRODUCT_CATEGORY / PRODUCT_FAMILY.
EvaluateOrderValueTargetUseCaseboolORDER_VALUE (maxTarget, valor do pedido).
EvaluateOrderSoqTargetUseCaseboolORDER SOQ (soqSum / quantidade).
EvaluateOrderDetailSoqTargetUseCaseboolORDER_DETAILED SOQ (por linha).
EvaluateTargetQuotaTargetUseCaseboolTARGET / QUOTIENT (quota).
EvaluatePaymentTermsTargetUseCaseboolPAYMENT_TERMS / ON_TIME_PAYMENT (ver roadmap).(see roadmap).(ver roadmap).
EvaluateNoTargetTargetUseCaseboolNO_TARGET (sem alvo; opcional dia de visita).(no target; optional visit day).(sin objetivo; opcional día de visita).
evaluation 1
UseCaseRetornaReturnsDevuelvePapelRolePapel
EvaluatePromotionRewardUseCasePromotionEvaluationOutcomeEntitypor promoção: resolve o maior nível atingido, chama ApplyLevelRewards, devolve efeito + FoC + runningCreditDays.per promotion: resolves the highest reached level, calls ApplyLevelRewards, returns effect + FoC + runningCreditDays.por promoción: resuelve el nivel más alto, llama ApplyLevelRewards, devuelve efecto + FoC + runningCreditDays.
reward_appliers 7
UseCaseRetornaReturnsDevuelvePapelRolePapel
ApplyLevelRewardsUseCasePromotionLevelRewardOutcomeEntityaplica todas as recompensas de um nível; telescopa níveis (subtrai o nível anterior).applies all rewards of a level; telescopes levels (subtracts the previous level).aplica todas las recompensas de un nivel; telescopa niveles.
ApplyRewardDetailUseCasePromotionRewardEffectEntityroteia um detalhe de recompensa ao aplicador certo por variable.code.routes a reward detail to the right applier by variable.code.rutea un detalle de recompensa al aplicador correcto.
ApplyProductRewardUseCasePromotionRewardEffectEntitybonificação de produto (FoC), com validação de estoque compartilhado.product free-of-charge (FoC), with shared-stock validation.bonificación de producto (FoC), con validación de stock compartido.
ApplyProductDiscountUseCasePromotionRewardEffectEntitydesconto por produto (com maxDiscount).per-product discount (with maxDiscount).descuento por producto (con maxDiscount).
ApplyOrderDiscountUseCasePromotionRewardEffectEntitydesconto no pedido (com teto cumulativo discountLimit).order discount (with cumulative discountLimit cap).descuento en el pedido (con tope discountLimit).
ApplyCreditDaysUseCasePromotionRewardEffectEntitydias de crédito promocionais (relativo, acumula runningCreditDays).promotional credit days (relative, accumulates runningCreditDays).días de crédito promocionales (relativo).
ApplyAbsoluteCreditDaysUseCasePromotionRewardEffectEntitydias de crédito absolutos.absolute credit days.días de crédito absolutos.
tier_escalation 1
UseCaseRetornaReturnsDevuelvePapelRolePapel
SubtractPreviousLevelRewardEffectUseCasePromotionRewardEffectEntitysubtrai o efeito do nível anterior (net field-wise, floor 0) — níveis telescopam, não somam. Não recalcula.subtracts the previous level's effect (net field-wise, floor 0) — levels telescope, don't stack. No recompute.resta el efecto del nivel anterior (net field-wise, floor 0) — los niveles telescopan.
conflict 2
UseCaseRetornaReturnsDevuelvePapelRolePapel
ResolveEligibleRewardDetailsUseCaseList<RewardSelection>resolve o conflito OR entre recompensas alternativas (a escolha do representante ou o default).resolves the OR conflict among alternative rewards (rep's choice or default).resuelve el conflicto OR entre recompensas alternativas.
FilterRewardsByChannelUseCaseList<PromotionTargetOrRewardEntity>filtra recompensas pelo canal (PromotionChannel: SALES REP / B2B).filters rewards by channel (PromotionChannel: SALES REP / B2B).filtra recompensas por canal (PromotionChannel).
detail 1 · exibiçãodisplayexhibición
UseCaseRetornaReturnsDevuelvePapelRolePapel
EvaluatePromotionDetailUseCasePromotionDetailEvaluationEntitymonta a visão de exibição do detalhe (alvos de entrada, produtos, níveis, recompensas, anyGoalAchieved) + productNames para os labels. Usado pela PromotionDetailPage via CartOrchestrator.evaluatePromotionDetail.builds the detail's display view (entry targets, products, levels, rewards, anyGoalAchieved) + productNames for labels. Used by PromotionDetailPage via CartOrchestrator.evaluatePromotionDetail.arma la vista de exhibición del detalle + productNames para los labels.
12

Notifier & State

A superfície de UI própria do ecossistema é o detalhe da promoção. O PromotionDetailNotifier (@riverpod, family por promotionId+accountSfid, with AsyncGuard) monta o State com os dados do catálogo em cache; a avaliação ao vivo não vive no State — vem de um provider separado (promotionDetailEvaluationProvider) que observa carrinho + contexto e chama o motor. A vitrine e o carrinho consomem o mesmo catálogo por seus próprios notifiers/providers (ver Pages e widgets).The ecosystem's own UI surface is the promotion detail. PromotionDetailNotifier (@riverpod, family by promotionId+accountSfid, with AsyncGuard) builds the State from the cached catalog; the live evaluation doesn't live in the State — it comes from a separate provider (promotionDetailEvaluationProvider) that watches cart + context and calls the engine. Showcase and cart consume the same catalog via their own notifiers/providers (see Pages & widgets).La superficie de UI propia es el detalle de la promoción. PromotionDetailNotifier (family por promotionId+accountSfid) arma el State desde el catálogo en caché; la evaluación en vivo viene de un provider separado (promotionDetailEvaluationProvider).

MétodosMethodsMétodos

_load({source}) private

RetornoReturnRetorno Future<PromotionDetailState>

Busca em paralelo (via ref.read): visita da conta (getCachedByAccountSfid), catálogo de produtos (order) e catálogo de promoções; filtra a promoção por id e monta o State (conta, promoção, lastSyncAt, mapa de produtos por sfid). Visita/promoção nulas → BusinessFailure.Fetches in parallel (via ref.read): account visit (getCachedByAccountSfid), product catalog (order) and promotion catalog; filters the promotion by id and builds the State (account, promotion, lastSyncAt, products-by-sfid map). Null visit/promotion → BusinessFailure.Busca en paralelo: visita de la cuenta, catálogo de productos y de promociones; filtra por id y arma el State. Visita/promoción nulas → BusinessFailure.

refresh() pull-to-refresh

RetornoReturnRetorno Future<void>

Pré-step: re-busca visitas remotas e invalida o cartContextProvider da conta; depois runGuarded(() => _load(source: remote)).Pre-step: re-fetches remote visits and invalidates the account's cartContextProvider; then runGuarded(() => _load(source: remote)).Pre-step: re-busca visitas remotas e invalida el cartContextProvider; luego runGuarded(() => _load(source: remote)).

State disponível para a PageState available to the PageState disponible para la Page

PromotionDetailState campos + getterfields + gettercampos + getter
campotipodefault
accountSfidStringrequired
accountAccountDataEntityrequired
promotionPromotionEntityrequired
lastSyncAtDateTime?null
productsMap<String, ProductEntity>{}

Getter: productBySfid({productSfid}). A avaliação viva (PromotionDetailEvaluationEntity) é lida à parte do promotionDetailEvaluationProvider, não do State.Getter: productBySfid({productSfid}). The live evaluation (PromotionDetailEvaluationEntity) is read separately from promotionDetailEvaluationProvider, not the State.Getter: productBySfid({productSfid}). La evaluación viva se lee aparte del promotionDetailEvaluationProvider.

13

Pages e widgetsPages & widgetsPages y widgets

Duas superfícies consomem promoções. A vitrine lista e navega; o detalhe mostra a promoção avaliada ao vivo contra o carrinho.Two surfaces consume promotions. The showcase lists and navigates; the detail shows the promotion evaluated live against the cart.Dos superficies consumen promociones. La vitrina lista y navega; el detalle muestra la promoción evaluada en vivo.

  • ProductShowcasePage aba Promoções (kPromotionsTabRoute)
    • ProductShowcasePromotionsSectionWidget state.isPromotionsTab · vazio → CustomEmptyState
      • ProductShowcasePromotionCardWidget 1 por promoção da conta
        • CustomButton "acessar promoção" → AppRouter.goToPromotionDetail(promotionId, accountSfid)
  • PromotionDetailPage ConsumerWidget · family(promotionId, accountSfid)
    • AppPageShell back · connectivity · search · notifications
      • CustomLoadingIndicator loading
      • FailureStateView error → invalidate
      • CustomPullToRefresh data → refresh()
        • DataLoadInfo state.lastSyncAt
        • AccountHeaderCard nome · SAP · overdue
        • PromotionDetailInfoWidget título · id · descrição · regras
        • _EvaluationSection watch promotionDetailEvaluationProvider · null → shrink
          • PromotionDetailEntryTargetsWidget evaluation.entryTargets
          • PromotionDetailProductsWidget targetProducts
            • PromotionDetailProductCardWidget
          • PromotionDetailTargetsRewardsCardWidget levels · isApplied → apply/select reward
            • PromotionDetailGoalLineWidget
          • PromotionDetailGoalsMessageWidget anyGoalAchieved
          • PromotionDetailRewardsCardWidget rewards (label, nome via productNames)

O carrinho não tem tela neste doc, mas consome promoções: cartContextProvider traz produtos de order + rewardProducts (via GetProductsForPromotionRewardUseCase), o CartOrchestrator roda o motor a cada intent (ApplyPromotionIntent, SelectPromotionRewardIntent) e a revisão do carrinho mostra as Bonificações. No envio, o pedido com promoção usa o serviço Promo_ (Order detail).The cart has no screen in this doc, but consumes promotions: cartContextProvider brings order products + rewardProducts (via GetProductsForPromotionRewardUseCase), CartOrchestrator runs the engine on each intent (ApplyPromotionIntent, SelectPromotionRewardIntent) and the cart review shows the Free of charge section. On submit, an order with a promotion uses the Promo_ service (Order detail).El carrito no tiene pantalla aquí, pero consume promociones: cartContextProvider trae productos de order + rewardProducts, CartOrchestrator corre el motor en cada intent y la revisión muestra las Bonificaciones. Al enviar, el pedido con promoción usa el servicio Promo_.

Mercados e roadmapMarkets & roadmapMercados y roadmap

Promoções é dirigido por End Market Configuration (promotionConfig). Só os três mercados ativos têm o bloco; AR/PY/PE não têm promotionConfig:Promotions is driven by End Market Configuration (promotionConfig). Only the three active markets carry the block; AR/PY/PE have no promotionConfig:Promociones se rige por End Market Configuration (promotionConfig). Solo los tres mercados activos tienen el bloque; AR/PY/PE no tienen promotionConfig:

BRx CLx ZAx AR PY PE
disponívelavailabledisponible presente, desligadopresent, offpresente, apagado ausenteabsentausente

Matriz das 4 flags de promotionConfig por mercado (uma linha por flag):Matrix of the 4 promotionConfig flags per market (one row per flag):Matriz de las 4 flags de promotionConfig por mercado (una fila por flag):

FlagFlagFlagBRCLZAARPYPE
spotPromotionsEnabledtruefalsefalse
enforcesBudgetCapfalsefalsetrue
enforcesDiscountLimitGatetruefalsefalse
gatesByCreditLimitfalsetruefalse
BR

BrasilBrazilBrasil Único com spot promotions (spotPromotionsEnabled: true → dispara getSpotPromotionList). Aplica o gate de teto de desconto (enforcesDiscountLimitGate). The only one with spot promotions (spotPromotionsEnabled: true → fires getSpotPromotionList). Applies the discount-cap gate (enforcesDiscountLimitGate). El único con spot promotions (spotPromotionsEnabled: true). Aplica el gate de tope de descuento (enforcesDiscountLimitGate).

CL

ChileChileChile Único com gatesByCreditLimit: true — a elegibilidade bloqueia a promoção quando o limite de crédito foi atingido. Sem spot. The only one with gatesByCreditLimit: true — eligibility blocks the promotion when the credit limit is reached. No spot. El único con gatesByCreditLimit: true — la elegibilidad bloquea la promoción al alcanzar el límite de crédito. Sin spot.

ZA

África do SulSouth AfricaSudáfrica Único com enforcesBudgetCap: true (teto de orçamento por promoção — ver roadmap, ainda não persistido). Sem spot. The only one with enforcesBudgetCap: true (per-promotion budget cap — see roadmap, not persisted yet). No spot. El único con enforcesBudgetCap: true (tope de presupuesto por promoción — ver roadmap). Sin spot.

AR · PY · PE Existem como mercados do app, mas não têm promotionConfig no EMC — o ecossistema de promoções fica inativo (AR/PY/PE = config mínima PANGEA). They exist as app markets, but have no promotionConfig in the EMC — the promotions ecosystem is inactive (AR/PY/PE = minimal PANGEA config). Existen como mercados de la app, pero no tienen promotionConfig en el EMC — el ecosistema de promociones queda inactivo.

Pendências / roadmapPending / roadmapPendientes / roadmap O motor atual cobre elegibilidade, alvo e recompensa por nível. Auditoria (08/06/20262026-06-08) vs o legado flutter-bat-salesrep mapeou 7 lacunas conhecidas — documentadas como ainda não portadas (não descritas como existentes): The current engine covers eligibility, target and per-level reward. An audit (08/06/20262026-06-08) vs the legacy flutter-bat-salesrep mapped 7 known gaps — documented as not yet ported (not described as present): El motor actual cubre elegibilidad, objetivo y recompensa por nivel. Una auditoría (08/06/20262026-06-08) vs el legado mapeó 7 lacunas conocidas — documentadas como aún no portadas:

  • Custom Mapping não consumidohasCustomTarget/hasCustomReward + PromotionCustomMapping são mapeados mas nenhum UseCase os lê (legado sobrescreve quantity/value por conta).Custom Mapping not consumedhasCustomTarget/hasCustomReward + PromotionCustomMapping are mapped but no UseCase reads them (legacy overrides quantity/value per account).Custom Mapping no consumido — mapeados pero ningún UseCase los lee.
  • Contabilidade pós-pedido não persistidatotalExecutions/discountEarned/rewardQuantityEarned são só lidos; nada grava. Gates ONCE/LIMITED/maxRewardQuantity/discountLimit nunca disparam entre pedidos.Post-order accounting not persistedtotalExecutions/discountEarned/rewardQuantityEarned are only read; nothing writes them. ONCE/LIMITED/maxRewardQuantity/discountLimit gates never fire between orders.Contabilidad post-pedido no persistida — solo se leen; los gates nunca disparan entre pedidos.
  • 4 de 11 critérios de elegibilidade faltam — indirect-order ZA, prompt-CL só credit-days, target-rules QUOTIENT volume≠0, stock-rules prompt esconder promoção.4 of 11 eligibility criteria missing — indirect-order ZA, prompt-CL credit-days only, target-rules QUOTIENT volume≠0, stock-rules prompt hide promotion.4 de 11 criterios de elegibilidad faltan.
  • Motor de Spot Promotion ausente — só fetch + toggle; sem achievement/apply/recalc.Spot Promotion engine absent — fetch + toggle only; no achievement/apply/recalc.Motor de Spot Promotion ausente — solo fetch + toggle.
  • Payment-Terms sempre falha + ON_TIME_PAYMENT sem evaluator — o proto não tem campo paymentType; chamado com paymentTypeToValidate: "". Gap de contrato → backend.Payment-Terms always fails + ON_TIME_PAYMENT has no evaluator — the proto lacks a paymentType field; called with paymentTypeToValidate: "". Contract gap → backend.Payment-Terms siempre falla + ON_TIME_PAYMENT sin evaluator — el proto no tiene campo paymentType.
  • QUOTIENT/MONTH sem fonte de volume realizado/alvo — campos nunca populados (legado sincroniza AccountVolume).QUOTIENT/MONTH has no source of realized/target volume — fields never populated (legacy syncs AccountVolume).QUOTIENT/MONTH sin fuente de volumen realizado/objetivo.
  • OrderChoice/PRA replay não consumido — as entities existem, nenhum UseCase lê; afeta a edição de pedido.OrderChoice/PRA replay not consumed — entities exist, no UseCase reads them; affects order editing.OrderChoice/PRA replay no consumido — afecta la edición de pedido.

Paridade (não são gaps): COIN sem efeito nos dois; EACH+SOQ comentado no legado; maxTarget em ALL_TOGETHER+SOQ. Fora de escopo: Incentives, Surprise Box, Combo, Telesales.Parity (not gaps): COIN has no effect in either; EACH+SOQ commented in legacy; maxTarget in ALL_TOGETHER+SOQ. Out of scope: Incentives, Surprise Box, Combo, Telesales.Paridad (no son gaps): COIN sin efecto; EACH+SOQ comentado; maxTarget en ALL_TOGETHER+SOQ. Fuera de alcance: Incentives, Surprise Box, Combo, Telesales.