## MG-7 — App falando com o Firebase pelos mesmos repositórios

**PRD:** `docs/prd/migracao-firebase.md`

**Como** participante, **quero** que o app continue igual, **para** a migração não
me custar nada — nem palpite, nem hábito.

**Prioridade:** Must — se o participante notar a troca, algo quebrou.

**Critérios de aceite:**

- Dado que o backend mudou, então **nenhuma tela muda**: a troca acontece atrás dos
  repositórios (`*_repository.dart`), não na apresentação.
- Dado que estou sem conexão, então o rascunho do palpite continua salvo no
  aparelho e sobe quando a rede volta.
- Dado que abro o app sem rede, então continuo vendo a lista de bolões e o ranking
  guardados, rotulados com a idade do dado.
- Dado que a contagem de prazo aparece, então ela segue o horário do servidor —
  mesmo com o relógio do aparelho alterado.
- Dado um bolão com 60 participantes, quando abro o ranking, então é uma leitura.
- Dado que o app aponta para o emulador em desenvolvimento, então isso é
  configuração, não código comentado.

**Tasks:**

_Backend_
- [x] `BE-1` Callable de criação: valida, traz os jogos da fonte e põe o criador dentro como admin (depende de MG-2 BE-1)
- [x] `BE-2` Callables de convite: gerar, invalidar e entrar, com cada recusa distinguível (depende de BE-1)
- [x] `BE-3` Callables de papel e saída, com o criador permanente preservado (depende de BE-1)
- [x] `BE-4` Índice `usuarios/{uid}/boloes` e contagem de ativos mantidos por gatilho (depende de BE-1)
- [x] `BE-5` Callable de alteração de regras e gatilho que trava no primeiro palpite (depende de BE-1)
- [x] `BE-6` Callables de ciclo de vida: encerrar, encerrar antes, arquivar, recriar (depende de BE-1)
- [x] `BE-7` Callable de correção manual com trilha de auditoria, e aviso a quem foi afetado (depende de BE-1)
- [x] `BE-8` Callables de extrato e ranking da rodada, compondo em memória sem `join` (depende de BE-1)

_Mobile_
- [x] `FE-1` Camada de dados falando com Firestore por trás dos repositórios existentes (depende de MG-2 BE-1)
- [x] `FE-2` Chamadas de regra por Function (callable) onde a rule não basta (depende de MG-5 BE-1)
- [x] `FE-3` Apontar para emulador por `--dart-define`, como já é feito com `API_URL` (depende de MG-1 BE-1)
- [x] `FE-4` Rascunho offline e fila de envio preservados sobre o Firestore (depende de FE-1)
- [x] `FE-5` Cache de leitura (lista, ranking, regras) preservado, com a idade visível (depende de FE-1)
- [ ] `FE-6` Remover o `ApiClient` HTTP quando nada mais o usar (depende de FE-1, FE-2)

_QA_
- [x] `QA-1` Escrever o plano de casos de teste a partir desta story
- [x] `QA-2` Executar em device os fluxos críticos: convite → palpite → ranking, offline e com relógio alterado

**Riscos/Dependências:**

- Depende de `MG-2`, `MG-3`, `MG-5`.
- **O cache offline muda de dono.** Hoje é o `CacheLocal` que grava envelope com
  idade; o Firestore tem persistência própria. Misturar os dois é o caminho para
  dado velho sem rótulo — decidir **um** e registrar por quê.
- Os 235 testes de widget não devem precisar mudar: eles falam com repositórios
  falsos. Se algum precisar, é sinal de que a troca vazou para a apresentação.

- **07/08 — as 4 tasks de backend feitas; a frente do app segue aberta.** Plano em
  `docs/testes/MG-7-plano.md`, execução parcial em `docs/testes/MG-7-execucao.md`.
  26 casos novos, suíte do Firebase em **158**.
