# Roteiro: Sunrise Walk

**Objetivo:** feature pessoal que calcula o horário do nascer do sol (localização
fixa) e o horário de saída de casa para caminhar, com um offset em minutos
configurável (antes/depois do nascer do sol) persistido numa tabela singleton.

> ## Como usar este arquivo (protocolo de execução)
> - Executar **uma etapa por vez**, na ordem. Cada etapa é **estanque** e tem como
>   verificar no fim.
> - Ao concluir uma etapa: marcar `[x]` (etapa e sub-itens), **preencher "Notas de
>   execução"**, salvar este arquivo e **PARAR**.
> - **Esperar o aval** do usuário **ou** a correção de erros. Erros → corrigir na
>   mesma etapa e parar de novo.
> - **No aval:** fazer **commit + push** dos arquivos da etapa (incluindo este
>   roteiro e os untracked relevantes). Depois **esperar a liberação** para iniciar a
>   **próxima** etapa.
> - **Entrega é do Claude; deploy/teste costuma ser do usuário.** Onde disser
>   "Verificar", o Claude entrega e descreve o teste; se não der para rodar aqui, o
>   usuário roda e retorna o comportamento (ou os erros/logs).

---

## Decisões (já acordadas)

1. Tabela `sunrise_walk_config` (nome já definido no contexto), linha única
   (singleton), coluna de minutos: `offset_minutes` (inteiro, com sinal, default
   `-15`). Negativo = antes do nascer do sol; positivo = depois.
2. Cálculo do nascer do sol: biblioteca **`astral`** (cálculo local, offline —
   sem chamada de rede a cada request). Adicionar a `app/requirements.txt`.
3. Localização fixa (hardcoded, não configurável por enquanto):
   lat `-22.902528365649843`, long `-43.559773079601726`,
   timezone `America/Sao_Paulo`.
4. Endpoint sob blueprint `personal`, rota `/personal/sunrise-walk` (kebab-case
   na URL, conforme convenção REST do projeto), com `@login_required` — mesmo
   padrão do resto do app.
5. Interface em inglês, seguindo a regra geral do CLAUDE.md. Título da página:
   "Walk with the sunrise". Checkbox: "before" + "sunrise"; botão: "Send".
6. Uma única rota `GET`/`POST` (mesmo padrão de `routes/issues/issues.py`):
   `GET` renderiza o form com os horários calculados; `POST` atualiza
   `offset_minutes` e re-renderiza com os novos horários.
7. Estrutura de arquivos segue o padrão existente: `routes/personal/`
   (blueprint), `models/sunrise_walk_config.py`, `services/sunrise_walk_service.py`,
   `templates/personal/sunrise_walk.html`.

---

## Estado atual (pontos de toque)

- @app/app.py — registra blueprints e imports de models; vai ganhar mais um
  blueprint e um model.
- @app/requirements.txt — vai ganhar a dependência `astral`.
- @app/models/issue.py — modelo de referência (padrão de `__tablename__`,
  `db.Column`, `__repr__`).
- @app/routes/issues/issues.py e @app/routes/issues/__init__.py — padrão de
  blueprint + rota GET/POST + `__init__.py` exportando `blueprint`.
- @app/templates/tickets/form.html — padrão de template de formulário a seguir.
- @app/templates/base.html — template base a estender.
- @doc/db-naming-guideline.md — convenção de nomenclatura de banco.
- @issues/personal/sunrise-walk/sunrise-walk-context.md — contexto original do
  pedido.

---

## Etapas

### [x] Etapa 1 — Model `SunriseWalkConfig`

**Objetivo:** criar a tabela singleton `sunrise_walk_config` via SQLAlchemy model.

Arquivos: @app/models/sunrise_walk_config.py, @app/app.py

- [x] Criar `app/models/sunrise_walk_config.py`:
  - `__tablename__ = "sunrise_walk_config"`
  - `id` (PK inteiro)
  - `offset_minutes` (Integer, `nullable=False`, `default=-15`)
  - `__repr__`
- [x] Registrar o import do model em `app/app.py` (junto aos outros
  `from models.X import Y  # noqa: F401`).

**Verificar:** rodar `flask --app app create-db` (ou reaproveitar o comando
existente) e confirmar que a tabela `sunrise_walk_config` foi criada no SQLite
de dev (`app/prumo.db`), sem erro e sem afetar as tabelas existentes.

_Notas de execução:_
- `app/.venv` estava quebrado: apontava para um Python pyenv 3.14.5 que não
  existe mais no disco (`C:\Users\Ricardo\.pyenv\...\3.14.5\python.exe`).
  Recriado com o Python 3.14.6 disponível em
  `C:\Users\Ricardo\AppData\Local\Python\bin\python.exe` (aprovado pelo
  usuário) e `requirements.txt` reinstalado.
