Skip to main content

Documento de Requisitos — Criar tela de recomendação upsell

Tarefa: #193223
Contexto: Feature: Upsell


Visão Geral

O que é

Tela "Recomendar" no fluxo de venda do zzapp, exibida entre Cliente e Conferir quando as três condições de upsell forem atendidas (feature flag + loja habilitada + carrinho estoque loja), avaliadas pelo UpsellRecommendationController.isActive.

Como funciona

  1. Entra na tela → chama API externa (CPF, StoreId, itens do cart) → retorna até 3 produtos com dados de estoque da loja
  2. Cards exibem chips NumerationListText com indicadores: bolinha vermelha (estoque < 3) e cinza (sem estoque)
  3. Toque no trailing (plus_circle) → modal (AddProductBottomSheet adaptado) → seleciona tamanho → card mostra chip único + check verde
  4. ADICIONAR RECOMENDAÇÃO → itens selecionados viram CartItemDTO → salvos em cartDTO.recommendedItems (lista separada) → navega para Conferir
  5. NÃO QUERO RECOMENDAR → limpa seleções → navega para Conferir

Escopo desta task

IncluiNão inclui
Tela StatelessWidget + AppGetBuilderAPI de recomendação externa (outra task)
UpsellRecommendationCard (stand-alone)POST cart com recommendedItems (outra task)
UpsellRecommendationController (fetch, cache, retry)Seção recomendações no Conferir (outra task)
UpsellRecommendationItem (model de tela)Navegação step Recomendar (outra task)
CartDTO.recommendedItems + CartController.addRecommendedProducts()Feature flags (task-07)
Estados: loading, cards, vazio, erroController base isActive (task-08)

Cache e preservação

Estado mantido no controller (lazySingleton) enquanto activeCartDTO != null. Retorno à tela na mesma venda restaura cache sem re-fetch da API. Reset automático ao finalizar venda conforme ADR-010.


Papéis Envolvidos

  • Vendedor(a) — visualiza a tela de recomendação, seleciona tamanhos dos itens recomendados, decide quais recomendações adicionar ao carrinho ou pular o fluxo.

Requisitos Funcionais

RF-01 — Chamada à API de recomendação ao entrar na tela

História de Usuário:
Como sistema, devo chamar a API externa de recomendação ao entrar na tela de upsell, se ainda não tiver sido executada para a venda ativa.

Critérios de Aceitação:

  1. QUANDO a tela de recomendação for exibida E a chamada à API de recomendação ainda não tiver sido executada para a venda ativa ENTÃO o sistema DEVE disparar a chamada com os parâmetros: CPF do cliente, StoreId e itens do carrinho (SKU, size).

  2. QUANDO a chamada à API estiver em andamento ENTÃO a tela DEVE exibir skeleton de carregamento.

  3. QUANDO a API retornar resposta com sucesso ENTÃO a tela DEVE substituir o skeleton pela lista de cards ou empty state, conforme o resultado.

Casos de Borda:

  • API já chamada anteriormente para esta venda: não repetir chamada, usar dados em cache no controller.

RF-02 — Exibição da lista de itens recomendados

História de Usuário:
Como vendedor(a), quero visualizar cards dos produtos recomendados para decidir quais adicionar ao carrinho.

Critérios de Aceitação:

  1. QUANDO a API retornar itens recomendados ENTÃO a tela DEVE exibir cada item como um card usando o widget UpsellRecommendationCard.

  2. QUANDO a API retornar múltiplos itens ENTÃO a tela DEVE exibi-los em ListView vertical, um abaixo do outro.

  3. QUANDO a API retornar sucesso com lista vazia (0 itens) ENTÃO a tela DEVE exibir o empty state "Nenhuma recomendação encontrada" centralizado, utilizando o padrão ZzEmptyState/NoContentEmptyState do projeto.

  4. QUANDO a API retornar erro ENTÃO a tela DEVE exibir estado de erro centralizado via ZzEmptyState com: title "Ocorreu um problema", subtitle "Tivemos um problema ao carregar os dados.\nTente atualizar a página", image "assets/images/no_content.svg" (ou similar do projeto), e botão ZzFilledButton "TENTE NOVAMENTE" que chama controller.retry().

