# Controle de execução — bolão

**Regra:** uma **story** fecha quando **todas** as tasks dela fecham · um **PRD**
fecha quando **todas** as stories dele fecham ou são canceladas.
**Reabertura de task pelo `raro-qa` reabre a story e o PRD.**

Nunca marque um nível pulando o de baixo. Story com 9 de 10 tasks fica `- [ ]`.
PRD com uma story aberta fica `- [ ]`.

Os planos de casos de teste vivem em **`docs/testes/`** — um arquivo por story,
com o ID de cada caso. Reabertura de task cita o ID (`reaberta — falhou PP-5-03`).

**Fonte de verdade são os arquivos em `docs/stories/`.** Se este controle
divergir deles, este controle está errado.

---

## Resumo

| PRD | Stories | Tasks | Estado |
| --- | ------- | ----- | ------ |
| [Identificação e acesso](prd/identificacao-e-acesso.md) | 3/3 | 26/26 | ✅ concluído |
| [Palpite e prazo](prd/palpite-e-prazo.md) | 8/10 | 78/83 | 🟡 em andamento |
| [Pontuação e ranking](prd/pontuacao-e-ranking.md) | 7/8 | 66/67 | 🟡 em andamento |
| [Criação e convite](prd/criacao-e-convite.md) | 10/10 | 86/86 | ✅ concluído |
| [Ciclo de vida do bolão](prd/ciclo-de-vida-do-bolao.md) | 8/8 | 63/63 | ✅ concluído |
| [Premiação e cobrança](prd/premiacao-e-cobranca.md) | — | — | ⚪ fora de escopo |
| **Fase 1 — produto** | **35/38** | **312/318** | 🟡 |
| [Fase 2: backend no Firebase](prd/migracao-firebase.md) | 3/9 | 57/81 | 🟡 em andamento |
| **Total** | **38/47** | **369/399** | 🟡 |

O total caiu de 320 para 318 tasks em 07/08: `CC-7 BE-1` e `FE-1` foram
**canceladas** por decisão do dono, e task cancelada sai do denominador em vez de
ficar contando como pendente para sempre.

`CC-3` conta em dois PRDs (Criação e convite · Ciclo de vida) porque consolidou
a mesma regra escrita nos dois. Por isso a soma das linhas dá 39 e o total
distinto é 38.

---

## [x] PRD — Identificação e acesso
`docs/prd/identificacao-e-acesso.md` · bloqueia o topo do backlog

- [x] `AC-1` Cadastro com e-mail, senha e nome de exibição — **10/10** tasks
- [x] `AC-2` Login com e-mail e senha — **7/7** tasks
  _`FE-3` (link de "esqueci a senha") cancelada: não há recuperação_
- [x] `AC-3` Sessão persistente e logout — **9/9** tasks
- ~~`AC-4`~~ cancelada: sem serviço de e-mail nesta versão — **não há
  recuperação de senha**, esquecer a senha custa a conta em definitivo

## [ ] PRD — Palpite e prazo
`docs/prd/palpite-e-prazo.md`

- [x] `PP-1` Palpitar nos jogos com prazo aberto — **10/10** tasks
- [x] `PP-2` Editar palpite enquanto o prazo estiver aberto — **7/7** tasks
- [x] `PP-3` Contagem de tempo restante por jogo — **7/7** tasks
- [x] `PP-4` Rascunho offline com envio automático — **8/8** tasks
- [x] `PP-5` Bloqueio do palpite no horário oficial do jogo — **7/7** tasks
- [x] `PP-6` Reabertura automática quando o horário oficial muda — **8/8** tasks
- [x] `PP-7` Sigilo do palpite até o jogo terminar — **7/7** tasks
- [ ] `PP-8` Lembrete de prazo apenas para quem não palpitou — **6/10** tasks
  _`FE-1`/`FE-2`/`OPS-1`/`QA-2` presas ao FCM; sem push, o lembrete vive como aviso no app_
- [x] `PP-9` Admin vê quem ainda não palpitou — **7/7** tasks
- [ ] `PP-10` Job de sincronização com a fonte de dados — **11/12** tasks
  _só `OPS-1` (hospedagem que não hiberne) segue aberta — é escolha de infra_

## [ ] PRD — Pontuação e ranking
`docs/prd/pontuacao-e-ranking.md`