- `flask --app app create-db` rodou sem erro: "Database created and
  roles/projects seeded."
- O SQLite real usado pelo Flask é `app/instance/prumo.db` (resolução padrão
  do Flask-SQLAlchemy via `instance_path`), não `app/prumo.db` na raiz de
  `app/` (que é um arquivo antigo/solto, não usado). Confirmado via
  `sqlite3`/`PRAGMA table_info` que `sunrise_walk_config` foi criada com
  `id INTEGER PK` e `offset_minutes INTEGER NOT NULL`, e que as tabelas
  existentes (`users`, `roles`, `issues`, etc.) permanecem intactas.

---

### [x] Etapa 2 — Serviço de cálculo (astral) + acesso ao singleton

**Objetivo:** encapsular o cálculo do nascer do sol/horário de saída e o
get-or-create da linha única de config.

Arquivos: @app/requirements.txt, @app/services/sunrise_walk_service.py

- [x] Adicionar `astral` a `app/requirements.txt` e instalar no venv local.
- [x] Criar `app/services/sunrise_walk_service.py` com:
  - Constantes de localização (lat, long, timezone) conforme Decisão 3.
  - `get_config()` — busca a única linha; se não existir, cria com
    `offset_minutes=-15`, faz commit e retorna.
  - `update_offset_minutes(value)` — atualiza a linha única (via `get_config()`)
    e persiste.
  - `calculate_times(offset_minutes)` — usa `astral.sun.sun` com a localização
    fixa e a data atual (calculada no timezone `America/Sao_Paulo`, não no
    timezone do servidor) para obter o horário do nascer do sol; soma
    `offset_minutes` para obter o horário de saída; retorna ambos
    (timezone-aware, já convertidos para `America/Sao_Paulo`).
- [x] Instalar a dependência nova no `.venv` local (`pip install -r
  app/requirements.txt`).

**Verificar:** via `flask --app app shell` (ou script pontual), chamar
`get_config()` e `calculate_times(-15)` e conferir que retornam um horário de
nascer do sol plausível para hoje e a saída 15 minutos antes.

_Notas de execução:_
- `astral>=3.2` adicionado a `app/requirements.txt` e instalado no venv
  (`app/.venv`, Python 3.14) — trouxe `tzdata` como dependência transitiva.
- `app/services/sunrise_walk_service.py` criado usando `astral.Observer` +
  `astral.sun.sun`, com `ZoneInfo("America/Sao_Paulo")` fixo (via `zoneinfo`
  da stdlib) tanto para calcular a data de "hoje" quanto para o `tzinfo`
  passado ao `sun()`, evitando o risco de dia errado perto da meia-noite em
  servidor UTC.
- `calculate_times` retorna a tupla `(sunrise, departure)`, ambos
  timezone-aware.
- Verificado via `create_app()` + `app_context()`: `get_config()` achou a
  linha singleton existente (`offset_minutes=-15`); `calculate_times(-15)`
  retornou nascer do sol `2026-07-09 06:35:45-03:00` (plausível para
  inverno no Rio) e saída `2026-07-09 06:20:45-03:00` (15 min antes).

---

### [x] Etapa 3 — Blueprint `personal` / rota `sunrise-walk`

**Objetivo:** expor a página em `GET/POST /personal/sunrise-walk`, autenticada.

Arquivos: @app/routes/personal/__init__.py, @app/routes/personal/sunrise_walk.py, @app/app.py

- [x] Criar `app/routes/personal/sunrise_walk.py`:
  - `blueprint = Blueprint("personal", __name__, url_prefix="/personal")`
  - Rota `@blueprint.route("/sunrise-walk", methods=["GET", "POST"])`,
    `@login_required`.
  - `POST`: lê checkbox "before" + valor numérico sem sinal do form; monta o
    `offset_minutes` com sinal (negativo se "before" marcado); chama
    `update_offset_minutes`.
  - Em ambos os métodos: chama `get_config()` e `calculate_times(...)`;
    renderiza `personal/sunrise_walk.html` passando config e horários
    calculados.
- [x] Criar `app/routes/personal/__init__.py` exportando `blueprint` (padrão
  de `routes/issues/__init__.py`).
- [x] Registrar o blueprint em `app/app.py`.

**Verificar:** com o servidor local rodando (`flask --app app run --debug`) e
uma sessão logada, `curl`/browser em `/personal/sunrise-walk` deve responder
200 (sem login, deve redirecionar para login).

