This commit is contained in:
Daniel Arantes Loverde
2026-03-19 08:44:31 -03:00
parent 5fa98ce358
commit c49725a5f7
16 changed files with 1496 additions and 147 deletions

744
subdomain_stores_plan.md Normal file
View File

@@ -0,0 +1,744 @@
# Subdomain Stores Plan
## Objetivo
Reorganizar o ecossistema do PediFoods para um modelo claro de subdomínios, mantendo a integração com o backend do Atomenta onde ela faz sentido, mas separando melhor identidade, navegação e contexto de cada área.
## Cenário Desejado
### 1. `pedifoods.com.br`
Papel:
- home pública
- landing page
- login
- onboarding inicial
- após login, exibir lista de lojas do usuário
Responsabilidade:
- identidade pública do PediFoods
- autenticação do usuário final/parceiro
- seleção da loja
### 2. `parceiro.pedifoods.com.br`
Papel:
- painel do lojista
- equivalente ao atual "Atomenta Store"
- gestão operacional da loja
Responsabilidade:
- backoffice do parceiro
- configuração da loja
- cardápio
- pedidos
- financeiro
- onboarding do parceiro
### 3. `nome-curto-loja.pedifoods.com.br`
Papel:
- storefront da loja
- vitrine pública ou semi-logada da loja
- produtos, categorias, carrinho e experiência do cliente
Responsabilidade:
- experiência da loja individual
- branding e contexto por loja
- leitura do catálogo
## Princípio Arquitetural
O PediFoods não deve mais parecer "um Atomenta renomeado".
Ele pode continuar:
- consumindo APIs do Atomenta
- reutilizando lógica de OTP
- reutilizando cookies/tokens e integração segura quando necessário
Mas deve deixar claro:
- qual domínio é PediFoods
- qual domínio é painel parceiro
- qual domínio é storefront da loja
## Direção Técnica
### Separar 3 contextos
1. `public app`
- domínio base `pedifoods.com.br`
- homepage, login, seleção de lojas
2. `partner app`
- subdomínio fixo `parceiro.pedifoods.com.br`
- painel do lojista
3. `store app`
- subdomínio dinâmico `{slug}.pedifoods.com.br`
- storefront da loja
### Manter 1 base de código no curto prazo
No curto prazo, o melhor custo-benefício é manter uma única codebase do PediFoods com roteamento por host.
Ou seja:
- o app lê o `Host`
- identifica o contexto
- renderiza a área correta
Exemplo de resolução:
- `pedifoods.com.br` => contexto `public`
- `parceiro.pedifoods.com.br` => contexto `partner`
- `slug.pedifoods.com.br` => contexto `store`
Isso evita:
- duplicação de projeto
- deploy múltiplo desnecessário
- divergência de regra de negócio
## Regras de Resolução de Host
### Contexto `public`
Hosts:
- `pedifoods.com.br`
- `www.pedifoods.com.br` se existir
Fluxos:
- home
- login
- cadastro
- recuperação de conta
- listagem de lojas do usuário autenticado
### Contexto `partner`
Host:
- `parceiro.pedifoods.com.br`
Fluxos:
- login do parceiro
- dashboard da loja
- onboarding
- catálogo
- pedidos
- financeiro
### Contexto `store`
Host:
- `{storeSlug}.pedifoods.com.br`
Fluxos:
- catálogo público da loja
- detalhes da loja
- carrinho
- pedidos do cliente
## Mudanças Estruturais Necessárias
### 1. Resolver host de forma centralizada
Criar uma camada única para:
- ler `req.hostname`
- classificar o contexto
- extrair `storeSlug` quando aplicável
Saída esperada:
- `appContext.type = public | partner | store`
- `appContext.storeSlug = string | null`
### 2. Parar de usar defaults de domínio do Atomenta no PediFoods
Hoje o PediFoods ainda carrega vários defaults como:
- `https://atomenta.com.br`
- `atomenta_token`
- `atomenta_auth`
- nomes e textos herdados
Isso precisa ser separado em 2 grupos:
#### Pode continuar apontando para Atomenta
- integração de API interna
- OTP/crypto
- comunicação backend-to-backend
- consulta de dados mestres, se esta for a arquitetura
#### Não deve mais apontar para Atomenta
- branding
- domínio público
- base URL de navegação do usuário
- nomes do app
- `package.json` e `ecosystem.config.js`
### 3. Definir estratégia de sessão/cookies
Decisão importante:
O sistema terá **dois contextos de autenticação diferentes**:
1. **partner session**
- usada no `parceiro.pedifoods.com.br`
- representa o lojista/operador
- dá acesso ao painel administrativo
2. **customer session**
- usada em `pedifoods.com.br` e em `{slug}.pedifoods.com.br`
- representa o cliente/comprador
- dá acesso à navegação, seleção de lojas, carrinho e compra
Essas duas sessões **não devem ser compartilhadas entre si**.
Se fossem compartilhadas:
- um usuário logado como parceiro poderia cair autenticado na storefront da própria loja
- haveria risco de misturar permissões administrativas com experiência de compra
- o modelo de segurança e UX ficaria ambíguo
#### Opção A
Sessão única compartilhada em `.pedifoods.com.br`
Resultado:
- mesma sessão valeria para:
- `pedifoods.com.br`
- `parceiro.pedifoods.com.br`
- `{slug}.pedifoods.com.br`
Problema:
- **não serve para este cenário**
- mistura sessão de lojista com sessão de cliente
#### Opção B
Sessões separadas por contexto
Modelo recomendado:
- `parceiro.pedifoods.com.br`
- cookie próprio de parceiro
- escopo exclusivo do painel
- `pedifoods.com.br`
- cookie próprio de cliente
- pode ser compartilhado com `{slug}.pedifoods.com.br` se fizer sentido para a jornada do comprador
- `{slug}.pedifoods.com.br`
- usa sessão do cliente, nunca sessão do parceiro
Vantagens:
- separa claramente os perfis
- evita vazamento de autenticação entre painel e loja
- reduz risco de autorização indevida
- deixa a jornada de compra independente da jornada administrativa
Desvantagens:
- exige mais cuidado no desenho de login/logout
- exige nomes de cookies e middleware distintos
Recomendação:
- usar **sessões separadas por contexto**
- nunca compartilhar sessão de `partner` com `customer`
- se necessário, compartilhar apenas a sessão do **cliente** entre:
- `pedifoods.com.br`
- `{slug}.pedifoods.com.br`
Implementação sugerida:
- cookie de parceiro:
- exemplo `pedifoods_partner_session`
- domínio restrito a `parceiro.pedifoods.com.br`
- cookie de cliente:
- exemplo `pedifoods_customer_session`
- domínio `.pedifoods.com.br` apenas se a jornada do cliente precisar navegar entre domínio principal e subdomínios de loja
## Estratégia de Dados
### Fonte de verdade
Precisamos assumir explicitamente:
- o Atomenta continua sendo a fonte de verdade operacional
- o PediFoods atua como camada especializada de experiência/lojista/storefront
Isso significa:
- login e sessão podem existir no PediFoods
- mas dados de loja, catálogo, usuário parceiro e regras sensíveis podem continuar vindo do Atomenta via API
## Fases de Implementação
## Fase 1. Higiene e identidade
Objetivo:
- remover confusão estrutural atual
Itens:
- corrigir `package.json` do PediFoods
- corrigir `ecosystem.config.js`
- revisar `env.example`
- revisar nomes, textos e defaults públicos
- separar claramente o que é branding PediFoods e o que é integração Atomenta
Saída:
- o projeto deixa de se identificar como Atomenta
## Fase 2. Resolver host e contexto
Objetivo:
- introduzir leitura de subdomínio
Itens:
- criar utilitário central de hostname
- classificar `public`, `partner`, `store`
- disponibilizar contexto em middleware
- ajustar templates/layouts e rotas para reagirem ao contexto
Saída:
- uma única app responde diferente por host
## Fase 3. Fluxo público `pedifoods.com.br`
Objetivo:
- consolidar homepage + login + listagem de lojas
Itens:
- manter home atual
- após login, mostrar lojas do usuário
- ao clicar na loja, redirecionar para `slug.pedifoods.com.br`
Saída:
- domínio principal vira hub do usuário
## Fase 4. Fluxo parceiro `parceiro.pedifoods.com.br`
Objetivo:
- isolar o backoffice do lojista
Itens:
- mover/ajustar as telas que hoje se comportam como "Atomenta Store"
- garantir que esse host sempre abra painel parceiro
- revisar permissões e middleware
Saída:
- painel do lojista com identidade própria
## Fase 5. Fluxo loja `slug.pedifoods.com.br`
Objetivo:
- transformar storefront em domínio por loja
Itens:
- mapear slug -> loja
- carregar tema, catálogo e dados por slug
- revisar SEO/meta tags
- revisar links absolutos
Saída:
- cada loja com seu subdomínio
## Fase 6. Sessão compartilhada
Objetivo:
- permitir navegação fluida entre subdomínios
Itens:
- cookie domain `.pedifoods.com.br`
- revisar login/logout
- revisar redirecionamentos
- revisar callbacks OTP
Saída:
- usuário loga uma vez e navega entre contextos compatíveis
## Riscos
### 1. Misturar branding com integração
Se trocar tudo de `atomenta` cegamente, pode quebrar integrações reais.
### 2. Cookies atuais
Se hoje os cookies ainda usam nomes herdados, uma troca brusca pode invalidar sessões e OTP.
### 3. Links absolutos hardcoded
Há muitos pontos do PediFoods ainda apontando para Atomenta.
Eles precisam ser classificados antes de alterar.
### 4. Deploy atual
Hoje o deploy sobe um projeto que ainda está semanticamente misturado.
Sem limpeza mínima, a arquitetura nova vai nascer confusa.
## Recomendações Práticas
### Curto prazo
- corrigir identidade mínima do projeto PediFoods
- mapear todos os usos de:
- `atomenta.com.br`
- `atomenta_token`
- `atomenta_auth`
- `ATOMENTA_*`
- classificar cada ocorrência em:
- branding
- integração legítima
### Médio prazo
- implementar middleware de contexto por host
- separar layouts `public`, `partner`, `store`
- começar pelo domínio `parceiro.pedifoods.com.br`
### Longo prazo
- consolidar storefront por slug
- revisar autenticação multi-subdomínio
- avaliar se no futuro compensa separar em múltiplos apps
## Ordem Recomendada de Execução
1. Higienizar identidade do projeto PediFoods
2. Mapear referências herdadas de Atomenta
3. Criar resolvedor central de host/subdomínio
4. Subir `parceiro.pedifoods.com.br`
5. Ajustar `pedifoods.com.br` para login + lista de lojas
6. Implementar `slug.pedifoods.com.br`
7. Revisar cookies e sessão compartilhada
## Decisão Recomendada Agora
A melhor decisão agora é:
- **não tentar “desatomentar” tudo de uma vez**
- primeiro separar:
- identidade pública
- painel parceiro
- storefront por subdomínio
- e só depois revisar dependências herdadas uma a uma
## Próximo Passo
Próxima entrega recomendada:
1. fazer uma auditoria do repositório PediFoods
2. classificar referências herdadas em:
- `pode trocar agora`
- `depende de integração com Atomenta`
3. implementar o resolvedor de subdomínio
4. subir primeiro o contexto `parceiro.pedifoods.com.br`
## Tasks de Implementação
Legenda:
- 🔴 pendente
- 🟢 concluído
### Fase 1. Higiene do projeto PediFoods
- 🟢 Corrigir identidade básica do projeto em `package.json`
- 🟢 Corrigir identidade básica do processo em `ecosystem.config.js`
- 🔴 Revisar `env.example` para separar branding PediFoods de integração Atomenta
- 🟢 Mapear todas as referências a `atomenta` no repositório
- 🟢 Classificar cada referência como:
- branding/UI
- integração técnica legítima
- legado morto
- 🟢 Remover ou ajustar branding incorreto do PediFoods
### Fase 2. Contexto de domínio e subdomínio
- 🟢 Criar utilitário central para resolver o host atual
- 🟢 Criar classificador de contexto:
- `public`
- `partner`
- `store`
- 🟢 Extrair `storeSlug` automaticamente quando o host for `{slug}.pedifoods.com.br`
- 🟢 Injetar esse contexto em middleware global
- 🟢 Expor o contexto para views/templates
- 🔴 Garantir fallback seguro para domínio desconhecido
### Fase 3. Sessões separadas
- 🟢 Criar sessão de parceiro independente
- 🔴 Criar sessão de cliente independente
- 🔴 Garantir que `parceiro.pedifoods.com.br` nunca reutilize sessão de cliente
- 🔴 Garantir que `{slug}.pedifoods.com.br` nunca reutilize sessão de parceiro
- 🟢 Revisar nomes dos cookies
- 🟢 Revisar `login`, `logout` e middleware de autenticação
- 🟢 Revisar redirecionamentos pós-login por contexto
### Fase 4. `pedifoods.com.br`
- 🟢 Consolidar a home pública
- 🟢 Consolidar login/cadastro/recuperação
- 🔴 Criar tela pós-login com listagem de lojas do usuário
- 🟢 Fazer o clique na loja redirecionar para `{slug}.pedifoods.com.br`
- 🟢 Revisar meta tags, canonical e links absolutos
### Fase 5. `parceiro.pedifoods.com.br`
- 🟢 Isolar o painel do lojista nesse host
- 🟢 Revisar middleware e permissões do painel
- 🟢 Revisar links internos do painel para não mandar ao domínio errado
- 🟢 Ajustar onboarding do parceiro
- 🟢 Ajustar rotas administrativas de loja, pedidos, catálogo e financeiro
- 🟢 Garantir que o painel não renderize a experiência pública da loja
### Fase 6. `{slug}.pedifoods.com.br`
- 🟢 Criar resolução da loja por slug
- 🟢 Buscar loja e catálogo pelo slug
- 🟢 Renderizar storefront por loja
- 🟢 Ajustar tema, título, descrição e identidade da loja
- 🟢 Ajustar links compartilháveis da loja
- 🟢 Revisar SEO e OpenGraph por loja
- 🔴 Garantir fallback para slug inexistente
### Fase 7. Gestão de slug da loja
- 🟢 Adicionar campo `storeSlug` como atributo persistido da loja
- 🟢 Validar unicidade global do slug
- 🟢 Validar formato do slug
- 🟢 Gerar pré-preenchimento automático do slug no fim do onboarding com base no nome fantasia da loja
- 🟢 Criar tela de configuração do slug no painel parceiro
- 🟢 Exibir aviso de impacto da troca de slug
- 🟢 Salvar data da última alteração de slug
- 🟢 Bloquear nova alteração por 90 dias
- 🟢 Exibir contador ou data de desbloqueio para nova alteração
- 🔴 Criar redirect temporário do slug antigo para o novo, se viável
### Fase 8. Integração com Atomenta
- 🟢 Revisar quais chamadas devem continuar indo ao Atomenta
- 🟢 Centralizar base URL interna da API do Atomenta
- 🔴 Revisar OTP e autenticação compartilhada
- 🟢 Revisar cookies herdados `atomenta_*`
- 🔴 Revisar endpoints de catálogo, loja, financeiro e customer lookup
- 🔴 Remover dependências herdadas desnecessárias
### Fase 9. Deploy e infraestrutura
- 🔴 Ajustar nginx/proxy para:
- `pedifoods.com.br`
- `parceiro.pedifoods.com.br`
- `*.pedifoods.com.br`
- 🔴 Validar wildcard localmente antes da etapa de DNS público
- 🔴 Garantir certificado TLS compatível com wildcard
- 🔴 Validar resolução correta de host até a app Node
## Status Atual
Legenda:
- 🟢 concluído
- 🟡 parcial
- 🔴 pendente
Resumo do que já foi entregue no app:
- 🟢 identidade básica do projeto corrigida
- 🟢 resolução central de host/subdomínio
- 🟢 separação inicial entre `public`, `partner` e `store`
- 🟢 slug canônico com preview, unicidade, cooldown de 90 dias e geração automática no onboarding
- 🟢 storefront canônica em `{slug}.pedifoods.com.br`
- 🟢 links públicos e links do painel abrindo a loja publicada correta
- 🟢 limpeza principal de branding visível do PediFoods
- 🟢 frontend priorizando `pedifoods_partner_token` com fallback para legado
O que ainda falta para fechar o fluxo completo:
- 🔴 autenticação real do cliente
- 🔴 tela pós-login do cliente com listagem de lojas
- 🔴 fallback para slug inexistente
- 🔴 redirect temporário de slug antigo para slug novo
- 🔴 wildcard real em proxy/Cloudflare/TLS
- 🔴 Validar deploy do PediFoods sem contaminar com Atomenta
- 🔴 Configurar wildcard DNS no Cloudflare
- esta task deve ficar por último
- quando chegarmos nela, você executa manualmente no Cloudflare
- eu só vou te avisar e te passar o valor exato
### Fase 10. Testes de ponta a ponta
- 🔴 Testar login de cliente em `pedifoods.com.br`
- 🔴 Testar seleção de loja e redirecionamento para `{slug}.pedifoods.com.br`
- 🔴 Testar login de parceiro em `parceiro.pedifoods.com.br`
- 🔴 Testar isolamento entre sessão de parceiro e sessão de cliente
- 🔴 Testar troca de slug com bloqueio de 90 dias
- 🔴 Testar slug inválido/inexistente
- 🔴 Testar logout em cada contexto
- 🔴 Testar links absolutos, redirects e cookies
## Política de Slug da Loja
Sua ideia funciona e faz sentido.
### Recomendação
O slug deve ser:
- criado uma primeira vez no onboarding da loja
- editável pelo parceiro no painel
- bloqueado por 90 dias após cada alteração
### Motivos para essa regra
- evita troca frequente de URL
- reduz confusão para clientes recorrentes
- protege links já compartilhados
- reduz impacto operacional em cache, indexing e hábito do usuário
### Como funcionaria
Cada loja teria campos como:
- `storeSlug`
- `slugUpdatedAt`
- `slugLockedUntil`
- `slugGeneratedBySystemAt`
- `slugFirstManualUpdateAt`
Regra:
- se nunca definiu slug:
- o sistema pré-preenche automaticamente
- o usuário pode ajustar antes de concluir
- se já definiu:
- só pode alterar novamente após 90 dias
### Geração inicial do slug
Recomendação:
- ao terminar o onboarding, gerar um pré-preenchimento automático baseado no nome fantasia
Exemplo:
- `Pizzaria do Zé` -> `pizzaria-do-ze`
- `Temaki House Prime` -> `temaki-house-prime`
O usuário:
- vê o slug sugerido
- pode editar antes de salvar pela primeira vez
Importante:
- o slug gerado automaticamente pelo sistema **não inicia** a janela de bloqueio de 90 dias
- ele serve apenas como valor inicial/sugestão persistida
- o contador de 90 dias só começa quando houver a **primeira alteração manual do lojista**
### Limite de tamanho
Recomendação:
- limitar o slug a **30 caracteres**
Esse limite deve contar:
- letras
- números
- hífens
Na prática:
- espaços viram hífen
- então o total final do slug salvo não pode passar de `30`
Exemplo de regra:
- nome fantasia muito grande:
- `Loja Muito Grande Com Nome Enorme da Silva`
- slug gerado:
- `loja-muito-grande-com-nome`
### Regra de sanitização sugerida
O gerador automático deve:
- converter para minúsculas
- remover acentos
- trocar espaços por hífen
- remover caracteres especiais
- colapsar hífens duplicados
- cortar em `30` caracteres
- remover hífen do início/fim
### Se o slug já existir
Se o slug gerado já estiver em uso:
- adicionar sufixo incremental curto
Exemplo:
- `pizzaria-do-ze`
- `pizzaria-do-ze-2`
- `pizzaria-do-ze-3`
Sempre respeitando o limite final de `30` caracteres
### Regra de bloqueio de 90 dias
Regra correta:
- **geração automática pelo sistema**
- não conta como alteração manual
- não bloqueia o campo
- **primeira edição manual feita pelo lojista**
- passa a contar como alteração real
- grava `slugFirstManualUpdateAt`
- grava `slugUpdatedAt`
- define `slugLockedUntil = slugUpdatedAt + 90 dias`
- **edições seguintes**
- só permitidas após `slugLockedUntil`
### Exemplo prático
1. onboarding termina
2. sistema gera:
- `pizzaria-do-ze`
3. lojista vê o valor e troca para:
- `pizza-do-ze-centro`
4. essa troca manual:
- conta como primeira alteração real
- inicia a trava de 90 dias
Se o lojista aceitar o slug automático sem mexer:
- nenhum bloqueio de 90 dias deve ser iniciado
- ele ainda pode fazer a primeira alteração manual depois
### Ajuste das tasks
- 🔴 Persistir metadado informando se o slug atual foi gerado automaticamente ou definido manualmente
- 🔴 Garantir que a geração automática no onboarding não inicie `slugLockedUntil`
- 🔴 Iniciar a janela de 90 dias somente na primeira alteração manual do lojista
### Validações necessárias
- slug único no sistema
- somente letras minúsculas, números e hífen
- tamanho mínimo e máximo
- blacklist de reservados, por exemplo:
- `www`
- `api`
- `admin`
- `app`
- `parceiro`
- `mail`
- `git`
- `proxy`
### Redirect do slug antigo
Se possível, é recomendável:
- guardar o slug antigo
- fazer redirect `301` do slug antigo para o novo por um período
Isso ajuda:
- SEO
- links salvos
- usuários acostumados com a URL antiga
### Sobre DNS e Cloudflare
Isso funciona bem se você usar:
- wildcard DNS `*.pedifoods.com.br`
- proxy reverso lendo o `Host`
Nesse modelo:
- você **não precisa criar um registro DNS novo por loja**
- o wildcard cobre todos os slugs
- a troca de slug depende mais da aplicação e do cache HTTP do que de propagação DNS individual
Ou seja:
- a trava de 90 dias continua fazendo sentido por produto e UX
- mas não por limitação técnica de DNS em si
### Recomendação final para slug
Implementar assim:
- slug definido no onboarding
- edição permitida no painel parceiro
- nova alteração somente após 90 dias
- aviso visual claro antes de salvar
- registro do slug antigo para redirect temporário