## AC-3 — Sessão persistente e logout

**PRD:** `docs/prd/identificacao-e-acesso.md`

**Como** participante casual, **quero** abrir o app e já estar dentro, **para**
não encontrar uma tela de login justamente quando estou correndo para palpitar
antes do apito.

**Prioridade:** Must — pedir login com o prazo estourando é o cenário exato
que faz o casual desistir e voltar ao WhatsApp.

**Critérios de aceite:**

- Dado que fiz login antes, quando abro o app, então já estou identificado sem
  digitar nada.
- Dado que fechei o app completamente, quando abro de novo, então a sessão
  continua válida.
- Dado que faço logout, quando abro o app, então preciso logar de novo e meus
  dados de bolão não ficam acessíveis.
- Dado que minha sessão expirou ou foi invalidada, quando tento palpitar, então
  sou levado ao login **sem perder o palpite que eu digitei** (PP-4).
- Dado que estou sem conexão com sessão válida, então continuo identificado e
  consigo digitar palpite em rascunho.

**Tasks:**

_Backend_
- [x] `BE-1` Emitir e validar credencial de sessão com prazo de validade (depende de AC-2 BE-1)
- [x] `BE-2` Endpoint de logout que invalida a sessão do device (depende de BE-1)
- [x] `BE-3` Renovação de sessão sem exigir novo login (depende de BE-1)

_Mobile_
- [x] `FE-1` Guardar a credencial de sessão em armazenamento seguro do device (depende de BE-1)
- [x] `FE-2` Restaurar sessão no cold start, sem tela de login (depende de FE-1)
- [x] `FE-3` Ação de logout, limpando sessão e dados locais de bolão (depende de BE-2)
- [x] `FE-4` Ao receber sessão inválida durante um envio de palpite, preservar o rascunho e levar ao login (depende de PP-4 FE-1)

_QA_
- [x] `QA-1` Escrever o plano de casos de teste a partir desta story
- [x] `QA-2` Executar o plano com app reinstalado, sessão expirada durante envio de palpite, e logout com rascunho pendente

**Riscos/Dependências:**

- Depende de AC-2 e PP-4.
- **O caso de borda que importa é sessão expirando no meio de um envio de
  palpite** com o prazo perto de fechar — perder o palpite aí é o pior
  resultado possível.
- Logout precisa limpar palpite de terceiro em cache (regra de sigilo, PP-7).

- **06/08:** achado ao testar o rascunho offline — abrir o app **sem rede** jogava
  o participante no login. O comentário no `restaurar()` já dizia "sem conexão
  não é sessão inválida" e o código fazia o contrário: os dois ramos caíam em
  `SemSessao`. Sem recuperação de senha nesta versão, deslogar por rede ruim
  pode custar a conta e o histórico. Agora a conta fica em cache no
  armazenamento seguro e a sessão sobrevive; token inválido continua limpando
  tudo, cache incluído.
- No caminho também foi corrigida uma fragilidade real: `restaurar()` usava
  `ref.read` e escrevia `state` depois de gaps assíncronos, sem checar
  `ref.mounted` — fechar o app no meio da restauração lançava erro assíncrono
  sem tratamento.

- **06/08 — plano em `docs/testes/AC-3-plano.md`; execução ✅ 12 de 12**
  (`docs/testes/AC-3-execucao.md`).
- Três lacunas eram de **segurança**, todas no mesmo tema (credencial que deveria
  estar morta): renovar com o refresh de sessão já revogada pelo logout
  (`AC-3-10` — se funcionasse, bastaria guardar o refresh antes de sair e o
  logout seria decorativo), token válido de conta **apagada** (`AC-3-11`, que é o
  caminho mais fácil de o guard devolver 500 em vez de 401) e a checagem de que o
  hash guardado não é **prefixo** do refresh (`AC-3-01`) — é assim que um hash
  truncado passaria pelo teste ingênuo.
- Erro meu no plano: escrevi `AC-3-10` presumindo logout pelo access token no
  header. Ele usa o **refresh no corpo**, e essa é a escolha certa — o access é um
  JWT, que não existe no banco para ser revogado.
