Relatório nº 2026017

Relatório técnico de projeto

App Personalizado - Personal Trainer — Plataforma de Treino Eduardo

Aplicativo web full-stack para prescrição, execução e acompanhamento de treinos, com portal do personal e portal do aluno. Documentação técnica para portfólio.

Cliente
Eduardo · Personal Trainer
Localização
Divinópolis/MG — presencial e online
Período
27 jul.–4 ago. 2026 (desenvolvimento contínuo)
Entregável
App web (portal do personal + portal do aluno)
Hospedagem
Cloudflare Workers (via OpenNext)

01 Visão geral

Um app de treino construído para resolver as reclamações reais de um app de treino

Eduardo é personal trainer em Divinópolis/MG, com atuação presencial e online, especialização em Musculação e Condicionamento Físico e mais de 5 anos de experiência. Antes do projeto, ele — como a maioria dos personal trainers brasileiros — dependia de um aplicativo de terceiros (MFit) para prescrever e acompanhar os treinos dos alunos. O ponto de partida do App Personalizado - Personal Trainer (nome interno do projeto) não foi uma lista de funcionalidades desejadas, e sim uma lista de queixas reais sobre a ferramenta anterior: sessão caindo sem aviso, "acesso não autorizado" aparecendo do nada, e nenhum autoatendimento para o aluno resolver o próprio cadastro.

O resultado é uma plataforma full-stack com dois portais completamente separados — um para o personal, outro para o aluno — construída sobre Next.js 16 (App Router) no frontend e Supabase (Postgres + Auth + Storage) no backend, hospedada inteira no Cloudflare Workers. Sessão via JWT com refresh automático resolve diretamente o problema de autenticação instável; toda regra de acesso é reforçada no banco via Row Level Security, nunca só no código do frontend.

O projeto foi especificado antes de qualquer linha de código num documento de arquitetura próprio (PROJECT.md, ~500 linhas) cobrindo modelo de dados, segurança, LGPD e um roadmap em etapas — e vem sendo construído seguindo esse roadmap uma parte por vez, com validação do Eduardo a cada etapa entregue.

02 Stack técnica

Next.js 16 + Supabase, sem servidor próprio para manter

Toda peça da stack roda gerenciada — nenhum servidor, fila ou banco para operar manualmente. O app inteiro (SSR incluso) é compilado para rodar como Cloudflare Worker via adaptador OpenNext.

Next.js
16.2
App Router, Server Actions e Server Components
React
19.2
Com TypeScript em 100% do código de aplicação
Tailwind
CSS v4
Tokens de marca mapeados via @theme inline
Supabase
Postgres + Auth (JWT) + Storage + Row Level Security
Cloudflare
Workers
Hospedagem via @opennextjs/cloudflare, sem servidor próprio
Resend +
DeepSeek
E-mail transacional e tradução automática pt-BR

03 A técnica central

Uma biblioteca de exercícios em inglês, traduzida e mantida em pt-BR sem trabalho manual

Em vez de digitar manualmente uma biblioteca de exercícios, o app consome e sincroniza o catálogo completo do ExerciseDB (via RapidAPI) e executa um pipeline interno de localização e tradução técnica automática para português do Brasil via DeepSeek. Diretrizes estritas de parsing e sanitização de dados garantem fidelidade total das instruções originais, sem intervenção humana manual em nenhuma etapa.

O pipeline roda sob demanda, disparado pelo próprio Eduardo na tela de exercícios, e foi desenhado para nunca travar a interface nem estourar cota de API por acidente: uma checagem barata de "tem exercício novo?" (uma única requisição, cacheada por 1h) evita rodar a sincronização completa sem necessidade, termos de categoria repetidos (grupo muscular, equipamento) são traduzidos uma única vez e reaproveitados, e toda chamada a uma API externa tem retry com backoff exponencial respeitando o cabeçalho Retry-After.

Antes
sempre a
página 1
Depois
catálogo
completo
paginação instável por offset → estratégia de cursor resiliente, com deduplicação e teto de segurança de 200 páginas

Regras do pipeline

04 Arquitetura de segurança e dados

O frontend nunca decide o que o usuário pode ver

A regra de ouro definida antes da primeira linha de código: o frontend nunca decide o que um usuário pode ver ou alterar — isso é sempre reforçado no banco, via Row Level Security. O frontend só reflete o que o Postgres já permitiu.

TODA REQUISIÇÃO PASSA PELO BANCO → Next.js 16 Server Actions / RSC Supabase Postgres + Auth (JWT) Storage (signed URLs) Row Level Security · 21 tabelas Portal do personal trainer_id = auth.uid() Portal do aluno student_id = auth.uid() 60 policies de RLS aplicam essa separação — nunca uma checagem só no client SUPABASE_SERVICE_ROLE_KEY nunca chega ao navegador — só existe em código de servidor
Server Actions do Next.js falam com o Postgres sempre usando o cliente autenticado do usuário (nunca a chave de serviço) — quem decide se aquela linha pode ser lida ou escrita é a policy de RLS no banco, não o código da rota.

