App de fitness inteligente: estrutura de dados, esquema de banco de dados e especificação de design do servidor
ArtefatoEste é o artefato usado no Episódio 2: Planeje a especificação do sistema do seu app (sem código). Use essa especificação como referência para estruturar coleções do Cloud Firestore, endpoints de servidor e memória de agente de três níveis.
Faça login para acessar este artefato
Faça login com seu Perfil do Desenvolvedor do Google para conferir e copiar o artefato completo usado na série Build It!.
O perfil do desenvolvedor é obrigatório
Você não se qualifica para um perfil de desenvolvedor. Peça para o administrador do Workspace ativar esse recurso.
Criar um perfil de desenvolvedor
Crie um Perfil do Desenvolvedor do Google sem custo financeiro para acessar todos os comandos e artefatos do Build It!
Participe do Builder Central para desbloquear
Resgate seu selo de membro Builder sem custo financeiro com um clique para desbloquear todos os comandos e artefatos do Build It!
Status: em andamento (elaboração com o assistente de planejamento)
Banco de dados de destino: Google Cloud Firestore
Meta da arquitetura: reconstrução limpa, sustentável e pronta para produção do protótipo de fitness inteligente do AI Studio.
0. Decisões sobre a pilha técnica principal e a arquitetura do sistema
Para garantir a estabilidade da produção, o desempenho e limites claros, a seguinte linha de base arquitetônica é bloqueada para todas as implementações:
- Framework de front-end: Next.js (App Router, React 19) com armazenamento em cache de recursos off-line de App Web Progressivo (PWA) (usando Serwist /
@ducanh2912/next-pwa). - Estilo e componentes de interface: Tailwind CSS v4 + Tailwind Variants (para gerenciamento de variantes) + Lucide React (ícones).
- Arquitetura de estado do cliente:
- Zustand (com middleware
persist): processa o estado do cliente local, tátil e efêmero (cronômetros, buffers de conjuntos ativos, gráfico de passos e entradas não salvas na academia). - TanStack Query (React Query): processa todo o estado assíncrono do servidor, o armazenamento em cache de documentos do Firestore, a revalidação em segundo plano, a eliminação de duplicação de solicitações e as mutações otimistas.
- Zustand (com middleware
- Ambiente de execução de back-end e hospedagem de APIs: gerenciadores de rotas do Next.js (
app/api/...) hospedados no Google Cloud Run na mesma região do Firestore e do cache de contexto do Gemini para latência interna inferior a 15 ms. - Autenticação: Firebase Authentication (Login do Google, e-mail/senha e vinculação de integração anônima). O cliente anexa
Authorization: Bearer <Firebase_ID_Token>em todas as solicitações de API, e o servidor verifica a identidade usandofirebase-admin.auth().verifyIdToken(token). - Banco de dados principal: Google Cloud Firestore.
1. Visão geral do sistema e jornada do usuário principal
1.1 Princípios de escopo e arquitetura
Esta especificação define os modelos de dados, os esquemas do Cloud Firestore, os contratos de endpoint do servidor e as estruturas de memória do agente de IA para o aplicativo Smart Fitness. Ele estabelece uma única fonte de verdade para todo o planejamento de treinos, registro de sessões históricas e memória de coaching em níveis autônomos, completamente dissociada dos frameworks de apresentação de front-end (que são abordados em uma especificação de design de UI/UX dedicada).
1.2 Princípios básicos de dados
- Separação de responsabilidades: limite estrito entre contratos de dados (back-end/banco de dados) e design de interface do cliente.
- Arquitetura de biblioteca híbrida: enciclopédia de exercícios estática e imutável (
exercises) combinada com a composição dinâmica de cronogramas semanais por IA (weekly_plans). - Mutações em etapas com intervenção humana: as alterações de treino geradas por IA são retornadas como propostas em etapas. As gravações no banco de dados ocorrem exclusivamente após a aceitação explícita do usuário.
- Memória viva contínua de 3 níveis: a IA captura, atualiza e limpa de forma autônoma os fatos do usuário em elementos permanentes, estados fisiológicos que expiram (com TTL explícito) e contexto situacional transitório.
- Leituras granulares de documento único: os dados são estruturados em documentos otimizados (como cadernos espirais de planos semanais e buckets de chat semanais) para minimizar a latência de leitura e eliminar consultas redundantes.
2. Endpoints de servidor e contratos de API
Cada endpoint precisa especificar a origem da resposta (
AGENT,DATABASEouHYBRID) para que os endpoints com suporte de agente possam ser configurados adequadamente durante a implementação.
POST /api/coach/chat
- Finalidade: interação de conversação com o Personal Trainer de IA, lidando com perguntas de coaching, suporte motivacional e geração de propostas de modificações de treino (ajustes diários e revisões semanais).
- Acionador: o usuário envia uma mensagem na guia Coach ou toca em um ícone de edição sugerida contextual inline (por exemplo, "Substituir desenvolvimento Arnold por desenvolvimento de ombros com halteres").
- Origem da resposta:
AGENT— Usa o Gemini com recursos de streaming e chamada de função/ferramenta estruturada. Não altera diretamente o banco de dados. Retorna uma proposta em etapas para revisão human-in-the-loop. - Cabeçalhos:
Authorization: Bearer <Firebase_ID_Token>,Content-Type: application/json
Payload da solicitação:
{
"message": "string (user input or selected prompt chip text)",
"context": {
"active_tab": "string (enum: today | week | coach)",
"week_start_date": "string (ISO date YYYY-MM-DD)",
"selected_day": "string (e.g. monday, optional)"
}
}Payload de resposta:
{
"message": "string (conversational coaching response displayed in chat bubble)",
"proposal": {
"id": "string (unique proposal ID, nullable)",
"type": "string (enum: NONE | UPDATE_DAY | UPDATE_WEEK)",
"target_day": "string (e.g. monday, nullable)",
"summary_diff": "string (concise summary of changes, e.g. 'Swapped Arnold Press ➔ DB Shoulder Press', nullable)",
"data": "map (full proposed workout day object or weekly plan object, nullable)"
},
"memory_mutations": [
{
"operation": "string (enum: CREATE | UPDATE | DELETE)",
"target_tier": "string (enum: tier_1_staples | tier_2_physiological | tier_3_situational)",
"memory_id": "string (required for UPDATE and DELETE)",
"entry": "map (memory payload with fact, category, expires_at, etc., required for CREATE and UPDATE)",
"reason": "string (concise rationale for why memory was mutated)"
}
]
}- Gravação dupla em
weekly_chats: em cada turno, o servidor persiste a mensagem do usuário recebida (text) e a resposta do coach (text) em/users/{userId}/weekly_chats/{week_start_date}usandoFieldValue.arrayUnion(). - Padrão de mutação em etapas: retorna o payload da proposta sem modificar
/weekly_plansaté que o usuário toque em [Accept Plan]. - Gerenciamento contínuo de memória autônoma: emite
memory_mutationspara criar, atualizar ou limpar itens do perfil de três níveis do usuário.
POST /api/workouts/log
- Finalidade: mantém uma sessão de treino concluída no arquivo de treinamento permanente do usuário, calcula os agregados finais de volume/repetições, verifica os recordes pessoais e sinaliza o dia programado como concluído.
- Acionador: o usuário toca em "Finalizar treino" na tela de monitoramento de treino.
- Origem da resposta:
DATABASE— Transação/gravação direta do banco de dados no Cloud Firestore.
Payload da solicitação:
{
"plan_reference": {
"week_start_date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. monday)"
},
"title": "string (e.g. Upper Body Hypertrophy)",
"started_at": "string (ISO timestamp)",
"completed_at": "string (ISO timestamp)",
"duration_seconds": "number (e.g. 3120)",
"exercises_logged": [
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"exercise_name": "string (e.g. Incline DB Bench Press)",
"sets": [
{
"set_number": "number (e.g. 1)",
"weight_lbs": "number (e.g. 60)",
"reps_completed": "number (e.g. 12)",
"completed": "boolean"
}
]
}
],
"user_notes": "string (optional subjective feedback)",
"perceived_exertion": "number (optional 1-10 RPE rating)"
}Payload de resposta:
{
"success": "boolean",
"log_id": "string (newly created workout log document ID)",
"summary": {
"total_volume_lbs": "number",
"total_sets_completed": "number",
"total_reps_completed": "number"
},
"new_prs": [
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"metric": "string (e.g. '10 Rep Max' or 'Estimated 1RM')",
"value": "string (e.g. '65 lbs' or '105 lbs')"
}
]
}3. Arquitetura do banco de dados do Cloud Firestore
Coleção: exercises
Hierarquia: coleção raiz (/exercises/{exercise_id}) • Finalidade: enciclopédia estática de exercícios com multimídia, dicas de postura, requisitos de equipamento e instruções.
{
"id": "string (canonical slug matching document ID, e.g. incline-db-bench-press)",
"name": "string (e.g. Incline DB Bench Press)",
"category": "string (e.g. Strength, Hypertrophy, Cardio, Mobility)",
"target_muscles": {
"primary": ["string (e.g. Upper Chest)"],
"secondary": ["string (e.g. Front Deltoids, Triceps)"]
},
"media": {
"video_url": "string (URL to demonstration video)",
"poster_url": "string (URL to video poster / thumbnail image)"
},
"instructions": ["string (step-by-step execution guidance)"],
"form_tips": ["string (critical cues and common pitfalls)"],
"equipment_required": ["string (e.g. Dumbbells, Incline Bench)"]
}Coleção: weekly_plans
Hierarquia: /users/{userId}/weekly_plans/{week_start_date} • Finalidade: o "Caderno espiral" que contém o cronograma de treinamento semanal completo, as métricas de resumo e os treinos de cada dia.
{
"week_start_date": "string (ISO date YYYY-MM-DD, e.g. 2026-09-07)",
"theme": "string (e.g. Hypertrophy Focus & Upper Body Overload)",
"summary": {
"total_time_minutes": "number (raw minutes, e.g. 180)",
"total_volume_lbs": "number (raw tonnage in lbs, e.g. 38400)",
"frequency": "number (active workout days, e.g. 4)"
},
"suggested_edits": ["string (pre-generated AI prompt suggestion)"],
"days": {
"monday": {
"date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. Monday)",
"is_rest_day": "boolean",
"status": "string (enum: scheduled | completed | skipped)",
"completed_log_id": "string (nullable)",
"completed_at": "string (ISO timestamp, nullable)",
"title": "string (e.g. Upper Body Hypertrophy)",
"focus_tag": "string (e.g. Push & Chest Dominant)",
"duration_minutes": "number",
"estimated_volume_lbs": "number",
"coach_note": "string",
"muscle_focus": [{ "muscle": "string", "intensity": "number (0-100)" }],
"suggested_edits": ["string"],
"blocks": [
{
"label": "string (e.g. Main Compound Block)",
"exercises": [
{
"exercise_id": "string",
"name": "string",
"target_sets": "number",
"target_reps": "string or number",
"rest_seconds": "number"
}
]
}
]
}
}
}Coleção: weekly_chats
Hierarquia: /users/{userId}/weekly_chats/{week_start_date} • Finalidade: o "Weekly Chat Bucket" contém o histórico completo de conversas de uma determinada semana de treinamento em uma única leitura de documento.
{
"week_start_date": "string (ISO date YYYY-MM-DD, e.g. 2026-09-07)",
"messages": [
{
"id": "string (unique message ID)",
"sender": "string (enum: user | coach)",
"text": "string (conversational message body)",
"created_at": "timestamp (server timestamp)",
"proposal": {
"id": "string (proposal UID, nullable)",
"type": "string (enum: UPDATE_DAY | UPDATE_WEEK, nullable)",
"target_day": "string (e.g. monday, nullable)",
"summary_diff": "string (nullable)",
"status": "string (enum: pending | accepted | dismissed)",
"data": "map (workout or week snapshot payload, nullable)"
}
}
]
}Coleção: workout_logs
Hierarquia: /users/{userId}/workout_logs/{log_id} • Finalidade: arquivo histórico permanente e imutável de sessões de treino concluídas.
{
"id": "string (document ID)",
"plan_reference": {
"week_start_date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. monday)"
},
"title": "string (e.g. Upper Body Hypertrophy)",
"started_at": "timestamp",
"completed_at": "timestamp",
"duration_seconds": "number",
"summary": {
"total_volume_lbs": "number",
"total_sets_completed": "number",
"total_reps_completed": "number"
},
"exercises_logged": [
{
"exercise_id": "string",
"exercise_name": "string",
"sets": [
{
"set_number": "number",
"weight_lbs": "number",
"reps_completed": "number",
"completed": "boolean"
}
]
}
],
"user_notes": "string (nullable)",
"perceived_exertion": "number (1-10 RPE, nullable)"
}Coleção: user_profiles
Hierarquia: /users/{userId}/profile/main • Finalidade: o "Gráfico do cliente ativo" que armazena o perfil de memória de três níveis do usuário.
{
"user_id": "string (document owner UID)",
"display_name": "string (e.g. Alex)",
"email": "string (e.g. alex@example.com)",
"preferences": {
"units": "string (enum: lbs | kg)",
"haptics": "boolean",
"sound": "boolean"
},
"equipment_ids": ["string (e.g. 'dumbbells', 'bench_adjustable', 'pull_up_bar')"],
"onboarding_status": "string (enum: not_started | in_progress | completed)",
"created_at": "timestamp",
"tier_1_staples": [
{
"id": "string (e.g. mem_01)",
"category": "string (enum: equipment | preference | goal | chronic_condition)",
"fact": "string (e.g. Home gym with dumbbells up to 50 lbs and adjustable bench)",
"source": "string (e.g. conversational_onboarding | manual_entry)"
}
],
"tier_2_physiological": [
{
"id": "string (e.g. mem_02)",
"body_part": "string (e.g. hamstrings, shoulder)",
"condition": "string (e.g. soreness, acute_fatigue, minor_strain)",
"fact": "string (e.g. Hamstrings are moderately sore from hill sprints)",
"effective_date": "string (ISO date YYYY-MM-DD)",
"expires_at": "timestamp"
}
],
"tier_3_situational": [
{
"id": "string (e.g. mem_03)",
"event": "string (e.g. golf_competition, travel)",
"fact": "string (e.g. Major golf tournament on Saturday; avoid excessive arm/lat fatigue on Friday)",
"week_start_date": "string (ISO date YYYY-MM-DD)",
"expires_at": "timestamp"
}
]
}Coleção: prs
Hierarquia: /users/{userId}/prs/{exercise_slug} • Finalidade: armazena recordes pessoais de todos os tempos, estimativas de 1 repetição máxima e recordes específicos de repetições.
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"exercise_name": "string (e.g. Incline DB Bench Press)",
"estimated_1rm": "number (calculated using Brzycki formula, e.g. 105)",
"max_weight_set": {
"weight_lbs": "number (e.g. 80)",
"reps": "number (e.g. 10)",
"achieved_at": "timestamp"
},
"rep_records": {
"10": 80,
"8": 85,
"5": 95
},
"last_updated": "timestamp"
}4. Ciclo de vida dos dados e pipeline de agentes
4.1 Pipeline 1: coaching, geração de treinos e modificação em etapas
- Ação do usuário: envia uma entrada de formato livre ou toca em um ícone de edição contextual pré-gerado.
- Montagem do contexto: o servidor recupera o perfil de memória de três níveis ativo e o plano da semana atual, removendo itens de memória expirados.
- Inferência do agente: o Gemini processa o comando e o contexto usando chamadas de função/ferramentas estruturadas.
- Mutação de memória autônoma: os fatos extraídos do usuário são aplicados a
/user_profiles/{userId}e sincronizados comequipment_ids. - Preparação da proposta em etapas: as modificações propostas para o treino são retornadas com
proposal.status = "pending"dentro de/weekly_chats/{week_start_date}. - Resolução e persistência do usuário: tocar em [Accept Plan] confirma os dados propostos para
/weekly_planse marca a proposta como"accepted".
4.2 Pipeline 2: execução e arquivamento de treinos (informações empíricas)
- Gravação de sessão ao vivo: o cliente acompanha a carga levantada, as repetições concluídas e as flags de conclusão no estado da interface local.
- Envio da sessão: o cliente chama
POST /api/workouts/logem "Finalizar treino". - Cálculo e validação agregados: o servidor calcula o volume cumulativo da sessão e verifica se há PRs usando a fórmula de Brzycki.
- Persistência imutável: grava um registro imutável em
/workout_logs, marca o dia"completed"em/weekly_planse faz upsert de novas PRs em/prs.
4.3 Pipeline 3: ingestão e invalidação de memória autônoma
- Ingestão: os fatos são extraídos continuamente de todas as interações de conversação.
- Classificação: nível 1 (básicos, sem validade), nível 2 (estado fisiológico, TTL de 3 a 4 dias), nível 3 (contexto situacional, TTL com base em eventos).
- Remoção e invalidação: os itens expirados (
current_timestamp > expires_at) são filtrados do contexto do comando e removidos de forma lenta.
5. Regras de segurança do Firestore e especificações de índice composto
5.1 Regras de segurança (firestore.rules)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
match /profile/{document=**} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /weekly_plans/{planId} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /weekly_chats/{chatId} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /prs/{exerciseSlug} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /workout_logs/{logId} {
allow read, create: if request.auth != null && request.auth.uid == userId;
allow update, delete: if false;
}
}
match /exercises/{exerciseId} {
allow read: if request.auth != null;
allow write: if false;
}
}
}5.2 Índices de consulta composta (firestore.indexes.json)
{
"indexes": [
{
"collectionGroup": "workout_logs",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "completed_at", "order": "DESCENDING" }]
},
{
"collectionGroup": "weekly_plans",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "week_start_date", "order": "DESCENDING" }]
},
{
"collectionGroup": "prs",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "last_updated", "order": "DESCENDING" }]
}
]
}6. Registro de alterações e decisões bloqueadas
| Data | Componente / Decisão | Tipo | Bloqueado por |
|---|---|---|---|
| Configuração inicial | Scaffold de especificação vazio inicializado | Configuração do sistema | Equipe |
| 2026-09-04 | Adicionamos as coleções do Firestore: exercises e weekly_plans | Esquema | Equipe |
| 2026-09-04 | Adição do endpoint POST /api/coach/chat e da coleção chat_messages | Endpoint e esquema | Equipe |
| 2026-09-04 | Armazenamento de chat refatorado para o esquema de bucket weekly_chats | Esquema | Equipe |
| 2026-09-04 | Adição do endpoint POST /api/workouts/log e da coleção workout_logs | Endpoint e esquema | Equipe |
| 2026-09-04 | Adicionamos o esquema de memória de três níveis user_profiles e atualizamos POST /api/coach/chat com memory_mutations | Endpoint e esquema | Equipe |
| 2026-09-04 | Seção 1 (Escopo e princípios do sistema) e Seção 4 (Ciclo de vida e pipelines de dados) concluídas | Conclusão de especificações completas | Equipe |
| 2026-09-22 | Especificações harmonizadas: pilha Next.js + Tailwind + Zustand, autenticação de portador, subcoleção de PR, regras do Firestore e slugs de exercício canônicos | Harmonização de especificações completas | Análise da arquitetura |