Browse Source

actualizacion productos, categorias y pedidos

OscarGil03 7 months ago
parent
commit
512e65e1b2

+ 59 - 0
lib/data/api_response.dart

@@ -0,0 +1,59 @@
+import 'dart:convert';
+
+import 'package:http/http.dart';
+
+class ApiResponse {
+  int? statusCode;
+  Paginacion? paginacion;
+  List<Map<String, dynamic>>? resultados;
+  Map<String, dynamic>? detalle;
+  Map<String, dynamic>? errores;
+  String mensaje = '';
+
+  bool get isOk => statusCode! >= 200 && statusCode! < 300;
+  bool get isError => statusCode! >= 400 && statusCode! < 500;
+  bool get isServerError => statusCode! >= 500;
+
+  ApiResponse(Response response) {
+    statusCode = response.statusCode;
+    var body = json.decode(response.body);
+    if (body.containsKey('paginacion')) {
+      var pag = body['paginacion'];
+      paginacion = Paginacion(
+        total: pag['total'],
+        pagina: pag['pagina'],
+        limite: pag['limite'],
+      );
+    }
+    if (body.containsKey('mensaje')) {
+      mensaje = body['mensaje'];
+    }
+    if (body.containsKey('resultado') && body["resultado"] != null) {
+      resultados = (body['resultado'] as List<dynamic>)
+        .cast<Map<String, dynamic>>()
+        .toList();
+    }
+    if (body.containsKey('detalle')) {
+      detalle = body['detalle'];
+    }
+    if (body.containsKey('errores')) {
+      errores = body['errores'];
+    }
+  }
+}
+
+class Paginacion {
+  final int total;
+  final int pagina;
+  final int limite;
+
+  Paginacion({required this.total, required this.pagina, required this.limite});
+
+  factory Paginacion.fromJson(Map<String, dynamic> json) {
+    return Paginacion(
+      total: json['total'],
+      pagina: json['pagina'],
+      limite: json['limite'],
+    );
+  }
+}

+ 62 - 0
lib/data/session/session_storage.dart

@@ -0,0 +1,62 @@
+import 'package:shared_preferences/shared_preferences.dart';
+
+class SessionStorage {
+  Future<SharedPreferences> _getPreferences() async {
+    return await SharedPreferences.getInstance();
+  }
+
+  Future<void> saveToken(String token) async {
+    final preferences = await _getPreferences();
+    await preferences.setString('token', token);
+  }
+
+  Future<void> saveId(int id) async {
+    final preferences = await _getPreferences();
+    await preferences.setInt('id', id);
+  }
+  
+  Future<void> saveCorreo(String value) async {
+    final preferences = await _getPreferences();
+    await preferences.setString('correo', value);
+  }
+
+  Future<void> saveNombre(String? value) async {
+    final preferences = await _getPreferences();
+    await preferences.setString('nombre', value.toString());
+  }
+
+  Future<String?> getNombre() async {
+    final preferences = await _getPreferences();
+    return preferences.getString('nombre');
+  }
+
+  Future<String?> getCorreo() async {
+    final preferences = await _getPreferences();
+    return preferences.getString('correo');
+  }
+
+  Future<String?> getEmpresa() async {
+    final preferences = await _getPreferences();
+    return preferences.getString('empresa');
+  }
+
+  Future<void> saveEmpresa(String? value) async {
+    final preferences = await _getPreferences();
+    await preferences.setString('empresa', value.toString());
+  }
+
+  Future<String?> getToken() async {
+    final preferences = await _getPreferences();
+    return preferences.getString('token');
+  }
+
+  Future<int?> getId() async {
+    final preferences = await _getPreferences();
+    return preferences.getInt('id');
+  }
+
+  Future<void> clearToken() async {
+    final preferences = await _getPreferences();
+    await preferences.remove('token');
+  }
+}

+ 21 - 4
lib/models/categoria_producto_model.dart

@@ -17,12 +17,19 @@ class CategoriaProducto extends Basico {
 
 
   @override
   @override
   Map<String, dynamic> toJson() {
   Map<String, dynamic> toJson() {
+    print("Convirtiendo Producto a JSON");
+    print("ID: $id, descripcion: $descripcion, Nombre: $nombre");
+    print("creado: $creado, modificado: $modificado, eliminado: $eliminado");
+
     return {
     return {
       'id': id,
       'id': id,
-      'nombre': nombre,
-      'descripcion': descripcion,
-      'esToping': esToping,
-      'maximo': maximo,
+      'nombre': nombre ?? '',
+      'descripcion': descripcion ?? '',
+      'esToping': esToping ?? 0,
+      'maximo': maximo ?? 0,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
     }..addAll(super.toJson());
     }..addAll(super.toJson());
   }
   }
 
 
@@ -34,6 +41,16 @@ class CategoriaProducto extends Basico {
     maximo = Basico.parseInt(json['maximo']);
     maximo = Basico.parseInt(json['maximo']);
   }
   }
 
 