Casos de Borda:

  • API retorna mais de 3 itens: a API externa é responsável por limitar a no máximo 3.
  • Item com imagem nula: ZzUrlImage trata fallback internamente.
  • Botão retry no estado de erro: re-dispara fetchRecommendations() e volta ao skeleton.

RF-03 — Card de item recomendado — estado não selecionado

História de Usuário:
Como vendedor(a), quero visualizar todos os tamanhos disponíveis em estoque na loja para um item recomendado não selecionado.

Critérios de Aceitação:

  1. QUANDO o item NÃO estiver selecionado ENTÃO o card DEVE exibir chips NumerationListText com todos os tamanhos presentes em sizesStockStore, mapeados com lowQuantity: quantity < 3 e noStock: quantity == 0.

  2. QUANDO o item NÃO estiver selecionado ENTÃO o indicador trailing DEVE ser o ícone PhosphorIcons.plus_circle na cor primaryRegular.

  3. QUANDO o item NÃO estiver selecionado E a vendedora tocar no indicador trailing (plus_circle) ENTÃO o sistema DEVE abrir a modal de seleção de tamanho.

Casos de Borda:

  • Item sem nenhum tamanho com estoque na loja: todos os chips renderizados como noStock (cinza) com badge "Tamanho indisponível".
  • Tamanhos com quantidade < 3: exibir indicador de baixa quantidade (bolinha vermelha no canto superior direito do chip).
  • Toque no corpo do card (fora do trailing): nenhuma ação. Apenas o ícone trailing dispara a modal.

RF-04 — Card de item recomendado — estado selecionado

História de Usuário:
Como vendedor(a), quero visualizar o tamanho selecionado de um item recomendado e poder desselecioná-lo.

Critérios de Aceitação:

  1. QUANDO o item estiver selecionado ENTÃO o card DEVE exibir um único chip NumerationListText com o tamanho selecionado (lowQuantity: false, noStock: false), substituindo a lista completa de chips.

  2. QUANDO o item estiver selecionado ENTÃO o indicador trailing DEVE ser o ícone PhosphorIcons.check_circle_fill na cor primaryRegular.

  3. QUANDO o item estiver selecionado E a vendedora tocar no indicador trailing (check_circle_fill) ENTÃO o sistema DEVE desselecionar o item (selectedSize = null, volta ao estado não selecionado com todos os chips restaurados).

Casos de Borda:

  • Desselecionar item que era o único selecionado: botão ADICIONAR RECOMENDAÇÃO deve exibir modal de alerta ao ser clicado.
  • Toque no corpo do card (fora do trailing): nenhuma ação. Apenas o ícone trailing desseleciona.

RF-05 — Modal de seleção de tamanho

História de Usuário:
Como vendedor(a), quero selecionar um tamanho específico para o item recomendado através de uma modal.

Critérios de Aceitação:

  1. QUANDO a vendedora tocar no indicador trailing (plus_circle) de um card não selecionado ENTÃO o sistema DEVE abrir modal via GetxBottomSheet.showBottomSheet seguindo o padrão visual do AddProductBottomSheet (título "Selecione o tamanho", chips circulares 45x45, seção "Estoque da loja").

  2. QUANDO a modal estiver aberta ENTÃO o sistema DEVE permitir clique apenas em tamanhos com estoque > 0 na loja.

  3. QUANDO a vendedora selecionar um tamanho E clicar no botão "ADICIONAR PRODUTO" ENTÃO o sistema DEVE fechar a modal, marcar o item como selecionado com o tamanho escolhido e atualizar o card para estado selecionado.

  4. QUANDO a vendedora fechar a modal sem clicar em "ADICIONAR PRODUTO" ENTÃO o sistema NÃO DEVE alterar o estado do card que abriu a modal.

Casos de Borda:

  • Botão ADICIONAR PRODUTO permanece desabilitado até um tamanho ser selecionado.
  • Produto com apenas 1 tamanho em estoque: modal abre normalmente exibindo o único chip disponível. Não há auto-seleção — vendedora precisa tocar no chip e confirmar.
  • Modal usa AppGetBuilder ou lógica local (UpsellRecommendationController) para reatividade da seleção.

RF-06 — Skeleton de carregamento

História de Usuário:
Como vendedor(a), quero ver um indicador de carregamento enquanto as recomendações são buscadas.

