Skip to content

Gestão de Resultados — Coordenador

Última atualização: 2026-04-12


1. Visão Geral

Sistema de inserção, gestão e premiação de resultados de olimpíadas. Permite inserção manual e importação em lote (background), definição de nota de corte, faixas de premiação automática/manual, e sincronização com o Mural Olímpico via snapshots materializados. Acessível a coordenador, escola, administrador e especialista.


2. Arquitetura

text
Frontend                              Backend                                  Banco
────────                              ───────                                  ─────
InserirDialog (wizard) ─┐             gestao-resultados (orquestrador)         resultados_aluno
ResultadosTabela ───────┤             ├── _shared/resultados-queries.ts        inscricoes_olimpiada
NotaCorteDialog ────────┤── useGestao ├── _shared/resultados-mutations.ts      configuracoes_fase_nivel
PremiacaoDialog ────────┤  Resultados ├── _shared/resultados-config.ts         mural_liberacoes
ImportacaoResultados ───┘             ├── _shared/resultados-import.ts         mural_dados_publicados
                                      └── _shared/snapshot-publicados.ts       importacao_resultados_sessoes

3. Schema (Tabelas)

TabelaPropósitoRLS
resultados_alunoPontuações brutas por fase (inscricao_id, pontuacao, situacao)coordenador (SELECT, INSERT, UPDATE, DELETE)
inscricoes_olimpiadaVínculo aluno↔olimpíada, auto-criado na importação se inexistentecoordenador (SELECT, INSERT)
configuracoes_fase_nivelNota de corte e faixas de premiação (ouro/prata/bronze/menção) por fase+nívelcoordenador (SELECT, INSERT, UPDATE, DELETE)
mural_liberacoesFlags de publicação (liberar_notas, liberar_resultados)coordenador (SELECT, INSERT, UPDATE)
mural_dados_publicadosSnapshots materializados para o muralservice_role (write)
importacao_resultados_sessoesSessões de importação background (status, progresso)service_role (write), coordenador (SELECT)

4. Edge Function: gestao-resultados

Orquestrador modular — roteia para handlers em _shared/resultados-*.ts.

Auth: requireRole(["coordenador", "escola", "administrador", "especialista"])
Feature Flag: requireFeatureFlag("resultados")
CORS: getCorsHeaders(req) em todas as respostas incluindo catch.

Queries (resultados-queries.ts)

ActionDescrição
batch_initConsolida olimpíadas + stats + stats_agregados em 1 chamada
listLista olimpíadas com estatísticas de resultados
list_by_olimpiadaResultados por olimpíada/fase (paginado, com filtros)
statsEstatísticas gerais (total, medalhas, classificados)
stats_agregadosEstatísticas por série, turma, ranking de competidores
preview_nota_cortePreview de classificados/não-classificados para uma nota de corte

Mutations (resultados-mutations.ts)

ActionDescriçãoLogging
upsert_batchInserção em lote (matrícula → inscrição, auto-cria se inexistente)resultados.upsert_batch
upsert_singleInserção/edição individualresultados.upsert_single
deleteExclui resultado (mantém inscrição)resultados.delete
delete_with_inscriptionExclui resultado + inscriçãoresultados.delete_with_inscription
delete_fase_resultsLimpa fase inteira (cascata completa)resultados.delete_fase_results

Config (resultados-config.ts)

ActionDescriçãoLogging
set_nota_corteDefine nota de corte → classifica/desclassifica alunosresultados.set_nota_corte
set_premiacaoDefine faixas de premiação automática (ouro/prata/bronze/menção)resultados.set_premiacao
set_premiacoes_manualDefine premiações manualmente por alunoresultados.set_premiacoes_manual
recomputeRecomputa snapshots do mural para fase/nívelresultados.recompute

Importação Background (resultados-import.ts)

ActionDescrição
start_import_sessionCria sessão + EdgeRuntime.waitUntil() para processamento background
get_import_session_statusPolling de status da sessão (progresso, erros)
cancel_import_sessionCancela sessão pendente/processando

Processamento: batches de 50 registros, recomputeSnapshotsIfPublished() ao final.


5. Validação Zero-Trust (Backend)

  • Pontuação máxima por nível: Validada no backend via fases_olimpiada.pontuacao_maxima_por_nivel. Excedentes rejeitados.
  • Matrícula: Lookup por escola_id + matrícula. Inexistente → erro.
  • Série/nível: Elegibilidade verificada — alunos de série incompatível rejeitados como "inaptos" (retornados em matriculasInaptas).
  • Inscricão auto-criada: Se aluno existe mas não tem inscrição, a inscrição é criada automaticamente durante upsert_batch.

6. Hook: useGestaoResultados.ts

Query Keys

KeyFormato
all['gestao-resultados']
olimpiadas['gestao-resultados', 'olimpiadas']
porOlimpiada['gestao-resultados', 'por-olimpiada', olimpiadaId, faseId]
porOlimpiadaPaginado['gestao-resultados', 'por-olimpiada-pag', ...filtros]
stats['gestao-resultados', 'stats', anoEdicao]
statsAgregados['gestao-resultados', 'stats-agregados', anoEdicao]
batchInit['gestao-resultados', 'batch-init', anoEdicao]

