# Rotas do projeto

Lista de todas as rotas registradas (`routes/web.php`, `routes/api.php` e o health check embutido
no `bootstrap/app.php`), com o que cada uma exige para ser acessada e o que ela devolve.

## 0. Convenções gerais

- **Base da API**: todas as rotas de negócio ficam sob o prefixo `/api/v1` (prefixo `api`
  adicionado automaticamente pelo Laravel a partir de `routes/api.php`, prefixo `v1` definido
  dentro do próprio arquivo).
- **Tenant obrigatório**: toda rota sob `/api/v1` passa pelo middleware `identify.tenant`. O
  cliente é identificado de uma das formas abaixo (ver `App\Http\Middleware\IdentifyTenant`):
  - **Pelo domínio (login por link)**: se o `Host` da requisição corresponder ao `domain`
    cadastrado de um tenant (ex.: `http://localhost.costarica/api/v1/login`), esse tenant é usado
    automaticamente — não é preciso enviar `X-Tenant-ID` nem `?tenant=`.
  - **Header** `X-Tenant-ID: <id_do_tenant>`, ou **query string** `?tenant=<id_do_tenant>` — usados
    quando o host da requisição não corresponde ao domínio de nenhum tenant (ex.: acesso direto por
    IP/host genérico, ferramentas internas, testes).
  - Se o host **corresponder** ao domínio de um tenant e **também** vier `X-Tenant-ID`/`?tenant=`
    com um id diferente desse tenant, a resposta é `409 { "error": "O tenant informado no
    header/query não corresponde ao domínio da requisição." }`.
  - Se o host **não** corresponder a nenhum domínio cadastrado e nenhum `X-Tenant-ID`/`?tenant=`
    for informado, a resposta é `400 { "error": "Tenant não informado." }`.
  - Se o id informado (por header/query, no caso acima) não existir na tabela central `tenants`, a
    resposta é `404 { "error": "Tenant inválido ou não encontrado." }`.
  - `tenants.domain` é obrigatório (`NOT NULL`) desde a migration
    `2026_08_07_000000_make_tenants_domain_not_null`.
  - O identificador do tenant **nunca** é usado para montar a URL da requisição — a API é sempre
    chamada na própria baseURL. O domínio (`domain`) cadastrado do tenant é só um dado auxiliar;
    identificação de verdade é sempre pelo `X-Tenant-ID`/`?tenant=`, inclusive nas rotas
    autenticadas (seção 4) — o middleware `ensure.tenant.domain` que antes exigia o host bater com
    o domínio do tenant não é mais usado em nenhuma rota (classe `EnsureTenantDomainMatch` segue no
    código, sem uso, caso precise reativar esse comportamento no futuro).
- **Autenticação**: rotas marcadas como "autenticada" abaixo exigem o middleware `auth:sanctum` em
  modo **stateful** (cookie de sessão httpOnly, não Bearer token) — ver
  `docs` de autenticação Sanctum/SPA. O login (`POST /api/v1/login`) autentica a sessão via cookie;
  chamadas seguintes reaproveitam esse cookie automaticamente (enviado pelo navegador). Sem sessão
  válida, a resposta é `401 { "message": "Unauthenticated." }`.
- **CSRF**: por ser autenticação stateful, requisições que alteram estado (`POST`) exigem que o
  front tenha buscado antes o cookie CSRF em `GET /sanctum/csrf-cookie` e reenviado o valor no
  header `X-XSRF-TOKEN`. Sem isso, a resposta é `419 { "message": "CSRF token mismatch." }`.
- **Content-Type**: todas as rotas de API recebem e devolvem JSON. Enviar sempre
  `Content-Type: application/json` (e, no Postman, marcar o body como *raw/JSON*).
- `bootstrap/app.php` habilita `statefulApi()` (Sanctum SPA) no grupo `api`. Não há rate limiting
  (`throttle`) configurado.

---

## 1. Web

### `GET /`

- **Middleware**: `web`
- **Headers**: nenhum obrigatório.
- **Body**: nenhum.
- **Retorno**: `200`, HTML (view `welcome`, a página padrão do Laravel). Não é uma rota de API.

---

## 2. Sistema

### `GET /up`

- **Middleware**: nenhum middleware de tenant/autenticação (health check do próprio framework,
  configurado em `bootstrap/app.php` via `health: '/up'`).
- **Headers**: nenhum obrigatório.
- **Body**: nenhum.
- **Retorno**: `200` quando a aplicação sobe normalmente; `500` se o boot da aplicação falhar.
  Usada por orquestradores (Docker/K8s) para verificar se o processo está de pé — não confirma
  conectividade com os bancos central/tenant.

---

## 3. API v1 — Rotas públicas