Critérios de Aceitação:

  1. QUANDO a tela de recomendação estiver carregando os dados da API ENTÃO o sistema DEVE exibir skeleton de carregamento no lugar da lista.

  2. QUANDO os dados forem recebidos com sucesso (lista ou vazio) ENTÃO o sistema DEVE substituir o skeleton pela lista de cards ou empty state.

  3. QUANDO a API retornar erro ENTÃO o sistema DEVE substituir o skeleton pelo estado de erro centralizado (título, descrição, botão TENTE NOVAMENTE).

Casos de Borda:

  • Skeleton deve ter aparência consistente com o layout dos cards (altura, espaçamento) para evitar salto de layout.
  • Reutilizar padrão de skeleton existente no projeto (ex: GridViewShimmer adaptado para lista).
  • Botão TENTE NOVAMENTE no estado de erro: ao ser pressionado, retorna ao skeleton e re-dispara fetchRecommendations().

RF-07 — Botão ADICIONAR RECOMENDAÇÃO

História de Usuário:
Como vendedor(a), quero confirmar as recomendações selecionadas e salvá-las no carrinho.

Critérios de Aceitação:

  1. QUANDO o skeleton for finalizado e a lista de itens estiver visível ENTÃO o botão ADICIONAR RECOMENDAÇÃO DEVE iniciar habilitado.

  2. QUANDO a vendedora clicar no botão SEM nenhum item com selectedSize != null ENTÃO o sistema DEVE exibir modal de alerta informando que é necessário selecionar ao menos um tamanho de um produto.

  3. QUANDO a vendedora clicar no botão COM ao menos um item no estado selecionado ENTÃO o sistema DEVE: filtrar itens com isSelected == true, converter cada um para CartItemDTO (sku, name, image, size: selectedSize, quantity: 1, price, fullPrice, discountType: none, discountValue: 0), salvar a lista em cartDTO.recommendedItems via CartController.addRecommendedProducts(), e navegar para a fase de conferir produtos.

  4. QUANDO a tela estiver em estado de erro (API falhou) ENTÃO o botão ADICIONAR RECOMENDAÇÃO DEVE permanecer desabilitado.

  5. QUANDO a vendedora retornar à tela de recomendação após já ter salvo recomendações ENTÃO o botão DEVE permitir alterar seleções (adicionar/remover/trocar tamanho) e salvar novamente (sobrescreve recommendedItems anteriores).

Casos de Borda:

  • recommendedItems não compõe itemsTotal/total do cart — são itens sugestivos, não itens do carrinho.
  • Botão desabilitado em estado de erro: reabilitado quando fetchRecommendations() retornar com sucesso.

RF-08 — Botão NÃO QUERO RECOMENDAR

História de Usuário:
Como vendedor(a), quero pular o fluxo de recomendação e ir direto para conferir o pedido.

Critérios de Aceitação:

  1. QUANDO o skeleton for finalizado ENTÃO o botão NÃO QUERO RECOMENDAR DEVE iniciar habilitado e permanecer habilitado em todos os estados (lista, vazio, erro).

  2. QUANDO a vendedora clicar no botão ENTÃO o sistema DEVE alterar o estado de todos os cards para não selecionado (selectedSize = null).

  3. QUANDO a vendedora clicar no botão ENTÃO o sistema DEVE navegar para a fase de conferir pedido.

Casos de Borda:

  • Navegação deve ocorrer mesmo se houver itens previamente selecionados — estes são limpos antes de avançar.
  • Botão disponível mesmo durante estado de erro: vendedora pode desistir do fluxo upsell a qualquer momento.

RF-09 — Model UpsellRecommendationItem

História de Usuário:
Como desenvolvedor, preciso de uma model dedicada para os itens de recomendação upsell que gerencia o estado de seleção e alimenta os chips NumerationListText.

Critérios de Aceitação:

  1. A model DEVE conter os campos de produto: id, sku, name, image, price, fullPrice, sizesStockStore, hasStock.

  2. A model DEVE conter campo mutável selectedSize (String?) para estado de seleção da tela.

  3. A model DEVE expor getter isSelected (retorna selectedSize != null).

  4. A model DEVE expor getter hasMarkdown (retorna fullPrice != null && fullPrice! > price).

  5. A model DEVE expor método List<Widget> buildSizeChips({String? highlightSize}) que retorna lista de NumerationListText para todos os sizesStockStore, com lowQuantity: quantity < 3 e noStock: quantity == 0. Se highlightSize for informado, apenas o chip correspondente é retornado.

  6. A model DEVE possuir método toProduct() que retorna Product preenchendo os campos consumidos por padrões existentes (imagem, nome, SKU, preço, markdown, hasStock).

