Mural Olímpico — Liberação de Resultados
Fluxo completo de publicação, snapshots materializados e sincronização de cache.
1. Visão Geral
O Mural Olímpico permite que coordenadores publiquem resultados de olimpíadas para visualização no mural de alunos/responsáveis. O fluxo utiliza snapshots materializados para garantir performance e controle de acesso granular.
Fluxo Principal
Coordenador insere resultados → Configura nota de corte/premiação (opcional)
→ Publica via card de liberação → Snapshots gerados em `mural_dados_publicados`
→ Aluno/responsável vê no mural2. Tabelas Envolvidas
| Tabela | Função |
|---|---|
resultados_aluno | Pontuações brutas por fase/nível |
configuracoes_fase_nivel | Nota de corte e faixas de premiação (ouro/prata/bronze/menção) |
mural_liberacoes | Flags de publicação por fase/nível |
mural_dados_publicados | Snapshots materializados para o mural |
escola_olimpiadas | Vínculo escola↔olimpíada (status ativa) |
3. Flags de Liberação (mural_liberacoes)
| Flag | O que controla | Efeito no mural |
|---|---|---|
liberar_notas | Pontuação do aluno | Mostra nota obtida |
liberar_resultados | Classificação + premiação | Mostra posição, medalha, certificado |
Exclusividade: Ativar notas desativa resultados e vice-versa. O backend detecta a mudança e aplica a exclusividade automaticamente.
Config embutida (JSONB)
config_notas:{ exibirNotaMaxima, exibirClassificacao, exibirPorNivel, exibirPorSerie }config_resultados:{ exibirPontuacao, exibirClassificacao, exibirPorNivel, exibirPorSerie }
4. Snapshots Materializados
Geração
Ao publicar, o backend (mural-escola action toggle_liberacao):
- Busca inscrições da escola+olimpíada em chunks de 80 IDs (limite PostgREST)
- Para cada inscrição, resolve resultado da fase correspondente
- Calcula empates por nível (
empate_nivel) e por série (empate_serie) - Resolve pontuação máxima via fallback:
resultado.pontuacao_maxima→fase.pontuacao_maxima_por_nivel[nivel] - Upsert em
mural_dados_publicados
SSOT do helper: supabase/functions/_shared/snapshot-publicados.ts (computeAndUpsertSnapshot())
Recomputação Automática
Snapshots são recomputados automaticamente em:
- Inserção/importação de resultados (
recomputeSnapshotsIfPublished) - Deleção de resultados (parcial ou total)
- Atualização de prêmios ou nota de corte
- Botão "Republicar snapshot" (🔄) no UI do coordenador
Limpeza
Quando liberar_notas=false E liberar_resultados=false, os snapshots da fase/nível são deletados (não apenas desativados).
5. "Limpar Fase" — Efeitos Cascata
A action delete_fase_results em gestao-resultados executa:
- Deleta
resultados_alunoda fase (+ filtro por nível se especificado) - Deleta
inscricoes_olimpiadacorrespondentes - Deleta
configuracoes_fase_nivelda fase/nível (nota de corte + faixas de premiação) - Recomputa snapshots (que serão vazios → deletados)
- Auto-despublica: seta
liberar_notas=falseeliberar_resultados=falseemmural_liberacoes - Varredura de fantasmas: para toda a olimpíada, verifica liberações com
trueque não possuem resultados reais → despublica
6. Badge "Publicado" — Lógica do Frontend
getStatusGeral(escolaOlimpiadaId)
Localização: src/components/mural-olimpico/mural-liberacoes.tsx
O badge é calculado cruzando mural_liberacoes com statsMap:
Para cada liberação com flag true:
→ Verificar se existe stat com total > 0 para mesma fase+nível
→ Só contar como "publicado" se houver dados reais| Resultado | Badge | Cor |
|---|---|---|
| ≥1 flag true COM dados | Publicado | Verde (bg-green-500) |
| Flags true mas SEM dados | Pendente | Amarelo (bg-amber-500) |
| Nenhuma flag true | Pendente | Amarelo (bg-amber-500) |
Isso evita badges fantasma: liberações que ficaram true no banco mas cujos dados foram limpos.
7. Sincronização de Cache (Frontend)
Query Keys envolvidas
| Key | Conteúdo | Onde usado |
|---|---|---|
['mural-liberacoes'] | Lista de LiberacaoMural (flags) | useMuralEscola, badge, cards |
['mural-liberacao-stats', 'batch', ...] | Stats por fase/nível (total, fases) | Cards de liberação, badge |
['gestao-resultados', ...] | Resultados do coordenador | Tabela de resultados |
Invalidação obrigatória
Toda operação que altera resultados ou liberações DEVE invalidar:
// Em useGestaoResultados.invalidateAll()
queryClient.invalidateQueries({ queryKey: resultadosKeys.all });
queryClient.invalidateQueries({ queryKey: ['mural-liberacao-stats'], refetchType: 'all' });
queryClient.invalidateQueries({ queryKey: ['mural-liberacoes'], refetchType: 'all' });O refetchType: 'all' garante refetch mesmo de queries inativas (usuário em outra tela).
Pontos de invalidação
| Operação | Invalida |
|---|---|
| Inserir resultados (manual ou importação) | resultados + stats + liberacoes |
| Limpar fase | resultados + stats + liberacoes |
| Toggle liberação (publicar/despublicar) | stats + liberacoes |
| Importação em background (sessão concluída) | resultados + stats + liberacoes |
8. Renderização dos Cards de Liberação
Layout unificado (todas as olimpíadas)
Olimpíada (tab no topo, uma por escola_olimpiada)
└── Fase (tab secundária)
└── Nível (botão dentro da fase)
└── Card de liberação (Notas ou Resultados)Independente do modo_config_fases da olimpíada (uniforme ou por_nivel), o Mural sempre renderiza na hierarquia Fase → Nível. O modo por_nivel é relevante apenas para configuração interna do especialista (questões/tempo), não para a visualização do coordenador.
Visibilidade dos cards
Um card de liberação só aparece se stats.total > 0 para aquela combinação fase+nível. Sem dados (ex: após limpar fase), o card não é renderizado.
9. Checklist — Alterações no Fluxo
□ Alterou resultados? → invalidar stats + liberacoes
□ Alterou configuracoes_fase_nivel? → recomputar snapshots se publicado
□ Limpou fase? → deletar configs + auto-despublicar + varrer fantasmas
□ Novo tipo de liberação? → atualizar types/mural.ts + mural-escola backend
□ Cache não atualiza? → verificar refetchType: 'all' nas invalidações
□ Badge fantasma? → verificar getStatusGeral cruza com stats