Procházet zdrojové kódy

Merge branch 'OGdev' into BRNDEV

c90Beretta před 2 měsíci
rodič
revize
aa54f9ea55

+ 4 - 3
lib/core/services/services.dart

@@ -1,5 +1,6 @@
-export '../services/base_service.dart';
-export '../services/login_service.dart';
-export '../services/profile_service.dart';
+export 'base_service.dart';
+export 'login_service.dart';
+export 'profile_service.dart';
 export 'database_service.dart';
 export 'api_response.dart';
+export 'session_storage.dart';

+ 6 - 2
lib/main.dart

@@ -7,8 +7,7 @@ import 'dart:io';
 import 'mvvm/views/home/home_screen.dart';
 import 'mvvm/views/login/login_screen.dart';
 import '/utils/themes.dart';
-import 'mvvm/viewmodels/login_view_model.dart';
-import 'mvvm/viewmodels/sucursal_view_model.dart';
+import 'mvvm/viewmodels/viewmodels.dart';
 import 'package:timezone/data/latest.dart' as tzdata;
 
 void main() async {
@@ -28,6 +27,11 @@ void main() async {
     runApp(MultiProvider(providers: [
       ChangeNotifierProvider(create: (_) => LoginViewModel()),
       ChangeNotifierProvider(create: (_) => SucursalViewModel()),
+      ChangeNotifierProvider(create: (_) => PermisoViewModel()),
+      ChangeNotifierProvider(create: (_) => UsuarioViewModel()),
+      ChangeNotifierProvider(create: (_) => ProductoViewModel()),
+      ChangeNotifierProvider(create: (_) => MesaViewModel()),
+      ChangeNotifierProvider(create: (_) => CategoriaProductoViewModel()),
       // Agrega aquí cualquier otro provider que necesites
     ], child: const MyApp()));
   });

+ 139 - 0
lib/mvvm/viewmodels/categoria_producto_view_model.dart

@@ -0,0 +1,139 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import '../../core/services/services.dart';
+import '../../core/models/models.dart';
+
+class CategoriaProductoViewModel extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<CategoriaProducto> _categoriaProductos = [];
+  bool _isLoading = false;
+  CategoriaProducto? _selectedCategoriaProducto;
+  Map<int, String> _categoriaMap = {};
+
+  List<CategoriaProducto> get categoriaProductos => _categoriaProductos;
+  CategoriaProducto? get selectedCategoriaProducto =>
+      _selectedCategoriaProducto;
+  bool get isLoading => _isLoading;
+
+  Map<int, String> get categoriaMap => _categoriaMap;
+
+  int _currentPage = 1;
+  int _totalProducts = 0;
+  int _limit = 20;
+
+  int get currentPage => _currentPage;
+  int get totalProducts => _totalProducts;
+  int get totalPages => (_totalProducts / _limit).ceil();
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  void selectCategoriaProducto(CategoriaProducto categoriaProducto) {
+    _selectedCategoriaProducto = categoriaProducto;
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalAll({int page = 1}) async {
+    _currentPage = page;
+    var db = await DatabaseService().db;
+
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM CategoriaProducto'));
+    _totalProducts = count ?? 0;
+
+    int offset = (_limit * (page - 1));
+
+    var query = await db.query('CategoriaProducto',
+        where: 'eliminado IS NULL',
+        orderBy: 'id asc',
+        limit: _limit,
+        offset: offset);
+    _categoriaProductos =
+        query.map((element) => CategoriaProducto.fromJson(element)).toList();
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalCategoria() async {
+    var db = await DatabaseService().db;
+    var query = await db!.query('CategoriaProducto', orderBy: 'idLocal asc');
+    List<CategoriaProducto> aux = [];
+    _categoriaMap = {};
+    for (var element in query) {
+      CategoriaProducto categoria = CategoriaProducto.fromJson(element);
+      aux.add(categoria);
+      _categoriaMap[categoria.id!] = categoria.nombre!;
+    }
+    _categoriaProductos = aux;
+    notifyListeners();
+  }
+
+  Future<List<CategoriaProducto>> getCategoriaProducto({String q = ''}) async {
+    var db = await DatabaseService().db;
+    List<Map> results = await db!.query('CategoriaProducto',
+        where: 'nombre LIKE ?', whereArgs: ['%$q%'], orderBy: 'nombre ASC');
+    return results
+        .map(
+            (map) => CategoriaProducto.fromJson(Map<String, dynamic>.from(map)))
+        .toList();
+  }
+
+  Future<void> addCategoriaProducto(CategoriaProducto categoriaProducto) async {
+    await DatabaseService().guardar(categoriaProducto);
+    fetchLocalAll();
+  }
+
+  Future<void> fetchLocalByName({required String nombre}) async {
+    var db = await DatabaseService().db;
+    var query = await db!.query(
+      'CategoriaProducto',
+      where: 'nombre LIKE "%$nombre%"',
+      orderBy: 'idLocal asc',
+    );
+    List<CategoriaProducto> aux = [];
+    for (var element in query) {
+      CategoriaProducto categoriaProducto = CategoriaProducto.fromJson(element);
+      aux.add(categoriaProducto);
+    }
+    _categoriaProductos = aux;
+    notifyListeners();
+  }
+
+  Future<void> updateCategoriaProducto(
+      CategoriaProducto categoriaProducto) async {
+    setIsLoading(true);
+    try {
+      int result = await DatabaseService().guardar(categoriaProducto);
+      print("Update result: $result");
+      fetchLocalAll();
+    } catch (e) {
+      print('Error updating product: $e');
+    }
+    setIsLoading(false);
+  }
+
+  Future<void> deleteCategoriaProducto(int id) async {
+    await DatabaseService().eliminar<CategoriaProducto>(id);
+    fetchLocalAll();
+  }
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchLocalAll(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchLocalAll(page: _currentPage - 1);
+    }
+  }
+}

