import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import '../app_config.dart'; import '../models/auth_session.dart'; abstract class AuthRepository { Future restoreSession(); Future signIn({required String email, required String password}); Future signUp({required String name, required String email, required String password}); Future signOut(); } class HttpAuthRepository implements AuthRepository { const HttpAuthRepository({http.Client? client}) : _client = client; final http.Client? _client; static const String _tokenKey = 'auth_token'; static const String _emailKey = 'auth_email'; static const String _nameKey = 'auth_name'; @override Future restoreSession() async { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString(_tokenKey); final email = prefs.getString(_emailKey); final name = prefs.getString(_nameKey); if (token == null || email == null) { return null; } return AuthSession(token: token, email: email, displayName: name ?? email); } @override Future signIn({required String email, required String password}) { return _authenticate( endpoint: '/auth/login', body: {'email': email, 'password': password}, ); } @override Future signUp({required String name, required String email, required String password}) { return _authenticate( endpoint: '/auth/register', body: {'name': name, 'email': email, 'password': password}, ); } Future _authenticate({required String endpoint, required Map body}) async { final uri = Uri.parse('${AppConfig.apiBaseUrl}$endpoint'); late final http.Response response; try { response = await (_client ?? http.Client()).post( uri, headers: const { 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: jsonEncode(body), ); } catch (_) { throw AuthException( 'No se pudo conectar con el backend en ${AppConfig.apiBaseUrl}. Verifica que el servicio esté activo.', ); } final Map payload = _decodeJson(response.body); if (response.statusCode < 200 || response.statusCode >= 300) { final message = payload['message']?.toString() ?? 'Credenciales inválidas o usuario no disponible.'; throw AuthException(message); } final token = payload['token']?.toString(); if (token == null || token.isEmpty) { throw AuthException('El backend respondió sin token de sesión.'); } final user = payload['user'] is Map ? payload['user'] as Map : {}; final emailValue = (user['email'] ?? body['email'])?.toString() ?? body['email'].toString(); final displayName = (user['name'] ?? user['fullName'] ?? body['name'] ?? emailValue).toString(); final session = AuthSession(token: token, email: emailValue, displayName: displayName); await _persistSession(session); return session; } Future _persistSession(AuthSession session) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_tokenKey, session.token); await prefs.setString(_emailKey, session.email); await prefs.setString(_nameKey, session.displayName); } Map _decodeJson(String responseBody) { if (responseBody.trim().isEmpty) { return {}; } final decoded = jsonDecode(responseBody); if (decoded is Map) { return decoded; } return {}; } @override Future signOut() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_tokenKey); await prefs.remove(_emailKey); await prefs.remove(_nameKey); } } class AuthException implements Exception { AuthException(this.message); final String message; @override String toString() => message; }