cambios de desarrollo

This commit is contained in:
abiandev
2026-06-01 16:11:02 +02:00
parent 48b349d68b
commit 6b7c7ec462
10 changed files with 1282 additions and 23 deletions
+168 -8
View File
@@ -1,4 +1,7 @@
const db = require('../config/db');
const agheeraPushClient = require('../services/agheeraPushClient');
const AGHEERA_CLIENT_ID = 532;
const getDniFromLocation = (locationData) => {
if (locationData?.extras?.alias) {
@@ -70,6 +73,137 @@ const getTripIdFromLocation = (locationData) => {
return null;
};
const getRawTimestampFromLocation = (locationData) => {
const candidates = [
locationData?.timestamp,
locationData?.location?.timestamp
];
for (const value of candidates) {
if (value !== undefined && value !== null && String(value).trim() !== '') {
return String(value).trim();
}
}
return null;
};
const getPersistedTimestampFromLocation = (locationData) => {
const rawTimestamp = getRawTimestampFromLocation(locationData);
if (rawTimestamp === null) {
return {
value: new Date(),
usedFallback: true,
reason: 'missing'
};
}
const parsedTimestamp = new Date(rawTimestamp);
if (Number.isNaN(parsedTimestamp.getTime())) {
return {
value: new Date(),
usedFallback: true,
reason: 'invalid',
rawTimestamp
};
}
return {
value: parsedTimestamp,
usedFallback: false,
reason: null,
rawTimestamp
};
};
const pushLocationToAgheera = async ({ latitude, longitude, dni, tripId, measurementTime }) => {
if (!tripId || !dni) {
return null;
}
const [tripRows] = await db.query(
`SELECT id_cliente
FROM c_viajes
WHERE id_viaje = ?
LIMIT 1`,
[tripId]
);
if (Number.parseInt(tripRows[0]?.id_cliente, 10) !== AGHEERA_CLIENT_ID) {
return null;
}
const baseResult = {
trip_id: tripId,
attempted: true,
success: false,
http_status: null,
error: null
};
const [authorizationRows] = await db.query(
`SELECT id_tipovehiculo AS matricula
FROM c_viajes_proveedor
WHERE id_viaje = ?
AND dni = ?
ORDER BY n_proveedor ASC
LIMIT 1`,
[tripId, dni]
);
const licensePlate = String(authorizationRows[0]?.matricula || '').trim();
if (!licensePlate) {
return {
...baseResult,
error: 'LICENSE_PLATE_NOT_FOUND'
};
}
try {
const result = await agheeraPushClient.pushPosition({
latitude,
longitude,
vehicleId: licensePlate,
licensePlate,
measurementTime
});
return {
...baseResult,
success: true,
http_status: result?.status ?? null
};
} catch (error) {
console.error('Agheera push failed after location update:', {
tripId,
dni,
message: error.message,
status: error.status || null
});
return {
...baseResult,
http_status: error.status || null,
error: error.message
};
}
};
const pushLocationsToAgheera = async (locationsToPush) => {
const results = [];
for (const location of locationsToPush) {
const result = await pushLocationToAgheera(location);
if (result) {
results.push(result);
}
}
return results;
};
const saveLocation = async (req, res) => {
try {
const data = req.body;
@@ -100,8 +234,8 @@ const saveLocation = async (req, res) => {
globalTripId = getTripIdFromLocation(data);
}
const now = new Date();
const rowsToInsert = [];
const locationsToPush = [];
for (const loc of locations) {
const coords = getCoordinatesFromLocation(loc);
@@ -109,13 +243,32 @@ const saveLocation = async (req, res) => {
const tripId = globalTripId !== null ? globalTripId : getTripIdFromLocation(loc);
if (coords && coords.lat !== undefined && coords.lat !== null && coords.lng !== undefined && coords.lng !== null) {
const persistedTimestamp = getPersistedTimestampFromLocation(loc);
if (persistedTimestamp.usedFallback) {
console.warn('Location timestamp fallback applied:', {
reason: persistedTimestamp.reason,
rawTimestamp: persistedTimestamp.rawTimestamp || null,
uuid: loc?.uuid || loc?.location?.uuid || null,
tripId,
dni: dni || null
});
}
rowsToInsert.push([
String(coords.lat),
String(coords.lng),
dni || null,
now,
persistedTimestamp.value,
tripId
]);
locationsToPush.push({
latitude: coords.lat,
longitude: coords.lng,
dni,
tripId,
measurementTime: persistedTimestamp.value
});
}
}
@@ -134,17 +287,24 @@ const saveLocation = async (req, res) => {
[rowsToInsert]
);
return res.json({
const agheeraResults = await pushLocationsToAgheera(locationsToPush);
const responseBody = {
success: true,
count: rowsToInsert.length,
message: 'Locations saved'
});
};
if (agheeraResults.length > 0) {
responseBody.agheera_push = agheeraResults.length === 1 ? agheeraResults[0] : agheeraResults;
}
return res.json(responseBody);
} catch (error) {
console.error('Error saving location:', error);
return res.status(500).json({ success: false, error: error.message });
}
};
module.exports = {
saveLocation
};
module.exports = {
saveLocation
};
+121 -8
View File
@@ -2,6 +2,7 @@ const db = require('../config/db');
const fs = require('fs');
const path = require('path');
const tripIncidenceMailer = require('../services/tripIncidenceMailer');
const agheeraPushClient = require('../services/agheeraPushClient');
const {
collectUploadedTripStatusFiles,
removeUploadedTripStatusFiles,
@@ -23,6 +24,7 @@ const INTERMEDIATE_POINT_ALLOWED_STATES = [3, 4, 5];
const INTERMEDIATE_POINT_ALLOWED_STATES_SET = new Set(INTERMEDIATE_POINT_ALLOWED_STATES);
const SQL_DATETIME_REGEX = /^(\d{4})-(\d{2})-(\d{2}) ([0-2]\d):([0-5]\d):([0-5]\d)$/;
const FAILED_TRIP_STATE = 9;
const AGHEERA_CLIENT_ID = 532;
const INTERMEDIATE_POINT_STATUS_IDS = new Set([3, 4, 5]);
const INCIDENCE_TEXT_CONTROL_CHARACTERS_REGEX = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
const GLOBAL_STATUS_KEYS_BY_STATE_ID = new Map([
@@ -238,6 +240,85 @@ const normalizeCoordinateValue = (rawValue) => {
return String(parsedNumericValue);
};
const pushTripStatusPositionToAgheera = async ({
tripRow,
authorizationRow,
latitud,
longitud,
measurementTime,
tripId,
requestId,
flow
}) => {
const clientId = Number.parseInt(tripRow?.id_cliente, 10);
if (clientId !== AGHEERA_CLIENT_ID) {
return null;
}
const baseResult = {
trip_id: tripId,
attempted: false,
success: false,
http_status: null,
error: null
};
if (latitud === null || longitud === null) {
return {
...baseResult,
error: 'COORDINATES_MISSING'
};
}
const licensePlate = String(authorizationRow?.matricula || '').trim();
if (!licensePlate) {
return {
...baseResult,
error: 'LICENSE_PLATE_NOT_FOUND'
};
}
try {
const result = await agheeraPushClient.pushPosition({
latitude: latitud,
longitude: longitud,
vehicleId: licensePlate,
licensePlate,
measurementTime
});
appendTripStatusDebugLog({
stage: 'update_trip_status:agheera_push_success',
request_id: requestId,
flow,
trip_id: tripId,
http_status: result?.status ?? null
});
return {
...baseResult,
attempted: true,
success: true,
http_status: result?.status ?? null
};
} catch (error) {
console.error('Agheera push failed after trip status update:', {
tripId,
requestId,
flow,
message: error.message,
status: error.status || null
});
return {
...baseResult,
attempted: true,
http_status: error.status || null,
error: error.message
};
}
};
const normalizeSqlDateTimeValue = (rawValue) => {
if (typeof rawValue !== 'string') {
return null;
@@ -1420,7 +1501,7 @@ const updateTripStatus = async (req, res) => {
await connection.beginTransaction();
const [tripRows] = await connection.query(
`SELECT id_viaje, id_estado, id_viaje_padre
`SELECT id_viaje, id_estado, id_viaje_padre, id_cliente
FROM c_viajes
WHERE id_viaje = ?
LIMIT 1
@@ -1453,7 +1534,7 @@ const updateTripStatus = async (req, res) => {
}
const [authorizationRows] = await connection.query(
`SELECT n_proveedor, id_proveedor
`SELECT n_proveedor, id_proveedor, id_tipovehiculo AS matricula
FROM c_viajes_proveedor
WHERE id_viaje = ?
AND dni = ?
@@ -1716,8 +1797,18 @@ const updateTripStatus = async (req, res) => {
idEstadoLogged: FAILED_TRIP_STATE,
idPuntoLogged: idPunto
});
const agheeraPushResult = await pushTripStatusPositionToAgheera({
tripRow: tripRows[0],
authorizationRow: authorizationRows[0],
latitud,
longitud,
measurementTime: now,
tripId,
requestId,
flow: 'failed_branch_manual'
});
return res.status(200).json({
const responseBody = {
success: true,
trip_id: tripId,
updated_status_id: idEstado,
@@ -1729,7 +1820,13 @@ const updateTripStatus = async (req, res) => {
failed_marked: true,
fotos_concat: fotosConcat || '',
updated_at: now.toISOString()
});
};
if (agheeraPushResult) {
responseBody.agheera_push = agheeraPushResult;
}
return res.status(200).json(responseBody);
} catch (error) {
if (connection) {
try {
@@ -1752,7 +1849,7 @@ const updateTripStatus = async (req, res) => {
}
const [tripRows] = await db.query(
`SELECT id_viaje, id_viaje_padre
`SELECT id_viaje, id_viaje_padre, id_cliente
FROM c_viajes
WHERE id_viaje = ?
LIMIT 1`,
@@ -1783,7 +1880,7 @@ const updateTripStatus = async (req, res) => {
}
const [authorizationRows] = await db.query(
`SELECT n_proveedor, id_proveedor
`SELECT n_proveedor, id_proveedor, id_tipovehiculo AS matricula
FROM c_viajes_proveedor
WHERE id_viaje = ?
AND dni = ?
@@ -1912,8 +2009,18 @@ const updateTripStatus = async (req, res) => {
idEstadoLogged: idEstado,
idPuntoLogged: idPunto
});
const agheeraPushResult = await pushTripStatusPositionToAgheera({
tripRow: tripRows[0],
authorizationRow: authorizationRows[0],
latitud,
longitud,
measurementTime: now,
tripId,
requestId,
flow: 'normal_manual'
});
return res.status(200).json({
const responseBody = {
success: true,
trip_id: tripId,
updated_status_id: idEstado,
@@ -1925,7 +2032,13 @@ const updateTripStatus = async (req, res) => {
failed_marked: false,
fotos_concat: fotosConcat || '',
updated_at: now.toISOString()
});
};
if (agheeraPushResult) {
responseBody.agheera_push = agheeraPushResult;
}
return res.status(200).json(responseBody);
} catch (error) {
const parsedTripId = Number.parseInt(req.params?.id, 10);
const parsedStatusId = Number.parseInt(req.body?.id_estado, 10);
+113
View File
@@ -0,0 +1,113 @@
const DEFAULT_AGHEERA_PUSH_URL = 'https://push-test.agheera.com/Telematics/Positions';
let httpClientOverride = null;
const getPushUrl = () =>
String(process.env.AGHEERA_PUSH_URL || DEFAULT_AGHEERA_PUSH_URL).trim();
const getApiKey = () =>
String(process.env.AGHEERA_API_KEY || '').trim();
const formatMeasurementTime = (dateValue) => {
const date = dateValue instanceof Date ? dateValue : new Date(dateValue);
return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
};
const normalizeNumber = (rawValue) => {
const numericValue = Number(rawValue);
return Number.isFinite(numericValue) ? numericValue : null;
};
const getHttpClient = () => {
if (typeof httpClientOverride === 'function') {
return httpClientOverride;
}
if (typeof fetch === 'function') {
return fetch;
}
return null;
};
const buildPositionPayload = ({
latitude,
longitude,
vehicleId,
licensePlate,
measurementTime
}) => ({
Vehicles: [
{
latitude: normalizeNumber(latitude),
longitude: normalizeNumber(longitude),
vehicleId: String(vehicleId || '').trim(),
licensePlate: String(licensePlate || '').trim(),
measurementTime: formatMeasurementTime(measurementTime)
}
]
});
const pushPosition = async ({
latitude,
longitude,
vehicleId,
licensePlate,
measurementTime
}) => {
const httpClient = getHttpClient();
if (!httpClient) {
throw new Error('Agheera HTTP client unavailable');
}
const apiKey = getApiKey();
if (!apiKey) {
throw new Error('Agheera API key missing');
}
const url = getPushUrl();
const payload = buildPositionPayload({
latitude,
longitude,
vehicleId,
licensePlate,
measurementTime
});
const response = await httpClient(url, {
method: 'POST',
headers: {
apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
const responseBody = typeof response?.text === 'function' ? await response.text() : '';
if (!response?.ok) {
const error = new Error('Agheera push failed');
error.status = response?.status || null;
error.body = responseBody;
throw error;
}
return {
status: response.status,
body: responseBody
};
};
const __setHttpClientForTests = (httpClient) => {
httpClientOverride = httpClient;
};
const __resetHttpClientForTests = () => {
httpClientOverride = null;
};
module.exports = {
DEFAULT_AGHEERA_PUSH_URL,
pushPosition,
__setHttpClientForTests,
__resetHttpClientForTests
};