Files
LCEssentials/subdomain_stores_plan.md
Daniel Arantes Loverde c49725a5f7 backup
2026-03-19 08:44:31 -03:00

20 KiB

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
  1. partner app
  • subdomínio fixo parceiro.pedifoods.com.br
  • painel do lojista
  1. 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
  1. 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.

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