Exports

ExportTipostaleTime
useResultadosBatchInitQuery5min
useResultadosOlimpiadasQuery5min
useResultadosPorOlimpiadaQuery5min
useResultadosPorOlimpiadaPaginadoQuery5min
useResultadosStatsQuery5min
useResultadosStatsAgregadosQuery5min
useResultadosMutationsMutations

Invalidação

Todas as mutations chamam invalidateAll():

typescript
await Promise.all([
  queryClient.invalidateQueries({ queryKey: resultadosKeys.all }),
  queryClient.invalidateQueries({ queryKey: ['mural-liberacao-stats'], refetchType: 'all' }),
  queryClient.invalidateQueries({ queryKey: ['mural-liberacoes'], refetchType: 'all' }),
]);

Cross-feature: invalida mural-liberacao-stats e mural-liberacoes para sincronia com o Mural.
refetchType: 'all' garante refetch mesmo de queries inativas.

Mutations Expostas (boolean wrappers)

FunçãoMutationBackend Action
inserirResultadosLoteinserirLoteMutupsert_batch
inserirResultadoinserirSingleMutupsert_single
definirNotaCortenotaCorteMutset_nota_corte
definirPremiacaopremiacaoMutset_premiacao
definirPremiacoesManualpremiacaoManualMutset_premiacoes_manual
excluirResultadoexcluirMutdelete
excluirResultadoEInscricaoexcluirComInscricaoMutdelete_with_inscription
excluirResultadosFaseexcluirFaseResultadosMutdelete_fase_results

7. Hook de Importação: useImportacaoResultados.ts

Gerencia sessões de importação background com polling:

  • startImportSession() → cria sessão e inicia polling
  • pollSessionStatus() → verifica progresso a cada 2s
  • cancelImportSession() → cancela sessão ativa
  • sessionId persistido em localStorage (sobrevive navegação)

8. Fluxo "Limpar Fase" — Efeitos Cascata

A action delete_fase_results executa:

  1. Deleta resultados_aluno da fase (+filtro por nível se especificado)
  2. Deleta inscricoes_olimpiada correspondentes
  3. Deleta configuracoes_fase_nivel (nota de corte + faixas)
  4. Recomputa snapshots (vazios → deletados)
  5. Auto-despublica: seta flags false em mural_liberacoes
  6. Varredura de fantasmas: liberações true sem dados reais → despublica

9. Padrões Aplicados

PadrãoReferência
Importação background (>50 registros)→ Ref: docs/development/IMPORT_SYSTEM_STANDARD.md
Shared Data Layer (chunks + derivação em memória)→ Ref: _shared/resultados-queries.ts
Snapshots materializados→ Ref: _shared/snapshot-publicados.ts
Cross-feature cache invalidation→ Ref: docs/development/REACT_QUERY_CACHE.md
Zero-trust validation→ Ref: docs/development/CODING_STANDARDS.md
Renderização Atômica→ Ref: docs/development/ATOMIC_RENDERING.md
Error handling (getUserFriendlyError)→ Ref: docs/development/CODING_STANDARDS.md

10. Testes

ArquivoTipoCobertura
gestao-resultados/index.test.tsContract (Deno)CORS, auth, anti-leak
mural-olimpico/__tests__/fase-content.test.tsxComponentRenderização condicional de resultados

11. Navegação

  • sections-registry.ts: id: 'resultados', permissionKey: 'resultados'
  • Feature flag: resultados (deve estar ativa em feature_flags)

12. Scoping por nivel_id (Fases por Nível)

Adicionado em: 2026-04-12

Para olimpíadas com modo_config_fases = 'por_nivel', todas as queries que buscam "fase anterior" ou "última fase" devem escopar por nivel_id. Sem esse scoping, queries com .single() falham com PGRST116 (múltiplas linhas) porque níveis diferentes compartilham a mesma ordem.

Padrão Obrigatório

typescript
let q = supabase.from("fases_olimpiada")
  .select("*")
  .eq("olimpiada_id", olimpiadaId)
  .eq("ordem", targetOrdem);

if (faseAtual.nivel_id) {
  q = q.eq("nivel_id", faseAtual.nivel_id);
} else {
  q = q.is("nivel_id", null);
}

const { data } = await q.maybeSingle(); // NUNCA .single()

Módulos Afetados

MóduloFunçãoScoping
resultados-queries.tsBusca fase anterior para preview✅ Escopado
resultados-mutations.tshandleUpsertBatch, handleUpsertSingle — fase anterior✅ Escopado
resultados-compute.tsehUltimaFase — detecção de última fase✅ Escopado
resultados-import.tsImportação background — última fase✅ Escopado

→ Ref: docs/audits/AUDIT_FASES_POR_NIVEL_2026-04-12.md


13. Referências

  • docs/features/MURAL.md — Liberação de resultados e snapshots
  • docs/development/IMPORT_SYSTEM_STANDARD.md — Padrão de importação
  • docs/security/AUTHENTICATION.md — Auth e roles
  • docs/audits/RESULTADOS_REFACTOR_AUDIT.md — Auditoria de refactor
  • docs/audits/AUDIT_FASES_POR_NIVEL_2026-04-12.md — Auditoria fases por nível