Decisões — Spike 198299 (grill-with-docs)
Data: 2026-07-09
Base: sp.md + pesquisa nos 5 repositórios
Nota: D1 foi proposta no grill interativo; UI de pergunta recusada — decisões por best judgment alinhadas ao código e à SP. Revisáveis antes da US de implementação.
Decisões
| # | Tema | Decisão | Racional |
|---|---|---|---|
| D1 | Persistência de “sem expiração” | Gravar DateTime.MaxValue no Elasticsearch; API/DTO/UI expõem null e o texto “Sem data de expiração” | Padrão já usado na vitrine da marca no mesmo serviço (ShowCaseBrandCommandHandler: Elastic não aceita null em campo de data). Filtros DateExpiration >= now e IsExpired() funcionam com MaxValue. Null persistido arrisca sumir em date_range; null na Store hoje vira default +31 via SetDateExpiration. |
| D2 | Escopo de tipos | Store + Recommendation. Brand/BrandSeller fora do CA | SP: menu Minhas + Histórico; atenção explícita de que marca é zzportal. |
| D3 | Default e máximo em dias | Manter +31 como no AS-IS do app/SetDateExpiration; documentar divergência com texto “+30” da SP | Evitar inventar terceiro número na Spike; alinhamento PO é pré-dev (Fase 0). |
| D4 | Default na criação vs limpar data | UI preenche default; se usuário limpa, backend grava sem expiração (MaxValue), sem reaplicar +N | CA-1 + CA-2: default da regra atual, mas remoção é ação explícita. |
| D5 | Vitrine já expirada | Fora do escopo mínimo: continua fora de listagens/get ativos | AS-IS não lista expiradas; reativação seria US à parte. |
| D6 | Unicidade Recommendation | “Sem expiração” = ativa para unicidade userId + customerCpf + storeId | Mesma definição de vitrine ativa. |
| D7 | Portal, web público, jobs | Sem mudança exigida pelos CAs | Marca já opcional; showcase-web não modela expiração; jobs de produto não expiram vitrine de vendedor. |
| D8 | Teto de dias no backend | Plano de US deve incluir validação de máximo no API se data informada (hoje só UI) | SP: “respeitando o máximo 30 dias”; backend AS-IS sem cap. |
| D-APP-8 | Edição Minhas — detectar remoção da data | Replicar padrão Histórico: _initialExpirationDate, hasExpirationDateChanged via _isSameDate (null ↔ data), picker sempre visível com clear, enableEditButton inclui mudança de expiração | Código AS-IS só habilita salvar se expirationDate != null; limpar data não habilita botão. Alinha D4 e Sem data de expiração. |
| D-APP-9 | UX clear no ZZDateFormField | Opção A: após clear, exibir texto legível no campo (não vazio); ícone X no suffix quando há data; texto parametrizável pelo caller (não hardcoded) | Alinha zzportal (isClearable + placeholder). Vitrines passam copy CA-4; demais telas mantêm hint atual. allowClear default false. |
| D-APP-10 | Serialização JSON dateExpiration | Chave sempre presente no body; valor JSON null quando sem prazo (não omitir chave) | Contrato explícito com API (D4); distingue clear intencional; testes de serialização validam "dateExpiration":null. |
| D-APP-11 | Dois fluxos de edição Minhas | Aplicar D-APP-8/D-APP-10 em detalhe e busca de produtos (show_case_search_products_controller): remover ?? +31 no PUT; setEditMode aceita DateTime? | AS-IS só corrigiria um caminho; editar produtos pela busca reaplicaria +31 após clear. |
| D-APP-12 | Picker Histórico — data mínima no edit | Amanhã (+1 dia), igual ao create; max +31 em create e edit | AS-IS edit permite hoje; create usa amanhã. Alinha API (GreaterThan(now)) e Minhas. |
| D-APP-13 | Deploy API + app | Simultâneo (mesmo release train); PR2 só após PR1 pronto; sem feature flag nesta US | App novo + API antiga quebra clear→null; RF-11. |
| D-APP-14 | Constante única +31 no zzapp | ShowCaseExpirationDefaults.maxDaysFromNow = 31 + noExpirationLabel | D3 pendente PO; espelha API ShowCaseExpirationRules; evita magic numbers espalhados. |
| D-APP-15 | Testes PR2 | Models obrigatórios; controllers desejáveis (_isSameDate, submit sem fallback) se acoplamento permitir teste unitário simples — senão, não forçar; smoke manual cobre controllers | Evita testes frágeis por autoclosure/GetX; prioriza D-APP-10. |
| D-APP-16 | loadData Minhas sincroniza expiração | Ao carregar detalhe/busca: expirationDate e _initialExpirationDate = valor da API (inclui null) | Sem sync, PUT só de nome envia null e converte vitrine em never-expire por engano. Histórico já faz. |
Decisões API (grill design showcase)
| # | Tema | Decisão | Racional |
|---|---|---|---|
| D-API-7 | Validação Store ByApp | Criar CreateShowCaseByAppValidator + UpdateShowCaseByAppValidator (regras de data compartilhadas): When(HasValue) + futura + cap MaxDaysFromNow | CreateShowCaseValidator atual é vazio e valida outro command; endpoint zzapp usa ByApp sem validator. D8/RF-08. |
| D-API-8 | Cap +31 na API | Inclusivo até início do dia (hoje + 31) em ToBrazillianTime(); regra compartilhada Store ByApp + Recommend | Paridade picker zzapp e filtros ES now/d; não usar instante Now.AddDays(31). |
| D-API-9 | Mínimo “data futura” na API | Estritamente após início de amanhã (dia civil BR via ToBrazillianTime()) — não GreaterThan(Now) | Paridade D-APP-12 (picker min amanhã); regra compartilhada com cap D-API-8. |
| D-API-10 | Escopo ToApiExpiration | Todos os DTOs de leitura Store/Recommend que expõem DateExpiration (inclui GET público) | Evita 9999-12-31 no JSON; RF-07 uniforme; showcase-web sem mudança de UX. |
| D-API-11 | SetDates() no create Recommend | Remover chamada no handler Recommend; manter método em ElasticBaseIndex | Só 2 call sites: Recommend create (L112) e Brand temp CSV (L167). No Recommend, DateExpiration do SetDates() é sempre sobrescrito — efeito útil é só DateCreated. PR1 espelha Store (DateCreated explícito + normalizer). Brand temp ainda depende do método; refatorar fora do CA. |
Contrato API recomendado (Store + Recommend)
Entrada dateExpiration | Significado | Persistência ES |
|---|---|---|
| Data futura (dentro do cap) | Expira nessa data | valor informado |
null | Sem data de expiração | DateTime.MaxValue |
| (cliente) default preenchido | Comportamento atual de prazo | data default enviada |
Default +N: responsabilidade primária do zzapp (já preenche). API não deve converter null em +31 (comportamento atual da Store que conflita com CA).
Saída DTO: se DateExpiration == DateTime.MaxValue → serializar/mapear como null (igual marca).
Cenários de borda
| Cenário | Comportamento esperado |
|---|---|
| Create Minhas, não toca no campo | Vai com default +31 (UI) → expira |
| Create Minhas, limpa data | null → MaxValue → “Sem data de expiração” |
| Edit: remove data de vitrine com prazo | MaxValue |
| Edit: adiciona data em “sem prazo” | data informada (cap UI/API) |
| Recommend já ativa sem prazo, tenta criar outra para mesmo CPF | Bloqueado (unicidade) |
| Link público após +N em vitrine com prazo | “Vitrine não encontrada” (AS-IS) |
| Link público em “sem prazo” após meses | Continua acessível se não deletada e produtos ok |
| Vitrine da marca | Inalterada nesta US |
Termos resolvidos no glossário
Ver CONTEXT.md: Vitrine de loja, Vitrine por histórico, Sem data de expiração, Vitrine ativa, Data de expiração vs exclusão.