Pular para o conteúdo
© Code by Kevin · Início
Todos os projetos

Estudo de caso · 01 / 035 min de leitura

SEK · Gestão Escolar

Sistema de gestão escolar offline, multiusuário e sem dependências

Papel
Autor único: levantamento, arquitetura, código, instalação e suporte
Equipe
Individual
Ano
2026
Status
Em uso diário
Stack
Node.js · node:sqlite · JavaScript · HTML · CSS · PowerShell

TL;DR

Um sistema de gestão escolar offline, multiusuário e sem nenhuma dependência externa, feito sozinho em 4 etapas para trocar planilhas soltas por um lugar só. Hoje está em uso por toda a secretaria da escola.

Contexto e problema

Trabalho como Jovem Aprendiz na secretaria de uma escola com cerca de 535 alunos, do Maternal ao Ensino Médio. O dia a dia corria em planilhas de Excel e documentos de Word avulsos, espalhados por vários sistemas: o sistema acadêmico (desktop), o de captação de famílias, o da Secretaria Escolar Digital, o da cantina.

Na prática, cada tarefa exigia procurar a planilha certa, copiar dados de um lugar para outro e montar documentos à mão. Rematrícula, pendências de documentos, contratos, declarações, bolsas, conferência de boletos e autorização de saída viviam em arquivos diferentes, e nenhum deles sabia dos outros.

Meu papel

Projeto pessoal e de iniciativa própria, feito sozinho: levantamento das necessidades, arquitetura, código, testes, documentação e instalação. Uso o Claude Code como par de programação, com um documento de handoff vivo (decisões, armadilhas, roteiro) para que cada sessão continue de onde a anterior parou.

Restrições

  • Sem TI dedicada e computadores Windows comuns, sem garantia de poder instalar programas.
  • Precisa funcionar sem internet: a rede da escola não é confiável.
  • Dados de menores: tudo o que envolve LGPD pesa mais do que qualquer conveniência.
  • Várias pessoas ao mesmo tempo: um PC funciona como servidor e os outros acessam pelo navegador.
  • Integração só por exportação: não há acesso garantido ao banco do sistema acadêmico, então os dados entram pelas planilhas .xlsx que ele já exporta.
  • Quem mantém pode não ser programador: instalar e atualizar precisam ser coisa de dois cliques.

Decisões técnicas e trade-offs

DecisãoAlternativaPor quê
App web local: um PC é o servidorNuvem / SaaSDado de menor fica fora da internet, não há mensalidade e tudo funciona offline.
Node.js portátil, sem npm e sem dependênciasnpm + framework (React, Express)Roda com dois cliques e sem build. Não há pacote para atualizar, quebrar ou ser comprometido.
Banco node:sqlite embutido, em modo WALMySQL / PostgreSQLUm arquivo só, sem servidor de banco para instalar; backup a quente com VACUUM INTO.
Importar as exportações .xlsx do sistema acadêmicoLer o MySQL dele diretoO acesso ao banco não é garantido; exportar é o que a secretaria já sabe fazer. O leitor de .xlsx é próprio, sem bibliotecas.
Atualização com 1 botão via git pull --ff-onlyInstalador ou cópia manualQuem não programa atualiza sozinho: o app mostra o que mudou, faz backup antes, aplica e reinicia. Recusa se houver alteração local.
Concorrência otimista (versão por registro)Travar o registro enquanto alguém editaNinguém fica bloqueado; o conflito só aparece quando de fato acontece, e com nome e hora.

Arquitetura

Fluxo principal
  1. 01Navegadores dos PCsHTML, CSS e JS puro; rota por hash
  2. 02Servidor Node.js portátilHTTP, sessões, CSRF, CSP e auditoria
  3. 03node:sqlite (WAL)um arquivo, migrações na abertura

Em volta

  • Backups cifradosAES-256-GCM, para pen drive ou OneDrive
  • Atualização por Gitchangelog, backup, aplica e reinicia
  • Exportações .xlsxalunos, responsáveis e boletins

O servidor é um processo Node.js sem frameworks. As rotas ficam em módulos por área (rotas/*.js) e recebem um contexto comum. Toda alteração passa por um registro de auditoria, e os documentos oficiais saem de modelos .xlsx preenchidos pelo próprio código.

Destaques de implementação

Backup cifrado que viaja no pen drive

O backup é feito com o sistema aberto (VACUUM INTO) e, se houver senha, sai cifrado com AES-256-GCM: chave derivada com scrypt, sal e IV aleatórios e uma marca no começo para reconhecer o arquivo. A restauração é feita em duas etapas: o app valida, agenda a troca e só substitui o banco na próxima abertura, guardando uma cópia do anterior.

app/lib/backup.js
function cifrar(dados, senha) {
  const sal = crypto.randomBytes(16);
  const iv = crypto.randomBytes(12);
  const chave = crypto.scryptSync(senha, sal, 32);
  const c = crypto.createCipheriv("aes-256-gcm", chave, iv);
  const corpo = Buffer.concat([c.update(dados), c.final()]);
  return Buffer.concat([MARCA, sal, iv, c.getAuthTag(), corpo]);
}

Duas pessoas editando a mesma ficha

A tela envia a versão que carregou. Se outra pessoa gravou depois, o servidor responde 409 dizendo quem e quando. Do lado do navegador, só os campos alterados são enviados: se cada pessoa mexeu num campo diferente, o trabalho das duas fica.

app/server.js
function conferirVersao(atual, b, u) {
  const versao = b._versao,
    forcar = b._forcar;
  delete b._versao;
  delete b._forcar;
  if (versao === undefined || forcar) return;
  if ((atual.atualizado_em || null) === (versao || null)) return;
  if (!atual.atualizado_por || atual.atualizado_por === u.login) return;
  // ...busca o nome de quem gravou
  falha(409, `${nome} alterou isto enquanto você editava.`, {
    conflito: { por: nome, em: atual.atualizado_em },
  });
}

O que mais cuida dos dados

  • Login por pessoa, com perfis (administração e aprendiz), troca de senha obrigatória no primeiro acesso, bloqueio de tela validado no servidor e cabeçalho CSP.
  • Sessões guardadas no banco (só o hash do token): reiniciar o servidor não desloga ninguém. Se ele cair, a tela mostra uma faixa de "sem conexão" e o que foi digitado é preservado como rascunho recuperável.
  • LGPD: registro de quem consultou cada ficha, exportação de tudo o que existe sobre um aluno e descarte que anonimiza ex-alunos sem perder a estatística.
  • Dados reais nunca vão para o Git: os modelos são limpos por um script antes de qualquer commit, e todos os testes usam uma demonstração com dados fictícios.

Resultado e impacto

  • Em uso por toda a secretaria. Tarefas que levavam horas ou até dias, como emitir histórico escolar, acompanhar pendências de documentos e montar planilhas de organização, hoje são resolvidas em poucos minutos. Esse ganho ainda é o relato da equipe, não uma medição formal.
  • Versão 5.8.x em evolução contínua, com cerca de 875 KB de JavaScript escrito sem framework.
  • Medido com uma demonstração de ~530 alunos fictícios: a API responde em menos de 30 ms e nenhuma tela passa de 0,4 s. Listas que chegavam a 30 mil pixels de altura caíram para cerca de 6 mil, com carregamento em partes.
  • Uma bateria de testes própria derruba o servidor de verdade, simula duas pessoas editando ao mesmo tempo, restaura backups e confere as 30 telas do app.

O que eu faria diferente e próximos passos

Faria o piloto antes. Construí quatro etapas antes de ter um único módulo rodando com dado real. Hoje eu começaria por um módulo só, em piloto de duas semanas, porque é isso que prova valor e ganha a confiança de quem decide. Sem piloto, o resto é só código.

Próximos passos, nesta ordem:

  1. Medir o tempo de cada tarefa antes e depois, para trocar o relato da equipe por números.
  2. Evoluir a partir do que a secretaria mais usa no dia a dia, um módulo de cada vez.

E o que eu decidi não fazer: reescrever em React (perderia meses e ganharia build e dependências), levar para a nuvem (dado de menor na internet multiplica a responsabilidade) e automatizar WhatsApp em massa (a API oficial é paga e a não oficial derruba o número da escola).

Links

  • Código: interno, por ser um sistema da escola com regras e modelos dela.