+  CategoriaProducto.fromApi(Map<String, dynamic> json) {
+    super.parseJson(json);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    maximo = Basico.parseInt(json['maximo']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
   Future<void> guardar() async {
   Future<void> guardar() async {
     idLocal = await RepoService().guardar(this);
     idLocal = await RepoService().guardar(this);
   }
   }

+ 36 - 0
lib/models/pedido_model.dart

@@ -1,5 +1,8 @@
+import 'package:intl/intl.dart';
+
 import 'basico_model.dart';
 import 'basico_model.dart';
 import 'pedido_producto_model.dart';
 import 'pedido_producto_model.dart';
+import '../services/services.dart';
 
 
 class Pedido extends Basico {
 class Pedido extends Basico {
   int? folio;
   int? folio;
@@ -21,6 +24,9 @@ class Pedido extends Basico {
   double? cantTarjeta;
   double? cantTarjeta;
   double? cantTransferencia;
   double? cantTransferencia;
   List<PedidoProducto> productos = [];
   List<PedidoProducto> productos = [];
+  int? idWeb;
+
+  String? sincronizado;
 
 
   Pedido({
   Pedido({
     super.id,
     super.id,
@@ -43,6 +49,8 @@ class Pedido extends Basico {
     this.cantTarjeta,
     this.cantTarjeta,
     this.cantTransferencia,
     this.cantTransferencia,
     this.productos = const [],
     this.productos = const [],
+    this.idWeb,
+    this.sincronizado,
   });
   });
 
 
   @override
   @override
@@ -70,6 +78,32 @@ class Pedido extends Basico {
     }..addAll(super.toJson());
     }..addAll(super.toJson());
   }
   }
 
 
+  Map<String, dynamic> toApi() {
+    idLocal = id;
+
+    Map<String, dynamic> apiMap = {
+      'idLocal': idLocal,
+      'folio': folio,
+      'estatus': estatus,
+      'comentarios': comentarios,
+      'nombreCliente': nombreCliente,
+      'creado': peticion,
+      'tipoPago': tipoPago,
+      'totalPedido': totalPedido,
+      'descuento': descuento,
+      'cantEfectivo': cantEfectivo,
+      'cantTarjeta': cantTarjeta,
+      'cantTransferencia': cantTransferencia,
+      'productos': productos.map((producto) => producto.toApi()).toList(),
+    };
+    Map<String, dynamic> basicoMap = super.toJson();
+    basicoMap.remove('id');
+    basicoMap.remove('eliminado');
+    apiMap.addAll(basicoMap);
+
+    return apiMap;
+  }
+
   Pedido.fromJson(Map<String, dynamic> json) {
   Pedido.fromJson(Map<String, dynamic> json) {
     super.parseJson(json);
     super.parseJson(json);
     id = (json['id'] as int?)!;
     id = (json['id'] as int?)!;
@@ -91,6 +125,8 @@ class Pedido extends Basico {
     cantEfectivo = Basico.parseDouble(json['cantEfectivo']);
     cantEfectivo = Basico.parseDouble(json['cantEfectivo']);
     cantTarjeta = Basico.parseDouble(json['cantTarjeta']);
     cantTarjeta = Basico.parseDouble(json['cantTarjeta']);
     cantTransferencia = Basico.parseDouble(json['cantTransferencia']);
     cantTransferencia = Basico.parseDouble(json['cantTransferencia']);
+    idWeb = Basico.parseInt(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
 
 
     List<PedidoProducto> _productos = [];
     List<PedidoProducto> _productos = [];
     if (json["productos"] != null && (json["productos"] as List).isNotEmpty) {
     if (json["productos"] != null && (json["productos"] as List).isNotEmpty) {

+ 17 - 0
lib/models/pedido_producto_model.dart

@@ -10,6 +10,8 @@ class PedidoProducto extends Basico {
   int? terminar;
   int? terminar;
   String? comentario;
   String? comentario;
   List<PedidoProductoTopping> toppings = [];
   List<PedidoProductoTopping> toppings = [];
+  int? idWeb;
+  String? sincronizado;
 
 
   PedidoProducto({
   PedidoProducto({
     super.id,
     super.id,
@@ -22,6 +24,8 @@ class PedidoProducto extends Basico {
     this.terminar,
     this.terminar,
     this.comentario,
     this.comentario,
     this.toppings = const [],
     this.toppings = const [],
+    this.idWeb,
+    this.sincronizado,
   });
   });
 
 
   @override
   @override
@@ -38,6 +42,17 @@ class PedidoProducto extends Basico {
     }..addAll(super.toJson());
     }..addAll(super.toJson());
   }
   }
 
 
+  Map<String, dynamic> toApi() {
+    return {
+      'idLocal': idProducto,
+      'nombre': producto?.nombre ?? '',
+      'costoUnitario': costoUnitario,
+      'descuento': descuento,
+      'cantidad': cantidad,
+      'comentario': comentario,
+    };
+  }
+
   PedidoProducto.fromJson(Map<String, dynamic> json) {
   PedidoProducto.fromJson(Map<String, dynamic> json) {
     super.parseJson(json);
     super.parseJson(json);
     idPedido = Basico.parseInt(json['idPedido']);
     idPedido = Basico.parseInt(json['idPedido']);
@@ -49,6 +64,8 @@ class PedidoProducto extends Basico {
     cantidad = Basico.parseInt(json['cantidad']);
     cantidad = Basico.parseInt(json['cantidad']);
     terminar = Basico.parseInt(json['terminar']);
     terminar = Basico.parseInt(json['terminar']);
     comentario = Basico.parseString(json['comentario']);
     comentario = Basico.parseString(json['comentario']);
+    idWeb = Basico.parseInt(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
     toppings = (json['toppings'] as List<dynamic>?)
     toppings = (json['toppings'] as List<dynamic>?)
             ?.map((topingJson) => PedidoProductoTopping.fromJson(
             ?.map((topingJson) => PedidoProductoTopping.fromJson(
                 topingJson as Map<String, dynamic>))
                 topingJson as Map<String, dynamic>))

+ 50 - 19
lib/models/producto_model.dart

@@ -14,9 +14,9 @@ class Producto extends Basico {
   int? verMenu;
   int? verMenu;
   String? codigo;
   String? codigo;
   String? descuento;
   String? descuento;
-  String? venceDescuento;
   int? toping;
   int? toping;
   List<Producto>? topings;
   List<Producto>? topings;
+  int? activo;
 
 
   Producto({
   Producto({
     super.id,
     super.id,
@@ -30,27 +30,37 @@ class Producto extends Basico {
     this.verMenu,
     this.verMenu,
     this.codigo,
     this.codigo,
     this.descuento,
     this.descuento,
-    this.venceDescuento,
     this.toping,
     this.toping,
     this.topings,
     this.topings,
+    this.activo,
   });
   });
 
 
   @override
   @override
   Map<String, dynamic> toJson() {
   Map<String, dynamic> toJson() {
+    print("Convirtiendo Producto a JSON");
+    print("ID: $id, Categoria: $idCategoria, Nombre: $nombre");
+    print("Descripcion: $descripcion, imagen: $imagen, venta: $venta");
+    print("existencia: $existencia, precio: $precio, verMenu: $verMenu");
+    print("codigo: $codigo, descuento: $descuento, creado: $creado");
+    print("eliminado: $eliminado, modificado: $modificado");
+
     return {
     return {
       'id': id,
       'id': id,
-      'idCategoria': idCategoria,
-      'nombre': nombre,
-      'descripcion': descripcion,
-      'imagen': imagen,
-      'venta': venta,
-      'existencia': existencia,
-      'precio': precio,
-      'verMenu': verMenu,
-      'codigo': codigo,
-      'descuento': descuento,
-      'venceDescuento': venceDescuento,
-      'toping': toping,
+      'idCategoria': idCategoria ?? 0,
+      'nombre': nombre ?? '',
+      'descripcion': descripcion ?? '',
+      'imagen': imagen ?? '',
+      'venta': venta ?? 0,
+      'existencia': existencia ?? 0,
+      'precio': precio ?? 0.0,
+      'verMenu': verMenu ?? 0,
+      'codigo': codigo ?? '',
+      'descuento': descuento ?? '',
+      'toping': toping ?? 0,
+      'activo': activo ?? 0,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
     }..addAll(super.toJson());
     }..addAll(super.toJson());
   }
   }
 
 
@@ -67,17 +77,20 @@ class Producto extends Basico {
       'verMenu': verMenu,
       'verMenu': verMenu,
       'codigo': codigo,
       'codigo': codigo,
       'descuento': descuento,
       'descuento': descuento,
-      'venceDescuento': venceDescuento,
       'toping': toping,
       'toping': toping,
+      'activo': activo,
+      'creado': creado != null ? creado!.toIso8601String() : null,
+      'modificado': modificado != null ? modificado!.toIso8601String() : null,
+      'eliminado': eliminado != null ? eliminado!.toIso8601String() : null,
     };
     };
   }
   }
 
 
   Producto.fromJson(Map<String, dynamic> json) {
   Producto.fromJson(Map<String, dynamic> json) {
     super.parseJson(json);
     super.parseJson(json);
     idCategoria = Basico.parseInt(json['idCategoria']);
     idCategoria = Basico.parseInt(json['idCategoria']);
-    categoria = json["categoria"] != null
-        ? CategoriaProducto.fromJson(json["categoria"])
-        : null;
+    // categoria = json["categoria"] != null
+    //     ? CategoriaProducto.fromJson(json["categoria"])
+    //     : null;
     nombre = Basico.parseString(json['nombre']);
     nombre = Basico.parseString(json['nombre']);
     descripcion = Basico.parseString(json['descripcion']);
     descripcion = Basico.parseString(json['descripcion']);
     imagen = Basico.parseString(json['imagen']);
     imagen = Basico.parseString(json['imagen']);
@@ -87,7 +100,7 @@ class Producto extends Basico {
     verMenu = Basico.parseInt(json['verMenu']);
     verMenu = Basico.parseInt(json['verMenu']);
     codigo = Basico.parseString(json['codigo']);
     codigo = Basico.parseString(json['codigo']);
     descuento = Basico.parseString(json['descuento']);
     descuento = Basico.parseString(json['descuento']);
-    venceDescuento = Basico.parseString(json['venceDescuento']);
+    activo = Basico.parseInt(json['activo']);
     if (json['toping'] is bool) {
     if (json['toping'] is bool) {
       toping = json['toping'] ? 1 : 0;
       toping = json['toping'] ? 1 : 0;
     } else {
     } else {
@@ -100,6 +113,24 @@ class Producto extends Basico {
     }
     }
   }
   }
 
 
+  Producto.fromApi(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idCategoria = Basico.parseInt(json['idCategoria']);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    imagen = Basico.parseString(json['imagen']);
+    venta = Basico.parseInt(json['venta']);
+    existencia = Basico.parseInt(json['existencia']);
+    precio = Basico.parseString(json['precio']);
+    verMenu = Basico.parseInt(json['verEnMenu']);
+    codigo = Basico.parseString(json['codigo']);
+    descuento = Basico.parseString(json['descuento']);
+    activo = Basico.parseInt(json['activo']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
   Future<void> guardar() async {
   Future<void> guardar() async {
     idLocal = await RepoService().guardar(this);
     idLocal = await RepoService().guardar(this);
   }
   }

+ 58 - 0
lib/services/base_service.dart

@@ -0,0 +1,58 @@
+import 'dart:convert';
+
+import '../data/session/session_storage.dart';
+import 'package:http/http.dart' as http;
+
+class BaseService {
+  int currentPage = 0;
+  int limit = 20;
+  int total = 0;
+  //String baseUrl = 'hermogas.est.api.rdsistemas.app';
+  //produccion: hermogas.est.api.rdsistemas.app
+  //prueba: hermogas.est.test.rdsistemas.app
+
+  String base_url = '';
+  String baseUrl = '';
+  Future<Map<String, String>> getDefaultHeaders({withAuth = true}) async {
+    Map<String, String> defaultHeaders = {'Content-Type': 'application/json'};
+
+    if (withAuth) {
+      String? token = await SessionStorage().getToken();
+      defaultHeaders['Authorization'] = 'Bearer ${token ?? ''}';
+    }
+
+    return defaultHeaders;
+  }
+
+  Future<http.Response> get(String endpoint,
+      {bool withAuth = true,
+      Map<String, String>? queryParameters,
+      Map<String, String>? headers}) async {
+    final uri = Uri.https(baseUrl, endpoint, queryParameters);
+    var defaultHeaders = await getDefaultHeaders(withAuth: withAuth);
+    var head = {...?headers, ...defaultHeaders};
+    return await http.get(uri, headers: head);
+  }
+
+  Future<http.Response> post(String endpoint,
+      {bool withAuth = true,
+      Map<String, String>? queryParameters,
+      Map<String, dynamic>? body,
+      Map<String, String>? headers}) async {
+    final uri = Uri.https(baseUrl, endpoint, queryParameters);
+    var defaultHeaders = await getDefaultHeaders(withAuth: withAuth);
+    var head = {...?headers, ...defaultHeaders};
+    return await http.post(uri, body: json.encode(body), headers: head);
+  }
+
+  Future<http.Response> delete(String endpoint,
+      {bool withAuth = true,
+      Map<String, String>? queryParameters,
+      Map<String, dynamic>? body,
+      Map<String, String>? headers}) async {
+    final uri = Uri.https(baseUrl, endpoint, queryParameters);
+    var defaultHeaders = await getDefaultHeaders(withAuth: withAuth);
+    var head = {...?headers, ...defaultHeaders};
+    return await http.delete(uri, body: json.encode(body), headers: head);
+  }
+}

+ 23 - 23
lib/services/productos_service.dart

@@ -16,33 +16,33 @@ class ProductosService {
   }
   }
 
 
   Future<void> fillCategoriaBD() async {
   Future<void> fillCategoriaBD() async {
-    final List<CategoriaProducto> categorias = [
-      CategoriaProducto(id: 1, nombre: 'SANDWICHES'),
-      CategoriaProducto(id: 2, nombre: 'BEBIDAS'),
-    ];
+    // final List<CategoriaProducto> categorias = [
+    //   CategoriaProducto(id: 1, nombre: 'SANDWICHES'),
+    //   CategoriaProducto(id: 2, nombre: 'BEBIDAS'),
+    // ];
 
 
-    RepoService<CategoriaProducto> repoService =
-        RepoService<CategoriaProducto>();
-    for (var categoria in categorias) {
-      await repoService.guardarLocal(categoria);
-    }
+    // RepoService<CategoriaProducto> repoService =
+    //     RepoService<CategoriaProducto>();
+    // for (var categoria in categorias) {
+    //   await repoService.guardarLocal(categoria);
+    // }
   }
   }
 
 
   Future<void> fillProductoBD() async {
   Future<void> fillProductoBD() async {
-    List<Producto> productos = [
-      Producto(
-          id: 1, idCategoria: 1, nombre: 'SANDWICH POLLO PARM', precio: '195'),
-      Producto(
-          id: 2, idCategoria: 1, nombre: 'SANDWICH MEATBALLS', precio: '195'),
-      Producto(
-          id: 3, idCategoria: 1, nombre: 'SANDWICH CAPRESE', precio: '180'),
-      Producto(id: 4, idCategoria: 2, nombre: 'AGUA', precio: '25'),
-      Producto(id: 5, idCategoria: 2, nombre: 'SODA BOTE', precio: '35'),
-    ];
+    // List<Producto> productos = [
+    //   Producto(
+    //       id: 1, idCategoria: 1, nombre: 'SANDWICH POLLO PARM', precio: '195'),
+    //   Producto(
+    //       id: 2, idCategoria: 1, nombre: 'SANDWICH MEATBALLS', precio: '195'),
+    //   Producto(
+    //       id: 3, idCategoria: 1, nombre: 'SANDWICH CAPRESE', precio: '180'),
+    //   Producto(id: 4, idCategoria: 2, nombre: 'AGUA', precio: '25'),
+    //   Producto(id: 5, idCategoria: 2, nombre: 'SODA BOTE', precio: '35'),
+    // ];
 
 
-    RepoService<Producto> repoService = RepoService<Producto>();
-    for (var producto in productos) {
-      await repoService.guardarLocal(producto);
-    }
+    // RepoService<Producto> repoService = RepoService<Producto>();
+    // for (var producto in productos) {
+    //   await repoService.guardarLocal(producto);
+    // }
   }
   }
 }
 }

+ 201 - 23
lib/services/repo_service.dart

@@ -6,7 +6,7 @@ import 'package:sqflite/sqflite.dart';
 import '../models/models.dart';
 import '../models/models.dart';
 
 
 class RepoService<T> {
 class RepoService<T> {
-  static int dbVersion = 8;
+  static int dbVersion = 13;
   static String dbName = 'olivamiapos01.db';
   static String dbName = 'olivamiapos01.db';
   static const String id = Basico.identificadorWeb;
   static const String id = Basico.identificadorWeb;
   static const String idLocal = Basico.identificadorLocal;
   static const String idLocal = Basico.identificadorLocal;
@@ -208,6 +208,55 @@ class RepoService<T> {
             )
             )
           ''');
           ''');
           break;
           break;
+
+        case 9:
+          await db.execute('''
+          ALTER TABLE Pedido ADD COLUMN idWeb INTEGER;
+        ''');
+          await db.execute('''
+          ALTER TABLE Pedido ADD COLUMN sincronizado TEXT;
+        ''');
+          await db.execute('''
+          ALTER TABLE PedidoProducto ADD COLUMN idWeb INTEGER;
+        ''');
+          await db.execute('''
+          ALTER TABLE PedidoProducto ADD COLUMN sincronizado TEXT;
+        ''');
+          break;
+
+        case 10:
+          await db.execute('''
+          update Pedido set sincronizado = null, peticion = strftime('%Y-%m-%dT%H:%M:%S',
+                datetime(substr(peticion, 7, 4) || '-' ||
+                substr(peticion, 4, 2) || '-' ||
+                substr(peticion, 1, 2) || ' ' ||
+                substr(peticion, 12, 8) || ' -07:00', 'localtime', '+07:00'))
+          WHERE strftime('%Y-%m-%dT%H:%M:%S',
+                datetime(substr(peticion, 7, 4) || '-' ||
+                substr(peticion, 4, 2) || '-' ||
+                substr(peticion, 1, 2) || ' ' ||
+                substr(peticion, 12, 8) || ' -07:00', 'localtime', '+07:00')) is not null
+        ''');
+          break;
+
+        case 12:
+          await db.execute('''
+            ALTER TABLE CategoriaProducto ADD COLUMN creado TEXT;
+          ''');
+          await db.execute('''
+            ALTER TABLE CategoriaProducto ADD COLUMN modificado TEXT;
+          ''');
+          await db.execute('''
+            ALTER TABLE Producto ADD COLUMN creado TEXT;
+          ''');
+          await db.execute('''
+            ALTER TABLE Producto ADD COLUMN modificado TEXT;
+          ''');
+          await db.execute('''
+            ALTER TABLE Producto ADD COLUMN activo INTEGER;
+          ''');
+
+          break;
       }
       }
       oldVersion++;
       oldVersion++;
     }
     }
@@ -215,36 +264,63 @@ class RepoService<T> {
 
 
   Future<int> guardar(T model) async {
   Future<int> guardar(T model) async {
     try {
     try {
-      var dbClient = await db;
-      String nombreTabla = model.runtimeType.toString();
+      print("Guardando modelo en la base de datos: ${model.runtimeType}");
+
+      // Comprobar si es un modelo que obtiene las fechas del servidor (Producto o CategoriaProducto)
+      if (model is Producto || model is CategoriaProducto) {
+        // No hacer nada, las fechas vienen del servidor
+        print("Usando fechas del servidor para ${model.runtimeType}");
+      } else if (model is Basico) {
+        // Generar las fechas localmente para otros modelos
+        asignarFechasLocalmente(model);
+      }
+
+      // Convertir el modelo a JSON para la base de datos
       String modelo = json.encode(model, toEncodable: toEncodable);
       String modelo = json.encode(model, toEncodable: toEncodable);
+      print("Modelo convertido a JSON: $modelo");
+
       Map<String, dynamic> modelMap = json.decode(modelo);
       Map<String, dynamic> modelMap = json.decode(modelo);
+      var dbClient = await db;
+      String nombreTabla = model.runtimeType.toString();
+
+      int? id = modelMap['id'];
 
 
-      int id = 0;
-      if (modelMap['id'] != null && modelMap['id'] != 0) {
+      if (id == null || id == 0) {
+        modelMap.remove('id');
+      }
+
+      List<Map> existing = id != null && id > 0
+          ? await dbClient!.query(nombreTabla, where: 'id = ?', whereArgs: [id])
+          : [];
+
+      if (existing.isNotEmpty) {
+        print("Actualizando registro existente con ID: $id");
         await dbClient!.update(
         await dbClient!.update(
           nombreTabla,
           nombreTabla,
           modelMap,
           modelMap,
           where: 'id = ?',
           where: 'id = ?',
-          whereArgs: [modelMap['id']],
+          whereArgs: [id],
         );
         );
-        id = modelMap['id'];
       } else {
       } else {
-        modelMap.remove('id');
+        print("Insertando nuevo registro en la tabla $nombreTabla");
         id = await dbClient!.insert(nombreTabla, modelMap);
         id = await dbClient!.insert(nombreTabla, modelMap);
       }
       }
 
 
-      if (model is Producto) {
-        await _guardarToppings(dbClient, id, model.topings);
-      }
-
-      return id;
+      return id!;
     } catch (e) {
     } catch (e) {
-      print('Error al guardar en $T: $e');
+      print('Error al guardar en dynamic: $e');
       return 0;
       return 0;
     }
     }
   }
   }
 
 
+  void asignarFechasLocalmente(Basico model) {
+    DateTime ahora = DateTime.now();
+    if (model.creado == null) {
+      model.creado = ahora.toUtc();
+    }
+    model.modificado = ahora.toUtc();
+  }
+
   Future<void> _guardarToppings(
   Future<void> _guardarToppings(
       Database db, int idProducto, List<Producto>? topings) async {
       Database db, int idProducto, List<Producto>? topings) async {
     await db.delete('ProductoTopping',
     await db.delete('ProductoTopping',
@@ -307,6 +383,7 @@ class RepoService<T> {
   }
   }
 
 
   dynamic toEncodable(dynamic item) {
   dynamic toEncodable(dynamic item) {
+    print("Serializando objeto: $item");
     if (item is Pedido) {
     if (item is Pedido) {
       return item.toJson();
       return item.toJson();
     } else if (item is PedidoProducto) {
     } else if (item is PedidoProducto) {
@@ -327,7 +404,8 @@ class RepoService<T> {
   Future<List<T>> obtenerTodos({String orderBy = 'id DESC'}) async {
   Future<List<T>> obtenerTodos({String orderBy = 'id DESC'}) async {
     var db = await this.db;
     var db = await this.db;
     String tableName = T.toString();
     String tableName = T.toString();
-    var result = await db!.query(tableName, orderBy: orderBy);
+    var result = await db!
+        .query(tableName, where: 'eliminado IS NULL', orderBy: orderBy);
     return result.map((map) => fromMap<T>(map)).toList();
     return result.map((map) => fromMap<T>(map)).toList();
   }
   }
 
 
@@ -361,6 +439,26 @@ class RepoService<T> {
         .toList();
         .toList();
   }
   }
 
 
+  Future<List<Deposito>> obtenerDepositosPorIdCorteCaja(int idCorteCaja) async {
+    var dbClient = await db;
+    List<Map<String, dynamic>> maps = await dbClient!.query(
+      'Deposito',
+      where: 'idCorteCaja = ?',
+      whereArgs: [idCorteCaja],
+    );
+    return maps.map((map) => Deposito.fromJson(map)).toList();
+  }
+
+  Future<List<Gasto>> obtenerGastosPorIdCorteCaja(int idCorteCaja) async {
+    var dbClient = await db;
+    List<Map<String, dynamic>> maps = await dbClient!.query(
+      'Gasto',
+      where: 'idCorteCaja = ?',
+      whereArgs: [idCorteCaja],
+    );
+    return maps.map((map) => Gasto.fromJson(map)).toList();
+  }
+
   Future<int> contarPedidos() async {
   Future<int> contarPedidos() async {
     Database? dbClient = await db;
     Database? dbClient = await db;
     var result = await dbClient!.rawQuery('SELECT COUNT(*) FROM Pedido');
     var result = await dbClient!.rawQuery('SELECT COUNT(*) FROM Pedido');
@@ -433,17 +531,18 @@ class RepoService<T> {
       DateTime startDate, DateTime endDate) async {
       DateTime startDate, DateTime endDate) async {
     var dbClient = await db;
     var dbClient = await db;
 
 
-    String startDateString =
-        DateFormat('yyyy-MM-dd 00:00:00').format(startDate);
-    String endDateString = DateFormat('yyyy-MM-dd 23:59:59').format(endDate);
+    String startDateString = startDate.toIso8601String();
+    String endDateString = endDate.toIso8601String();
+
+    print(
+        'Ejecutando consulta: SELECT * FROM Pedido WHERE peticion BETWEEN $startDateString AND $endDateString');
 
 
     List<Map<String, dynamic>> maps = await dbClient!.rawQuery('''
     List<Map<String, dynamic>> maps = await dbClient!.rawQuery('''
     SELECT * FROM Pedido 
     SELECT * FROM Pedido 
-    WHERE 
-      (datetime(substr(peticion, 7, 4) || '-' || substr(peticion, 4, 2) || '-' || substr(peticion, 1, 2) || ' ' || substr(peticion, 12)) BETWEEN ? AND ?)
-    OR 
-      (datetime(substr(peticion, 7, 4) || '-' || substr(peticion, 1, 2) || '-' || substr(peticion, 4, 2) || ' ' || substr(peticion, 12)) BETWEEN ? AND ?)
-  ''', [startDateString, endDateString, startDateString, endDateString]);
+    WHERE peticion BETWEEN ? AND ?
+  ''', [startDateString, endDateString]);
+
+    print('Resultado de la consulta: ${maps.length} pedidos encontrados.');
 
 
     return maps.map((map) => Pedido.fromJson(map)).toList();
     return maps.map((map) => Pedido.fromJson(map)).toList();
   }
   }
@@ -493,4 +592,83 @@ class RepoService<T> {
     return await dbClient!
     return await dbClient!
         .delete('Descuento', where: 'id = ?', whereArgs: [id]);
         .delete('Descuento', where: 'id = ?', whereArgs: [id]);
   }
   }
+
+  Future<Variable?> obtenerPorNombre(String nombre) async {
+    var dbClient = await db;
+    List<Map<String, dynamic>> maps = await dbClient!.query(
+      'Variable',
+      where: 'nombre = ?',
+      whereArgs: [nombre],
+    );
+
+    if (maps.isNotEmpty) {
+      return Variable.fromJson(Map<String, dynamic>.from(maps.first));
+    }
+
+    return null;
+  }
+
+  Future<List<Pedido>> obtenerPedidosOrdenadosPorFecha() async {
+    var dbClient = await db;
+
+    String orderBy =
+        "datetime(substr(peticion, 7, 4) || '-' || substr(peticion, 4, 2) || '-' || substr(peticion, 1, 2) || ' ' || substr(peticion, 12)) ASC";
+
+    List<Map<String, dynamic>> result = await dbClient!.query(
+      'Pedido',
+      where: 'sincronizado IS NULL',
+      orderBy: orderBy,
+    );
+
+    return result.map((map) => Pedido.fromJson(map)).toList();
+  }
+
+  Future<void> sincronizarCategorias(
+      List<CategoriaProducto> categoriasApi) async {
+    var db = await RepoService().db;
+
+    var categoriasLocalesQuery = await db!.query('CategoriaProducto');
+    List<CategoriaProducto> categoriasLocales = categoriasLocalesQuery
+        .map((e) => CategoriaProducto.fromJson(e))
+        .toList();
+
+    for (var categoriaApi in categoriasApi) {
+      var categoriaLocal = categoriasLocales.firstWhere(
+        (categoria) => categoria.id == categoriaApi.id,
+        orElse: () => CategoriaProducto(),
+      );
+
+      if (categoriaLocal.id != 0 &&
+          categoriaApi.modificado != null &&
+          (categoriaLocal.modificado == null ||
+              categoriaApi.modificado!.isAfter(categoriaLocal.modificado!))) {
+        await RepoService().guardar(categoriaApi);
+      } else if (categoriaLocal.id == 0) {
+        await RepoService().guardar(categoriaApi);
+      }
+    }
+  }
+
+  Future<void> sincronizarProductos(List<Producto> productosApi) async {
+    var db = await RepoService().db;
+
+    var productosLocalesQuery = await db!.query('Producto');
+    List<Producto> productosLocales =
+        productosLocalesQuery.map((e) => Producto.fromJson(e)).toList();
+
+    for (var productoApi in productosApi) {
+      var productoLocal = productosLocales.firstWhere(
+        (producto) => producto.id == productoApi.id,
+        orElse: () => Producto(),
+      );
+
+      if (productoLocal.id != 0 &&
+          productoApi.modificado != null &&
+          productoApi.modificado!.isAfter(productoLocal.modificado!)) {
+        await RepoService().guardar(productoApi);
+      } else if (productoLocal.id == 0) {
+        await RepoService().guardar(productoApi);
+      }
+    }
+  }
 }
 }

+ 1 - 0
lib/services/services.dart

@@ -1 +1,2 @@
 export '../services/repo_service.dart';
 export '../services/repo_service.dart';
+export '../services/base_service.dart';

+ 5 - 5
lib/viewmodels/categoria_producto_view_model.dart

@@ -39,20 +39,20 @@ class CategoriaProductoViewModel extends ChangeNotifier {
   }
   }
 
 
   Future<void> fetchLocalAll({int page = 1}) async {
   Future<void> fetchLocalAll({int page = 1}) async {
-    _currentPage = page; // Ajusta la página actual
+    _currentPage = page;
     var db = await RepoService().db;
     var db = await RepoService().db;
 
 
-    // Primero, obtén el total de productos para calcular el total de páginas
     int? count = Sqflite.firstIntValue(
     int? count = Sqflite.firstIntValue(
         await db!.rawQuery('SELECT COUNT(*) FROM CategoriaProducto'));
         await db!.rawQuery('SELECT COUNT(*) FROM CategoriaProducto'));
     _totalProducts = count ?? 0;
     _totalProducts = count ?? 0;
 
 
-    // Calcular el offset basado en la página actual y el límite
     int offset = (_limit * (page - 1));
     int offset = (_limit * (page - 1));
 
 
-    // Consulta con paginación
     var query = await db.query('CategoriaProducto',
     var query = await db.query('CategoriaProducto',
-        orderBy: 'id asc', limit: _limit, offset: offset);
+        where: 'eliminado IS NULL',
+        orderBy: 'id asc',
+        limit: _limit,
+        offset: offset);
     _categoriaProductos =
     _categoriaProductos =
         query.map((element) => CategoriaProducto.fromJson(element)).toList();
         query.map((element) => CategoriaProducto.fromJson(element)).toList();
     notifyListeners();
     notifyListeners();

+ 94 - 0
lib/viewmodels/pedido_view_model.dart

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:intl/intl.dart';
 import 'package:intl/intl.dart';
 import 'package:sqflite/sqflite.dart';
 import 'package:sqflite/sqflite.dart';
 
 
+import '../data/api_response.dart';
 import '../models/models.dart';
 import '../models/models.dart';
 import '../services/services.dart';
 import '../services/services.dart';
 
 
@@ -208,7 +209,15 @@ class PedidoViewModel extends ChangeNotifier {
       DateTime startDate, DateTime endDate) async {
       DateTime startDate, DateTime endDate) async {
     setIsLoading(true);
     setIsLoading(true);
     RepoService<Pedido> repoPedido = RepoService<Pedido>();
     RepoService<Pedido> repoPedido = RepoService<Pedido>();
+
+    print('Consulta SQL de pedidos desde: $startDate hasta: $endDate');
+
     List<Pedido> pedidos = await repoPedido.buscarPorFecha(startDate, endDate);
     List<Pedido> pedidos = await repoPedido.buscarPorFecha(startDate, endDate);
+
+    print('Pedidos obtenidos desde la base de datos: ${pedidos.length}');
+    pedidos.forEach((pedido) => print(
+        'Pedido Folio: ${pedido.folio}, Estatus: ${pedido.estatus}, Total: ${pedido.totalPedido}'));
+
     setIsLoading(false);
     setIsLoading(false);
     notifyListeners();
     notifyListeners();
     return pedidos;
     return pedidos;
@@ -220,4 +229,89 @@ class PedidoViewModel extends ChangeNotifier {
         where: 'id = ?', whereArgs: [idPedido]);
         where: 'id = ?', whereArgs: [idPedido]);
     fetchLocalPedidosForScreen();
     fetchLocalPedidosForScreen();
   }
   }
+
+  Future<bool> sincronizarPedidos() async {
+    List<Pedido> pedidosNoSincronizados =
+        await fetchAllLocalPedidosOrdenadosPorFecha();
+
+    if (pedidosNoSincronizados.isNotEmpty) {
+      Pedido pedidoNoSincronizado = pedidosNoSincronizados.first;
+
+      if (pedidoNoSincronizado.productos.isEmpty) {
+        pedidoNoSincronizado =
+            await fetchPedidoConProductos(pedidoNoSincronizado.id) ??
+                pedidoNoSincronizado;
+      }
+
+      Map<String, dynamic> pedidoJson =
+          await prepararPedidoParaApi(pedidoNoSincronizado);
+
+      print('JSON enviado: $pedidoJson');
+
+      var response = ApiResponse(await BaseService()
+          .post('/pos/pedido/sincronizar', body: pedidoJson));
+
+      if (response.isOk && response.detalle != null) {
+        int idWeb = response.detalle!['id'];
+        String sincronizado = response.detalle!['sincronizado'];
+
+        await actualizarPedidoSincronizado(
+            pedidoNoSincronizado.id!, idWeb, sincronizado);
+
+        return true;
+      } else {
+        print('Error en la sincronización del pedido: ${response.mensaje}');
+        return true;
+      }
+    } else {
+      print('No se encontraron pedidos no sincronizados.');
+      return false;
+    }
+  }
+
+  Future<void> actualizarPedidoSincronizado(
+      int idPedido, int idWeb, String sincronizado) async {
+    var db = await RepoService().db;
+
+    await db!.update(
+      'Pedido',
+      {
+        'idWeb': idWeb,
+        'sincronizado': sincronizado,
+      },
+      where: 'id = ?',
+      whereArgs: [idPedido],
+    );
+  }
+
+  Future<List<Pedido>> fetchAllLocalPedidosOrdenadosPorFecha() async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+
+    List<Pedido> allPedidos =
+        await repoPedido.obtenerPedidosOrdenadosPorFecha();
+
+    setIsLoading(false);
+    return allPedidos;
+  }
+}
+
+Future<Map<String, dynamic>> prepararPedidoParaApi(Pedido pedido) async {
+  String? claveSucursal = await obtenerClaveSucursal();
+
+  Map<String, dynamic> apiMap = pedido.toApi();
+
+  apiMap['claveSucursal'] = claveSucursal;
+
+  if (pedido.idWeb != null && pedido.idWeb! > 0) {
+    apiMap['idWeb'] = pedido.idWeb;
+  }
+
+  return apiMap;
+}
+
+Future<String?> obtenerClaveSucursal() async {
+  RepoService<Variable> repoVariable = RepoService<Variable>();
+  Variable? sucursalVariable = await repoVariable.obtenerPorNombre('Sucursal');
+  return sucursalVariable?.clave;
 }
 }

+ 83 - 9
lib/viewmodels/producto_view_model.dart

@@ -1,7 +1,11 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:sqflite/sqflite.dart';
 import 'package:sqflite/sqflite.dart';
+
+import '../data/api_response.dart';
+import '../services/base_service.dart';
 import '../models/models.dart';
 import '../models/models.dart';
 import '../services/services.dart';
 import '../services/services.dart';
+import '../services/repo_service.dart';
 
 
 class ProductoViewModel<T> extends ChangeNotifier {
 class ProductoViewModel<T> extends ChangeNotifier {
   String _busqueda = "";
   String _busqueda = "";
@@ -45,7 +49,10 @@ class ProductoViewModel<T> extends ChangeNotifier {
     int offset = (_limit * (page - 1));
     int offset = (_limit * (page - 1));
 
 
     var query = await db.query('Producto',
     var query = await db.query('Producto',
-        orderBy: 'idLocal asc', limit: _limit, offset: offset);
+        where: 'eliminado IS NULL',
+        orderBy: 'idLocal asc',
+        limit: _limit,
+        offset: offset);
     _productos = query.map((element) => Producto.fromJson(element)).toList();
     _productos = query.map((element) => Producto.fromJson(element)).toList();
 
 
     notifyListeners();
     notifyListeners();
@@ -69,7 +76,7 @@ class ProductoViewModel<T> extends ChangeNotifier {
   Future<void> fetchAllByCategory(int idCategoria) async {
   Future<void> fetchAllByCategory(int idCategoria) async {
     var db = await RepoService().db;
     var db = await RepoService().db;
     var query = await db!.query('Producto',
     var query = await db!.query('Producto',
-        where: 'idCategoria = ?',
+        where: 'idCategoria = ? and eliminado IS NULL',
         whereArgs: [idCategoria],
         whereArgs: [idCategoria],
         orderBy: 'idLocal asc');
         orderBy: 'idLocal asc');
     _productos = query.map((e) => Producto.fromJson(e)).toList();
     _productos = query.map((e) => Producto.fromJson(e)).toList();
@@ -80,15 +87,11 @@ class ProductoViewModel<T> extends ChangeNotifier {
     var db = await RepoService().db;
     var db = await RepoService().db;
     var query = await db!.query(
     var query = await db!.query(
       'Producto',
       'Producto',
-      where: 'nombre LIKE "%$nombre%"',
+      where: 'nombre LIKE ?',
+      whereArgs: ['%$nombre%'],
       orderBy: 'idLocal asc',
       orderBy: 'idLocal asc',
     );
     );
-    List<Producto> aux = [];
-    for (var element in query) {
-      Producto producto = Producto.fromJson(element);
-      aux.add(producto);
-    }
-    _productos = aux;
+    _productos = query.map((e) => Producto.fromJson(e)).toList();
     notifyListeners();
     notifyListeners();
   }
   }
 
 
@@ -175,6 +178,77 @@ class ProductoViewModel<T> extends ChangeNotifier {
     return null;
     return null;
   }
   }
 
 
+  Future<bool> sincronizarCategorias() async {
+    try {
+      final response = ApiResponse(await BaseService().get('/pos/categoria'));
+
+      print(response.resultados);
+
+      if (response.isOk && response.resultados != null) {
+        List<CategoriaProducto> categoriasApi = response.resultados!
+            .map((json) => CategoriaProducto.fromApi(json))
+            .toList();
+
+        if (categoriasApi.isNotEmpty) {
+          await RepoService().sincronizarCategorias(categoriasApi);
+          notifyListeners();
+          return true;
+        }
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar categorías: $e');
+      return false;
+    }
+  }
+
+  Future<bool> sincronizarProductos() async {
+    try {
+      Map<String, String> parametros = {"limite": "-1"};
+      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) {
+          await RepoService().sincronizarProductos(productosApi);
+          notifyListeners();
+          return true;
+        }
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar productos: $e');
+      return false;
+    }
+  }
+
+  Future<void> sincronizarProductosYCategorias() async {
+    print('Sincronizando productos');
+    setIsLoading(true);
+    try {
+      bool categoriasSincronizadas = await sincronizarCategorias();
+      print('Categorias sincronizadas: $categoriasSincronizadas');
+
+      if (categoriasSincronizadas) {
+        bool productosSincronizados = await sincronizarProductos();
+        if (productosSincronizados) {
+          await fetchLocalAll();
+        }
+        print('Productos sincronizados: $productosSincronizados');
+      }
+      notifyListeners();
+    } catch (e, stackTrace) {
+      // Capturar el mensaje detallado del error
+      throw Exception(
+          "Error al sincronizar productos y categorías: $e\n$stackTrace");
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
   void setIsLoading(bool loading) {
   void setIsLoading(bool loading) {
     _isLoading = loading;
     _isLoading = loading;
     notifyListeners();
     notifyListeners();

+ 52 - 1
lib/views/categoria_producto/categoria_producto_screen.dart

@@ -16,6 +16,7 @@ class CategoriaProductoScreen extends StatefulWidget {
 class _CategoriaProductoScreenState extends State<CategoriaProductoScreen> {
 class _CategoriaProductoScreenState extends State<CategoriaProductoScreen> {
   final _busqueda = TextEditingController(text: '');
   final _busqueda = TextEditingController(text: '');
   ScrollController horizontalScrollController = ScrollController();
   ScrollController horizontalScrollController = ScrollController();
+  int _versionTapCount = 0;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -45,6 +46,42 @@ class _CategoriaProductoScreenState extends State<CategoriaProductoScreen> {
     });
     });
   }
   }
 
 
+  void _mostrarResultado(BuildContext context, String mensaje, bool exito) {
+    showDialog(
+      context: context,
+      builder: (BuildContext context) {
+        return AlertDialog(
+          title: Text(
+              exito ? 'Sincronización exitosa' : 'Error de sincronización'),
+          content: SingleChildScrollView(
+            child: Text(mensaje),
+          ),
+          actions: [
+            TextButton(
+              child: Text('OK'),
+              onPressed: () {
+                Navigator.of(context).pop();
+              },
+            ),
+          ],
+        );
+      },
+    );
+  }
+
+  Future<void> _sincronizarProductos(BuildContext context) async {
+    final productoViewModel =
+        Provider.of<ProductoViewModel>(context, listen: false);
+
+    try {
+      await productoViewModel.sincronizarProductosYCategorias();
+      _mostrarResultado(
+          context, 'La sincronización se completó exitosamente.', true);
+    } catch (e) {
+      _mostrarResultado(context, e.toString(), false);
+    }
+  }
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final model = Provider.of<CategoriaProductoViewModel>(context);
     final model = Provider.of<CategoriaProductoViewModel>(context);
@@ -67,7 +104,6 @@ class _CategoriaProductoScreenState extends State<CategoriaProductoScreen> {
               PopupMenuItem(
               PopupMenuItem(
                 child: const Text('Eliminar'),
                 child: const Text('Eliminar'),
                 onTap: () async {
                 onTap: () async {
-                  // Retrasa la ejecución para permitir que se cierre el menú popup.
                   await Future.delayed(Duration(milliseconds: 100));
                   await Future.delayed(Duration(milliseconds: 100));
                   bool confirmado = await showDialog<bool>(
                   bool confirmado = await showDialog<bool>(
                         context: context,
                         context: context,
@@ -165,6 +201,21 @@ class _CategoriaProductoScreenState extends State<CategoriaProductoScreen> {
               TextStyle(color: AppTheme.secondary, fontWeight: FontWeight.w500),
               TextStyle(color: AppTheme.secondary, fontWeight: FontWeight.w500),
         ),
         ),
         iconTheme: IconThemeData(color: AppTheme.secondary),
         iconTheme: IconThemeData(color: AppTheme.secondary),
+        actions: [
+          TextButton.icon(
+            icon: Icon(Icons.sync, color: AppTheme.secondary),
+            label: Text(
+              "Sincronizar",
+              style: TextStyle(
+                  color: AppTheme.secondary,
+                  fontWeight: FontWeight.w500,
+                  fontSize: 18),
+            ),
+            onPressed: () async {
+              await _sincronizarProductos(context);
+            },
+          )
+        ],
       ),
       ),
       body: Column(
       body: Column(
         children: [
         children: [

+ 16 - 4
lib/views/pedido/pedido_csv.dart

@@ -1,5 +1,4 @@
 import 'dart:convert';
 import 'dart:convert';
-
 import 'package:csv/csv.dart';
 import 'package:csv/csv.dart';
 import 'package:intl/intl.dart';
 import 'package:intl/intl.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:path_provider/path_provider.dart';
@@ -18,12 +17,12 @@ Future<void> exportarPedidosACSV(List<Pedido> pedidos, String fileName) async {
       "Toppings",
       "Toppings",
       "Descuento (%)",
       "Descuento (%)",
       "Estado",
       "Estado",
-      "Fecha",
       "Total con Descuento",
       "Total con Descuento",
       "Tipo de Pago",
       "Tipo de Pago",
       "Cantidad Efectivo",
       "Cantidad Efectivo",
       "Cantidad Tarjeta",
       "Cantidad Tarjeta",
-      "Cantidad Transferencia"
+      "Cantidad Transferencia",
+      "Fecha",
     ]
     ]
   ];
   ];
 
 
@@ -56,6 +55,9 @@ Future<void> exportarPedidosACSV(List<Pedido> pedidos, String fileName) async {
       double precioDescuento = subtotal * (descuento / 100);
       double precioDescuento = subtotal * (descuento / 100);
       double totalConDescuento = subtotal - precioDescuento;
       double totalConDescuento = subtotal - precioDescuento;
 
 
+      // Aquí es donde formateamos la fecha a la zona horaria local
+      String formattedFecha = _formatDateTime(pedido.peticion);
+
       List<dynamic> row = [
       List<dynamic> row = [
         pedido.folio,
         pedido.folio,
         pedido.nombreCliente,
         pedido.nombreCliente,
@@ -65,12 +67,12 @@ Future<void> exportarPedidosACSV(List<Pedido> pedidos, String fileName) async {
         toppingsText,
         toppingsText,
         descuento,
         descuento,
         pedido.estatus,
         pedido.estatus,
-        pedido.peticion ?? '',
         formatCurrency(totalConDescuento),
         formatCurrency(totalConDescuento),
         pedido.tipoPago ?? 'No especificado',
         pedido.tipoPago ?? 'No especificado',
         formatCurrency(pedido.cantEfectivo ?? 0.0),
         formatCurrency(pedido.cantEfectivo ?? 0.0),
         formatCurrency(pedido.cantTarjeta ?? 0.0),
         formatCurrency(pedido.cantTarjeta ?? 0.0),
         formatCurrency(pedido.cantTransferencia ?? 0.0),
         formatCurrency(pedido.cantTransferencia ?? 0.0),
+        formattedFecha,
       ];
       ];
       rows.add(row);
       rows.add(row);
     }
     }
@@ -88,6 +90,16 @@ Future<void> exportarPedidosACSV(List<Pedido> pedidos, String fileName) async {
   print('Archivo CSV guardado en $path');
   print('Archivo CSV guardado en $path');
 }
 }
 
 
+// Formato de la fecha a hora local, similar a lo que ya hiciste en tu pantalla
+String _formatDateTime(String? dateTimeString) {
+  if (dateTimeString == null) return "Sin fecha";
+
+  DateTime parsedDate = DateTime.parse(dateTimeString);
+
+  var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
+  return formatter.format(parsedDate.toLocal()); // Convertimos a hora local
+}
+
 String formatCurrency(double amount) {
 String formatCurrency(double amount) {
   final format = NumberFormat("#,##0.00", "es_MX");
   final format = NumberFormat("#,##0.00", "es_MX");
   return format.format(amount);
   return format.format(amount);

+ 27 - 5
lib/views/pedido/pedido_detalle_screen.dart

@@ -50,12 +50,25 @@ class PedidoDetalleScreen extends StatelessWidget {
               color: Colors.white,
               color: Colors.white,
               child: Column(
               child: Column(
                 children: [
                 children: [
+                  // Colocamos la fecha y el cliente en una misma fila
                   ListTile(
                   ListTile(
-                    title: Text(
-                      'Cliente: ${pedido.nombreCliente}',
-                      style:
-                          TextStyle(fontWeight: FontWeight.bold, fontSize: 22),
+                    title: Row(
+                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                      children: [
+                        Text(
+                          'Cliente: ${pedido.nombreCliente}',
+                          style: TextStyle(
+                              fontWeight: FontWeight.bold, fontSize: 22),
+                        ),
+                        Text(
+                          'Fecha: ${_formatDateTime(pedido.peticion)}',
+                          style: TextStyle(
+                              fontWeight: FontWeight.bold, fontSize: 22),
+                        ),
+                      ],
                     ),
                     ),
+                  ),
+                  ListTile(
                     subtitle: Text(
                     subtitle: Text(
                       'Comentarios: ${pedido.comentarios}',
                       'Comentarios: ${pedido.comentarios}',
                       style:
                       style:
@@ -70,7 +83,7 @@ class PedidoDetalleScreen extends StatelessWidget {
                         fontWeight: FontWeight.bold,
                         fontWeight: FontWeight.bold,
                       ),
                       ),
                     ),
                     ),
-                  )
+                  ),
                 ],
                 ],
               ),
               ),
             ),
             ),
@@ -313,4 +326,13 @@ class PedidoDetalleScreen extends StatelessWidget {
       ],
       ],
     );
     );
   }
   }
+
+  String _formatDateTime(String? dateTimeString) {
+    if (dateTimeString == null) return "Sin fecha";
+
+    DateTime parsedDate = DateTime.parse(dateTimeString);
+
+    var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
+    return formatter.format(parsedDate.toLocal());
+  }
 }
 }

+ 577 - 268
lib/views/pedido/pedido_form.dart

@@ -2,6 +2,7 @@ import 'dart:async';
 import 'dart:io';
 import 'dart:io';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:intl/intl.dart';
 import 'package:intl/intl.dart';
 import 'package:provider/provider.dart';
 import 'package:provider/provider.dart';
 import '../pedido/pedido_ticket.dart';
 import '../pedido/pedido_ticket.dart';
@@ -32,6 +33,7 @@ class _PedidoFormState extends State<PedidoForm> {
   bool _estadoBusqueda = false;
   bool _estadoBusqueda = false;
   Pedido? pedidoActual;
   Pedido? pedidoActual;
   ScrollController _gridViewController = ScrollController();
   ScrollController _gridViewController = ScrollController();
+  ScrollController _categoryScrollController = ScrollController();
   final _searchController = TextEditingController();
   final _searchController = TextEditingController();
   final NumberFormat _numberFormat = NumberFormat.decimalPattern('es_MX');
   final NumberFormat _numberFormat = NumberFormat.decimalPattern('es_MX');
   int? selectedDescuento = 0;
   int? selectedDescuento = 0;
@@ -91,12 +93,22 @@ class _PedidoFormState extends State<PedidoForm> {
     Provider.of<DescuentoViewModel>(context, listen: false).cargarDescuentos();
     Provider.of<DescuentoViewModel>(context, listen: false).cargarDescuentos();
   }
   }
 
 
-  _onSearchChanged(String value) {
+  void _onSearchChanged(String value) async {
     if (value.isEmpty) {
     if (value.isEmpty) {
-      cargarProductosIniciales();
+      cargarProductosPorCategoria(
+          categoriaSeleccionada?.id ?? categorias.first.id);
     } else {
     } else {
-      Provider.of<ProductoViewModel>(context, listen: false)
+      setState(() {
+        _estadoBusqueda = true;
+      });
+
+      await Provider.of<ProductoViewModel>(context, listen: false)
           .fetchLocalByName(nombre: value);
           .fetchLocalByName(nombre: value);
+
+      setState(() {
+        productos =
+            Provider.of<ProductoViewModel>(context, listen: false).productos;
+      });
     }
     }
   }
   }
 
 
@@ -158,6 +170,9 @@ class _PedidoFormState extends State<PedidoForm> {
     String errorMessage = '';
     String errorMessage = '';
     double faltante = totalPedido;
     double faltante = totalPedido;
     bool totalCompletado = false;
     bool totalCompletado = false;
+    bool efectivoCompleto = false;
+    bool tarjetaCompleto = false;
+    bool transferenciaCompleto = false;
 
 
     void _calcularCambio(StateSetter setState) {
     void _calcularCambio(StateSetter setState) {
       double totalPagado = (double.tryParse(efectivoController.text) ?? 0) +
       double totalPagado = (double.tryParse(efectivoController.text) ?? 0) +
@@ -174,6 +189,13 @@ class _PedidoFormState extends State<PedidoForm> {
           faltante = 0;
           faltante = 0;
           totalCompletado = true;
           totalCompletado = true;
         }
         }
+
+        // Si el total ha sido alcanzado o excedido, desactivar otros métodos de pago
+        if (totalPagado >= totalPedido) {
+          if (!efectivoSeleccionado) efectivoSeleccionado = false;
+          if (!tarjetaSeleccionada) tarjetaSeleccionada = false;
+          if (!transferenciaSeleccionada) transferenciaSeleccionada = false;
+        }
       });
       });
     }
     }
 
 
@@ -188,254 +210,516 @@ class _PedidoFormState extends State<PedidoForm> {
       _calcularCambio(setState);
       _calcularCambio(setState);
     }
     }
 
 
+    bool _isPaymentOptionEnabled(bool isSelected) {
+      return !totalCompletado || isSelected;
+    }
+
     bool? shouldSave = await showDialog<bool>(
     bool? shouldSave = await showDialog<bool>(
       context: context,
       context: context,
       builder: (BuildContext context) {
       builder: (BuildContext context) {
         return StatefulBuilder(
         return StatefulBuilder(
           builder: (context, setState) {
           builder: (context, setState) {
-            return AlertDialog(
-              actionsPadding: EdgeInsets.fromLTRB(50, 10, 50, 30),
-              title: const Text(
-                'Finalizar Pedido',
-                style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
-              ),
-              content: Column(
-                mainAxisSize: MainAxisSize.min,
-                children: [
-                  AppTextField(
-                    controller: nombreController,
-                    etiqueta: 'Nombre',
-                    hintText: "Nombre del Cliente",
-                    errorText: errorMessage.isEmpty ? null : errorMessage,
-                    onChanged: (value) {
-                      setState(() {
-                        errorMessage = value.trim().isEmpty
-                            ? "El nombre del cliente es obligatorio."
-                            : '';
-                      });
-                    },
-                  ),
-                  const SizedBox(height: 10),
-                  AppTextField(
-                    controller: comentarioController,
-                    etiqueta: 'Comentarios (opcional)',
-                    hintText: 'Comentarios',
-                    maxLines: 3,
-                  ),
-                  const SizedBox(height: 10),
-                  Align(
-                    alignment: Alignment.center,
-                    child: Text(
-                      'Métodos de pago',
-                      style:
-                          TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
-                    ),
-                  ),
-                  const SizedBox(height: 10),
-                  // Efectivo
-                  Row(
-                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                    children: [
-                      Row(
-                        children: [
-                          Checkbox(
-                            activeColor: AppTheme.primary,
-                            value: efectivoSeleccionado,
-                            onChanged:
-                                (totalCompletado && !efectivoSeleccionado)
-                                    ? null
-                                    : (bool? value) {
-                                        setState(() {
-                                          efectivoSeleccionado = value ?? false;
-                                          if (!efectivoSeleccionado) {
-                                            efectivoController.clear();
-                                            _calcularCambio(setState);
-                                          }
-                                        });
-                                      },
-                          ),
-                          const Text(
-                            "Efectivo",
-                            style: TextStyle(
-                                fontSize: 18, fontWeight: FontWeight.bold),
-                          ),
-                        ],
-                      ),
-                      if (efectivoSeleccionado)
-                        SizedBox(
-                          width: 150,
-                          child: AppTextField(
-                            controller: efectivoController,
-                            etiqueta: 'Cantidad',
-                            hintText: '0.00',
-                            keyboardType: TextInputType.number,
-                            onChanged: (value) => _calcularCambio(setState),
-                          ),
-                        ),
-                    ],
+            return RawKeyboardListener(
+                focusNode: FocusNode(),
+                onKey: (RawKeyEvent event) {
+                  if (event.isKeyPressed(LogicalKeyboardKey.enter) &&
+                      totalCompletado) {
+                    Navigator.of(context).pop(true);
+                  }
+                },
+                child: AlertDialog(
+                  actionsPadding: EdgeInsets.fromLTRB(50, 10, 50, 30),
+                  title: const Text(
+                    'Finalizar Pedido',
+                    style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
                   ),
                   ),
-                  const SizedBox(height: 10),
-                  // Tarjeta
-                  Row(
-                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                    children: [
-                      Row(
-                        children: [
-                          Checkbox(
-                            activeColor: AppTheme.primary,
-                            value: tarjetaSeleccionada,
-                            onChanged: (totalCompletado && !tarjetaSeleccionada)
-                                ? null
-                                : (bool? value) {
-                                    setState(() {
-                                      tarjetaSeleccionada = value ?? false;
-                                      if (!tarjetaSeleccionada) {
-                                        tarjetaController.clear();
-                                        _calcularCambio(setState);
-                                      }
-                                    });
+                  content: Container(
+                    height: 600,
+                    child: Column(
+                      children: [
+                        Expanded(
+                          child: SingleChildScrollView(
+                            child: Column(
+                              children: [
+                                AppTextField(
+                                  controller: nombreController,
+                                  etiqueta: 'Nombre',
+                                  hintText: "Nombre del Cliente",
+                                ),
+                                const SizedBox(height: 10),
+                                AppTextField(
+                                  controller: comentarioController,
+                                  etiqueta: 'Comentarios (opcional)',
+                                  hintText: 'Comentarios',
+                                  maxLines: 2,
+                                ),
+                                const SizedBox(height: 10),
+                                Align(
+                                  alignment: Alignment.center,
+                                  child: Text(
+                                    'Métodos de pago',
+                                    style: TextStyle(
+                                        fontWeight: FontWeight.bold,
+                                        fontSize: 20),
+                                  ),
+                                ),
+                                const SizedBox(height: 10),
+                                // Efectivo
+                                GestureDetector(
+                                  onTap: () {
+                                    if (_isPaymentOptionEnabled(
+                                        efectivoSeleccionado)) {
+                                      setState(() {
+                                        efectivoSeleccionado =
+                                            !efectivoSeleccionado;
+                                        if (!efectivoSeleccionado) {
+                                          efectivoCompleto = false;
+                                          efectivoController.clear();
+                                          _calcularCambio(setState);
+                                        } else if (efectivoCompleto) {
+                                          efectivoController.text =
+                                              totalPedido.toStringAsFixed(2);
+                                          _calcularCambio(setState);
+                                        }
+                                      });
+                                    }
                                   },
                                   },
-                          ),
-                          const Text(
-                            "Tarjeta",
-                            style: TextStyle(
-                                fontSize: 18, fontWeight: FontWeight.bold),
-                          ),
-                        ],
-                      ),
-                      if (tarjetaSeleccionada)
-                        SizedBox(
-                          width: 150,
-                          child: AppTextField(
-                            controller: tarjetaController,
-                            etiqueta: 'Cantidad',
-                            hintText: '0.00',
-                            keyboardType: TextInputType.number,
-                            onChanged: (value) {
-                              _validarCantidad(setState, tarjetaController);
-                            },
+                                  child: Row(
+                                    mainAxisAlignment:
+                                        MainAxisAlignment.spaceBetween,
+                                    crossAxisAlignment:
+                                        CrossAxisAlignment.center,
+                                    children: [
+                                      Row(
+                                        children: [
+                                          Checkbox(
+                                            activeColor: AppTheme.primary,
+                                            value: efectivoSeleccionado,
+                                            onChanged: _isPaymentOptionEnabled(
+                                                    efectivoSeleccionado)
+                                                ? (bool? value) {
+                                                    setState(() {
+                                                      efectivoSeleccionado =
+                                                          value ?? false;
+                                                      if (!efectivoSeleccionado) {
+                                                        efectivoCompleto =
+                                                            false;
+                                                        efectivoController
+                                                            .clear();
+                                                        _calcularCambio(
+                                                            setState);
+                                                      } else if (efectivoCompleto) {
+                                                        efectivoController
+                                                                .text =
+                                                            totalPedido
+                                                                .toStringAsFixed(
+                                                                    2);
+                                                        _calcularCambio(
+                                                            setState);
+                                                      }
+                                                    });
+                                                  }
+                                                : null,
+                                          ),
+                                          const Text(
+                                            "Efectivo",
+                                            style: TextStyle(
+                                                fontSize: 18,
+                                                fontWeight: FontWeight.bold),
+                                          ),
+                                        ],
+                                      ),
+                                      if (efectivoSeleccionado)
+                                        SizedBox(
+                                          width: 180,
+                                          child: Row(
+                                            crossAxisAlignment:
+                                                CrossAxisAlignment.start,
+                                            children: [
+                                              Column(
+                                                children: [
+                                                  const Text('Exacto',
+                                                      style: TextStyle(
+                                                          fontSize: 18,
+                                                          fontWeight:
+                                                              FontWeight.bold,
+                                                          color: Colors.black)),
+                                                  const SizedBox(
+                                                    height: 17,
+                                                  ),
+                                                  Checkbox(
+                                                    activeColor:
+                                                        AppTheme.primary,
+                                                    value: efectivoCompleto,
+                                                    onChanged:
+                                                        efectivoSeleccionado
+                                                            ? (bool? value) {
+                                                                setState(() {
+                                                                  efectivoCompleto =
+                                                                      value ??
+                                                                          false;
+                                                                  if (efectivoCompleto) {
+                                                                    efectivoController
+                                                                            .text =
+                                                                        totalPedido
+                                                                            .toStringAsFixed(2);
+                                                                    _calcularCambio(
+                                                                        setState);
+                                                                  } else {
+                                                                    efectivoController
+                                                                        .clear();
+                                                                    _calcularCambio(
+                                                                        setState);
+                                                                  }
+                                                                });
+                                                              }
+                                                            : null,
+                                                  ),
+                                                ],
+                                              ),
+                                              const SizedBox(
+                                                width: 5,
+                                              ),
+                                              Expanded(
+                                                child: AppTextField(
+                                                  controller:
+                                                      efectivoController,
+                                                  etiqueta: 'Cantidad',
+                                                  hintText: '0.00',
+                                                  keyboardType:
+                                                      TextInputType.number,
+                                                  onChanged: (value) =>
+                                                      _calcularCambio(setState),
+                                                ),
+                                              ),
+                                            ],
+                                          ),
+                                        ),
+                                    ],
+                                  ),
+                                ),
+                                const SizedBox(height: 10),
+                                // Tarjeta
+                                GestureDetector(
+                                  onTap: () {
+                                    if (_isPaymentOptionEnabled(
+                                        tarjetaSeleccionada)) {
+                                      setState(() {
+                                        tarjetaSeleccionada =
+                                            !tarjetaSeleccionada;
+                                        if (!tarjetaSeleccionada) {
+                                          tarjetaController.clear();
+                                          _calcularCambio(setState);
+                                        }
+                                      });
+                                    }
+                                  },
+                                  child: Row(
+                                    mainAxisAlignment:
+                                        MainAxisAlignment.spaceBetween,
+                                    crossAxisAlignment:
+                                        CrossAxisAlignment.center,
+                                    children: [
+                                      Row(
+                                        children: [
+                                          Checkbox(
+                                            activeColor: AppTheme.primary,
+                                            value: tarjetaSeleccionada,
+                                            onChanged: _isPaymentOptionEnabled(
+                                                    tarjetaSeleccionada)
+                                                ? (bool? value) {
+                                                    setState(() {
+                                                      tarjetaSeleccionada =
+                                                          value ?? false;
+                                                      if (!tarjetaSeleccionada) {
+                                                        tarjetaController
+                                                            .clear();
+                                                        _calcularCambio(
+                                                            setState);
+                                                      }
+                                                    });
+                                                  }
+                                                : null,
+                                          ),
+                                          const Text(
+                                            "Tarjeta",
+                                            style: TextStyle(
+                                                fontSize: 18,
+                                                fontWeight: FontWeight.bold),
+                                          ),
+                                        ],
+                                      ),
+                                      if (tarjetaSeleccionada)
+                                        SizedBox(
+                                          width: 180,
+                                          child: Row(
+                                            crossAxisAlignment:
+                                                CrossAxisAlignment.start,
+                                            children: [
+                                              Column(
+                                                children: [
+                                                  const Text('Exacto',
+                                                      style: TextStyle(
+                                                          fontSize: 18,
+                                                          fontWeight:
+                                                              FontWeight.bold,
+                                                          color: Colors.black)),
+                                                  const SizedBox(
+                                                    height: 17,
+                                                  ),
+                                                  Checkbox(
+                                                    activeColor:
+                                                        AppTheme.primary,
+                                                    value: tarjetaCompleto,
+                                                    onChanged:
+                                                        tarjetaSeleccionada
+                                                            ? (bool? value) {
+                                                                setState(() {
+                                                                  tarjetaCompleto =
+                                                                      value ??
+                                                                          false;
+                                                                  if (tarjetaCompleto) {
+                                                                    tarjetaController
+                                                                            .text =
+                                                                        totalPedido
+                                                                            .toStringAsFixed(2);
+                                                                    _calcularCambio(
+                                                                        setState);
+                                                                  } else {
+                                                                    tarjetaController
+                                                                        .clear();
+                                                                    _calcularCambio(
+                                                                        setState);
+                                                                  }
+                                                                });
+                                                              }
+                                                            : null,
+                                                  ),
+                                                ],
+                                              ),
+                                              const SizedBox(
+                                                width: 5,
+                                              ),
+                                              Expanded(
+                                                child: AppTextField(
+                                                  controller: tarjetaController,
+                                                  etiqueta: 'Cantidad',
+                                                  hintText: '0.00',
+                                                  keyboardType:
+                                                      TextInputType.number,
+                                                  onChanged: (value) {
+                                                    _validarCantidad(setState,
+                                                        tarjetaController);
+                                                  },
+                                                ),
+                                              ),
+                                            ],
+                                          ),
+                                        ),
+                                    ],
+                                  ),
+                                ),
+                                const SizedBox(height: 10),
+                                // Transferencia
+                                GestureDetector(
+                                  onTap: () {
+                                    if (_isPaymentOptionEnabled(
+                                        transferenciaSeleccionada)) {
+                                      setState(() {
+                                        transferenciaSeleccionada =
+                                            !transferenciaSeleccionada;
+                                        if (!transferenciaSeleccionada) {
+                                          transferenciaController.clear();
+                                          _calcularCambio(setState);
+                                        }
+                                      });
+                                    }
+                                  },
+                                  child: Row(
+                                    mainAxisAlignment:
+                                        MainAxisAlignment.spaceBetween,
+                                    crossAxisAlignment:
+                                        CrossAxisAlignment.center,
+                                    children: [
+                                      Row(
+                                        children: [
+                                          Checkbox(
+                                            activeColor: AppTheme.primary,
+                                            value: transferenciaSeleccionada,
+                                            onChanged: _isPaymentOptionEnabled(
+                                                    transferenciaSeleccionada)
+                                                ? (bool? value) {
+                                                    setState(() {
+                                                      transferenciaSeleccionada =
+                                                          value ?? false;
+                                                      if (!transferenciaSeleccionada) {
+                                                        transferenciaController
+                                                            .clear();
+                                                        _calcularCambio(
+                                                            setState);
+                                                      }
+                                                    });
+                                                  }
+                                                : null,
+                                          ),
+                                          const Text(
+                                            "Transferencia",
+                                            style: TextStyle(
+                                                fontSize: 18,
+                                                fontWeight: FontWeight.bold),
+                                          ),
+                                        ],
+                                      ),
+                                      if (transferenciaSeleccionada)
+                                        SizedBox(
+                                          width: 180,
+                                          child: Row(
+                                            crossAxisAlignment:
+                                                CrossAxisAlignment.start,
+                                            children: [
+                                              Column(
+                                                children: [
+                                                  const Text('Exacto',
+                                                      style: TextStyle(
+                                                          fontSize: 18,
+                                                          fontWeight:
+                                                              FontWeight.bold,
+                                                          color: Colors.black)),
+                                                  const SizedBox(
+                                                    height: 17,
+                                                  ),
+                                                  Checkbox(
+                                                    activeColor:
+                                                        AppTheme.primary,
+                                                    value:
+                                                        transferenciaCompleto,
+                                                    onChanged:
+                                                        transferenciaSeleccionada
+                                                            ? (bool? value) {
+                                                                setState(() {
+                                                                  transferenciaCompleto =
+                                                                      value ??
+                                                                          false;
+                                                                  if (transferenciaCompleto) {
+                                                                    transferenciaController
+                                                                            .text =
+                                                                        totalPedido
+                                                                            .toStringAsFixed(2);
+                                                                    _calcularCambio(
+                                                                        setState);
+                                                                  } else {
+                                                                    transferenciaController
+                                                                        .clear();
+                                                                    _calcularCambio(
+                                                                        setState);
+                                                                  }
+                                                                });
+                                                              }
+                                                            : null,
+                                                  ),
+                                                ],
+                                              ),
+                                              const SizedBox(
+                                                width: 5,
+                                              ),
+                                              Expanded(
+                                                child: AppTextField(
+                                                  controller:
+                                                      transferenciaController,
+                                                  etiqueta: 'Cantidad',
+                                                  hintText: '0.00',
+                                                  keyboardType:
+                                                      TextInputType.number,
+                                                  onChanged: (value) {
+                                                    _validarCantidad(setState,
+                                                        transferenciaController);
+                                                  },
+                                                ),
+                                              ),
+                                            ],
+                                          ),
+                                        ),
+                                    ],
+                                  ),
+                                ),
+                                const SizedBox(height: 10),
+                                // Mostrar el total del pedido y la cantidad faltante
+                                Align(
+                                  alignment: Alignment.centerRight,
+                                  child: Column(
+                                      crossAxisAlignment:
+                                          CrossAxisAlignment.end,
+                                      children: [
+                                        Text(
+                                          'Total del pedido: \$${totalPedido.toStringAsFixed(2)}',
+                                          style: const TextStyle(
+                                              fontWeight: FontWeight.bold,
+                                              fontSize: 18),
+                                        ),
+                                        if (faltante > 0)
+                                          Text(
+                                            'Faltante: \$${faltante.toStringAsFixed(2)}',
+                                            style: const TextStyle(
+                                                color: Colors.red,
+                                                fontSize: 18,
+                                                fontWeight: FontWeight.bold),
+                                          )
+                                        else if (cambio > 0)
+                                          Text(
+                                              'Cambio: \$${cambio.toStringAsFixed(2)}',
+                                              style: const TextStyle(
+                                                  color: Colors.green,
+                                                  fontSize: 18,
+                                                  fontWeight: FontWeight.bold)),
+                                      ]),
+                                ),
+                              ],
+                            ),
                           ),
                           ),
                         ),
                         ),
-                    ],
-                  ),
-                  const SizedBox(height: 10),
-                  // Transferencia
-                  Row(
-                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                    children: [
-                      Row(
-                        children: [
-                          Checkbox(
-                            activeColor: AppTheme.primary,
-                            value: transferenciaSeleccionada,
-                            onChanged:
-                                (totalCompletado && !transferenciaSeleccionada)
-                                    ? null
-                                    : (bool? value) {
-                                        setState(() {
-                                          transferenciaSeleccionada =
-                                              value ?? false;
-                                          if (!transferenciaSeleccionada) {
-                                            transferenciaController.clear();
-                                            _calcularCambio(setState);
-                                          }
-                                        });
-                                      },
-                          ),
-                          const Text(
-                            "Transferencia",
-                            style: TextStyle(
-                                fontSize: 18, fontWeight: FontWeight.bold),
-                          ),
-                        ],
-                      ),
-                      if (transferenciaSeleccionada)
-                        SizedBox(
-                          width: 150,
-                          child: AppTextField(
-                            controller: transferenciaController,
-                            etiqueta: 'Cantidad',
-                            hintText: '0.00',
-                            keyboardType: TextInputType.number,
-                            onChanged: (value) {
-                              _validarCantidad(
-                                  setState, transferenciaController);
-                            },
-                          ),
+                        // Aquí mantenemos los botones fijos
+                        Row(
+                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                          children: [
+                            TextButton(
+                              child: const Text('Cancelar',
+                                  style: TextStyle(fontSize: 18)),
+                              onPressed: () {
+                                Navigator.of(context).pop(false);
+                              },
+                              style: ButtonStyle(
+                                  padding: MaterialStatePropertyAll(
+                                      EdgeInsets.fromLTRB(30, 20, 30, 20)),
+                                  backgroundColor:
+                                      MaterialStatePropertyAll(Colors.red),
+                                  foregroundColor: MaterialStatePropertyAll(
+                                      AppTheme.secondary)),
+                            ),
+                            const SizedBox(width: 100),
+                            TextButton(
+                              child: const Text('Guardar',
+                                  style: TextStyle(fontSize: 18)),
+                              onPressed: totalCompletado
+                                  ? () {
+                                      Navigator.of(context).pop(true);
+                                    }
+                                  : null,
+                              style: ButtonStyle(
+                                  padding: MaterialStatePropertyAll(
+                                      EdgeInsets.fromLTRB(30, 20, 30, 20)),
+                                  backgroundColor: MaterialStatePropertyAll(
+                                      totalCompletado
+                                          ? AppTheme.tertiary
+                                          : Colors.grey),
+                                  foregroundColor: MaterialStatePropertyAll(
+                                      AppTheme.quaternary)),
+                            ),
+                          ],
                         ),
                         ),
-                    ],
-                  ),
-                  const SizedBox(height: 10),
-                  // Mostrar el total del pedido y la cantidad faltante
-                  Align(
-                    alignment: Alignment.centerRight,
-                    child: Column(
-                        crossAxisAlignment: CrossAxisAlignment.end,
-                        children: [
-                          Text(
-                            'Total del pedido: \$${totalPedido.toStringAsFixed(2)}',
-                            style: const TextStyle(
-                                fontWeight: FontWeight.bold, fontSize: 18),
-                          ),
-                          if (faltante > 0)
-                            Text(
-                              'Faltante: \$${faltante.toStringAsFixed(2)}',
-                              style: const TextStyle(
-                                  color: Colors.red,
-                                  fontSize: 18,
-                                  fontWeight: FontWeight.bold),
-                            )
-                          else if (cambio > 0)
-                            Text('Cambio: \$${cambio.toStringAsFixed(2)}',
-                                style: const TextStyle(
-                                    color: Colors.green,
-                                    fontSize: 18,
-                                    fontWeight: FontWeight.bold)),
-                        ]),
-                  ),
-                ],
-              ),
-              actions: <Widget>[
-                Row(
-                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                  children: [
-                    TextButton(
-                      child: const Text('Cancelar',
-                          style: TextStyle(fontSize: 18)),
-                      onPressed: () {
-                        Navigator.of(context).pop(false);
-                      },
-                      style: ButtonStyle(
-                          padding: MaterialStatePropertyAll(
-                              EdgeInsets.fromLTRB(30, 20, 30, 20)),
-                          backgroundColor: MaterialStatePropertyAll(Colors.red),
-                          foregroundColor:
-                              MaterialStatePropertyAll(AppTheme.secondary)),
-                    ),
-                    const SizedBox(width: 100),
-                    TextButton(
-                      child:
-                          const Text('Guardar', style: TextStyle(fontSize: 18)),
-                      onPressed: () {
-                        if (nombreController.text.trim().isEmpty) {
-                          setState(() => errorMessage =
-                              "El nombre del cliente es obligatorio.");
-                          return;
-                        }
-                        Navigator.of(context).pop(true);
-                      },
-                      style: ButtonStyle(
-                          padding: MaterialStatePropertyAll(
-                              EdgeInsets.fromLTRB(30, 20, 30, 20)),
-                          backgroundColor:
-                              MaterialStatePropertyAll(AppTheme.tertiary),
-                          foregroundColor:
-                              MaterialStatePropertyAll(AppTheme.quaternary)),
+                      ],
                     ),
                     ),
-                  ],
-                )
-              ],
-            );
+                  ),
+                ));
           },
           },
         );
         );
       },
       },
@@ -447,11 +731,10 @@ class _PedidoFormState extends State<PedidoForm> {
   }
   }
 
 
   void prepararPedidoActual(String nombreCliente, String comentarios) async {
   void prepararPedidoActual(String nombreCliente, String comentarios) async {
-    DateTime now = DateTime.now();
-    String formattedDate = DateFormat('dd-MM-yyyy kk:mm:ss').format(now);
+    String now = DateTime.now().toUtc().toIso8601String();
 
 
     Pedido nuevoPedido = Pedido(
     Pedido nuevoPedido = Pedido(
-      peticion: formattedDate,
+      peticion: now,
       nombreCliente: nombreCliente,
       nombreCliente: nombreCliente,
       comentarios: comentarios,
       comentarios: comentarios,
       estatus: "NUEVO",
       estatus: "NUEVO",
@@ -538,6 +821,7 @@ class _PedidoFormState extends State<PedidoForm> {
   void dispose() {
   void dispose() {
     _gridViewController.dispose();
     _gridViewController.dispose();
     _searchController.dispose();
     _searchController.dispose();
+    _categoryScrollController.dispose();
     super.dispose();
     super.dispose();
   }
   }
 
 
@@ -970,6 +1254,8 @@ class _PedidoFormState extends State<PedidoForm> {
   Widget _buildProductsSection() {
   Widget _buildProductsSection() {
     return Column(
     return Column(
       children: [
       children: [
+        const SizedBox(height: 5),
+        _buildSearchBar(),
         const SizedBox(height: 10),
         const SizedBox(height: 10),
         _buildCategoryButtons(),
         _buildCategoryButtons(),
         const SizedBox(height: 15),
         const SizedBox(height: 15),
@@ -986,7 +1272,9 @@ class _PedidoFormState extends State<PedidoForm> {
               itemCount: productos.length,
               itemCount: productos.length,
               itemBuilder: (context, index) {
               itemBuilder: (context, index) {
                 final producto = productos[index];
                 final producto = productos[index];
-                if (producto.idCategoria != categoriaSeleccionada?.id) {
+                // Si no se está buscando, aplicar el filtro de categoría
+                if (!_estadoBusqueda &&
+                    producto.idCategoria != categoriaSeleccionada?.id) {
                   return Container();
                   return Container();
                 }
                 }
                 return Card(
                 return Card(
@@ -1043,31 +1331,38 @@ class _PedidoFormState extends State<PedidoForm> {
 
 
     return Container(
     return Container(
       height: 50,
       height: 50,
-      child: ListView.builder(
-        scrollDirection: Axis.horizontal,
-        itemCount: categoriasFiltradas.length,
-        itemBuilder: (context, index) {
-          final categoria = categoriasFiltradas[index];
-          bool isSelected = categoriaSeleccionada?.id == categoria.id;
-          return Padding(
-            padding: const EdgeInsets.symmetric(horizontal: 4.0),
-            child: ElevatedButton(
-              onPressed: () {
-                cargarProductosPorCategoria(categoria.id);
-                setState(() {
-                  categoriaSeleccionada = categoria;
-                });
-              },
-              style: ElevatedButton.styleFrom(
-                primary: isSelected ? AppTheme.tertiary : Colors.grey,
-                foregroundColor:
-                    isSelected ? AppTheme.quaternary : AppTheme.secondary,
-                onPrimary: AppTheme.secondary,
+      child: Scrollbar(
+        thumbVisibility: true,
+        trackVisibility: true,
+        interactive: true,
+        controller: _categoryScrollController,
+        child: ListView.builder(
+          controller: _categoryScrollController,
+          scrollDirection: Axis.horizontal,
+          itemCount: categoriasFiltradas.length,
+          itemBuilder: (context, index) {
+            final categoria = categoriasFiltradas[index];
+            bool isSelected = categoriaSeleccionada?.id == categoria.id;
+            return Padding(
+              padding: const EdgeInsets.symmetric(horizontal: 4.0),
+              child: ElevatedButton(
+                onPressed: () {
+                  cargarProductosPorCategoria(categoria.id);
+                  setState(() {
+                    categoriaSeleccionada = categoria;
+                  });
+                },
+                style: ElevatedButton.styleFrom(
+                  primary: isSelected ? AppTheme.tertiary : Colors.grey,
+                  foregroundColor:
+                      isSelected ? AppTheme.quaternary : AppTheme.secondary,
+                  onPrimary: AppTheme.secondary,
+                ),
+                child: Text(categoria.nombre!),
               ),
               ),
-              child: Text(categoria.nombre!),
-            ),
-          );
-        },
+            );
+          },
+        ),
       ),
       ),
     );
     );
   }
   }
@@ -1080,9 +1375,23 @@ class _PedidoFormState extends State<PedidoForm> {
         decoration: InputDecoration(
         decoration: InputDecoration(
           hintText: 'Buscar producto...',
           hintText: 'Buscar producto...',
           prefixIcon: const Icon(Icons.search),
           prefixIcon: const Icon(Icons.search),
+          suffixIcon: IconButton(
+            icon: Icon(Icons.clear),
+            onPressed: () {
+              _searchController.clear();
+              _onSearchChanged('');
+            },
+          ),
           border: OutlineInputBorder(
           border: OutlineInputBorder(
             borderRadius: BorderRadius.circular(12.0),
             borderRadius: BorderRadius.circular(12.0),
           ),
           ),
+          enabledBorder: OutlineInputBorder(
+              borderRadius: BorderRadius.circular(12.0),
+              borderSide: BorderSide(width: 1.5)),
+          focusedBorder: OutlineInputBorder(
+            borderSide: BorderSide(color: Colors.black),
+            borderRadius: BorderRadius.circular(12.0),
+          ),
         ),
         ),
         onChanged: _onSearchChanged,
         onChanged: _onSearchChanged,
       ),
       ),

+ 341 - 216
lib/views/pedido/pedido_screen.dart

@@ -10,6 +10,7 @@ import '../../models/models.dart';
 import '../../viewmodels/viewmodels.dart';
 import '../../viewmodels/viewmodels.dart';
 import '../../widgets/widgets_components.dart' as clase;
 import '../../widgets/widgets_components.dart' as clase;
 import 'pedido_form.dart';
 import 'pedido_form.dart';
+import 'pedido_sync.dart';
 
 
 class PedidoScreen extends StatefulWidget {
 class PedidoScreen extends StatefulWidget {
   const PedidoScreen({Key? key}) : super(key: key);
   const PedidoScreen({Key? key}) : super(key: key);
@@ -19,6 +20,7 @@ class PedidoScreen extends StatefulWidget {
 }
 }
 
 
 class _PedidoScreenState extends State<PedidoScreen> {
 class _PedidoScreenState extends State<PedidoScreen> {
+  int _syncAgainTapCount = 0;
   final _busqueda = TextEditingController(text: '');
   final _busqueda = TextEditingController(text: '');
   DateTime? fechaInicio;
   DateTime? fechaInicio;
   DateTime? fechaFin;
   DateTime? fechaFin;
@@ -99,6 +101,11 @@ class _PedidoScreenState extends State<PedidoScreen> {
     TextStyle estilo = const TextStyle(fontWeight: FontWeight.bold);
     TextStyle estilo = const TextStyle(fontWeight: FontWeight.bold);
     List<DataRow> registros = [];
     List<DataRow> registros = [];
     for (Pedido item in pvm.pedidos) {
     for (Pedido item in pvm.pedidos) {
+      final sincronizadoStatus =
+          item.sincronizado == null || item.sincronizado!.isEmpty
+              ? "No Sincronizado"
+              : _formatDateTime(item.sincronizado);
+
       registros.add(DataRow(cells: [
       registros.add(DataRow(cells: [
         DataCell(
         DataCell(
             Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
             Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
@@ -195,7 +202,11 @@ class _PedidoScreenState extends State<PedidoScreen> {
           onTap: () => go(item),
           onTap: () => go(item),
         ),
         ),
         DataCell(
         DataCell(
-          Text(item.peticion ?? "Sin fecha"),
+          Text(_formatDateTime(item.peticion)),
+          onTap: () => go(item),
+        ),
+        DataCell(
+          Text(sincronizadoStatus),
           onTap: () => go(item),
           onTap: () => go(item),
         ),
         ),
       ]));
       ]));
@@ -203,10 +214,19 @@ class _PedidoScreenState extends State<PedidoScreen> {
 
 
     return Scaffold(
     return Scaffold(
       appBar: AppBar(
       appBar: AppBar(
-          title: Text(
-            'Pedidos',
-            style: TextStyle(
-                color: AppTheme.secondary, fontWeight: FontWeight.w500),
+          title: GestureDetector(
+            onTap: () {
+              _syncAgainTapCount++;
+              if (_syncAgainTapCount == 5) {
+                alertaSync(context);
+                _syncAgainTapCount = 0;
+              }
+            },
+            child: Text(
+              'Pedidos',
+              style: TextStyle(
+                  color: AppTheme.secondary, fontWeight: FontWeight.w500),
+            ),
           ),
           ),
           actions: <Widget>[
           actions: <Widget>[
             IconButton(
             IconButton(
@@ -216,183 +236,224 @@ class _PedidoScreenState extends State<PedidoScreen> {
             ),
             ),
           ],
           ],
           iconTheme: IconThemeData(color: AppTheme.secondary)),
           iconTheme: IconThemeData(color: AppTheme.secondary)),
-      floatingActionButton: FloatingActionButton.extended(
-        onPressed: () async {
-          await Navigator.push(
-            context,
-            MaterialPageRoute(
-              builder: (context) => PedidoForm(),
-            ),
-          ).then((_) => Provider.of<PedidoViewModel>(context, listen: false)
-              .fetchLocalPedidosForScreen());
-        },
-        icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
-        label: Text(
-          "Agregar Pedido",
-          style: TextStyle(fontSize: 20, color: AppTheme.quaternary),
-        ),
-        shape: RoundedRectangleBorder(
-          borderRadius: BorderRadius.circular(8),
-        ),
-        materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
-        backgroundColor: AppTheme.tertiary,
-      ),
-      body: Column(
+      body: Stack(
         children: [
         children: [
-          Expanded(
-            child: ListView(
-              padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
-              children: [
-                const SizedBox(height: 8),
-                clase.tarjeta(
-                  Padding(
-                    padding: const EdgeInsets.all(8.0),
-                    child: LayoutBuilder(
-                      builder: (context, constraints) {
-                        if (screenWidth > 1000) {
-                          return Row(
-                            crossAxisAlignment: CrossAxisAlignment.end,
-                            children: [
-                              Expanded(
-                                flex: 7,
-                                child: _buildDateRangePicker(),
-                              ),
-                              const SizedBox(width: 5),
-                              botonBuscar()
-                            ],
-                          );
-                        } else {
-                          return Column(
-                            children: [
-                              Row(
-                                children: [_buildDateRangePicker()],
-                              ),
-                              Row(
-                                children: [botonBuscar()],
-                              ),
-                            ],
-                          );
-                        }
-                      },
+          Column(
+            children: [
+              Expanded(
+                child: ListView(
+                  padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
+                  children: [
+                    const SizedBox(height: 8),
+                    clase.tarjeta(
+                      Padding(
+                        padding: const EdgeInsets.all(8.0),
+                        child: LayoutBuilder(
+                          builder: (context, constraints) {
+                            if (screenWidth > 1000) {
+                              return Row(
+                                crossAxisAlignment: CrossAxisAlignment.end,
+                                children: [
+                                  Expanded(
+                                    flex: 7,
+                                    child: _buildDateRangePicker(),
+                                  ),
+                                  const SizedBox(width: 5),
+                                  botonBuscar()
+                                ],
+                              );
+                            } else {
+                              return Column(
+                                children: [
+                                  Row(
+                                    children: [_buildDateRangePicker()],
+                                  ),
+                                  Row(
+                                    children: [botonBuscar()],
+                                  ),
+                                ],
+                              );
+                            }
+                          },
+                        ),
+                      ),
                     ),
                     ),
-                  ),
-                ),
-                const SizedBox(height: 8),
-                pvm.isLoading
-                    ? const Center(child: CircularProgressIndicator())
-                    : Container(),
-                clase.tarjeta(
-                  Column(
-                    children: [
-                      LayoutBuilder(builder: (context, constraints) {
-                        return SingleChildScrollView(
-                          scrollDirection: Axis.vertical,
-                          child: Scrollbar(
-                            controller: horizontalScrollController,
-                            interactive: true,
-                            thumbVisibility: true,
-                            thickness: 10.0,
-                            child: SingleChildScrollView(
-                              controller: horizontalScrollController,
-                              scrollDirection: Axis.horizontal,
-                              child: ConstrainedBox(
-                                constraints: BoxConstraints(
-                                    minWidth: isMobile
-                                        ? constraints.maxWidth
-                                        : screenWidth),
-                                child: DataTable(
-                                  columnSpacing: columnSpacing,
-                                  sortAscending: true,
-                                  sortColumnIndex: 1,
-                                  columns: [
-                                    DataColumn(label: Text(" ", style: estilo)),
-                                    DataColumn(
-                                        label: Text("FOLIO", style: estilo)),
-                                    DataColumn(
-                                        label: Text("NOMBRE", style: estilo)),
-                                    DataColumn(
-                                        label:
-                                            Text("COMENTARIOS", style: estilo)),
-                                    DataColumn(
-                                        label: Text("ESTATUS", style: estilo)),
-                                    DataColumn(
-                                        label: Text("FECHA", style: estilo)),
-                                  ],
-                                  rows: registros,
+                    const SizedBox(height: 8),
+                    pvm.isLoading
+                        ? const Center(child: CircularProgressIndicator())
+                        : Container(),
+                    clase.tarjeta(
+                      Column(
+                        children: [
+                          LayoutBuilder(builder: (context, constraints) {
+                            return SingleChildScrollView(
+                              scrollDirection: Axis.vertical,
+                              child: Scrollbar(
+                                controller: horizontalScrollController,
+                                interactive: true,
+                                thumbVisibility: true,
+                                thickness: 10.0,
+                                child: SingleChildScrollView(
+                                  controller: horizontalScrollController,
+                                  scrollDirection: Axis.horizontal,
+                                  child: ConstrainedBox(
+                                    constraints: BoxConstraints(
+                                        minWidth: isMobile
+                                            ? constraints.maxWidth
+                                            : screenWidth),
+                                    child: DataTable(
+                                      columnSpacing: columnSpacing,
+                                      sortAscending: true,
+                                      sortColumnIndex: 1,
+                                      columns: [
+                                        DataColumn(
+                                            label: Text(" ", style: estilo)),
+                                        DataColumn(
+                                            label:
+                                                Text("FOLIO", style: estilo)),
+                                        DataColumn(
+                                            label:
+                                                Text("NOMBRE", style: estilo)),
+                                        DataColumn(
+                                            label: Text("COMENTARIOS",
+                                                style: estilo)),
+                                        DataColumn(
+                                            label:
+                                                Text("ESTATUS", style: estilo)),
+                                        DataColumn(
+                                            label:
+                                                Text("FECHA", style: estilo)),
+                                        DataColumn(
+                                            label: Text("SINCRONIZADO",
+                                                style: estilo)),
+                                      ],
+                                      rows: registros,
+                                    ),
+                                  ),
                                 ),
                                 ),
                               ),
                               ),
-                            ),
-                          ),
-                        );
-                      }),
-                    ],
-                  ),
-                ),
-                const SizedBox(height: 15),
-                if (!pvm.isLoading)
-                  Row(
-                    mainAxisAlignment: MainAxisAlignment.center,
-                    children: [
-                      TextButton(
-                        onPressed:
-                            pvm.currentPage > 1 ? pvm.previousPage : null,
-                        child: Text('Anterior'),
-                        style: ButtonStyle(
-                          backgroundColor:
-                              MaterialStateProperty.resolveWith<Color?>(
-                            (Set<MaterialState> states) {
-                              if (states.contains(MaterialState.disabled)) {
-                                return Colors.grey;
-                              }
-                              return AppTheme.tertiary;
-                            },
-                          ),
-                          foregroundColor:
-                              MaterialStateProperty.resolveWith<Color?>(
-                            (Set<MaterialState> states) {
-                              if (states.contains(MaterialState.disabled)) {
-                                return Colors.black;
-                              }
-                              return Colors.white;
-                            },
-                          ),
-                        ),
+                            );
+                          }),
+                        ],
                       ),
                       ),
-                      SizedBox(width: 15),
-                      Text('Página ${pvm.currentPage} de ${pvm.totalPages}'),
-                      SizedBox(width: 15),
-                      TextButton(
-                        onPressed: pvm.currentPage < pvm.totalPages
-                            ? pvm.nextPage
-                            : null,
-                        child: Text('Siguiente'),
-                        style: ButtonStyle(
-                          backgroundColor:
-                              MaterialStateProperty.resolveWith<Color?>(
-                            (Set<MaterialState> states) {
-                              if (states.contains(MaterialState.disabled)) {
-                                return Colors.grey;
-                              }
-                              return AppTheme.tertiary;
-                            },
+                    ),
+                    const SizedBox(height: 15),
+                    if (!pvm.isLoading)
+                      Row(
+                        mainAxisAlignment: MainAxisAlignment.center,
+                        children: [
+                          TextButton(
+                            onPressed:
+                                pvm.currentPage > 1 ? pvm.previousPage : null,
+                            child: Text('Anterior'),
+                            style: ButtonStyle(
+                              backgroundColor:
+                                  MaterialStateProperty.resolveWith<Color?>(
+                                (Set<MaterialState> states) {
+                                  if (states.contains(MaterialState.disabled)) {
+                                    return Colors.grey;
+                                  }
+                                  return AppTheme.tertiary;
+                                },
+                              ),
+                              foregroundColor:
+                                  MaterialStateProperty.resolveWith<Color?>(
+                                (Set<MaterialState> states) {
+                                  if (states.contains(MaterialState.disabled)) {
+                                    return Colors.black;
+                                  }
+                                  return Colors.white;
+                                },
+                              ),
+                            ),
                           ),
                           ),
-                          foregroundColor:
-                              MaterialStateProperty.resolveWith<Color?>(
-                            (Set<MaterialState> states) {
-                              if (states.contains(MaterialState.disabled)) {
-                                return Colors.black;
-                              }
-                              return Colors.white;
-                            },
+                          SizedBox(width: 15),
+                          Text(
+                              'Página ${pvm.currentPage} de ${pvm.totalPages}'),
+                          SizedBox(width: 15),
+                          TextButton(
+                            onPressed: pvm.currentPage < pvm.totalPages
+                                ? pvm.nextPage
+                                : null,
+                            child: Text('Siguiente'),
+                            style: ButtonStyle(
+                              backgroundColor:
+                                  MaterialStateProperty.resolveWith<Color?>(
+                                (Set<MaterialState> states) {
+                                  if (states.contains(MaterialState.disabled)) {
+                                    return Colors.grey;
+                                  }
+                                  return AppTheme.tertiary;
+                                },
+                              ),
+                              foregroundColor:
+                                  MaterialStateProperty.resolveWith<Color?>(
+                                (Set<MaterialState> states) {
+                                  if (states.contains(MaterialState.disabled)) {
+                                    return Colors.black;
+                                  }
+                                  return Colors.white;
+                                },
+                              ),
+                            ),
                           ),
                           ),
-                        ),
+                        ],
                       ),
                       ),
-                    ],
+                    const SizedBox(height: 15),
+                  ],
+                ),
+              ),
+            ],
+          ),
+          Positioned(
+            bottom: 16,
+            right: 16,
+            child: FloatingActionButton.extended(
+              heroTag: 'addPedido',
+              onPressed: () async {
+                await Navigator.push(
+                  context,
+                  MaterialPageRoute(
+                    builder: (context) => PedidoForm(),
                   ),
                   ),
-                const SizedBox(height: 15),
-              ],
+                ).then((_) =>
+                    Provider.of<PedidoViewModel>(context, listen: false)
+                        .fetchLocalPedidosForScreen());
+              },
+              icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
+              label: Text(
+                "Agregar Pedido",
+                style: TextStyle(fontSize: 20, color: AppTheme.quaternary),
+              ),
+              shape: RoundedRectangleBorder(
+                borderRadius: BorderRadius.circular(8),
+              ),
+              materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+              backgroundColor: AppTheme.tertiary,
             ),
             ),
           ),
           ),
+          // Positioned(
+          //   bottom: 16,
+          //   left: 16,
+          //   child: FloatingActionButton.extended(
+          //     heroTag: 'sincronizacion',
+          //     onPressed: () {
+          //       alerta(context, etiqueta: "Sincronización Empezada");
+          //       PedidoSync().startSync(
+          //           Provider.of<PedidoViewModel>(context, listen: false));
+          //     },
+          //     icon: Icon(Icons.sync, size: 30, color: AppTheme.quaternary),
+          //     label: Text(
+          //       "Sincronización",
+          //       style: TextStyle(fontSize: 20, color: AppTheme.quaternary),
+          //     ),
+          //     shape: RoundedRectangleBorder(
+          //       borderRadius: BorderRadius.circular(8),
+          //     ),
+          //     materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+          //     backgroundColor: AppTheme.tertiary,
+          //   ),
+          // ),
         ],
         ],
       ),
       ),
     );
     );
@@ -444,59 +505,123 @@ class _PedidoScreenState extends State<PedidoScreen> {
 
 
   Widget botonBuscar() {
   Widget botonBuscar() {
     return Expanded(
     return Expanded(
-        flex: 2,
-        child: Row(
-          children: [
-            Expanded(
-              flex: 2,
-              child: Padding(
-                padding: const EdgeInsets.only(bottom: 5),
-                child: ElevatedButton(
-                  onPressed: clearSearchAndReset,
-                  style: ElevatedButton.styleFrom(
-                    shape: RoundedRectangleBorder(
-                      borderRadius: BorderRadius.circular(20.0),
-                    ),
-                    primary: AppTheme.tertiary,
-                    padding: const EdgeInsets.symmetric(vertical: 25),
+      flex: 2,
+      child: Row(
+        children: [
+          Expanded(
+            flex: 2,
+            child: Padding(
+              padding: const EdgeInsets.only(bottom: 5),
+              child: ElevatedButton(
+                onPressed: clearSearchAndReset,
+                style: ElevatedButton.styleFrom(
+                  shape: RoundedRectangleBorder(
+                    borderRadius: BorderRadius.circular(20.0),
                   ),
                   ),
-                  child: Text('Limpiar',
-                      style: TextStyle(color: AppTheme.quaternary)),
+                  primary: AppTheme.tertiary,
+                  padding: const EdgeInsets.symmetric(vertical: 25),
                 ),
                 ),
+                child: Text('Limpiar',
+                    style: TextStyle(color: AppTheme.quaternary)),
               ),
               ),
             ),
             ),
-            const SizedBox(width: 8),
-            Expanded(
-              flex: 2,
-              child: Padding(
-                padding: const EdgeInsets.only(bottom: 5),
-                child: ElevatedButton(
-                  onPressed: () async {
-                    if (_busqueda.text.isNotEmpty) {
-                      await Provider.of<PedidoViewModel>(context, listen: false)
-                          .buscarPedidosPorFolio(_busqueda.text.trim());
-                    } else if (fechaInicio != null && fechaFin != null) {
-                      await Provider.of<PedidoViewModel>(context, listen: false)
-                          .buscarPedidosPorFecha(fechaInicio!, fechaFin!);
-                    } else {
-                      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
-                          content: Text(
-                              'Introduce un folio o selecciona un rango de fechas para buscar.')));
-                    }
-                  },
-                  style: ElevatedButton.styleFrom(
-                    shape: RoundedRectangleBorder(
-                      borderRadius: BorderRadius.circular(20.0),
-                    ),
-                    primary: AppTheme.tertiary,
-                    padding: const EdgeInsets.symmetric(vertical: 25),
+          ),
+          const SizedBox(width: 8),
+          Expanded(
+            flex: 2,
+            child: Padding(
+              padding: const EdgeInsets.only(bottom: 5),
+              child: ElevatedButton(
+                onPressed: () async {
+                  if (_busqueda.text.isNotEmpty) {
+                    await Provider.of<PedidoViewModel>(context, listen: false)
+                        .buscarPedidosPorFolio(_busqueda.text.trim());
+                  } else if (fechaInicio != null && fechaFin != null) {
+                    DateTime fechaInicioUTC = DateTime(fechaInicio!.year,
+                            fechaInicio!.month, fechaInicio!.day)
+                        .toUtc();
+                    DateTime fechaFinUTC = DateTime(fechaFin!.year,
+                            fechaFin!.month, fechaFin!.day, 23, 59, 59)
+                        .toUtc();
+
+                    await Provider.of<PedidoViewModel>(context, listen: false)
+                        .buscarPedidosPorFecha(fechaInicioUTC, fechaFinUTC);
+                  } else {
+                    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
+                        content: Text(
+                            'Introduce un folio o selecciona un rango de fechas para buscar.')));
+                  }
+                },
+                style: ElevatedButton.styleFrom(
+                  shape: RoundedRectangleBorder(
+                    borderRadius: BorderRadius.circular(20.0),
                   ),
                   ),
-                  child: Text('Buscar',
-                      style: TextStyle(color: AppTheme.quaternary)),
+                  primary: AppTheme.tertiary,
+                  padding: const EdgeInsets.symmetric(vertical: 25),
                 ),
                 ),
+                child: Text('Buscar',
+                    style: TextStyle(color: AppTheme.quaternary)),
               ),
               ),
             ),
             ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  void alertaSync(BuildContext context) {
+    showDialog(
+      context: context,
+      builder: (BuildContext context) {
+        return AlertDialog(
+          title: const Text('Confirmación',
+              style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22)),
+          content: const Text('¿Deseas sincronizar todos los pedidos de nuevo?',
+              style: TextStyle(fontSize: 18)),
+          actions: [
+            Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: [
+                TextButton(
+                  child: const Text('No', style: TextStyle(fontSize: 18)),
+                  style: ButtonStyle(
+                      padding: MaterialStatePropertyAll(
+                          EdgeInsets.fromLTRB(20, 10, 20, 10)),
+                      backgroundColor: MaterialStatePropertyAll(Colors.red),
+                      foregroundColor:
+                          MaterialStatePropertyAll(AppTheme.secondary)),
+                  onPressed: () {
+                    Navigator.of(context).pop();
+                  },
+                ),
+                TextButton(
+                  child: const Text('Sí', style: TextStyle(fontSize: 18)),
+                  style: ButtonStyle(
+                      padding: MaterialStatePropertyAll(
+                          EdgeInsets.fromLTRB(20, 10, 20, 10)),
+                      backgroundColor:
+                          MaterialStatePropertyAll(AppTheme.tertiary),
+                      foregroundColor:
+                          MaterialStatePropertyAll(AppTheme.quaternary)),
+                  onPressed: () {
+                    // No hace nada por el momento
+                    Navigator.of(context).pop();
+                  },
+                ),
+              ],
+            )
           ],
           ],
-        ));
+        );
+      },
+    );
+  }
+
+  String _formatDateTime(String? dateTimeString) {
+    if (dateTimeString == null) return "Sin fecha";
+
+    DateTime parsedDate = DateTime.parse(dateTimeString);
+
+    var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
+    return formatter.format(parsedDate.toLocal());
   }
   }
 }
 }

+ 37 - 0
lib/views/pedido/pedido_sync.dart

@@ -0,0 +1,37 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import '../../viewmodels/viewmodels.dart';
+
+class PedidoSync {
+  static final PedidoSync _instance = PedidoSync._internal();
+  Timer? _syncTimer;
+
+  factory PedidoSync() {
+    return _instance;
+  }
+
+  PedidoSync._internal();
+
+  void startSync(PedidoViewModel pedidoViewModel) {
+    if (_syncTimer != null && _syncTimer!.isActive) return;
+
+    _syncTimer = Timer.periodic(Duration(seconds: 5), (timer) async {
+      bool hasMoreToSync = await pedidoViewModel.sincronizarPedidos();
+      // if (!hasMoreToSync) {
+      //   timer.cancel();
+      //   _syncTimer = null;
+      //   print('Sincronización completa, no hay más pedidos por sincronizar.');
+      // }
+    });
+  }
+
+  void stopSync() {
+    _syncTimer?.cancel();
+    _syncTimer = null;
+  }
+
+  bool isSyncing() {
+    return _syncTimer != null && _syncTimer!.isActive;
+  }
+}

+ 19 - 5
lib/views/pedido/pedido_ticket.dart

@@ -13,14 +13,19 @@ Future<void> imprimirTicketsJuntos(BuildContext context, Pedido pedido) async {
   bool ticketCocinaActivo =
   bool ticketCocinaActivo =
       await Provider.of<VariableViewModel>(context, listen: false)
       await Provider.of<VariableViewModel>(context, listen: false)
           .isVariableActive('ticket_cocina');
           .isVariableActive('ticket_cocina');
+  bool ticketVentaActivo =
+      await Provider.of<VariableViewModel>(context, listen: false)
+          .isVariableActive('ticket_venta');
   final pdf = pw.Document();
   final pdf = pw.Document();
   final image = pw.MemoryImage(
   final image = pw.MemoryImage(
     (await rootBundle.load('assets/OlivaLogo-BN.png')).buffer.asUint8List(),
     (await rootBundle.load('assets/OlivaLogo-BN.png')).buffer.asUint8List(),
   );
   );
 
 
-  pdf.addPage(
-    generarPaginaPrimerTicket(pedido, image),
-  );
+  if (ticketVentaActivo) {
+    pdf.addPage(
+      generarPaginaPrimerTicket(pedido, image),
+    );
+  }
 
 
   if (ticketCocinaActivo) {
   if (ticketCocinaActivo) {
     pdf.addPage(
     pdf.addPage(
@@ -116,7 +121,7 @@ pw.Page generarPaginaPrimerTicket(Pedido pedido, pw.MemoryImage image) {
                   padding: const pw.EdgeInsets.only(right: 15),
                   padding: const pw.EdgeInsets.only(right: 15),
                   child: pw.Column(children: [
                   child: pw.Column(children: [
                     pw.SizedBox(height: 5),
                     pw.SizedBox(height: 5),
-                    pw.Text('Fecha: ${pedido.peticion}',
+                    pw.Text('Fecha: ${_formatDateTime(pedido.peticion)}',
                         style: const pw.TextStyle(fontSize: 9)),
                         style: const pw.TextStyle(fontSize: 9)),
                     pw.Text('Oliva Mía',
                     pw.Text('Oliva Mía',
                         style: const pw.TextStyle(fontSize: 9)),
                         style: const pw.TextStyle(fontSize: 9)),
@@ -201,7 +206,7 @@ pw.Page generarPaginaSegundoTicket(Pedido pedido) {
           pw.Text('.', style: pw.TextStyle(fontSize: 1)),
           pw.Text('.', style: pw.TextStyle(fontSize: 1)),
           pw.Padding(
           pw.Padding(
               padding: const pw.EdgeInsets.only(right: 15),
               padding: const pw.EdgeInsets.only(right: 15),
-              child: pw.Text('Fecha: ${pedido.peticion}',
+              child: pw.Text('Fecha: ${_formatDateTime(pedido.peticion)}',
                   style: pw.TextStyle(
                   style: pw.TextStyle(
                       fontSize: 9, fontWeight: pw.FontWeight.bold))),
                       fontSize: 9, fontWeight: pw.FontWeight.bold))),
           pw.SizedBox(height: 5),
           pw.SizedBox(height: 5),
@@ -280,3 +285,12 @@ Future<void> printPdf(Uint8List pdfBytes) async {
     onLayout: (PdfPageFormat format) => pdfBytes,
     onLayout: (PdfPageFormat format) => pdfBytes,
   );
   );
 }
 }
+
+String _formatDateTime(String? dateTimeString) {
+  if (dateTimeString == null) return "Sin fecha";
+
+  DateTime parsedDate = DateTime.parse(dateTimeString);
+
+  var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
+  return formatter.format(parsedDate.toLocal());
+}

+ 52 - 1
lib/views/producto/producto_screen.dart

@@ -46,6 +46,42 @@ class _ProductoScreenState extends State<ProductoScreen> {
     });
     });
   }
   }
 
 
+  Future<void> _sincronizarProductos(BuildContext context) async {
+    final productoViewModel =
+        Provider.of<ProductoViewModel>(context, listen: false);
+
+    try {
+      await productoViewModel.sincronizarProductosYCategorias();
+      _mostrarResultado(
+          context, 'La sincronización se completó exitosamente.', true);
+    } catch (e) {
+      _mostrarResultado(context, e.toString(), false);
+    }
+  }
+
+  void _mostrarResultado(BuildContext context, String mensaje, bool exito) {
+    showDialog(
+      context: context,
+      builder: (BuildContext context) {
+        return AlertDialog(
+          title: Text(
+              exito ? 'Sincronización exitosa' : 'Error de sincronización'),
+          content: SingleChildScrollView(
+            child: Text(mensaje),
+          ),
+          actions: [
+            TextButton(
+              child: Text('OK'),
+              onPressed: () {
+                Navigator.of(context).pop();
+              },
+            ),
+          ],
+        );
+      },
+    );
+  }
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final model = Provider.of<ProductoViewModel>(context);
     final model = Provider.of<ProductoViewModel>(context);
@@ -161,7 +197,7 @@ class _ProductoScreenState extends State<ProductoScreen> {
           },
           },
         ),
         ),
         DataCell(
         DataCell(
-          Text(item.precio ?? "Sin Precio"),
+          Text(item.precio.toString()),
           onTap: () {
           onTap: () {
             Provider.of<ProductoViewModel>(context, listen: false)
             Provider.of<ProductoViewModel>(context, listen: false)
                 .selectProducto(item);
                 .selectProducto(item);
@@ -179,6 +215,21 @@ class _ProductoScreenState extends State<ProductoScreen> {
               TextStyle(color: AppTheme.secondary, fontWeight: FontWeight.w500),
               TextStyle(color: AppTheme.secondary, fontWeight: FontWeight.w500),
         ),
         ),
         iconTheme: IconThemeData(color: AppTheme.secondary),
         iconTheme: IconThemeData(color: AppTheme.secondary),
+        actions: [
+          TextButton.icon(
+            icon: Icon(Icons.sync, color: AppTheme.secondary),
+            label: Text(
+              "Sincronizar",
+              style: TextStyle(
+                  color: AppTheme.secondary,
+                  fontWeight: FontWeight.w500,
+                  fontSize: 18),
+            ),
+            onPressed: () async {
+              await _sincronizarProductos(context);
+            },
+          )
+        ],
       ),
       ),
       body: Column(
       body: Column(
         children: [
         children: [

+ 17 - 4
lib/views/variable/variable_screen.dart

@@ -128,7 +128,7 @@ class _VariablesScreenState extends State<VariablesScreen> {
           ),
           ),
         ])),
         ])),
         DataCell(
         DataCell(
-          Text(item.id.toString()),
+          Text(item.nombre.toString()),
           onTap: () {
           onTap: () {
             Provider.of<VariableViewModel>(context, listen: false)
             Provider.of<VariableViewModel>(context, listen: false)
                 .selectVariable(item);
                 .selectVariable(item);
@@ -136,7 +136,18 @@ class _VariablesScreenState extends State<VariablesScreen> {
           },
           },
         ),
         ),
         DataCell(
         DataCell(
-          Text(item.nombre.toString()),
+          Text(item.clave.toString()),
+          onTap: () {
+            Provider.of<VariableViewModel>(context, listen: false)
+                .selectVariable(item);
+            go(item);
+          },
+        ),
+        DataCell(
+          Icon(
+            item.activo == true ? Icons.check_circle : Icons.cancel,
+            color: item.activo == true ? Colors.green : Colors.red,
+          ),
           onTap: () {
           onTap: () {
             Provider.of<VariableViewModel>(context, listen: false)
             Provider.of<VariableViewModel>(context, listen: false)
                 .selectVariable(item);
                 .selectVariable(item);
@@ -224,9 +235,11 @@ class _VariablesScreenState extends State<VariablesScreen> {
                                   columns: [
                                   columns: [
                                     DataColumn(label: Text(" ", style: estilo)),
                                     DataColumn(label: Text(" ", style: estilo)),
                                     DataColumn(
                                     DataColumn(
-                                        label: Text("ID", style: estilo)),
-                                    DataColumn(
                                         label: Text("NOMBRE", style: estilo)),
                                         label: Text("NOMBRE", style: estilo)),
+                                    DataColumn(
+                                        label: Text("CLAVE", style: estilo)),
+                                    DataColumn(
+                                        label: Text("ACTIVO", style: estilo)),
                                   ],
                                   ],
                                   rows: registros,
                                   rows: registros,
                                 ),
                                 ),

+ 14 - 2
lib/views/venta/venta_screen.dart

@@ -249,11 +249,23 @@ class _VentaScreenState extends State<VentaScreen> {
   }
   }
 
 
   void cargarPedidos(DateTime fecha) async {
   void cargarPedidos(DateTime fecha) async {
-    final inicioDelDia = DateTime(fecha.year, fecha.month, fecha.day);
-    final finDelDia = DateTime(fecha.year, fecha.month, fecha.day, 23, 59, 59);
+    // Convertir el inicio y fin del día local a UTC
+    final inicioDelDia = DateTime(fecha.year, fecha.month, fecha.day)
+        .toUtc(); // Convierte la fecha local de inicio del día a UTC
+
+    final finDelDia = DateTime(fecha.year, fecha.month, fecha.day, 23, 59, 59)
+        .toUtc(); // Convierte la fecha local de fin del día a UTC
+
+    print('Buscando pedidos desde: $inicioDelDia hasta: $finDelDia (en UTC)');
+
+    // Realizar la búsqueda en UTC
     final pedidos = await Provider.of<PedidoViewModel>(context, listen: false)
     final pedidos = await Provider.of<PedidoViewModel>(context, listen: false)
         .buscarPorFecha(inicioDelDia, finDelDia);
         .buscarPorFecha(inicioDelDia, finDelDia);
 
 
+    print('Pedidos obtenidos: ${pedidos.length}');
+    pedidos.forEach((pedido) => print(
+        'Pedido: ${pedido.folio}, Total: ${pedido.totalPedido}, Estatus: ${pedido.estatus}'));
+
     final pedidosNoCancelados =
     final pedidosNoCancelados =
         pedidos.where((p) => p.estatus != "CANCELADO").toList();
         pedidos.where((p) => p.estatus != "CANCELADO").toList();
     final pedidosCancelados =
     final pedidosCancelados =

+ 1 - 1
lib/widgets/app_drawer.dart

@@ -193,7 +193,7 @@ class AppDrawer extends StatelessWidget {
               child: Align(
               child: Align(
                 alignment: Alignment.bottomCenter,
                 alignment: Alignment.bottomCenter,
                 child: Text(
                 child: Text(
-                  'v1.24.08.16',
+                  'v1.24.10.10',
                   style: TextStyle(fontWeight: FontWeight.w300),
                   style: TextStyle(fontWeight: FontWeight.w300),
                 ),
                 ),
               ))
               ))