Skip to content

feat(prova): Provas Custom backend — schema + CustomProvaFactory + dispatcher (Cards 01+02+03) - #160

Merged
FernandoAlmeidaPinto merged 4 commits into
developfrom
feature/provas-custom-card-03-dispatcher-sync
Jul 26, 2026
Merged

feat(prova): Provas Custom backend — schema + CustomProvaFactory + dispatcher (Cards 01+02+03)#160
FernandoAlmeidaPinto merged 4 commits into
developfrom
feature/provas-custom-card-03-dispatcher-sync

Conversation

@FernandoAlmeidaPinto

Copy link
Copy Markdown
Member

Resumo

Backend do ms-simulado para a Etapa 4 (Provas Custom). PR encadeado que entrega os Cards 01, 02 e 03 juntos (deploy unit único do ms-simulado, conforme o README da etapa).

Habilita provas personalizadas: admin escolhe uma categoria custom: true (criada no CRUD da etapa 3), define nome livre + PDF opcional, e o backend gera 1 simulado (vs. 5/3 do ENEM) via CustomProvaFactory. Também adiciona criadorId/cursinhoId em Prova/Simulado, preparando a etapa 6.

Card 01 — Schema + DTO + backfill (f3f81b2)

  • Prova.criadorId (required) + Prova.cursinhoId (default null), setados no construtor
  • Simulado.criadorId? + Simulado.cursinhoId? (opcionais)
  • CreateProvaDTOInput: nome?/nomeSimulado? novos, filename agora opcional, criadorId com @IsString()
  • scripts/backfill-criador-id.ts (+ npm backfill:criador-id) — updateMany Mongo p/ legadas = 'system'

Card 02 — CustomProvaFactory (6612706)

  • implements IProvaFactory: 6 métodos. 1 simulado/prova, nome único por criador, sem frentes/EnemService
  • transações Mongo em createQuestion/updateQuestion (padrão ENEM)
  • getMissingNumbers: [] quando qtd null; faixa 1..N caso contrário

Card 03 — Dispatcher + regra bloqueado + sync (35fe21d)

  • getFactory(categoria, ano) roteia p/ CustomProvaFactory quando categoria.custom; ENEM inalterado
  • helper atingiuQuantidade (categoria livre / null sempre "atinge") aplicado em addQuestionSimulados, approvedQuestion, refuseQuestion e no executeSync
  • executeSync.selectQuestionsForSimulado: branch custom retorna todas as questões da prova (sem string matching no nome livre)

Correções aplicadas vs. spec dos cards

  • Construtor de Prova é (item, categoria) — 2 args (o card do 02 escreveu new Prova(item, exame, categoria)).
  • getFactory recebe (categoria, ano), não (prova) — em create() a prova ainda não existe (é criada pela própria factory); a categoria carrega custom + exame, servindo todos os callers.
  • verifyNumberProva é chamado pelo service, não dentro de createQuestion (espelha o ENEM).
  • getByFilter já é herdado da BaseRepository; CategoriaRepository dispensado no CustomProvaFactory (categoria já injetada).
  • Backfill é updateMany Mongo, não SQL UPDATE (o card citava SQL).
  • criadorId no DTO precisa de @IsString() — o ValidationPipe usa whitelist: true e removeria campo sem decorator.

Test Plan

  • npm run build passa
  • npx jest60/60 (14 factory + 4 schema + 4 dispatcher/service + 3 helper + demais)
  • Rodar npm run backfill:criador-id em janela de manutenção ANTES do deploy (schema novo tem criadorId required)
  • Smoke em homol: criar prova ENEM (fluxo antigo intacto) + criar prova custom (1 simulado); rodar executeSync com 1 ENEM + 1 custom e conferir que nenhuma perde questões

Observação: 8 erros de formatação prettier pré-existentes em simulado.service.ts (processAnswer) ficaram intactos — não relacionados a este PR.

Closes #157
Closes #158
Closes #159

🤖 Generated with Claude Code

FernandoAlmeidaPinto and others added 3 commits July 26, 2026 12:47
… + backfill

Card 01 da Etapa 4 (Provas Custom). Prep puro pros cards seguintes.

