import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable, BehaviorSubject, tap, catchError, throwError } from 'rxjs'; import { Router } from '@angular/router'; import { JobResponse } from './job-types'; import { API_BASE_URL } from '../config'; export interface User { id_usuario: number; username: string; nombre_completo: string; nombre_rol: string; sedes?: Array<{ codigo_establecimiento: string; nombre_establecimiento: string; }>; } export interface LoginRequest { username: string; password: string; } export interface LoginResponse { token: string; usuario: User; } export interface RegisterRequest { username: string; email: string; password: string; nombre_completo: string; id_rol: number; sedes?: string[]; } export interface ActualizarUsuarioPayload { username?: string; password?: string; email?: string; nombre_completo?: string; id_rol?: number; } /* Interfaz Rol para la pantalla de usuarios */ export interface Rol { id_rol: number; nombre_rol: string; descripcion?: string | null; } export interface EstadisticasAutorizacionesDia { fecha: string; total: number; autorizadas: number; no_autorizadas: number; pendientes: number; } export interface EstadisticasAutorizaciones { rango: { inicio: string; fin: string }; resumen: { autorizadas: number; no_autorizadas: number; pendientes: number; total: number; }; dias: EstadisticasAutorizacionesDia[]; } @Injectable({ providedIn: 'root' }) export class AuthService { private readonly API_URL = API_BASE_URL; private readonly TOKEN_KEY = 'auth_token'; private readonly USER_KEY = 'current_user'; private currentUserSubject = new BehaviorSubject(null); public currentUser$ = this.currentUserSubject.asObservable(); private isAuthenticatedSubject = new BehaviorSubject(false); public isAuthenticated$ = this.isAuthenticatedSubject.asObservable(); private logoutTimeoutId: ReturnType | null = null; constructor( private http: HttpClient, private router: Router ) { this.initializeAuth(); } private initializeAuth(): void { const token = this.getToken(); const user = this.getUserFromStorage(); if (token && user) { this.currentUserSubject.next(user); this.isAuthenticatedSubject.next(true); this.scheduleTokenExpiry(token); } else { this.clearAuth(); } } // =========== LOGIN =========== login(credentials: LoginRequest): Observable { return this.http.post(`${this.API_URL}/auth/login`, credentials).pipe( tap(response => { this.setAuth(response.token, response.usuario); }), catchError((error: any) => { console.error('Error en login:', error); return throwError(() => error); }) ); } // =========== REGISTER (SOLO ADMIN) =========== register(userData: RegisterRequest): Observable { const headers = this.getAuthHeaders(); return this.http.post(`${this.API_URL}/auth/register`, userData, { headers }).pipe( catchError((error: any) => { console.error('Error en register:', error); return throwError(() => error); }) ); } // =========== LOGOUT =========== logout(): void { this.clearAuth(); this.router.navigate(['/login']); } // =========== VERIFY TOKEN =========== verifyToken(): Observable<{ usuario: User }> { const headers = this.getAuthHeaders(); return this.http.get<{ usuario: User }>(`${this.API_URL}/auth/verify`, { headers }).pipe( tap(response => { this.updateUser(response.usuario); }) ); } // =========== HELPERS DE AUTH =========== getToken(): string | null { return localStorage.getItem(this.TOKEN_KEY); } private setAuth(token: string, user: User): void { localStorage.setItem(this.TOKEN_KEY, token); localStorage.setItem(this.USER_KEY, JSON.stringify(user)); this.currentUserSubject.next(user); this.isAuthenticatedSubject.next(true); this.scheduleTokenExpiry(token); } private clearAuth(): void { this.clearLogoutTimer(); localStorage.removeItem(this.TOKEN_KEY); localStorage.removeItem(this.USER_KEY); this.currentUserSubject.next(null); this.isAuthenticatedSubject.next(false); } private scheduleTokenExpiry(token: string): void { const exp = this.getTokenExpiry(token); if (!exp) { return; } const timeoutMs = exp - Date.now(); if (timeoutMs <= 0) { this.logout(); return; } this.clearLogoutTimer(); this.logoutTimeoutId = setTimeout(() => { this.logout(); }, timeoutMs); } private clearLogoutTimer(): void { if (this.logoutTimeoutId) { clearTimeout(this.logoutTimeoutId); this.logoutTimeoutId = null; } } private getTokenExpiry(token: string): number | null { try { const payload = token.split('.')[1]; if (!payload) return null; const base64 = payload.replace(/-/g, '+').replace(/_/g, '/'); const padded = base64.padEnd(base64.length + (4 - (base64.length % 4)) % 4, '='); const decoded = JSON.parse(atob(padded)); if (!decoded?.exp) return null; return decoded.exp * 1000; } catch { return null; } } private updateUser(user: User): void { localStorage.setItem(this.USER_KEY, JSON.stringify(user)); this.currentUserSubject.next(user); } private getUserFromStorage(): User | null { const userStr = localStorage.getItem(this.USER_KEY); return userStr ? JSON.parse(userStr) : null; } getAuthHeaders(): HttpHeaders { const token = this.getToken(); return new HttpHeaders({ 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }); } getCurrentUser(): User | null { return this.currentUserSubject.value; } isLoggedIn(): boolean { return this.isAuthenticatedSubject.value; } hasRole(role: string): boolean { const user = this.getCurrentUser(); return user ? user.nombre_rol === role : false; } isAdministrador(): boolean { return this.hasRole('administrador'); } isAdministrativoSede(): boolean { return this.hasRole('administrativo_sede'); } puedeCargarPacientes(): boolean { return this.isAdministrador(); } puedeDescargarPdfs(): boolean { const user = this.getCurrentUser(); return !!user && (user.nombre_rol === 'administrador' || user.nombre_rol === 'administrativo_sede'); } puedeVerTodasAutorizaciones(): boolean { return this.isAdministrador(); } puedeGenerarAutorizaciones(): boolean { const user = this.getCurrentUser(); return !!user && (user.nombre_rol === 'administrador' || user.nombre_rol === 'administrativo_sede'); } tieneAccesoAEstablecimiento(codigoEstablecimiento: string): boolean { const user = this.getCurrentUser(); if (!user) return false; if (user.nombre_rol === 'administrador') return true; if (user.nombre_rol === 'administrativo_sede' && user.sedes) { return user.sedes.some(sede => sede.codigo_establecimiento === codigoEstablecimiento); } return false; } // =========== ADMIN: USUARIOS Y REPORTES =========== getUsuarios(limit = 200, offset = 0): Observable { const headers = this.getAuthHeaders(); const params = { limit, offset }; return this.http.get(`${this.API_URL}/usuarios`, { headers, params }); } cambiarEstadoUsuario(idUsuario: number, activo: boolean): Observable { const headers = this.getAuthHeaders(); return this.http.patch( `${this.API_URL}/usuarios/${idUsuario}/estado`, { activo }, { headers } ); } actualizarUsuario(idUsuario: number, payload: ActualizarUsuarioPayload): Observable { const headers = this.getAuthHeaders(); return this.http.patch( `${this.API_URL}/usuarios/${idUsuario}`, payload, { headers } ); } // Alias usado en usuarios.ts actualizarEstadoUsuario(idUsuario: number, activo: boolean): Observable { return this.cambiarEstadoUsuario(idUsuario, activo); } // Obtener autorizaciones por fecha (solo administradores) getAutorizacionesPorFecha( fechaInicio: string, fechaFin: string, limit = 100000, offset = 0, establecimiento?: string, ambito?: string, ips?: string ): Observable { const headers = this.getAuthHeaders(); const params: any = { fecha_inicio: fechaInicio, fecha_fin: fechaFin, limit, offset }; if (establecimiento) { params.establecimiento = establecimiento; } if (ambito) { params.ambito = ambito; } if (ips) { params.ips = ips; } return this.http.get(`${this.API_URL}/autorizaciones-por-fecha`, { headers, params }); } // Obtener roles desde el backend getRoles(): Observable { const headers = this.getAuthHeaders(); return this.http.get(`${this.API_URL}/roles`, { headers }); } getEstadisticasAutorizaciones( fechaInicio?: string, fechaFin?: string ): Observable { const headers = this.getAuthHeaders(); const params: any = {}; if (fechaInicio) params.fecha_inicio = fechaInicio; if (fechaFin) params.fecha_fin = fechaFin; return this.http.get( `${this.API_URL}/autorizaciones-estadisticas`, { headers, params } ); } crearJobPdfAutorizacion( numeroAutorizacion: string, version?: number ): Observable { const headers = this.getAuthHeaders(); const payload: any = { numero_autorizacion: numeroAutorizacion }; if (typeof version === 'number') { payload.version = version; } return this.http.post( `${this.API_URL}/jobs/autorizacion-pdf`, payload, { headers } ); } crearJobZipAutorizaciones( fechaInicio: string, fechaFin: string, establecimiento?: string, ambito?: string, ips?: string ): Observable { const headers = this.getAuthHeaders(); const payload: any = { fecha_inicio: fechaInicio, fecha_fin: fechaFin }; if (establecimiento) { payload.establecimiento = establecimiento; } if (ambito) { payload.ambito = ambito; } if (ips) { payload.ips = ips; } return this.http.post( `${this.API_URL}/jobs/autorizaciones-zip`, payload, { headers } ); } }