/** * Store de viajes usando Zustand * Gestiona el viaje activo, paradas y estado del tracking */ import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { viajesApi, paradasApi } from '../services/api'; import { storage, STORAGE_KEYS } from '../services/storage'; import { startLocationTracking, stopLocationTracking, getCurrentLocation, } from '../services/location'; import type { Viaje, Parada, Ubicacion, TipoParada, EstadoViaje } from '../types'; interface ViajeState { // Estado viajeActivo: Viaje | null; proximoViaje: Viaje | null; paradaActual: Parada | null; isTracking: boolean; isLoading: boolean; error: string | null; // Estadísticas del día viajesHoy: number; distanciaHoy: number; tiempoConduccionHoy: number; } interface ViajeStore extends ViajeState { // Acciones de viaje fetchViajeActivo: () => Promise; fetchProximoViaje: () => Promise; iniciarViaje: (viajeId: string) => Promise<{ success: boolean; error?: string }>; pausarViaje: (motivo?: string) => Promise<{ success: boolean; error?: string }>; reanudarViaje: () => Promise<{ success: boolean; error?: string }>; finalizarViaje: (notas?: string) => Promise<{ success: boolean; error?: string }>; // Acciones de paradas registrarLlegadaParada: (paradaId: string) => Promise<{ success: boolean; error?: string }>; registrarSalidaParada: ( paradaId: string, notas?: string ) => Promise<{ success: boolean; error?: string }>; registrarParadaNoProgramada: (data: { tipo: TipoParada; notas?: string; }) => Promise<{ success: boolean; error?: string }>; // Utilidades setViajeActivo: (viaje: Viaje | null) => void; updateEstadisticasDia: (stats: Partial) => void; clearError: () => void; reset: () => void; } const initialState: ViajeState = { viajeActivo: null, proximoViaje: null, paradaActual: null, isTracking: false, isLoading: false, error: null, viajesHoy: 0, distanciaHoy: 0, tiempoConduccionHoy: 0, }; export const useViajeStore = create()( persist( (set, get) => ({ ...initialState, // Obtiene el viaje activo del servidor fetchViajeActivo: async () => { try { set({ isLoading: true, error: null }); const response = await viajesApi.getViajeActivo(); if (response.success) { const viaje = response.data; if (viaje) { // Encontrar parada actual (primera no completada) const paradaActual = viaje.paradas.find((p) => !p.completada && p.orden === 1) || viaje.paradas.find((p) => !p.completada) || null; // Si hay viaje en curso, asegurar que el tracking está activo if (viaje.estado === 'en_curso') { await startLocationTracking(viaje.id); set({ isTracking: true }); } set({ viajeActivo: viaje, paradaActual }); // Guardar localmente para modo offline await storage.set(STORAGE_KEYS.VIAJE_ACTIVO, viaje); } else { set({ viajeActivo: null, paradaActual: null }); } } } catch (error) { // Intentar cargar desde storage local const viajeLocal = await storage.get(STORAGE_KEYS.VIAJE_ACTIVO); if (viajeLocal) { set({ viajeActivo: viajeLocal }); } set({ error: error instanceof Error ? error.message : 'Error al obtener viaje', }); } finally { set({ isLoading: false }); } }, // Obtiene el próximo viaje asignado fetchProximoViaje: async () => { try { const response = await viajesApi.getProximoViaje(); if (response.success) { set({ proximoViaje: response.data }); } } catch (error) { console.error('Error obteniendo próximo viaje:', error); } }, // Inicia un viaje iniciarViaje: async (viajeId: string) => { try { set({ isLoading: true, error: null }); const response = await viajesApi.iniciarViaje(viajeId); if (response.success && response.data) { const viaje = response.data; // Iniciar tracking de ubicación await startLocationTracking(viaje.id); const paradaActual = viaje.paradas.find((p) => !p.completada) || null; set({ viajeActivo: viaje, paradaActual, isTracking: true, proximoViaje: null, }); await storage.set(STORAGE_KEYS.VIAJE_ACTIVO, viaje); return { success: true }; } return { success: false, error: response.error || 'No se pudo iniciar el viaje', }; } catch (error) { const message = error instanceof Error ? error.message : 'Error al iniciar viaje'; set({ error: message }); return { success: false, error: message }; } finally { set({ isLoading: false }); } }, // Pausa el viaje activo pausarViaje: async (motivo?: string) => { const { viajeActivo } = get(); if (!viajeActivo) { return { success: false, error: 'No hay viaje activo' }; } try { set({ isLoading: true, error: null }); const response = await viajesApi.pausarViaje(viajeActivo.id, motivo); if (response.success && response.data) { // Detener tracking await stopLocationTracking(); set({ viajeActivo: response.data, isTracking: false, }); return { success: true }; } return { success: false, error: response.error || 'No se pudo pausar el viaje', }; } catch (error) { const message = error instanceof Error ? error.message : 'Error al pausar viaje'; set({ error: message }); return { success: false, error: message }; } finally { set({ isLoading: false }); } }, // Reanuda el viaje pausado reanudarViaje: async () => { const { viajeActivo } = get(); if (!viajeActivo) { return { success: false, error: 'No hay viaje activo' }; } try { set({ isLoading: true, error: null }); const response = await viajesApi.reanudarViaje(viajeActivo.id); if (response.success && response.data) { // Reanudar tracking await startLocationTracking(response.data.id); set({ viajeActivo: response.data, isTracking: true, }); return { success: true }; } return { success: false, error: response.error || 'No se pudo reanudar el viaje', }; } catch (error) { const message = error instanceof Error ? error.message : 'Error al reanudar viaje'; set({ error: message }); return { success: false, error: message }; } finally { set({ isLoading: false }); } }, // Finaliza el viaje activo finalizarViaje: async (notas?: string) => { const { viajeActivo } = get(); if (!viajeActivo) { return { success: false, error: 'No hay viaje activo' }; } try { set({ isLoading: true, error: null }); const response = await viajesApi.finalizarViaje(viajeActivo.id, notas); if (response.success) { // Detener tracking await stopLocationTracking(); // Actualizar estadísticas del día const state = get(); set({ viajeActivo: null, paradaActual: null, isTracking: false, viajesHoy: state.viajesHoy + 1, distanciaHoy: state.distanciaHoy + (viajeActivo.distanciaRecorrida || 0), tiempoConduccionHoy: state.tiempoConduccionHoy + (viajeActivo.tiempoTranscurrido || 0), }); await storage.remove(STORAGE_KEYS.VIAJE_ACTIVO); return { success: true }; } return { success: false, error: response.error || 'No se pudo finalizar el viaje', }; } catch (error) { const message = error instanceof Error ? error.message : 'Error al finalizar viaje'; set({ error: message }); return { success: false, error: message }; } finally { set({ isLoading: false }); } }, // Registra llegada a una parada registrarLlegadaParada: async (paradaId: string) => { try { set({ isLoading: true, error: null }); const ubicacion = await getCurrentLocation(); if (!ubicacion) { return { success: false, error: 'No se pudo obtener la ubicación' }; } const response = await paradasApi.registrarLlegada(paradaId, ubicacion); if (response.success && response.data) { const { viajeActivo } = get(); if (viajeActivo) { // Actualizar parada en el viaje const paradasActualizadas = viajeActivo.paradas.map((p) => p.id === paradaId ? response.data! : p ); const viajeActualizado = { ...viajeActivo, paradas: paradasActualizadas, }; set({ viajeActivo: viajeActualizado, paradaActual: response.data, }); } return { success: true }; } return { success: false, error: response.error || 'No se pudo registrar la llegada', }; } catch (error) { const message = error instanceof Error ? error.message : 'Error al registrar llegada'; set({ error: message }); return { success: false, error: message }; } finally { set({ isLoading: false }); } }, // Registra salida de una parada registrarSalidaParada: async (paradaId: string, notas?: string) => { try { set({ isLoading: true, error: null }); const response = await paradasApi.registrarSalida(paradaId, notas); if (response.success && response.data) { const { viajeActivo } = get(); if (viajeActivo) { // Actualizar parada y encontrar la siguiente const paradasActualizadas = viajeActivo.paradas.map((p) => p.id === paradaId ? { ...response.data!, completada: true } : p ); const siguienteParada = paradasActualizadas.find((p) => !p.completada) || null; const viajeActualizado = { ...viajeActivo, paradas: paradasActualizadas, }; set({ viajeActivo: viajeActualizado, paradaActual: siguienteParada, }); } return { success: true }; } return { success: false, error: response.error || 'No se pudo registrar la salida', }; } catch (error) { const message = error instanceof Error ? error.message : 'Error al registrar salida'; set({ error: message }); return { success: false, error: message }; } finally { set({ isLoading: false }); } }, // Registra una parada no programada registrarParadaNoProgramada: async (data: { tipo: TipoParada; notas?: string; }) => { const { viajeActivo } = get(); if (!viajeActivo) { return { success: false, error: 'No hay viaje activo' }; } try { set({ isLoading: true, error: null }); const ubicacion = await getCurrentLocation(); if (!ubicacion) { return { success: false, error: 'No se pudo obtener la ubicación' }; } const response = await paradasApi.registrarParadaNoProgramada({ viajeId: viajeActivo.id, tipo: data.tipo, ubicacion, notas: data.notas, }); if (response.success && response.data) { // Añadir parada al viaje const viajeActualizado = { ...viajeActivo, paradas: [...viajeActivo.paradas, response.data], }; set({ viajeActivo: viajeActualizado }); return { success: true }; } return { success: false, error: response.error || 'No se pudo registrar la parada', }; } catch (error) { const message = error instanceof Error ? error.message : 'Error al registrar parada'; set({ error: message }); return { success: false, error: message }; } finally { set({ isLoading: false }); } }, // Establece viaje activo manualmente setViajeActivo: (viaje: Viaje | null) => { set({ viajeActivo: viaje }); if (viaje) { storage.set(STORAGE_KEYS.VIAJE_ACTIVO, viaje); } else { storage.remove(STORAGE_KEYS.VIAJE_ACTIVO); } }, // Actualiza estadísticas del día updateEstadisticasDia: (stats: Partial) => { set(stats); }, // Limpia error clearError: () => { set({ error: null }); }, // Reinicia el store reset: () => { stopLocationTracking(); set(initialState); }, }), { name: 'viaje-storage', storage: createJSONStorage(() => AsyncStorage), partialState: (state) => ({ viajeActivo: state.viajeActivo, viajesHoy: state.viajesHoy, distanciaHoy: state.distanciaHoy, tiempoConduccionHoy: state.tiempoConduccionHoy, }), } ) ); export default useViajeStore;