Regras de segurança aplicadas

05 Sistema de marca em código

O mesmo manual de identidade, com uma regra a mais: dois portais, um símbolo só

A paleta e a tipografia seguem o mesmo manual de identidade oficial do Eduardo usado no site institucional — mas o app precisava resolver um problema que o site não tinha: diferenciar visualmente dois portais (personal e aluno) sem parecer dois produtos diferentes.

Bronze
#BF8A5C
Acento do portal do personal
Teal Claro
#2E9AAB
Acento do portal do aluno
Teal Petróleo
#156876
Ação secundária, ambos os portais
Charcoal
#141414
Fundo base — modo escuro (padrão)
Off-white
#F5F4F1
Fundo base — modo claro (também oficial)
Bronze Escuro
#8A5A32
Texto sobre fundo claro

Identidade de portal (Manual, item 2.10): "mesmo símbolo, muda só o acento" — trainer e aluno usam o mesmo logotipo, a mesma tipografia (Montserrat display + Inter corpo) e o mesmo raio de canto de 3px; o que muda entre um portal e outro é só a cor de destaque (--em-portal-trainer vs --em-portal-student), resolvida em uma única linha de CSS por contexto.

Um detalhe de rigor: cor de marca nunca é cor de dado

O manual proíbe explicitamente usar bronze ou teal "puros" como cor de texto de um dado numérico — carga, séries, repetições. A regra está documentada como comentário no topo do próprio arquivo de tokens do projeto, não só num PDF de marca separado: --em-bronze e --em-teal só podem aparecer em borda, ícone e rótulo; para texto legível, o token mapeado é sempre uma variante já calibrada para contraste (--em-bronze-light/--em-bronze-dark, --em-teal-light/--em-teal-dark), escolhida automaticamente conforme o app está em modo escuro ou claro.

O modo claro, aliás, não é tratado como modo "alternativo": o próprio manual da marca declara que "o modo claro é oficial, não uma exceção" — por isso o app implementa os dois temas com o mesmo nível de acabamento, com persistência da escolha do usuário e sem depender só da preferência do sistema operacional.

06 Funcionalidades

Dois portais, um banco de dados só

Dashboard por ordem de urgência

Painel do personal reorganizado para mostrar primeiro o que precisa de ação — semáforo de risco por aluno, métricas reais de adesão, feedback recente e ranking de frequência — em vez de uma lista neutra de alunos.

Esteira inteligente de recuperação

Algoritmo dedicado que monitora padrões de frequência — alunos inativos há mais de 7 dias ou com anamnese pendente — e executa ações automáticas de retenção e engajamento, com trava anti-spam diária antes de qualquer disparo.

Biblioteca de exercícios própria

Catálogo global sincronizado e traduzido (Seção 03), mais exercícios customizados do próprio Eduardo, com upload de vídeo e edição manual de qualquer campo traduzido.

Criação e atribuição de treino

Rotinas com séries/reps/carga/descanso por exercício, prescrição de carga guiada pelo objetivo declarado na anamnese do aluno, atribuídas a um ou mais alunos.

Anamnese de 17 perguntas com trava de acesso

Questionário de saúde e objetivos que bloqueia o acesso ao treino até ser preenchido, com modal de boas-vindas e checkbox de legitimidade das respostas.

Avaliação física + evolução

Registro simplificado (peso, %gordura, massa magra, circunferências, fonte) com gráfico de evolução comparando as três métricas ao longo do tempo.

Execução de treino e feedback

Tela "treino de hoje", sessão com marcação de série/carga em tempo real e feedback pós-treino (nota + comentário) que alimenta o dashboard do trainer.

Mensagens e notificações in-app

Conversa direta entre trainer e aluno por vínculo, com notificações in-app para treino atribuído, feedback recebido, vínculo aceito e mensagem nova.

07 Métricas de engenharia & resiliência

Escala do sistema até este relatório

Sem ferramenta de auditoria externa envolvida neste relatório — os números abaixo vêm direto do repositório: schema, histórico de commits e código de aplicação.

21
Tabelas no schema, todas com Row Level Security habilitado
60
Policies de RLS distintas aplicadas nessas tabelas
14
Migrations versionadas em supabase/migrations
~10,9 mil
Linhas de TypeScript/TSX em app, components e lib
51
Commits desde o "Initial commit" do Create Next App
2
Portais completos (personal e aluno) sobre a mesma base de código

