import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { BehaviorSubject, Observable, of } from 'rxjs'; import { catchError, map, tap } from 'rxjs/operators'; import { User } from '../models/user.model'; import { environment } from '../../environments/environment'; import { StorageService } from './storage.service'; // Clave para almacenar información de sesión const AUTH_TOKEN_KEY = 'auth_token'; const USER_KEY = 'user_data'; // Interfaz para credenciales de login interface LoginCredentials { email: string; password: string; } // Interfaz para datos de registro interface RegisterData { name: string; email: string; password: string; } @Injectable({ providedIn: 'root' }) export class AuthService { private apiUrl = `${environment.apiUrl}/api/users`; private authUrl = `${environment.apiUrl}/api/auth`; private currentUserSubject = new BehaviorSubject(null); public currentUser$ = this.currentUserSubject.asObservable(); private isInitialized = false; constructor( private http: HttpClient, private storageService: StorageService ) { // Intentar recuperar sesión anterior this.initializeSession(); } /** * Inicializa la sesión si hay datos guardados */ async initializeSession() { if (this.isInitialized) return; try { const userData = await this.storageService.get(USER_KEY) as User | null; if (userData) { console.log('Sesión recuperada del almacenamiento local:', userData); // Normalizar campos para compatibilidad this.normalizeUserData(userData); this.currentUserSubject.next(userData); } else { console.log('No hay sesión guardada, redirigiendo a login'); this.currentUserSubject.next(null); } } catch (error) { console.error('Error al inicializar sesión:', error); this.currentUserSubject.next(null); } this.isInitialized = true; } /** * Espera a que se complete la inicialización * Importante para evitar redirecciones erróneas cuando la app inicia */ async waitForInitialization(): Promise { if (this.isInitialized) return; // Si no se ha inicializado, iniciamos el proceso await this.initializeSession(); // Esperar hasta que se inicialice (con timeout) let attempts = 0; while (!this.isInitialized && attempts < 10) { await new Promise(resolve => setTimeout(resolve, 100)); attempts++; } } /** * Normaliza los datos de usuario para compatibilidad entre frontend/backend */ private normalizeUserData(user: User) { // Sincronizar campos de imagen if (user.profilePicUrl && !user.profilePic) { user.profilePic = user.profilePicUrl; } else if (user.profilePic && !user.profilePicUrl) { user.profilePicUrl = user.profilePic; } // Sincronizar preferencias de notificaciones if (user.notificationsEnabled !== undefined && !user.preferences) { user.preferences = { notifications: user.notificationsEnabled, favoriteClasses: [] }; } else if (user.preferences?.notifications !== undefined && user.notificationsEnabled === undefined) { user.notificationsEnabled = user.preferences.notifications; } } getCurrentUser(): User | null { return this.currentUserSubject.value; } /** * Iniciar sesión con email y contraseña */ login(credentials: LoginCredentials): Observable { console.log('Intentando login con:', credentials); return this.http.post(`${this.authUrl}/login`, credentials).pipe( tap(async (response) => { console.log('Respuesta de login:', response); // Guardar token si el backend lo devuelve if (response.token) { await this.storageService.set(AUTH_TOKEN_KEY, response.token); } // Verificar y normalizar datos del usuario const user = response.user || response; this.normalizeUserData(user); // Guardar datos del usuario await this.storageService.set(USER_KEY, user); // Actualizar estado de sesión this.currentUserSubject.next(user); }), map(response => response.user || response) ); } /** * Registrar un nuevo usuario */ register(userData: RegisterData): Observable { console.log('Registrando usuario:', userData); const registerData = { name: userData.name, email: userData.email, password: userData.password }; return this.http.post(`${this.authUrl}/register`, registerData).pipe( tap(async (response) => { console.log('Respuesta de registro:', response); // Guardar token si el backend lo devuelve if (response.token) { await this.storageService.set(AUTH_TOKEN_KEY, response.token); } // Verificar y normalizar datos del usuario const user = response.user || response; this.normalizeUserData(user); // Guardar datos del usuario await this.storageService.set(USER_KEY, user); // Actualizar estado de sesión this.currentUserSubject.next(user); }), map(response => response.user || response) ); } /** * Método para simular login/registro en una app de demo */ simulateAuth(userData: User): Observable { return of(userData).pipe( tap(async (user) => { this.normalizeUserData(user); await this.storageService.set(USER_KEY, user); this.currentUserSubject.next(user); }) ); } updateUserProfile(userData: Partial): Observable { const currentUser = this.getCurrentUser(); if (!currentUser) { return of(currentUser as unknown as User); } console.log('Actualizando perfil de usuario con:', userData); // Asegurar que ambos campos de imagen estén sincronizados if (userData.profilePic && !userData.profilePicUrl) { userData.profilePicUrl = userData.profilePic; } else if (userData.profilePicUrl && !userData.profilePic) { userData.profilePic = userData.profilePicUrl; } // Sincronizar notificaciones entre los dos formatos if (userData.preferences?.notifications !== undefined && userData.notificationsEnabled === undefined) { userData.notificationsEnabled = userData.preferences.notifications; } else if (userData.notificationsEnabled !== undefined && (!userData.preferences || userData.preferences.notifications === undefined)) { if (!userData.preferences) userData.preferences = { notifications: false, favoriteClasses: [] }; userData.preferences.notifications = userData.notificationsEnabled; } // Solo enviamos al servidor los campos que espera const backendUserData = { name: userData.name, profilePicUrl: userData.profilePicUrl, notificationsEnabled: userData.notificationsEnabled || (userData.preferences ? userData.preferences.notifications : undefined) }; return this.http.put(`${this.apiUrl}/${currentUser.id}`, backendUserData).pipe( tap(async (user) => { console.log('Usuario actualizado correctamente:', user); // Sincronizar campos para mantener compatibilidad if (user.profilePicUrl && !user.profilePic) { user.profilePic = user.profilePicUrl; } else if (user.profilePic && !user.profilePicUrl) { user.profilePicUrl = user.profilePic; } // Mantener los campos que espera el frontend if (user.notificationsEnabled !== undefined && !user.preferences) { user.preferences = { notifications: user.notificationsEnabled, favoriteClasses: currentUser.preferences?.favoriteClasses || [] }; } // Actualizar almacenamiento local await this.storageService.set(USER_KEY, user); this.currentUserSubject.next(user); }), catchError(error => { console.error('Error al actualizar usuario:', error); // Devolver el usuario actualizado localmente para que la UI no se rompa // en caso de error de red const updatedUser = { ...currentUser, ...userData }; this.currentUserSubject.next(updatedUser); return of(updatedUser); }) ); } /** * Cerrar sesión y eliminar datos */ async logout(): Promise { try { // En una aplicación real, enviaríamos petición al backend // this.http.post(`${this.authUrl}/logout`, {}).subscribe(); // Limpiar datos de sesión await this.storageService.clear(); // Limpiamos TODA la caché de preferencias // Actualizar estado this.currentUserSubject.next(null); this.isInitialized = false; // Permitir nueva inicialización return true; } catch (error) { console.error('Error al cerrar sesión:', error); return false; } } /** * Verificar si hay sesión activa */ isLoggedIn(): boolean { return !!this.currentUserSubject.value; } }