- [x] `PR-1` Calcular o ponto de cada palpite (10/5/3) — **9/9** tasks
- [x] `PR-2` Extrato do ponto por jogo — **7/7** tasks
- [x] `PR-3` Ranking geral do bolão — **9/9** tasks
- [x] `PR-4` Ranking da rodada, separado do geral — **8/8** tasks
- [x] `PR-5` Aplicar e exibir os critérios de desempate — **9/9** tasks
- [x] `PR-6` Recalcular pontos quando um placar é corrigido — **8/8** tasks
- [ ] `PR-7` Avisar quem foi afetado por um recálculo — **7/8** tasks
  _`QA-2` aberta: "permissão negada" não é executável sem push (FCM)_
- [x] `PR-10` Correção manual de placar pelo admin — **9/9** tasks
- ~~`PR-8`~~ cancelada: não existem cancelado nem W.O. — todo jogo não realizado
  é adiado, logo não há anulação a implementar
- ~~`PR-9`~~ cancelada: não é possível entrar atrasado, logo não há pontuação
  inicial a definir

## [x] PRD — Criação e convite
`docs/prd/criacao-e-convite.md`

- [x] `CC-1` Criar bolão com os jogos já carregados — **11/11** tasks
  _`BE-1` fechada em 07/08 com o token: `FootballDataAdapter` na mesma porta_
- [x] `CC-2` Definir tabela de pontos e desempate na criação — **8/8** tasks
  _`BE-3` verificado em 07/08: o cálculo, o extrato e o desempate leem a config do bolão_
- [x] `CC-3` Travar as regras no primeiro palpite — **7/7** tasks
- [x] `CC-4` Gerar e compartilhar o link de convite — **8/8** tasks
- [x] `CC-5` Entrar no bolão pelo link — **11/11** tasks
- [x] `CC-6` Ver as regras do bolão a qualquer momento — **7/7** tasks
- [x] `CC-7` Lista de participantes — **6/6** tasks
  _`BE-1`/`FE-1` **canceladas em 07/08**: o dono decidiu não rastrear quem só abriu o convite_
- [x] `CC-8` Promover e remover segundo administrador — **8/8** tasks
- [x] `CC-9` Remover participante do bolão — **10/10** tasks
  _`FE-3` resolvida em 07/08: ação indisponível para o criador, **com o motivo na linha dele**_
- [x] `CC-10` Lista dos meus bolões na abertura do app — **10/10** tasks
  _acrescentada em 06/08: sem ela o app não navega; nenhum épico original a cobria_

## [x] PRD — Ciclo de vida do bolão
`docs/prd/ciclo-de-vida-do-bolao.md`

- [x] `CV-1` Encerrar o bolão com campeão e ranking final — **8/8** tasks
- [x] `CV-2` Co-campeões quando o empate persiste — **6/6** tasks
- [x] `CV-3` Encerramento antecipado pelo admin — **8/8** tasks
- [x] `CV-4` Sair do bolão por vontade própria — **9/9** tasks
- [x] `CV-5` Histórico de bolões anteriores e títulos — **7/7** tasks
- [x] `CV-6` Arquivar e desarquivar bolão — **9/9** tasks
- [x] `CV-7` Recriar o bolão na temporada seguinte — **9/9** tasks
- [x] `CC-3` Travar as regras no primeiro palpite — **7/7** tasks
  _(mesma story de Criação e convite; fecha os dois PRDs)_

## [ ] PRD — Fase 2: backend no Firebase
`docs/prd/migracao-firebase.md` · arquitetura em `docs/spikes/backend-firebase.md`

Decidido em 07/08: Firestore + Cloud Functions, **migrando antes de desligar** — as
duas implementações coexistem até os 234 casos e2e passarem na nova. A ordem segue
o risco, não a facilidade.

- [x] `MG-1` Projeto Firebase e emuladores rodando local, sem custo — **8/8** tasks
  _emuladores de pé, rules fechadas com teste, e o JDK keg-only resolvido no script_
- [x] `MG-2` Modelo de dados no Firestore e regras de participação — **7/7** tasks
  _28 casos de rule verdes; "meus bolões" virou índice sob o usuário (consulta de grupo não dá)_
- [x] `MG-3` Sigilo do palpite e prazo aplicados nas security rules — **7/7** tasks
  _34 casos; descoberto que **rules não são filtros** — a consulta tem de provar o próprio acesso_
- [ ] `MG-4` Autenticação no Firebase Auth, com recuperação de senha — **4/10** tasks
  _servidor feito; app (`FE-*`) fica com `MG-7`. 🔴 O Auth **distingue** senha errada de e-mail inexistente e quebra `AC-2-02` — correção é configuração, não verificável em emulador_
- [ ] `MG-5` Apuração e classificação materializada em Cloud Functions — **8/10** tasks
  _regra **portada** do NestJS com os mesmos IDs (`PR-1-*`, `PR-5-*`); extrato, correção e aviso fecharam com `MG-7`_