+ 177 - 0
lib/mvvm/viewmodels/mesa_view_model.dart

@@ -0,0 +1,177 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import '../../core/services/services.dart';
+import '../../core/models/models.dart';
+
+class MesaViewModel extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<Mesa> _mesas = [];
+  bool _isLoading = false;
+  Mesa? _selectedMesa;
+
+  List<Mesa> get mesas => _mesas;
+  bool get isLoading => _isLoading;
+  Mesa? get selectedMesa => _selectedMesa;
+
+  int _currentPage = 1;
+  int _totalMesas = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalMesas => _totalMesas;
+  int get totalPages => (_totalMesas / _limit).ceil();
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  void selectMesa(Mesa mesa) {
+    _selectedMesa = mesa;
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalAll({
+    int page = 1,
+    bool sinLimite = false,
+    String orderBy = 'id ASC',
+  }) async {
+    _currentPage = page;
+    var db = await DatabaseService().db;
+
+    if (!sinLimite) {
+      int? count = Sqflite.firstIntValue(
+          await db!.rawQuery('SELECT COUNT(*) FROM Mesa'));
+      _totalMesas = count ?? 0;
+    }
+
+    String? limitOffsetClause;
+    if (!sinLimite) {
+      int offset = (_limit * (page - 1));
+      limitOffsetClause = 'LIMIT $_limit OFFSET $offset';
+    } else {
+      limitOffsetClause = '';
+    }
+
+    var query = await db!
+        .rawQuery('SELECT * FROM Mesa ORDER BY $orderBy $limitOffsetClause');
+
+    _mesas = query.map((element) => Mesa.fromJson(element)).toList();
+
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalByName({required String nombre}) async {
+    var db = await DatabaseService().db;
+    var query = await db!.query(
+      'Mesa',
+      where: 'nombre LIKE "%$nombre%"',
+      orderBy: 'id asc',
+    );
+    List<Mesa> aux = [];
+    for (var element in query) {
+      Mesa mesa = Mesa.fromJson(element);
+      aux.add(mesa);
+    }
+    _mesas = aux;
+    notifyListeners();
+  }
+
+  Mesa fetchLocalById({required int? idMesa}) {
+    final mesa = mesas.firstWhere((mesa) => mesa.id == idMesa,
+        orElse: () => Mesa(id: 0, nombre: 'Mesa desconocida'));
+    return mesa;
+  }
+
+  Future<void> addMesa(Mesa mesa) async {
+    mesa.creado = DateTime.now().toUtc();
+    await DatabaseService().guardar(mesa);
+    await fetchLocalAll();
+  }
+
+  Future<void> updateMesa(Mesa mesa) async {
+    setIsLoading(true);
+    try {
+      mesa.modificado = DateTime.now().toUtc();
+      await DatabaseService().guardar(mesa);
+      await fetchLocalAll();
+    } catch (e) {
+      debugPrint('Error updating mesa: $e');
+    }
+    setIsLoading(false);
+  }
+
+  Future<void> deleteMesa(int id) async {
+    await DatabaseService().eliminar<Mesa>(id);
+    fetchLocalAll();
+  }
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchLocalAll(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchLocalAll(page: _currentPage - 1);
+    }
+  }
+
+  Future<bool> isMesaActive(String clave) async {
+    var db = await DatabaseService().db;
+    var result = await db!.query(
+      'Mesa',
+      where: 'clave = ?',
+      whereArgs: [clave],
+    );
+
+    if (result.isNotEmpty) {
+      var mesa = Mesa.fromJson(result.first);
+      return mesa.activa == true;
+    }
+
+    return false;
+  }
+
+  Future<bool> sincronizarMesas() async {
+    String? claveSucursal =
+        await DatabaseService().obtenerClaveSucursalSeleccionada();
+
+    try {
+      Map<String, String> parametros = {
+        "claveSucursal": claveSucursal!,
+        "limite": "-1"
+      };
+
+      final response = ApiResponse(await BaseService()
+          .get('/pos/mesa', queryParameters: parametros, withAuth: true));
+
+      if (response.isOk && response.resultados != null) {
+        List<Mesa> mesasApi =
+            response.resultados!.map((json) => Mesa.fromApi(json)).toList();
+
+        if (mesasApi.isNotEmpty) {
+          debugPrint("Mesas API obtenidas: ${mesasApi.length}");
+          await DatabaseService().sincronizarMesas(mesasApi);
+          return true;
+        } else {
+          debugPrint("No se encontraron mesas en la API.");
+        }
+      } else {
+        debugPrint("Error en la respuesta de la API o resultados nulos MESAS.");
+      }
+      return false;
+    } catch (e) {
+      debugPrint('Error al sincronizar mesas: $e');
+      return false;
+    }
+  }
+}

