Ápice é uma plataforma web de estudos para o ENEM, com foco em redação, simulados, acompanhamento de desempenho e ferramentas de IA para estudo guiado.
O projeto usa React/Vite no frontend e Netlify Functions no backend. As chamadas de IA, autenticação, pagamentos e sincronização de dados passam por funções serverless para proteger chaves, controlar acesso e centralizar regras de negócio.
A arquitetura do Ápice é estruturada em três camadas principais:
┌─────────────────────────────────┐
│ React 19 / Vite Client │
└────────────────┬────────────────┘
│
│ authFetch() [JWT Header]
▼
┌─────────────────────────────────┐
│ Netlify Functions │ (utils/auth.js, validate.js, rateLimit.js)
└────────────────┬────────────────┘
│
├───────────────────────────────┐
▼ ▼
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ netlify/ai/ai.js │ │ Netlify Blobs │
└────────────────┬────────────────┘ └─────────────────────────────────┘
│ (Fallback Engine) (Estado e Billing do Usuário)
▼
┌───────────────┼───────────────┐
▼ ▼ ▼
Groq Gemini OpenRouter / xAI / HF- Frontend (React 19 & Vite 8): Gerencia a interface do usuário, mantém o estado de estudo local do estudante (usando LocalStorage temporário), e interage com o backend através da função utilitária
authFetch(), que anexa automaticamente o token JWT do Netlify Identity nas requisições. - Backend (Netlify Functions - Serverless): Centraliza as regras de negócio, valida os dados de entrada, executa autenticação estrita, aplica políticas de rate limit e gerencia a conexão com serviços de terceiros (Provedores de IA e gateway de pagamento AbacatePay).
- Persistência (Netlify Blobs): Banco de dados chave-valor serverless de alta consistência usado para persistir o estado do usuário, histórico de uso do modo convidado e controle de rate limiting em tempo de execução.
O orquestrador central de IA está localizado no arquivo netlify/ai/ai.js. Ele expõe APIs simplificadas para as funções serverless e executa diversas etapas complexas por trás de cada requisição.
La camada de IA suporta 5 provedores externos: Groq, Gemini, OpenRouter, xAI (Grok) e Hugging Face.
- Seleção Dinâmica: O sistema lê as variáveis de ambiente
AI_<PROVIDER>_ENABLEDeAI_<PROVIDER>_ORDERpara construir dinamicamente a cadeia de fallback. - Execução: Se o provedor primário falhar (por limite de cota, erro de rede ou timeout), a engine intercepta o erro, registra a falha no console e tenta automaticamente o próximo provedor disponível na fila de prioridades, garantindo alta disponibilidade ao usuário final.
O chat do Professor IA utiliza a função runGroqProfessorWithTools para habilitar chamadas de ferramentas diretamente na inteligência artificial:
- Definição de Ferramentas: São expostas ferramentas como
search_web(pesquisa factual de atualidades) egenerate_quiz(geração de quizzes interativos). - Ciclo de Execução:
- O modelo recebe a pergunta do usuário e decide se precisa chamar alguma ferramenta.
- Se sim, a função serverless intercepta a requisição, executa a busca contextual (usando a API Google Custom Search integrada em
searchContext) e devolve os dados estruturados de volta ao modelo. - O modelo gera a resposta final baseada no contexto injetado pelas ferramentas.
Os prompts instruem os modelos de linguagem a retornar exclusivamente objetos JSON válidos para que a interface possa renderizar de forma rica (separando texto, explicações e metadados).
- Sanitização: Como os modelos frequentemente envolvem o JSON em blocos de código Markdown (```json ... ```), a camada de IA do backend limpa esses caracteres antes de tentar efetuar o
JSON.parse. - Fallback de Validação: Se o retorno ainda for inválido, o sistema aciona expressões regulares e limpadores de strings para recuperar o JSON quebrado.
Para evitar abusos de consumo de tokens (Prompt Injection de tamanho massivo ou estouro de memória), todas as rotas passam pelo utilitário netlify/functions/utils/validate.js, que rejeita a requisição antes de enviá-la para a IA se os limites abaixo forem excedidos:
- Mensagem do Usuário: Máximo de 15.000 caracteres.
- Histórico de Conversas: Limite no número de mensagens anteriores enviadas no contexto.
- Texto de Redação: Máximo de 8.000 caracteres.
O fluxo de monetização e controle de planos premium é integrado com a API da AbacatePay e mantido nas funções serverless sob forte controle de estado.
[Client] [Netlify Functions] [AbacatePay]
│ │ │
│ POST /abacatepay-checkout │ │
├──────────────────────────────────>│ │
│ │ 1. Valida plano local │
│ │ 2. Cria Customer ID │
│ │ 3. Valida Produto Remoto │
│ │ 4. Cria checkout │
│ ├───────────────────────────────────>│
│ │ │
│ <─────────────────────────────────┼────────────────────────────────────┤
│ Retorna URL do Checkout │ │
│ │ │
│ (Cliente efetua o pagamento) │ │
│ │ │
│ Webhook POST │ │
│ ──────────────────────────────────┼───────────────────────────────────>│
│ │ │
│ │ 1. Valida WEBHOOK_SECRET │
│ │ 2. Decodifica externalId │
│ │ 3. Grava "paid" no Netlify Blobs │
│ │<───────────────────────────────────┤- Validação de Plano: O backend verifica se o
planKeyenviado existe na tabela estáticaPRICING_PLANS. - Criação do Cliente: O sistema consulta as informações cadastrais e gera um
customerIdúnico na AbacatePay para rastreabilidade de faturas. - Validação de Produto: Para evitar descompassos entre o código e o painel da AbacatePay:
- Assinaturas: Valida se o produto está ativo e se o ciclo cadastrado corresponde ao esperado pelo plano (
MONTHLY,SEMIANNUALLYouANNUALLY). - Pagamentos Únicos: Garante que o produto não possua ciclo de recorrência associado.
- Assinaturas: Valida se o produto está ativo e se o ciclo cadastrado corresponde ao esperado pelo plano (
- Construção do Payload: O checkout é criado com metadados cruciais criptografados/codificados no
externalId(formato:apice_<planKey>_<timestamp>_<userId>).
- Autenticação: O webhook é protegido por um segredo exclusivo. A função rejeita qualquer payload em que o parâmetro
webhookSecretnão coincida com a variável de ambienteWEBHOOK_SECRETdo Netlify. - Processamento do Evento: Ao receber uma notificação de status
bill.paidousubscription.active, a função decodifica oexternalIdpara obter ouserIdcorrespondente. - Atualização do Blob: A função serverless atualiza o Netlify Blobs com o novo estado de billing verificado (
status: 'paid'), tornando o backend a única autoridade confiável de acesso.
O Ápice adota uma arquitetura híbrida de autenticação e persistência para garantir alta performance e conformidade de segurança.
- Netlify Identity (JWT): Usado apenas para atestar a identidade criptográfica do usuário. O JWT expõe dados básicos (
sub,email). Não armazenamos o progresso do aluno ou o estado de pagamento nouser_metadatado JWT para evitar tokens excessivamente grandes e lentidão no tráfego de requisições. - Netlify Blobs: Armazena o estado real. Toda mutação de dados é feita gravando no Netlify Blobs do projeto sob a chave
user-state:${userId}.
O arquivo JSON armazenado possui a seguinte estrutura técnica:
{
"accountOwnerId": "uuid-do-usuario-no-identity",
"savedAt": "2026-05-23T17:00:00.000Z",
"planTier": "free" | "paid",
"planStatus": "free" | "paid",
"planKey": "monthly" | "yearly" | "semiannual" | "",
"billing": {
"status": "free" | "paid",
"planKey": "monthly" | "yearly",
"billingMode": "subscription" | "one_time",
"gateway": "abacatepay-v2",
"paidAt": "2026-05-23T17:00:00.000Z",
"subscriptionActive": true,
"cancelAtPeriodEnd": false,
"checkoutId": "ap-checkout-id",
"subscriptionId": "ap-subscription-id",
"accessEndsAt": "2027-05-23T17:00:00.000Z",
"remoteStatus": "ACTIVE",
"updatedAt": "2026-05-23T17:00:00.000Z"
},
"history": {
"redacoes": [],
"simulados": [],
"conquistas": []
}
}- carregar-estado.js: Obtém o JSON acima do Blob com consistência forte e o retorna ao frontend.
- salvar-estado.js: Mescla o histórico de estudos do frontend com o estado da nuvem. Regra de Segurança: Esta rota bloqueia qualquer tentativa de mutação manual do bloco
"billing","planTier", ou"planStatus"enviada pelo cliente. O estado financeiro é alterado apenas por rotas internas verificadas e pelos webhooks oficiais assinados da AbacatePay.
O backend do projeto depende das seguintes variáveis de ambiente cadastradas no painel da Netlify:
| Nome da Variável | Função |
|---|---|
ABACATE_PAY |
Chave API da AbacatePay (Usada como fallback secundário) |
ABACATE_V2 |
Chave API Principal da AbacatePay (V2) |
CLIMA |
Chave de API do OpenWeatherMap para o widget meteorológico |
GEMINI |
Chave de API do Google Gemini |
GROK |
Chave de API do xAI Grok |
GROQ |
Chave de API do Groq Cloud |
HF |
Chave de API da Hugging Face |
OR |
Chave de API do OpenRouter |
WEBHOOK_SECRET |
Token secreto para autenticação das requisições do webhook da AbacatePay |
NETLIFY_AUTH_TOKEN |
Token de acesso para deploys automáticos e CLI |
- Node.js 22+.
- Netlify CLI instalado globalmente:
npm install -g netlify-cli.
- Instale as dependências:
npm install
- Inicialize o servidor local integrado do Netlify Dev:
netlify dev
- O frontend estará disponível em
http://localhost:8888com proxy automático para as funções serverless em/.netlify/functions/.