feat(prova): Provas Custom backend — schema + CustomProvaFactory + dispatcher (Cards 01+02+03) - #160
Merged
FernandoAlmeidaPinto merged 4 commits intoJul 26, 2026
Conversation
… + 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>
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
deleted the
feature/provas-custom-card-03-dispatcher-sync
branch
July 26, 2026 18:27
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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) viaCustomProvaFactory. Também adicionacriadorId/cursinhoIdem Prova/Simulado, preparando a etapa 6.Card 01 — Schema + DTO + backfill (
f3f81b2)Prova.criadorId(required) +Prova.cursinhoId(default null), setados no construtorSimulado.criadorId?+Simulado.cursinhoId?(opcionais)CreateProvaDTOInput:nome?/nomeSimulado?novos,filenameagora opcional,criadorIdcom@IsString()scripts/backfill-criador-id.ts(+ npmbackfill:criador-id) —updateManyMongo p/ legadas ='system'Card 02 —
CustomProvaFactory(6612706)implements IProvaFactory: 6 métodos. 1 simulado/prova, nome único por criador, sem frentes/EnemServicecreateQuestion/updateQuestion(padrão ENEM)getMissingNumbers:[]quando qtdnull; faixa1..Ncaso contrárioCard 03 — Dispatcher + regra
bloqueado+ sync (35fe21d)getFactory(categoria, ano)roteia p/CustomProvaFactoryquandocategoria.custom; ENEM inalteradoatingiuQuantidade(categoria livre /nullsempre "atinge") aplicado emaddQuestionSimulados,approvedQuestion,refuseQuestione noexecuteSyncexecuteSync.selectQuestionsForSimulado: branch custom retorna todas as questões da prova (sem string matching no nome livre)Correções aplicadas vs. spec dos cards
Provaé(item, categoria)— 2 args (o card do 02 escreveunew Prova(item, exame, categoria)).getFactoryrecebe(categoria, ano), não(prova)— emcreate()a prova ainda não existe (é criada pela própria factory); a categoria carregacustom+exame, servindo todos os callers.verifyNumberProvaé chamado pelo service, não dentro decreateQuestion(espelha o ENEM).getByFilterjá é herdado daBaseRepository;CategoriaRepositorydispensado noCustomProvaFactory(categoria já injetada).updateManyMongo, não SQLUPDATE(o card citava SQL).criadorIdno DTO precisa de@IsString()— oValidationPipeusawhitelist: truee removeria campo sem decorator.Test Plan
npm run buildpassanpx jest— 60/60 (14 factory + 4 schema + 4 dispatcher/service + 3 helper + demais)npm run backfill:criador-idem janela de manutenção ANTES do deploy (schema novo temcriadorIdrequired)executeSynccom 1 ENEM + 1 custom e conferir que nenhuma perde questõesCloses #157
Closes #158
Closes #159
🤖 Generated with Claude Code