mas cambios

This commit is contained in:
2026-03-12 10:54:46 -04:00
parent 6b1e9a25af
commit 951a1f64ac
25 changed files with 5788 additions and 284 deletions

224
src/hooks/useCollections.ts Normal file
View File

@@ -0,0 +1,224 @@
import { useState, useEffect, useCallback } from 'react';
import { collectionsApi, Collection, CollectionStats, CreateCollectionDto, UpdateCollectionDto, AddCollectionItemDto } from '@/services/collectionsApi';
import { useAuth } from '@/contexts/AuthContext';
import { toast } from 'sonner';
export const useCollections = () => {
const { user } = useAuth();
const [collections, setCollections] = useState<Collection[]>([]);
const [stats, setStats] = useState<CollectionStats | null>(null);
const [selectedCollection, setSelectedCollection] = useState<Collection | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Cargar colecciones
const loadCollections = useCallback(async () => {
if (!user?.id) return;
try {
setLoading(true);
setError(null);
const data = await collectionsApi.getMyCollections();
setCollections(data);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Error al cargar colecciones';
setError(errorMessage);
console.error('Error loading collections:', err);
} finally {
setLoading(false);
}
}, [user?.id]);
// Cargar estadísticas
const loadStats = useCallback(async () => {
if (!user?.id) return;
try {
const data = await collectionsApi.getCollectionsStats();
setStats(data);
} catch (err) {
console.error('Error loading collections stats:', err);
}
}, [user?.id]);
// Crear colección
const createCollection = useCallback(async (data: CreateCollectionDto): Promise<Collection | null> => {
if (!user?.id) {
toast.error('Inicia sesión para crear colecciones');
return null;
}
try {
const newCollection = await collectionsApi.createCollection(data);
toast.success('Colección creada');
await loadCollections();
await loadStats();
return newCollection;
} catch (err) {
console.error('Error creating collection:', err);
toast.error('Error al crear colección');
return null;
}
}, [user?.id, loadCollections, loadStats]);
// Obtener colección por ID
const getCollectionById = useCallback(async (id: string): Promise<Collection | null> => {
try {
const collection = await collectionsApi.getCollectionById(id);
setSelectedCollection(collection);
return collection;
} catch (err) {
console.error('Error fetching collection:', err);
return null;
}
}, []);
// Actualizar colección
const updateCollection = useCallback(async (id: string, data: UpdateCollectionDto): Promise<boolean> => {
if (!user?.id) {
toast.error('Inicia sesión para actualizar colecciones');
return false;
}
try {
await collectionsApi.updateCollection(id, data);
toast.success('Colección actualizada');
await loadCollections();
return true;
} catch (err) {
console.error('Error updating collection:', err);
toast.error('Error al actualizar colección');
return false;
}
}, [user?.id, loadCollections]);
// Eliminar colección
const deleteCollection = useCallback(async (id: string): Promise<boolean> => {
if (!user?.id) {
toast.error('Inicia sesión para eliminar colecciones');
return false;
}
try {
await collectionsApi.deleteCollection(id);
toast.success('Colección eliminada');
setCollections(prev => prev.filter(c => c.id !== id));
await loadStats();
return true;
} catch (err) {
console.error('Error deleting collection:', err);
toast.error('Error al eliminar colección');
return false;
}
}, [user?.id, loadStats]);
// Agregar item a colección
const addItemToCollection = useCallback(async (collectionId: string, data: AddCollectionItemDto): Promise<boolean> => {
if (!user?.id) {
toast.error('Inicia sesión para agregar items');
return false;
}
try {
await collectionsApi.addItemToCollection(collectionId, data);
toast.success('Item agregado a la colección');
// Recargar la colección si está seleccionada
if (selectedCollection?.id === collectionId) {
await getCollectionById(collectionId);
}
await loadCollections();
return true;
} catch (err) {
console.error('Error adding item to collection:', err);
toast.error('Error al agregar item');
return false;
}
}, [user?.id, selectedCollection?.id, getCollectionById, loadCollections]);
// Quitar item de colección
const removeItemFromCollection = useCallback(async (collectionId: string, itemId: string): Promise<boolean> => {
if (!user?.id) {
toast.error('Inicia sesión para quitar items');
return false;
}
try {
await collectionsApi.removeItemFromCollection(collectionId, itemId);
toast.success('Item eliminado de la colección');
// Actualizar colección seleccionada
if (selectedCollection?.id === collectionId) {
await getCollectionById(collectionId);
}
await loadCollections();
return true;
} catch (err) {
console.error('Error removing item from collection:', err);
toast.error('Error al eliminar item');
return false;
}
}, [user?.id, selectedCollection?.id, getCollectionById, loadCollections]);
// Reordenar items
const reorderItems = useCallback(async (collectionId: string, itemIds: string[]): Promise<boolean> => {
try {
await collectionsApi.reorderCollectionItems(collectionId, itemIds);
if (selectedCollection?.id === collectionId) {
await getCollectionById(collectionId);
}
return true;
} catch (err) {
console.error('Error reordering items:', err);
toast.error('Error al reordenar items');
return false;
}
}, [selectedCollection?.id, getCollectionById]);
// Reordenar colecciones
const reorderCollections = useCallback(async (collectionIds: string[]): Promise<boolean> => {
try {
await collectionsApi.reorderCollections(collectionIds);
await loadCollections();
return true;
} catch (err) {
console.error('Error reordering collections:', err);
toast.error('Error al reordenar colecciones');
return false;
}
}, [loadCollections]);
// Limpiar error
const clearError = useCallback(() => {
setError(null);
}, []);
// Carga inicial
useEffect(() => {
if (user?.id) {
loadCollections();
loadStats();
}
}, [user?.id, loadCollections, loadStats]);
return {
collections,
stats,
selectedCollection,
loading,
error,
loadCollections,
loadStats,
createCollection,
getCollectionById,
updateCollection,
deleteCollection,
addItemToCollection,
removeItemFromCollection,
reorderItems,
reorderCollections,
setSelectedCollection,
clearError,
getCollectionsCount: () => collections.length,
};
};
export default useCollections;

