O rastreamento em tempo real e uma das funcionalidades mais valorizadas em apps de servico: saber onde esta o entregador, quando o tecnico vai chegar ou acompanhar o progresso de um servico ao vivo. Este guia apresenta a arquitetura e implementacao de um sistema de rastreamento escalavel.
Componentes do sistema de rastreamento
Um sistema de rastreamento em tempo real tem tres camadas:
Emissor: o dispositivo movel do prestador/entregador que captura a localizacao via GPS e envia periodicamente para o servidor.
Broker/servidor: recebe as atualizacoes de localizacao, armazena (temporariamente), e propaga para os clientes interessados (clientes com pedido ativo, painel admin).
Receptor: o app do cliente e/ou painel admin que recebe as atualizacoes e atualiza a visualizacao no mapa em tempo real.
WebSockets vs Polling vs Server-Sent Events
Para atualizacoes em tempo real, ha tres opcoes principais:
Polling: cliente faz requisicao HTTP a cada N segundos pedindo a localizacao atual. Simples de implementar, mas desperdicador: gera requisicoes mesmo quando nenhum dado mudou.
Server-Sent Events (SSE): servidor mantem uma conexao HTTP aberta e empurra atualizacoes para o cliente quando disponivel. Unidirecional (servidor para cliente), simples, usa HTTP normal.
WebSockets: conexao bidirec full-duplex entre cliente e servidor. Mais complexo, mas necessario quando o cliente tambem precisa enviar dados de volta (como confirmacao de recebimento). Melhor para alto volume de usuarios.
Para rastreamento de entregadores, WebSockets e a escolha correta pois o app do entregador envia localizacao E o servidor precisa enviar atualizacoes de status do pedido para o entregador.
Backend com Socket.io: sala por pedido
O padrao de “sala” do Socket.io e elegante para rastreamento: cada pedido tem uma sala propria. O entregador e o cliente entram na sala do pedido, e todas as mensagens sao restritias a essa sala.
const io = require(“socket.io”)(server);
io.on(“connection”, (socket) => {
socket.on(“entrar_sala_pedido”, async ({ pedidoId, token }) => {
const usuario = await verificarToken(token);
if (!usuario) { socket.disconnect(); return; }
// Verifica que o usuario tem acesso a esse pedido
const temAcesso = await verificarAcessoPedido(usuario.id, pedidoId);
if (!temAcesso) { socket.emit(“erro”, “Sem permissao”); return; }
socket.join(`pedido:${pedidoId}`);
socket.data.userId = usuario.id;
socket.data.pedidoId = pedidoId;
});
socket.on(“atualizar_localizacao”, async ({ lat, lng, velocidade }) => {
const { pedidoId, userId } = socket.data;
if (!pedidoId) return;
// Verifica que quem emite e o entregador do pedido
const pedido = await db.query(“SELECT entregador_id FROM pedidos WHERE id=$1”, [pedidoId]);
if (pedido.rows[0]?.entregador_id !== userId) return;
// Armazena ultima localizacao
await redis.setex(`loc:${pedidoId}`, 600, JSON.stringify({ lat, lng, velocidade, ts: Date.now() }));
// Propaga para todos na sala exceto o proprio emitente
socket.to(`pedido:${pedidoId}`).emit(“localizacao”, { lat, lng, velocidade });
});
});
Escalabilidade: Redis Adapter para multiplas instancias
Se o backend precisar escalar para multiplas instancias (load balancer na frente), o Socket.io precisa de um adapter compartilhado para que mensagens emitidas em uma instancia cheguem aos sockets conectados em outras instancias:
const { createAdapter } = require(“@socket.io/redis-adapter”);
const { createClient } = require(“redis”);
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
Com isso, o sistema escala horizontalmente sem nenhum estado compartilhado em memoria.
ETA (Estimated Time of Arrival) dinamico
Exibir o tempo estimado de chegada e um diferencial importante. A forma mais precisa e usar a Directions API do Google com a posicao atual do entregador:
const calcularETA = async (posEntregador, posDestino) => {
const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${posEntregador.lat},${posEntregador.lng}&destination=${posDestino.lat},${posDestino.lng}&departure_time=now&traffic_model=best_guess&key=${GOOGLE_MAPS_KEY}`;
const res = await fetch(url);
const data = await res.json();
if (data.status !== “OK”) return null;
const duration = data.routes[0].legs[0].duration_in_traffic?.value
|| data.routes[0].legs[0].duration.value; // segundos
return Math.ceil(duration / 60); // minutos
};
Recalcule o ETA a cada 30-60 segundos e envie para o cliente via WebSocket para que o contador no app do cliente seja atualizado dinamicamente.
Tem um projeto em mente?
Somos especialistas em transformar ideias em produtos digitais. Apps, sites, automações e IA — vamos construir juntos.