Encantra — Arquitetura e Convenções
Este documento é o contrato técnico entre os módulos da plataforma. Leia antes de contribuir.
Stack
- PHP 8.2+ sem framework pesado, sem etapa de build (deploy por
git push→ Hostinger auto-deploy). - MySQL 8 / MariaDB (InnoDB, utf8mb4). Isolamento multi-tenant por linha (
tenant_idem toda tabela de negócio). - Composer com
vendor/versionado:stripe/stripe-php(cobrança),anthropic-ai/sdk+guzzlehttp/guzzle(Claude, provedor alternativo),phpmailer/phpmailer(SMTP),erusev/parsedown(Markdown). A OpenAI é chamada por HTTP direto (App\Core\Http). - Front-end: Aurora Design System (
assets/css/encantra.css+assets/js/encantra.js), ícones Lucide, fontes Sora/Inter.
Estrutura
index.php front controller
app/Core/ núcleo (DB, Router, Auth, Tenant, AI, Mailer, Queue, Events, Hooks, Knowledge…)
app/Modules/<Nome>/ módulos: module.php + Controllers/ + Services/ + Views/
app/Views/ layouts e parciais compartilhados
assets/ design system, JS, imagens (CSS de módulo: assets/css/<modulo>.css)
database/migrations/ SQL numerado (001-099 núcleo, 100 Suporte, 200 Jornada, 300 Connect/API, 400 Master/Site)
database/seeds/ demo.php + demo_<modulo>.php
cron/run.php fila de jobs + tarefas periódicas (cron a cada minuto)
docs/ documentação (produto, API, protocolo EKSP)
Regras de ouro
- Toda query de dados de tenant filtra por
tenant_id = Tenant::id(). Nunca confie em IDs vindos do usuário sem esse filtro. - Módulos não leem tabelas de outros módulos. Use
Events(fatos),Hooks(UI e sinais) e serviços do núcleo (Knowledge,Timeline,Notify). - POST/PUT/DELETE web exigem CSRF (
csrf_field()); APIs usam'auth' => 'api'. - Saída sempre escapada com
e(). Markdown viamarkdown()(modo seguro). - Segredos nunca no Git:
config/config.php(infra) eplatform_settingscriptografado (SMTP, IA, billing). - Toda ação relevante gera
Audit::log(); toda interação com cliente geraTimeline::add().
Registro de módulo (module.php)
return function (Router $r): void {
$r->group('/app/support', ['auth' => 'tenant', 'module' => 'support'], function (Router $r) {
$r->get('/tickets', [TicketController::class, 'index']);
});
Nav::add('app', ['group' => 'Suporte', 'label' => 'Tickets', 'icon' => 'inbox', 'url' => '/app/support/tickets', 'order' => 10, 'can' => 'support.view', 'module' => 'support']);
Events::on('ticket.created', fn (array $p) => ...);
Queue::handler('support.ai_triage', fn (array $p) => ...);
Queue::schedule('support.sla_check', '*/5 * * * *', fn () => ...);
Hooks::add('app.home', fn () => view('support::widgets/home', [...], null), 10);
};
Ordem do menu: Início 1 · Suporte 10–39 · Customer Success 40–69 · Integrações 70–89 · Configurações 90+.
Pontos de extensão (Hooks)
| Ponto | Assinatura | Uso |
|---|---|---|
app.home |
fn(): string |
widgets do painel inicial |
master.home |
fn(): string |
widgets do painel master |
search |
fn(string $q): array |
resultados da paleta ⌘K |
contact.panels |
fn(array $contact): string |
painéis laterais do contato/ticket |
account.panels |
fn(array $account): string |
painéis na visão 360º da conta |
account.tabs |
fn(array $account): array |
abas extras na conta |
account.signals |
fn(array $account): array |
sinais para o health score (ex.: ['support.open_tickets' => 3, 'support.csat' => 4.6]) |
settings.sections |
fn(): array |
cartões em Configurações (['title','description','icon','url','order']) |
usage.meters |
fn(): array |
medidores de uso do plano ([['key'=>'tickets_month','label'=>'Tickets no mês','used'=>120]]; o limite vem de Tenant::limit(key)) |
Eventos de domínio
ticket.created|updated|status_changed|assigned|replied|solved|sla_breached, csat.received, chat.started|handoff,
account.created|updated|health_changed|stage_changed, contact.created, survey.responded,
journey.started|completed, task.overdue, connector.synced, usage.event_received,
tenant.created (novo workspace — cada módulo cria seus padrões: SLA, times, modelo de saúde, playbook de onboarding).
Payload sempre com tenant_id e IDs das entidades (ticket_id, account_id, contact_id...). Todo evento é replicado aos webhooks do tenant.
IA (Lumi)
AI::json($feature, $system, $user, $schema) e AI::text($feature, $system, $messages). Provedor configurável em
Master › Configurações › IA (ai.provider): OpenAI (padrão, ai.openai_model = gpt-5-mini, mesma chave das demais
plataformas Inspire4U) ou Anthropic (claude-opus-5-5, com prompt caching e fallback server-side). BYOK por workspace:
chaves sk-ant-… usam Claude, demais sk-… usam OpenAI. Esforço low para classificação e medium para redação,
cota mensal por plano (ai_requests_month). Recuperação de contexto: Knowledge::search() + Knowledge::contextBlock().
Desenvolvimento local
mysql -e "CREATE DATABASE encantra_dev"
cp config/config.example.php config/config.php # ajuste banco e app.key
php bin/migrate.php --seed-demo # usuários demo: senha Encantra@2026
php -S localhost:8080 index.php
php cron/run.php # processa fila
Variáveis úteis: ENCANTRA_DB_NAME, ENCANTRA_APP_URL, ENCANTRA_MIGRATION_PREFIXES="0,1", ENCANTRA_SEEDS="support".
Cobrança
Gateway configurável (billing.provider): Stripe (padrão — mesma conta das demais plataformas Inspire4U), Asaas ou manual.
Stripe: assinatura recorrente via Checkout + Portal do Cliente; faturas avulsas via Hosted Invoice; webhook assinado em
/webhook/stripe (eventos checkout.session.completed, invoice.paid|payment_failed|voided, customer.subscription.updated|deleted),
idempotente pela tabela billing_events. Serviço: App\Modules\Master\Services\Stripe.
Encontrou algo para melhorar? Escreva para master@inspire4u.com.br.