# 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 Autor: Daniel Arantes Loverde