### `GET /api/v1/tenants`

- **Controller**: `App\Http\Controllers\Api\V1\TenantController@index`
- **Middleware**: nenhum (rota central, propositalmente fora do `identify.tenant` — é
  ela que permite ao consumidor descobrir o domínio/link de cada cliente).
- **Headers**: nenhum obrigatório.
- **Body**: nenhum.
- **Retorno**: `200`, lista de todos os tenants cadastrados na base central, ordenados por nome.
  O `id` retornado aqui é o valor a ser enviado no header `X-Tenant-ID` em todas as chamadas
  seguintes (`link`/domínio é só metadado, não é mais usado para montar nenhuma URL):
  ```json
  [
    { "id": "cliente-a", "nome": "Cliente A", "link": "cliente-a.example.com" }
  ]
  ```

As demais rotas desta seção precisam apenas do tenant identificado (`X-Tenant-ID` ou
`?tenant=`), não exigem login.

### `POST /api/v1/login`

- **Controller**: `App\Http\Controllers\Api\V1\AuthController@login`
- **Middleware**: `identify.tenant`
- **Headers**:
  - `X-Tenant-ID: <id_do_tenant>` — opcional se a requisição for feita direto para o domínio do
    tenant (login por link); pode ser substituído pela query string `?tenant=<id_do_tenant>`.
  - `Content-Type: application/json`
- **Body** (JSON, validado por `LoginRequest`):
  ```json
  {
    "login": "string, obrigatório",
    "senha": "string, obrigatório"
  }
  ```
- **Retornos**:
  - `200` — credenciais corretas e usuário ativo. Autentica a sessão via cookie httpOnly (guard
    `web`/Sanctum stateful) — sem token nenhum no body. A resposta inclui o header
    `X-Tenant-ID: <id_do_tenant>` com o tenant resolvido:
    ```json
    {
      "message": "Login realizado com sucesso.",
      "nome": "NOME DO USUÁRIO"
    }
    ```
  - `422` — `login`/`senha` ausentes (validação do `LoginRequest`), ou credenciais inválidas, ou
    usuário inativo (`usuarioativo != 1`):
    ```json
    { "message": "...", "errors": { "login": ["As credenciais fornecidas são inválidas."] } }
    ```
  - `400` — tenant não informado (host genérico e sem `X-Tenant-ID`/`?tenant=`).
  - `404` — `X-Tenant-ID`/`?tenant=` informado não existe na tabela `tenants`.
  - `409` — host corresponde ao domínio de um tenant, mas `X-Tenant-ID`/`?tenant=` informa outro id:
    ```json
    { "error": "O tenant informado no header/query não corresponde ao domínio da requisição." }
    ```

### `POST /api/v1/password/forgot`

- **Controller**: `App\Http\Controllers\Api\V1\PasswordResetController@forgot`
- **Middleware**: `identify.tenant`
- **Headers**:
  - `X-Tenant-ID: <id_do_tenant>` — opcional se acessado pelo domínio do tenant (ou `?tenant=` na
    query string).
  - `Content-Type: application/json`
- **Body** (validado inline via `$request->validate()`, sem `FormRequest` dedicado):
  ```json
  { "email": "string, obrigatório, formato de e-mail" }
  ```
- **Retornos**:
  - `200` — sempre, exista ou não o e-mail (evita enumerar usuários):
    ```json
    { "message": "Se o e-mail existir, as instruções foram enviadas." }
    ```
  - `422` — `email` ausente ou em formato inválido.
  - `400` — tenant não informado. `404` — tenant informado não existe.
  - `409` — host corresponde ao domínio de um tenant e `X-Tenant-ID`/`?tenant=` informa outro id.
  - Observação: o envio do e-mail em si ainda não está implementado (`PasswordResetService::sendResetLink`
    só grava o token no banco; o disparo do e-mail está comentado no código).

### `POST /api/v1/password/reset`

- **Controller**: `App\Http\Controllers\Api\V1\PasswordResetController@reset`
- **Middleware**: `identify.tenant`
- **Headers**:
  - `X-Tenant-ID: <id_do_tenant>` — opcional se acessado pelo domínio do tenant (ou `?tenant=` na
    query string).
  - `Content-Type: application/json`
- **Body** (validado inline via `$request->validate()`, sem `FormRequest` dedicado):
  ```json
  {
    "token": "string, obrigatório (recebido no fluxo de forgot)",
    "password": "string, obrigatório, mínimo 6 caracteres"
  }
  ```