- [ ] `MG-6` Sincronização e lembretes no Cloud Scheduler — **4/13** tasks
  _sincronização e lock em documento feitos; lembrete e push dependem de app no projeto real (`MG-7`)_
  _fecha `PP-10 OPS-1` e as 4 tasks de `PP-8` presas ao FCM_
- [ ] `MG-7` App falando com o Firebase pelos mesmos repositórios — **15/16** tasks
  _falta só `FE-6` (remover o `ApiClient`), que espera `MG-8` verificar a paridade antes de queimar a ponte de volta_
- [ ] `MG-8` Paridade verificada: os 234 casos na stack nova — **4/5** tasks
  _`docs/cobertura.py`: 281/420 com o id. Primeira divergência real registrada (`CV-4-01`), e corrigida_
- [ ] `MG-9` Desligar o backend NestJS — **0/5** tasks

**`MG-1` a `MG-8` não dependem de decisão financeira:** rodam em emulador, que não
exige cartão. Só o **deploy** exige plano Blaze — e essa decisão é do dono.

## ⚪ Premiação e cobrança — fora de escopo
`docs/prd/premiacao-e-cobranca.md` · sem dinheiro nesta versão, sem story

---

## Impedimentos que travam o início

Sobraram **6 tasks**, e nenhuma é código a escrever: 5 esperam a credencial do
FCM e 1 espera a escolha de hospedagem. Todas as decisões de produto foram
resolvidas em 07/08.

| Impedimento | Trava |
| ----------- | ----- |
| ~~Token da `football-data.org`~~ | **resolvido em 07/08** — `CC-1 BE-1`, `PP-10 BE-1`/`BE-6` e as duas `QA-2` fecharam |
| ~~Decisão `CC-7` (rastrear quem só abriu o convite)~~ | **resolvida em 07/08** — não rastrear; `BE-1`/`FE-1` canceladas |
| ~~Decisão `CC-9 FE-3` (recusa ao remover o criador)~~ | **resolvida em 07/08** — ação indisponível com o motivo visível |
| ~~Regra do empate (1x1 num 4x4)~~ | **confirmada em 07/08** — vale 3 pontos; nada muda no código |
| **Credencial do FCM** (`PP-8 OPS-1`) | `PP-8 FE-1`/`FE-2`/`QA-2` e `PR-7 QA-2` — **5 das 6 tasks abertas** |
| **Hospedagem que não hiberne** (`PP-10 OPS-1`) | o job de sincronização: dormindo, `PP-6` e `PR-1` param **em silêncio** |
| `git config user.name` / `user.email` vazios | o commit — decisão do dono é **não comitar** por enquanto |

### Instabilidade da suíte e2e — histórico e estado atual

**Causa 1 — transporte HTTP (06/08, mitigada).** A suíte falhava em ~1 de 7
execuções com `socket hang up`, sempre no transporte e nunca em regra de negócio.
Mitigada com um `app.listen(0)` por suíte, em vez de `listen`/`close` por
requisição. **Não reapareceu** desde então, mas nunca declarei corrigida: a causa
raiz não foi provada.

**Causa 2 — banco compartilhado entre dev e teste (07/08, resolvida).** `dev` e
`test` usavam o mesmo `DATABASE_URL`. Consequências reais, não teóricas:

- apagar dado de desenvolvimento perturbava uma execução da suíte (aconteceu em
  07/08: uma falha isolada, logo depois de eu limpar a tabela `matches`, que não
  reproduziu em sete execuções seguidas — e cujo nome eu não capturei);
- o servidor de dev roda com `JOBS_HABILITADOS=true`, então o cron podia tocar
  exatamente as linhas que a suíte estava afirmando;
- e desde 07/08 esse cron usa a **fonte real**, então podia trocar um placar de
  verdade no meio de um teste.

**Resolvido em 07/08:** a suíte e2e usa banco próprio (`bolao_test`, derivado com
sufixo `_test`; `DATABASE_URL_TEST` sobrescreve), criado e migrado por ela mesma
no `globalSetup`. A URL é injetada por `setupFiles`, porque o `PrismaService` lê
`DATABASE_URL` no construtor e cada worker do Jest tem o seu `process.env`.

**Evidência:** a suíte rodou **verde** enquanto o servidor de desenvolvimento, na
mesma máquina e com a fonte real, criava três bolões, importava 40 jogos novos e
executava três sincronizações. Antes, isso era exatamente a receita da
interferência: ao fim, `bolao` tinha 3 bolões e 1 usuário; `bolao_test`, zero.
