# `MG-8 QA-1` — Mapa de cobertura da migração

**Story:** `docs/stories/MG-8-paridade.md` · **Data:** 07/08/2026
**Gerado por:** `python3 docs/cobertura.py` (rode antes de acreditar nesta tabela)

O risco escrito na própria story é este: *"é a story mais fácil de fingir que foi
feita"*. Por isso o mapa é **script**, não tabela escrita à mão — tabela à mão
envelhece em silêncio, e quem lê não tem como saber.

O critério do script é rigoroso de propósito: um caso conta como verificado
quando **o id dele aparece** num teste da stack nova (rule, Function, widget ou
device) ou como origem declarada de um caso `MG-*`. Citação de id é o que permite
comparar linha a linha com a execução do NestJS.

## Onde estamos

| | casos | |
| -- | -- | -- |
| verificados com o id na stack nova | **188** | 44% |
| a verificar | 231 | 55% |
| cancelados | 1 | — |
| **total nos planos da fase 1** | **420** | |

**44% não é "44% do produto funciona".** É quanto do plano está *provado do jeito
que esta story exige*. Os 231 restantes se dividem em três grupos bem diferentes,
e tratá-los como uma coisa só seria o erro que a story avisa:

### Grupo A — teste existe, etiqueta não

Casos de tela cobertos pelos **244 testes de widget**, que continuam passando e
não dependem do backend: eles falam com repositório falso. O que falta é o id no
nome do caso. Não há risco de regressão silenciosa aqui; há risco de contabilidade
errada — que é o que este mapa existe para evitar.

### Grupo B — o mecanismo deixou de existir

Casos que testavam coisa que o Firebase Auth passou a fazer. O critério da story é
explícito: **reescrever e registrar, nunca descartar em silêncio**.

| Caso | O que testava | O que vale agora |
| -- | -- | -- |
| `AC-3-01`, `AC-3-05`, `AC-3-06`, `AC-3-10` | rotação e revogação de refresh token nossos | o SDK do Firebase renova e revoga sozinho; não há token nosso para rodar |
| `AC-3-07`, `AC-3-08` | logout invalidando o access token | `signOut()` local + a sessão do Auth; o caso vira "cold start depois do logout" |
| `AC-2-03`, `AC-2-04` | limite de tentativas de login | passa a ser do Auth (`too-many-requests`), com política do Google, não nossa |
| `AC-2-09` | ausência de recuperação de senha | **deixa de valer**: o Auth dá recuperação de graça, e `AC-4` volta ao escopo |
| `AC-1-02` | senha e hash nunca voltam na resposta | não existe resposta nossa com senha: a credencial nunca passa pelo nosso servidor |
| `PP-10-11` | trava de sincronização concorrente | virou lock em documento (`MG-6-03`/`MG-6-03b`) |
| `PR-3-11` | ranking em uma consulta | passa a medir **leituras**, e a medição está abaixo |

### Grupo C — falta reexecutar

O resto: casos de regra que existem igual nos dois backends e ainda não foram
reexecutados com o id na stack nova. É o trabalho de `QA-3`, e é onde ficam os
piores riscos por story:

- 🔴 `CV-2` (co-campeões) e `PP-8` (lembrete de prazo) estão em **zero**. `PP-8`
  depende de `MG-6 BE-3`/`BE-4`, que não existem ainda; `CV-2` é regra pura e não
  tem desculpa.
- 🟡 `PP-3` (contagem de tempo) em 1/11 e `PR-6` (recálculo) em 1/10.
- ✅ `PR-1` (cálculo do ponto) é a única em 100% — foi portada com os 14 casos.

## O que já foi reexecutado com o id original

O arquivo `paridade-seguranca.spec.ts` traz **34 casos** de sigilo, prazo, papel,
autorização e convite mantendo o id de origem: `PP-7-05` continua sendo
`PP-7-05`. Foi o grupo escolhido primeiro de propósito — é o que a story aponta
como o primeiro a sumir quando "os testes passam" basta como prova.

Duas coisas mudaram de **meio** e estão registradas caso a caso: onde o NestJS
recusava com um `if` num service, hoje recusa **o banco** (rule) ou **a callable**.
Para quem chama a API direto, a diferença é grande: não existe mais resposta para
filtrar.

## `QA-5` — custo medido, não estimado

Medido com um contador em volta do Firestore, rodando os fluxos de verdade contra
o emulador (`custo.spec.ts`). O Firestore cobra por **documento**, então é
documento que se conta.

| fluxo | leituras | escritas |
| -- | -- | -- |
| criar bolão (10 jogos) | 13 | 2 |
| entrar por convite | 14 | 1 |
| abrir a rodada no app (10 jogos) | 11 | 0 |
| palpitar na rodada (10 jogos) | 0 | 10 |
| ranking geral (página de 50, bolão de 60) | 50 | 0 |
| ranking da rodada (60 participantes) | 72 | 0 |
| extrato da rodada | 22 | 0 |
| quem não palpitou (60 participantes) | 82 | 0 |
| encerrar o bolão | 70 | 1 |

**O requisito não funcional está cumprido:** o ranking geral custa uma leitura por
**linha da página**, não por participante nem por palpite. Somar na leitura seria
60 × 10 × 38 documentos por abertura de tela — o free tier acabaria antes do
returno do campeonato. É o que justifica a classificação materializada.

O que **cresce com o bolão** é a cobrança da rodada (82 para 60 participantes) e o
encerramento (70). São ações raras — o admin abre a cobrança uma vez por rodada, e
encerra uma vez por temporada. Com o free tier em 50 mil leituras/dia, um bolão de
60 pessoas gasta por rodada, no pior caso, algo como:

- 60 pessoas × (11 abrir a rodada + 22 extrato + 50 ranking) ≈ **5.000 leituras**
- \+ 82 da cobrança + 72 do ranking da rodada ≈ 5.150

Uma rodada por semana, com folga de 10× para o participante que abre o app várias
vezes ao dia. **Cabe.**

## O que falta para `MG-8` fechar

- `QA-3`: reexecutar o grupo C, começando por `CV-2` e `PR-6`.
- `QA-4`: registrar divergência como defeito, com o id — ainda não houve nenhuma.
- 🔴 **Duas decisões do dono seguem abertas** e travam a paridade de `AC-2`:
  ligar a *email enumeration protection* no projeto real (sem ela, `AC-2-02`
  falha: o login diz se o e-mail existe) e decidir sobre o Identity Platform, sem
  o qual o mínimo de 8 caracteres de senha não é aplicado no servidor.
