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_id em 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

  1. Toda query de dados de tenant filtra por tenant_id = Tenant::id(). Nunca confie em IDs vindos do usuário sem esse filtro.
  2. 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).
  3. POST/PUT/DELETE web exigem CSRF (csrf_field()); APIs usam 'auth' => 'api'.
  4. Saída sempre escapada com e(). Markdown via markdown() (modo seguro).
  5. Segredos nunca no Git: config/config.php (infra) e platform_settings criptografado (SMTP, IA, billing).
  6. Toda ação relevante gera Audit::log(); toda interação com cliente gera Timeline::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.