import 'dart:convert'; import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:pluto_grid/pluto_grid.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:nethive_neo/helpers/globals.dart'; import 'package:nethive_neo/models/nethive/empresa_model.dart'; import 'package:nethive_neo/models/nethive/negocio_model.dart'; class EmpresasNegociosProvider extends ChangeNotifier { // State managers para las grillas PlutoGridStateManager? empresasStateManager; PlutoGridStateManager? negociosStateManager; // Controladores de búsqueda final busquedaEmpresaController = TextEditingController(); final busquedaNegocioController = TextEditingController(); // Listas de datos List empresas = []; List negocios = []; List empresasRows = []; List negociosRows = []; // Variables para formularios String? logoFileName; String? imagenFileName; Uint8List? logoToUpload; Uint8List? imagenToUpload; // Variables de selección String? empresaSeleccionadaId; Empresa? empresaSeleccionada; // Variable para controlar si el provider está activo bool _isDisposed = false; EmpresasNegociosProvider() { getEmpresas(); } @override void dispose() { _isDisposed = true; busquedaEmpresaController.dispose(); busquedaNegocioController.dispose(); super.dispose(); } // Método seguro para notificar listeners void _safeNotifyListeners() { if (!_isDisposed) { notifyListeners(); } } // Métodos para empresas Future getEmpresas([String? busqueda]) async { try { var query = supabaseLU.from('empresa').select(); if (busqueda != null && busqueda.isNotEmpty) { query = query.or( 'nombre.ilike.%$busqueda%,rfc.ilike.%$busqueda%,email.ilike.%$busqueda%'); } final res = await query.order('fecha_creacion', ascending: false); empresas = (res as List) .map((empresa) => Empresa.fromMap(empresa)) .toList(); _buildEmpresasRows(); _safeNotifyListeners(); } catch (e) { print('Error en getEmpresas: ${e.toString()}'); } } void _buildEmpresasRows() { empresasRows.clear(); for (Empresa empresa in empresas) { empresasRows.add(PlutoRow(cells: { 'id': PlutoCell(value: empresa.id), 'nombre': PlutoCell(value: empresa.nombre), 'rfc': PlutoCell(value: empresa.rfc), 'direccion': PlutoCell(value: empresa.direccion), 'telefono': PlutoCell(value: empresa.telefono), 'email': PlutoCell(value: empresa.email), 'fecha_creacion': PlutoCell(value: empresa.fechaCreacion.toString().split(' ')[0]), 'logo_url': PlutoCell( value: empresa.logoUrl != null ? "${supabaseLU.supabaseUrl}/storage/v1/object/public/nethive/logos/${empresa.logoUrl}?${DateTime.now().millisecondsSinceEpoch}" : '', ), 'imagen_url': PlutoCell( value: empresa.imagenUrl != null ? "${supabaseLU.supabaseUrl}/storage/v1/object/public/nethive/imagenes/${empresa.imagenUrl}?${DateTime.now().millisecondsSinceEpoch}" : '', ), 'editar': PlutoCell(value: empresa.id), 'eliminar': PlutoCell(value: empresa.id), 'ver_negocios': PlutoCell(value: empresa.id), })); } } Future getNegociosPorEmpresa(String empresaId) async { try { final res = await supabaseLU .from('negocio') .select() .eq('empresa_id', empresaId) .order('fecha_creacion', ascending: false); negocios = (res as List) .map((negocio) => Negocio.fromMap(negocio)) .toList(); _buildNegociosRows(); _safeNotifyListeners(); } catch (e) { print('Error en getNegociosPorEmpresa: ${e.toString()}'); } } void _buildNegociosRows() { negociosRows.clear(); for (Negocio negocio in negocios) { negociosRows.add(PlutoRow(cells: { 'id': PlutoCell(value: negocio.id), 'empresa_id': PlutoCell(value: negocio.empresaId), 'nombre': PlutoCell(value: negocio.nombre), 'direccion': PlutoCell(value: negocio.direccion), 'direccion_completa': PlutoCell( value: negocio.direccion), // Nuevo campo para la segunda columna 'latitud': PlutoCell(value: negocio.latitud.toString()), 'longitud': PlutoCell(value: negocio.longitud.toString()), 'tipo_local': PlutoCell(value: negocio.tipoLocal), 'fecha_creacion': PlutoCell(value: negocio.fechaCreacion.toString().split(' ')[0]), 'logo_url': PlutoCell( value: negocio.logoUrl != null ? "${supabaseLU.supabaseUrl}/storage/v1/object/public/nethive/logos/${negocio.logoUrl}?${DateTime.now().millisecondsSinceEpoch}" : '', ), 'imagen_url': PlutoCell( value: negocio.imagenUrl != null ? "${supabaseLU.supabaseUrl}/storage/v1/object/public/nethive/imagenes/${negocio.imagenUrl}?${DateTime.now().millisecondsSinceEpoch}" : '', ), 'acceder_infraestructura': PlutoCell(value: negocio.id), 'editar': PlutoCell(value: negocio.id), 'eliminar': PlutoCell(value: negocio.id), 'ver_componentes': PlutoCell(value: negocio.id), })); } } // Métodos para subir archivos Future selectLogo() async { logoFileName = null; logoToUpload = null; FilePickerResult? picker = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['jpg', 'png', 'jpeg'], ); if (picker != null) { var now = DateTime.now(); var formatter = DateFormat('yyyyMMddHHmmss'); var timestamp = formatter.format(now); logoFileName = 'logo-$timestamp-${picker.files.single.name}'; logoToUpload = picker.files.single.bytes; // Notificar inmediatamente después de seleccionar _safeNotifyListeners(); } } Future selectImagen() async { imagenFileName = null; imagenToUpload = null; FilePickerResult? picker = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['jpg', 'png', 'jpeg'], ); if (picker != null) { var now = DateTime.now(); var formatter = DateFormat('yyyyMMddHHmmss'); var timestamp = formatter.format(now); imagenFileName = 'imagen-$timestamp-${picker.files.single.name}'; imagenToUpload = picker.files.single.bytes; // Notificar inmediatamente después de seleccionar _safeNotifyListeners(); } } Future uploadLogo() async { if (logoToUpload != null && logoFileName != null) { await supabaseLU.storage.from('nethive/logos').uploadBinary( logoFileName!, logoToUpload!, fileOptions: const FileOptions( cacheControl: '3600', upsert: false, ), ); return logoFileName; } return null; } Future uploadImagen() async { if (imagenToUpload != null && imagenFileName != null) { await supabaseLU.storage.from('nethive/imagenes').uploadBinary( imagenFileName!, imagenToUpload!, fileOptions: const FileOptions( cacheControl: '3600', upsert: false, ), ); return imagenFileName; } return null; } // CRUD Empresas Future crearEmpresa({ required String nombre, required String rfc, required String direccion, required String telefono, required String email, }) async { try { final logoUrl = await uploadLogo(); final imagenUrl = await uploadImagen(); final res = await supabaseLU.from('empresa').insert({ 'nombre': nombre, 'rfc': rfc, 'direccion': direccion, 'telefono': telefono, 'email': email, 'logo_url': logoUrl, 'imagen_url': imagenUrl, }).select(); if (res.isNotEmpty) { await getEmpresas(); resetFormData(); return true; } return false; } catch (e) { print('Error en crearEmpresa: ${e.toString()}'); return false; } } Future crearNegocio({ required String empresaId, required String nombre, required String direccion, required double latitud, required double longitud, required String tipoLocal, }) async { try { final logoUrl = await uploadLogo(); final imagenUrl = await uploadImagen(); final res = await supabaseLU.from('negocio').insert({ 'empresa_id': empresaId, 'nombre': nombre, 'direccion': direccion, 'latitud': latitud, 'longitud': longitud, 'tipo_local': tipoLocal, 'logo_url': logoUrl, 'imagen_url': imagenUrl, }).select(); if (res.isNotEmpty) { await getNegociosPorEmpresa(empresaId); resetFormData(); return true; } return false; } catch (e) { print('Error en crearNegocio: ${e.toString()}'); return false; } } Future eliminarEmpresa(String empresaId) async { try { // Primero eliminar todos los negocios asociados await supabaseLU.from('negocio').delete().eq('empresa_id', empresaId); // Luego eliminar la empresa await supabaseLU.from('empresa').delete().eq('id', empresaId); // Solo actualizar si el provider sigue activo if (!_isDisposed) { await getEmpresas(); } return true; } catch (e) { print('Error en eliminarEmpresa: ${e.toString()}'); return false; } } Future eliminarNegocio(String negocioId) async { try { await supabaseLU.from('negocio').delete().eq('id', negocioId); // Solo actualizar si el provider sigue activo y hay una empresa seleccionada if (!_isDisposed && empresaSeleccionadaId != null) { await getNegociosPorEmpresa(empresaSeleccionadaId!); } return true; } catch (e) { print('Error en eliminarNegocio: ${e.toString()}'); return false; } } // Métodos de utilidad void setEmpresaSeleccionada(String empresaId) { empresaSeleccionadaId = empresaId; empresaSeleccionada = empresas.firstWhere((e) => e.id == empresaId); getNegociosPorEmpresa(empresaId); _safeNotifyListeners(); } void resetFormData() { logoFileName = null; imagenFileName = null; logoToUpload = null; imagenToUpload = null; _safeNotifyListeners(); } void buscarEmpresas(String busqueda) { getEmpresas(busqueda.isEmpty ? null : busqueda); } Widget? getImageWidget(dynamic image, {double height = 100, double width = 100}) { if (image == null || image.toString().isEmpty) { return Container( height: height, width: width, decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), ), child: Image.asset( 'assets/images/placeholder_no_image.jpg', height: height, width: width, fit: BoxFit.cover, ), ); } else if (image is Uint8List) { return Image.memory( image, height: height, width: width, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Image.asset( 'assets/images/placeholder_no_image.jpg', height: height, width: width, fit: BoxFit.cover, ); }, ); } else if (image is String) { return Image.network( image, height: height, width: width, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Image.asset( 'assets/images/placeholder_no_image.jpg', height: height, width: width, fit: BoxFit.cover, ); }, ); } return Image.asset( 'assets/images/placeholder_no_image.jpg', height: height, width: width, fit: BoxFit.cover, ); } }