176
src/hooks/useFavorites.ts Normal file
View File

@@ -0,0 +1,176 @@
import { useState, useEffect, useCallback } from 'react';
import { favoritesApi, Favorite, FavoriteItemType, CreateFavoriteDto, FavoritesCounts } from '@/services/favoritesApi';
import { useAuth } from '@/contexts/AuthContext';
import { toast } from 'sonner';
export const useFavorites = (initialItemType?: FavoriteItemType) => {
const { user } = useAuth();
const [favorites, setFavorites] = useState<Favorite[]>([]);
const [counts, setCounts] = useState<FavoritesCounts | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [selectedType, setSelectedType] = useState<FavoriteItemType | undefined>(initialItemType);
// Cargar favoritos
const loadFavorites = useCallback(async (itemType?: FavoriteItemType) => {
if (!user?.id) return;
try {
setLoading(true);
setError(null);
const data = await favoritesApi.getMyFavorites(itemType || selectedType);
setFavorites(data);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Error al cargar favoritos';
setError(errorMessage);
console.error('Error loading favorites:', err);
} finally {
setLoading(false);
}
}, [user?.id, selectedType]);
// Cargar conteos
const loadCounts = useCallback(async () => {
if (!user?.id) return;
try {
const data = await favoritesApi.getFavoritesCounts();
setCounts(data);
} catch (err) {
console.error('Error loading favorites counts:', err);
}
}, [user?.id]);
// Agregar a favoritos
const addFavorite = useCallback(async (data: CreateFavoriteDto): Promise<boolean> => {
if (!user?.id) {
toast.error('Inicia sesión para agregar favoritos');
return false;
}
try {
await favoritesApi.addFavorite(data);
toast.success('Agregado a favoritos');
await loadFavorites();
await loadCounts();
return true;
} catch (err) {
console.error('Error adding favorite:', err);
toast.error('Error al agregar favorito');
return false;
}
}, [user?.id, loadFavorites, loadCounts]);
// Toggle favorito
const toggleFavorite = useCallback(async (data: CreateFavoriteDto): Promise<boolean> => {
if (!user?.id) {
toast.error('Inicia sesión para gestionar favoritos');
return false;
}
try {
const result = await favoritesApi.toggleFavorite(data);
if (result.action === 'added') {
toast.success('Agregado a favoritos');
} else {
toast.success('Eliminado de favoritos');
}
await loadFavorites();
await loadCounts();
return true;
} catch (err) {
console.error('Error toggling favorite:', err);
toast.error('Error al actualizar favorito');
return false;
}
}, [user?.id, loadFavorites, loadCounts]);
// Eliminar favorito
const removeFavorite = useCallback(async (favoriteId: string): Promise<boolean> => {
if (!user?.id) {
toast.error('Inicia sesión para gestionar favoritos');
return false;
}
try {
await favoritesApi.removeFavorite(favoriteId);
toast.success('Eliminado de favoritos');
setFavorites(prev => prev.filter(f => f.id !== favoriteId));
await loadCounts();
return true;
} catch (err) {
console.error('Error removing favorite:', err);
toast.error('Error al eliminar favorito');
return false;
}
}, [user?.id, loadCounts]);
// Verificar si es favorito
const checkFavorite = useCallback(async (itemType: FavoriteItemType, itemId: string): Promise<boolean> => {
if (!user?.id) return false;
try {
const result = await favoritesApi.checkFavorite(itemType, itemId);
return result.isFavorite;
} catch (err) {
console.error('Error checking favorite:', err);
return false;
}
}, [user?.id]);
// Verificar si un item está en los favoritos cargados
const isFavorite = useCallback((itemId: string, itemType?: FavoriteItemType): boolean => {
return favorites.some(f => f.itemId === itemId && (!itemType || f.itemType === itemType));
}, [favorites]);
// Obtener favorito por ID
const getFavoriteById = useCallback((favoriteId: string): Favorite | undefined => {
return favorites.find(f => f.id === favoriteId);
}, [favorites]);
// Filtrar por tipo
const filterByType = useCallback((type: FavoriteItemType): Favorite[] => {
return favorites.filter(f => f.itemType === type);
}, [favorites]);
// Cambiar tipo seleccionado
const changeType = useCallback((type?: FavoriteItemType) => {
setSelectedType(type);
loadFavorites(type);
}, [loadFavorites]);
// Limpiar error
const clearError = useCallback(() => {
setError(null);
}, []);
// Carga inicial
useEffect(() => {
if (user?.id) {
loadFavorites();
loadCounts();
}
}, [user?.id, loadFavorites, loadCounts]);
return {
favorites,
counts,
loading,
error,
selectedType,
loadFavorites,
loadCounts,
addFavorite,
toggleFavorite,
removeFavorite,
checkFavorite,
isFavorite,
getFavoriteById,
filterByType,
changeType,
clearError,
getFavoritesCount: () => favorites.length,
};
};
export default useFavorites;

