{"openapi":"3.1.0","info":{"title":"TAFEITO API","version":"1.1.0","description":"API da plataforma TAFEITO — gestão de treinos para preparação física de concursos (TAF). Expõe alunos, treinos prescritos, check-ins semanais, assinaturas e histórico de notificações de WhatsApp, e permite montar e corrigir treinos.\n\n**Leitura e escrita.** As operações de escrita (createStudentSession, updateSession, deleteSession, addSessionBlock, updateSessionBlock, deleteSessionBlock, duplicateStudentWeek, createStudent, updateStudent, setStudentAccess) exigem uma chave com escopo `write`; as demais funcionam com escopo `read`. Uma chave de leitura recebe 403 ao tentar escrever.\n\n**Nenhum endpoint envia mensagem de WhatsApp ao aluno.** O disparo continua sendo do app e do agendador — o que se escreve aqui aparece no app do aluno, mas não toca no celular dele.\n\nToda resposta inclui `generated_at`, `today` e `timezone` — use `today` como referência de data em vez de presumir a data atual.","contact":{"name":"TAFEITO"}},"servers":[{"url":"https://app.tafeitoconcurso.com.br/api/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Chave de API no header 'Authorization: Bearer <chave>'."}}},"paths":{"/overview":{"get":{"operationId":"getOverview","summary":"Panorama da operação","description":"Retrato geral: total de alunos, quantos ativos, quantos inadimplentes, treinos e check-ins da semana, aderência, estado das integrações e as notificações mais recentes. É a chamada certa para começar quando a pergunta é ampla ('como está a operação?', 'algum problema hoje?').","parameters":[{"name":"notifications_limit","in":"query","required":false,"schema":{"type":"integer","default":10,"maximum":100},"description":"Quantas notificações recentes trazer."}],"responses":{"200":{"description":"Sucesso."}}}},"/students":{"get":{"operationId":"listStudents","summary":"Listar e buscar alunos","description":"Índice de alunos com resumo da semana (treinos previstos, concluídos e pendentes), plano, situação da assinatura e data do último check-in. Use para localizar um aluno pelo nome antes de aprofundar, ou para responder de uma vez perguntas como 'quem está atrasado nos treinos?'.","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Busca por nome, e-mail ou telefone (parcial)."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["all","active","inactive","past_due"],"default":"all"},"description":"'active'/'inactive' filtram pelo acesso ao app; 'past_due' traz quem está com assinatura em atraso."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"maximum":500}}],"responses":{"200":{"description":"Sucesso."}}},"post":{"operationId":"createStudent","summary":"Cadastrar um aluno","description":"Cria o cadastro do aluno e já o vincula a um professor. Sem 'password', a senha provisória é o próprio e-mail e o aluno é obrigado a trocá-la no primeiro acesso. Sem 'professor', entra com o professor padrão.\n\nNÃO envia nenhuma mensagem ao aluno — quem avisa que a conta existe é a equipe. E-mail repetido devolve 409.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Nome completo do aluno."},"email":{"type":"string","description":"E-mail — é também o login."},"phone":{"type":"string","description":"WhatsApp com DDD (ex.: '11988887777'). O DDI 55 é acrescentado sozinho."},"goal":{"type":"string","description":"Objetivo, ex.: 'PM-SP 2027'."},"professor":{"type":"string","description":"Professor responsável, por nome ou e-mail. Padrão: professor padrão."},"password":{"type":"string","description":"Senha definitiva (mín. 6). Omita para usar o e-mail como senha provisória."},"plan_name":{"type":"string","description":"Cria também uma assinatura manual ativa com esse nome."},"due_date":{"type":"string","description":"Vencimento da assinatura (YYYY-MM-DD)."}},"required":["name","email"]}}}},"responses":{"200":{"description":"Aluno criado."},"409":{"description":"Já existe aluno com esse e-mail."}}}},"/agenda":{"get":{"operationId":"getAgenda","summary":"Agenda de todos os alunos em um período","description":"Treinos de toda a base agrupados por dia. Responde 'quem treina amanhã?' e 'o que ainda não foi concluído nesta semana?' em uma chamada, sem percorrer aluno por aluno. Traz só o cabeçalho de cada treino — use getSession para o detalhe dos blocos.","parameters":[{"name":"week","in":"query","required":false,"schema":{"type":"string"},"description":"Semana inteira (segunda a domingo). Use 'current', 'next', 'previous' ou uma data YYYY-MM-DD (devolve a semana que a contém)."},{"name":"date","in":"query","required":false,"schema":{"type":"string"},"description":"Um único dia. Aceita YYYY-MM-DD, 'today', 'tomorrow', 'yesterday'."},{"name":"from","in":"query","required":false,"schema":{"type":"string"},"description":"Início da janela. Aceita YYYY-MM-DD ou relativo ('today', '+7d', '-14d')."},{"name":"to","in":"query","required":false,"schema":{"type":"string"},"description":"Fim da janela, mesmo formato de 'from'. Padrão: 6 dias após 'from'."},{"name":"only_active","in":"query","required":false,"schema":{"type":"boolean","default":true},"description":"Se false, inclui também alunos com acesso cortado."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["planned","done","skipped"]}}],"responses":{"200":{"description":"Sucesso."}}}},"/sessions/{id}":{"get":{"operationId":"getSession","summary":"Detalhe de um treino","description":"Um treino específico com os blocos prescritos e, colado a cada bloco, o que o aluno de fato executou — permite comparar prescrito × realizado. É também onde saem os ids dos blocos, exigidos para editá-los.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID do treino, obtido em getAgenda ou getStudentSessions."}],"responses":{"200":{"description":"Sucesso."},"404":{"description":"Treino não encontrado."}}},"patch":{"operationId":"updateSession","summary":"Alterar um treino","description":"Muda data, título, foco, orientação ou situação do treino. Só os campos enviados são tocados. Marcar status 'done' carimba a conclusão; voltar para 'planned' limpa o carimbo. A alteração aparece na hora no app do aluno.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID do treino, obtido em getAgenda ou getStudentSessions."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string","description":"Data do treino. Aceita YYYY-MM-DD ou relativo: 'today', 'tomorrow', '+7d', '-14d'."},"title":{"type":"string","description":"Título do treino."},"focus":{"type":"string","description":"Foco, ex.: 'Resistência aeróbia'."},"coach_notes":{"type":"string","description":"Orientação exibida ao aluno."},"status":{"type":"string","enum":["planned","done","skipped"],"description":"'done' = concluído, 'skipped' = não realizado."},"sort_order":{"type":"integer","description":"Ordem quando há mais de um treino no dia."},"student_feedback":{"type":"string","description":"Relato do aluno sobre o treino."},"rpe":{"type":"integer","description":"Percepção de esforço do aluno, de 1 a 10."}}}}}},"responses":{"200":{"description":"Treino alterado."},"404":{"description":"Treino não encontrado."}}},"delete":{"operationId":"deleteSession","summary":"Apagar um treino","description":"Remove o treino do app do aluno, junto com seus blocos e os resultados que ele já tenha registrado. É definitivo — não há lixeira. Para tirar um treino da conta do aluno sem perder o histórico, prefira status 'skipped'.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID do treino, obtido em getAgenda ou getStudentSessions."}],"responses":{"200":{"description":"Treino apagado."},"404":{"description":"Treino não encontrado."}}}},"/modalities":{"get":{"operationId":"listModalities","summary":"Catálogo de modalidades do TAF","description":"Modalidades disponíveis (corrida, barra fixa, shuttle run…) e como cada uma é medida. Útil para interpretar os blocos de treino e para escolher o valor de 'modality' ao prescrever.","parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Sucesso."}}}},"/checkin-questions":{"get":{"operationId":"listCheckinQuestions","summary":"Perguntas do check-in semanal","description":"Enunciados e códigos das perguntas do check-in. Os códigos marcados como 'chartable' são os aceitos em getStudentMetrics.","parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Sucesso."}}}},"/students/{student}":{"get":{"operationId":"getStudent","summary":"Dossiê completo do aluno","description":"Perfil, preferências de notificação, assinatura (com dias restantes até o corte, se inadimplente), plano ativo, estatísticas históricas, os treinos da semana corrente e o último check-in — tudo em uma resposta. É a chamada certa para 'como está o fulano?'.","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."}],"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}},"patch":{"operationId":"updateStudent","summary":"Editar o cadastro do aluno","description":"Corrige dados do aluno (nome, e-mail, telefone, objetivo), troca o professor responsável e liga/desliga cada tipo de aviso de WhatsApp. Só os campos enviados mudam.\n\nNão corta nem libera acesso — para isso use setStudentAccess.","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string","description":"Troca também o login do aluno."},"phone":{"type":"string","description":"WhatsApp com DDD. Envie null para limpar."},"goal":{"type":"string","description":"Objetivo, ex.: 'PM-SP 2027'."},"professor":{"type":"string","description":"Novo professor responsável, por nome ou e-mail."},"notify_enabled":{"type":"boolean","description":"Chave geral do WhatsApp do aluno. False silencia tudo."},"notify_weekly_plan":{"type":"boolean","description":"Aviso com a planilha quando o professor termina a semana."},"notify_checkin":{"type":"boolean","description":"Lembrete do check-in, segunda de manhã."},"timezone":{"type":"string","description":"Fuso do aluno, ex.: 'America/Sao_Paulo'."}}}}}},"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}}},"/students/{student}/sessions":{"get":{"operationId":"getStudentSessions","summary":"Treinos do aluno em um período","description":"Treinos prescritos com todos os blocos e a prescrição já formatada (ex.: '6 × 800 m · 3:20/km · descanso 2min') — o mesmo texto que o aluno vê no app e no PDF do WhatsApp. Padrão: semana corrente.","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."},{"name":"week","in":"query","required":false,"schema":{"type":"string"},"description":"Semana inteira (segunda a domingo). Use 'current', 'next', 'previous' ou uma data YYYY-MM-DD (devolve a semana que a contém)."},{"name":"date","in":"query","required":false,"schema":{"type":"string"},"description":"Um único dia. Aceita YYYY-MM-DD, 'today', 'tomorrow', 'yesterday'."},{"name":"from","in":"query","required":false,"schema":{"type":"string"},"description":"Início da janela. Aceita YYYY-MM-DD ou relativo ('today', '+7d', '-14d')."},{"name":"to","in":"query","required":false,"schema":{"type":"string"},"description":"Fim da janela, mesmo formato de 'from'. Padrão: 6 dias após 'from'."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["planned","done","skipped"]},"description":"Filtra por situação do treino."}],"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}},"post":{"operationId":"createStudentSession","summary":"Criar um treino para o aluno","description":"Cria o treino com os blocos já prescritos, numa chamada só — é a forma certa de montar um treino. O aluno passa a ver o treino no app na data indicada; nenhuma mensagem é disparada por isso.\n\nExemplo de 'blocks': [{\"modality\":\"corrida\",\"sets\":6,\"distance_m\":800,\"target_pace\":\"3:20/km\",\"rest_seconds\":120}].","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string","description":"Data do treino. Aceita YYYY-MM-DD ou relativo: 'today', 'tomorrow', '+7d', '-14d'."},"title":{"type":"string","description":"Título do treino, ex.: 'Intervalado 6x800m'."},"focus":{"type":"string","description":"Foco, ex.: 'Resistência aeróbia'."},"coach_notes":{"type":"string","description":"Orientação exibida ao aluno."},"sort_order":{"type":"integer","description":"Ordem quando há mais de um treino no mesmo dia."},"blocks":{"type":"array","description":"Blocos da prescrição, na ordem em que o aluno vai executar. Máximo 30.","items":{"type":"object","properties":{"modality":{"type":"string","description":"Modalidade do bloco, por nome ou slug (ex.: 'corrida', 'barra fixa'). Veja o catálogo em listModalities. Nome ambíguo devolve 409 com as candidatas."},"label":{"type":"string","description":"Nome do bloco quando não é uma modalidade do catálogo (ex.: 'Aquecimento')."},"sets":{"type":"integer","description":"Séries ou tiros (ex.: 6 em '6 × 800 m')."},"reps":{"type":"integer","description":"Repetições por série (barra fixa, flexão, abdominal)."},"distance_m":{"type":"integer","description":"Distância em METROS (ex.: 800 para 800 m, 5000 para 5 km)."},"duration_seconds":{"type":"integer","description":"Duração alvo em SEGUNDOS (ex.: 720 para 12 minutos)."},"target_pace":{"type":"string","description":"Pace alvo, ex.: '3:20/km' ou '2:10/100m'."},"target_time":{"type":"string","description":"Tempo alvo do bloco, ex.: '12:00'."},"intensity":{"type":"string","description":"Intensidade, ex.: '80% FCmáx', 'PSE 7', 'ritmo de prova'."},"rest_seconds":{"type":"integer","description":"Descanso entre séries, em segundos."},"notes":{"type":"string","description":"Observação do professor sobre este bloco."},"order":{"type":"integer","description":"Posição do bloco no treino. Omitido, vai para o fim."}}}}},"required":["date","title"]}}}},"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}}},"/students/{student}/checkins":{"get":{"operationId":"getStudentCheckins","summary":"Check-ins semanais do aluno","description":"Histórico de check-ins com as respostas já pareadas ao enunciado da pergunta, do mais recente ao mais antigo. Use para avaliar percepção de carga, sono, dores e evolução relatada.","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":12,"maximum":200}}],"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}}},"/students/{student}/metrics":{"get":{"operationId":"getStudentMetrics","summary":"Série temporal de uma métrica de check-in","description":"Evolução numérica de uma resposta de check-in ao longo das semanas (peso, horas de sono, PSE…), com mínimo, máximo e variação total. Chame sem 'question' para descobrir quais códigos existem.","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."},{"name":"question","in":"query","required":false,"schema":{"type":"string"},"description":"Código da pergunta. Omitido, a resposta lista os códigos disponíveis."}],"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}}},"/students/{student}/subscription":{"get":{"operationId":"getStudentSubscription","summary":"Assinatura e histórico de cobrança do aluno","description":"Situação da assinatura (Eduzz ou manual), vencimento, ciclo de falhas de pagamento em curso, dias restantes até o corte de acesso e a trilha de eventos recebidos do provedor.","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."},{"name":"events_limit","in":"query","required":false,"schema":{"type":"integer","default":20,"maximum":100}}],"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}}},"/students/{student}/activities":{"get":{"operationId":"getStudentActivities","summary":"Treinos que o aluno executou, importados do Strava","description":"O que o aluno realmente fez, com todas as métricas que o Strava entrega: distância, tempo em movimento e total, pace, velocidade, ganho de elevação, frequência cardíaca média e máxima, cadência, potência, calorias e esforço relativo. Cada atividade diz a qual treino prescrito ela ficou vinculada.\n\nUse junto de getStudentSessions para comparar prescrito × realizado. Sem janela de datas, devolve o histórico recente.","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."},{"name":"week","in":"query","required":false,"schema":{"type":"string"},"description":"Semana inteira (segunda a domingo). Use 'current', 'next', 'previous' ou uma data YYYY-MM-DD (devolve a semana que a contém)."},{"name":"date","in":"query","required":false,"schema":{"type":"string"},"description":"Um único dia. Aceita YYYY-MM-DD, 'today', 'tomorrow', 'yesterday'."},{"name":"from","in":"query","required":false,"schema":{"type":"string"},"description":"Início da janela. Aceita YYYY-MM-DD ou relativo ('today', '+7d', '-14d')."},{"name":"to","in":"query","required":false,"schema":{"type":"string"},"description":"Fim da janela, mesmo formato de 'from'. Padrão: 6 dias após 'from'."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":30,"maximum":200}},{"name":"detail","in":"query","required":false,"schema":{"type":"boolean","default":false},"description":"Se true, inclui cada volta e cada parcial de 1 km. Respostas bem maiores — peça só quando a pergunta for sobre a variação dentro do treino."}],"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}}},"/students/{student}/notifications":{"get":{"operationId":"getStudentNotifications","summary":"Notificações de WhatsApp enviadas ao aluno","description":"Histórico de disparos (PDF semanal, aviso da véspera, aviso do dia, cobrança) com situação de entrega e erro, quando houve. Responde 'ele recebeu o treino da semana?'.","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":30,"maximum":200}}],"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}}},"/sessions/{id}/blocks":{"post":{"operationId":"addSessionBlock","summary":"Acrescentar um bloco a um treino","description":"Adiciona um bloco de prescrição a um treino que já existe. Sem 'order', vai para o fim. Para montar um treino inteiro de uma vez, prefira createStudentSession com a lista 'blocks'.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID do treino, obtido em getAgenda ou getStudentSessions."}],"requestBody":{"required":true,"description":"Ao menos 'modality' ou 'label'.","content":{"application/json":{"schema":{"type":"object","properties":{"modality":{"type":"string","description":"Modalidade do bloco, por nome ou slug (ex.: 'corrida', 'barra fixa'). Veja o catálogo em listModalities. Nome ambíguo devolve 409 com as candidatas."},"label":{"type":"string","description":"Nome do bloco quando não é uma modalidade do catálogo (ex.: 'Aquecimento')."},"sets":{"type":"integer","description":"Séries ou tiros (ex.: 6 em '6 × 800 m')."},"reps":{"type":"integer","description":"Repetições por série (barra fixa, flexão, abdominal)."},"distance_m":{"type":"integer","description":"Distância em METROS (ex.: 800 para 800 m, 5000 para 5 km)."},"duration_seconds":{"type":"integer","description":"Duração alvo em SEGUNDOS (ex.: 720 para 12 minutos)."},"target_pace":{"type":"string","description":"Pace alvo, ex.: '3:20/km' ou '2:10/100m'."},"target_time":{"type":"string","description":"Tempo alvo do bloco, ex.: '12:00'."},"intensity":{"type":"string","description":"Intensidade, ex.: '80% FCmáx', 'PSE 7', 'ritmo de prova'."},"rest_seconds":{"type":"integer","description":"Descanso entre séries, em segundos."},"notes":{"type":"string","description":"Observação do professor sobre este bloco."},"order":{"type":"integer","description":"Posição do bloco no treino. Omitido, vai para o fim."}}}}}},"responses":{"200":{"description":"Bloco criado — devolve o treino inteiro atualizado."},"404":{"description":"Treino não encontrado."}}}},"/sessions/{id}/blocks/{block}":{"patch":{"operationId":"updateSessionBlock","summary":"Alterar um bloco do treino","description":"Corrige a prescrição de um bloco (distância, pace, séries, descanso…). Só os campos enviados mudam. O id do bloco vem em 'blocks[].id' de getSession.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID do treino, obtido em getAgenda ou getStudentSessions."},{"name":"block","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID do bloco, do campo 'blocks[].id' de getSession."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"modality":{"type":"string","description":"Modalidade do bloco, por nome ou slug (ex.: 'corrida', 'barra fixa'). Veja o catálogo em listModalities. Nome ambíguo devolve 409 com as candidatas."},"label":{"type":"string","description":"Nome do bloco quando não é uma modalidade do catálogo (ex.: 'Aquecimento')."},"sets":{"type":"integer","description":"Séries ou tiros (ex.: 6 em '6 × 800 m')."},"reps":{"type":"integer","description":"Repetições por série (barra fixa, flexão, abdominal)."},"distance_m":{"type":"integer","description":"Distância em METROS (ex.: 800 para 800 m, 5000 para 5 km)."},"duration_seconds":{"type":"integer","description":"Duração alvo em SEGUNDOS (ex.: 720 para 12 minutos)."},"target_pace":{"type":"string","description":"Pace alvo, ex.: '3:20/km' ou '2:10/100m'."},"target_time":{"type":"string","description":"Tempo alvo do bloco, ex.: '12:00'."},"intensity":{"type":"string","description":"Intensidade, ex.: '80% FCmáx', 'PSE 7', 'ritmo de prova'."},"rest_seconds":{"type":"integer","description":"Descanso entre séries, em segundos."},"notes":{"type":"string","description":"Observação do professor sobre este bloco."},"order":{"type":"integer","description":"Posição do bloco no treino. Omitido, vai para o fim."}}}}}},"responses":{"200":{"description":"Bloco alterado — devolve o treino inteiro atualizado."},"404":{"description":"Bloco não pertence a esse treino."}}},"delete":{"operationId":"deleteSessionBlock","summary":"Remover um bloco do treino","description":"Apaga um bloco da prescrição. O treino continua existindo, com os demais blocos.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID do treino, obtido em getAgenda ou getStudentSessions."},{"name":"block","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID do bloco, do campo 'blocks[].id' de getSession."}],"responses":{"200":{"description":"Bloco removido — devolve o treino inteiro atualizado."},"404":{"description":"Bloco não pertence a esse treino."}}}},"/students/{student}/access":{"post":{"operationId":"setStudentAccess","summary":"Liberar ou cortar o acesso do aluno","description":"Portão único do sistema. Com 'active' false o aluno para de conseguir fazer login E sai do disparo de WhatsApp; os treinos e o histórico continuam guardados. Com true, tudo volta.\n\nCortar exige informar 'reason' — o motivo fica registrado no cadastro.","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"active":{"type":"boolean","description":"true libera o acesso, false corta."},"reason":{"type":"string","description":"Motivo do corte, ex.: 'inadimplência de 30 dias'. Obrigatório quando active=false."}},"required":["active"]}}}},"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}}},"/students/{student}/sessions/duplicate-week":{"post":{"operationId":"duplicateStudentWeek","summary":"Copiar a semana de treinos para a semana seguinte","description":"Duplica todos os treinos de uma semana do aluno para a semana seguinte, com os blocos, como planejados (sem os resultados). Atalho para quando a próxima semana repete a atual.\n\nRecusa se a semana de destino já tiver treinos, para não duplicar por engano.","parameters":[{"name":"student","in":"path","required":true,"schema":{"type":"string"},"description":"Identificação do aluno. Aceita UUID, e-mail, telefone ou parte do nome (ex.: 'Guilherme'). Se o nome for ambíguo, a resposta é 409 com a lista de candidatos — repita usando o e-mail ou o id de um deles."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"week":{"type":"string","description":"Semana de ORIGEM: 'current' (padrão), 'previous', 'next' ou uma data YYYY-MM-DD dentro dela. O destino é sempre a semana seguinte a ela."}}}}}},"responses":{"200":{"description":"Sucesso."},"404":{"description":"Aluno não encontrado."},"409":{"description":"Nome ambíguo — a resposta traz os candidatos."}}}}}}