- **As tasks `BE-*` nasceram aqui porque a lacuna apareceu aqui.** `MG-2` modelou
  as coleções e escreveu as rules que **protegem** cada uma; ninguém tinha escrito
  quem **escreve** nelas. Sem isso não havia como criar bolão, convidar, entrar,
  sair ou trocar de papel — e `FE-1`/`FE-2` não teriam com o que falar.
- **Callable para participação, escrita direta para palpite.** As operações de
  participação escrevem em mais de um lugar de uma vez e dependem de dado que o
  cliente não pode ver (hash do convite, jogos da fonte). O palpite não: é o
  caminho quente, e Function no meio somaria cold start ao momento em que todo
  mundo palpita junto.
- **O índice da tela inicial virou gatilho.** Cinco portas mexem no mesmo estado;
  a primeira que esquecesse de atualizar o índice sumiria com o bolão da tela de
  alguém, sem erro nenhum. A contagem de ativos é recontada, não incrementada —
  `increment` erra em silêncio quando o gatilho roda duas vezes.
- **`MG-7-21` mudou de alvo ao ser escrito:** a guarda "o bolão não pode ficar sem
  admin" **nunca dispara**, porque o criador é admin permanente e sempre sobra um.
  O caso ficou registrando isso em vez de fingir um cenário impossível.

- **07/08 (segunda leva) — regras, ciclo de vida, correção, extrato e ranking da
  rodada.** Suíte do Firebase em **188** casos. Fecham junto as tasks `BE-4`,
  `BE-5` e `BE-6` de `MG-5`.
- **Um furo do `CC-3` apareceu ao ligar o app:** a rule deixava o admin escrever
  direto no bolão, e rule **não consegue olhar se já existe palpite** — ou seja,
  dava para trocar a tabela de pontos depois do primeiro palpite. A escrita direta
  no documento do bolão passou a ser negada por inteiro, e o caso `MG-2-07`
  inverteu: hoje afirma que nem o admin altera.
- **O encerramento antecipado não empurra mais o horário dos jogos.** No NestJS
  ele fazia isso para fechar o prazo; aqui o jogo é global e isso fecharia o prazo
  dos outros bolões junto. A rule do palpite já recusa bolão encerrado.
- **A consolidação do aviso virou fusão.** O coletor em memória do NestJS não
  existe em Functions (cada jogo corrigido é uma invocação separada), então aviso
  não lido e recente do mesmo bolão recebe o jogo novo em vez de virar outro.
- **A correção manual não devolve mais quantos avisos saíram** — quem apura é
  gatilho. Ela devolve quantos palpites serão reapurados, e o texto da tela
  precisa acompanhar isso em `FE-2`.

- **07/08 (app) — `FE-1` a `FE-5` e `QA-2` feitas; `FE-6` fica para depois de
  `MG-8`.** 244 testes de widget passando **sem mudança de asserção** — o que
  mudou neles foi uma palavra por arquivo, o falso estendendo a implementação HTTP
  em vez do contrato. Mais 4 casos em device contra os emuladores e a fonte
  oficial.
- **Um dono só para o offline:** persistência do Firestore **desligada**. Ela
  responde dado antigo sem dizer que é antigo; o `CacheLocal` grava a idade e a
  mostra. Ter os dois seria ter duas respostas para a mesma pergunta, uma sem
  rótulo.
- **O relógio virou desvio, não instante:** a hora do servidor vem de uma callable
  uma vez por sessão, descontando meia viagem de rede. Sem medida, a contagem cai
  no aparelho e a recusa continua sendo da rule.
- **Quatro defeitos que só o device mostrou:** cleartext bloqueado pelo Android
  (liberado só em debug), formato da chave de API validado pelo SDK, nome de
  cadastro virando o trecho antes do arroba no bolão, e a tela do extrato
  quebrando em vez de mostrar erro em falha inesperada.
- **`FE-6` fica de propósito para depois de `MG-8`:** enquanto a paridade não é
  verificada, poder voltar por `--dart-define=BACKEND=http` é o que separa
  "trocamos de backend" de "ficamos sem backend".
