import 'dart:convert'; import 'package:sysfarma_mobile/src/core/models/tenant_session.dart'; import 'package:sysfarma_mobile/src/core/observability/observability.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; final secureStorageProvider = Provider((ref) => const FlutterSecureStorage()); final tenantSessionStoreProvider = Provider( (ref) => TenantSessionStore(ref.watch(secureStorageProvider)), ); /// Emite cambios de sesión activa. Cualquier screen puede watch-earlo /// para reaccionar a login/logout/cambio de rol. final currentSessionProvider = FutureProvider((ref) async { final store = ref.watch(tenantSessionStoreProvider); return store.readSession(); }); class TenantSessionStore { const TenantSessionStore(this._storage); static const _baseUrlKey = 'tenant.base_url'; static const _tokenKey = 'tenant.api_token'; static const _companyNameKey = 'tenant.company_name'; static const _rucKey = 'tenant.ruc'; static const _userTypeKey = 'tenant.user_type'; static const _activeEstablishmentIdKey = 'tenant.active_establishment_id'; static const _moduleValuesKey = 'tenant.module_values'; static const _annularPurchaseKey = 'tenant.annular_purchase'; static const _editPurchaseKey = 'tenant.edit_purchase'; static const _deletePurchaseKey = 'tenant.delete_purchase'; static const _annularSaleKey = 'tenant.annular_sale'; static const _editPaymentMethodKey = 'tenant.edit_payment_method'; static const _yapeInboxEnabledKey = 'tenant.yape_inbox_enabled'; static const _yapeListenerEnabledKey = 'tenant.yape_listener_enabled'; final FlutterSecureStorage _storage; Future saveSession(TenantSession session) async { await _storage.write(key: _baseUrlKey, value: session.baseUrl); await _storage.write(key: _tokenKey, value: session.token); await _storage.write(key: _companyNameKey, value: session.companyName ?? ''); await _storage.write(key: _rucKey, value: session.ruc ?? ''); await _storage.write(key: _userTypeKey, value: session.userType ?? ''); await _storage.write( key: _activeEstablishmentIdKey, value: session.activeEstablishmentId?.toString() ?? '', ); await _storage.write( key: _moduleValuesKey, value: session.moduleValues != null ? jsonEncode(session.moduleValues) : '', ); await _storage.write(key: _annularPurchaseKey, value: session.annularPurchase.toString()); await _storage.write(key: _editPurchaseKey, value: session.editPurchase.toString()); await _storage.write(key: _deletePurchaseKey, value: session.deletePurchase.toString()); await _storage.write(key: _annularSaleKey, value: session.annularSale.toString()); await _storage.write(key: _editPaymentMethodKey, value: session.editPaymentMethod.toString()); await _storage.write(key: _yapeInboxEnabledKey, value: session.yapeInboxEnabled.toString()); await _storage.write(key: _yapeListenerEnabledKey, value: session.yapeListenerEnabled.toString()); await Observability.setSessionContext(session); } Future readSession() async { final baseUrl = await _storage.read(key: _baseUrlKey); final token = await _storage.read(key: _tokenKey); if (baseUrl == null || token == null || baseUrl.isEmpty || token.isEmpty) { const testMode = bool.fromEnvironment('MOBILE_WEB_TEST_MODE', defaultValue: false); if (kIsWeb && testMode) { return const TenantSession( baseUrl: 'http://demo.enterfarmaplus.test', token: 'mock-token', companyName: 'Demo', ruc: '00000000000', userType: 'admin', ); } return null; } final companyName = await _storage.read(key: _companyNameKey); final ruc = await _storage.read(key: _rucKey); final userType = await _storage.read(key: _userTypeKey); final activeEstablishmentIdRaw = await _storage.read(key: _activeEstablishmentIdKey); final activeEstablishmentId = int.tryParse(activeEstablishmentIdRaw ?? ''); final moduleValuesRaw = await _storage.read(key: _moduleValuesKey); List? moduleValues; if (moduleValuesRaw != null && moduleValuesRaw.isNotEmpty) { try { moduleValues = List.from(jsonDecode(moduleValuesRaw) as List); } catch (_) { moduleValues = null; } } return TenantSession( baseUrl: baseUrl, token: token, companyName: companyName, ruc: (ruc != null && ruc.isNotEmpty) ? ruc : null, userType: (userType != null && userType.isNotEmpty) ? userType : null, activeEstablishmentId: activeEstablishmentId, moduleValues: moduleValues, annularPurchase: await _readBool(_annularPurchaseKey, defaultValue: true), editPurchase: await _readBool(_editPurchaseKey, defaultValue: true), deletePurchase: await _readBool(_deletePurchaseKey, defaultValue: true), annularSale: await _readBool(_annularSaleKey, defaultValue: true), editPaymentMethod: await _readBool(_editPaymentMethodKey, defaultValue: false), yapeInboxEnabled: await _readBool(_yapeInboxEnabledKey, defaultValue: false), yapeListenerEnabled: await _readBool(_yapeListenerEnabledKey, defaultValue: false), ); } Future _readBool(String key, {required bool defaultValue}) async { final raw = await _storage.read(key: key); if (raw == null || raw.isEmpty) return defaultValue; return raw == 'true'; } Future readRuc() async { final ruc = await _storage.read(key: _rucKey); return (ruc != null && ruc.isNotEmpty) ? ruc : null; } /// Lee los datos de tenant (RUC, baseUrl, companyName) aunque no exista /// token activo. Útil para prellenar login después de logout. Future<({String? ruc, String? baseUrl, String? companyName})> readTenantConfig() async { final ruc = await _storage.read(key: _rucKey); final baseUrl = await _storage.read(key: _baseUrlKey); final companyName = await _storage.read(key: _companyNameKey); return ( ruc: (ruc != null && ruc.isNotEmpty) ? ruc : null, baseUrl: (baseUrl != null && baseUrl.isNotEmpty) ? baseUrl : null, companyName: (companyName != null && companyName.isNotEmpty) ? companyName : null, ); } /// Logout "suave": borra solo credenciales, conserva tenant (RUC, baseUrl, /// companyName) para que la próxima pantalla de login ya venga prellenada. Future clear() async { await _storage.delete(key: _tokenKey); await _storage.delete(key: _userTypeKey); await _storage.delete(key: _activeEstablishmentIdKey); await _storage.delete(key: _moduleValuesKey); await _storage.delete(key: _annularPurchaseKey); await _storage.delete(key: _editPurchaseKey); await _storage.delete(key: _deletePurchaseKey); await _storage.delete(key: _annularSaleKey); await _storage.delete(key: _editPaymentMethodKey); await _storage.delete(key: _yapeInboxEnabledKey); await _storage.delete(key: _yapeListenerEnabledKey); await Observability.clearSessionContext(); } /// Logout "duro": borra todo, incluyendo el tenant. Útil para cambiar de /// empresa. Future clearAll() async { await _storage.delete(key: _baseUrlKey); await _storage.delete(key: _tokenKey); await _storage.delete(key: _companyNameKey); await _storage.delete(key: _rucKey); await _storage.delete(key: _userTypeKey); await _storage.delete(key: _activeEstablishmentIdKey); await _storage.delete(key: _moduleValuesKey); await _storage.delete(key: _annularPurchaseKey); await _storage.delete(key: _editPurchaseKey); await _storage.delete(key: _deletePurchaseKey); await _storage.delete(key: _annularSaleKey); await _storage.delete(key: _editPaymentMethodKey); await _storage.delete(key: _yapeInboxEnabledKey); await _storage.delete(key: _yapeListenerEnabledKey); await Observability.clearSessionContext(); } }