Como uma planilha de controle manual se tornou um sistema web completo rodando inteiramente no browser.
O Problema Original
O controle operacional da recepção da unidade WPM era feito de forma manual: planilhas, anotações em papel, WhatsApp para pendências. Dados de alunos novos, vendas de addons, NPS e escala de trabalho não tinham uma fonte única da verdade.
Cada recepcionista registrava informações de forma diferente. Relatórios mensais levavam horas para compilar. Pendências se perdiam entre conversas.
A Decisão
A solução foi criar um sistema digital próprio. A escolha de começar como um único arquivo HTML foi deliberada: sem servidor, sem deploy, sem dependências externas. Qualquer computador com um browser poderia abrir e usar imediatamente.
Isso eliminou a barreira de entrada e permitiu iterações rápidas. O arquivo poderia ser passado por pen-drive, WhatsApp ou e-mail.
Escopo Inicial — O que precisava existir
Operacional
Registro de alunos novos com atendente responsável, vendas de addons vinculadas aos atendimentos, controle de pendências com status e responsáveis.
Gestão
Dashboard executivo com métricas do mês, NPS com ranking de recepcionistas, escala de trabalho mensal com regras trabalhistas.
Infraestrutura
Segregação por período mensal, exportação e importação de backups JSON, sistema de fechamento mensal que avança o período automaticamente.
WPM Gestão Interna — um sistema operacional completo de gestão de recepção rodando 100% no browser, sem backend.
Definição Técnica
O sistema é um Single Page Application (SPA) manual — um único arquivo .html com todo o HTML, CSS e JavaScript embutidos. O estado da aplicação é armazenado no localStorage do browser, segregado por chaves no formato wpm_MM_AAAA. Não há servidor, não há banco de dados externo, não há login.
A UI é controlada por um sistema de abas (tabs) que exibem e ocultam seções sem recarregar a página. Todo o fluxo de dados segue um padrão unidirecional: Input → Estado → saveState() → render().
Equipe Gerenciada
Recepcionistas
Wallace, PESSOA 2, PESSOA 3, PESSOA 4 — podem ser atendentes de alunos novos e hostess de pendências.
Professores
PESSOA A, PESSOA B, PESSOA C, PESSOA D, PESSOA E, PESSOA F, PESSOA G — aparecem na escala. PESSOA G não pode fazer abertura aos sábados e feriados (restrição interjornada).
Períodos Mensais
O sistema trata cada mês como uma unidade isolada. Dados de 01_2026 não vazam para 02_2026. O fechamento mensal congela os dados, gera um JSON de backup e avança automaticamente para o próximo período.
Campos deriváveis — como dia da semana a partir de uma data — nunca são salvos no storage. São sempre calculados em tempo de execução para economizar o limite de ~5MB do localStorage.
Zero build tools. Zero Node.js. Zero backend. Tudo que o sistema usa está disponível nativamente no browser ou carregado via CDN com SRI.
Core — Nativo
Bibliotecas via CDN (SRI)
Persistência
localStorage com chaves no padrão wpm_{periodo}. Serialização via JSON.stringify e JSON.parse. Proteção contra QuotaExceededError em todos os setItem.
Dados de backup exportados como JSON com metadados. Importação com sanitização profunda antes de aplicar ao estado.
Gráficos
Chart.js para gráficos de barras (produtividade por atendente) e doughnut (NPS). Instâncias destruídas com chartInstance.destroy() antes de recriar, prevenindo memory leaks.
Função destroyChart() centraliza o padrão de limpeza de canvas antes de novas instanciações.
Exportações
jsPDF + autotable para PDFs de relatórios e escala. SheetJS para exportação CSV/Excel de alunos, pendências e escala.
Todos os URL.createObjectURL() são revogados com revokeObjectURL() após download para evitar memory leaks de Blob.
Cada aba do sistema é um módulo completo com CRUD, filtros, exportações e integração com os demais módulos.
Integrações entre módulos
Os módulos não são ilhas isoladas. Eles compartilham estado e se afetam mutuamente:
Alunos → Addons
Ao cadastrar um aluno com addon, a venda é automaticamente refletida no módulo de Addons. Ao editar ou excluir, o addon é atualizado. O Dashboard agrega ambos.
Escala → Dashboard
O dashboard mostra um resumo de dias trabalhados por funcionário com base na escala do mês ativo. Alertas de inconsistência aparecem no Dashboard e na Central de Inconsistências.
NPS → Dashboard
O score e o ranking do NPS são exibidos nos cards executivos do Dashboard. Mudanças no módulo NPS refletem imediatamente no resumo geral.
A identidade visual foi mantida consistente desde as primeiras versões. Tema dark com glassmorphism, tipografia Montserrat e amarelo primário.
Paleta de Cores
Princípios UX
Toast notifications para salvamentos. Confirmações confirm() para ações destrutivas. Badges de status com cores semânticas.
Tabelas dentro de wrappers com overflow-x:auto. Sidebar como drawer em mobile. Modais em 90-100% da largura.
Atributos aria-label, role="tab", role="tabpanel" nas navegações principais. Contraste adequado entre texto e fundo.
Uma falha de layout crítica foi descoberta ao usar height:100vh; overflow:hidden em contêineres. Isso criava scroll traps e gaps de conteúdo. O padrão correto adotado: min-height:100vh no contêiner da app, scroll natural do body e position:sticky para elementos fixos.
33 versões produzidas ao longo da fase browser-only, cada uma com um objetivo específico.
Cada crise foi documentada e gerou uma regra de arquitetura permanente.
v7–v8 — Camada fora do layout
O que aconteceu
Adição de uma faixa extra de botões abaixo das abas de navegação foi implementada fora da malha principal de layout. O resultado foi uma interface visual quebrada, com sobreposição de elementos e scroll comportando-se incorretamente.
Regra gerada
Qualquer expansão do layout deve ser testada em todas as viewports antes de entregar. Componentes novos fora da malha principal têm custo alto de estabilidade.
v21 — Tela vazia ao abrir
O que aconteceu
Mudança no namespace de storage quebrou silenciosamente o carregamento do estado. A aplicação abria, mas sem mês ativo e sem dados — completamente inutilizável. Usuário em produção afetado.
Regra gerada
Mudanças de storage precisam de fallback automático e migração com rollback. O carregamento deve detectar dados em chaves antigas e migrar antes de deletar.
v23–v24 — Dados de teste invisíveis
O que aconteceu
O localStorage reutilizava dados de versões anteriores. O seed determinístico da v23 gerava dados, mas eles não eram visíveis porque a chave de storage estava com namespace da versão antiga.
Regra gerada
Cada versão deve ter namespace isolado. Dados de teste devem ser gerados com RNG baseado na chave do período para garantir reprodutibilidade e isolamento.
v13–v17 — Degradação por patches iterativos
O que aconteceu
Aplicação sequencial de str_replace em arquivo de 5000+ linhas gerou funções duplicadas, CSS desconectado, IDs órfãos e bugs que se acumulavam. A qualidade do código degradou progressivamente a cada patch.
Regra gerada
Patches iterativos em arquivos massivos são anti-padrão. Prefira reescrita atômica via heredoc (cat > arquivo.html << 'EOF') para mudanças estruturais.
Aprendizados técnicos e de processo que guiarão a próxima fase do sistema.
A degradação de v13 a v17 foi causada por múltiplos str_replace em um arquivo de 5000+ linhas. Cada patch introduzia pequenas inconsistências que se acumulavam. A solução é a reescrita atômica completa do arquivo quando há mudanças estruturais, ou a adoção de arquitetura modular (a próxima fase).
A tabela de alunos usava a classe .pending-table (projetada para 8 colunas) numa tabela de 11 colunas. O calendário tinha classes definidas em JS mas ausentes no stylesheet. Ambos causaram layout quebrado. Regra: cada componente precisa de CSS dedicado com definição explícita de colunas.
Claude tendeu a arquiteturas de segurança mais robustas (cobertura de esc(), sanitizeDeep, safeLocalSet). GPT tendeu a fluxos UX mais coesos e lógica de reset unificada. Os melhores resultados vieram de merges deliberados com trilha de auditoria explícita.
A decisão de retornar à v6 quando v7-v8 falharam foi a decisão certa. Versões estáveis devem ser marcadas explicitamente antes de evoluções arriscadas. Rollback não é fracasso — é parte do processo de engenharia.
O protocolo de validação da V33 — node --check para sintaxe JS, parser HTML Python para estrutura, detecção de IDs/funções duplicadas, auditoria de todos os localStorage.setItem — deve ser padrão em todas as entregas. Não existe "entrega rápida" sem validação.
As funções resetSelectedMonth() e closeCurrentMonth() precisam usar buildCleanPeriod() que preserva apenas a configuração da equipe — jamais dados de demo. Qualquer dado residual de um mês anterior contaminando um novo período era um bug crítico de negócio.
Segurança ofensiva e defensiva implementada em todas as camadas do sistema.
Prevenção de XSS
Tolerância zero para injeção direta via innerHTML. Todos os 128 pontos de renderização dinâmica passam pela função esc() que escapa HTML antes de inserir no DOM.
Alternativas seguras usadas: document.createElement() + textContent e DocumentFragment para renderizações complexas.
SRI em Dependências
Todas as bibliotecas externas (Chart.js, jsPDF, SheetJS, Montserrat) são carregadas com atributos integrity="sha384-..." e crossorigin="anonymous".
Isso garante que, caso o CDN seja comprometido, o browser rejeite o script/fonte adulterado antes de executar.
Sanitização de Backups
A função importarBackup() executa um loop de higienização profunda via sanitizeDeep() em todas as chaves do JSON antes de fazer o merge com o estado local via Object.assign.
Isso previne injeção de código através de arquivos JSON adulterados.
Proteção do localStorage
Todos os localStorage.setItem() estão envolvidos em blocos try...catch via safeLocalSet() para capturar QuotaExceededError e notificar o usuário com uma mensagem clara em vez de falhar silenciosamente.
Regras que não podem ser quebradas em nenhuma versão, presente ou futura do sistema.
Alunos novos só podem ter um recepcionista como atendente (Wallace, PESSOA 2, PESSOA 3, PESSOA 4). Pendências só aceitam recepcionistas como hostess. Professores só aparecem na escala. Esta regra reflete a hierarquia operacional real da unidade.
Ao cadastrar um aluno com addon, a venda é criada no módulo de Addons. Ao editar o aluno, o addon é atualizado. Ao excluir o aluno, o addon é removido. Não pode existir addon órfão.
Um funcionário não pode ser escalado no sábado E no domingo da mesma semana. Como trabalham de segunda a sexta (5 dias), trabalhar nos dois dias do fim de semana resultaria em 7 dias consecutivos — acima do limite legal de 6 dias.
PESSOA G não pode pegar turnos de abertura (prof1) aos sábados ou feriados, por conta do descanso interjornada. A função podeAbertura() retorna false para PESSOA G nestas situações. Esta restrição deve ser preservada em todas as versões futuras.
O cálculo de prioridade na escala automática pondera 70% do histórico recente (últimos 3 meses) e 30% do histórico total. Feriados têm peso maior que domingos curtos. Isso garante distribuição justa de turnos ao longo do tempo.
Todos os campos de data no storage usam o formato YYYY-MM-DD. Nunca armazenar strings de data localizadas ou timestamps com timezone. Isso previne bugs de fuso horário onde uma data se torna o dia anterior ou posterior dependendo do browser.
Dados do período 03_2026 nunca devem vazar para 02_2026 ou 04_2026. Toda operação que escreve ou lê dados deve verificar se está operando no período ativo correto. Reset de mês limpa apenas o período selecionado.
12 critérios de validação obrigatórios, todos passados antes da aprovação para testes operacionais.
Uma abordagem diferenciada: desenvolvimento paralelo com Claude (Anthropic) e GPT (OpenAI), comparando outputs e fazendo merges deliberados.
Claude (Anthropic)
Pontos fortes identificados
Arquitetura de segurança consistentemente mais robusta. Cobertura sistemática de esc() em todos os pontos de injeção. Implementação padronizada de sanitizeDeep() e safeLocalSet(). Auditoria detalhada pós-entrega.
Área de melhoria
Algumas escolhas de arquitetura UX menos intuitivas. Padrão de reset de período com lógica mais fragmentada.
GPT (OpenAI)
Pontos fortes identificados
Fluxos UX mais coesos e intuitivos. Lógica unificada de reset de período com buildCleanPeriod() mais elegante. Menor fragmentação do estado de UI.
Área de melhoria
Cobertura de segurança menos sistemática. Tendência a simplificar sanitização em favor de brevidade de código.
Como o merge foi feito na V33
A versão final não foi uma escolha entre uma IA ou outra — foi uma síntese deliberada com trilha de auditoria:
Base: V31 (Claude)
Toda a arquitetura de segurança — esc(), sanitizeDeep(), safeLocalSet(), confirmações destrutivas, SRI em CDNs. O esqueleto do sistema.
Patches: V32 (GPT)
Lógica unificada de resetSelectedMonth() e closeCurrentMonth() usando buildCleanPeriod(). Melhorias no fluxo de navegação entre períodos.
Resultado: V33 (Merge)
Melhor arquitetura de segurança + melhor lógica de período. Validada pelos 12 critérios. Aprovada por Maclayne após inspeção manual.
Maclayne avalia ambas as IAs criticamente e espera que cada IA faça o mesmo com a outra. Não existe "a IA certa". Existe a análise técnica do output de cada uma, identificação dos pontos fortes de cada versão, e a síntese deliberada na versão final. Esta metodologia produziu resultados consistentemente superiores às versões produzidas por uma única IA em isolamento.
A fase browser-only foi o laboratório de regras de negócio. A próxima fase é a migração para arquitetura profissional com banco de dados, API e frontend componentizado.
Visão da Nova Arquitetura
O sistema browser-only provou as regras de negócio em condições reais. Agora o objetivo é evoluir para uma arquitetura que suporte múltiplos usuários simultâneos, banco de dados relacional, autenticação, CI/CD e testes automatizados E2E.
O WPMRECEPV33.html passa a ser a referência funcional oficial (legacy-reference/) — jamais reescrito, mas lido como fonte da verdade de comportamento e regras.
Frontend
Backend
Roadmap por Sprints
Sprint 0 — Fundação
Monorepo, Docker PostgreSQL, TypeScript configurado, ESLint/Prettier, README de setup local.
Sprint 1 — Modelagem
Schema Prisma com todas as entidades, migrations, seed de funcionários fixos e 12 períodos mensais.
Sprint 2 — API Backend
Fastify com CRUD de todos os módulos, validação Zod, testes com 80% de cobertura.
Sprint 3 — Frontend Base
React + Vite, identidade WPM, 8 abas, seletor de período, Dashboard e Alunos end-to-end.
Sprint 4 — Módulos Restantes
NPS, Escala, Eventos, Addons, Configurações com backup/export/import. Gestão mensal completa.
Sprint 5 — Deploy
E2E com Playwright, CI GitHub Actions, deploy Vercel (web) + Railway (API) + Neon (banco).
28+ meses de operação real com a equipe. Todas as regras de negócio foram testadas e refinadas em condições reais. Os bugs encontrados geraram regras permanentes. A próxima fase não começa do zero — começa com um legado funcional sólido, documentado e auditado.