Decisões de resiliência que aparecem no código, não só no discurso

08 Engenharia & depuração

Quatro bugs que só aparecem em produção

Registro honesto de problemas reais encontrados durante o desenvolvimento — o tipo de detalhe que separa "funciona no meu navegador" de "funciona".

01

O mesmo bug de navegação, três vezes

router.push() seguido imediatamente de router.refresh() no mesmo handler faz o refresh tentar revalidar os dados da página que está sendo deixada enquanto a navegação do push ainda está em andamento — os dois competem e a transição nunca resolve, deixando o botão preso em "Enviando..." para sempre, mesmo com o dado já salvo com sucesso no servidor. Encontrado ao vivo no feedback pós-treino; a causa raiz só ficou clara reproduzindo o fluxo passo a passo.

Correção: remover o refresh() redundante — o push() já busca dados novos da rota de destino sozinho. O mesmo padrão reapareceu no envio da anamnese e no botão "Entrar" do login dias depois; encontrado em minutos ao grepar todo router.push() seguido de router.refresh() no restante do código, já com o padrão mapeado.

02

A paginação que sempre devolvia a primeira página

A sincronização da biblioteca de exercícios usava o parâmetro offset documentado pela API do ExerciseDB para avançar de página — mas a API o ignora silenciosamente. offset=0 e offset=25 devolviam exatamente a mesma resposta, então o loop de sincronização lia a página 1 para sempre, achando (por contagem de itens recebidos) que estava avançando.

Correção: paginação real é por cursor (meta.nextCursor, passado como after=<id>), com deduplicação por ID e um teto absoluto de 200 páginas como rede de segurança contra qualquer novo comportamento inesperado da API.

03

Variáveis de ambiente somem sozinhas a cada deploy

Sem uma configuração de ambiente devidamente versionada no repositório, o pipeline de build da Cloudflare Workers reexecutava automaticamente uma etapa de migração a cada novo deploy — regenerando a configuração de runtime do zero, sem conhecimento das variáveis previamente cadastradas no painel. O resultado prático era o sintoma clássico de "as credenciais somem sozinhas" a cada nova publicação.

Ajuste: reestruturação da arquitetura de ambientes e sincronização de variáveis de runtime no servidor de borda (Edge), garantindo persistência de credenciais sem dependência do ciclo de build.

04

Uma incompatibilidade silenciosa de runtime entre framework e adaptador de borda

Uma peça central do roteamento do Next.js 16 é projetada, por padrão, para um runtime que o adaptador de borda da Cloudflare ainda não suporta totalmente — gerando falha direta no deploy em produção. A causa raiz só ficou clara depois de testes comparativos diretos entre diferentes convenções de bundle contra o ambiente real de deploy.

Ajuste: adaptação do bundle de borda para manter total compatibilidade com os seletores de runtime do Next.js 16 e do adaptador Edge, garantindo o processamento de rotas em baixa latência no nível da CDN.

09 Conformidade & responsabilidade

Dado de saúde é tratado como dado de saúde, não como campo de formulário

A anamnese pergunta explicitamente sobre limitação médica, cirurgia prévia, medicamento de uso contínuo e grupo de risco (pressão, diabetes, doença coronária) — informação que existe para o Eduardo adaptar a prescrição de treino dentro do escopo de um profissional de Educação Física registrado no CREF, nunca em substituição a acompanhamento médico. A avaliação física foi mantida deliberadamente simples (peso, %gordura, massa magra, fonte, data) em vez de implementar protocolos próprios de bioimpedância — quanto menos dado sensível de saúde armazenado além do estritamente necessário, menor o risco.

10 Deploy

Do commit ao ar, num único worker

Deploy serverless de alta performance: a aplicação inteira — incluindo Server-Side Rendering (SSR), Server Actions e ativos estáticos — é compilada e implantada na rede global da Cloudflare Workers via adaptador de borda (Edge). A arquitetura dispensa gestão de servidores tradicionais e mantém latência mínima em qualquer região do mundo, sem pipeline própria de CI a manter.

Variáveis de ambiente de produção são cadastradas diretamente no painel da Cloudflare, nunca no repositório, com persistência garantida entre deploys (Seção 08, item 03). As migrations do banco de dados são versionadas e aplicadas em um pipeline isolado e seguro, independente do fluxo de publicação do app — reduzindo o risco de inconsistência entre schema e aplicação em produção.

// Próximo Projeto

O relatório acabou. O seu projeto pode começar agora.

Se o nível de detalhe técnico e o cuidado com resultado que você viu aqui é o que o seu negócio precisa, vamos conversar. A Make Solution projeta e constrói sites, aplicativos e automações do zero — com a mesma engenharia, a mesma transparência e o mesmo compromisso com retorno real.