Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
0da9c2c
feat(prova): schema QuestaoNaContainer (subdoc questao+numero)
FernandoAlmeidaPinto Aug 2, 2026
458a931
feat(prova): campo paralelo questoesNovo em Prova
FernandoAlmeidaPinto Aug 2, 2026
ac2fbac
feat(simulado): campo paralelo questoesNovo em Simulado
FernandoAlmeidaPinto Aug 2, 2026
53424e9
feat(migration): script 0001 migrate.sh popula questoesNovo (bash/jq)
FernandoAlmeidaPinto Aug 2, 2026
7a8d0c8
feat(migration): script 0001 validate.sh (contagens + idiomáticas)
FernandoAlmeidaPinto Aug 2, 2026
5971d29
docs(migration): README (backup/export/run/reimport) + rollback do 0001
FernandoAlmeidaPinto Aug 2, 2026
a96db8f
feat(prova): helper single-write question-container (questoesNovo)
FernandoAlmeidaPinto Aug 3, 2026
5014316
feat(prova): repository lê/escreve questoesNovo (getById populate + a…
FernandoAlmeidaPinto Aug 3, 2026
dd33c16
refactor(prova): helper aceita ObjectId|string (remove cast no remove…
FernandoAlmeidaPinto Aug 3, 2026
7d67fdb
feat(simulado): repository popula questoesNovo.questao (answer substi…
FernandoAlmeidaPinto Aug 3, 2026
9b28912
feat(simulado): add/removeQuestionSimulados single-write em questoesNovo
FernandoAlmeidaPinto Aug 3, 2026
dcb01dd
refactor(simulado): questoesNovo não-opcional (alinha com Prova, remo…
FernandoAlmeidaPinto Aug 3, 2026
8bee031
feat(prova): approved/refuseQuestion leem questoesNovo + contagem por…
FernandoAlmeidaPinto Aug 3, 2026
9459358
test(prova): cobre totalQuestaoValidadas + refuseQuestion (questoesNovo)
FernandoAlmeidaPinto Aug 3, 2026
1f409f7
feat(simulado): processAnswer lê questoesNovo
FernandoAlmeidaPinto Aug 3, 2026
71a49ec
fix(historico): totalQuestionsTest lê questoesNovo (dashboard pós-cut…
FernandoAlmeidaPinto Aug 3, 2026
5199af7
test(prova): fixa populate de questoesNovo.questao no getById
FernandoAlmeidaPinto Aug 3, 2026
4ed5d0d
feat(prova): helper updateNumeroNoContainer (numero-sync cutover)
FernandoAlmeidaPinto Aug 3, 2026
5e9e2ef
refactor(prova): getProvaWithQuestion/getAllPopulated sem populate de…
FernandoAlmeidaPinto Aug 3, 2026
16d1d57
feat(prova): Enem2017PlusFactory getMissingNumbers/verifyNumberProva …
FernandoAlmeidaPinto Aug 3, 2026
bf306e6
feat(prova): Enem2010_2016Factory getMissingNumbers/verifyNumberProva…
FernandoAlmeidaPinto Aug 3, 2026
b4e39af
feat(prova): CustomProvaFactory getMissingNumbers/verifyNumberProva l…
FernandoAlmeidaPinto Aug 3, 2026
b54b43a
feat(prova): executeSync grava questoesNovo single-write (selectQuest…
FernandoAlmeidaPinto Aug 3, 2026
b8b80c9
feat(prova): updateQuestion reconcilia numero no subdoc (cutover, 3 f…
FernandoAlmeidaPinto Aug 3, 2026
aa874d3
test(prova): numero-sync integração no Enem2017PlusFactory.updateQues…
FernandoAlmeidaPinto Aug 3, 2026
4a0eb61
feat(simulado): GetSimuladoById monta de questoesNovo + getById sem p…
FernandoAlmeidaPinto Aug 3, 2026
c6af73b
refactor(prova): getById sem populate de questoes (cutover, so questo…
FernandoAlmeidaPinto Aug 3, 2026
bcf6187
feat(historico): getById popula simulado.questoesNovo.questao (cutover)
FernandoAlmeidaPinto Aug 3, 2026
9b5220c
feat(historico): getById remapeia questoesNovo.questao para simulado.…
FernandoAlmeidaPinto Aug 3, 2026
bee6ea1
refactor(prova): remove executeSync + endpoints /prova/sync (obsoleto…
FernandoAlmeidaPinto Aug 3, 2026
4068542
refactor: remove ultimos usos do array antigo questoes (populate/fixt…
FernandoAlmeidaPinto Aug 3, 2026
1c49595
refactor: rename questoesNovo -> questoes (subdoc vira o array oficial)
FernandoAlmeidaPinto Aug 3, 2026
5e37f43
test: remove asserts tautologicos de populate pos-rename questoes
FernandoAlmeidaPinto Aug 3, 2026
d94e2dc
feat(questao): canInsertQuestion via reverse-lookup em Prova.questoes…
FernandoAlmeidaPinto Aug 3, 2026
9a47331
feat(questao): delete via reverse-lookup das provas contendo a questão
FernandoAlmeidaPinto Aug 3, 2026
cc94b93
feat(questao): updateStatus aprova/rejeita por reverse-lookup das provas
FernandoAlmeidaPinto Aug 3, 2026
c5a4f09
feat(questao): getAll retorna provasContendo (reverse-lookup) + sort …
FernandoAlmeidaPinto Aug 3, 2026
d292ea1
fix(questao): allowlist de sort inclui updatedAt (default do banco de…
FernandoAlmeidaPinto Aug 3, 2026
13d2287
fix(questao): sort do banco de questões só por updatedAt (numero sera…
FernandoAlmeidaPinto Aug 3, 2026
23d8883
feat(questao): updateClassificacao via reverse-lookup + remove popula…
FernandoAlmeidaPinto Aug 3, 2026
eb2cfc0
feat(prova): factory updateQuestion deriva provaToLeaveId via reverse…
FernandoAlmeidaPinto Aug 3, 2026
5c77f41
refactor(prova): numero do subdoc vem do input (addQuestaoToContainer…
FernandoAlmeidaPinto Aug 3, 2026
deb1d14
feat(simulado): ano via reverse-lookup + GetSimuladoById sem prova (c…
FernandoAlmeidaPinto Aug 3, 2026
97854ee
refactor(questao): remove campos legado prova/numero do schema (+ gat…
FernandoAlmeidaPinto Aug 3, 2026
9410da2
chore(migration): script 0002 cleanup (unset antigo + rename questoes…
FernandoAlmeidaPinto Aug 3, 2026
a60e0ac
chore(migration): endurece script 0002 (pre-check simulados + validat…
FernandoAlmeidaPinto Aug 3, 2026
090113d
Merge branch 'develop' into feature/etapa9-card07a-backend-cleanup-qu…
FernandoAlmeidaPinto Aug 9, 2026
cb97972
feat(prova): helper syncNumeroNaProvaESimulados (numero-sync escopado)
FernandoAlmeidaPinto Aug 9, 2026
5163b01
feat(prova): ProvaService.syncNumero delegando ao helper
FernandoAlmeidaPinto Aug 9, 2026
b48d6d1
refactor(prova): factories usam syncNumeroNaProvaESimulados (dedup)
FernandoAlmeidaPinto Aug 9, 2026
fe81977
fix(questao): numero-only sincroniza prova+simulados; remove provaCha…
FernandoAlmeidaPinto Aug 9, 2026
e21eebd
feat(questao): getById retorna provasContendo (reverse-lookup)
FernandoAlmeidaPinto Aug 9, 2026
800af68
refactor(prova): completa dedup numero-sync (enem_2010_2016) + fixes …
FernandoAlmeidaPinto Aug 9, 2026
e99fa31
feat(migration-0002): questao.prova -> provaBase (mantem numero) no c…
FernandoAlmeidaPinto Aug 9, 2026
b88586e
fix(migration-0002): numero sai da questao (so provaBase fica)
FernandoAlmeidaPinto Aug 9, 2026
c40be3e
feat(questao): expõe provaBase (ancora da prova de origem) no schema …
FernandoAlmeidaPinto Aug 9, 2026
dcc470d
fix(prova): numero-sync dentro da transacao (session-aware) [review C…
FernandoAlmeidaPinto Aug 9, 2026
e0e00b4
fix(historico): getById preserva numero do relacionamento no achatame…
FernandoAlmeidaPinto Aug 9, 2026
4ea396f
fix(simulado): guard em processAnswer para simulado sem questoes [rev…
FernandoAlmeidaPinto Aug 9, 2026
1ae32dc
perf(prova): índice reverso questoes.questao no schema [review Alto]
FernandoAlmeidaPinto Aug 9, 2026
d13dfbc
fix(questao): guard no updateClassificacao numero-only se prova nao c…
FernandoAlmeidaPinto Aug 9, 2026
974037a
refactor(questao): tipa getById com ProvaContendo em vez de any [review]
FernandoAlmeidaPinto Aug 10, 2026
03c8c57
fix(questao): remove provider SimuladoService duplicado no module [re…
FernandoAlmeidaPinto Aug 10, 2026
9929a8d
fix(prova): removeQuestion nao persiste quando a questao nao estava n…
FernandoAlmeidaPinto Aug 10, 2026
297a8fe
refactor(prova): extrai resolveQuestaoId e dedup normalizacao de id […
FernandoAlmeidaPinto Aug 10, 2026
70e799f
fix(migration-0001): aceita Questao.prova como objeto oid ou string
FernandoAlmeidaPinto Aug 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions scripts/migrations/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Dumps exportados/gerados pelas migrations (dados reais — nunca commitar).
# Só os scripts (*.sh) e docs (*.md) são versionados.
*.json
*.log
backup-*/
75 changes: 75 additions & 0 deletions scripts/migrations/0001-questoes-em-multiplas-provas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Migração 0001 — Questão Multi-Prova (Fase 2)

Popula `Prova.questoesNovo` e `Simulado.questoesNovo` a partir do modelo antigo
(`Questao.prova` + `Questao.numero`). Arrays antigos (`questoes`) permanecem
intactos — os dois coexistem após esta fase. Nenhum consumer lê `questoesNovo`
ainda (Fase 3+). Reversível (`$unset questoesNovo`).

Os scripts NÃO conectam ao Mongo: operam sobre JSON exportado localmente.

## Pré-requisitos

`jq` (>=1.6), `openssl`, `bash`, e o Mongo Database Tools (`mongodump`,
`mongoexport`, `mongoimport`). `$MONGODB` = URI do banco alvo.

## Procedimento (homol primeiro, depois prod)

### 1. Backup obrigatório (restore safety)

```bash
DATE=$(date +%Y%m%d-%H%M%S)
mongodump --uri="$MONGODB" --collection=questoes --out="./backup-$DATE/"
mongodump --uri="$MONGODB" --collection=provas --out="./backup-$DATE/"
mongodump --uri="$MONGODB" --collection=simulados --out="./backup-$DATE/"
du -sh "./backup-$DATE/" # confirmar tamanho não-zero
# subir ./backup-$DATE/ para R2/S3 com retenção >=90 dias; documentar o caminho no PR
```

Backup apenas destas 3 collections (NÃO `historico`).

### 2. Exportar as collections (JSON array, Extended JSON relaxado = default)

```bash
mongoexport --uri="$MONGODB" --collection=questoes --jsonArray --out=questoes.json
mongoexport --uri="$MONGODB" --collection=provas --jsonArray --out=provas.json
mongoexport --uri="$MONGODB" --collection=simulados --jsonArray --out=simulados.json
```

> Caso o modelo precise de outras collections no futuro, exportá-las aqui e
> ajustar o `migrate.sh`. Hoje só estas 3.

### 3. Rodar a migração

```bash
bash migrate.sh . # gera provas.out.json e simulados.out.json
bash validate.sh . # deve terminar com "✅ Validação OK" (exit 0)
```

Se `validate.sh` sair com código 1, NÃO reimportar — investigar os logs.

### 4. Smoke em homol (antes de reimportar em prod)

Reimporte (passo 5) em homol e confira no frontend — tudo ainda lê do array
antigo, então deve estar idêntico:
- DashProva abre; abrir 1 prova; abrir 1 simulado.
- Responder 1 simulado → histórico funciona.

### 5. Reimportar (drop + import — backup já salvo)

```bash
mongosh "$MONGODB" --eval 'db.provas.drop(); db.simulados.drop();'
mongoimport --uri="$MONGODB" --collection=provas --jsonArray --file=provas.out.json
mongoimport --uri="$MONGODB" --collection=simulados --jsonArray --file=simulados.out.json
```

`questoes` **não muda** nesta fase — não reimportar.

### 6. Produção

Repetir 1→5 em janela de manutenção (~10min), sem deploys/writes concorrentes.
Guardar logs (`bash migrate.sh . > migrate-prod-$DATE.log 2>&1`). Backup
obrigatório antes. Se algo falhar → `rollback.md`.

## Idempotência

`migrate.sh` sobrescreve `questoesNovo`; rodar 2x = mesmo resultado.
101 changes: 101 additions & 0 deletions scripts/migrations/0001-questoes-em-multiplas-provas/migrate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
#
# Migração 0001 — Questão Multi-Prova (Fase 2).
# Popula Prova.questoesNovo e Simulado.questoesNovo a partir do modelo antigo
# (Questao.prova + Questao.numero). Arrays antigos ("questoes") intactos.
#
# Uso: bash migrate.sh [dir] (dir default = diretório atual)
# Entrada (mongoexport --jsonArray, Extended JSON):
# <dir>/questoes.json <dir>/provas.json <dir>/simulados.json
# Saída:
# <dir>/provas.out.json <dir>/simulados.out.json
#
# Idempotente: sobrescreve questoesNovo. Não conecta ao Mongo.
set -euo pipefail

DIR="${1:-.}"
QUESTOES="$DIR/questoes.json"
PROVAS="$DIR/provas.json"
SIMULADOS="$DIR/simulados.json"

for f in "$QUESTOES" "$PROVAS" "$SIMULADOS"; do
[ -f "$f" ] || { echo "❌ Arquivo não encontrado: $f" >&2; exit 1; }
done

# Gera N ObjectIds (24-hex) como array JSON de strings.
gen_ids() {
n="$1"
if [ "$n" -eq 0 ]; then echo "[]"; return; fi
i=0
while [ "$i" -lt "$n" ]; do
openssl rand -hex 12
i=$((i + 1))
done | jq -R . | jq -s .
}

# ============ PROVA ============
nq=$(jq '[.[] | select(.prova != null and (.numero != null))] | length' "$QUESTOES")
echo "[STEP 1] Questoes com prova+numero: $nq" >&2
prova_ids=$(gen_ids "$nq")

jq -n \
--slurpfile questoes "$QUESTOES" \
--slurpfile provas "$PROVAS" \
--argjson idpool "$prova_ids" '
($questoes[0]) as $qs
| ($provas[0]) as $ps
| ( [ $qs[]
| select(.prova != null and (.numero != null))
| { provaId: (.prova | if type == "object" then .["$oid"] else . end), questao: {"$oid": ._id["$oid"]}, numero: .numero } ]
| to_entries
| map(.value + { _id: {"$oid": $idpool[.key]} }) ) as $entries
| ( $entries
| group_by(.provaId)
| map({ key: .[0].provaId, value: (map({_id, questao, numero})) })
| from_entries ) as $byProva
| $ps | map(.questoesNovo = ($byProva[._id["$oid"]] // []))
' > "$DIR/provas.out.json"

total_prova=$(jq '[.[] | .questoesNovo | length] | add // 0' "$DIR/provas.out.json")
echo "[STEP 2] Entries em provas.out.json: $total_prova" >&2

orfas=$(jq -n --slurpfile q "$QUESTOES" --slurpfile p "$PROVAS" '
($p[0] | map(._id["$oid"])) as $ids
| [ $q[0][]
| select(.prova != null and (.numero != null))
| select(((.prova | if type == "object" then .["$oid"] else . end)) as $pid | ($ids | index($pid)) | not) ] | length')
echo "[STEP 2b] Questoes órfãs (prova fora do dump): $orfas" >&2

# ============ SIMULADO ============
ns=$(jq -n --slurpfile q "$QUESTOES" --slurpfile s "$SIMULADOS" '
($q[0] | map(select(.numero != null)) | INDEX(._id["$oid"])) as $qidx
| [ $s[0][] | (.questoes // [])[] | .["$oid"] | select($qidx[.] != null) ] | length')
echo "[STEP 3] Refs de simulado com numero: $ns" >&2
sim_ids=$(gen_ids "$ns")

jq -n \
--slurpfile questoes "$QUESTOES" \
--slurpfile simulados "$SIMULADOS" \
--argjson idpool "$sim_ids" '
($questoes[0] | map(select(.numero != null)) | INDEX(._id["$oid"])) as $qidx
| ($simulados[0]) as $ss
| ( [ $ss | to_entries[]
| .key as $si | .value as $sim
| ($sim.questoes // [])[]
| .["$oid"] as $ref
| select($qidx[$ref] != null)
| { si: $si, questao: {"$oid": $ref}, numero: $qidx[$ref].numero } ]
| to_entries
| map(.value + { _id: {"$oid": $idpool[.key]} }) ) as $entries
| ( $entries
| group_by(.si)
| map({ key: (.[0].si | tostring), value: (map({_id, questao, numero})) })
| from_entries ) as $bySim
| [ $ss | to_entries[]
| .value + { questoesNovo: ($bySim[(.key | tostring)] // []) } ]
' > "$DIR/simulados.out.json"

total_sim=$(jq '[.[] | .questoesNovo | length] | add // 0' "$DIR/simulados.out.json")
echo "[STEP 3b] Entries em simulados.out.json: $total_sim" >&2

echo "[DONE] provas.out.json ($total_prova entries), simulados.out.json ($total_sim entries)." >&2
27 changes: 27 additions & 0 deletions scripts/migrations/0001-questoes-em-multiplas-provas/rollback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Rollback — Migração 0001 (Fase 2)

O sistema continua lendo do array antigo (`questoes`), que permanece intacto.
Rollback é trivial nesta fase.

## Se algo deu errado durante/logo após a migração

```bash
mongosh "$MONGODB" --eval '
db.provas.updateMany({}, { $unset: { questoesNovo: "" } });
db.simulados.updateMany({}, { $unset: { questoesNovo: "" } });
'
```

Reverter o PR do Card 01 (schemas) só se necessário.

## Se a migração corrompeu dado antigo (não deveria — só adiciona campo)

Restaurar do backup:

```bash
mongorestore --uri="$MONGODB" --drop --collection=provas ./backup-<DATE>/<db>/provas.bson
mongorestore --uri="$MONGODB" --drop --collection=simulados ./backup-<DATE>/<db>/simulados.bson
mongorestore --uri="$MONGODB" --drop --collection=questoes ./backup-<DATE>/<db>/questoes.bson
```

Backup fica arquivado por precaução (retenção >=90 dias).
73 changes: 73 additions & 0 deletions scripts/migrations/0001-questoes-em-multiplas-provas/validate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
#
# Validação da migração 0001. Roda sobre os JSONs (input + output), sem Mongo.
# Uso: bash validate.sh [dir] (default = diretório atual)
# Requer: questoes.json, provas.out.json, simulados.out.json
# Exit 0 = ok, 1 = falha.
set -euo pipefail

DIR="${1:-.}"
QUESTOES="$DIR/questoes.json"
PROVAS_OUT="$DIR/provas.out.json"
SIMULADOS_OUT="$DIR/simulados.out.json"

for f in "$QUESTOES" "$PROVAS_OUT" "$SIMULADOS_OUT"; do
[ -f "$f" ] || { echo "❌ Arquivo não encontrado: $f" >&2; exit 1; }
done

fail=0

# [1] Total estrito: sum(prova.questoesNovo) == count(questoes com prova+numero
# cuja prova EXISTE no dump). Órfãs (prova fora do dump) são descontadas —
# elas têm prova+numero mas corretamente não entram em nenhum questoesNovo.
expected=$(jq -n --slurpfile q "$QUESTOES" --slurpfile p "$PROVAS_OUT" '
($p[0] | map(._id["$oid"])) as $ids
| [ $q[0][] | select(.prova != null and (.numero != null))
| select(((.prova | if type == "object" then .["$oid"] else . end)) as $pid | ($ids | index($pid))) ] | length')
got=$(jq '[.[] | .questoesNovo | length] | add // 0' "$PROVAS_OUT")
if [ "$expected" = "$got" ]; then
echo "✓ [1] Total prova entries = $got (esperado $expected)"
else
echo "✗ [1] Total prova entries = $got, esperado $expected" >&2; fail=1
fi

# [2] Idiomáticas preservadas: nº de grupos (prova,numero) com >=2 igual input vs output
exp_pairs=$(jq -n --slurpfile q "$QUESTOES" '
[ $q[0][] | select(.prova != null and (.numero != null))
| ((.prova | if type == "object" then .["$oid"] else . end) + "|" + (.numero | tostring)) ]
| group_by(.) | map(select(length >= 2)) | length')
got_pairs=$(jq '
[ .[] | (.questoesNovo // [])
| group_by(.numero) | map(select(length >= 2)) | length ] | add // 0' "$PROVAS_OUT")
if [ "$exp_pairs" = "$got_pairs" ]; then
echo "✓ [2] Grupos idiomáticos (>=2 mesmo numero) = $got_pairs"
else
echo "✗ [2] Idiomáticas: esperado $exp_pairs grupos, obtido $got_pairs" >&2; fail=1
fi

# [3] Simulado: questoesNovo.length == count(refs com numero!=null)
sim_bad=$(jq -n --slurpfile q "$QUESTOES" --slurpfile s "$SIMULADOS_OUT" '
($q[0] | map(select(.numero != null)) | INDEX(._id["$oid"])) as $qidx
| [ $s[0][]
| ( [ (.questoes // [])[] | .["$oid"] | select($qidx[.] != null) ] | length ) as $exp
| select((.questoesNovo | length) != $exp)
| ._id["$oid"] ]')
sim_bad_count=$(echo "$sim_bad" | jq 'length')
if [ "$sim_bad_count" = "0" ]; then
echo "✓ [3] Todos os simulados com questoesNovo.length correto"
else
echo "✗ [3] $sim_bad_count simulados com contagem errada: $(echo "$sim_bad" | jq -c '.[0:5]')" >&2; fail=1
fi

# [4] Órfãs (warn, não falha)
orfas=$(jq -n --slurpfile q "$QUESTOES" --slurpfile p "$PROVAS_OUT" '
($p[0] | map(._id["$oid"])) as $ids
| [ $q[0][] | select(.prova != null and (.numero != null))
| select(((.prova | if type == "object" then .["$oid"] else . end)) as $pid | ($ids | index($pid)) | not) ] | length')
echo "ℹ [4] Questoes órfãs (prova fora do dump): $orfas (warn)"

if [ "$fail" -eq 0 ]; then
echo "✅ Validação OK"; exit 0
else
echo "❌ Validação falhou"; exit 1
fi
96 changes: 96 additions & 0 deletions scripts/migrations/0002-cleanup-questoes-antigas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Migração 0002 — Cleanup Questões Antigas (Etapa 9, final)

Cleanup final da Etapa 9. Dropa o array antigo de refs (`questoes`), renomeia o
subdoc `questoesNovo` → `questoes` em `Prova`/`Simulado`, e em `Questao` renomeia
`prova` → `provaBase` (ponteiro pra prova de origem) e **remove** `numero` — o
número passa a viver só no relacionamento (`Prova.questoes[].numero`). No dash,
casa-se `provaBase` com `provasContendo` pra obter prova+número. Questão sem prova
fica sem `provaBase`.

Como a **0001**, os scripts de transformação **NÃO conectam ao Mongo**: operam
sobre JSON exportado localmente e geram `*.out.json` para reimportar.

A **derrubada/criação de índices** é a única parte que precisa do banco (não sai
no `mongoexport` nem entra no `mongoimport`) e por isso fica num script final
separado, `indices.sh`, rodado por último.

Os scripts:

| Script | Conecta ao Mongo? | O que faz |
|--------|:-:|-----------|
| `cleanup.sh` | não | rename `questoesNovo`→`questoes`; `prova`→`provaBase` + remove `numero` → `*.out.json` |
| `validate.sh` | não | valida os `*.out.json` |
| `indices.sh` | **sim** | dropa índices legados e cria os índices reversos finais |

## Pré-requisitos

`jq` (>=1.6), `bash`, o Mongo Database Tools (`mongodump`, `mongoexport`,
`mongoimport`) e `mongosh` (só para `indices.sh`). `$MONGODB` = URI do banco alvo.

## Procedimento (homol primeiro, depois prod)

Rodar **somente** após a 0001 ter sido aplicada (reimportada) e o deploy do
código anterior (Card 05) estar validado.

### 1. Backup obrigatório (restore safety)

```bash
mongodump --uri="$MONGODB" \
--collection=provas \
--collection=simulados \
--collection=questoes \
--out=./backup-0002
du -sh ./backup-0002 # confirmar tamanho não-zero
# subir ./backup-0002 para R2/S3 com retenção >=90 dias; anotar o caminho no PR
```

### 2. Exportar as collections (JSON array, Extended JSON relaxado = default)

```bash
mongoexport --uri="$MONGODB" --collection=provas --jsonArray --out=provas.json
mongoexport --uri="$MONGODB" --collection=simulados --jsonArray --out=simulados.json
mongoexport --uri="$MONGODB" --collection=questoes --jsonArray --out=questoes.json
```

### 3. Rodar o cleanup

```bash
bash cleanup.sh . # gera provas.out.json, simulados.out.json, questoes.out.json
bash validate.sh . # deve terminar com "✅ validate 0002 OK" (exit 0)
```

O pré-check do `cleanup.sh` **aborta** se achar prova/simulado com `questoesNovo`
vazio mas `questoes` antigo populado (sinal de que a 0001 não rodou, ou de que o
dump já foi migrado). Se sair com código 1, NÃO reimportar — investigar os logs.

### 4. Reimportar (drop + import — backup já salvo)

```bash
mongosh "$MONGODB" --eval 'db.provas.drop(); db.simulados.drop(); db.questoes.drop();'
mongoimport --uri="$MONGODB" --collection=provas --jsonArray --file=provas.out.json
mongoimport --uri="$MONGODB" --collection=simulados --jsonArray --file=simulados.out.json
mongoimport --uri="$MONGODB" --collection=questoes --jsonArray --file=questoes.out.json
```

### 5. Índices (conecta ao banco) — logo após reimportar

```bash
MONGODB='...' bash indices.sh # deve terminar com "indices 0002 OK"
```

### 6. Deploy do código do Card 07a — imediatamente

O código do Card 07a lê `questoes` no shape novo `[{questao, numero}]`. Fazer o
deploy logo após reimportar + `indices.sh`, para não deixar o banco no shape novo
com código antigo em execução.

### 7. Produção

Repetir 1→6 em janela de manutenção, sem deploys/writes concorrentes. Backup
obrigatório antes. Se algo falhar → `rollback.md`.

## Idempotência

`cleanup.sh` reescreve os `*.out.json` sem tocar nos `.json` de entrada — rodar
2x sobre o mesmo dump dá o mesmo resultado. `indices.sh` também é idempotente
(`dropIndex` ignora índice inexistente; `createIndex` é no-op se já existe).
Loading
Loading