- **Retornos**:
  - `200` — token válido e senha alterada:
    ```json
    { "message": "Senha alterada com sucesso." }
    ```
  - `400` — token inválido/expirado, **ou** tenant não informado (mesmo status `400` para os dois
    casos — ver `error` na resposta para diferenciar):
    ```json
    { "error": "Token inválido ou expirado." }
    ```
  - `404` — `X-Tenant-ID`/`?tenant=` informado não existe na tabela `tenants`.
  - `422` — `token`/`password` ausentes ou `password` com menos de 6 caracteres.
  - `409` — host corresponde ao domínio de um tenant e `X-Tenant-ID`/`?tenant=` informa outro id.

---

## 4. API v1 — Rotas autenticadas

Além do tenant (mesma identificação da seção 3 — `X-Tenant-ID`/`?tenant=`/domínio), exigem sessão
autenticada (middleware `auth:sanctum`, guard `web`, cookie httpOnly obtido em `POST /api/v1/login`).
Sem cookie de sessão válido, a resposta é `401 { "message": "Unauthenticated." }`.

Não há mais nenhuma exigência de que o `Host` da requisição seja o domínio cadastrado do tenant —
a identificação é sempre só pelo header/query, igual às rotas públicas (seção 3), inclusive aqui.

### `GET /api/v1/main`

- **Controller**: `App\Http\Controllers\Api\V1\MainController@index`
- **Middleware**: `identify.tenant`, `auth:sanctum`
- **Headers**:
  - `X-Tenant-ID: <id_do_tenant>` (ou `?tenant=` na query string).
  - Cookie de sessão (Sanctum stateful) — sem `Authorization: Bearer`.
- **Body**: nenhum (é um `GET`).
- **Retorno**:
  - `200` — lotação do professor autenticado, lida da view `view_lotacao_professor` (do cache do
    usuário, quando já existir; ver `docs/004 - Rota Main e Cache de Lotacao.md`):
    ```json
    [
      {
        "ano": 2026,
        "escolas": [
          {
            "escola": "NOME DA ESCOLA",
            "escola_id": 1,
            "turmas": [
              {
                "turma": "NOME DA TURMA",
                "tipo": "REGULAR",
                "turma_id": 10,
                "series": [
                  {
                    "serie": "NOME DA SERIE",
                    "serie_id": 5,
                    "disciplinas": [
                      { "disciplina": "NOME DA DISCIPLINA", "regencia_id": 123 }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
    ```
  - `401` — sem sessão autenticada.
  - `409` — host corresponde ao domínio de um tenant e `X-Tenant-ID`/`?tenant=` informa outro id.
  - `400`/`404` — tenant não informado/não encontrado (sem `X-Tenant-ID`/inválido).

### `POST /api/v1/logout`

- **Controller**: `App\Http\Controllers\Api\V1\AuthController@logout`
- **Middleware**: `identify.tenant`, `auth:sanctum`
- **Headers**:
  - `X-Tenant-ID: <id_do_tenant>` (ou `?tenant=` na query string).
  - Cookie de sessão (Sanctum stateful) + `X-XSRF-TOKEN` (CSRF).
- **Body**: nenhum.
- **Retorno**:
  - `200` — encerra a sessão (guard `web`) e limpa o cache de lotação do usuário:
    ```json
    { "message": "Logout realizado com sucesso." }
    ```
  - `401` — sem sessão autenticada.
  - `409` — host corresponde ao domínio de um tenant e `X-Tenant-ID`/`?tenant=` informa outro id.
  - `400`/`404` — tenant não informado/não encontrado (sem `X-Tenant-ID`/inválido).

### `GET /api/v1/lancamentos`

- **Controller**: `App\Http\Controllers\Api\V1\LancamentoController@index`
- **Middleware**: `identify.tenant`, `ensure.session.tenant`, `auth:sanctum`
- **Headers**:
  - `X-Tenant-ID: <id_do_tenant>` (ou `?tenant=` na query string).
  - Cookie de sessão (Sanctum stateful) — sem `Authorization: Bearer`.
- **Query string** (validada por `LancamentoMenuRequest`):
  ```
  ?ano=<ano_letivo>&regencia=<id_da_regencia>
  ```
  Ambos obrigatórios e numéricos. O `regencia` é o `regencia_id` recebido dentro de cada
  disciplina no retorno de `GET /api/v1/main`; o `ano` é o ano letivo (chave `ano` do mesmo
  retorno). É a rota chamada quando o professor clica em uma disciplina na tela inicial.
- **Body**: nenhum (é um `GET`).
- **Retorno**:
  - `200` — menus (nome, link, ícone) configurados para a disciplina/ano informados:
    ```json
    {
      "data": [
        { "nome": "Conteúdo Programático", "link": "/conteudo-programatico", "icone": "conteudo-programatico.svg" }
      ]
    }
    ```
  - `422` — `ano`/`regencia` ausentes ou não numéricos.
  - `401` — sem sessão autenticada.
  - `409` — host corresponde ao domínio de um tenant e `X-Tenant-ID`/`?tenant=` informa outro id.
  - `400`/`404` — tenant não informado/não encontrado (sem `X-Tenant-ID`/inválido).

