Skip to content

04 — Importação Inteligente de Alunos: o padrão de referência

Ref: 00_INDEX · Esta é a régua contra a qual 03 e 99 comparam.

Contexto

O usuário descreveu o objetivo como "do mesmo jeito que resolvemos a importação de alunos". Este doc destila os 7 elementos desse padrão e demarca exatamente onde cada um vive no código.

Anatomia do padrão (7 elementos)

A1 — Sessão persistente no banco

Tabela importacao_alunos_sessoes (existe — confirmado no inventário Supabase).

  • Cada upload cria uma row, com id (sessionId), dados_importacao (JSON), progresso, status, mensagem.
  • Source of truth: se a aba fecha, refresh, perde rede — a sessão continua viva no DB.

→ Equivalente para resultados: importacao_resultados_sessoes já existe (mesmo inventário). Reutilizar a mesma forma para recompute.

A2 — Endpoint retorna task_id imediatamente

Padrão visto em gestao-alunos/index.ts (action start_import_session): handler valida payload, cria a row, devolve { success: true, sessionId } e dispara o trabalho real em background.

→ Tempo de Response: dezenas de ms. Nada de esperar processamento.

A3 — Background via EdgeRuntime.waitUntil

_shared/alunos-import.ts é chamado dentro de EdgeRuntime.waitUntil(processarImportacao(...)). Sem void (que o Deno mata pós-Response).

→ Memória obrigatória: ver mem://architecture/edge-function-background-task-standard.

A4 — Batches de tamanho fixo (BATCH_SIZE)

alunos-import.ts:166-235 itera em batchStart += BATCH_SIZE (50). Cada batch faz insert + update separados (não delete-and-recreate), com log de progresso ao final.

Vantagem: cada batch é pequeno o suficiente para caber em uma transação rápida, e dá ponto de retomada se algo falhar.

A5 — Update de progresso na sessão a cada batch

alunos-import.ts:228-231 atualiza progresso + mensagem na row da sessão a cada lote. UI lê isso via polling (ver A6).

A6 — Polling client-side com retomada em refresh

src/hooks/useImportacaoSessao.ts:

  • localStorage.setItem(IMPORT_SESSION_KEY, newSessionId) (linha 192) — persiste sessionId no navegador.
  • Ao montar (linha 234): se há sessionId no localStorage, busca status no backend → se ainda processando, retoma o polling; se finalizado, limpa.
  • setInterval(..., 3000) ms para polling, com cleanup em unmount.
  • Detecção de stale (polling sem progresso) cataloga sessão zumbi.

→ Resultado prático: usuário pode fechar a aba, voltar 10min depois, reabrir, e vê a importação ter completado.

A7 — Idempotência por chave canônica

alunos-import.ts:

  • Matriculas duplicadas no arquivo são detectadas via matriculasImportadas.has(matricula.toLowerCase()) (linha 125).
  • Conflito existente no banco resolvido via onConflict no upsert.
  • Lookups internos por id (não por nome) — ex.: inscricaoMap.set(aluno.id, inscricao.id) em resultados-import.ts:159. Padrão correto, vale como referência cruzada com 07.

Tabela de paridade — Alunos vs Resultados

ElementoAlunos (import)Resultados (import)Resultados (recompute)
A1 Sessão DBimportacao_alunos_sessoesimportacao_resultados_sessoes❌ ausente
A2 task_id imediato✅ (provável, validar)❌ síncrono
A3 waitUntil✅ (provável, validar)
A4 BATCH_SIZE✅ 50✅ via queryInChunksparcial (só nos reads)
A5 progresso✅ (validar)
A6 polling + retomadauseImportacaoSessaouseImportacaoResultados (presente)
A7 idempotência por idinscricaoMapparcial (upsert por inscricao_id,fase_id)

Recompute é o gap óbvio (5 das 7 colunas vermelhas).

O que extrair para o SSOT (99)

  1. Toda operação de escrita em massa >50 itens DEVE seguir A1–A7.
  2. Endpoint nunca espera o processamento. Response em <300ms com task_id.
  3. UI tem hook genérico de polling com retomada via localStorage.
  4. Sessões zumbis são detectadas (timeout) e marcadas como falhada por job cron.

Próximas etapas

→ Replicar A1–A7 em recompute (Onda B do roadmap). → Hook genérico useBackgroundTask({ taskId, pollIntervalMs }) para qualquer feature consumir — proposto no 99 §6.