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:
-
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.
-
QUANDO o step 'Recomendar' estiver ativo ENTÃO o step Conferir DEVE ser renumerado para 4.
-
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,selectedeunselected. -
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:
-
QUANDO o UpsellRecommendationController avaliar
isActiveENTÃO o sistema DEVE retornartruesomente se TODAS as condições abaixo forem verdadeiras:FirebaseRemoteConfigService.dictionary.enableUpsellRecommendation == truecodeStoreda loja atual consta na listaFirebaseRemoteConfigService.dictionary.enableUpsellRecommendationByCodeStorecartDTO.saleEcommerce == false(carrinho é do tipo estoque loja)
-
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
byCodeStoremas 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:
-
QUANDO o primeiro item for adicionado ao carrinho (momento em que
saleEcommerceé definido) ENTÃO o sistema DEVE reavaliarisActivevia listener noOpenSalesController.activeCartDTO. -
QUANDO os itens do carrinho forem alterados (adição ou remoção de produtos) ENTÃO o sistema DEVE reavaliar
isActive. -
QUANDO o carrinho for esvaziado (
saleEcommercevolta anull) ENTÃOisActiveDEVE retornarfalsee o step DEVE desaparecer. -
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 (??emCartDTO.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:
-
O
UpsellRecommendationControllerDEVE ser registrado comoregisterLazySingletonemget_it.dart, garantindo que uma única instância sobreviva entre todas as telas do fluxo de venda. -
O controller DEVE estender
GetxController. -
O controller DEVE expor
RxBool isActivereativo, consumido peloNewSellSteppervia listener. -
QUANDO
isActivefor computado ENTÃO o controller DEVE acessar estaticamenteFirebaseRemoteConfigService.dictionarypara as feature flags e viagetIt.get<CartController>()para osaleEcommercedo carrinho. -
QUANDO
OpenSalesController.activeCartDTOemitir um valornullENTÃO o controller DEVE resetarisActiveparafalse. -
QUANDO
OpenSalesController.activeCartDTOemitir um cart comsaleEcommerce == null(cart sem itens) ENTÃOisActiveDEVE serfalse.
Casos de Borda:
- Ao iniciar com
activeCartDTOnull (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:
-
NewSellStepperDEVE permanecer comoStatelessWidget, seguindo a regra do projeto (README.md linha 55: "Todos os widgets devem ser Stateless"). -
O widget DEVE receber apenas
SteppSell currentStepcomo parâmetro (não maisList<StatusStepp>). -
QUANDO o
build()for executado ENTÃO o widget DEVE internamente acessargetIt.get<UpsellRecommendationController>()e embrulhar a renderização dos steps em umAppGetBuilder(init: controller, builder: () { ... }). -
QUANDO o
UpsellRecommendationControllerchamarupdate()ENTÃO oAppGetBuilderDEVE reconstruir a sub-árvore dos steps viasetState()interno. -
QUANDO
controller.isActive == trueENTÃO o widget DEVE renderizar o step 'Recomendar' (índice 2) entre Cliente (índice 1) e Conferir (índice 2 ou 3 dinâmico), com_SteppDividerentre cada step. -
QUANDO
controller.isActive == falseENTÃ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 nodisposedo antigo e readicionado noinitStatedo novo — comportamento correto e consistente com o uso emcheck_sell_crm_values.dart. - O callback
onScreenClosed()do AppGetBuilder não é acionado para oUpsellRecommendationController(não estendeDefaultApiController), 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:
-
O enum
SteppSellDEVE ganhar o valorrecomendar. -
A classe
StepperModelDEVE ganhar o factoryrecomendaStepperModel(StatusStepp status)comnumber: 3,title: 'Recomendar'esteppSell: SteppSell.recomendar. -
O factory
StepperModel.conferenciaStepperModelDEVE ganhar parâmetro opcionalint numbercom valor default3. QUANDO upsell estiver ativo ENTÃO o parâmetro DEVE ser4. -
O enum
StatusSteppDEVE 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:
-
Cada tela que consome
NewSellStepperDEVE passar a chamarNewSellStepper(currentStep: SteppSell.xxx)em vez deNewSellStepper(stepperSellXxx). -
O mapeamento
NewSellType→SteppSellDEVE ser:product/customerProfile→SteppSell.produto;customer→SteppSell.cliente. -
Telas de check (
CheckSellScreen,CheckCustomerScreen, etc.) DEVEM passar oSteppSellcorrespondente ao seu contexto. -
Nenhuma tela DEVE precisar de
AppGetBuilderadicional para o stepper — o widget é internamente reativo.
Casos de Borda:
- Telas que condicionalmente escondem o stepper (ex:
CheckProductsScreencom!checkSell) mantêm a lógica deVisibilityexistente.
Dependências
| Dependência | Descrição | Status |
|---|---|---|
| Task 07 — Feature flags upsell | Registro das chaves enableUpsellRecommendation e enableUpsellRecommendationByCodeStore no FirebaseRemoteConfigKeysEnum e FirebaseRemoteConfigServiceDictionary | Pendente |
| Task — Tela de recomendação Upsell | Construção da tela própria do step 'Recomendar' onde o vendedor visualiza e gerencia itens recomendados | Planejada (outra task) |
| Task — API de recomendação Upsell | Integração com API externa que recebe CPF, StoreId e dados do carrinho e retorna lista de itens recomendados | Planejada (outra task) |
| Task — Navegação para step Recomendar | Lógica de navegação do botão AVANÇAR entre Cliente → Recomendar (quando ativo) → Conferir | Planejada (outra task) |
Questões em Aberto
- Nenhuma questão em aberto identificada até o momento.