← Proyectos/gps-vehicle-tracking
Live💻 Full Stack

GPS Vehicle Tracking System

Sistema fullstack de rastreo de flota en tiempo real con mapas interactivos, comandos remotos vía AWS IoT y almacenamiento en DynamoDB.

ReactMapLibre GLNode.jsExpressAWS IoTDynamoDBJWTTerraform

Problema & Solución

Problema

Una empresa de logística necesita monitorear su flota de vehículos en tiempo real: ver la posición actual en un mapa, consultar el historial de coordenadas, y enviar comandos remotos (bloquear motor, activar alarma) directamente al dispositivo GPS instalado en el vehículo.

Solución

Dashboard React + MapLibre GL que muestra posiciones en tiempo real. Backend Node.js + Express que gestiona autenticación JWT, CRUD de vehículos y dispositivos GPS en DynamoDB, y envío de comandos remotos via AWS IoT (MQTT). Los dispositivos GPS publican coordenadas al IoT endpoint; el backend las persiste en DynamoDB con TTL.

Diagrama de Arquitectura


  ┌────────────────────────────────────────────────────────────────┐
  │  Dashboard React (Vite + MapLibre GL + TailwindCSS)            │
  │  • Mapa interactivo con posición de vehículos                  │
  │  • CommandPanel: bloquear motor, alarma, flash de luces         │
  │  • Historial de coordenadas por vehículo                       │
  └──────────────────────────────┬─────────────────────────────────┘
                                 │ JWT Auth + REST
                                 ▼
  ┌──────────────────────────────────────────────────────────────┐
  │  Backend (Node.js + Express 4)                               │
  │  • Auth: bcryptjs + jsonwebtoken                             │
  │  • Rutas: /auth, /gps, /vehicles, /commands                  │
  │  • AWS IoT: publica comandos MQTT al dispositivo             │
  └────────────────────┬──────────────────────┬──────────────────┘
                       │                      │
                       ▼                      ▼
  ┌─────────────────────────┐    ┌─────────────────────────────┐
  │  DynamoDB               │    │  AWS IoT Core               │
  │  • Usuarios             │    │  MQTT endpoint              │
  │  • GPS (dispositivos)   │    │  Comandos → dispositivo     │
  │  • Vehículos            │    │  (block_engine, alarm, etc) │
  │  • Coordenadas (TTL)    │    └─────────────────────────────┘
  │  • Comandos             │                 │
  │  • Eventos (TTL)        │                 ▼ MQTT
  └─────────────────────────┘    ┌─────────────────────────────┐
                                 │  Dispositivo GPS (hardware)  │
                                 │  Publica coordenadas         │
                                 │  Recibe comandos MQTT        │
                                 └─────────────────────────────┘

Implementación

1

Frontend — Mapa interactivo con MapLibre GL

El dashboard usa MapLibre GL (open-source, sin costo de API key) para renderizar mapas. Los vehículos se muestran como marcadores con su posición actual. El componente Map.jsx actualiza las posiciones consultando GET /api/gps periódicamente. React Router gestiona las páginas: Dashboard, Login, detalle de vehículo.

2

Backend — Autenticación JWT + RBAC

El backend usa bcryptjs para hashear contraseñas y jsonwebtoken para emitir tokens JWT. El middleware auth.middleware.js verifica el token en cada request protegido. Los usuarios se almacenan en DynamoDB (tabla Usuarios) con un GSI en email para búsqueda eficiente.

3

Envío de comandos remotos vía AWS IoT

El servicio iot-commands.service.js publica mensajes MQTT al IoT endpoint de AWS usando el AWS SDK. Cada comando tiene un payload específico (ej: block_engine → {CMD: 'setdigout 100'}). El estado del comando (pending → sent → executed/failed/timeout) se persiste en DynamoDB (tabla Comandos).

4

Persistencia de coordenadas con TTL en DynamoDB

Las coordenadas GPS se almacenan en DynamoDB con TTL habilitado — expiran automáticamente después del período configurado, controlando el costo de storage. Un GSI en gpsId + timestamp permite consultar el historial de posiciones de un dispositivo ordenado cronológicamente.

Tech Stack

Frontend

React 18.3.1Vite 5.4.2React Router 6.26.2MapLibre GL 4.7.1TailwindCSS 3.4.1Axios 1.7.7Lucide React

Backend

Node.js 18.xExpress 4.19.2jsonwebtoken 9.0.2bcryptjs 2.4.3AWS SDK 2.xuuid 10.0.0dotenv 16.x

Infraestructura AWS

AWS IoT Core (MQTT)DynamoDB (6 tablas + TTL)DynamoDB GSI (email, deviceId, gpsId-timestamp)IAM RolesTerraform

Decisiones Técnicas

MapLibre GL vs Google Maps vs Leaflet

Elegido

MapLibre GL

Alternativas

  • Google Maps — mejor UX y cobertura, costo por uso ($7/1000 requests)
  • Leaflet — open-source, simple, raster tiles, menos performance con muchos marcadores
  • Mapbox GL — similar a MapLibre (es el fork), requiere API key y tiene costos

Razón

MapLibre GL es open-source y no requiere API key ni cargos por uso. Soporta mapas vectoriales con WebGL — mejor performance que Leaflet (raster tiles). Leaflet es más simple pero limitado para mapas con muchos marcadores en tiempo real. Google Maps tiene mejor UX pero cobra por request.

DynamoDB vs RDS para persistir coordenadas GPS

Elegido

DynamoDB con TTL

Alternativas

  • RDS PostgreSQL con PostGIS — queries geoespaciales avanzadas, requiere gestionar instancias
  • TimescaleDB — optimizado para series de tiempo, más complejo de operar

Razón

Las coordenadas GPS son writes frecuentes (cada 30s por vehículo) con lecturas por rango de tiempo. DynamoDB On-Demand escala automáticamente sin capacity planning. El TTL elimina coordenadas antiguas automáticamente sin queries de limpieza. Para 100 vehículos → 2,880 writes/hora → DynamoDB maneja esto sin configuración.

Snippets de Código

Backend — Envío de comando MQTT via AWS IoTjavascript
import AWS from 'aws-sdk';

const iotData = new AWS.IotData({
  endpoint: process.env.IOT_ENDPOINT,
  region: process.env.AWS_REGION,
});

const COMMAND_PAYLOADS = {
  block_engine:     { CMD: 'setdigout 100' },
  unblock_engine:   { CMD: 'setdigout 000' },
  activate_alarm:   { CMD: 'setdigout 010' },
  deactivate_alarm: { CMD: 'setdigout 000' },
  flash_lights:     { CMD: 'setdigout 001' },
  request_status:   { CMD: 'getinfo' },
};

export async function sendIoTCommand(deviceId, command, parameters = {}) {
  const payload = COMMAND_PAYLOADS[command] ?? { CMD: command, ...parameters };

  await iotData.publish({
    topic: `gps/${deviceId}/commands`,
    qos: 1,
    payload: JSON.stringify(payload),
  }).promise();
}
Frontend — Cliente Axios con interceptor JWTjavascript
import axios from 'axios';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  headers: { 'Content-Type': 'application/json' },
});

// Adjunta el JWT automáticamente a cada request
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

export const sendCommand = async (gpsId, command, parameters = {}) => {
  const { data } = await api.post(`/commands/${gpsId}/send`, {
    command,
    parameters,
  });
  return data;
};

export const getCommandHistory = async (gpsId, limit = 50) => {
  const { data } = await api.get(`/commands/${gpsId}/history`, {
    params: { limit },
  });
  return data;
};