Casos de Borda:

  • image pode ser nulo: toProduct() deve tratar com images vazio e ZzUrlImage usa fallback interno.
  • fullPrice nulo: hasMarkdown retorna false, preço exibido normalmente.
  • sizesStockStore vazio: buildSizeChips retorna lista vazia; card exibe badge "Tamanho indisponível".

RF-10 — Card UpsellRecommendationCard (stand-alone)

História de Usuário:
Como desenvolvedor, preciso de um card dedicado para itens de recomendação upsell que renderiza tamanhos como chips NumerationListText com indicadores de estoque.

Critérios de Aceitação:

  1. O card DEVE ser um StatelessWidget com layout similar ao NewCardProductList: Container com Row (ZzUrlImage 60x60 + Column de detalhes + trailing).

  2. O card DEVE receber um UpsellRecommendationItem e callbacks onToggle (selecionar/desselecionar) e onSelectSize (abrir modal).

  3. QUANDO o item estiver selecionado ENTÃO a seção de tamanhos DEVE exibir um único chip NumerationListText com o size selecionado (lowQuantity: false, noStock: false).

  4. QUANDO o item NÃO estiver selecionado ENTÃO a seção de tamanhos DEVE exibir Wrap de chips NumerationListText para todos os sizesStockStore, mapeados conforme item.buildSizeChips().

  5. O trailing DEVE ser um GestureDetector com Icon(PhosphorIcons.plus_circle, color: primaryRegular, size: 20) quando não selecionado e Icon(PhosphorIcons.check_circle_fill, color: primaryRegular, size: 20) quando selecionado.

  6. QUANDO o item estiver selecionado E o usuário tocar no trailing ENTÃO o sistema DEVE chamar onToggle() para desselecionar.

  7. QUANDO o item NÃO estiver selecionado E o usuário tocar no trailing ENTÃO o sistema DEVE chamar onSelectSize() para abrir a modal.

  8. QUANDO hasStock == false ENTÃO o badge "Tamanho indisponível" DEVE ser renderizado abaixo dos detalhes.

  9. O corpo do card (fora do trailing) NÃO DEVE ter ação de toque. Apenas o trailing é interativo.

Casos de Borda:

  • Item com sizesStockStore vazio: seção de tamanhos vazia, badge "Tamanho indisponível" visível.
  • Desselecionar item que era o único selecionado: botão ADICIONAR RECOMENDAÇÃO deve ficar sem efeito (exibe modal de alerta no clique).

RF-11 — Integração com UpsellRecommendationController

História de Usuário:
Como desenvolvedor, preciso estender o UpsellRecommendationController (criado na task-08) com o estado da tela de recomendação.

Critérios de Aceitação:

  1. O controller DEVE expor RxList<UpsellRecommendationItem> recommendedItems para a lista de itens.

  2. O controller DEVE expor RxBool isLoading e RxBool hasError para controle de estado da tela.

  3. O controller DEVE expor RxBool hasLoadedRecommendations que indica se já carregou dados da API para a venda atual.

  4. O controller DEVE possuir método fetchRecommendations() que: se hasLoadedRecommendations == false, chama a API externa; se true, apenas exibe o cache (sem re-fetch). Após sucesso, seta hasLoadedRecommendations = true.

  5. O controller DEVE possuir método retry() que redefine hasError = false, isLoading = true e re-dispara fetchRecommendations().

  6. O controller DEVE possuir método toggleItemSelection(UpsellRecommendationItem item, String? size) que: se size != null, define selectedSize = size; se size == null, define selectedSize = null (desseleciona).

  7. O controller DEVE possuir método skipAllRecommendations() que limpa selectedSize de todos os itens no recommendedItems.

  8. O controller DEVE possuir getter hasAnySelected que retorna true se ao menos um item tem isSelected == true.

  9. O controller DEVE subscrever-se a OpenSalesController.activeCartDTO. QUANDO activeCartDTO emitir null ENTÃO o controller DEVE resetar: recommendedItems.clear(), hasLoadedRecommendations = false, hasError = false, isLoading = false, isActive = false.

