Fase 1 · Planejamento · v2 — Hostinger (PHP + MySQL)
MyImobee: a vitrine digital do estoque da imobiliária.
Plataforma SaaS multi-tenant para imobiliárias e corretores regionais: portal público rápido e indexável, painel administrativo e cadastro assistido por IA com revisão humana obrigatória. Este documento cobre a arquitetura, o banco, os fluxos, o mapa de telas e as estratégias de autenticação, isolamento, IA e imagens. As telas estão no protótipo navegável.
1. Arquitetura proposta
Um único aplicativo Laravel serve o portal, o painel e o master, com separação por rota, por middleware e por permissão. As páginas são renderizadas no servidor (Blade), o que é ótimo para SEO e para celulares mais simples. O Alpine.js cuida das interações. Sem Node.js no servidor: CSS e JS são compilados no CI.
host → agency_domains → imobiliária da requisição · painel: imobiliária ativa do usuário (com checagem de vínculo).env.2. Estrutura inicial do banco
Toda tabela de negócio carrega agency_id. IDs BIGINT internos, ULID público na imobiliária, created_at/updated_at em todas e exclusão lógica onde a URL precisa sobreviver. MySQL 8 / MariaDB com InnoDB, utf8mb4 e FULLTEXT na busca.
{{ t.name }}{{ t.tag }}status (rascunho, publicado, reservado, vendido, alugado, inativo) · purpose (venda, aluguel) · role (admin, imobiliaria, corretor) · badge (destaque, novidade, oportunidade, exclusivo). Código do imóvel: contador agencies.next_code lido com SELECT … FOR UPDATE dentro de uma transação (IMB-1024), índice único (agency_id, code) e busca por código no portal.3. Fluxos de usuário
4. Mapa de telas e URLs
{{ i.url }}{{ i.label }}5. Componentes do design system
Fundação: tema escuro de baixa saturação, Inter peso 500 nos títulos, raio de 8px, acento usado como linha e brilho — não como preenchimento. Botão principal com contorno, foco visível no acento, fotos grandes.
6. Autenticação e perfis
- Autenticação do Laravel (Breeze/Fortify): e-mail e senha com hash argon2id, verificação de e-mail, recuperação de senha e login com Google (Socialite). Sessão em cookie
httpOnly,SecureeSameSite=Lax, guardada na tabelasessions. - A imobiliária ativa fica em
users.current_agency_id; o middleware confirma o vínculo emmembershipsa cada requisição do painel. Quem pertence a várias imobiliárias alterna entre elas. - Perfis: admin (
is_platform_admin, MyImobee), imobiliaria (tudo da própria imobiliária), corretor (os próprios imóveis e leads). Regras em Policies, prontas para novos níveis. - Master com 2FA (TOTP) obrigatório. A opção “Acessar painel” de uma imobiliária usa
Tenant::withoutScope()em modo suporte, registrado emaudit_logs. - Proteções: CSRF em todos os formulários, throttle de login (5 tentativas/min), bloqueio progressivo e aviso por e-mail em novo acesso.
- Favoritos começam em
localStorage; com conta de visitante, passam para a tabelafavorites(junção no primeiro login).
7. Estratégia multi-tenant
- Banco compartilhado, isolamento pela aplicação: o MySQL não tem Row Level Security. Toda tabela de negócio tem
agency_id NOT NULLindexado, e todo model usa o traitBelongsToAgency: filtro global em toda consulta eagency_idpreenchido em toda criação, sem poder ser alterado depois. - Resolução pelo domínio: o middleware
ResolveTenantlê ohost(caririprime.myimobee.com.brouwww.caririprime.com.br), consultaagency_domains(com cache de 5 min) e define o tenant.myimobee.com.bré o portal agregado, que só lê anúncios públicos. - Sem tenant, sem dados: uma consulta fora de contexto não devolve nada; uma criação sem tenant lança exceção. Contornar exige
Tenant::withoutScope(), permitido só em jobs internos e no master. - Arquivos:
media/{agency_uuid}/{property_id}/{ulid}-{largura}.webp. Uploads e exclusões passam sempre pela Policy do imóvel. - Testes de isolamento no CI bloqueiam o deploy: A não lista, não abre, não edita e não exclui nada de B; um
agency_idforjado no request é ignorado; o domínio de A nunca mostra imóvel de B. - Limites do plano aplicados no servidor (imóveis, usuários, armazenamento, IA, destaques, domínio próprio).
trait BelongsToAgency {
public static function bootBelongsToAgency(): void {
static::addGlobalScope('agency', function (Builder $q) {
if (Tenant::bypassed()) return;
$id = Tenant::id();
if ($id === null) { $q->publicVisible(); return; } // portal agregado
$q->where($q->getModel()->getTable().'.agency_id', $id);
});
static::creating(fn ($m) => $m->agency_id = Tenant::id()
?? throw new LogicException('Criação sem tenant.'));
static::updating(fn ($m) => $m->isDirty('agency_id')
&& throw new LogicException('agency_id não pode mudar.'));
}
}
8. Fluxo do cadastro com IA
- Entrada: texto livre, fotos (até 40) e, na próxima fase, PDF/Word/planilha (extração de texto no servidor antes da IA).
- Extração estruturada: a IA devolve JSON validado por schema; cada campo vem com
source(texto, foto ou nulo). Campo ausente =null→ “Não informado”. - Inferência visual nunca vira fato: aparece como “Possível característica — confirmar antes de publicar” e só entra no anúncio se o usuário confirmar.
- Descrição (Objetivo, Comercial, Premium, SEO) e SEO são gerados só a partir dos campos confirmados.
- Revisão humana obrigatória: o resultado é salvo como rascunho; a publicação exige clique em “Publicar imóvel” e confirmação explícita.
- Execução:
AiServicechama a API da Claude pelo PHP, no servidor (a chave fica no.env). São três requisições em sequência (extrair → descrever → SEO), e a tela marca cada etapa. Tempo limite de 45 s e uma nova tentativa. - Rastreio e custo: cada chamada grava
ai_jobs(tenant, tipo, tokens, custo, duração, erro). Limite mensal por plano e rate limit por usuário. - Falha: sem resposta da IA, o sistema faz leitura básica local e marca tudo para revisão — o corretor nunca fica bloqueado.
9. Fluxo de upload de imagens
- Pré-processamento no navegador (redimensiona para 1600px e converte para WebP) reduz o upload em 80–95% em redes móveis — já funcionando no protótipo.
- Upload de uma foto por requisição (
POSTmultipart, com progresso na tela). O servidor valida o tipo real pelos magic bytes (finfo), o tamanho (15 MB), a quantidade por imóvel (40) e a cota de armazenamento do plano. ImageService(Intervention Image + GD) gera WebP em 1600/960/480/320 e AVIF quando o PHP da hospedagem suporta. Também reescreve a imagem sem EXIF/GPS, cria um placeholder borrado inline, calcula um hash 8×8 para achar duplicadas e dá uma nota de qualidade pela resolução.- Uma cópia de 512px, privada, é enviada à IA para identificar ambientes.
property_mediaguarda ordem, foto principal, legenda, alt e ambiente. As fotos são entregues pelo CDN da Hostinger comsrcset, lazy loading e cache longo.
10. Integrações
| Integração | Agora | Depois |
|---|---|---|
| {{ i.n }} | {{ i.now }} | {{ i.later }} |
11. SEO e performance
- URLs amigáveis estáveis:
/imovel/casa-3-quartos-salesianos-juazeiro-do-norte/1024. O número final é a chave; se o slug mudar, 301 para o novo. - Páginas indexáveis de cidade, bairro e tipo:
/imoveis/juazeiro-do-norte-ce,/imoveis/juazeiro-do-norte-ce/casas, com texto local e links internos. - Gerados automaticamente: title, meta description, Open Graph (imagem principal 1200×630), canonical, breadcrumb (BreadcrumbList) e JSON-LD
RealEstateListing+Offer. - Vendido/alugado mantém a URL no ar com o aviso e imóveis semelhantes — preserva o SEO e aproveita o tráfego.
sitemap.xmlerobots.txtpor imobiliária (comando agendado de hora em hora). Cache de páginas no banco (10 min), limpo ao publicar ou editar, eCache-Controlpúblico nas páginas do portal.- Meta: LCP abaixo de 2,5 s em 4G, CLS abaixo de 0,1. Imagem hero com prioridade, restante lazy, fontes com
display=swap, JS mínimo no portal.