### `GET /api/v1/atestados`

- **Controller**: `App\Http\Controllers\Api\V1\AtestadoAlunoController@index`
- **Middleware**: `identify.tenant`, `ensure.session.tenant`, `auth:sanctum`
- **Headers**:
  - `X-Tenant-ID: <id_do_tenant>` (ou `?tenant=` na query string).
  - Cookie de sessão (Sanctum stateful) — sem `Authorization: Bearer`.
- **Query string** (validada por `AtestadoAlunoRequest`):
  ```
  ?regencia=<id_da_regencia>
  ```
  Obrigatório e numérico. É o `regencia_id` da disciplina (mesmo valor usado em
  `GET /api/v1/lancamentos`), aberto a partir do card "Atestados dos Alunos" na tela de
  lançamentos da disciplina.
- **Body**: nenhum (é um `GET`).
- **Retorno**:
  - `200` — atestados dos alunos da turma da regência informada, ordenados por data de início:
    ```json
    {
      "data": [
        {
          "aluno_nome": "NOME DO ALUNO",
          "data_inicio": "01/03/2026",
          "data_fim": "05/03/2026",
          "tipo_ausencia": "ATESTADO MÉDICO"
        }
      ]
    }
    ```
  - `422` — `regencia` ausente ou não numérico.
  - `401` — sem sessão autenticada.
  - `409` — host corresponde ao domínio de um tenant e `X-Tenant-ID`/`?tenant=` informa outro id.
  - `400`/`404` — tenant não informado/não encontrado (sem `X-Tenant-ID`/inválido).

### `POST /api/v1/password/change`

- **Controller**: `App\Http\Controllers\Api\V1\AuthController@changePassword`
- **Middleware**: `identify.tenant`, `auth:sanctum`
- **Headers**:
  - `X-Tenant-ID: <id_do_tenant>` (ou `?tenant=` na query string).
  - Cookie de sessão (Sanctum stateful) + `X-XSRF-TOKEN` (CSRF).
  - `Content-Type: application/json`
- **Body** (JSON, validado por `ChangePasswordRequest`):
  ```json
  {
    "senha_atual": "string, obrigatório",
    "nova_senha": "string, obrigatório, mínimo 6 caracteres"
  }
  ```
- **Retornos**:
  - `200` — senha atual correta, senha alterada:
    ```json
    { "message": "Senha alterada com sucesso." }
    ```
  - `422` — `senha_atual`/`nova_senha` ausentes, `nova_senha` com menos de 6 caracteres, ou
    `senha_atual` incorreta:
    ```json
    { "message": "...", "errors": { "senha_atual": ["A senha atual informada está incorreta."] } }
    ```
  - `401` — sem sessão autenticada.
  - `409` — host corresponde ao domínio de um tenant e `X-Tenant-ID`/`?tenant=` informa outro id.
  - `400`/`404` — tenant não informado/não encontrado (sem `X-Tenant-ID`/inválido).

---

## 5. Resumo

| Método | Rota | Headers obrigatórios | Body | Login |
|---|---|---|---|---|
| GET | `/` | nenhum | — | não |
| GET | `/up` | nenhum | — | não |
| GET | `/api/v1/tenants` | nenhum | — | não |
| POST | `/api/v1/login` | `X-Tenant-ID`\* | `login`, `senha` | não |
| POST | `/api/v1/password/forgot` | `X-Tenant-ID`\* | `email` | não |
| POST | `/api/v1/password/reset` | `X-Tenant-ID`\* | `token`, `password` | não |
| GET | `/api/v1/main` | `X-Tenant-ID`\*, cookie de sessão | — | sim |
| GET | `/api/v1/lancamentos` | `X-Tenant-ID`\*, cookie de sessão | query `ano`, `regencia` | sim |
| GET | `/api/v1/atestados` | `X-Tenant-ID`\*, cookie de sessão | query `regencia` | sim |
| POST | `/api/v1/logout` | `X-Tenant-ID`\*, cookie de sessão, `X-XSRF-TOKEN` | — | sim |
| POST | `/api/v1/password/change` | `X-Tenant-ID`\*, cookie de sessão, `X-XSRF-TOKEN` | `senha_atual`, `nova_senha` | sim |

\* Dispensável se a requisição for feita direto para o domínio (`domain`) cadastrado do tenant —
ver seção 0. Não há mais distinção entre rotas públicas e autenticadas quanto a isso: em nenhuma
delas o host precisa ser o domínio do tenant.