Casos de Borda:

  • fetchRecommendations() com API inexistente: retornar hasError = true (fallback para estado de erro).
  • retry() chamado durante carregamento: ignorar (isLoading já true).
  • Ao sair e retornar para a tela na mesma venda: estado preservado (cache ativo), sem re-fetch.

RF-12 — Estrutura da tela (StatelessWidget + AppGetBuilder)

História de Usuário:
Como desenvolvedor, a tela deve seguir os padrões arquiteturais do zzapp: StatelessWidget com AppGetBuilder.

Critérios de Aceitação:

  1. A tela DEVE ser um StatelessWidget.

  2. A tela DEVE acessar o controller via getIt.get<UpsellRecommendationController>().

  3. A tela DEVE embrulhar o corpo em AppGetBuilder(init: controller, builder: () { ... }).

  4. A tela DEVE possuir ZzAppBar com título da página e NewSellHomeButton.getActions().

  5. A tela DEVE possuir bottomNavigationBar com os dois botões: ADICIONAR RECOMENDAÇÃO (ZzFilledButton, expanded) e NÃO QUERO RECOMENDAR (texto secundário).

  6. QUANDO o controller.hasError == true ENTÃO o corpo DEVE renderizar o estado de erro centralizado (ZzEmptyState com title, subtitle, botão TENTE NOVAMENTE).

  7. QUANDO o controller.isLoading == true ENTÃO o corpo DEVE renderizar o skeleton de carregamento.

  8. QUANDO a tela for construída e controller.hasLoadedRecommendations == false ENTÃO o sistema DEVE disparar fetchRecommendations() no initState do AppGetBuilder.

  9. QUANDO a tela for construída e controller.hasLoadedRecommendations == true ENTÃO o sistema DEVE exibir os itens em cache sem disparar fetch.

Casos de Borda:

  • AppGetBuilder aninhado: se a tela pai (NewSellScreen) também usa AppGetBuilder, o rebuild do pai recria o AppGetBuilder interno — comportamento esperado. O cache no controller (lazySingleton) garante que o estado sobrevive ao rebuild.
  • Bottom bar visível durante estado de erro: ADICIONAR RECOMENDAÇÃO desabilitado, NÃO QUERO RECOMENDAR habilitado.
  • Bottom bar visível durante skeleton: ambos os botões visíveis mas sem ação até carregamento concluir.
  • Retorno à tela após salvar recomendações: tela restaura estado do cache (itens + seleções preservadas), vendedora pode alterar seleções e salvar novamente.

RF-13 — Campo recommendedItems no CartDTO

História de Usuário:
Como desenvolvedor, preciso que o CartDTO suporte uma lista separada de itens recomendados que não compõem o total do carrinho.

Critérios de Aceitação:

  1. CartDTO DEVE possuir campo List<CartItemDTO>? recommendedItems (nullable, default null).

  2. recommendedItems NÃO DEVE compor os cálculos de itemsTotal nem total do cart.

  3. O campo DEVE ser serializado em toJson() e deserializado em CartDTO.fromJson().

  4. O campo DEVE ser suportado no copyWith() com o padrão Value()/Nil() existente.

  5. A extension MutableCartDTO DEVE possuir método addRecommendedItems(List<CartItemDTO> items) que substitui recommendedItems pela nova lista.

Casos de Borda:

  • cart vazio (cartItems.isEmpty) com recommendedItems preenchido: permitido — recommendedItems são independentes.
  • recommendedItems vazio ou null após salvar: copyWith(recommendedItems: Value([])) para lista vazia; copyWith(recommendedItems: const Nil()) para null.
  • Apenas itens com selectedSize != null viram CartItemDTO: filtro é responsabilidade do caller (addRecommendedProducts), não do CartDTO.

RF-14 — CartController.addRecommendedProducts()

História de Usuário:
Como desenvolvedor, preciso de um método no CartController que converta UpsellRecommendationItem selecionados em CartItemDTO e os salve no cartDTO.recommendedItems.

