Fecha: 6 de Noviembre 2025 MΓ³dulo: Combustible GPS + Estados Operacionales
Calcular el consumo real de combustible considerando:
- Recargas externas (combustible agregado desde fuera)
- Trasvasije interno (movimiento entre tanques del mismo nodo)
- Ruido por movimiento del barco (variaciones por oleaje)
- Consumo durante recarga (~80 L/h mientras se carga)
Ejemplo sin considerar recargas:
Stock Inicial (01/10): 5000L
Stock Final (31/10): 3000L
ββββββββββββββββββββββββββββ
Consumo Aparente: 2000L β INCORRECTO
Si hubo una recarga de 12000L el 09/10:
Stock Inicial: 5000L
Recarga: +12000L
Stock Final: 3000L
ββββββββββββββββββββββββββββ
Consumo Real: 14000L β
CORRECTO
Agrupa todos los sensores de combustible del nodo por timestamp para analizar la suma total:
{
ts: '2025-10-09 12:00:00',
sensors: {
123: { value: 5000, name: 'Babor' },
124: { value: 4500, name: 'Estribor' },
125: { value: 3000, name: 'Popa' }
},
total: 12500 // β Suma de todos los tanques
}const THRESHOLDS = {
NOISE: 200, // Β±200L variaciΓ³n por movimiento del barco
EXTERNAL_REFILL_MIN: 5000, // Recarga externa mΓnima
TRANSFER_MIN: 500, // Trasvasije mΓnimo detectable
CONSUMPTION_DURING_REFILL: 80, // L/h mientras se carga
MAX_REFILL_DURATION: 6 // MΓ‘ximo 6 horas para una recarga
};if (totalDiff > EXTERNAL_REFILL_MIN) {
// Suma total del nodo aumentΓ³ >5000L
// Ejemplo: 12500L β 24500L = +12000L recarga
}if (Math.abs(totalDiff) <= NOISE && tankDiff > TRANSFER_MIN) {
// Suma total constante PERO un tanque subiΓ³
// Ejemplo:
// - Total: 12500L β 12500L (sin cambio)
// - Babor: 5000L β 6000L (+1000L)
// - Estribor: 4500L β 3500L (-1000L)
// = Trasvasije de Estribor β Babor
}if (Math.abs(totalDiff) <= NOISE) {
// Variaciones pequeΓ±as por movimiento
// Se ignoran en el cΓ‘lculo de consumo
}Durante una recarga, el barco sigue operando y consumiendo:
if (timeDiff > 0 && timeDiff <= MAX_REFILL_DURATION) {
estimated_consumption = CONSUMPTION_DURING_REFILL * timeDiff;
// Ej: Recarga de 3 horas = ~240L consumidos durante la carga
}Servicio especializado en detectar recargas y trasvasije:
class FuelRefillDetectionService {
// Agrupa puntos por timestamp
static groupByTimestamp(fuelData)
// Detecta eventos de recarga/trasvasije
static detectRefillEvents(fuelData)
// Calcula consumo real
static calculateRealConsumption(fuelData)
}MΓ©todos principales:
// Detectar eventos
const events = FuelRefillDetectionService.detectRefillEvents(fuelData);
// Retorna:
{
external_refills: [
{
type: 'EXTERNAL_REFILL',
timestamp: '2025-10-09 12:00:00',
amount: 12000,
duration_hours: 3,
estimated_consumption_during_refill: 240,
tanks: { ... }
}
],
transfers: [
{
type: 'TRANSFER',
timestamp: '2025-10-09 15:00:00',
from: { id: 124, name: 'Estribor', amount: 1000 },
to: { id: 123, name: 'Babor', amount: 1000 }
}
],
noise_filtered: 42 // Puntos ignorados por ruido
}
// Calcular consumo real
const analysis = FuelRefillDetectionService.calculateRealConsumption(fuelData);
// Retorna:
{
real_consumption: 14000,
apparent_consumption: 2000,
total_refills: 12000,
refill_count: 1,
transfer_count: 1,
events: { ... }
}Integra la detecciΓ³n de recargas en el anΓ‘lisis:
static async getFullAnalysis(nodeId, sensorNodeIds, companyId, startDate, endDate) {
// ... obtener datos GPS y combustible ...
// Detectar recargas y trasvasije
const refillAnalysis = FuelRefillDetectionService.calculateRealConsumption(fuelData);
// Calcular mΓ©tricas de L/h por estado
const consumptionRates = {
navegando: consumptionByState.navegando.total / navigationSummary.navegando.hours,
fondeado: consumptionByState.fondeado.total / navigationSummary.fondeado.hours,
detenido: consumptionByState.detenido.total / navigationSummary.detenido.hours
};
return {
tracking: { ... },
fuel: {
data: fuelWithStates,
consumptionByState,
dailyConsumption,
refillAnalysis // β Nuevo
},
...
};
}async getConsumptionByState(req, res) {
// Intentar obtener del cachΓ© Redis
const cached = await this.cache.get('result', { ... });
if (cached) {
return res.json({ success: true, ...cached, cached: true });
}
// Calcular y guardar en cachΓ©
const analysis = await FuelConsumptionByStateService.getFullAnalysis(...);
await this.cache.set('result', { ... }, analysis);
res.json({ success: true, ...analysis });
}TTL del cachΓ©:
- Datos recientes (<7 dΓas): 5 minutos
- Datos antiguos (>7 dΓas): 30 minutos
Ahora muestran L/h ademΓ‘s de horas y distancia:
renderStateSummary() {
navegandoRate.textContent = `β½ ${summary.navegando.liters_per_hour.toFixed(1)} L/h`;
fondeadoRate.textContent = `β½ ${summary.fondeado.liters_per_hour.toFixed(1)} L/h`;
detenidoRate.textContent = `β½ ${summary.detenido.liters_per_hour.toFixed(1)} L/h`;
}renderRefillAlerts() {
// Detectar si hay eventos
if (hasEvents) {
// Mostrar alertas de recargas externas
// Mostrar alertas de trasvasije
// Mostrar consumo real vs aparente
}
}VisualizaciΓ³n:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π’οΈ Eventos Detectados β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π Recargas Externas: β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β 09/10/2025 12:30:00 β β
β β β¬οΈ +12000L (~240L consumidos durante) β β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β
β π Trasvasije Interno: β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β 09/10/2025 15:00:00 β β
β β Estribor β‘οΈ Babor β β
β β ~1000L trasvasados β β
β βββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Consumo Aparente: 2000L β
β Consumo Real: 14000L β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
{
navegando: {
hours: 18.8,
distance: 156.2,
liters_per_hour: 95.3, // β Nuevo
consumption: 1792
},
fondeado: {
hours: 1.5,
distance: 0.2,
liters_per_hour: 12.7, // β Nuevo
consumption: 19
},
detenido: {
hours: 9.8,
distance: 0.0,
liters_per_hour: 8.5, // β Nuevo
consumption: 83
}
}{
real_consumption: 14000,
apparent_consumption: 2000,
total_refills: 12000,
refill_count: 1,
transfer_count: 1,
events: {
external_refills: [...],
transfers: [...],
noise_filtered: 42
}
}- Consumo real preciso: Considera todas las recargas
- DetecciΓ³n automΓ‘tica: No requiere input manual
- Distingue trasvasije: No confunde con recarga externa
- Filtra ruido: Ignora variaciones por movimiento
- MΓ©tricas por estado: L/h navegando, fondeado, detenido
- CachΓ© inteligente: Respuestas rΓ‘pidas con Redis
- Alertas visuales: Usuario ve eventos detectados
Entrada:
Punto 1: Total = 5000L
Punto 2: Total = 17000L (+12000L)
Resultado:
β
Recarga externa detectada: +12000L
β
Consumo real calculado correctamente
Entrada:
Punto 1: Babor=5000L, Estribor=4500L, Total=9500L
Punto 2: Babor=6000L, Estribor=3500L, Total=9500L
Resultado:
β
Trasvasije detectado: Estribor β Babor (1000L)
β
NO detectado como recarga externa
Entrada:
Punto 1: Total = 9500L
Punto 2: Total = 9600L (+100L)
Resultado:
β
Filtrado como ruido (<200L)
β
NO afecta cΓ‘lculo de consumo
- β services/FuelRefillDetectionService.js (Nuevo)
- β services/FuelConsumptionByStateService.js (Modificado)
- β controllers/apps/sensorMonitor/api/FuelKpiController.js (CachΓ© agregado)
- β
public/javascripts/apps/sensorMonitor/kpi/fuel/fuelGpsStates.js
- Alertas de recargas
- MΓ©tricas L/h en tarjetas
Implementado: 6 de Noviembre 2025 Autor: Sistema Kalafate IoT