Skip to main content

Documento de Requisitos — Atualizar componente de steps do fluxo de venda para step Upsell condicional

Tarefa: #19323
Contexto: Feature: Upsell


Visão Geral

O componente NewSellStepper do fluxo de venda no zzapp precisa ser atualizado para exibir um step condicional 'Recomendar' inserido dinamicamente entre as etapas Cliente e Conferir. A decisão de exibir ou não o step é baseada em feature flag, habilitação por loja, e tipo de estoque do carrinho (apenas fluxo store). A atualização introduz um UpsellRecommendationController dedicado e implementa reatividade via AppGetBuilder (padrão oficial do projeto), mantendo o stepper como StatelessWidget.


Papéis Envolvidos

  • Vendedor(a) — visualiza o header de steps no fluxo de venda e identifica quando o fluxo upsell está disponível

Requisitos Funcionais

RF-01 — Step condicional 'Recomendar' no header

História de Usuário:
Como vendedor(a), quero ver um step 'Recomendar' no header do fluxo de venda quando as condições de upsell forem atendidas, para saber que há itens recomendados disponíveis.

Critérios de Aceitação:

  1. QUANDO o NewSellStepper for renderizado E as condições de elegibilidade de upsell forem atendidas ENTÃO o sistema DEVE exibir o step 'Recomendar' com número 3 entre os steps Cliente e Conferir.

  2. QUANDO o step 'Recomendar' estiver ativo ENTÃO o step Conferir DEVE ser renumerado para 4.

  3. QUANDO o step 'Recomendar' estiver ativo ENTÃO o sistema DEVE renderizá-lo com o mesmo padrão visual dos demais steps: círculo numerado com label, suportando os estados complete, selected e unselected.

  4. QUANDO as condições de elegibilidade NÃO forem atendidas ENTÃO o sistema NÃO DEVE exibir o step 'Recomendar' e o fluxo DEVE manter o comportamento atual de 3 steps (Produtos, Cliente, Conferir).

Casos de Borda:

  • Carrinho sem itens (saleEcommerce é null): step não aparece.
  • Carrinho com itens ecommerce (saleEcommerce == true): step não aparece.
  • Carrinho com itens estoque loja + feature flags ok: step aparece.

RF-02 — Condições de elegibilidade do step Upsell

História de Usuário:
Como sistema, devo validar três condições simultâneas antes de exibir o step de upsell no header.

Critérios de Aceitação:

  1. QUANDO o UpsellRecommendationController avaliar isActive ENTÃO o sistema DEVE retornar true somente se TODAS as condições abaixo forem verdadeiras:

    • FirebaseRemoteConfigService.dictionary.enableUpsellRecommendation == true
    • codeStore da loja atual consta na lista FirebaseRemoteConfigService.dictionary.enableUpsellRecommendationByCodeStore
    • cartDTO.saleEcommerce == false (carrinho é do tipo estoque loja)
  2. SE qualquer uma das três condições for falsa ENTÃO o sistema DEVE retornar isActive == false.

Casos de Borda:

  • Loja não consta na whitelist byCodeStore mas flag global está ativa: step NÃO aparece.
  • Flag global desativada: step NÃO aparece independentemente das outras condições.

RF-03 — Reatividade da visibilidade do step

História de Usuário:
Como sistema, devo reavaliar dinamicamente a visibilidade do step de upsell quando o estado do carrinho mudar.

Critérios de Aceitação:

  1. QUANDO o primeiro item for adicionado ao carrinho (momento em que saleEcommerce é definido) ENTÃO o sistema DEVE reavaliar isActive via listener no OpenSalesController.activeCartDTO.

  2. QUANDO os itens do carrinho forem alterados (adição ou remoção de produtos) ENTÃO o sistema DEVE reavaliar isActive.

  3. QUANDO o carrinho for esvaziado (saleEcommerce volta a null) ENTÃO isActive DEVE retornar false e o step DEVE desaparecer.

  4. SE a feature flag for alterada em runtime E o usuário já estiver na tela de recomendação ENTÃO o step DEVE permanecer visível (não desaparece durante o uso).

Casos de Borda:

  • Remoção de todos os itens do carrinho: step volta a não aparecer.
  • Adição de produto ecommerce em carrinho que era store: como saleEcommerce é lock-in no 1º item (?? em CartDTO.addItem()), o tipo não muda — step permanece visível.

RF-04 — UpsellRecommendationController

História de Usuário:
Como desenvolvedor, preciso de um controller dedicado que encapsule a lógica de elegibilidade do upsell e notifique o stepper sobre mudanças.

Critérios de Aceitação:

  1. O UpsellRecommendationController DEVE ser registrado como registerLazySingleton em get_it.dart, garantindo que uma única instância sobreviva entre todas as telas do fluxo de venda.

  2. O controller DEVE estender GetxController.

  3. O controller DEVE expor RxBool isActive reativo, consumido pelo NewSellStepper via listener.

  4. QUANDO isActive for computado ENTÃO o controller DEVE acessar estaticamente FirebaseRemoteConfigService.dictionary para as feature flags e via getIt.get<CartController>() para o saleEcommerce do carrinho.

  5. QUANDO OpenSalesController.activeCartDTO emitir um valor null ENTÃO o controller DEVE resetar isActive para false.

  6. QUANDO OpenSalesController.activeCartDTO emitir um cart com saleEcommerce == null (cart sem itens) ENTÃO isActive DEVE ser false.