View File

@@ -0,0 +1,177 @@
/**
* useNotifications Hook
* Hook para gestionar notificaciones en el Dashboard - Fase 2
*/
import { useState, useEffect, useCallback } from 'react';
import { notificationsApi, Notification, NotificationType, NotificationStats } from '@/services/notificationsApi';
import { useToast } from '@/hooks/useToast';
interface UseNotificationsOptions {
autoFetch?: boolean;
pollingInterval?: number; // en milisegundos, 0 = sin polling
}
interface UseNotificationsReturn {
notifications: Notification[];
unreadCount: number;
stats: NotificationStats | null;
loading: boolean;
error: string | null;
// Actions
fetchNotifications: (type?: NotificationType, isRead?: boolean) => Promise<void>;
markAsRead: (notificationId: string) => Promise<void>;
markAllAsRead: () => Promise<void>;
deleteNotification: (notificationId: string) => Promise<void>;
deleteAllRead: () => Promise<void>;
refresh: () => Promise<void>;
}
export function useNotifications(options: UseNotificationsOptions = {}): UseNotificationsReturn {
const { autoFetch = true, pollingInterval = 0 } = options;
const { toast } = useToast();
const [notifications, setNotifications] = useState<Notification[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
const [stats, setStats] = useState<NotificationStats | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchNotifications = useCallback(async (type?: NotificationType, isRead?: boolean) => {
setLoading(true);
setError(null);
try {
const result = await notificationsApi.getMyNotifications({ type, isRead, limit: 50 });
setNotifications(result.notifications);
setUnreadCount(result.unreadCount);
} catch (err) {
const message = err instanceof Error ? err.message : 'Error al cargar notificaciones';
setError(message);
console.error('Error fetching notifications:', err);
} finally {
setLoading(false);
}
}, []);
const fetchUnreadCount = useCallback(async () => {
try {
const count = await notificationsApi.getUnreadCount();
setUnreadCount(count);
} catch (err) {
console.error('Error fetching unread count:', err);
}
}, []);
const markAsRead = useCallback(async (notificationId: string) => {
try {
await notificationsApi.markAsRead(notificationId);
setNotifications(prev =>
prev.map(n => n.id === notificationId ? { ...n, isRead: true, readAt: new Date().toISOString() } : n)
);
setUnreadCount(prev => Math.max(0, prev - 1));
} catch (err) {
toast({
title: 'Error',
description: 'No se pudo marcar la notificacion como leida',
variant: 'destructive',
});
throw err;
}
}, [toast]);
const markAllAsRead = useCallback(async () => {
try {
const result = await notificationsApi.markAllAsRead();
setNotifications(prev =>
prev.map(n => ({ ...n, isRead: true, readAt: new Date().toISOString() }))
);
setUnreadCount(0);
toast({
title: 'Listo',
description: `${result.count} notificaciones marcadas como leidas`,
});
} catch (err) {
toast({
title: 'Error',
description: 'No se pudieron marcar las notificaciones como leidas',
variant: 'destructive',
});
throw err;
}
}, [toast]);
const deleteNotification = useCallback(async (notificationId: string) => {
try {
const notification = notifications.find(n => n.id === notificationId);
await notificationsApi.deleteNotification(notificationId);
setNotifications(prev => prev.filter(n => n.id !== notificationId));
if (notification && !notification.isRead) {
setUnreadCount(prev => Math.max(0, prev - 1));
}
toast({
title: 'Eliminada',
description: 'Notificacion eliminada correctamente',
});
} catch (err) {
toast({
title: 'Error',
description: 'No se pudo eliminar la notificacion',
variant: 'destructive',
});
throw err;
}
}, [notifications, toast]);
const deleteAllRead = useCallback(async () => {
try {
const result = await notificationsApi.deleteAllRead();
setNotifications(prev => prev.filter(n => !n.isRead));
toast({
title: 'Listo',
description: `${result.count} notificaciones eliminadas`,
});
} catch (err) {
toast({
title: 'Error',
description: 'No se pudieron eliminar las notificaciones',
variant: 'destructive',
});
throw err;
}
}, [toast]);
const refresh = useCallback(async () => {
await fetchNotifications();
}, [fetchNotifications]);
// Auto fetch on mount
useEffect(() => {
if (autoFetch) {
fetchNotifications();
}
}, [autoFetch, fetchNotifications]);
// Polling for unread count
useEffect(() => {
if (pollingInterval > 0) {
const interval = setInterval(fetchUnreadCount, pollingInterval);
return () => clearInterval(interval);
}
}, [pollingInterval, fetchUnreadCount]);
return {
notifications,
unreadCount,
stats,
loading,
error,
fetchNotifications,
markAsRead,
markAllAsRead,
deleteNotification,
deleteAllRead,
refresh,
};
}
export default useNotifications;

211
src/hooks/useQuiz.ts Normal file
View File

@@ -0,0 +1,211 @@
import { useState, useEffect, useCallback } from 'react';
import { quizApi, QuizQuestion, QuizResponse, SubmitQuizDto, QuizAnswer } from '@/services/quizApi';
import { useAuth } from '@/contexts/AuthContext';
import { toast } from 'sonner';
export const useQuiz = () => {
const { user } = useAuth();
const [questions, setQuestions] = useState<QuizQuestion[]>([]);
const [quizResponse, setQuizResponse] = useState<QuizResponse | null>(null);
const [currentAnswers, setCurrentAnswers] = useState<QuizAnswer[]>([]);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
// Cargar preguntas
const loadQuestions = useCallback(async () => {
try {
setLoading(true);
setError(null);
const data = await quizApi.getQuestions();
setQuestions(data);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Error al cargar preguntas';
setError(errorMessage);
console.error('Error loading questions:', err);
} finally {
setLoading(false);
}
}, []);
// Cargar respuesta del usuario
const loadMyResponse = useCallback(async () => {
if (!user?.id) return;
try {
const data = await quizApi.getMyQuizResponse();
setQuizResponse(data);
} catch (err) {
console.error('Error loading quiz response:', err);
}
}, [user?.id]);
// Responder pregunta actual
const answerQuestion = useCallback((questionId: string, selectedOptions: string[]) => {
setCurrentAnswers(prev => {
const existing = prev.findIndex(a => a.questionId === questionId);
if (existing >= 0) {
const updated = [...prev];
updated[existing] = { questionId, selectedOptions };
return updated;
}
return [...prev, { questionId, selectedOptions }];
});
}, []);
// Ir a siguiente pregunta
const nextQuestion = useCallback(() => {
if (currentQuestionIndex < questions.length - 1) {
setCurrentQuestionIndex(prev => prev + 1);
}
}, [currentQuestionIndex, questions.length]);
// Ir a pregunta anterior
const prevQuestion = useCallback(() => {
if (currentQuestionIndex > 0) {
setCurrentQuestionIndex(prev => prev - 1);
}
}, [currentQuestionIndex]);
// Ir a pregunta específica
const goToQuestion = useCallback((index: number) => {
if (index >= 0 && index < questions.length) {
setCurrentQuestionIndex(index);
}
}, [questions.length]);
// Enviar quiz
const submitQuiz = useCallback(async (): Promise<QuizResponse | null> => {
if (!user?.id) {
toast.error('Inicia sesión para completar el quiz');
return null;
}
// Verificar que todas las preguntas estén respondidas
const unanswered = questions.filter(
q => !currentAnswers.find(a => a.questionId === q.id)
);
if (unanswered.length > 0) {
toast.error(`Faltan ${unanswered.length} preguntas por responder`);
return null;
}
try {
setSubmitting(true);
const response = await quizApi.submitQuiz({ answers: currentAnswers });
setQuizResponse(response);
toast.success(`¡Quiz completado! Tu Travel Persona es: ${response.travelPersona}`);
return response;
} catch (err) {
console.error('Error submitting quiz:', err);
toast.error('Error al enviar quiz');
return null;
} finally {
setSubmitting(false);
}
}, [user?.id, questions, currentAnswers]);
// Reiniciar quiz
const resetQuiz = useCallback(async (): Promise<boolean> => {
if (!user?.id) {
toast.error('Inicia sesión para reiniciar el quiz');
return false;
}
try {
await quizApi.resetQuiz();
setQuizResponse(null);
setCurrentAnswers([]);
setCurrentQuestionIndex(0);
toast.success('Quiz reiniciado');
return true;
} catch (err) {
console.error('Error resetting quiz:', err);
toast.error('Error al reiniciar quiz');
return false;
}
}, [user?.id]);
// Verificar si el quiz está completado
const isCompleted = useCallback((): boolean => {
return quizResponse?.isCompleted ?? false;
}, [quizResponse]);
// Obtener la Travel Persona
const getTravelPersona = useCallback(() => {
if (quizResponse?.isCompleted && quizResponse.travelPersona) {
return {
persona: quizResponse.travelPersona,
description: quizResponse.personaDescription,
};
}
return null;
}, [quizResponse]);
// Obtener respuesta de una pregunta
const getAnswer = useCallback((questionId: string): string[] | undefined => {
return currentAnswers.find(a => a.questionId === questionId)?.selectedOptions;
}, [currentAnswers]);
// Verificar si una pregunta está respondida
const isAnswered = useCallback((questionId: string): boolean => {
return currentAnswers.some(a => a.questionId === questionId && a.selectedOptions.length > 0);
}, [currentAnswers]);
// Obtener progreso del quiz
const getProgress = useCallback(() => {
const answered = currentAnswers.filter(a => a.selectedOptions.length > 0).length;
return {
answered,
total: questions.length,
percentage: questions.length > 0 ? Math.round((answered / questions.length) * 100) : 0,
};
}, [currentAnswers, questions.length]);
// Limpiar error
const clearError = useCallback(() => {
setError(null);
}, []);
// Pregunta actual
const currentQuestion = questions[currentQuestionIndex] || null;
// Carga inicial
useEffect(() => {
loadQuestions();
if (user?.id) {
loadMyResponse();
}
}, [loadQuestions, user?.id, loadMyResponse]);
return {
questions,
quizResponse,
currentQuestion,
currentQuestionIndex,
currentAnswers,
loading,
submitting,
error,
loadQuestions,
loadMyResponse,
answerQuestion,
nextQuestion,
prevQuestion,
goToQuestion,
submitQuiz,
resetQuiz,
isCompleted,
getTravelPersona,
getAnswer,
isAnswered,
getProgress,
clearError,
isFirstQuestion: currentQuestionIndex === 0,
isLastQuestion: currentQuestionIndex === questions.length - 1,
};
};
export default useQuiz;

289
src/hooks/useTrips.ts Normal file
View File

@@ -0,0 +1,289 @@
import { useState, useEffect, useCallback } from 'react';
import { tripsApi, Trip, TripStatus, TripStats, TripDay, TripActivity, CreateTripDto, UpdateTripDto, CreateTripDayDto, UpdateTripDayDto, CreateTripActivityDto, UpdateTripActivityDto } from '@/services/tripsApi';
import { useAuth } from '@/contexts/AuthContext';
import { toast } from 'sonner';
export const useTrips = (initialStatus?: TripStatus) => {
const { user } = useAuth();
const [trips, setTrips] = useState<Trip[]>([]);
const [stats, setStats] = useState<TripStats | null>(null);
const [selectedTrip, setSelectedTrip] = useState<Trip | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [selectedStatus, setSelectedStatus] = useState<TripStatus | undefined>(initialStatus);
// Cargar viajes
const loadTrips = useCallback(async (status?: TripStatus) => {
if (!user?.id) return;
try {
setLoading(true);
setError(null);
const data = await tripsApi.getMyTrips(status || selectedStatus);
setTrips(data);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Error al cargar viajes';
setError(errorMessage);
console.error('Error loading trips:', err);
} finally {
setLoading(false);
}
}, [user?.id, selectedStatus]);
// Cargar estadísticas
const loadStats = useCallback(async () => {
if (!user?.id) return;
try {
const data = await tripsApi.getTripsStats();
setStats(data);
} catch (err) {
console.error('Error loading trips stats:', err);
}
}, [user?.id]);
// Crear viaje
const createTrip = useCallback(async (data: CreateTripDto): Promise<Trip | null> => {
if (!user?.id) {
toast.error('Inicia sesión para crear viajes');
return null;
}
try {
const newTrip = await tripsApi.createTrip(data);
toast.success('Viaje creado');
await loadTrips();
await loadStats();
return newTrip;
} catch (err) {
console.error('Error creating trip:', err);
toast.error('Error al crear viaje');
return null;
}
}, [user?.id, loadTrips, loadStats]);
// Obtener viaje por ID
const getTripById = useCallback(async (id: string): Promise<Trip | null> => {
try {
const trip = await tripsApi.getTripById(id);
setSelectedTrip(trip);
return trip;
} catch (err) {
console.error('Error fetching trip:', err);
return null;
}
}, []);
// Actualizar viaje
const updateTrip = useCallback(async (id: string, data: UpdateTripDto): Promise<boolean> => {
if (!user?.id) {
toast.error('Inicia sesión para actualizar viajes');
return false;
}
try {
const updated = await tripsApi.updateTrip(id, data);
toast.success('Viaje actualizado');
if (selectedTrip?.id === id) {
setSelectedTrip(updated);
}
await loadTrips();
return true;
} catch (err) {
console.error('Error updating trip:', err);
toast.error('Error al actualizar viaje');
return false;
}
}, [user?.id, selectedTrip?.id, loadTrips]);
// Eliminar viaje
const deleteTrip = useCallback(async (id: string): Promise<boolean> => {
if (!user?.id) {
toast.error('Inicia sesión para eliminar viajes');
return false;
}
try {
await tripsApi.deleteTrip(id);
toast.success('Viaje eliminado');
setTrips(prev => prev.filter(t => t.id !== id));
if (selectedTrip?.id === id) {
setSelectedTrip(null);
}
await loadStats();
return true;
} catch (err) {
console.error('Error deleting trip:', err);
toast.error('Error al eliminar viaje');
return false;
}
}, [user?.id, selectedTrip?.id, loadStats]);
// ============ DAYS ============
// Agregar día
const addDay = useCallback(async (tripId: string, data: CreateTripDayDto): Promise<TripDay | null> => {
try {
const newDay = await tripsApi.addDay(tripId, data);
toast.success('Día agregado');
if (selectedTrip?.id === tripId) {
await getTripById(tripId);
}
return newDay;
} catch (err) {
console.error('Error adding day:', err);
toast.error('Error al agregar día');
return null;
}
}, [selectedTrip?.id, getTripById]);
// Actualizar día
const updateDay = useCallback(async (tripId: string, dayId: string, data: UpdateTripDayDto): Promise<boolean> => {
try {
await tripsApi.updateDay(tripId, dayId, data);
toast.success('Día actualizado');
if (selectedTrip?.id === tripId) {
await getTripById(tripId);
}
return true;
} catch (err) {
console.error('Error updating day:', err);
toast.error('Error al actualizar día');
return false;
}
}, [selectedTrip?.id, getTripById]);
// Eliminar día
const deleteDay = useCallback(async (tripId: string, dayId: string): Promise<boolean> => {
try {
await tripsApi.deleteDay(tripId, dayId);
toast.success('Día eliminado');
if (selectedTrip?.id === tripId) {
await getTripById(tripId);
}
return true;
} catch (err) {
console.error('Error deleting day:', err);
toast.error('Error al eliminar día');
return false;
}
}, [selectedTrip?.id, getTripById]);
// ============ ACTIVITIES ============
// Agregar actividad
const addActivity = useCallback(async (tripId: string, dayId: string, data: CreateTripActivityDto): Promise<TripActivity | null> => {
try {
const newActivity = await tripsApi.addActivity(tripId, dayId, data);
toast.success('Actividad agregada');
if (selectedTrip?.id === tripId) {
await getTripById(tripId);
}
return newActivity;
} catch (err) {
console.error('Error adding activity:', err);
toast.error('Error al agregar actividad');
return null;
}
}, [selectedTrip?.id, getTripById]);
// Actualizar actividad
const updateActivity = useCallback(async (tripId: string, dayId: string, activityId: string, data: UpdateTripActivityDto): Promise<boolean> => {
try {
await tripsApi.updateActivity(tripId, dayId, activityId, data);
toast.success('Actividad actualizada');
if (selectedTrip?.id === tripId) {
await getTripById(tripId);
}
return true;
} catch (err) {
console.error('Error updating activity:', err);
toast.error('Error al actualizar actividad');
return false;
}
}, [selectedTrip?.id, getTripById]);
// Eliminar actividad
const deleteActivity = useCallback(async (tripId: string, dayId: string, activityId: string): Promise<boolean> => {
try {
await tripsApi.deleteActivity(tripId, dayId, activityId);
toast.success('Actividad eliminada');
if (selectedTrip?.id === tripId) {
await getTripById(tripId);
}
return true;
} catch (err) {
console.error('Error deleting activity:', err);
toast.error('Error al eliminar actividad');
return false;
}
}, [selectedTrip?.id, getTripById]);
// Reordenar actividades
const reorderActivities = useCallback(async (tripId: string, dayId: string, activityIds: string[]): Promise<boolean> => {
try {
await tripsApi.reorderActivities(tripId, dayId, activityIds);
if (selectedTrip?.id === tripId) {
await getTripById(tripId);
}
return true;
} catch (err) {
console.error('Error reordering activities:', err);
toast.error('Error al reordenar actividades');
return false;
}
}, [selectedTrip?.id, getTripById]);
// Cambiar estado seleccionado
const changeStatus = useCallback((status?: TripStatus) => {
setSelectedStatus(status);
loadTrips(status);
}, [loadTrips]);
// Filtrar por estado
const filterByStatus = useCallback((status: TripStatus): Trip[] => {
return trips.filter(t => t.status === status);
}, [trips]);
// Limpiar error
const clearError = useCallback(() => {
setError(null);
}, []);
// Carga inicial
useEffect(() => {
if (user?.id) {
loadTrips();
loadStats();
}
}, [user?.id, loadTrips, loadStats]);
return {
trips,
stats,
selectedTrip,
loading,
error,
selectedStatus,
loadTrips,
loadStats,
createTrip,
getTripById,
updateTrip,
deleteTrip,
addDay,
updateDay,
deleteDay,
addActivity,
updateActivity,
deleteActivity,
reorderActivities,
changeStatus,
filterByStatus,
setSelectedTrip,
clearError,
getTripsCount: () => trips.length,
};
};
export default useTrips;