FlotillasGPS - Sistema completo de monitoreo de flotillas GPS
Sistema completo para monitoreo y gestion de flotas de vehiculos con: - Backend FastAPI con PostgreSQL/TimescaleDB - Frontend React con TypeScript y TailwindCSS - App movil React Native con Expo - Soporte para dispositivos GPS, Meshtastic y celulares - Video streaming en vivo con MediaMTX - Geocercas, alertas, viajes y reportes - Autenticacion JWT y WebSockets en tiempo real Documentacion completa y guias de usuario incluidas.
This commit is contained in:
7
mobile/src/hooks/index.ts
Normal file
7
mobile/src/hooks/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Exportación centralizada de hooks
|
||||
*/
|
||||
|
||||
export { useAuth, default as useAuthHook } from './useAuth';
|
||||
export { useLocation, default as useLocationHook } from './useLocation';
|
||||
export { useViaje, default as useViajeHook } from './useViaje';
|
||||
82
mobile/src/hooks/useAuth.ts
Normal file
82
mobile/src/hooks/useAuth.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Hook personalizado para autenticación
|
||||
* Proporciona acceso al estado de auth y acciones comunes
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
|
||||
export const useAuth = () => {
|
||||
const {
|
||||
conductor,
|
||||
token,
|
||||
dispositivo,
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
initialize,
|
||||
requestCode,
|
||||
verifyCode,
|
||||
logout,
|
||||
refreshToken,
|
||||
updateConductor,
|
||||
} = useAuthStore();
|
||||
|
||||
// Inicializar al montar
|
||||
useEffect(() => {
|
||||
initialize();
|
||||
}, [initialize]);
|
||||
|
||||
// Login con teléfono
|
||||
const login = useCallback(
|
||||
async (telefono: string) => {
|
||||
const result = await requestCode(telefono);
|
||||
return result;
|
||||
},
|
||||
[requestCode]
|
||||
);
|
||||
|
||||
// Verificar código
|
||||
const verify = useCallback(
|
||||
async (telefono: string, codigo: string) => {
|
||||
const result = await verifyCode(telefono, codigo);
|
||||
return result;
|
||||
},
|
||||
[verifyCode]
|
||||
);
|
||||
|
||||
// Cerrar sesión
|
||||
const signOut = useCallback(async () => {
|
||||
await logout();
|
||||
}, [logout]);
|
||||
|
||||
// Refrescar token
|
||||
const refresh = useCallback(async () => {
|
||||
return await refreshToken();
|
||||
}, [refreshToken]);
|
||||
|
||||
// Actualizar perfil
|
||||
const updateProfile = useCallback(
|
||||
(updates: Parameters<typeof updateConductor>[0]) => {
|
||||
updateConductor(updates);
|
||||
},
|
||||
[updateConductor]
|
||||
);
|
||||
|
||||
return {
|
||||
// Estado
|
||||
conductor,
|
||||
token,
|
||||
dispositivo,
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
|
||||
// Acciones
|
||||
login,
|
||||
verify,
|
||||
signOut,
|
||||
refresh,
|
||||
updateProfile,
|
||||
};
|
||||
};
|
||||
|
||||
export default useAuth;
|
||||
243
mobile/src/hooks/useLocation.ts
Normal file
243
mobile/src/hooks/useLocation.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* Hook personalizado para tracking de ubicación
|
||||
* Proporciona acceso a ubicación actual y control del tracking
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import {
|
||||
requestLocationPermissions,
|
||||
checkLocationPermissions,
|
||||
getCurrentLocation,
|
||||
startLocationTracking,
|
||||
stopLocationTracking,
|
||||
isTrackingActive,
|
||||
syncOfflineLocations,
|
||||
getPendingLocationsCount,
|
||||
calculateDistance,
|
||||
} from '../services/location';
|
||||
import { useUbicacionStore } from '../store/ubicacionStore';
|
||||
import type { Ubicacion, PermisoStatus } from '../types';
|
||||
|
||||
interface UseLocationOptions {
|
||||
autoStart?: boolean;
|
||||
viajeId?: string;
|
||||
updateInterval?: number; // ms
|
||||
}
|
||||
|
||||
interface UseLocationReturn {
|
||||
// Estado
|
||||
ubicacion: Ubicacion | null;
|
||||
isTracking: boolean;
|
||||
isLoading: boolean;
|
||||
permissionStatus: PermisoStatus['ubicacion'];
|
||||
backgroundPermission: PermisoStatus['ubicacionBackground'];
|
||||
pendingCount: number;
|
||||
isOnline: boolean;
|
||||
error: string | null;
|
||||
|
||||
// Acciones
|
||||
requestPermissions: () => Promise<boolean>;
|
||||
startTracking: (viajeId?: string) => Promise<boolean>;
|
||||
stopTracking: () => Promise<void>;
|
||||
refreshLocation: () => Promise<Ubicacion | null>;
|
||||
syncPending: () => Promise<number>;
|
||||
getDistanceTo: (lat: number, lon: number) => number | null;
|
||||
}
|
||||
|
||||
export const useLocation = (options: UseLocationOptions = {}): UseLocationReturn => {
|
||||
const { autoStart = false, viajeId, updateInterval = 5000 } = options;
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isTracking, setIsTracking] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [permissionStatus, setPermissionStatus] =
|
||||
useState<PermisoStatus['ubicacion']>('undetermined');
|
||||
const [backgroundPermission, setBackgroundPermission] =
|
||||
useState<PermisoStatus['ubicacionBackground']>('undetermined');
|
||||
|
||||
const {
|
||||
ubicacionActual,
|
||||
isOnline,
|
||||
pendingCount,
|
||||
setUbicacionActual,
|
||||
syncPendingLocations,
|
||||
refreshPendingCount,
|
||||
} = useUbicacionStore();
|
||||
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// Verificar permisos al montar
|
||||
useEffect(() => {
|
||||
const checkPermissions = async () => {
|
||||
const permissions = await checkLocationPermissions();
|
||||
setPermissionStatus(permissions.foreground ? 'granted' : 'denied');
|
||||
setBackgroundPermission(permissions.background ? 'granted' : 'denied');
|
||||
};
|
||||
|
||||
checkPermissions();
|
||||
refreshPendingCount();
|
||||
}, [refreshPendingCount]);
|
||||
|
||||
// Auto-start tracking si está configurado
|
||||
useEffect(() => {
|
||||
if (autoStart && permissionStatus === 'granted') {
|
||||
startTracking(viajeId);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
};
|
||||
}, [autoStart, permissionStatus, viajeId]);
|
||||
|
||||
// Solicitar permisos
|
||||
const requestPermissions = useCallback(async (): Promise<boolean> => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const granted = await requestLocationPermissions();
|
||||
|
||||
if (granted) {
|
||||
const permissions = await checkLocationPermissions();
|
||||
setPermissionStatus(permissions.foreground ? 'granted' : 'denied');
|
||||
setBackgroundPermission(permissions.background ? 'granted' : 'denied');
|
||||
return true;
|
||||
}
|
||||
|
||||
setPermissionStatus('denied');
|
||||
setError('Permisos de ubicación denegados');
|
||||
return false;
|
||||
} catch (err) {
|
||||
setError('Error solicitando permisos');
|
||||
return false;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Iniciar tracking
|
||||
const startTracking = useCallback(
|
||||
async (tripId?: string): Promise<boolean> => {
|
||||
if (isTracking) return true;
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const started = await startLocationTracking(tripId || viajeId);
|
||||
|
||||
if (started) {
|
||||
setIsTracking(true);
|
||||
|
||||
// Configurar intervalo de actualización de UI
|
||||
intervalRef.current = setInterval(async () => {
|
||||
const location = await getCurrentLocation();
|
||||
if (location) {
|
||||
setUbicacionActual(location);
|
||||
}
|
||||
}, updateInterval);
|
||||
|
||||
// Obtener ubicación inicial
|
||||
const initialLocation = await getCurrentLocation();
|
||||
if (initialLocation) {
|
||||
setUbicacionActual(initialLocation);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
setError('No se pudo iniciar el tracking');
|
||||
return false;
|
||||
} catch (err) {
|
||||
setError('Error iniciando tracking');
|
||||
return false;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[isTracking, viajeId, updateInterval, setUbicacionActual]
|
||||
);
|
||||
|
||||
// Detener tracking
|
||||
const stopTracking = useCallback(async (): Promise<void> => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
|
||||
await stopLocationTracking();
|
||||
setIsTracking(false);
|
||||
}, []);
|
||||
|
||||
// Refrescar ubicación manualmente
|
||||
const refreshLocation = useCallback(async (): Promise<Ubicacion | null> => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const location = await getCurrentLocation();
|
||||
|
||||
if (location) {
|
||||
setUbicacionActual(location);
|
||||
return location;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (err) {
|
||||
setError('Error obteniendo ubicación');
|
||||
return null;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [setUbicacionActual]);
|
||||
|
||||
// Sincronizar ubicaciones pendientes
|
||||
const syncPending = useCallback(async (): Promise<number> => {
|
||||
try {
|
||||
const synced = await syncPendingLocations();
|
||||
await refreshPendingCount();
|
||||
return synced;
|
||||
} catch (err) {
|
||||
console.error('Error sincronizando:', err);
|
||||
return 0;
|
||||
}
|
||||
}, [syncPendingLocations, refreshPendingCount]);
|
||||
|
||||
// Calcular distancia a un punto
|
||||
const getDistanceTo = useCallback(
|
||||
(lat: number, lon: number): number | null => {
|
||||
if (!ubicacionActual) return null;
|
||||
|
||||
return calculateDistance(
|
||||
ubicacionActual.latitud,
|
||||
ubicacionActual.longitud,
|
||||
lat,
|
||||
lon
|
||||
);
|
||||
},
|
||||
[ubicacionActual]
|
||||
);
|
||||
|
||||
return {
|
||||
// Estado
|
||||
ubicacion: ubicacionActual,
|
||||
isTracking,
|
||||
isLoading,
|
||||
permissionStatus,
|
||||
backgroundPermission,
|
||||
pendingCount,
|
||||
isOnline,
|
||||
error,
|
||||
|
||||
// Acciones
|
||||
requestPermissions,
|
||||
startTracking,
|
||||
stopTracking,
|
||||
refreshLocation,
|
||||
syncPending,
|
||||
getDistanceTo,
|
||||
};
|
||||
};
|
||||
|
||||
export default useLocation;
|
||||
232
mobile/src/hooks/useViaje.ts
Normal file
232
mobile/src/hooks/useViaje.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Hook personalizado para gestión de viajes
|
||||
* Proporciona acceso al viaje activo y acciones de control
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useViajeStore } from '../store/viajeStore';
|
||||
import { useLocation } from './useLocation';
|
||||
import type { Viaje, Parada, TipoParada } from '../types';
|
||||
|
||||
interface UseViajeReturn {
|
||||
// Estado del viaje
|
||||
viaje: Viaje | null;
|
||||
proximoViaje: Viaje | null;
|
||||
paradaActual: Parada | null;
|
||||
isLoading: boolean;
|
||||
isTracking: boolean;
|
||||
error: string | null;
|
||||
|
||||
// Estadísticas
|
||||
viajesHoy: number;
|
||||
distanciaHoy: number;
|
||||
tiempoHoy: number;
|
||||
progreso: number;
|
||||
|
||||
// Info calculada
|
||||
distanciaRestante: number;
|
||||
tiempoEstimadoRestante: number;
|
||||
distanciaAParada: number | null;
|
||||
|
||||
// Acciones de viaje
|
||||
cargarViajeActivo: () => Promise<void>;
|
||||
cargarProximoViaje: () => Promise<void>;
|
||||
iniciar: (viajeId: string) => Promise<{ success: boolean; error?: string }>;
|
||||
pausar: (motivo?: string) => Promise<{ success: boolean; error?: string }>;
|
||||
reanudar: () => Promise<{ success: boolean; error?: string }>;
|
||||
finalizar: (notas?: string) => Promise<{ success: boolean; error?: string }>;
|
||||
|
||||
// Acciones de paradas
|
||||
llegarAParada: (paradaId: string) => Promise<{ success: boolean; error?: string }>;
|
||||
salirDeParada: (
|
||||
paradaId: string,
|
||||
notas?: string
|
||||
) => Promise<{ success: boolean; error?: string }>;
|
||||
registrarParada: (data: {
|
||||
tipo: TipoParada;
|
||||
notas?: string;
|
||||
}) => Promise<{ success: boolean; error?: string }>;
|
||||
|
||||
// Utilidades
|
||||
limpiarError: () => void;
|
||||
}
|
||||
|
||||
export const useViaje = (): UseViajeReturn => {
|
||||
const {
|
||||
viajeActivo,
|
||||
proximoViaje,
|
||||
paradaActual,
|
||||
isLoading,
|
||||
isTracking,
|
||||
error,
|
||||
viajesHoy,
|
||||
distanciaHoy,
|
||||
tiempoConduccionHoy,
|
||||
fetchViajeActivo,
|
||||
fetchProximoViaje,
|
||||
iniciarViaje,
|
||||
pausarViaje,
|
||||
reanudarViaje,
|
||||
finalizarViaje,
|
||||
registrarLlegadaParada,
|
||||
registrarSalidaParada,
|
||||
registrarParadaNoProgramada,
|
||||
clearError,
|
||||
} = useViajeStore();
|
||||
|
||||
const { ubicacion, getDistanceTo } = useLocation({
|
||||
autoStart: !!viajeActivo && viajeActivo.estado === 'en_curso',
|
||||
viajeId: viajeActivo?.id,
|
||||
});
|
||||
|
||||
// Cargar viaje activo al montar
|
||||
useEffect(() => {
|
||||
fetchViajeActivo();
|
||||
fetchProximoViaje();
|
||||
}, [fetchViajeActivo, fetchProximoViaje]);
|
||||
|
||||
// Calcular progreso del viaje
|
||||
const progreso = useMemo(() => {
|
||||
if (!viajeActivo || !viajeActivo.distanciaEstimada) return 0;
|
||||
|
||||
const recorrido = viajeActivo.distanciaRecorrida || 0;
|
||||
return Math.min(100, (recorrido / viajeActivo.distanciaEstimada) * 100);
|
||||
}, [viajeActivo]);
|
||||
|
||||
// Calcular distancia restante
|
||||
const distanciaRestante = useMemo(() => {
|
||||
if (!viajeActivo) return 0;
|
||||
|
||||
const estimada = viajeActivo.distanciaEstimada || 0;
|
||||
const recorrida = viajeActivo.distanciaRecorrida || 0;
|
||||
|
||||
return Math.max(0, estimada - recorrida);
|
||||
}, [viajeActivo]);
|
||||
|
||||
// Calcular tiempo estimado restante
|
||||
const tiempoEstimadoRestante = useMemo(() => {
|
||||
if (!viajeActivo) return 0;
|
||||
|
||||
const estimado = viajeActivo.tiempoEstimado || 0;
|
||||
const transcurrido = viajeActivo.tiempoTranscurrido || 0;
|
||||
|
||||
return Math.max(0, estimado - transcurrido);
|
||||
}, [viajeActivo]);
|
||||
|
||||
// Calcular distancia a la parada actual
|
||||
const distanciaAParada = useMemo(() => {
|
||||
if (!paradaActual || !ubicacion) return null;
|
||||
|
||||
return getDistanceTo(
|
||||
paradaActual.punto.latitud,
|
||||
paradaActual.punto.longitud
|
||||
);
|
||||
}, [paradaActual, ubicacion, getDistanceTo]);
|
||||
|
||||
// Cargar viaje activo
|
||||
const cargarViajeActivo = useCallback(async () => {
|
||||
await fetchViajeActivo();
|
||||
}, [fetchViajeActivo]);
|
||||
|
||||
// Cargar próximo viaje
|
||||
const cargarProximoViaje = useCallback(async () => {
|
||||
await fetchProximoViaje();
|
||||
}, [fetchProximoViaje]);
|
||||
|
||||
// Iniciar viaje
|
||||
const iniciar = useCallback(
|
||||
async (viajeId: string) => {
|
||||
return await iniciarViaje(viajeId);
|
||||
},
|
||||
[iniciarViaje]
|
||||
);
|
||||
|
||||
// Pausar viaje
|
||||
const pausar = useCallback(
|
||||
async (motivo?: string) => {
|
||||
return await pausarViaje(motivo);
|
||||
},
|
||||
[pausarViaje]
|
||||
);
|
||||
|
||||
// Reanudar viaje
|
||||
const reanudar = useCallback(async () => {
|
||||
return await reanudarViaje();
|
||||
}, [reanudarViaje]);
|
||||
|
||||
// Finalizar viaje
|
||||
const finalizar = useCallback(
|
||||
async (notas?: string) => {
|
||||
return await finalizarViaje(notas);
|
||||
},
|
||||
[finalizarViaje]
|
||||
);
|
||||
|
||||
// Registrar llegada a parada
|
||||
const llegarAParada = useCallback(
|
||||
async (paradaId: string) => {
|
||||
return await registrarLlegadaParada(paradaId);
|
||||
},
|
||||
[registrarLlegadaParada]
|
||||
);
|
||||
|
||||
// Registrar salida de parada
|
||||
const salirDeParada = useCallback(
|
||||
async (paradaId: string, notas?: string) => {
|
||||
return await registrarSalidaParada(paradaId, notas);
|
||||
},
|
||||
[registrarSalidaParada]
|
||||
);
|
||||
|
||||
// Registrar parada no programada
|
||||
const registrarParada = useCallback(
|
||||
async (data: { tipo: TipoParada; notas?: string }) => {
|
||||
return await registrarParadaNoProgramada(data);
|
||||
},
|
||||
[registrarParadaNoProgramada]
|
||||
);
|
||||
|
||||
// Limpiar error
|
||||
const limpiarError = useCallback(() => {
|
||||
clearError();
|
||||
}, [clearError]);
|
||||
|
||||
return {
|
||||
// Estado
|
||||
viaje: viajeActivo,
|
||||
proximoViaje,
|
||||
paradaActual,
|
||||
isLoading,
|
||||
isTracking,
|
||||
error,
|
||||
|
||||
// Estadísticas
|
||||
viajesHoy,
|
||||
distanciaHoy,
|
||||
tiempoHoy: tiempoConduccionHoy,
|
||||
progreso,
|
||||
|
||||
// Info calculada
|
||||
distanciaRestante,
|
||||
tiempoEstimadoRestante,
|
||||
distanciaAParada,
|
||||
|
||||
// Acciones de viaje
|
||||
cargarViajeActivo,
|
||||
cargarProximoViaje,
|
||||
iniciar,
|
||||
pausar,
|
||||
reanudar,
|
||||
finalizar,
|
||||
|
||||
// Acciones de paradas
|
||||
llegarAParada,
|
||||
salirDeParada,
|
||||
registrarParada,
|
||||
|
||||
// Utilidades
|
||||
limpiarError,
|
||||
};
|
||||
};
|
||||
|
||||
export default useViaje;
|
||||
Reference in New Issue
Block a user