Critérios de Aceitação:

  1. O CartController DEVE possuir método addRecommendedProducts(List<UpsellRecommendationItem> items).

  2. O método DEVE filtrar apenas itens com isSelected == true.

  3. Para cada item filtrado, o método DEVE criar um CartItemDTO com: productId = item.id, name = item.name, sku = item.sku, image = item.image, quantity = 1, size = item.selectedSize, unitPrice = item.fullPrice ?? item.price, unitPriceWithDiscount = item.price, discountType = CartItemDTODiscountType.none, discountValue = 0, brand = item.brand, hasStock = item.hasStock.

  4. O método DEVE usar o padrão changeWith() (síncrono, sem chamada de API) para mutar o cartDTO, chamando cart.addRecommendedItems(cartItemDTOs).

  5. Itens sem selectedSize (isSelected == false) NÃO DEVEM ser incluídos na lista.

Casos de Borda:

  • Lista de entrada vazia ou sem itens selecionados: recommendedItems é setado como lista vazia ([]).
  • Chamadas subsequentes: sobrescreve recommendedItems anteriores (comportamento de replace, não append).
  • Se cartDTO for null (sem venda ativa): método não deve executar; logar warning.

RF-15 — Preservação de estado da tela na mesma venda

História de Usuário:
Como vendedor(a), quero que minhas seleções na tela de recomendação sejam preservadas enquanto o atendimento estiver em aberto, para poder revisar e ajustar as recomendações antes de finalizar.

Critérios de Aceitação:

  1. QUANDO a vendedora navegar para fora da tela de recomendação (ex: Conferir) E retornar na mesma venda ENTÃO o sistema DEVE exibir a lista de recomendações e seleções exatamente como estavam, sem re-fetch da API.

  2. QUANDO a vendedora retornar à tela ENTÃO ela DEVE poder alterar seleções: adicionar novos tamanhos, trocar tamanho, desselecionar itens, ou selecionar itens antes não selecionados.

  3. QUANDO a vendedora clicar em ADICIONAR RECOMENDAÇÃO novamente ENTÃO o sistema DEVE sobrescrever recommendedItems no CartDTO com a nova lista de seleções.

  4. QUANDO o atendimento for finalizado (activeCartDTO → null) ENTÃO o sistema DEVE limpar todo o estado: recommendedItems.clear(), hasLoadedRecommendations = false, hasError = false, isLoading = false, isActive = false.

  5. QUANDO uma nova venda for iniciada ENTÃO o controller DEVE estar limpo (resetado) e fetchRecommendations() DEVE ser disparado novamente ao entrar na tela.

Casos de Borda:

  • Vendedora seleciona itens → ADICIONAR RECOMENDAÇÃO → Conferir → volta → desseleciona tudo → ADICIONAR RECOMENDAÇÃO: recommendedItems fica vazio ([]), permitido.
  • Vendedora inicia venda → vai para Recomendação → volta para Produtos → adiciona mais produtos → volta para Recomendação: estado preservado, sem re-fetch.
  • Vendedora clica no botão Home (NewSellHomeButton) e inicia nova venda: activeCartDTO → null → controller reseta → nova tela faz fetch limpo.

Dependências

DependênciaDescriçãoStatus
Task 08 — Stepper condicionalCriação do UpsellRecommendationController (lazySingleton, GetxController, isActive, auto-reset) e registro em get_it.dart. Sem esta task, o controller não existe no escopo de DI.Pendente
Task 07 — Feature flags upsellRegistro das chaves enableUpsellRecommendation e enableUpsellRecommendationByCodeStore no FirebaseRemoteConfigKeysEnum e FirebaseRemoteConfigServiceDictionary. Necessário para o controller avaliar isActive.Pendente
API de recomendação externaEndpoint que recebe CPF do cliente, StoreId e itens do carrinho (SKU, size) e retorna lista de produtos recomendados (formato similar a busca de produtos, máximo 3 itens). Desenvolvida em outra task.Pendente
API de persistência (POST cart)Alteração do endpoint POST cart para receber propriedade opcional recommendedItems no body, contendo os itens com tamanhos selecionados. Desenvolvida em outra task.Pendente
Tela de Conferir — seção de recomendaçõesNova seção na tela de Conferir (CheckSellScreen) para exibir cartDTO.recommendedItems como cards similares aos cartItems, com suporte a desconto por item. Desenvolvida em outra task.Pendente
Navegação para step RecomendarLógica de navegação do botão AVANÇAR entre Cliente → Recomendar (quando upsell ativo) → Conferir. Necessário para a tela ser alcançável no fluxo de venda.Pendente

Questões em Aberto

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