Casos de Borda:

  • Ao iniciar com activeCartDTO null (sem venda ativa): isActive = false.
  • O controller se auto-reseta via listener — nenhum outro componente precisa chamar reset() manualmente.

RF-05 — NewSellStepper reativo via AppGetBuilder

História de Usuário:
Como componente de UI, preciso reagir a mudanças no UpsellRecommendationController usando o padrão oficial de reatividade do projeto (AppGetBuilder), mantendo o stepper como StatelessWidget sem exigir wrappers adicionais nas telas que me consomem.

Critérios de Aceitação:

  1. NewSellStepper DEVE permanecer como StatelessWidget, seguindo a regra do projeto (README.md linha 55: "Todos os widgets devem ser Stateless").

  2. O widget DEVE receber apenas SteppSell currentStep como parâmetro (não mais List<StatusStepp>).

  3. QUANDO o build() for executado ENTÃO o widget DEVE internamente acessar getIt.get<UpsellRecommendationController>() e embrulhar a renderização dos steps em um AppGetBuilder(init: controller, builder: () { ... }).

  4. QUANDO o UpsellRecommendationController chamar update() ENTÃO o AppGetBuilder DEVE reconstruir a sub-árvore dos steps via setState() interno.

  5. QUANDO controller.isActive == true ENTÃO o widget DEVE renderizar o step 'Recomendar' (índice 2) entre Cliente (índice 1) e Conferir (índice 2 ou 3 dinâmico), com _SteppDivider entre cada step.

  6. QUANDO controller.isActive == false ENTÃO o widget DEVE renderizar apenas 3 steps (Produtos, Cliente, Conferir), mantendo o comportamento original.

Casos de Borda:

  • AppGetBuilder aninhado: se a tela pai também usa AppGetBuilder com CartController, o rebuild do pai recria o AppGetBuilder interno do stepper. O listener é removido no dispose do antigo e readicionado no initState do novo — comportamento correto e consistente com o uso em check_sell_crm_values.dart.
  • O callback onScreenClosed() do AppGetBuilder não é acionado para o UpsellRecommendationController (não estende DefaultApiController), o que é esperado para um controller que não faz chamadas de API.

RF-06 — Modelos e enums do stepper

História de Usuário:
Como desenvolvedor, preciso de novos tipos no domínio para representar o step de upsell com numeração dinâmica.

Critérios de Aceitação:

  1. O enum SteppSell DEVE ganhar o valor recomendar.

  2. A classe StepperModel DEVE ganhar o factory recomendaStepperModel(StatusStepp status) com number: 3, title: 'Recomendar' e steppSell: SteppSell.recomendar.

  3. O factory StepperModel.conferenciaStepperModel DEVE ganhar parâmetro opcional int number com valor default 3. QUANDO upsell estiver ativo ENTÃO o parâmetro DEVE ser 4.

  4. O enum StatusStepp DEVE permanecer inalterado (complete, selected, unselected).


RF-07 — Atualização dos call sites do NewSellStepper

História de Usuário:
Como desenvolvedor, quero que as telas existentes continuem funcionando com mudanças mínimas ao atualizar o componente stepper.

Critérios de Aceitação:

  1. Cada tela que consome NewSellStepper DEVE passar a chamar NewSellStepper(currentStep: SteppSell.xxx) em vez de NewSellStepper(stepperSellXxx).

  2. O mapeamento NewSellTypeSteppSell DEVE ser: product/customerProfileSteppSell.produto; customerSteppSell.cliente.

  3. Telas de check (CheckSellScreen, CheckCustomerScreen, etc.) DEVEM passar o SteppSell correspondente ao seu contexto.

  4. Nenhuma tela DEVE precisar de AppGetBuilder adicional para o stepper — o widget é internamente reativo.

Casos de Borda:

  • Telas que condicionalmente escondem o stepper (ex: CheckProductsScreen com !checkSell) mantêm a lógica de Visibility existente.

Dependências

DependênciaDescriçãoStatus
Task 07 — Feature flags upsellRegistro das chaves enableUpsellRecommendation e enableUpsellRecommendationByCodeStore no FirebaseRemoteConfigKeysEnum e FirebaseRemoteConfigServiceDictionaryPendente
Task — Tela de recomendação UpsellConstrução da tela própria do step 'Recomendar' onde o vendedor visualiza e gerencia itens recomendadosPlanejada (outra task)
Task — API de recomendação UpsellIntegração com API externa que recebe CPF, StoreId e dados do carrinho e retorna lista de itens recomendadosPlanejada (outra task)
Task — Navegação para step RecomendarLógica de navegação do botão AVANÇAR entre Cliente → Recomendar (quando ativo) → ConferirPlanejada (outra task)

Questões em Aberto

  • Nenhuma questão em aberto identificada até o momento.