# Guia de reconstrução — Prumo (do zero ao banco de dados)

> **O que é este documento.** Uma receita linear e reproduzível para recriar o
> Prumo do zero, da pasta vazia até o app com banco de dados rodando em produção.
> Já incorpora as correções descobertas no caminho — você não repete os erros que
> nós cometemos. O *porquê* de cada armadilha está no fim, no
> [Apêndice de armadilhas](#apêndice-de-armadilhas), e o registro narrativo
> completo está em `doc/etapa-4-banco-e-deploy-webhook.md`.
>
> **Cobertura:** Etapas 1 a 4 (estrutura → health → deploy → banco). As etapas 5–8
> (landing, auth, OAuth, dashboard) entram aqui conforme forem concluídas.
>
> **Como ler:** cada parte tem *arquivos a criar* (com o conteúdo exato),
> *comandos* e *como verificar*. Faça uma parte por vez e confirme antes de seguir.

---

## Pré-requisitos

- **Python 3.14** local (instalado via pyenv-win neste projeto). A produção roda
  **3.13** — manter compatibilidade entre as duas versões.
- **Git** e uma conta no **GitHub** (repo privado).
- **Conta de hospedagem com cPanel** que ofereça *Setup Python App* (Passenger WSGI)
  e *Git Version Control*. Aqui é `bianch.in` servindo `app.prumoboard.com`.
- Editor de texto e terminal (os comandos abaixo usam PowerShell no Windows e bash
  no terminal do cPanel).

### Visão geral da arquitetura (para orientar)

- **Factory pattern:** `app/app.py` exporta `create_app()`. Nenhuma rota mora lá —
  só registro de blueprints e init de extensões.
- **Entrada de produção:** `passenger_wsgi.py` **na raiz** chama `create_app()` e
  expõe `application`.
- **Rotas em blueprints:** `app/routes/<contexto>/`, uma subpasta por assunto, cada
  uma com `__init__.py` exportando `blueprint`.
- **Config por ambiente:** `app/config.py` lê tudo de variáveis de ambiente, que
  vêm do `.env` (nunca commitado).
- **Banco:** SQLite no dev, MySQL (PyMySQL) na produção — escolhido pela mesma
  `DATABASE_URL`.

---

## Parte 1 — Estrutura de pastas e arquivos base

**Objetivo:** árvore de diretórios completa, `create_app()` sem rotas, Flask sobe
sem erros.

### 1.1 Criar a árvore

```
C:\Pr\app\prumo\
├── app/
│   ├── app.py
│   ├── config.py
│   ├── passenger_wsgi.py        (cópia dev — a de produção fica na raiz, ver Parte 3)
│   ├── requirements.txt
│   ├── .env.example
│   ├── .cpanel.yml
│   ├── routes/
│   │   ├── __init__.py          (vazio)
│   │   ├── health/__init__.py   (vazio por enquanto)
│   │   ├── auth/__init__.py     (vazio)
│   │   └── main/__init__.py     (vazio)
│   ├── models/__init__.py       (vazio)
│   └── services/__init__.py     (vazio)
├── site/
│   └── .htaccess
├── .gitignore
└── README.md
```

> Os `__init__.py` vazios em `routes/`, `routes/health/`, `routes/auth/`,
> `routes/main/`, `models/` e `services/` são o que torna cada pasta um pacote
> importável. Crie todos já, mesmo os que só serão usados nas etapas seguintes.

### 1.2 `app/config.py`

Uma única classe `Config`; tudo vem do ambiente, com defaults seguros para dev:

```python
import os


class Config:
    SECRET_KEY = os.environ.get("SECRET_KEY", "dev-insecure-key")
    SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///prumo.db")
    GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID")
    GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET")
    DEPLOY_TOKEN = os.environ.get("DEPLOY_TOKEN")
```

> ⚠️ **Atenção ao nome:** a chave precisa se chamar `SQLALCHEMY_DATABASE_URI`
> (é o nome que o Flask-SQLAlchemy lê), mas no `.env` a variável é `DATABASE_URL`.
> O mapeamento acontece nesta linha. Ver [Armadilha D](#armadilha-d).

### 1.3 `app/app.py` (versão da Parte 1, ainda sem blueprints)

```python
from flask import Flask
from config import Config


def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)
    return app
```

> Este arquivo cresce a cada etapa. A versão final (Parte 4) está em
> [4.4](#44-appapppy-versão-final-da-parte-4).

### 1.4 `app/requirements.txt`

Já liste tudo que o projeto vai usar até a Etapa 8 — evita `pip install` repetido:

```
Flask>=3.1
SQLAlchemy>=2.0
Flask-SQLAlchemy>=3.1
PyMySQL>=1.1
Flask-Login>=0.6
Authlib>=1.3
python-dotenv>=1.0
```

> **PyMySQL** é Python puro — funciona em shared hosting sem extensão C compilada
> (driver C de MySQL costuma falhar no cPanel).

### 1.5 `app/.env.example`

Template commitável (o `.env` real **nunca** vai pro git):

```
# Gere um valor seguro: python -c "import secrets; print(secrets.token_hex(32))"
SECRET_KEY=

# SQLite local (desenvolvimento):
DATABASE_URL=sqlite:///prumo.db
# MySQL produção (cPanel):
# DATABASE_URL=mysql+pymysql://usuario:senha@localhost/prumo

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# Token para o endpoint POST /health/deploy (gere com: python -c "import secrets; print(secrets.token_hex(32))")
DEPLOY_TOKEN=
```

### 1.6 `.gitignore`

Base do template Python do GitHub, com estes acréscimos críticos no fim:

```
.env
.venv
instance/
deploy.ini
erros.txt
app/instance/prumo.db
```

> ⚠️ `deploy.ini` guarda o token de deploy — **jamais** commitar. O SQLite local é
> gerado em `app/instance/`, não na raiz de `app/` (ver [Armadilha E](#armadilha-e)).

### 1.7 `site/.htaccess`

Redireciona o domínio raiz para o subdomínio do app:

```apache
Redirect 301 / https://app.prumoboard.com/
```

### 1.8 `README.md`

Cartão de visita curto — stack e deploy em duas linhas.

### 1.9 Criar o venv e instalar

```powershell
cd C:\Pr\app\prumo
py -3.14 -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r app\requirements.txt
```

### Verificar a Parte 1

```powershell
cd C:\Pr\app\prumo\app
flask --app app run --debug
```
Esperado: o servidor sobe sem erros. `curl http://localhost:5000/` retorna **HTTP
404** — correto, porque ainda não existe nenhuma rota.

---

## Parte 2 — Blueprint de diagnóstico (health)

**Objetivo:** três endpoints JSON de diagnóstico num blueprint próprio. O
`/health/hello` vira o **canário do deploy** — deve responder em todas as etapas
seguintes.

### 2.1 `app/routes/health/health.py`

(Versão da Parte 2 — o endpoint `/deploy` é adicionado na Parte 3.)

```python
from datetime import datetime, timezone
from flask import Blueprint, jsonify

blueprint = Blueprint("health", __name__, url_prefix="/health")


@blueprint.get("/")
def health():
    return jsonify({"status": "ok", "service": "prumo", "timestamp": datetime.now(timezone.utc).isoformat()})


@blueprint.get("/ping")
def ping():
    return jsonify({"pong": True})


@blueprint.get("/hello")
def hello():
    return jsonify({"message": "Hello Prumo"})
```

> Timestamp em UTC com `datetime.now(timezone.utc).isoformat()` (não use o
> `datetime.utcnow()`, deprecado).

### 2.2 `app/routes/health/__init__.py`

Expõe o blueprint para import limpo (`from routes.health import blueprint`):

```python
from .health import blueprint
```

### 2.3 Registrar no `app/app.py`

Adicione, dentro de `create_app()`:

```python
    from routes.health import blueprint as health_bp
    app.register_blueprint(health_bp)
```

### Verificar a Parte 2

```powershell
flask --app app run --debug
```
```bash
curl http://localhost:5000/health/        # {"status":"ok","service":"prumo","timestamp":"..."}
curl http://localhost:5000/health/ping    # {"pong":true}
curl http://localhost:5000/health/hello   # {"message":"Hello Prumo"}
```
`curl http://localhost:5000/` ainda retorna 404 (não há rota raiz ainda — correto).

---

## Parte 3 — Repositório GitHub e deploy no cPanel

**Objetivo:** código no GitHub e `curl https://app.prumoboard.com/health/hello`
retornando `{"message":"Hello Prumo"}` em produção.

> Esta parte é quase toda **manual** (cPanel + GitHub). É a etapa mais traiçoeira —
> leia as armadilhas [A](#armadilha-a), [B](#armadilha-b) e [C](#armadilha-c)
> **antes** de começar.

### 3.1 `passenger_wsgi.py` na **raiz** do projeto

Este é o entry point de produção. Ele carrega o `.env`, ajusta o `sys.path` para
apontar para `app/` (onde mora o factory) e expõe `application`:

```python
import sys
import os
from dotenv import load_dotenv

load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'app', '.env'))

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'app'))

from app import create_app

application = create_app()
```

> ⚠️ Três detalhes que **já custaram caro** (ver [Armadilha A](#armadilha-a)):
> 1. O `sys.path.insert` aponta para `app/`, não para a raiz — senão `from app
>    import create_app` não acha o factory.
> 2. O `load_dotenv` é obrigatório: em produção o Passenger **não** passa por
>    `flask run`, então ninguém mais carregaria o `.env` (ver
>    [Armadilha F](#armadilha-f)). Sem isso, `DEPLOY_TOKEN`, `SECRET_KEY` etc.
>    chegam vazios.
> 3. Cuidado para o arquivo na raiz não importar a si mesmo (recursão infinita) —
>    o factory está em `app/app.py`, e é dele que importamos.

### 3.2 `app/.cpanel.yml`

Roda `pip install` após cada pull feito pela interface de Git do cPanel:

```yaml
---
deployment:
  tasks:
    - export DEPLOYPATH=/home/$USER/app.prumoboard.com
    - /bin/pip install -r $DEPLOYPATH/requirements.txt
```

### 3.3 Subir para o GitHub

```powershell
cd C:\Pr\app\prumo
git init
git add -A
git commit -m "chore: estrutura inicial"
# criar repo privado "prumo" no GitHub, depois:
git remote add origin git@github.com:<user>/prumo.git
git push -u origin main
```

### 3.4 Configurar o cPanel (manual)

1. **Subdomínio:** Domains → Create Subdomain → `app` em `prumoboard.com` →
   document root `/home/bianchin/public_html/app.prumoboard.com/`.
2. **Python App:** Software → Setup Python App:
   - Python **3.13**
   - Application root: `/home/bianchin/public_html/app.prumoboard.com`
   - Startup file: `passenger_wsgi.py`
   - Entry point: `application`
3. **Deploy Key:** gerar chave SSH no cPanel e adicioná-la ao GitHub como **Deploy
   Key** (read-only).
4. **Git Version Control:** clonar `git@github.com:<user>/prumo.git` em
   `/home/bianchin/public_html/app.prumoboard.com`.
5. **Primeiro `pip install` (manual):** no terminal do cPanel, ativar o virtualenv
   e instalar — só nesta primeira vez (depois o `.cpanel.yml` cuida):
   ```bash
   source /home/bianchin/virtualenv/public_html/app.prumoboard.com/3.13/bin/activate
   cd /home/bianchin/public_html/app.prumoboard.com
   pip install --prefer-binary -r app/requirements.txt
   ```
6. **`site/.htaccess`** vai para `public_html/` do domínio raiz, manualmente, uma
   única vez.

### 3.5 Mecanismo de deploy: endpoint `POST /health/deploy`

> **Por que não usar o deploy "nativo" do cPanel?** Porque ele não é confiável: a
> API `VersionControl/update` retorna sucesso **sem** executar o `git pull`, e o
> "Update from Remote" da interface faz o pull mas **não** roda o `.cpanel.yml`
> (logo não reinicia o Passenger). SSH externo está bloqueado nesta hospedagem.
> Ver armadilhas [B](#armadilha-b) e [C](#armadilha-c). A solução é o próprio app
> fazer o deploy via HTTPS.

Adicione o endpoint ao `app/routes/health/health.py` (no topo, troque os imports e
acrescente as constantes e a função):

```python
import os
import subprocess
from datetime import datetime, timezone
from flask import Blueprint, jsonify, request, current_app

blueprint = Blueprint("health", __name__, url_prefix="/health")

REPO_ROOT = "/home/bianchin/public_html/app.prumoboard.com"
VENV_PIP = "/home/bianchin/virtualenv/public_html/app.prumoboard.com/3.13/bin/pip"
RESTART_FILE = os.path.join(REPO_ROOT, "tmp", "restart.txt")

# ... os três endpoints GET / , /ping , /hello da Parte 2 continuam aqui ...


@blueprint.post("/deploy")
def deploy():
    expected = current_app.config.get("DEPLOY_TOKEN")
    if not expected:
        return jsonify({"error": "deploy not configured"}), 503

    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Bearer ") or auth[7:] != expected:
        return jsonify({"error": "unauthorized"}), 401

    def run(cmd, **kwargs):
        return subprocess.run(cmd, capture_output=True, text=True, **kwargs)

    pull = run(["git", "pull", "origin", "main"], cwd=REPO_ROOT)
    if pull.returncode != 0:
        return jsonify({"error": "git pull failed", "detail": pull.stderr}), 500

    pip = run([VENV_PIP, "install", "--prefer-binary", "-q", "-r", "app/requirements.txt"], cwd=REPO_ROOT)
    if pip.returncode != 0:
        return jsonify({"error": "pip install failed", "detail": pip.stderr}), 500

    open(RESTART_FILE, "w").close()      # touch tmp/restart.txt → reinicia o Passenger

    return jsonify({"ok": True, "pull": pull.stdout.strip()})
```

### 3.6 Cliente local de deploy: `deploy.py` (na raiz)

```python
import configparser
import urllib.request
import urllib.parse
import json
import sys

config = configparser.ConfigParser()
config.read("deploy.ini")

deploy_token = config["deploy"]["token"]
app_url = config["deploy"]["app_url"]


def deploy_call():
    url = f"{app_url}/health/deploy"
    req = urllib.request.Request(
        url,
        method="POST",
        headers={"Authorization": f"Bearer {deploy_token}"},
    )
    try:
        with urllib.request.urlopen(req, timeout=60) as resp:
            return resp.status, json.loads(resp.read())
    except urllib.error.HTTPError as e:
        return e.code, json.loads(e.read())


print("Disparando deploy...")
status, data = deploy_call()
if status != 200:
    print(f"Erro no deploy (HTTP {status}): {data}", file=sys.stderr)
    sys.exit(1)

print(f"Deploy concluído. {data.get('pull', '')}")
```

### 3.7 `deploy.ini` (na raiz, **gitignored**)

```ini
[deploy]
token = <mesmo valor do DEPLOY_TOKEN no .env do servidor>
app_url = https://app.prumoboard.com
```

> O **mesmo token** precisa estar em dois lugares: `deploy.ini [deploy] token`
> (local) e `DEPLOY_TOKEN` no `.env` **do servidor**. Gere com
> `python -c "import secrets; print(secrets.token_hex(32))"`.

### 3.8 O problema ovo-e-galinha do bootstrap

O mecanismo que conserta o deploy precisa, ele mesmo, ser deployado por um meio
confiável. Por isso os **primeiros** commits desta etapa ainda dependem de pull +
restart manuais no terminal do cPanel:

```bash
cd /home/bianchin/public_html/app.prumoboard.com
git pull origin main
source /home/bianchin/virtualenv/public_html/app.prumoboard.com/3.13/bin/activate
pip install --prefer-binary -r app/requirements.txt
touch tmp/restart.txt        # reinicia o Passenger
```

Também garanta a linha do token no `.env` do servidor (uma vez):
```bash
echo "DEPLOY_TOKEN=<TOKEN>" >> /home/bianchin/public_html/app.prumoboard.com/app/.env
touch /home/bianchin/public_html/app.prumoboard.com/tmp/restart.txt
```
Do commit que adiciona o `load_dotenv` no `passenger_wsgi.py` em diante,
`python deploy.py` passa a funcionar sozinho.

### O ciclo de deploy a partir daqui

```powershell
git push origin main
python deploy.py        # POST /health/deploy → git pull + pip install + restart
```

### Verificar a Parte 3

```bash
curl -i https://app.prumoboard.com/health/hello   # HTTP 200, {"message":"Hello Prumo"}
curl https://app.prumoboard.com/health/           # status + timestamp
```
Se falhar → cPanel → Logs → Error Log.

---

## Parte 4 — Banco de dados e modelo User

**Objetivo:** SQLAlchemy configurado (SQLite dev / MySQL prod) e a tabela `users`
criada via `flask create-db`.

### 4.1 `app/database.py`

A instância do SQLAlchemy fica isolada aqui para evitar import circular:

```python
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


def init_db(app):
    db.init_app(app)
```

### 4.2 `app/models/user.py`

```python
from datetime import datetime, timezone
from database import db


class User(db.Model):
    __tablename__ = "users"

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True, nullable=False)
    name = db.Column(db.String(255))
    password_hash = db.Column(db.String(255), nullable=True)
    google_id = db.Column(db.String(128), nullable=True)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
```

> `password_hash` e `google_id` são nullable de propósito: um usuário pode ter só
> senha (Etapa 6) ou só Google (Etapa 7).

### 4.3 `.gitignore` — banco local

Confirme que `app/instance/prumo.db` está ignorado (já incluído na Parte 1.6). O
Flask grava o SQLite em `instance/`, **não** na raiz de `app/` (ver
[Armadilha E](#armadilha-e)).

### 4.4 `app/app.py` (versão final da Parte 4)

```python
import click
from flask import Flask
from config import Config
from database import db, init_db


def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)

    init_db(app)

    from models.user import User  # noqa: F401 — registra o modelo no metadata do SQLAlchemy

    from routes.health import blueprint as health_bp
    app.register_blueprint(health_bp)

    @app.cli.command("create-db")
    def create_db_command():
        db.create_all()
        click.echo("Database created.")

    return app
```

> ⚠️ **O import do `User` dentro do factory é obrigatório** e não é decorativo.
> `db.create_all()` só cria tabelas dos modelos **registrados no metadata** do
> SQLAlchemy, e um modelo só é registrado quando seu módulo é importado. Sem essa
> linha, `flask create-db` roda sem erro mas **não cria a tabela `users`**. Ver
> [Armadilha G](#armadilha-g). O `# noqa: F401` evita que o linter remova o import
> "não usado".

### Verificar a Parte 4

```powershell
cd app
flask --app app create-db          # cria app/instance/prumo.db + tabela users
flask --app app shell
>>> from models.user import User
>>> User.query.all()
[]
```

---

## Apêndice de armadilhas

Os *porquês* completos, com sintoma → causa → solução. Cada uma tem um link de
volta no corpo do guia.

### Armadilha A — `passenger_wsgi.py` quebrado de três jeitos
Na primeira subida em produção, três erros distintos no `passenger_wsgi.py` da raiz:
**(1)** recursão infinita (uma versão antiga importava a si mesma); **(2)**
`sys.path` apontando para a raiz em vez de `app/`, então `from app import
create_app` não achava o factory; **(3)** faltava `load_dotenv`, então o `.env`
nunca era lido em produção. A versão correta está em [3.1](#31-passenger_wsgipy-na-raiz-do-projeto).

### Armadilha B — `VersionControl/update` mente
A API do cPanel `VersionControl/update` retornava `status: 1` (sucesso) **sem
executar o `git pull`**. O `deploy.py` antigo dizia "Deploy concluído" e o código
novo nunca chegava ao servidor. Foi o que motivou trocar tudo pelo endpoint
`POST /health/deploy`, que mostra o diff real do pull (ex.: `Fast-forward ...`).

### Armadilha C — SSH externo bloqueado nesta hospedagem
Tentamos deploy via SSH direto (como o GitHub Actions faria). Não dá: porta 22 →
`Connection refused`; porta 26 → respondia mas era SMTP (Exim); 2222/65002/22022/
2200 → timeout. Um scan só achou serviços web/FTP/DNS. O terminal do cPanel é um
*jail* restrito (sem `ss`/`netstat` úteis, sem acesso ao `sshd_config`). Rota
abandonada — daí o deploy via HTTPS. (O GitHub Actions em `.github/workflows/
deploy.yml` resolveria, mas está bloqueado por pendência de billing.)

### Armadilha D — `DATABASE_URL` vs `SQLALCHEMY_DATABASE_URI`
O Flask-SQLAlchemy só lê a config chamada `SQLALCHEMY_DATABASE_URI`. No `.env` a
variável é `DATABASE_URL` (nome mais convencional). O `config.py` faz a ponte na
linha `SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", ...)`. Se você usar
o nome errado, o app cai no SQLite default sem avisar.

### Armadilha E — o SQLite some
`Test-Path app\prumo.db` dava `False` mesmo após criar o banco. Causa: o Flask
grava o SQLite na pasta `instance/`, não na raiz de `app/`. Por isso o
`.gitignore` ignora `app/instance/prumo.db`, e é lá que o arquivo aparece.

### Armadilha F — `503 deploy not configured` (token vazio em produção)
O endpoint `/health/deploy` respondia, mas `DEPLOY_TOKEN` vinha vazio. Causa: em
produção o Passenger usa `passenger_wsgi.py` direto — **não** passa por `flask
run`, que é quem normalmente carrega o `.env`. Solução: `load_dotenv()` no topo do
`passenger_wsgi.py` apontando para `app/.env`. Diagnóstico no servidor:
```bash
cat /home/bianchin/public_html/app.prumoboard.com/app/.env   # a linha DEPLOY_TOKEN existe?
```

### Armadilha G — `flask create-db` cria o banco mas não a tabela
`flask create-db` rodava sem erro, mas `User.query.all()` falhava com
`OperationalError: no such table: users`. Causa: `db.create_all()` só cria tabelas
dos modelos registrados no metadata, e `User` nunca era importado. Solução:
`from models.user import User  # noqa: F401` **dentro** de `create_app()`. Depois,
deletar o `prumo.db` vazio e recriar.

### Armadilha H — Passenger não reinicia após "Update from Remote"
O código novo chegava ao servidor (confirmado por `tail`), mas o app continuava na
versão antiga. Causa: o "Update from Remote" do cPanel só faz `git pull` — não roda
as tarefas do `.cpanel.yml` (que incluem o `touch tmp/restart.txt`). Solução no
bootstrap: `touch tmp/restart.txt` manual. Depois que o `/health/deploy` passou a
existir, ele mesmo faz o restart.

---

## Comandos de troubleshooting (referência rápida)

Caminhos no servidor:
- App root: `/home/bianchin/public_html/app.prumoboard.com`
- venv pip: `/home/bianchin/virtualenv/public_html/app.prumoboard.com/3.13/bin/pip`
- `.env`: `/home/bianchin/public_html/app.prumoboard.com/app/.env`

```bash
# Deploy normal
git push origin main && python deploy.py

# Forçar só o restart do Passenger
touch /home/bianchin/public_html/app.prumoboard.com/tmp/restart.txt

# Confirmar versão no ar
curl https://app.prumoboard.com/health/hello
git -C /home/bianchin/public_html/app.prumoboard.com log --oneline -3

# Gerar novo token de deploy (atualizar nos dois lados + restart)
python -c "import secrets; print(secrets.token_hex(32))"

# Banco local
cd app && flask --app app create-db && flask --app app shell
```

---

## Histórico de etapas cobertas

| Etapa | Tópico | Parte deste guia |
|-------|--------|------------------|
| 1 | Factory + estrutura de pastas | Parte 1 |
| 2 | Blueprint health | Parte 2 |
| 3 | GitHub + deploy cPanel | Parte 3 |
| 4 | SQLAlchemy + User | Parte 4 |
| 5–8 | Landing, auth, OAuth, dashboard | *pendentes — entram aqui ao concluir* |