_Notas de execução:_
- `app/routes/personal/sunrise_walk.py` criado seguindo o padrão de
  `routes/issues/issues.py`: blueprint `personal`, rota
  `/personal/sunrise-walk` (GET/POST) com `@login_required`. No POST, lê
  `offset_minutes` (valor sem sinal) e checkbox `before` do form; monta o
  offset com sinal negativo quando "before" está marcado e chama
  `update_offset_minutes`. Em ambos os métodos busca `get_config()`,
  calcula `sunrise`/`departure` via `calculate_times` e renderiza
  `personal/sunrise_walk.html` (arquivo ainda não existe — Etapa 4 — mas o
  import/registro do blueprint não depende do template existir).
- `app/routes/personal/__init__.py` exporta `blueprint` a partir de
  `sunrise_walk.py`.
- `app/app.py`: adicionado `from routes.personal import blueprint as
  personal_bp` + `app.register_blueprint(personal_bp)` logo após o registro
  de `issues_bp`.
- Verificação: `python -c "from app import create_app; ..."` confirmou que
  `/personal/sunrise-walk` está registrada no `url_map` sem erros de import.
  Subiu `flask --app app run --debug` e `curl` sem sessão logada retornou
  `302` para `http://localhost:5000/login?next=%2Fpersonal%2Fsunrise-walk`,
  confirmando o `@login_required`. Servidor de teste encerrado ao final
  (não deixado rodando em background).

---

### [x] Etapa 4 — Template e verificação end-to-end

**Objetivo:** tela "Walk with the sunrise" com o controle de offset e os
horários calculados.

Arquivos: @app/templates/personal/sunrise_walk.html

- [x] Criar `app/templates/personal/sunrise_walk.html` (estende `base.html`):
  - Título "Walk with the sunrise".
  - Checkbox "before" + texto "sunrise".
  - Input numérico sem sinal para os minutos (valor absoluto do
    `offset_minutes` atual; checkbox pré-marcado se o valor salvo for
    negativo).
  - Botão "Send" (submit do form POST).
  - Abaixo: horário do nascer do sol e horário de saída de casa, formatados
    (ex. `HH:MM`).
- [x] Testar manualmente no browser: logar, abrir a página, alternar o
  checkbox e o valor, enviar, e confirmar que os horários exibidos mudam
  conforme o offset salvo.

**Verificar:** o usuário loga, acessa `/personal/sunrise-walk`, ajusta o
offset (ex. "15 minutos depois" desmarcando "before") e confirma visualmente
que o horário de saída passa a ser 15 min **depois** do nascer do sol exibido.

_Notas de execução:_
- `app/templates/personal/sunrise_walk.html` criado estendendo `base.html`,
  reaproveitando as classes já existentes (`ticket-form-wrap`, `form`,
  `form-actions`, `btn btn-primary`, `ticket-meta`) do padrão de
  `templates/tickets/form.html`. Form POST para `url_for('personal.sunrise_walk')`;
  checkbox `before` pré-marcado via `config.offset_minutes < 0`; input
  `offset_minutes` com o valor absoluto (`|abs`); horários formatados com
  `strftime('%H:%M')`.
- Verificação end-to-end feita via browser (Claude in Chrome), com um usuário
  de teste temporário (`sunrise-test@prumo.test`) criado e removido só para
  este teste — não sobrou no banco. Servidor local (`flask --app app run
  --debug --no-reload`) subido e derrubado no final.
  - Estado inicial (offset `-15`, salvo nas etapas anteriores): "before"
    marcado, "15" minutos, Sunrise `06:35`, Departure `06:20` (15 min antes,
    correto).
  - Desmarcado "before" e clicado "Send" (mantendo 15 minutos): página
    recarregou com Sunrise `06:35` e Departure `06:50` — 15 min **depois**
    do nascer do sol, confirmando que o sinal do offset é persistido e
    recalculado corretamente.

---

## Riscos / observações

- `astral` calcula o nascer do sol para uma **data**; usar a data atual no
  timezone fixo (`America/Sao_Paulo`), não a data/hora do servidor (que pode
  rodar em UTC no cPanel), para não pegar o dia errado perto da meia-noite.
- O padrão do projeto é sempre calcular o "dia de hoje" no timezone da
  localização fixa, não no timezone do processo Flask.
- `db.create_all()` (via `flask create-db`) só cria tabelas que não existem —
  seguro rodar de novo em cima do banco de dev já populado.
- Sem testes automatizados no projeto ainda (conforme CLAUDE.md) — toda
  verificação é manual/curl, como nas etapas acima.
- Depois do primeiro `git push` desta feature, lembrar de rodar
  `python deploy.py` (regra do projeto).
