TaskFlow Corporate é um sistema completo de gerenciamento de tarefas em tempo real para equipes que trabalham no mesmo escritório ou rede local. Sem mensalidade, sem dados na nuvem.
Interface do sistema
Desenvolvido pensando em pequenas e médias equipes que valorizam simplicidade, velocidade e privacidade de dados.
Cada tecnologia foi escolhida por ter exatamente o que o sistema precisa — sem overhead, sem dependências desnecessárias.
server.js e serve os arquivos estáticos para toda a rede local./api/login, /api/users) e serve o index.html.index.html — sem build, sem bundler, sem dependências de cliente.tasks.json e users.json. Zero configuração de banco de dados. Backup é copiar dois arquivos.Apenas um computador precisa ter o Node.js instalado. Os outros acessam pelo navegador, como qualquer site.
POST /api/login. O servidor valida com bcrypt e devolve um token de sessão.join. O servidor valida o token antes de aceitar a conexão.init — todas as tarefas, usuários, quem está online e quem está editando.tasks.json e users.json — dados nunca se perdem.Trechos reais do sistema explicados em detalhe — para quem quer entender ou contribuir.
// Rota de login: recebe email + senha, valida e devolve token app.post("/api/login", async (req, res) => { const { email, password } = req.body; // 1. Busca o usuário pelo email no array em memória const user = users.find(u => u.email.toLowerCase() === email.trim().toLowerCase() ); if (!user) return res.status(401).json({ error: "Email não encontrado" }); // 2. Compara a senha com o hash salvo — bcrypt.compare é assíncrono // e leva ~100ms intencionalmente para dificultar força bruta const ok = await bcrypt.compare(password, user.passwordHash); if (!ok) return res.status(401).json({ error: "Senha incorreta" }); // 3. Gera um token aleatório de 256 bits e salva na memória const token = createSession(user.id); // 4. Devolve o token + dados públicos (sem o hash da senha) res.json({ ok: true, token, user: publicUser(user) }); });
Por que bcrypt? Diferente de MD5 ou SHA-256, o bcrypt foi projetado para ser lento — o custo computacional (BCRYPT_ROUNDS = 10) garante ~100ms por verificação, tornando ataques de dicionário inviáveis. O salt aleatório embutido garante que dois usuários com a mesma senha tenham hashes diferentes. A senha original nunca é armazenada — apenas o hash.
// Quando um usuário cria uma tarefa, o servidor: // 1. Valida a sessão 2. Salva 3. Propaga para todos socket.on("task_create", (task) => { // Só aceita se o socket tiver sessão válida if (!authSocket(socket.id)) return; tasks.unshift(task); // adiciona no topo da lista saveTasks(); // persiste no disco imediatamente // broadcast: envia para TODOS exceto o criador socket.broadcast.emit("task_created", task); }); // No cliente (index.html), qualquer usuário recebe e atualiza a UI: socket.on("task_created", (task) => { TASKS.unshift(task); // atualiza o array local renderContent(); // re-renderiza a tela instantaneamente pushNotif(`Nova tarefa: ${task.title}`, "+"); });
socket.broadcast.emit vs io.emit: socket.broadcast.emit envia para todos exceto o remetente — o criador já atualizou sua UI localmente. io.emit envia para todos incluindo o remetente — usado em atualizações de presença, onde todos precisam da lista completa. O resultado é latência zero na visualização para quem criou e sincronização automática para o restante da equipe.
const TASKS_FILE = path.join(__dirname, "tasks.json"); const USERS_FILE = path.join(__dirname, "users.json"); // Carrega tarefas do disco na inicialização do servidor function loadTasks() { try { if (fs.existsSync(TASKS_FILE)) return JSON.parse(fs.readFileSync(TASKS_FILE, "utf8")); } catch(e) { console.error("Erro ao carregar tasks.json:", e.message); } return []; // retorna vazio se arquivo não existir } // Salva no disco após cada alteração — síncrono para garantir consistência function saveTasks() { try { fs.writeFileSync( TASKS_FILE, JSON.stringify(tasks, null, 2), // indentado para legibilidade "utf8" ); } catch(e) { console.error("Erro ao salvar tasks.json:", e.message); } } // Estado em memória — carregado uma vez, mantido sincronizado let tasks = loadTasks(); let users = loadUsers();
Por que JSON em vez de banco de dados? Para equipes de até ~50 pessoas e centenas de tarefas, a diferença de performance é imperceptível. A vantagem é enorme: zero configuração, backup com dois arquivos, legibilidade humana, e sem servidor separado. O estado fica inteiramente em memória durante a execução — leitura é instantânea. A escrita síncrona (writeFileSync) garante que nenhuma alteração se perde se o servidor reiniciar.
// Map em memória: token (hex 64 chars) → { userId } const sessions = new Map(); // Cria sessão com token criptograficamente seguro function createSession(userId) { // crypto.randomBytes(32) = 256 bits de entropia — impossível de adivinhar const token = crypto.randomBytes(32).toString("hex"); sessions.set(token, { userId }); return token; } // Valida token e retorna o usuário correspondente (ou null) function getSessionUser(token) { if (!token) return null; const sess = sessions.get(token); if (!sess) return null; return users.find(u => u.id === sess.userId) || null; } // O evento "join" do Socket.io valida o token ANTES de aceitar socket.on("join", (data) => { const sessionUser = getSessionUser(data.token); if (!sessionUser || sessionUser.id !== data.userId) { socket.emit("auth_error", { error: "Sessão inválida" }); return; // rejeita a conexão } // ✓ Token válido — registra o socket e envia o estado inicial connectedUsers.set(socket.id, { ...data }); socket.emit("init", { tasks, users: publicUsers(), ... }); });
Por que tokens em vez de apenas verificar o userId? Sem token, qualquer pessoa poderia abrir o console do navegador e emitir socket.emit("join", {"{"} userId: "u1" {"}"}) se passando pelo admin. O token de 256 bits gerado com crypto.randomBytes é computacionalmente impossível de forjar — e é invalidado automaticamente quando o servidor reinicia ou o usuário faz logout.
Qualquer pessoa com Node.js consegue subir o servidor em menos de 2 minutos.
ipconfig e envie o link http://SEU_IP:3000 para os colegas. O login padrão do admin é admin@corp.com / admin123.Gratuito, privado, sem cadastro, sem nuvem. Só você e sua equipe.