- Prova: criadorId (required) + cursinhoId (default null), setados no constructor
- Simulado: criadorId? + cursinhoId? (opcionais, propagados pela factory no Card 02)
- CreateProvaDTOInput: nome?/nomeSimulado? novos, filename agora opcional,
  criadorId com @IsString() (necessário: ValidationPipe usa whitelist:true, campo
  sem decorator seria removido). criadorId é injetado pelo api-vcnafacul (Card 04).
- scripts/backfill-criador-id.ts: one-off Mongo (updateMany) p/ legadas = 'system',
  rodar em janela de manutenção ANTES do deploy do schema novo.

Correções vs card: backfill é updateMany Mongo (card citava SQL); criadorId no DTO
precisa de @IsString() (card sugeria sem decorator).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ova)

Card 02 da Etapa 4 (Provas Custom). Cria a classe (ainda não conectada ao
dispatcher — isso é o Card 03).

- implements IProvaFactory: createProva/createSimulados/createQuestion/
  updateQuestion/verifyNumberProva/getMissingNumbers
- createProva: nome obrigatório (400), unicidade por criador (409), totalQuestao
  da categoria (aceita null), enemAreas=[]
- createSimulados: exatamente 1 simulado, criadorId/cursinhoId propagados
- createQuestion/updateQuestion: transação Mongo (commit/abort/endSession) no
  mesmo padrão das factories ENEM, porém com 1 simulado e sem frentes/EnemService
- getMissingNumbers: [] quando quantidadeTotalQuestao null; faixa 1..N caso contrário

Correções vs card: construtor de Prova é (item, categoria) — 2 args, não 3;
verifyNumberProva é chamado pelo service (não dentro de createQuestion);
getByFilter herdado da BaseRepository; CategoriaRepository dispensado (categoria
já injetada). 14 testes unitários cobrindo cada método + rollback + edge cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sync custom

Card 03 da Etapa 4 (Provas Custom). Liga as peças dos Cards 01/02.

- dispatcher: getFactory agora recebe (categoria, ano) e roteia p/ CustomProvaFactory
  quando categoria.custom; ENEM inalterado. (assinatura mudou de (exame,ano) pois
  em create() a prova ainda nao existe — precisa da categoria, nao da prova)
- helper atingiuQuantidade: categoria livre (qtd null) sempre 'atinge' — antes o
  null nunca satisfazia length===null e simulados custom nunca desbloqueavam
- aplica o helper em addQuestionSimulados, approvedQuestion, refuseQuestion e no
  recalculo do executeSync
- executeSync.selectQuestionsForSimulado: branch custom retorna todas as questoes
  da prova (sem string matching no nome livre — senao o sync as descartaria)

Testes: roteamento do dispatcher (custom/ENEM), helper, approvedQuestion com qtd
null vs numerica, e branch custom do selectQuestionsForSimulado. 60/60 na suite.

Nota: 8 erros de formatacao prettier pre-existentes em simulado.service.ts
(processAnswer) ficam intactos — fora de escopo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@FernandoAlmeidaPinto FernandoAlmeidaPinto self-assigned this Jul 26, 2026
Bug pré-existente que bloqueava TODA criação de prova (ENEM e custom):
o CategoriaExistValidator (usado pelo @CategoriaExist em CreateProvaDTOInput.categoria)
nunca foi registrado como provider — diferente dos irmãos CategoriaUnique/MateriaExist/
FrenteExist/ExameExist que já estavam no categoria.module.

Com useContainer(..., { fallbackOnErrors: true }) no main.ts, o class-validator não
resolvia o validador pelo container Nest e caía no fallback: new CategoriaExistValidator()
SEM DI -> this.categoriaRepository undefined -> getById() lança -> catch retorna false
-> '\''categoria não existe'\'' para QUALQUER valor (inclusive ids válidos).

Diagnóstico: POST direto em v1/prova com categoria válida dava 400 'categoria não existe',
mas GET v1/categoria/:id (DI normal) retornava 200. Após registrar o validador, o POST
passa da validação e chega na factory.

Descoberto testando o fluxo de Provas Custom (Etapa 4) ponta-a-ponta.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@FernandoAlmeidaPinto
FernandoAlmeidaPinto merged commit 5244377 into develop Jul 26, 2026
3 checks passed
@FernandoAlmeidaPinto
FernandoAlmeidaPinto deleted the feature/provas-custom-card-03-dispatcher-sync branch July 26, 2026 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment