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 validação, antes do piloto com a secretaria.
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 (hoje em 1 PC, com meta de 3).
- Integração só por exportação: não há acesso garantido ao banco do sistema acadêmico, então os
dados entram pelas planilhas
.xlsxque 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ão | Alternativa | Por quê |
|---|---|---|
| App web local: um PC é o servidor | Nuvem / SaaS | Dado de menor fica fora da internet, não há mensalidade e tudo funciona offline. |
| Node.js portátil, sem npm e sem dependências | npm + 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 WAL | MySQL / PostgreSQL | Um arquivo só, sem servidor de banco para instalar; backup a quente com VACUUM INTO. |
Importar as exportações .xlsx do sistema acadêmico | Ler o MySQL dele direto | O 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-only | Instalador ou cópia manual | Quem 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 edita | Ninguém fica bloqueado; o conflito só aparece quando de fato acontece, e com nome e hora. |
Arquitetura
- 01Navegadores dos PCsHTML, CSS e JS puro; rota por hash
- 02Servidor Node.js portátilHTTP, sessões, CSRF, CSP e auditoria
- 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.
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.
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
- 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:
- Piloto real de um módulo pequeno (atendimentos, portão ou vivências).
- Instalação nos 3 computadores da secretaria, testada no local.
- Conferir o histórico escolar gerado com quem o assina hoje.
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.
- Demo em vídeo: TODO(kevin): link do vídeo no LinkedIn