# Execução — plano `CC-5` (06/08)

**Veredito: ✅ Aprovado** — 12 de 12.

> Primeira passada (06/08, manhã) foi **⚠️ parcial, 10 de 12**: `CC-5-02` e
> `CC-5-12` dependiam de `CC-5 FE-1` (deep link) e `AC-1 FE-4`, então **não havia
> o que executar**. As duas tasks foram feitas na sequência e os casos passaram.

| Caso | Resultado | Evidência |
| ---- | --------- | --------- |
| CC-5-01 | passou | `test/convite.e2e-spec.ts` + `test/cc5_plano_test.dart` |
| CC-5-02 | passou | `test/convite_funil_test.dart` + device (app fechado) |
| CC-5-03 | passou | `test/convite.e2e-spec.ts` — 404 |
| CC-5-04 | passou | idem — 410, distinto de 404 |
| CC-5-05 | passou | idem — regeração revoga o anterior |
| CC-5-06 | passou | idem — 409 "já começou" |
| CC-5-07 | passou | idem — 410 "encerrado" |
| CC-5-08 | passou | idem + `test/cc5_plano_test.dart` |
| CC-5-09 | passou | idem — a saída é definitiva |
| CC-5-10 | passou | idem — 401 sem token |
| CC-5-11 | passou | `test/cc5_plano_test.dart` — quatro textos distintos |
| CC-5-12 | passou | idem — folha de boas-vindas com regras e prazo |

## Três defeitos silenciosos no deep link

O `intent-filter` funcionava e o link **não fazia nada** — o pior tipo de falha,
porque não há erro nenhum para investigar. Foram quatro camadas, cada uma
escondendo a seguinte:

1. **Formato do link.** Com esquema próprio, o Flutter recebe só o `path` da URI:
   `bolao://convite/<token>` chegava como `/<token>`.
2. **`flutter_deeplinking_enabled` ausente.** Sem essa meta-data o embedding
   Android abre o app pelo intent mas não entrega a URI ao Router.
3. **Riverpod 3 descarta todo provider por padrão.** O router grava o token
   dentro do `redirect`, momento em que ninguém observa aquele provider — sem
   `ref.keepAlive()` o token morria antes de a tela inicial existir.
4. **Ordem do `redirect`.** A checagem do convite estava **depois** da guarda de
   "sessão carregando", e no cold start a URI chega sempre durante a restauração
   da sessão — que é exatamente o caso do link, porque foi ele que abriu o app.

Cada uma exigiu instrumentar o app e ler `logcat` para ser vista. Nenhuma
apareceria em teste de widget: as três primeiras são de plataforma, e a quarta só
se manifesta no cold start com sessão persistida.

## Bug encontrado por `CC-5-11`

**Título:** `TextEditingController` usado depois de descartado no diálogo de
entrada por convite

**Passos para reproduzir:**
1. Na tela inicial, tocar em "entrar por convite".
2. Digitar qualquer código e confirmar.

**Esperado:** o diálogo fecha e o resultado (sucesso ou motivo da recusa)
aparece.

**Obtido:** exceção do framework — *"A TextEditingController was used after being
disposed"* — e a árvore de widgets em estado inconsistente
(`_dependents.isEmpty': is not true` no `Overlay`).

**Severidade: alta.** `showDialog` retorna quando a rota **começa** a sair, não
quando ela deixa a árvore: o `TextField` continua sendo construído durante a
animação de fechamento. Eu descartava o controller na linha seguinte ao `await`,
ou seja, sempre alguns frames antes da hora.

Em debug isso é uma asserção vermelha na tela. Em release é comportamento
indefinido — e no caminho de entrada por convite, que é o funil de todo
participante novo.

**Corrigido** extraindo o diálogo para um widget próprio (`_DialogoDeConvite`),
que **é dono** do controller e o descarta no seu próprio `dispose` — só o
`State` dele sabe quando o campo saiu de fato da árvore.

Vale notar que este defeito passou por device sem aparecer: eu abri e usei esse
diálogo em verificação manual antes, e a exceção não interrompeu o fluxo o
suficiente para eu notar. Foi o teste de widget que o tornou visível.