+ 97 - 0
lib/mvvm/viewmodels/permiso_view_model.dart

@@ -0,0 +1,97 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+
+import '../../core/services/services.dart';
+import '../../core/models/models.dart';
+
+class PermisoViewModel extends ChangeNotifier {
+  List<Permiso> _permisos = [];
+  bool _isLoading = false;
+  Permiso? _selectedPermiso;
+
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<Permiso> get permisos => _permisos;
+  bool get isLoading => _isLoading;
+
+  int _currentPage = 1;
+  int _totalPermisos = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalPermisos => _totalPermisos;
+  int get totalPages => (_totalPermisos / _limit).ceil();
+
+  List<String> _userPermisos = [];
+  List<String> get userPermisos => _userPermisos;
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  void selectPermiso(Permiso permiso) {
+    _selectedPermiso = permiso;
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalAll({int page = 1}) async {
+    _currentPage = page;
+    var db = await DatabaseService().db;
+
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM Permiso'));
+    _totalPermisos = count ?? 0;
+
+    int offset = (_limit * (page - 1));
+
+    var query = await db.query('Permiso',
+        orderBy: 'id asc', limit: _limit, offset: offset);
+    _permisos = query.map((element) => Permiso.fromJson(element)).toList();
+    notifyListeners();
+  }
+
+  Future<void> sincronizarPermisos() async {
+    _isLoading = true;
+    notifyListeners();
+    try {
+      final response = ApiResponse(await BaseService().get('/pos/permiso'));
+      if (response.isOk && response.resultados != null) {
+        _permisos =
+            response.resultados!.map((json) => Permiso.fromJson(json)).toList();
+        await DatabaseService().sincronizarPermisos(_permisos);
+      }
+    } catch (e) {
+      print('Error fetching permisos: $e');
+    }
+    _isLoading = false;
+    notifyListeners();
+  }
+
+  Future<void> fetchUserPermisos() async {
+    _isLoading = true;
+    notifyListeners();
+
+    try {
+      int? userId = await SessionStorage().getId();
+      if (userId != null) {
+        var db = await DatabaseService().db;
+
+        var query = await db!.query(
+          'UsuarioPermiso',
+          where: 'idUsuario = ?',
+          whereArgs: [userId],
+        );
+
+        _userPermisos =
+            query.map((row) => row['idPermiso'].toString()).toList();
+      }
+    } catch (e) {
+      print('Error fetching user permisos: $e');
+    } finally {
+      _isLoading = false;
+      notifyListeners();
+    }
+  }
+}

+ 437 - 0
lib/mvvm/viewmodels/producto_view_model.dart

@@ -0,0 +1,437 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+
+import '../../core/services/services.dart';
+import '../../core/models/models.dart';
+
+class ProductoViewModel<T> extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<Producto> _productos = [];
+  List<CategoriaProducto> _toppingCategories = [];
+  bool _isLoading = false;
+  int? _selectedCategoriaId = 0;
+  Producto? _selectedProducto;
+
+  List<Producto> get productos => _productos;
+  Producto? get selectedProducto => _selectedProducto;
+  bool get isLoading => _isLoading;
+
+  int _currentPage = 1;
+  int _totalProducts = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalProducts => _totalProducts;
+  int get totalPages => (_totalProducts / _limit).ceil();
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  void selectProducto(Producto producto) {
+    _selectedProducto = producto;
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalAll({int page = 1}) async {
+    _currentPage = page;
+    var db = await DatabaseService().db;
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM Producto'));
+    _totalProducts = count ?? 0;
+
+    int offset = (_limit * (page - 1));
+
+    var query = await db.query('Producto',
+        where: 'eliminado IS NULL',
+        orderBy: 'id asc',
+        limit: _limit,
+        offset: offset);
+    _productos = query.map((element) => Producto.fromJson(element)).toList();
+
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalByID({required int idCategoria}) async {
+    var db = await DatabaseService().db;
+    var query = await db!.query('Producto',
+        where: 'idCategoria = ?',
+        whereArgs: [idCategoria],
+        orderBy: 'idLocal asc');
+    List<Producto> aux = [];
+    for (var element in query) {
+      Producto producto = Producto.fromJson(element);
+      aux.add(producto);
+    }
+    _productos = aux;
+    notifyListeners();
+  }
+
+  Future<void> fetchAllByCategory(int idCategoria) async {
+    var db = await DatabaseService().db;
+    var query = await db!.query('Producto',
+        where: 'idCategoria = ? and eliminado IS NULL',
+        whereArgs: [idCategoria],
+        orderBy: 'idLocal asc');
+    _productos = query.map((e) => Producto.fromJson(e)).toList();
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalByName({required String nombre}) async {
+    var db = await DatabaseService().db;
+    var query = await db!.query(
+      'Producto',
+      where: 'nombre LIKE ?',
+      whereArgs: ['%$nombre%'],
+      orderBy: 'idLocal asc',
+    );
+    _productos = query.map((e) => Producto.fromJson(e)).toList();
+    notifyListeners();
+  }
+
+  Future<int> addProducto(Producto producto) async {
+    setIsLoading(true);
+    try {
+      int id = await DatabaseService().guardar(producto);
+      fetchLocalAll();
+      return id;
+    } catch (e) {
+      print('Error al agregar producto: $e');
+      return -1;
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  Future<void> updateProducto(Producto producto) async {
+    setIsLoading(true);
+    try {
+      int result = await DatabaseService().guardar(producto);
+      print("Resultado: $result");
+      fetchLocalAll();
+    } catch (e) {
+      print('Error al actualizar: $e');
+    }
+    setIsLoading(false);
+  }
+
+  Future<void> deleteProducto(int id) async {
+    await DatabaseService().eliminar<Producto>(id);
+    fetchLocalAll();
+  }
+
+  Future<bool> updateProductImagePath(int productId, String imagePath) async {
+    setIsLoading(true);
+    try {
+      Producto? producto =
+          await DatabaseService().obtenerProductoPorId(productId);
+      producto!.imagen = imagePath;
+      await DatabaseService().guardar(producto);
+      notifyListeners();
+      return true;
+    } catch (e) {
+      print('Error al actualizar la imagen del producto: $e');
+      return false;
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  Future<List<CategoriaProducto>> fetchToppingCategories() async {
+    var db = await DatabaseService().db;
+    var query = await db!.query('CategoriaProducto',
+        where: 'esToping = ?', whereArgs: [1], orderBy: 'id asc');
+    _toppingCategories =
+        query.map((element) => CategoriaProducto.fromJson(element)).toList();
+    return _toppingCategories;
+  }
+
+  Future<List<Producto>> fetchProductsByCategory(int categoryId) async {
+    var db = await DatabaseService().db;
+    var query = await db!.query('Producto',
+        where: 'idCategoria = ?', whereArgs: [categoryId], orderBy: 'id asc');
+    return query.map((e) => Producto.fromJson(e)).toList();
+  }
+
+  Future<List<int>> obtenerToppingsPorProducto(int idProducto) async {
+    var dbClient = await DatabaseService().db;
+    var result = await dbClient!.query(
+      'ProductoTopping',
+      where: 'idProducto = ?',
+      whereArgs: [idProducto],
+    );
+    return result.map((map) => map['idTopping'] as int).toList();
+  }
+
+  Future<Producto?> obtenerProductoPorId(int idProducto) async {
+    Database? dbClient = await DatabaseService().db;
+    List<Map> maps = await dbClient!
+        .query('Producto', where: 'id = ?', whereArgs: [idProducto]);
+    if (maps.isNotEmpty) {
+      return Producto.fromJson(Map<String, dynamic>.from(maps.first));
+    }
+    return null;
+  }
+
+  Future<bool> sincronizarCategorias() async {
+    String? claveSucursal =
+        await DatabaseService().obtenerClaveSucursalSeleccionada();
+
+    try {
+      Map<String, String> parametros = {
+        "claveSucursal": claveSucursal!,
+        "limite": "-1"
+      };
+      final response = ApiResponse(await BaseService()
+          .get('/pos/categoria', queryParameters: parametros));
+
+      //print(response.resultados);
+
+      if (response.isOk && response.resultados != null) {
+        List<CategoriaProducto> categoriasApi = response.resultados!
+            .map((json) => CategoriaProducto.fromApi(json))
+            .toList();
+
+        if (categoriasApi.isNotEmpty) {
+          await DatabaseService().sincronizarCategorias(categoriasApi);
+          notifyListeners();
+          return true;
+        }
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar categorías: $e');
+      return false;
+    }
+  }
+
+  Future<bool> sincronizarProductos() async {
+    String? claveSucursal =
+        await DatabaseService().obtenerClaveSucursalSeleccionada();
+
+    try {
+      Map<String, String> parametros = {
+        "limite": "-1",
+        "claveSucursal": claveSucursal!,
+        "expand": "media"
+      };
+      final response = ApiResponse(await BaseService()
+          .get('/pos/producto', queryParameters: parametros));
+
+      if (response.isOk && response.resultados != null) {
+        List<Producto> productosApi =
+            response.resultados!.map((json) => Producto.fromApi(json)).toList();
+
+        if (productosApi.isNotEmpty) {
+          print("Productos API obtenidos: ${productosApi.length}");
+
+          // // Aquí mantengo tu lógica de descarga de imágenes.
+          // for (var productoApi in productosApi) {
+          //   if (productoApi.media != null && productoApi.media!.isNotEmpty) {
+          //     for (var media in productoApi.media!) {
+          //       // Descargar y guardar la imagen localmente
+          //       String? localImagePath = await downloadAndStoreImage(
+          //           media.ruta!, productoApi.id!, media.nombre!);
+
+          //       if (localImagePath != null) {
+          //         productoApi.imagen = localImagePath;
+          //       }
+          //     }
+          //   }
+          // }
+
+          // Delegamos la sincronización de los productos al DatabaseService
+          await DatabaseService().sincronizarProductos(productosApi);
+          notifyListeners();
+          return true;
+        } else {
+          print("No se encontraron productos en la API.");
+        }
+      } else {
+        print("Error en la respuesta de la API o resultados nulos.");
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar productos: $e');
+      return false;
+    }
+  }
+
+  Future<bool> sincronizarProductoTopping() async {
+    String? claveSucursal =
+        await DatabaseService().obtenerClaveSucursalSeleccionada();
+
+    try {
+      Map<String, String> parametros = {
+        "limite": "-1",
+        "claveSucursal": claveSucursal!,
+      };
+
+      final response = ApiResponse(await BaseService()
+          .get('/pos/producto-topping', queryParameters: parametros));
+
+      if (response.isOk && response.resultados != null) {
+        List<ProductoTopping> toppingApi = response.resultados!
+            .map((json) => ProductoTopping.fromApi(json))
+            .toList();
+
+        if (toppingApi.isNotEmpty) {
+          print("Producto Toppings API obtenidos: ${toppingApi.length}");
+
+          await DatabaseService().sincronizarProductoTopping(toppingApi);
+          notifyListeners();
+          return true;
+        } else {
+          print("No se encontraron toppings de producto en la API.");
+        }
+      } else {
+        print("Error en la respuesta de la API o resultados nulos.");
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar producto toppings: $e');
+      return false;
+    }
+  }
+
+  Future<void> sincronizarProductosYCategorias() async {
+    setIsLoading(true);
+    try {
+      bool categoriasSincronizadas = await sincronizarCategorias();
+
+      if (categoriasSincronizadas) {
+        bool productosSincronizados = await sincronizarProductos();
+        if (productosSincronizados) {
+          bool toppingsSincronizados = await sincronizarProductoTopping();
+          if (toppingsSincronizados) {
+            await fetchLocalAll();
+          }
+        }
+      }
+      notifyListeners();
+    } catch (e, stackTrace) {
+      throw Exception(
+          "Error al sincronizar productos, categorías y toppings: $e\n$stackTrace");
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  Future<bool> sincronizarProductosLocales() async {
+    List<Producto> productosNoSincronizados = await DatabaseService()
+        .obtenerProductosNoSincronizadosOrdenadosPorFecha();
+
+    if (productosNoSincronizados.isNotEmpty) {
+      Producto productoNoSincronizado = productosNoSincronizados.first;
+
+      Map<String, dynamic> productoJson =
+          await prepararProductoParaApi(productoNoSincronizado);
+
+      print('JSON Producto enviado: $productoJson');
+
+      // Llamada a la API
+      var response = ApiResponse(await BaseService()
+          .post('/pos/producto/sincronizar', body: productoJson));
+
+      if (response.isOk && response.detalle != null) {
+        int idWeb = response.detalle!['idWeb'];
+        String sincronizado = response.detalle!['sincronizado'];
+        await DatabaseService().actualizarProductoSincronizado(
+            productoNoSincronizado.id!, idWeb, sincronizado);
+        return true;
+      } else {
+        print('Error en la sincronización del producto: ${response.mensaje}');
+        return true;
+      }
+    } else {
+      print('No se encontraron productos no sincronizados.');
+      return false;
+    }
+  }
+
+  Future<Map<String, dynamic>> prepararProductoParaApi(
+      Producto producto) async {
+    String? claveSucursal =
+        await DatabaseService().obtenerClaveSucursalSeleccionada();
+    Map<String, dynamic> apiMap = producto.toJson();
+
+    apiMap['claveSucursal'] = claveSucursal;
+
+    if (producto.idWeb != null && producto.idWeb! > 0) {
+      apiMap['idWeb'] = producto.idWeb;
+    }
+
+    return apiMap;
+  }
+
+  Future<Map<String, dynamic>> prepararCategoriaParaApi(
+      CategoriaProducto categoria) async {
+    String? claveSucursal =
+        await DatabaseService().obtenerClaveSucursalSeleccionada();
+    Map<String, dynamic> apiMap = categoria.toJson();
+    // Asegúrate que CategoriaProducto tenga un toJson y si necesitas toApi, créalo similar a Pedido.
+
+    apiMap['claveSucursal'] = claveSucursal;
+
+    if (categoria.idWeb != null && categoria.idWeb! > 0) {
+      apiMap['idWeb'] = categoria.idWeb;
+    }
+
+    return apiMap;
+  }
+
+  Future<bool> sincronizarCategoriasLocales() async {
+    // Obtener categorias no sincronizadas
+    List<CategoriaProducto> categoriasNoSincronizadas = await DatabaseService()
+        .obtenerCategoriasNoSincronizadasOrdenadasPorFecha();
+
+    if (categoriasNoSincronizadas.isNotEmpty) {
+      CategoriaProducto categoriaNoSincronizada =
+          categoriasNoSincronizadas.first;
+
+      Map<String, dynamic> categoriaJson =
+          await prepararCategoriaParaApi(categoriaNoSincronizada);
+
+      print('JSON Categoria enviado: $categoriaJson');
+
+      // Llamada a la API
+      var response = ApiResponse(await BaseService()
+          .post('/pos/categoria/sincronizar', body: categoriaJson));
+
+      if (response.isOk && response.detalle != null) {
+        int idWeb = response.detalle!['idWeb'];
+        String sincronizado = response.detalle!['sincronizado'];
+        await DatabaseService().actualizarCategoriaSincronizada(
+            categoriaNoSincronizada.id!, idWeb, sincronizado);
+        return true;
+      } else {
+        print(
+            'Error en la sincronización de la categoría: ${response.mensaje}');
+        return true;
+      }
+    } else {
+      print('No se encontraron categorias no sincronizadas.');
+      return false;
+    }
+  }
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchLocalAll(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchLocalAll(page: _currentPage - 1);
+    }
+  }
+}

+ 64 - 0
lib/mvvm/viewmodels/usuarios_view_model.dart

@@ -0,0 +1,64 @@
+import 'dart:convert';
+
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+
+import '../../core/services/services.dart';
+import '../../core/models/models.dart';
+
+class UsuarioViewModel extends ChangeNotifier {
+  List<Usuario> _usuarios = [];
+  bool _isLoading = false;
+
+  List<Usuario> get usuarios => _usuarios;
+  bool get isLoading => _isLoading;
+
+  int _currentPage = 1;
+  int _totalProducts = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalProducts => _totalProducts;
+  int get totalPages => (_totalProducts / _limit).ceil();
+
+  Future<void> fetchLocalAll({int page = 1}) async {
+    _currentPage = page;
+    var db = await DatabaseService().db;
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM Usuario'));
+    _totalProducts = count ?? 0;
+
+    int offset = (_limit * (page - 1));
+
+    var query = await db.query('Usuario',
+        where: 'eliminado IS NULL',
+        orderBy: 'id asc',
+        limit: _limit,
+        offset: offset);
+    _usuarios = query.map((element) => Usuario.fromJson(element)).toList();
+
+    notifyListeners();
+  }
+
+  Future<bool> sincronizarUsuarios() async {
+    try {
+      Map<String, String> parametros = {"expand": 'permisos'};
+      final response = ApiResponse(
+          await BaseService().get('/pos/usuario', queryParameters: parametros));
+
+      if (response.isOk && response.resultados != null) {
+        List<Usuario> usuariosApi =
+            response.resultados!.map((json) => Usuario.fromApi(json)).toList();
+        if (usuariosApi.isNotEmpty) {
+          await DatabaseService().sincronizarUsuarios(usuariosApi);
+          notifyListeners();
+          return true;
+        }
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar usuarios: $e');
+      return false;
+    }
+  }
+}

+ 8 - 0
lib/mvvm/viewmodels/viewmodels.dart

@@ -0,0 +1,8 @@
+export '../viewmodels/login_view_model.dart';
+export '../viewmodels/sucursal_view_model.dart';
+export '../viewmodels/permiso_view_model.dart';
+export '../viewmodels/home_view_model.dart';
+export '../viewmodels/usuarios_view_model.dart';
+export '../viewmodels/mesa_view_model.dart';
+export '../viewmodels/producto_view_model.dart';
+export '../viewmodels/categoria_producto_view_model.dart';

+ 40 - 18
lib/mvvm/views/home/home_screen.dart

@@ -1,7 +1,10 @@
 import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
 import 'package:turquessa_mesas_hoster/utils/widgets/custom_appbar.dart';
 import 'package:turquessa_mesas_hoster/core/models/mesas_model.dart';
 
+import '../../viewmodels/viewmodels.dart';
+
 class HomeScreen extends StatefulWidget {
   const HomeScreen({super.key});
 
@@ -13,11 +16,21 @@ class Formulario extends State<HomeScreen> {
   @override
   void initState() {
     super.initState();
+    final mesaViewModel = Provider.of<MesaViewModel>(context, listen: false);
+
+    WidgetsBinding.instance.addPostFrameCallback((_) async {
+      Provider.of<ProductoViewModel>(context, listen: false)
+          .sincronizarProductosYCategorias();
+
+      await mesaViewModel.sincronizarMesas();
+      await mesaViewModel.fetchLocalAll(sinLimite: true, orderBy: 'nombre ASC');
+    });
   }
 
   @override
   Widget build(BuildContext context) {
-
+    final mesaViewModel = Provider.of<MesaViewModel>(context);
+    var _selectedIndex;
     return Scaffold(
       backgroundColor: Colors.grey.shade200,
       appBar: AppBar(
@@ -57,24 +70,33 @@ class Formulario extends State<HomeScreen> {
           ),
           Expanded(
             child: Center(
-                child: GridView.builder(
-                    gridDelegate:
-                        const SliverGridDelegateWithFixedCrossAxisCount(
-                            crossAxisCount: 4,
-                            childAspectRatio: 1.0,
-                            crossAxisSpacing: 10.0,
-                            mainAxisSpacing: 10.0),
-                    padding: const EdgeInsets.all(10),
-                    itemCount: 8,
-                    itemBuilder: (context, index) {
-                      return TableCard(
-                        icon: Icons.table_chart,
-                        color: Colors.blue,
-                        title: 'Mesa ${index + 1}',
-                      );
-                    })),
+              child: GridView.builder(
+                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+                  crossAxisCount: 4,
+                  childAspectRatio: 1.0,
+                  crossAxisSpacing: 10.0,
+                  mainAxisSpacing: 10.0,
+                ),
+                padding: const EdgeInsets.all(10),
+                itemCount: mesaViewModel.mesas.length,
+                itemBuilder: (context, index) {
+                  final mesa = mesaViewModel.mesas[index];
+                  return GestureDetector(
+                    onTap: () {
+                      mesaViewModel.selectMesa(mesa);
+                    },
+                    child: TableCard(
+                      icon: Icons.table_chart,
+                      //TODO: Agregar campo de estatus de la mesa para definir los colores
+                      color: (mesa.activa == true) ? Colors.blue : Colors.grey,
+                      title: mesa.nombre ?? 'Mesa sin nombre',
+                    ),
+                  );
+                },
+              ),
+            ),
           ),
-          // if (selectedTable != null)
+          //if (mesaViewModel.selectedMesa != null)
           Expanded(
               flex: 1,
               child: Container(

+ 27 - 23
lib/mvvm/views/login/login_screen.dart

@@ -3,8 +3,7 @@ import 'package:provider/provider.dart';
 
 import '../../../core/models/models.dart';
 import '../../../utils/themes.dart';
-import '../../viewmodels/login_view_model.dart';
-import '../../viewmodels/sucursal_view_model.dart';
+import '../../viewmodels/viewmodels.dart';
 import '../../../utils/widgets/widgets.dart';
 import '../home/home_screen.dart';
 
@@ -36,29 +35,34 @@ class _LoginScreenState extends State<LoginScreen> {
       final loginViewModel =
           Provider.of<LoginViewModel>(context, listen: false);
 
+      Provider.of<PermisoViewModel>(context, listen: false)
+          .sincronizarPermisos();
+      Provider.of<UsuarioViewModel>(context, listen: false)
+          .sincronizarUsuarios();
+
       // Verificar la sesión
       loginViewModel.checkSession().then((_) {
-        // if (loginViewModel.status == Status.authenticated) {
-        print("Sesión activa detectada, redirigiendo al HomeScreen.");
-        Navigator.pushReplacement(
-          context,
-          MaterialPageRoute(builder: (context) => const HomeScreen()),
-        );
-        // } else {
-        //   print("No se detectó sesión, mostrando la pantalla de login.");
-        //   final sucursalViewModel =
-        //       Provider.of<SucursalViewModel>(context, listen: false);
-        //   sucursalViewModel.sincronizarSucursales().then((_) {
-        //     setState(() {
-        //       final sucursales = sucursalViewModel.sucursales;
-        //       _selectedSucursal = sucursales.firstWhere(
-        //         (sucursal) => sucursal.seleccionado == 1,
-        //         orElse: () =>
-        //             sucursales.isNotEmpty ? sucursales[0] : Sucursal(),
-        //       );
-        //     });
-        //   });
-        // }
+        if (loginViewModel.status == Status.authenticated) {
+          print("Sesión activa detectada, redirigiendo al HomeScreen.");
+          Navigator.pushReplacement(
+            context,
+            MaterialPageRoute(builder: (context) => const HomeScreen()),
+          );
+        } else {
+          print("No se detectó sesión, mostrando la pantalla de login.");
+          final sucursalViewModel =
+              Provider.of<SucursalViewModel>(context, listen: false);
+          sucursalViewModel.sincronizarSucursales().then((_) {
+            setState(() {
+              final sucursales = sucursalViewModel.sucursales;
+              _selectedSucursal = sucursales.firstWhere(
+                (sucursal) => sucursal.seleccionado == 1,
+                orElse: () =>
+                    sucursales.isNotEmpty ? sucursales[0] : Sucursal(),
+              );
+            });
+          });
+        }
       });
     });
   }