瀏覽代碼

Modulo corte de caja, pedidos con mesa y token totp seguridad

OscarGil03 4 月之前
父節點
當前提交
0e18511ca2

+ 7 - 0
lib/main.dart

@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
 import 'package:provider/provider.dart';
 import 'package:provider/provider.dart';
 import 'package:sqflite_common_ffi/sqflite_ffi.dart';
 import 'package:sqflite_common_ffi/sqflite_ffi.dart';
 import 'dart:io';
 import 'dart:io';
+import 'package:flutter_single_instance/flutter_single_instance.dart';
 
 
 import 'services/productos_service.dart';
 import 'services/productos_service.dart';
 import 'views/home/home_screen.dart';
 import 'views/home/home_screen.dart';
@@ -16,6 +17,11 @@ void main() async {
   WidgetsFlutterBinding.ensureInitialized();
   WidgetsFlutterBinding.ensureInitialized();
   tzdata.initializeTimeZones();
   tzdata.initializeTimeZones();
 
 
+  if (!await FlutterSingleInstance.platform.isFirstInstance()) {
+    print("La aplicación ya se está ejecutando");
+    exit(0);
+  }
+
   // Inicialización de la base de datos para plataformas de escritorio con FFI
   // Inicialización de la base de datos para plataformas de escritorio con FFI
   if (Platform.isWindows || Platform.isLinux) {
   if (Platform.isWindows || Platform.isLinux) {
     sqfliteFfiInit();
     sqfliteFfiInit();
@@ -48,6 +54,7 @@ void main() async {
       ChangeNotifierProvider(create: (_) => PermisoViewModel()),
       ChangeNotifierProvider(create: (_) => PermisoViewModel()),
       ChangeNotifierProvider(create: (_) => CorteCajaViewModel()),
       ChangeNotifierProvider(create: (_) => CorteCajaViewModel()),
       ChangeNotifierProvider(create: (_) => MesaViewModel()),
       ChangeNotifierProvider(create: (_) => MesaViewModel()),
+      ChangeNotifierProvider(create: (_) => PropinaViewModel()),
       // Agrega aquí cualquier otro provider que necesites
       // Agrega aquí cualquier otro provider que necesites
     ], child: const MyApp()));
     ], child: const MyApp()));
   });
   });

+ 4 - 0
lib/models/categoria_producto_model.dart

@@ -7,6 +7,8 @@ class CategoriaProducto extends Basico {
   int? esToping;
   int? esToping;
   int? maximo;
   int? maximo;
   int? minimo;
   int? minimo;
+  int? idWeb;
+  String? sincronizado;
 
 
   CategoriaProducto({
   CategoriaProducto({
     super.id,
     super.id,
@@ -15,6 +17,8 @@ class CategoriaProducto extends Basico {
     this.esToping,
     this.esToping,
     this.maximo,
     this.maximo,
     this.minimo,
     this.minimo,
+    this.idWeb,
+    this.sincronizado,
   });
   });
 
 
   @override
   @override

+ 20 - 1
lib/models/corte_caja_model.dart

@@ -1,4 +1,5 @@
-import 'basico_model.dart';
+import 'package:turquessa_app/models/models.dart';
+
 import '../services/services.dart';
 import '../services/services.dart';
 
 
 class CorteCaja {
 class CorteCaja {
@@ -20,6 +21,12 @@ class CorteCaja {
   DateTime? creado;
   DateTime? creado;
   DateTime? eliminado;
   DateTime? eliminado;
   DateTime? modificado;
   DateTime? modificado;
+  String? idWeb;
+  String? sincronizado;
+
+  List<Deposito> depositos = [];
+  List<Retiro> retiros = [];
+  List<Gasto> gastos = [];
 
 
   CorteCaja({
   CorteCaja({
     this.id,
     this.id,
@@ -40,6 +47,11 @@ class CorteCaja {
     this.creado,
     this.creado,
     this.modificado,
     this.modificado,
     this.eliminado,
     this.eliminado,
+    this.idWeb,
+    this.sincronizado,
+    this.depositos = const [],
+    this.retiros = const [],
+    this.gastos = const [],
   });
   });
 
 
   Map<String, dynamic> toJson() {
   Map<String, dynamic> toJson() {
@@ -62,6 +74,8 @@ class CorteCaja {
       'creado': creado?.toIso8601String(),
       'creado': creado?.toIso8601String(),
       'modificado': modificado?.toIso8601String(),
       'modificado': modificado?.toIso8601String(),
       'eliminado': eliminado?.toIso8601String(),
       'eliminado': eliminado?.toIso8601String(),
+      'sincronizado': sincronizado,
+      'idWeb': idWeb,
     };
     };
   }
   }
 
 
@@ -85,6 +99,9 @@ class CorteCaja {
       'creado': creado,
       'creado': creado,
       'modificado': modificado,
       'modificado': modificado,
       'eliminado': eliminado,
       'eliminado': eliminado,
+      'depositos': depositos.map((d) => d.toApi()).toList(),
+      'retiros': retiros.map((r) => r.toApi()).toList(),
+      'gastos': gastos.map((g) => g.toApi()).toList(),
     };
     };
   }
   }
 
 
@@ -107,6 +124,8 @@ class CorteCaja {
     creado = Basico.parseDate(json['creado']);
     creado = Basico.parseDate(json['creado']);
     eliminado = Basico.parseDate(json['eliminado']);
     eliminado = Basico.parseDate(json['eliminado']);
     modificado = Basico.parseDate(json['modificado']);
     modificado = Basico.parseDate(json['modificado']);
+    idWeb = Basico.parseString(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
   }
   }
 
 
   Future<void> guardar() async {
   Future<void> guardar() async {

+ 12 - 1
lib/models/item_carrito_model.dart

@@ -1,3 +1,4 @@
+import 'package:flutter/material.dart';
 import 'toping_model.dart';
 import 'toping_model.dart';
 import 'producto_model.dart';
 import 'producto_model.dart';
 
 
@@ -6,12 +7,22 @@ class ItemCarrito {
   int cantidad;
   int cantidad;
   Map<int, Set<int>> selectedToppings;
   Map<int, Set<int>> selectedToppings;
   Map<int, List<Producto>> selectableToppings;
   Map<int, List<Producto>> selectableToppings;
+  String? comentario;
+  bool expandido;
+  TextEditingController comentarioController;
 
 
   ItemCarrito({
   ItemCarrito({
     required this.producto,
     required this.producto,
     this.cantidad = 1,
     this.cantidad = 1,
+    this.comentario,
+    this.expandido = false,
     Map<int, Set<int>>? selectedToppings,
     Map<int, Set<int>>? selectedToppings,
     Map<int, List<Producto>>? selectableToppings,
     Map<int, List<Producto>>? selectableToppings,
   })  : selectedToppings = selectedToppings ?? {},
   })  : selectedToppings = selectedToppings ?? {},
-        selectableToppings = selectableToppings ?? {};
+        selectableToppings = selectableToppings ?? {},
+        comentarioController = TextEditingController(text: comentario ?? '');
+
+  void dispose() {
+    comentarioController.dispose();
+  }
 }
 }

+ 1 - 0
lib/models/models.dart

@@ -24,3 +24,4 @@ export '../models/permiso_model.dart';
 export '../models/usuario_permiso_model.dart';
 export '../models/usuario_permiso_model.dart';
 export '../models/retiro_model.dart';
 export '../models/retiro_model.dart';
 export '../models/mesa_model.dart';
 export '../models/mesa_model.dart';
+export '../models/propina_model.dart';

+ 17 - 2
lib/models/pedido_model.dart

@@ -1,7 +1,6 @@
 import 'package:intl/intl.dart';
 import 'package:intl/intl.dart';
 
 
-import 'basico_model.dart';
-import 'pedido_producto_model.dart';
+import 'models.dart';
 import '../services/services.dart';
 import '../services/services.dart';
 
 
 class Pedido extends Basico {
 class Pedido extends Basico {
@@ -25,6 +24,9 @@ class Pedido extends Basico {
   double? cantTransferencia;
   double? cantTransferencia;
   List<PedidoProducto> productos = [];
   List<PedidoProducto> productos = [];
   int? idWeb;
   int? idWeb;
+  String? uuid;
+  String? idCorteCaja;
+  List<Propinas> propinas = [];
 
 
   String? sincronizado;
   String? sincronizado;
 
 
@@ -51,6 +53,8 @@ class Pedido extends Basico {
     this.productos = const [],
     this.productos = const [],
     this.idWeb,
     this.idWeb,
     this.sincronizado,
     this.sincronizado,
+    this.uuid,
+    this.idCorteCaja,
   });
   });
 
 
   @override
   @override
@@ -77,6 +81,7 @@ class Pedido extends Basico {
       'cantTransferencia': cantTransferencia,
       'cantTransferencia': cantTransferencia,
       'sincronizado': sincronizado,
       'sincronizado': sincronizado,
       'idWeb': idWeb,
       'idWeb': idWeb,
+      'uuid': uuid,
     }..addAll(super.toJson());
     }..addAll(super.toJson());
   }
   }
 
 
@@ -97,7 +102,10 @@ class Pedido extends Basico {
       'cantEfectivo': cantEfectivo,
       'cantEfectivo': cantEfectivo,
       'cantTarjeta': cantTarjeta,
       'cantTarjeta': cantTarjeta,
       'cantTransferencia': cantTransferencia,
       'cantTransferencia': cantTransferencia,
+      'idMesa': idMesa,
+      'uuid': uuid,
       'productos': productos.map((producto) => producto.toApi()).toList(),
       'productos': productos.map((producto) => producto.toApi()).toList(),
+      'propinas': propinas.map((propina) => propina.toApi()).toList(),
     };
     };
     Map<String, dynamic> basicoMap = super.toJson();
     Map<String, dynamic> basicoMap = super.toJson();
     basicoMap.remove('id');
     basicoMap.remove('id');
@@ -128,6 +136,7 @@ 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']);
+    uuid = Basico.parseString(json['uuid']);
     idWeb = Basico.parseInt(json['idWeb']);
     idWeb = Basico.parseInt(json['idWeb']);
     sincronizado = Basico.parseString(json['sincronizado']);
     sincronizado = Basico.parseString(json['sincronizado']);
 
 
@@ -139,5 +148,11 @@ class Pedido extends Basico {
       }
       }
     }
     }
     productos = _productos;
     productos = _productos;
+
+    if (json["propinas"] != null && (json["propinas"] as List).isNotEmpty) {
+      propinas = (json["propinas"] as List)
+          .map((item) => Propinas.fromJson(item))
+          .toList();
+    }
   }
   }
 }
 }

+ 0 - 1
lib/models/pedido_producto_model.dart

@@ -53,7 +53,6 @@ class PedidoProducto extends Basico {
       'costoUnitario': costoUnitario,
       'costoUnitario': costoUnitario,
       'descuento': descuento,
       'descuento': descuento,
       'cantidad': cantidad,
       'cantidad': cantidad,
-      'idWeb': idWeb,
       'comentario': comentario,
       'comentario': comentario,
       'eliminado': eliminado,
       'eliminado': eliminado,
       'toppings': toppings.map((topping) => topping.toApi()).toList(),
       'toppings': toppings.map((topping) => topping.toApi()).toList(),

+ 10 - 0
lib/models/producto_model.dart

@@ -19,6 +19,8 @@ class Producto extends Basico {
   List<Producto>? topings;
   List<Producto>? topings;
   int? activo;
   int? activo;
   List<Media>? media;
   List<Media>? media;
+  int? idWeb;
+  String? sincronizado;
 
 
   Producto({
   Producto({
     super.id,
     super.id,
@@ -36,6 +38,8 @@ class Producto extends Basico {
     this.topings,
     this.topings,
     this.activo,
     this.activo,
     this.media,
     this.media,
+    this.idWeb,
+    this.sincronizado,
   });
   });
 
 
   @override
   @override
@@ -64,6 +68,8 @@ class Producto extends Basico {
       'creado': creado?.toIso8601String(),
       'creado': creado?.toIso8601String(),
       'modificado': modificado?.toIso8601String(),
       'modificado': modificado?.toIso8601String(),
       'eliminado': eliminado?.toIso8601String(),
       'eliminado': eliminado?.toIso8601String(),
+      'sincronizado': sincronizado,
+      'idWeb': idWeb,
     };
     };
 
 
     return data..addAll(super.toJson());
     return data..addAll(super.toJson());
@@ -106,6 +112,8 @@ class Producto extends Basico {
     codigo = Basico.parseString(json['codigo']);
     codigo = Basico.parseString(json['codigo']);
     descuento = Basico.parseString(json['descuento']);
     descuento = Basico.parseString(json['descuento']);
     activo = Basico.parseInt(json['activo']);
     activo = Basico.parseInt(json['activo']);
+    idWeb = Basico.parseInt(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
     if (json['toping'] is bool) {
     if (json['toping'] is bool) {
       toping = json['toping'] ? 1 : 0;
       toping = json['toping'] ? 1 : 0;
     } else {
     } else {
@@ -134,6 +142,8 @@ class Producto extends Basico {
     creado = Basico.parseDate(json['creado']);
     creado = Basico.parseDate(json['creado']);
     modificado = Basico.parseDate(json['modificado']);
     modificado = Basico.parseDate(json['modificado']);
     eliminado = Basico.parseDate(json['eliminado']);
     eliminado = Basico.parseDate(json['eliminado']);
+    idWeb = Basico.parseInt(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
     if (json['media'] != null) {
     if (json['media'] != null) {
       media = (json['media'] as List).map((i) => Media.fromJson(i)).toList();
       media = (json['media'] as List).map((i) => Media.fromJson(i)).toList();
     }
     }

+ 54 - 0
lib/models/propina_model.dart

@@ -0,0 +1,54 @@
+import 'models.dart';
+
+class Propinas extends Basico {
+  int? idPedido;
+  double? cantidad;
+  String? comentario;
+  String? sincronizado;
+  int? idWeb;
+
+  Propinas({
+    super.id,
+    this.idPedido,
+    this.cantidad,
+    this.comentario,
+    this.sincronizado,
+    this.idWeb,
+    super.eliminado,
+    super.creado,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idPedido': idPedido,
+      'cantidad': cantidad,
+      'comentario': comentario,
+      'sincronizado': sincronizado,
+      'creado': creado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+      'idWeb': idWeb,
+    }..addAll(super.toJson());
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'idPedido': idPedido,
+      'cantidad': cantidad,
+      'comentario': comentario,
+      'sincronizado': sincronizado,
+      'creado': creado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  Propinas.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idPedido = Basico.parseInt(json['idPedido']);
+    cantidad = Basico.parseDouble(json['cantidad']);
+    comentario = Basico.parseString(json['comentario']);
+    idWeb = Basico.parseInt(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
+  }
+}

+ 116 - 4
lib/services/repo_service.dart

@@ -9,7 +9,7 @@ import 'services.dart';
 import '../views/producto/producto_imagen.dart';
 import '../views/producto/producto_imagen.dart';
 
 
 class RepoService<T> {
 class RepoService<T> {
-  static int dbVersion = 23;
+  static int dbVersion = 26;
   static String dbName = 'posTurquessa.db';
   static String dbName = 'posTurquessa.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;
@@ -27,6 +27,7 @@ class RepoService<T> {
     'Variable': Variable(),
     'Variable': Variable(),
     'Descuento': Descuento(),
     'Descuento': Descuento(),
     'Mesa': Mesa(),
     'Mesa': Mesa(),
+    'Propinas': Propinas(),
   };
   };
 
 
   Future<Database?> get db async {
   Future<Database?> get db async {
@@ -644,6 +645,54 @@ class RepoService<T> {
           });
           });
 
 
           break;
           break;
+
+        case 23:
+          await db.execute('''
+          ALTER TABLE Pedido ADD COLUMN uuid TEXT;
+        ''');
+
+          break;
+
+        case 24:
+          await db.execute('''
+          CREATE TABLE Propinas (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            idLocal INTEGER,
+            idPedido INTEGER,
+            cantidad REAL,
+            comentario TEXT,
+            sincronizado TEXT,
+            idWeb INTEGER,
+            creado TEXT,
+            modificado TEXT,
+            eliminado TEXT
+          )
+        ''');
+
+          break;
+
+        case 25:
+          await db.execute('''
+          ALTER TABLE Pedido ADD COLUMN idCorteCaja TEXT;
+        ''');
+
+          await db.execute('''
+          ALTER TABLE Producto ADD COLUMN idWeb INTEGER;
+        ''');
+
+          await db.execute('''
+          ALTER TABLE Producto ADD COLUMN sincronizado TEXT;
+        ''');
+
+          await db.execute('''
+          ALTER TABLE CategoriaProducto ADD COLUMN idWeb INTEGER;
+        ''');
+
+          await db.execute('''
+          ALTER TABLE CategoriaProducto ADD COLUMN sincronizado TEXT;
+        ''');
+
+          break;
       }
       }
       oldVersion++;
       oldVersion++;
     }
     }
@@ -860,11 +909,21 @@ class RepoService<T> {
     return item.toJson();
     return item.toJson();
   }
   }
 
 
-  Future<List<T>> obtenerTodos({String orderBy = 'id DESC'}) async {
+  Future<List<T>> obtenerTodos<T>({
+    String where = 'eliminado IS NULL',
+    List<dynamic>? whereArgs,
+    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, where: 'eliminado IS NULL', orderBy: orderBy);
+
+    var result = await db!.query(
+      tableName,
+      where: where,
+      whereArgs: whereArgs,
+      orderBy: orderBy,
+    );
+
     return result.map((map) => fromMap<T>(map)).toList();
     return result.map((map) => fromMap<T>(map)).toList();
   }
   }
 
 
@@ -878,6 +937,8 @@ class RepoService<T> {
         return Usuario.fromJson(map) as T;
         return Usuario.fromJson(map) as T;
       case CorteCaja:
       case CorteCaja:
         return CorteCaja.fromJson(map) as T;
         return CorteCaja.fromJson(map) as T;
+      // case Propinas:
+      //   return Propinas.fromJson(map) as T;
       default:
       default:
         throw Exception('Tipo no soportado');
         throw Exception('Tipo no soportado');
     }
     }
@@ -1540,4 +1601,55 @@ class RepoService<T> {
       }
       }
     }
     }
   }
   }
+
+  Future<List<Producto>>
+      obtenerProductosNoSincronizadosOrdenadosPorFecha() async {
+    var dbClient = await db;
+    List<Map<String, dynamic>> result = await dbClient!.query(
+      'Producto',
+      where: 'sincronizado IS NULL AND eliminado IS NULL',
+      orderBy: 'creado ASC',
+    );
+    return result.map((map) => Producto.fromJson(map)).toList();
+  }
+
+  Future<List<CategoriaProducto>>
+      obtenerCategoriasNoSincronizadasOrdenadasPorFecha() async {
+    var dbClient = await db;
+    // Similar a productos, ordenando por 'modificado'
+    List<Map<String, dynamic>> result = await dbClient!.query(
+      'CategoriaProducto',
+      where: 'sincronizado IS NULL AND eliminado IS NULL',
+      orderBy: 'creado ASC',
+    );
+    return result.map((map) => CategoriaProducto.fromJson(map)).toList();
+  }
+
+  Future<void> actualizarProductoSincronizado(
+      int idProducto, int idWeb, String sincronizado) async {
+    var dbClient = await db;
+    await dbClient!.update(
+      'Producto',
+      {
+        'idWeb': idWeb,
+        'sincronizado': sincronizado,
+      },
+      where: 'id = ?',
+      whereArgs: [idProducto],
+    );
+  }
+
+  Future<void> actualizarCategoriaSincronizada(
+      int idCategoria, int idWeb, String sincronizado) async {
+    var dbClient = await db;
+    await dbClient!.update(
+      'CategoriaProducto',
+      {
+        'idWeb': idWeb,
+        'sincronizado': sincronizado,
+      },
+      where: 'id = ?',
+      whereArgs: [idCategoria],
+    );
+  }
 }
 }

+ 119 - 0
lib/viewmodels/corte_caja_view_model.dart

@@ -1,9 +1,13 @@
+import 'package:collection/collection.dart';
+
 import '../../widgets/widgets.dart';
 import '../../widgets/widgets.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:intl/intl.dart';
 import 'package:intl/intl.dart';
 import 'package:uuid/uuid.dart';
 import 'package:uuid/uuid.dart';
+import '../data/api_response.dart';
 import '../models/models.dart';
 import '../models/models.dart';
 import '../services/repo_service.dart';
 import '../services/repo_service.dart';
+import '../services/services.dart';
 import '../views/corte_caja/corte_caja_ticket.dart';
 import '../views/corte_caja/corte_caja_ticket.dart';
 
 
 class CorteCajaViewModel extends ChangeNotifier {
 class CorteCajaViewModel extends ChangeNotifier {
@@ -174,11 +178,110 @@ class CorteCajaViewModel extends ChangeNotifier {
     notifyListeners();
     notifyListeners();
   }
   }
 
 
+  Future<CorteCaja?> fetchCorteCajaConDetalles(String idCorteCaja) async {
+    // Similar a fetchDepositosAndRetiros, pero devuelve el corte ya cargado
+    await fetchDepositosAndRetiros(idCorteCaja);
+    return _cortes.firstWhereOrNull((c) => c.id == idCorteCaja);
+  }
+
   void selectCorte(CorteCaja corte) {
   void selectCorte(CorteCaja corte) {
     _selectedCorte = corte;
     _selectedCorte = corte;
     notifyListeners();
     notifyListeners();
   }
   }
 
 
+  Future<List<CorteCaja>>
+      fetchAllLocalCortesCajasNoSincronizadosOrdenadosPorFecha() async {
+    setIsLoading(true);
+    var db = await RepoService().db;
+
+    // Suponiendo que tenemos un campo 'sincronizado' similar a Pedidos.
+    // Aquí obtendremos todos los corteCaja donde sincronizado IS NULL
+    List<Map<String, dynamic>> result = await db!.query('CorteCaja',
+        where: 'sincronizado IS NULL',
+        orderBy: 'fechaApertura ASC' // Ordenar por fecha de apertura
+        );
+
+    List<CorteCaja> cortesNoSync =
+        result.map((map) => CorteCaja.fromJson(map)).toList();
+    setIsLoading(false);
+    return cortesNoSync;
+  }
+
+  Future<bool> sincronizarCorteCajas() async {
+    // Similar a sincronizarPedidos
+    List<CorteCaja> cortesNoSincronizados =
+        await fetchAllLocalCortesCajasNoSincronizadosOrdenadosPorFecha();
+
+    if (cortesNoSincronizados.isNotEmpty) {
+      CorteCaja corteNoSincronizado = cortesNoSincronizados.first;
+
+      // Cargar los detalles (depositos, retiros, gastos)
+      if (corteNoSincronizado.id != null &&
+          corteNoSincronizado.id!.isNotEmpty) {
+        await fetchCorteCajaConDetalles(corteNoSincronizado.id!);
+        // Obtenemos el corte con la misma ID desde la lista de cortes
+        corteNoSincronizado =
+            _cortes.firstWhere((c) => c.id == corteNoSincronizado.id);
+
+// Asignamos los depósitos, retiros y gastos del ViewModel al objeto corteNoSincronizado
+        corteNoSincronizado.depositos = this.depositos;
+        corteNoSincronizado.retiros = this.retiros;
+        corteNoSincronizado.gastos = this.gastos;
+      }
+
+      Map<String, dynamic> corteJson =
+          await prepararCorteCajaParaApi(corteNoSincronizado);
+
+      print('JSON Corte Caja enviado: $corteJson');
+
+      var response = ApiResponse(await BaseService()
+          .post('/pos/corte-caja/sincronizar2', body: corteJson));
+
+      if (response.isOk && response.detalle != null) {
+        String idWeb = response.detalle!['idWeb'];
+        String sincronizado = response.detalle!['sincronizado'];
+
+        await actualizarCorteCajaSincronizado(
+            corteNoSincronizado.id!, idWeb, sincronizado);
+
+        return true;
+      } else {
+        print(
+            'Error en la sincronización del corte de caja: ${response.mensaje}');
+        return true; // Retornar true para que siga intentando
+      }
+    } else {
+      print('No se encontraron cortes de caja no sincronizados.');
+      return false;
+    }
+  }
+
+  Future<void> actualizarCorteCajaSincronizado(
+      String idCorteCaja, String idWeb, String sincronizado) async {
+    var db = await RepoService().db;
+
+    await db!.update(
+      'CorteCaja',
+      {
+        'idWeb': idWeb,
+        'sincronizado': sincronizado,
+      },
+      where: 'id = ?',
+      whereArgs: [idCorteCaja],
+    );
+  }
+
+  Future<Map<String, dynamic>> prepararCorteCajaParaApi(CorteCaja corte) async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+    Map<String, dynamic> apiMap = corte.toApi();
+    apiMap['claveSucursal'] = claveSucursal;
+    if (corte.idWeb != null && corte.idWeb!.isNotEmpty) {
+      apiMap['idWeb'] = corte.idWeb;
+    }
+    return apiMap;
+  }
+
   Future<void> addDeposito(double monto, String descripcion, String? persona,
   Future<void> addDeposito(double monto, String descripcion, String? persona,
       String corteCajaId) async {
       String corteCajaId) async {
     Deposito nuevoDeposito = Deposito(
     Deposito nuevoDeposito = Deposito(
@@ -196,6 +299,11 @@ class CorteCajaViewModel extends ChangeNotifier {
 
 
     calcularCorteFinal();
     calcularCorteFinal();
 
 
+    if (_selectedCorte != null) {
+      _selectedCorte!.sincronizado = null;
+      await _selectedCorte!.guardar();
+    }
+
     notifyListeners();
     notifyListeners();
   }
   }
 
 
@@ -216,6 +324,11 @@ class CorteCajaViewModel extends ChangeNotifier {
 
 
     calcularCorteFinal();
     calcularCorteFinal();
 
 
+    if (_selectedCorte != null) {
+      _selectedCorte!.sincronizado = null;
+      await _selectedCorte!.guardar();
+    }
+
     notifyListeners();
     notifyListeners();
   }
   }
 
 
@@ -236,6 +349,11 @@ class CorteCajaViewModel extends ChangeNotifier {
 
 
     calcularCorteFinal();
     calcularCorteFinal();
 
 
+    if (_selectedCorte != null) {
+      _selectedCorte!.sincronizado = null;
+      await _selectedCorte!.guardar();
+    }
+
     notifyListeners();
     notifyListeners();
   }
   }
 
 
@@ -315,6 +433,7 @@ class CorteCajaViewModel extends ChangeNotifier {
     _selectedCorte?.gasto = gasto;
     _selectedCorte?.gasto = gasto;
     _selectedCorte?.retiro = retiro;
     _selectedCorte?.retiro = retiro;
     _selectedCorte?.deposito = deposito;
     _selectedCorte?.deposito = deposito;
+    _selectedCorte!.sincronizado = null;
 
 
     calcularCorteFinal();
     calcularCorteFinal();
 
 

+ 16 - 7
lib/viewmodels/mesa_view_model.dart

@@ -34,19 +34,28 @@ class MesaViewModel extends ChangeNotifier {
     notifyListeners();
     notifyListeners();
   }
   }
 
 
-  Future<void> fetchLocalAll({int page = 1}) async {
+  Future<void> fetchLocalAll({int page = 1, bool sinLimite = false}) async {
     _currentPage = page;
     _currentPage = page;
     var db = await RepoService().db;
     var db = await RepoService().db;
 
 
-    int? count =
-        Sqflite.firstIntValue(await db!.rawQuery('SELECT COUNT(*) FROM Mesa'));
-    _totalMesas = count ?? 0;
+    if (!sinLimite) {
+      int? count = Sqflite.firstIntValue(
+          await db!.rawQuery('SELECT COUNT(*) FROM Mesa'));
+      _totalMesas = count ?? 0;
+    }
 
 
-    int offset = (_limit * (page - 1));
+    String? limitOffsetClause;
+    if (!sinLimite) {
+      int offset = (_limit * (page - 1));
+      limitOffsetClause = 'LIMIT $_limit OFFSET $offset';
+    } else {
+      limitOffsetClause = '';
+    }
 
 
-    var query = await db.query('Mesa',
-        orderBy: 'id asc', limit: _limit, offset: offset);
+    var query = await db!
+        .rawQuery('SELECT * FROM Mesa ORDER BY id ASC $limitOffsetClause');
     _mesas = query.map((element) => Mesa.fromJson(element)).toList();
     _mesas = query.map((element) => Mesa.fromJson(element)).toList();
+
     notifyListeners();
     notifyListeners();
   }
   }
 
 

+ 5 - 1
lib/viewmodels/pedido_view_model.dart

@@ -238,6 +238,7 @@ class PedidoViewModel extends ChangeNotifier {
     if (pedido != null) {
     if (pedido != null) {
       RepoService<PedidoProducto> repoProducto = RepoService<PedidoProducto>();
       RepoService<PedidoProducto> repoProducto = RepoService<PedidoProducto>();
       RepoService<Producto> repoProductoInfo = RepoService<Producto>();
       RepoService<Producto> repoProductoInfo = RepoService<Producto>();
+      RepoService<Propinas> repoPropina = RepoService<Propinas>();
       List<PedidoProducto> productos =
       List<PedidoProducto> productos =
           await repoProducto.obtenerPorIdPedido(idPedido);
           await repoProducto.obtenerPorIdPedido(idPedido);
 
 
@@ -264,6 +265,9 @@ class PedidoViewModel extends ChangeNotifier {
         }
         }
 
 
         producto.toppings = toppings;
         producto.toppings = toppings;
+        // List<Propinas> propinas = await repoPropina.obtenerTodos<Propinas>(
+        //     where: 'idPedido = ?', whereArgs: [idPedido]);
+        // pedido.propinas = propinas;
       }
       }
 
 
       pedido.productos = productos;
       pedido.productos = productos;
@@ -343,7 +347,7 @@ class PedidoViewModel extends ChangeNotifier {
       Map<String, dynamic> pedidoJson =
       Map<String, dynamic> pedidoJson =
           await prepararPedidoParaApi(pedidoNoSincronizado);
           await prepararPedidoParaApi(pedidoNoSincronizado);
 
 
-      // print('JSON enviado: $pedidoJson');
+      //print('JSON enviado: $pedidoJson');
 
 
       var response = ApiResponse(await BaseService()
       var response = ApiResponse(await BaseService()
           .post('/pos/pedido/sincronizar', body: pedidoJson));
           .post('/pos/pedido/sincronizar', body: pedidoJson));

+ 98 - 1
lib/viewmodels/producto_view_model.dart

@@ -284,7 +284,6 @@ class ProductoViewModel<T> extends ChangeNotifier {
         if (toppingApi.isNotEmpty) {
         if (toppingApi.isNotEmpty) {
           print("Producto Toppings API obtenidos: ${toppingApi.length}");
           print("Producto Toppings API obtenidos: ${toppingApi.length}");
 
 
-          // Delegamos la sincronización de los toppings al RepoService
           await RepoService().sincronizarProductoTopping(toppingApi);
           await RepoService().sincronizarProductoTopping(toppingApi);
           notifyListeners();
           notifyListeners();
           return true;
           return true;
@@ -324,6 +323,104 @@ class ProductoViewModel<T> extends ChangeNotifier {
     }
     }
   }
   }
 
 
+  Future<bool> sincronizarProductosLocales() async {
+    List<Producto> productosNoSincronizados =
+        await RepoService().obtenerProductosNoSincronizadosOrdenadosPorFecha();
+
+    if (productosNoSincronizados.isNotEmpty) {
+      Producto productoNoSincronizado = productosNoSincronizados.first;
+
+      Map<String, dynamic> productoJson =
+          await prepararProductoParaApi(productoNoSincronizado);
+
+      print('JSON Producto enviado: $productoJson');
+
+      // Llamada a la API
+      var response = ApiResponse(await BaseService()
+          .post('/pos/producto/sincronizar', body: productoJson));
+
+      if (response.isOk && response.detalle != null) {
+        int idWeb = response.detalle!['idWeb'];
+        String sincronizado = response.detalle!['sincronizado'];
+        await RepoService().actualizarProductoSincronizado(
+            productoNoSincronizado.id!, idWeb, sincronizado);
+        return true;
+      } else {
+        print('Error en la sincronización del producto: ${response.mensaje}');
+        return true;
+      }
+    } else {
+      print('No se encontraron productos no sincronizados.');
+      return false;
+    }
+  }
+
+  Future<Map<String, dynamic>> prepararProductoParaApi(
+      Producto producto) async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+    Map<String, dynamic> apiMap = producto.toJson();
+
+    apiMap['claveSucursal'] = claveSucursal;
+
+    if (producto.idWeb != null && producto.idWeb! > 0) {
+      apiMap['idWeb'] = producto.idWeb;
+    }
+
+    return apiMap;
+  }
+
+  Future<Map<String, dynamic>> prepararCategoriaParaApi(
+      CategoriaProducto categoria) async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+    Map<String, dynamic> apiMap = categoria.toJson();
+    // Asegúrate que CategoriaProducto tenga un toJson y si necesitas toApi, créalo similar a Pedido.
+
+    apiMap['claveSucursal'] = claveSucursal;
+
+    if (categoria.idWeb != null && categoria.idWeb! > 0) {
+      apiMap['idWeb'] = categoria.idWeb;
+    }
+
+    return apiMap;
+  }
+
+  Future<bool> sincronizarCategoriasLocales() async {
+    // Obtener categorias no sincronizadas
+    List<CategoriaProducto> categoriasNoSincronizadas =
+        await RepoService().obtenerCategoriasNoSincronizadasOrdenadasPorFecha();
+
+    if (categoriasNoSincronizadas.isNotEmpty) {
+      CategoriaProducto categoriaNoSincronizada =
+          categoriasNoSincronizadas.first;
+
+      Map<String, dynamic> categoriaJson =
+          await prepararCategoriaParaApi(categoriaNoSincronizada);
+
+      print('JSON Categoria enviado: $categoriaJson');
+
+      // Llamada a la API
+      var response = ApiResponse(await BaseService()
+          .post('/pos/categoria/sincronizar', body: categoriaJson));
+
+      if (response.isOk && response.detalle != null) {
+        int idWeb = response.detalle!['idWeb'];
+        String sincronizado = response.detalle!['sincronizado'];
+        await RepoService().actualizarCategoriaSincronizada(
+            categoriaNoSincronizada.id!, idWeb, sincronizado);
+        return true;
+      } else {
+        print(
+            'Error en la sincronización de la categoría: ${response.mensaje}');
+        return true;
+      }
+    } else {
+      print('No se encontraron categorias no sincronizadas.');
+      return false;
+    }
+  }
+
   void setIsLoading(bool loading) {
   void setIsLoading(bool loading) {
     _isLoading = loading;
     _isLoading = loading;
     notifyListeners();
     notifyListeners();

+ 27 - 0
lib/viewmodels/propina_view_model.dart

@@ -0,0 +1,27 @@
+import 'package:flutter/foundation.dart';
+import '/models/models.dart';
+import '/services/repo_service.dart';
+
+class PropinaViewModel extends ChangeNotifier {
+  final RepoService _repoService = RepoService();
+
+  Future<void> guardarPropina(Propinas propina) async {
+    try {
+      await _repoService.guardar(propina);
+    } catch (e) {
+      print('Error al guardar la propina: $e');
+    }
+  }
+
+  Future<List<Propinas>> obtenerPropinasPorPedido(int idPedido) async {
+    try {
+      return await _repoService.obtenerTodos<Propinas>(
+        where: 'idPedido = ?',
+        whereArgs: [idPedido],
+      );
+    } catch (e) {
+      print('Error al obtener propinas: $e');
+      return [];
+    }
+  }
+}

+ 1 - 0
lib/viewmodels/viewmodels.dart

@@ -13,3 +13,4 @@ export '../viewmodels/sucursal_view_model.dart';
 export '../viewmodels/permiso_view_model.dart';
 export '../viewmodels/permiso_view_model.dart';
 export '../viewmodels/corte_caja_view_model.dart';
 export '../viewmodels/corte_caja_view_model.dart';
 export '../viewmodels/mesa_view_model.dart';
 export '../viewmodels/mesa_view_model.dart';
+export '../viewmodels/propina_view_model.dart';

+ 66 - 84
lib/views/categoria_producto/categoria_producto_screen.dart

@@ -27,15 +27,28 @@ class _CategoriaProductoScreenState extends State<CategoriaProductoScreen> {
   }
   }
 
 
   void go(CategoriaProducto categoriaProducto) {
   void go(CategoriaProducto categoriaProducto) {
-    Navigator.push(
-      context,
-      MaterialPageRoute(
-        builder: (context) =>
-            CategoriaProductoForm(categoriaProducto: categoriaProducto),
-      ),
-    ).then((_) =>
-        Provider.of<CategoriaProductoViewModel>(context, listen: false)
-            .fetchLocalAll());
+    showDialog(
+      context: context,
+      builder: (context) {
+        return TotpCuadroConfirmacion(
+          title: "Editar Categoría Producto",
+          content:
+              "Por favor, ingresa el código de autenticación para continuar.",
+          onSuccess: () {
+            Navigator.push(
+              context,
+              MaterialPageRoute(
+                builder: (context) =>
+                    CategoriaProductoForm(categoriaProducto: categoriaProducto),
+              ),
+            ).then((_) {
+              Provider.of<CategoriaProductoViewModel>(context, listen: false)
+                  .fetchLocalAll();
+            });
+          },
+        );
+      },
+    );
   }
   }
 
 
   void clearSearchAndReset() {
   void clearSearchAndReset() {
@@ -108,72 +121,29 @@ class _CategoriaProductoScreenState extends State<CategoriaProductoScreen> {
               PopupMenuItem(
               PopupMenuItem(
                 child: const Text('Eliminar'),
                 child: const Text('Eliminar'),
                 onTap: () async {
                 onTap: () async {
-                  await Future.delayed(Duration(milliseconds: 100));
-                  bool confirmado = await showDialog<bool>(
-                        context: context,
-                        builder: (context) {
-                          return AlertDialog(
-                            title: const Text("Eliminar",
-                                style: TextStyle(
-                                    fontWeight: FontWeight.w500, fontSize: 22)),
-                            content: const Text(
-                                '¿Estás seguro de que deseas eliminar esta categoría?',
-                                style: TextStyle(fontSize: 18)),
-                            actions: [
-                              Row(
-                                mainAxisAlignment:
-                                    MainAxisAlignment.spaceBetween,
-                                children: [
-                                  TextButton(
-                                    onPressed: () =>
-                                        Navigator.of(context).pop(false),
-                                    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)),
-                                  ),
-                                  TextButton(
-                                    onPressed: () =>
-                                        Navigator.of(context).pop(true),
-                                    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)),
-                                  ),
-                                ],
-                              )
-                            ],
-                          );
-                        },
-                      ) ??
-                      false;
-
-                  if (confirmado) {
-                    await Provider.of<CategoriaProductoViewModel>(context,
-                            listen: false)
-                        .deleteCategoriaProducto(item.id);
-                    Provider.of<CategoriaProductoViewModel>(context,
-                            listen: false)
-                        .fetchLocalAll();
-                  }
+                  Future.delayed(Duration.zero, () {
+                    showDialog(
+                      context: context,
+                      builder: (context) {
+                        return TotpCuadroConfirmacion(
+                          title: "Eliminar Categoría Producto",
+                          content:
+                              "Por favor, ingresa el código de autenticación para continuar.",
+                          onSuccess: () async {
+                            await Provider.of<CategoriaProductoViewModel>(
+                                    context,
+                                    listen: false)
+                                .deleteCategoriaProducto(item.id);
+                            Provider.of<CategoriaProductoViewModel>(context,
+                                    listen: false)
+                                .fetchLocalAll();
+                          },
+                        );
+                      },
+                    );
+                  });
                 },
                 },
-              )
+              ),
             ],
             ],
             icon: const Icon(Icons.more_vert),
             icon: const Icon(Icons.more_vert),
           ),
           ),
@@ -380,16 +350,28 @@ class _CategoriaProductoScreenState extends State<CategoriaProductoScreen> {
       ),
       ),
       floatingActionButton: FloatingActionButton.extended(
       floatingActionButton: FloatingActionButton.extended(
         onPressed: () async {
         onPressed: () async {
-          CategoriaProducto nuevoProducto = CategoriaProducto();
-          Navigator.push(
-            context,
-            MaterialPageRoute(
-              builder: (context) =>
-                  CategoriaProductoForm(categoriaProducto: nuevoProducto),
-            ),
-          ).then((_) =>
-              Provider.of<CategoriaProductoViewModel>(context, listen: false)
-                  .fetchLocalAll());
+          showDialog(
+            context: context,
+            builder: (context) {
+              return TotpCuadroConfirmacion(
+                title: "Agregar Categoría",
+                content:
+                    "Por favor, ingresa el código de autenticación para continuar.",
+                onSuccess: () {
+                  CategoriaProducto nuevoProducto = CategoriaProducto();
+                  Navigator.push(
+                    context,
+                    MaterialPageRoute(
+                      builder: (context) => CategoriaProductoForm(
+                          categoriaProducto: nuevoProducto),
+                    ),
+                  ).then((_) => Provider.of<CategoriaProductoViewModel>(context,
+                          listen: false)
+                      .fetchLocalAll());
+                },
+              );
+            },
+          );
         },
         },
         icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
         icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
         label: Text(
         label: Text(

+ 104 - 25
lib/views/corte_caja/corte_caja_form.dart

@@ -7,6 +7,7 @@ import '../../models/corte_caja_model.dart';
 import '../../viewmodels/corte_caja_view_model.dart';
 import '../../viewmodels/corte_caja_view_model.dart';
 import '../../widgets/app_textfield.dart';
 import '../../widgets/app_textfield.dart';
 import '../../widgets/widgets.dart';
 import '../../widgets/widgets.dart';
+import 'dart:async';
 
 
 class CorteCajaForm extends StatefulWidget {
 class CorteCajaForm extends StatefulWidget {
   final String? corteCajaId;
   final String? corteCajaId;
@@ -33,6 +34,9 @@ class _CorteCajaFormState extends State<CorteCajaForm> {
   final TextEditingController _descripcionGastoController =
   final TextEditingController _descripcionGastoController =
       TextEditingController();
       TextEditingController();
   final TextEditingController _personaGastoController = TextEditingController();
   final TextEditingController _personaGastoController = TextEditingController();
+  late double fondoInicial;
+  Timer? _validationTimer;
+  bool isFondoValid = true;
   DateTime? fechaApertura;
   DateTime? fechaApertura;
   bool isFirst = true;
   bool isFirst = true;
   String formatNumber(double? value) {
   String formatNumber(double? value) {
@@ -56,6 +60,8 @@ class _CorteCajaFormState extends State<CorteCajaForm> {
           viewModel.selectCorte(corte);
           viewModel.selectCorte(corte);
           fechaApertura = viewModel.selectedCorte?.fechaApertura;
           fechaApertura = viewModel.selectedCorte?.fechaApertura;
 
 
+          fondoInicial = viewModel.selectedCorte?.fondo ?? 0.0;
+
           viewModel.fondoController.text =
           viewModel.fondoController.text =
               viewModel.selectedCorte?.fondo != null
               viewModel.selectedCorte?.fondo != null
                   ? formatNumber(viewModel.selectedCorte!.fondo!)
                   ? formatNumber(viewModel.selectedCorte!.fondo!)
@@ -76,6 +82,33 @@ class _CorteCajaFormState extends State<CorteCajaForm> {
     }
     }
   }
   }
 
 
+  void _validateFondo(CorteCajaViewModel viewModel) {
+    if (_validationTimer != null) {
+      _validationTimer!.cancel();
+    }
+
+    _validationTimer = Timer(Duration(seconds: 2), () {
+      final fondoActual = double.tryParse(
+            viewModel.fondoController.text.replaceAll(',', ''),
+          ) ??
+          0.0;
+
+      if (fondoActual < fondoInicial) {
+        setState(() {
+          isFondoValid = false;
+        });
+
+        alerta(context,
+            etiqueta: "El fondo no puede ser menor al que ya se tenía.");
+        viewModel.fondoController.text = formatNumber(fondoInicial);
+      } else {
+        setState(() {
+          isFondoValid = true;
+        });
+      }
+    });
+  }
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final viewModel = Provider.of<CorteCajaViewModel>(context);
     final viewModel = Provider.of<CorteCajaViewModel>(context);
@@ -148,6 +181,7 @@ class _CorteCajaFormState extends State<CorteCajaForm> {
                         keyboardType: TextInputType.number,
                         keyboardType: TextInputType.number,
                         separarMiles: true,
                         separarMiles: true,
                         onChanged: (value) {
                         onChanged: (value) {
+                          _validateFondo(viewModel);
                           viewModel.calcularCorteFinal();
                           viewModel.calcularCorteFinal();
                         },
                         },
                       ),
                       ),
@@ -167,7 +201,16 @@ class _CorteCajaFormState extends State<CorteCajaForm> {
                     ),
                     ),
                   ],
                   ],
                 ),
                 ),
-                SizedBox(height: 20),
+                SizedBox(height: 10),
+                if (!isFondoValid)
+                  Text(
+                    "El fondo no puede ser menor a $fondoInicial",
+                    style: TextStyle(
+                        color: Colors.red,
+                        fontSize: 16,
+                        fontWeight: FontWeight.bold),
+                  ),
+                SizedBox(height: 10),
                 Row(
                 Row(
                   children: [
                   children: [
                     Expanded(
                     Expanded(
@@ -667,15 +710,27 @@ class _CorteCajaFormState extends State<CorteCajaForm> {
                             _descripcionDepositoController.text;
                             _descripcionDepositoController.text;
                         String persona = _personaDepositoController.text;
                         String persona = _personaDepositoController.text;
 
 
-                        viewModel.addDeposito(
-                          monto,
-                          descripcion,
-                          persona,
-                          widget.corteCajaId!,
+                        showDialog(
+                          context: context,
+                          builder: (context) {
+                            return TotpCuadroConfirmacion(
+                              title: "Agregar Depósito",
+                              content:
+                                  "Por favor, ingresa el código de autenticación para continuar.",
+                              onSuccess: () {
+                                viewModel.addDeposito(
+                                  monto,
+                                  descripcion,
+                                  persona,
+                                  widget.corteCajaId!,
+                                );
+                                _montoDepositoController.clear();
+                                _descripcionDepositoController.clear();
+                                _personaDepositoController.clear();
+                              },
+                            );
+                          },
                         );
                         );
-                        _montoDepositoController.clear();
-                        _descripcionDepositoController.clear();
-                        _personaDepositoController.clear();
                       },
                       },
                       child: Text(
                       child: Text(
                         "Agregar",
                         "Agregar",
@@ -822,15 +877,27 @@ class _CorteCajaFormState extends State<CorteCajaForm> {
                         String descripcion = _descripcionRetiroController.text;
                         String descripcion = _descripcionRetiroController.text;
                         String persona = _personaRetiroController.text;
                         String persona = _personaRetiroController.text;
 
 
-                        viewModel.addRetiro(
-                          monto,
-                          descripcion,
-                          persona,
-                          widget.corteCajaId!,
+                        showDialog(
+                          context: context,
+                          builder: (context) {
+                            return TotpCuadroConfirmacion(
+                              title: "Agregar Retiro",
+                              content:
+                                  "Por favor, ingresa el código de autenticación para continuar.",
+                              onSuccess: () {
+                                viewModel.addRetiro(
+                                  monto,
+                                  descripcion,
+                                  persona,
+                                  widget.corteCajaId!,
+                                );
+                                _montoRetiroController.clear();
+                                _descripcionRetiroController.clear();
+                                _personaRetiroController.clear();
+                              },
+                            );
+                          },
                         );
                         );
-                        _montoRetiroController.clear();
-                        _descripcionRetiroController.clear();
-                        _personaRetiroController.clear();
                       },
                       },
                       child: Text(
                       child: Text(
                         "Agregar",
                         "Agregar",
@@ -977,15 +1044,27 @@ class _CorteCajaFormState extends State<CorteCajaForm> {
                         String descripcion = _descripcionGastoController.text;
                         String descripcion = _descripcionGastoController.text;
                         String persona = _personaGastoController.text;
                         String persona = _personaGastoController.text;
 
 
-                        viewModel.addGasto(
-                          monto,
-                          descripcion,
-                          persona,
-                          widget.corteCajaId!,
+                        showDialog(
+                          context: context,
+                          builder: (context) {
+                            return TotpCuadroConfirmacion(
+                              title: "Agregar Gasto",
+                              content:
+                                  "Por favor, ingresa el código de autenticación para continuar.",
+                              onSuccess: () {
+                                viewModel.addGasto(
+                                  monto,
+                                  descripcion,
+                                  persona,
+                                  widget.corteCajaId!,
+                                );
+                                _montoGastoController.clear();
+                                _descripcionGastoController.clear();
+                                _personaGastoController.clear();
+                              },
+                            );
+                          },
                         );
                         );
-                        _montoGastoController.clear();
-                        _descripcionGastoController.clear();
-                        _personaGastoController.clear();
                       },
                       },
                       child: Text(
                       child: Text(
                         "Agregar",
                         "Agregar",

+ 4 - 3
lib/views/home/home_screen.dart

@@ -21,8 +21,8 @@ class Formulario extends State<HomeScreen> {
     // Future(() async {
     // Future(() async {
     //   await Provider.of<LoginViewModel>(context, listen: false).setValores();
     //   await Provider.of<LoginViewModel>(context, listen: false).setValores();
     // });
     // });
-    PedidoSync()
-        .startSync(Provider.of<PedidoViewModel>(context, listen: false));
+    PedidoSync().startSync(Provider.of<PedidoViewModel>(context, listen: false),
+        Provider.of<CorteCajaViewModel>(context, listen: false));
 
 
     WidgetsBinding.instance.addPostFrameCallback((_) {
     WidgetsBinding.instance.addPostFrameCallback((_) {
       Provider.of<ProductoViewModel>(context, listen: false)
       Provider.of<ProductoViewModel>(context, listen: false)
@@ -37,7 +37,8 @@ class Formulario extends State<HomeScreen> {
       }
       }
 
 
       Provider.of<CorteCajaViewModel>(context, listen: false).fetchCortes();
       Provider.of<CorteCajaViewModel>(context, listen: false).fetchCortes();
-      Provider.of<MesaViewModel>(context, listen: false).fetchLocalAll();
+      Provider.of<MesaViewModel>(context, listen: false)
+          .fetchLocalAll(sinLimite: true);
     });
     });
   }
   }
 
 

+ 60 - 77
lib/views/mesa/mesa_screen.dart

@@ -22,14 +22,28 @@ class _MesasScreenState extends State<MesasScreen> {
     Provider.of<MesaViewModel>(context, listen: false).fetchLocalAll();
     Provider.of<MesaViewModel>(context, listen: false).fetchLocalAll();
   }
   }
 
 
-  void go(Mesa variable) {
-    Navigator.push(
-      context,
-      MaterialPageRoute(
-        builder: (context) => MesaForm(mesa: variable),
-      ),
-    ).then((_) =>
-        Provider.of<MesaViewModel>(context, listen: false).fetchLocalAll());
+  void go(Mesa mesa) {
+    showDialog(
+      context: context,
+      builder: (context) {
+        return TotpCuadroConfirmacion(
+          title: "Editar Mesa",
+          content:
+              "Por favor, ingresa el código de autenticación para continuar.",
+          onSuccess: () {
+            Navigator.push(
+              context,
+              MaterialPageRoute(
+                builder: (context) => MesaForm(mesa: mesa),
+              ),
+            ).then((_) {
+              Provider.of<MesaViewModel>(context, listen: false)
+                  .fetchLocalAll();
+            });
+          },
+        );
+      },
+    );
   }
   }
 
 
   void clearSearchAndReset() {
   void clearSearchAndReset() {
@@ -61,68 +75,24 @@ class _MesasScreenState extends State<MesasScreen> {
               PopupMenuItem(
               PopupMenuItem(
                 child: const Text('Eliminar'),
                 child: const Text('Eliminar'),
                 onTap: () async {
                 onTap: () async {
-                  await Future.delayed(Duration(milliseconds: 100));
-                  bool confirmado = await showDialog<bool>(
-                        context: context,
-                        builder: (context) {
-                          return AlertDialog(
-                            title: const Text("Eliminar",
-                                style: TextStyle(
-                                    fontWeight: FontWeight.w500, fontSize: 22)),
-                            content: const Text(
-                                '¿Estás seguro de que deseas eliminar esta variable?',
-                                style: TextStyle(fontSize: 18)),
-                            actions: [
-                              Row(
-                                mainAxisAlignment:
-                                    MainAxisAlignment.spaceBetween,
-                                children: [
-                                  TextButton(
-                                    onPressed: () =>
-                                        Navigator.of(context).pop(false),
-                                    child: const Text('No',
-                                        style: TextStyle(fontSize: 18)),
-                                    style: ButtonStyle(
-                                        padding: MaterialStatePropertyAll(
-                                            EdgeInsets.fromLTRB(
-                                                20, 10, 20, 10)),
-                                        backgroundColor:
-                                            MaterialStatePropertyAll(
-                                                AppTheme.primary),
-                                        foregroundColor:
-                                            MaterialStatePropertyAll(
-                                                AppTheme.secondary)),
-                                  ),
-                                  TextButton(
-                                    onPressed: () =>
-                                        Navigator.of(context).pop(true),
-                                    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)),
-                                  ),
-                                ],
-                              )
-                            ],
-                          );
-                        },
-                      ) ??
-                      false;
-
-                  if (confirmado) {
-                    await model.deleteMesa(item.id!);
-                    model.fetchLocalAll();
-                  }
+                  Future.delayed(Duration.zero, () {
+                    showDialog(
+                      context: context,
+                      builder: (context) {
+                        return TotpCuadroConfirmacion(
+                          title: "Eliminar Mesa",
+                          content:
+                              "Por favor, ingresa el código de autenticación para continuar.",
+                          onSuccess: () async {
+                            await model.deleteMesa(item.id!);
+                            model.fetchLocalAll();
+                          },
+                        );
+                      },
+                    );
+                  });
                 },
                 },
-              )
+              ),
             ],
             ],
             icon: const Icon(Icons.more_vert),
             icon: const Icon(Icons.more_vert),
           ),
           ),
@@ -311,14 +281,27 @@ class _MesasScreenState extends State<MesasScreen> {
       ),
       ),
       floatingActionButton: FloatingActionButton.extended(
       floatingActionButton: FloatingActionButton.extended(
         onPressed: () async {
         onPressed: () async {
-          Mesa nuevaMesa = Mesa();
-          Navigator.push(
-            context,
-            MaterialPageRoute(
-              builder: (context) => MesaForm(mesa: nuevaMesa),
-            ),
-          ).then((_) => Provider.of<MesaViewModel>(context, listen: false)
-              .fetchLocalAll());
+          showDialog(
+            context: context,
+            builder: (context) {
+              return TotpCuadroConfirmacion(
+                title: "Agregar Mesa",
+                content:
+                    "Por favor, ingresa el código de autenticación para continuar.",
+                onSuccess: () {
+                  Mesa nuevaMesa = Mesa();
+                  Navigator.push(
+                    context,
+                    MaterialPageRoute(
+                      builder: (context) => MesaForm(mesa: nuevaMesa),
+                    ),
+                  ).then((_) =>
+                      Provider.of<MesaViewModel>(context, listen: false)
+                          .fetchLocalAll());
+                },
+              );
+            },
+          );
         },
         },
         icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
         icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
         label: Text(
         label: Text(

+ 541 - 38
lib/views/pedido/pedido_detalle_screen.dart

@@ -1,19 +1,39 @@
+import 'package:collection/collection.dart';
 import 'package:intl/intl.dart';
 import 'package:intl/intl.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
 import '../../models/models.dart';
 import '../../models/models.dart';
+import '../../services/services.dart';
 import '../../themes/themes.dart';
 import '../../themes/themes.dart';
+import '../../viewmodels/viewmodels.dart';
+import '../../widgets/widgets.dart';
 import '../pedido/pedido_ticket.dart';
 import '../pedido/pedido_ticket.dart';
 
 
-class PedidoDetalleScreen extends StatelessWidget {
+class PedidoDetalleScreen extends StatefulWidget {
   final Pedido pedido;
   final Pedido pedido;
 
 
-  const PedidoDetalleScreen({Key? key, required this.pedido}) : super(key: key);
+  PedidoDetalleScreen({Key? key, required this.pedido}) : super(key: key);
+
+  @override
+  _PedidoDetalleScreenState createState() => _PedidoDetalleScreenState();
+}
+
+class _PedidoDetalleScreenState extends State<PedidoDetalleScreen> {
+  late Pedido pedido;
+
+  @override
+  void initState() {
+    super.initState();
+    pedido = widget.pedido;
+  }
 
 
   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);
   }
   }
 
 
+  final List<String> tiposDePago = ['Efectivo', 'Tarjeta', 'Transferencia'];
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     double totalSinDescuento =
     double totalSinDescuento =
@@ -145,6 +165,12 @@ class PedidoDetalleScreen extends StatelessWidget {
                                   ),
                                   ),
                                 ],
                                 ],
                               ),
                               ),
+                              if (producto.comentario!.isNotEmpty)
+                                Text(
+                                  'Comentarios: ${producto.comentario!}',
+                                  style: TextStyle(
+                                      fontSize: 15, color: AppTheme.tertiary),
+                                ),
                               if (producto.toppings.isNotEmpty)
                               if (producto.toppings.isNotEmpty)
                                 Padding(
                                 Padding(
                                   padding: const EdgeInsets.only(top: 4.0),
                                   padding: const EdgeInsets.only(top: 4.0),
@@ -245,15 +271,59 @@ class PedidoDetalleScreen extends StatelessWidget {
               color: Colors.white,
               color: Colors.white,
               child: Padding(
               child: Padding(
                 padding: const EdgeInsets.all(8.0),
                 padding: const EdgeInsets.all(8.0),
-                child: Column(
-                  crossAxisAlignment: CrossAxisAlignment.start,
-                  children: [
-                    Text('Pago',
-                        style: TextStyle(
-                            fontSize: 22, fontWeight: FontWeight.bold)),
-                    const SizedBox(height: 10),
-                    _buildPaymentDetails(),
-                  ],
+                child: Consumer<PedidoViewModel>(
+                  builder: (context, viewModel, _) {
+                    return Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        Row(
+                          children: [
+                            Text('Pago',
+                                style: TextStyle(
+                                    fontSize: 22, fontWeight: FontWeight.bold)),
+                            Spacer(),
+                            ElevatedButton(
+                              onPressed: () {
+                                showDialog(
+                                  context: context,
+                                  builder: (context) {
+                                    return TotpCuadroConfirmacion(
+                                      title: "Cambiar Método de Pago",
+                                      content:
+                                          "Por favor, ingresa el código de autenticación para continuar.",
+                                      onSuccess: () {
+                                        _mostrarModalCambiarMetodoPago(context);
+                                      },
+                                    );
+                                  },
+                                );
+                              },
+                              child: Text('Cambiar Método Pago',
+                                  style: TextStyle(
+                                      color: AppTheme.quaternary,
+                                      fontWeight: FontWeight.w500,
+                                      fontSize: 16)),
+                              style: ElevatedButton.styleFrom(
+                                backgroundColor: AppTheme.tertiary,
+                                padding:
+                                    const EdgeInsets.fromLTRB(20, 10, 20, 10),
+                              ),
+                            ),
+                          ],
+                        ),
+                        const SizedBox(height: 10),
+                        if ((pedido.cantEfectivo ?? 0) > 0)
+                          _buildReadOnlyPaymentRow(
+                              "Efectivo", pedido.cantEfectivo ?? 0.0),
+                        if ((pedido.cantTarjeta ?? 0) > 0)
+                          _buildReadOnlyPaymentRow(
+                              "Tarjeta", pedido.cantTarjeta ?? 0.0),
+                        if ((pedido.cantTransferencia ?? 0) > 0)
+                          _buildReadOnlyPaymentRow(
+                              "Transferencia", pedido.cantTransferencia ?? 0.0),
+                      ],
+                    );
+                  },
                 ),
                 ),
               ),
               ),
             ),
             ),
@@ -286,41 +356,409 @@ class PedidoDetalleScreen extends StatelessWidget {
     );
     );
   }
   }
 
 
-  Widget _buildPaymentDetails() {
-    List<Widget> paymentDetails = [];
+  Widget _buildReadOnlyPaymentRow(String paymentType, double amount) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(vertical: 6.0),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          Text(paymentType,
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
+          Text('\$${formatCurrency(amount)}',
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
+        ],
+      ),
+    );
+  }
 
 
-    if (pedido.cantEfectivo != null && pedido.cantEfectivo! > 0) {
-      paymentDetails.add(_buildPaymentRow("Efectivo", pedido.cantEfectivo!));
-    }
-    if (pedido.cantTarjeta != null && pedido.cantTarjeta! > 0) {
-      paymentDetails.add(_buildPaymentRow("Tarjeta", pedido.cantTarjeta!));
-    }
-    if (pedido.cantTransferencia != null && pedido.cantTransferencia! > 0) {
-      paymentDetails
-          .add(_buildPaymentRow("Transferencia", pedido.cantTransferencia!));
-    }
-    if (paymentDetails.isEmpty) {
-      paymentDetails.add(Text("No se especificaron métodos de pago.",
-          style: TextStyle(fontSize: 16, color: Colors.grey[600])));
+  Future<void> _mostrarModalCambiarMetodoPago(BuildContext context) async {
+    double totalPedido = pedido.totalPedido ?? 0.0;
+    TextEditingController efectivoController = TextEditingController(
+        text: (pedido.cantEfectivo ?? 0) > 0
+            ? pedido.cantEfectivo!.toStringAsFixed(2)
+            : '');
+    TextEditingController tarjetaController = TextEditingController(
+        text: (pedido.cantTarjeta ?? 0) > 0
+            ? pedido.cantTarjeta!.toStringAsFixed(2)
+            : '');
+    TextEditingController transferenciaController = TextEditingController(
+        text: (pedido.cantTransferencia ?? 0) > 0
+            ? pedido.cantTransferencia!.toStringAsFixed(2)
+            : '');
+
+    bool efectivoSeleccionado = (pedido.cantEfectivo ?? 0) > 0;
+    bool tarjetaSeleccionada = (pedido.cantTarjeta ?? 0) > 0;
+    bool transferenciaSeleccionada = (pedido.cantTransferencia ?? 0) > 0;
+
+    bool efectivoCompleto = false;
+    bool tarjetaCompleto = false;
+    bool transferenciaCompleto = false;
+
+    double cambio = 0.0;
+    double faltante = totalPedido;
+    bool totalCompletado = false;
+
+    void _calcularCambio(StateSetter setState) {
+      double totalPagado = (double.tryParse(efectivoController.text) ?? 0) +
+          (double.tryParse(tarjetaController.text) ?? 0) +
+          (double.tryParse(transferenciaController.text) ?? 0);
+
+      setState(() {
+        cambio = totalPagado - totalPedido;
+        faltante = cambio < 0 ? totalPedido - totalPagado : 0;
+        totalCompletado = cambio >= 0;
+      });
     }
     }
 
 
-    return Column(
-      crossAxisAlignment: CrossAxisAlignment.start,
-      children: paymentDetails,
+    bool? shouldSave = await showDialog<bool>(
+      context: context,
+      builder: (BuildContext context) {
+        return StatefulBuilder(
+          builder: (context, setState) {
+            return AlertDialog(
+              actionsPadding: EdgeInsets.fromLTRB(50, 10, 50, 30),
+              title: const Text(
+                'Cambiar Método de Pago',
+                style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
+              ),
+              content: SingleChildScrollView(
+                child: AnimatedSize(
+                  duration: const Duration(milliseconds: 300),
+                  curve: Curves.easeInOut,
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.stretch,
+                    children: [
+                      Align(
+                        alignment: Alignment.center,
+                        child: Text(
+                          'Métodos de pago',
+                          style: TextStyle(
+                              fontWeight: FontWeight.bold, fontSize: 20),
+                        ),
+                      ),
+                      const SizedBox(height: 10),
+                      _buildPaymentMethodRow(
+                        setState,
+                        totalPedido,
+                        label: 'Efectivo',
+                        selected: efectivoSeleccionado,
+                        exactSelected: efectivoCompleto,
+                        controller: efectivoController,
+                        onSelected: (value) {
+                          setState(() {
+                            efectivoSeleccionado = value;
+                            if (!efectivoSeleccionado) {
+                              efectivoCompleto = false;
+                              efectivoController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        onExactSelected: (value) {
+                          setState(() {
+                            efectivoCompleto = value;
+                            if (efectivoCompleto) {
+                              efectivoController.text =
+                                  totalPedido.toStringAsFixed(2);
+                              efectivoSeleccionado = true;
+                              tarjetaSeleccionada = false;
+                              transferenciaSeleccionada = false;
+                              tarjetaController.clear();
+                              transferenciaController.clear();
+                            } else {
+                              efectivoController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        disableOtherMethods:
+                            tarjetaCompleto || transferenciaCompleto,
+                        onChangedMonto: () => _calcularCambio(setState),
+                      ),
+                      _buildPaymentMethodRow(
+                        setState,
+                        totalPedido,
+                        label: 'Tarjeta',
+                        selected: tarjetaSeleccionada,
+                        exactSelected: tarjetaCompleto,
+                        controller: tarjetaController,
+                        sinCambio: true,
+                        onSelected: (value) {
+                          setState(() {
+                            tarjetaSeleccionada = value;
+                            if (!tarjetaSeleccionada) {
+                              tarjetaCompleto = false;
+                              tarjetaController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        onExactSelected: (value) {
+                          setState(() {
+                            tarjetaCompleto = value;
+                            if (tarjetaCompleto) {
+                              tarjetaController.text =
+                                  totalPedido.toStringAsFixed(2);
+                              tarjetaSeleccionada = true;
+                              efectivoSeleccionado = false;
+                              transferenciaSeleccionada = false;
+                              efectivoController.clear();
+                              transferenciaController.clear();
+                            } else {
+                              tarjetaController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        disableOtherMethods:
+                            efectivoCompleto || transferenciaCompleto,
+                        onChangedMonto: () => _calcularCambio(setState),
+                      ),
+                      _buildPaymentMethodRow(
+                        setState,
+                        totalPedido,
+                        label: 'Transferencia',
+                        selected: transferenciaSeleccionada,
+                        exactSelected: transferenciaCompleto,
+                        controller: transferenciaController,
+                        sinCambio: true,
+                        onSelected: (value) {
+                          setState(() {
+                            transferenciaSeleccionada = value;
+                            if (!transferenciaSeleccionada) {
+                              transferenciaCompleto = false;
+                              transferenciaController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        onExactSelected: (value) {
+                          setState(() {
+                            transferenciaCompleto = value;
+                            if (transferenciaCompleto) {
+                              transferenciaController.text =
+                                  totalPedido.toStringAsFixed(2);
+                              transferenciaSeleccionada = true;
+                              efectivoSeleccionado = false;
+                              tarjetaSeleccionada = false;
+                              efectivoController.clear();
+                              tarjetaController.clear();
+                            } else {
+                              transferenciaController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        disableOtherMethods:
+                            efectivoCompleto || tarjetaCompleto,
+                        onChangedMonto: () => _calcularCambio(setState),
+                      ),
+                      const SizedBox(height: 10),
+                      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: [
+                TextButton(
+                  child: const Text('Cancelar', style: TextStyle(fontSize: 18)),
+                  onPressed: () => Navigator.of(context).pop(false),
+                  style: ButtonStyle(
+                    padding: MaterialStateProperty.all(
+                        EdgeInsets.fromLTRB(30, 20, 30, 20)),
+                    backgroundColor: MaterialStateProperty.all(Colors.red),
+                    foregroundColor:
+                        MaterialStateProperty.all(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: MaterialStateProperty.all(
+                        EdgeInsets.fromLTRB(30, 20, 30, 20)),
+                    backgroundColor: MaterialStateProperty.all(
+                        totalCompletado ? AppTheme.tertiary : Colors.grey),
+                    foregroundColor:
+                        MaterialStateProperty.all(AppTheme.quaternary),
+                  ),
+                ),
+              ],
+            );
+          },
+        );
+      },
     );
     );
+
+    if (shouldSave ?? false) {
+      double cantEfectivo = efectivoSeleccionado
+          ? double.tryParse(efectivoController.text) ?? 0
+          : 0;
+      double cantTarjeta = tarjetaSeleccionada
+          ? double.tryParse(tarjetaController.text) ?? 0
+          : 0;
+      double cantTransferencia = transferenciaSeleccionada
+          ? double.tryParse(transferenciaController.text) ?? 0
+          : 0;
+
+      pedido.cantEfectivo = cantEfectivo;
+      pedido.cantTarjeta = cantTarjeta;
+      pedido.cantTransferencia = cantTransferencia;
+
+      List<String> nuevosMetodos = [];
+      if (cantEfectivo > 0) nuevosMetodos.add('Efectivo');
+      if (cantTarjeta > 0) nuevosMetodos.add('Tarjeta');
+      if (cantTransferencia > 0) nuevosMetodos.add('Transferencia');
+
+      pedido.tipoPago =
+          nuevosMetodos.isNotEmpty ? nuevosMetodos.join(',') : 'No Definido';
+
+      pedido.sincronizado = null;
+
+      await _guardarPedido(pedido, context);
+
+      // Recargamos el pedido desde la BD para tener sus datos actualizados
+      PedidoViewModel viewModel =
+          Provider.of<PedidoViewModel>(context, listen: false);
+      Pedido? pedidoActualizado =
+          await viewModel.fetchPedidoConProductos(pedido.id!);
+
+      if (pedidoActualizado != null) {
+        setState(() {
+          pedido = pedidoActualizado;
+        });
+      }
+    }
   }
   }
 
 
-  Widget _buildPaymentRow(String paymentType, double amount) {
+  Widget _buildPaymentMethodRow(
+    StateSetter setState,
+    double totalPedido, {
+    required String label,
+    required bool selected,
+    required bool exactSelected,
+    required TextEditingController controller,
+    required Function(bool) onSelected,
+    required Function(bool) onExactSelected,
+    required bool disableOtherMethods,
+    required Function() onChangedMonto,
+    bool sinCambio = false,
+  }) {
     return Row(
     return Row(
       mainAxisAlignment: MainAxisAlignment.spaceBetween,
       mainAxisAlignment: MainAxisAlignment.spaceBetween,
+      crossAxisAlignment: CrossAxisAlignment.center,
       children: [
       children: [
-        Text(
-          paymentType,
-          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+        Row(
+          children: [
+            Checkbox(
+              activeColor: AppTheme.primary,
+              value: selected,
+              onChanged: disableOtherMethods
+                  ? null
+                  : (value) {
+                      onSelected(value ?? false);
+                    },
+            ),
+            GestureDetector(
+              onTap: disableOtherMethods
+                  ? null
+                  : () {
+                      onSelected(!selected);
+                    },
+              child: Text(
+                label,
+                style:
+                    const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+              ),
+            ),
+          ],
         ),
         ),
-        Text(
-          '\$${formatCurrency(amount)}',
-          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+        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: exactSelected,
+                    onChanged: !disableOtherMethods
+                        ? (value) {
+                            onExactSelected(value ?? false);
+                            if (value == true) {
+                              setState(() {
+                                // nada especial, ya manejamos esto arriba
+                              });
+                            }
+                          }
+                        : null,
+                  ),
+                ],
+              ),
+              const SizedBox(width: 5),
+              Expanded(
+                child: AppTextField(
+                  controller: controller,
+                  enabled: selected,
+                  etiqueta: 'Cantidad',
+                  hintText: '0.00',
+                  keyboardType: TextInputType.number,
+                  onChanged: (value) {
+                    if (sinCambio) {
+                      double? input = double.tryParse(value) ?? 0;
+                      if (input > totalPedido) {
+                        controller.text = totalPedido.toStringAsFixed(2);
+                        controller.selection = TextSelection.fromPosition(
+                          TextPosition(offset: controller.text.length),
+                        );
+                      }
+                    }
+                    onChangedMonto();
+                  },
+                ),
+              ),
+            ],
+          ),
         ),
         ),
       ],
       ],
     );
     );
@@ -328,10 +766,75 @@ class PedidoDetalleScreen extends StatelessWidget {
 
 
   String _formatDateTime(String? dateTimeString) {
   String _formatDateTime(String? dateTimeString) {
     if (dateTimeString == null) return "Sin fecha";
     if (dateTimeString == null) return "Sin fecha";
-
     DateTime parsedDate = DateTime.parse(dateTimeString);
     DateTime parsedDate = DateTime.parse(dateTimeString);
-
     var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
     var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
     return formatter.format(parsedDate.toLocal());
     return formatter.format(parsedDate.toLocal());
   }
   }
+
+  Future<void> _guardarPedido(Pedido pedido, BuildContext buildContext) async {
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+    RepoService<PedidoProducto> repoPedidoProducto =
+        RepoService<PedidoProducto>();
+    RepoService<PedidoProductoTopping> repoPedidoProductoTopping =
+        RepoService<PedidoProductoTopping>();
+
+    try {
+      if (pedido.id != null && pedido.id! > 0) {
+        await repoPedido.guardar(pedido);
+      } else {
+        pedido.id = await repoPedido.guardarLocal(pedido);
+      }
+
+      List<PedidoProducto> productosExistentes =
+          await repoPedidoProducto.obtenerPorIdPedido(pedido.id!);
+
+      for (var productoExistente in productosExistentes) {
+        bool sigueExistiendo = pedido.productos.any(
+            (producto) => producto.idProducto == productoExistente.idProducto);
+
+        if (!sigueExistiendo) {
+          productoExistente.eliminado = DateTime.now().toUtc();
+          await repoPedidoProducto.guardar(productoExistente);
+        }
+      }
+
+      for (var producto in pedido.productos) {
+        PedidoProducto pedidoProducto = PedidoProducto(
+          idPedido: pedido.id,
+          idProducto: producto.idProducto,
+          cantidad: producto.cantidad,
+          costoUnitario: producto.costoUnitario,
+          comentario: producto.comentario,
+        );
+
+        PedidoProducto? productoExistente = productosExistentes
+            .firstWhereOrNull((p) => p.idProducto == pedidoProducto.idProducto);
+
+        if (productoExistente != null) {
+          pedidoProducto.id = productoExistente.id;
+          await repoPedidoProducto.guardar(pedidoProducto);
+        } else {
+          int idPedidoProducto =
+              await repoPedidoProducto.guardarLocal(pedidoProducto);
+          for (var topping in producto.toppings) {
+            PedidoProductoTopping pedidoProductoTopping = PedidoProductoTopping(
+              idPedidoProducto: idPedidoProducto,
+              idTopping: topping.idTopping,
+              idCategoria: topping.idCategoria,
+            );
+            await repoPedidoProductoTopping.guardarLocal(pedidoProductoTopping);
+          }
+        }
+      }
+
+      ScaffoldMessenger.of(buildContext).showSnackBar(
+        const SnackBar(content: Text("Pedido actualizado correctamente.")),
+      );
+    } catch (e) {
+      print("Error al guardar el pedido: $e");
+      ScaffoldMessenger.of(buildContext).showSnackBar(
+        const SnackBar(content: Text("Error al actualizar el pedido.")),
+      );
+    }
+  }
 }
 }

+ 455 - 250
lib/views/pedido/pedido_form.dart

@@ -13,6 +13,7 @@ import '../../models/models.dart';
 import '../../viewmodels/viewmodels.dart';
 import '../../viewmodels/viewmodels.dart';
 import 'package:collection/collection.dart';
 import 'package:collection/collection.dart';
 import '../../widgets/widgets.dart';
 import '../../widgets/widgets.dart';
+import 'package:uuid/uuid.dart';
 import '../../services/services.dart';
 import '../../services/services.dart';
 import 'package:timezone/data/latest.dart' as timezone;
 import 'package:timezone/data/latest.dart' as timezone;
 import 'package:timezone/timezone.dart' as timezone;
 import 'package:timezone/timezone.dart' as timezone;
@@ -55,6 +56,8 @@ class _PedidoFormState extends State<PedidoForm> {
   TextEditingController efectivoController = TextEditingController();
   TextEditingController efectivoController = TextEditingController();
   TextEditingController tarjetaController = TextEditingController();
   TextEditingController tarjetaController = TextEditingController();
   TextEditingController transferenciaController = TextEditingController();
   TextEditingController transferenciaController = TextEditingController();
+  TextEditingController _propinaCantidadController = TextEditingController();
+  TextEditingController _propinaComentarioController = TextEditingController();
   double cambio = 0.0;
   double cambio = 0.0;
   double faltante = 0.0;
   double faltante = 0.0;
   bool totalCompletado = false;
   bool totalCompletado = false;
@@ -131,6 +134,7 @@ class _PedidoFormState extends State<PedidoForm> {
           return ItemCarrito(
           return ItemCarrito(
             producto: producto.producto!,
             producto: producto.producto!,
             cantidad: producto.cantidad!,
             cantidad: producto.cantidad!,
+            comentario: producto.comentario,
             selectedToppings: producto.toppings.fold<Map<int, Set<int>>>(
             selectedToppings: producto.toppings.fold<Map<int, Set<int>>>(
               {},
               {},
               (acc, topping) {
               (acc, topping) {
@@ -143,6 +147,8 @@ class _PedidoFormState extends State<PedidoForm> {
         }).toList();
         }).toList();
       });
       });
 
 
+      selectedDescuento = pedidoCompleto.descuento ?? 0;
+
       _recalcularTotal();
       _recalcularTotal();
     }
     }
   }
   }
@@ -295,11 +301,14 @@ class _PedidoFormState extends State<PedidoForm> {
       _mostrarDialogoPedidoVacio();
       _mostrarDialogoPedidoVacio();
       return;
       return;
     }
     }
+
     TextEditingController nombreController = TextEditingController();
     TextEditingController nombreController = TextEditingController();
     TextEditingController descripcionController = TextEditingController();
     TextEditingController descripcionController = TextEditingController();
+    TextEditingController mesaSearchController = TextEditingController();
 
 
-    int? idMesaSeleccionada;
+    Mesa? mesaSeleccionada;
 
 
+    // Inicializa las mesas disponibles
     await Provider.of<MesaViewModel>(context, listen: false).fetchLocalAll();
     await Provider.of<MesaViewModel>(context, listen: false).fetchLocalAll();
     List<Mesa> mesasDisponibles =
     List<Mesa> mesasDisponibles =
         Provider.of<MesaViewModel>(context, listen: false).mesas;
         Provider.of<MesaViewModel>(context, listen: false).mesas;
@@ -327,21 +336,21 @@ class _PedidoFormState extends State<PedidoForm> {
                 hintText: 'Descripción del Pedido',
                 hintText: 'Descripción del Pedido',
               ),
               ),
               const SizedBox(height: 10),
               const SizedBox(height: 10),
-              DropdownButtonFormField<int>(
-                hint: const Text('Seleccionar Mesa'),
-                items: mesasDisponibles
-                    .map((mesa) => DropdownMenuItem<int>(
-                          value: mesa.id,
-                          child: Text(mesa.nombre ?? 'Sin Nombre'),
-                        ))
-                    .toList(),
-                onChanged: (value) {
-                  idMesaSeleccionada = value;
+              AppDropdownSearch(
+                controller: mesaSearchController,
+                etiqueta: 'Seleccionar Mesa',
+                asyncItems: (String query) async {
+                  await Provider.of<MesaViewModel>(context, listen: false)
+                      .fetchLocalByName(nombre: query);
+                  return Provider.of<MesaViewModel>(context, listen: false)
+                      .mesas;
+                },
+                itemAsString: (dynamic mesa) =>
+                    (mesa as Mesa).nombre ?? 'Sin Nombre',
+                selectedItem: mesaSeleccionada,
+                onChanged: (dynamic nuevaMesa) {
+                  mesaSeleccionada = nuevaMesa as Mesa;
                 },
                 },
-                decoration: const InputDecoration(
-                  labelText: 'Mesa',
-                  border: OutlineInputBorder(),
-                ),
               ),
               ),
             ],
             ],
           ),
           ),
@@ -350,18 +359,17 @@ class _PedidoFormState extends State<PedidoForm> {
               onPressed: () => Navigator.of(context).pop(false),
               onPressed: () => Navigator.of(context).pop(false),
               child: const Text('Cancelar'),
               child: const Text('Cancelar'),
               style: ButtonStyle(
               style: ButtonStyle(
-                  padding: MaterialStatePropertyAll(
-                      EdgeInsets.fromLTRB(20, 10, 20, 10)),
-                  backgroundColor: MaterialStatePropertyAll(Colors.red),
-                  foregroundColor:
-                      MaterialStatePropertyAll(AppTheme.secondary)),
-            ),
-            const SizedBox(
-              width: 10,
+                padding: MaterialStatePropertyAll(
+                  EdgeInsets.fromLTRB(20, 10, 20, 10),
+                ),
+                backgroundColor: MaterialStatePropertyAll(Colors.red),
+                foregroundColor: MaterialStatePropertyAll(AppTheme.secondary),
+              ),
             ),
             ),
+            const SizedBox(width: 10),
             TextButton(
             TextButton(
               onPressed: () {
               onPressed: () {
-                if (idMesaSeleccionada != null) {
+                if (mesaSeleccionada != null) {
                   Navigator.of(context).pop(true);
                   Navigator.of(context).pop(true);
                 } else {
                 } else {
                   ScaffoldMessenger.of(context).showSnackBar(
                   ScaffoldMessenger.of(context).showSnackBar(
@@ -373,11 +381,12 @@ class _PedidoFormState extends State<PedidoForm> {
               },
               },
               child: const Text('Guardar'),
               child: const Text('Guardar'),
               style: ButtonStyle(
               style: ButtonStyle(
-                  padding: MaterialStatePropertyAll(
-                      EdgeInsets.fromLTRB(20, 10, 20, 10)),
-                  backgroundColor: MaterialStatePropertyAll(Colors.black),
-                  foregroundColor:
-                      MaterialStatePropertyAll(AppTheme.quaternary)),
+                padding: MaterialStatePropertyAll(
+                  EdgeInsets.fromLTRB(20, 10, 20, 10),
+                ),
+                backgroundColor: MaterialStatePropertyAll(Colors.black),
+                foregroundColor: MaterialStatePropertyAll(AppTheme.quaternary),
+              ),
             ),
             ),
           ],
           ],
         );
         );
@@ -390,9 +399,10 @@ class _PedidoFormState extends State<PedidoForm> {
         nombreCliente: nombreController.text,
         nombreCliente: nombreController.text,
         comentarios: descripcionController.text,
         comentarios: descripcionController.text,
         estatus: 'NUEVO',
         estatus: 'NUEVO',
-        idMesa: idMesaSeleccionada,
+        idMesa: mesaSeleccionada?.id,
         totalPedido: totalPedido,
         totalPedido: totalPedido,
         descuento: selectedDescuento,
         descuento: selectedDescuento,
+        uuid: Uuid().v4(),
         idUsuario: await SessionStorage().getId(),
         idUsuario: await SessionStorage().getId(),
         productos: carrito.map((item) {
         productos: carrito.map((item) {
           return PedidoProducto(
           return PedidoProducto(
@@ -400,6 +410,7 @@ class _PedidoFormState extends State<PedidoForm> {
             producto: item.producto,
             producto: item.producto,
             costoUnitario: item.producto.precio.toString(),
             costoUnitario: item.producto.precio.toString(),
             cantidad: item.cantidad,
             cantidad: item.cantidad,
+            comentario: item.comentario,
             toppings: item.selectedToppings.entries.expand((entry) {
             toppings: item.selectedToppings.entries.expand((entry) {
               return entry.value.map((toppingId) {
               return entry.value.map((toppingId) {
                 return PedidoProductoTopping(
                 return PedidoProductoTopping(
@@ -482,6 +493,7 @@ class _PedidoFormState extends State<PedidoForm> {
     bool efectivoCompleto = false;
     bool efectivoCompleto = false;
     bool tarjetaCompleto = false;
     bool tarjetaCompleto = false;
     bool transferenciaCompleto = false;
     bool transferenciaCompleto = false;
+    bool propinaExpandida = false;
 
 
     void _calcularCambio(StateSetter setState) {
     void _calcularCambio(StateSetter setState) {
       double totalPagado = (double.tryParse(efectivoController.text) ?? 0) +
       double totalPagado = (double.tryParse(efectivoController.text) ?? 0) +
@@ -506,189 +518,216 @@ class _PedidoFormState extends State<PedidoForm> {
                 'Finalizar Pedido',
                 'Finalizar Pedido',
                 style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
                 style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
               ),
               ),
-              content: Container(
-                height:
-                    pedidoActual == null || pedidoActual!.id == 0 ? 600 : 400,
-                child: Column(
-                  children: [
-                    Expanded(
-                      child: SingleChildScrollView(
-                        child: Column(
-                          children: [
-                            if (nombreController != null)
-                              AppTextField(
-                                controller: nombreController,
-                                etiqueta: 'Nombre',
-                                hintText: "Nombre del Cliente",
-                              ),
-                            if (comentarioController != null) ...[
-                              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
-                            _buildPaymentMethodRow(
-                              setState,
-                              label: 'Efectivo',
-                              selected: efectivoSeleccionado,
-                              exactSelected: efectivoCompleto,
-                              controller: efectivoController,
-                              onSelected: (value) {
-                                setState(() {
-                                  efectivoSeleccionado = value;
-                                  if (!efectivoSeleccionado) {
-                                    efectivoCompleto = false;
-                                    efectivoController.clear();
-                                  }
-                                  _calcularCambio(setState);
-                                });
-                              },
-                              onExactSelected: (value) {
-                                setState(() {
-                                  efectivoCompleto = value;
-                                  if (efectivoCompleto) {
-                                    efectivoController.text =
-                                        totalPedido.toStringAsFixed(2);
-                                    efectivoSeleccionado = true;
-                                    tarjetaSeleccionada = false;
-                                    transferenciaSeleccionada = false;
-                                    tarjetaController.clear();
-                                    transferenciaController.clear();
-                                  } else {
-                                    efectivoController.clear();
-                                  }
-                                  _calcularCambio(setState);
-                                });
-                              },
-                              disableOtherMethods:
-                                  tarjetaCompleto || transferenciaCompleto,
-                              onChangedMonto: () => _calcularCambio(setState),
-                            ),
-                            // Tarjeta
-                            _buildPaymentMethodRow(
-                              setState,
-                              label: 'Tarjeta',
-                              selected: tarjetaSeleccionada,
-                              exactSelected: tarjetaCompleto,
-                              controller: tarjetaController,
-                              onSelected: (value) {
-                                setState(() {
-                                  tarjetaSeleccionada = value;
-                                  if (!tarjetaSeleccionada) {
-                                    tarjetaCompleto = false;
-                                    tarjetaController.clear();
-                                  }
-                                  _calcularCambio(setState);
-                                });
-                              },
-                              onExactSelected: (value) {
-                                setState(() {
-                                  tarjetaCompleto = value;
-                                  if (tarjetaCompleto) {
-                                    tarjetaController.text =
-                                        totalPedido.toStringAsFixed(2);
-                                    tarjetaSeleccionada = true;
-                                    efectivoSeleccionado = false;
-                                    transferenciaSeleccionada = false;
-                                    efectivoController.clear();
-                                    transferenciaController.clear();
-                                  } else {
-                                    tarjetaController.clear();
-                                  }
-                                  _calcularCambio(setState);
-                                });
-                              },
-                              disableOtherMethods:
-                                  efectivoCompleto || transferenciaCompleto,
-                              onChangedMonto: () => _calcularCambio(setState),
-                            ),
-                            // Transferencia
-                            _buildPaymentMethodRow(
-                              setState,
-                              label: 'Transferencia',
-                              selected: transferenciaSeleccionada,
-                              exactSelected: transferenciaCompleto,
-                              controller: transferenciaController,
-                              onSelected: (value) {
-                                setState(() {
-                                  transferenciaSeleccionada = value;
-                                  if (!transferenciaSeleccionada) {
-                                    transferenciaCompleto = false;
-                                    transferenciaController.clear();
-                                  }
-                                  _calcularCambio(setState);
-                                });
-                              },
-                              onExactSelected: (value) {
-                                setState(() {
-                                  transferenciaCompleto = value;
-                                  if (transferenciaCompleto) {
-                                    transferenciaController.text =
-                                        totalPedido.toStringAsFixed(2);
-                                    transferenciaSeleccionada = true;
-                                    efectivoSeleccionado = false;
-                                    tarjetaSeleccionada = false;
-                                    efectivoController.clear();
-                                    tarjetaController.clear();
-                                  } else {
-                                    transferenciaController.clear();
-                                  }
-                                  _calcularCambio(setState);
-                                });
-                              },
-                              disableOtherMethods:
-                                  efectivoCompleto || tarjetaCompleto,
-                              onChangedMonto: () => _calcularCambio(setState),
-                            ),
-                          ],
+              content: SingleChildScrollView(
+                child: AnimatedSize(
+                  duration: const Duration(milliseconds: 300),
+                  curve: Curves.easeInOut,
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.stretch,
+                    children: [
+                      if (nombreController != null)
+                        AppTextField(
+                          controller: nombreController,
+                          etiqueta: 'Nombre',
+                          hintText: "Nombre del Cliente",
+                        ),
+                      if (comentarioController != null) ...[
+                        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),
                         ),
                         ),
                       ),
                       ),
-                    ),
-                    // Total y Faltante
-                    Align(
-                      alignment: Alignment.centerRight,
-                      child: Column(
-                        crossAxisAlignment: CrossAxisAlignment.end,
+                      const SizedBox(height: 10),
+                      // Efectivo
+                      _buildPaymentMethodRow(
+                        setState,
+                        label: 'Efectivo',
+                        selected: efectivoSeleccionado,
+                        exactSelected: efectivoCompleto,
+                        controller: efectivoController,
+                        onSelected: (value) {
+                          setState(() {
+                            efectivoSeleccionado = value;
+                            if (!efectivoSeleccionado) {
+                              efectivoCompleto = false;
+                              efectivoController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        onExactSelected: (value) {
+                          setState(() {
+                            efectivoCompleto = value;
+                            if (efectivoCompleto) {
+                              efectivoController.text =
+                                  totalPedido.toStringAsFixed(2);
+                              efectivoSeleccionado = true;
+                              tarjetaSeleccionada = false;
+                              transferenciaSeleccionada = false;
+                              tarjetaController.clear();
+                              transferenciaController.clear();
+                            } else {
+                              efectivoController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        disableOtherMethods:
+                            tarjetaCompleto || transferenciaCompleto,
+                        onChangedMonto: () => _calcularCambio(setState),
+                      ),
+                      // Tarjeta
+                      _buildPaymentMethodRow(
+                        setState,
+                        label: 'Tarjeta',
+                        selected: tarjetaSeleccionada,
+                        exactSelected: tarjetaCompleto,
+                        controller: tarjetaController,
+                        sinCambio: true,
+                        onSelected: (value) {
+                          setState(() {
+                            tarjetaSeleccionada = value;
+                            if (!tarjetaSeleccionada) {
+                              tarjetaCompleto = false;
+                              tarjetaController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        onExactSelected: (value) {
+                          setState(() {
+                            tarjetaCompleto = value;
+                            if (tarjetaCompleto) {
+                              tarjetaController.text =
+                                  totalPedido.toStringAsFixed(2);
+                              tarjetaSeleccionada = true;
+                              efectivoSeleccionado = false;
+                              transferenciaSeleccionada = false;
+                              efectivoController.clear();
+                              transferenciaController.clear();
+                            } else {
+                              tarjetaController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        disableOtherMethods:
+                            efectivoCompleto || transferenciaCompleto,
+                        onChangedMonto: () => _calcularCambio(setState),
+                      ),
+                      // Transferencia
+                      _buildPaymentMethodRow(
+                        setState,
+                        label: 'Transferencia',
+                        selected: transferenciaSeleccionada,
+                        exactSelected: transferenciaCompleto,
+                        controller: transferenciaController,
+                        sinCambio: true,
+                        onSelected: (value) {
+                          setState(() {
+                            transferenciaSeleccionada = value;
+                            if (!transferenciaSeleccionada) {
+                              transferenciaCompleto = false;
+                              transferenciaController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        onExactSelected: (value) {
+                          setState(() {
+                            transferenciaCompleto = value;
+                            if (transferenciaCompleto) {
+                              transferenciaController.text =
+                                  totalPedido.toStringAsFixed(2);
+                              transferenciaSeleccionada = true;
+                              efectivoSeleccionado = false;
+                              tarjetaSeleccionada = false;
+                              efectivoController.clear();
+                              tarjetaController.clear();
+                            } else {
+                              transferenciaController.clear();
+                            }
+                            _calcularCambio(setState);
+                          });
+                        },
+                        disableOtherMethods:
+                            efectivoCompleto || tarjetaCompleto,
+                        onChangedMonto: () => _calcularCambio(setState),
+                      ),
+                      // Propina Expandable
+                      ExpansionTile(
+                        title: const Text(
+                          'Agregar Propina',
+                          style: TextStyle(
+                              fontSize: 18, fontWeight: FontWeight.bold),
+                        ),
+                        initiallyExpanded: propinaExpandida,
+                        onExpansionChanged: (isExpanded) {
+                          setState(() {
+                            propinaExpandida = isExpanded;
+                          });
+                        },
                         children: [
                         children: [
-                          Text(
-                            'Total del pedido: \$${totalPedido.toStringAsFixed(2)}',
-                            style: const TextStyle(
-                                fontWeight: FontWeight.bold, fontSize: 18),
+                          AppTextField(
+                            separarMiles: true,
+                            controller: _propinaCantidadController,
+                            etiqueta: 'Propina',
+                            hintText: '0.00',
+                            keyboardType: TextInputType.number,
                           ),
                           ),
-                          if (faltante > 0)
-                            Text(
-                              'Faltante: \$${faltante.toStringAsFixed(2)}',
-                              style: const TextStyle(
-                                  color: Colors.red,
-                                  fontSize: 18,
-                                  fontWeight: FontWeight.bold),
-                            )
-                          else if (cambio > 0)
+                          const SizedBox(height: 10),
+                          AppTextField(
+                            controller: _propinaComentarioController,
+                            etiqueta: 'Comentario',
+                            hintText: 'Comentario',
+                            maxLines: 2,
+                          ),
+                        ],
+                      ),
+                      const SizedBox(height: 10),
+                      Align(
+                        alignment: Alignment.centerRight,
+                        child: Column(
+                          crossAxisAlignment: CrossAxisAlignment.end,
+                          children: [
                             Text(
                             Text(
-                              'Cambio: \$${cambio.toStringAsFixed(2)}',
+                              'Total del pedido: \$${totalPedido.toStringAsFixed(2)}',
                               style: const TextStyle(
                               style: const TextStyle(
-                                  color: Colors.green,
-                                  fontSize: 18,
-                                  fontWeight: FontWeight.bold),
+                                  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: [
               actions: [
@@ -734,6 +773,8 @@ class _PedidoFormState extends State<PedidoForm> {
           tipoPago: _obtenerTipoPago(),
           tipoPago: _obtenerTipoPago(),
           estatus: "TERMINADO",
           estatus: "TERMINADO",
         );
         );
+
+        await _guardarPropina(pedidoActual!.id!);
       } else {
       } else {
         prepararPedidoActual(
         prepararPedidoActual(
           nombreController?.text ?? '',
           nombreController?.text ?? '',
@@ -746,6 +787,29 @@ class _PedidoFormState extends State<PedidoForm> {
     }
     }
   }
   }
 
 
+  Future<void> _guardarPropina(int idPedido) async {
+    double? cantidad =
+        double.tryParse(_propinaCantidadController.text.replaceAll(',', '')) ??
+            0.0;
+    String comentario = _propinaComentarioController.text.trim();
+
+    if (cantidad != null && cantidad > 0) {
+      Propinas nuevaPropina = Propinas(
+        idPedido: idPedido,
+        cantidad: cantidad,
+        comentario: comentario,
+        sincronizado: null,
+        creado: DateTime.now().toUtc(),
+      );
+
+      await Provider.of<PropinaViewModel>(context, listen: false)
+          .guardarPropina(nuevaPropina);
+      print('Propina guardada correctamente');
+    } else {
+      print('Propina no guardada (cantidad inválida)');
+    }
+  }
+
   Widget _buildPaymentMethodRow(
   Widget _buildPaymentMethodRow(
     StateSetter setState, {
     StateSetter setState, {
     required String label,
     required String label,
@@ -756,6 +820,7 @@ class _PedidoFormState extends State<PedidoForm> {
     required Function(bool) onExactSelected,
     required Function(bool) onExactSelected,
     required bool disableOtherMethods,
     required bool disableOtherMethods,
     required Function() onChangedMonto,
     required Function() onChangedMonto,
+    bool sinCambio = false,
   }) {
   }) {
     return Row(
     return Row(
       mainAxisAlignment: MainAxisAlignment.spaceBetween,
       mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -825,7 +890,16 @@ class _PedidoFormState extends State<PedidoForm> {
                   etiqueta: 'Cantidad',
                   etiqueta: 'Cantidad',
                   hintText: '0.00',
                   hintText: '0.00',
                   keyboardType: TextInputType.number,
                   keyboardType: TextInputType.number,
-                  onChanged: (_) {
+                  onChanged: (value) {
+                    if (sinCambio) {
+                      double? input = double.tryParse(value) ?? 0;
+                      if (input > totalPedido) {
+                        controller.text = totalPedido.toStringAsFixed(2);
+                        controller.selection = TextSelection.fromPosition(
+                          TextPosition(offset: controller.text.length),
+                        );
+                      }
+                    }
                     onChangedMonto();
                     onChangedMonto();
                   },
                   },
                 ),
                 ),
@@ -868,6 +942,7 @@ class _PedidoFormState extends State<PedidoForm> {
       cantEfectivo: cantEfectivo,
       cantEfectivo: cantEfectivo,
       cantTarjeta: cantTarjeta,
       cantTarjeta: cantTarjeta,
       cantTransferencia: cantTransferencia,
       cantTransferencia: cantTransferencia,
+      uuid: Uuid().v4(),
     );
     );
 
 
     List<PedidoProducto> listaPedidoProducto = carrito.map((item) {
     List<PedidoProducto> listaPedidoProducto = carrito.map((item) {
@@ -887,7 +962,7 @@ class _PedidoFormState extends State<PedidoForm> {
         producto: item.producto,
         producto: item.producto,
         costoUnitario: item.producto.precio.toString(),
         costoUnitario: item.producto.precio.toString(),
         cantidad: item.cantidad,
         cantidad: item.cantidad,
-        comentario: comentarios,
+        comentario: item.comentario,
         toppings: selectedToppings,
         toppings: selectedToppings,
       );
       );
     }).toList();
     }).toList();
@@ -1081,11 +1156,52 @@ class _PedidoFormState extends State<PedidoForm> {
                       )
                       )
                       .toList(),
                       .toList(),
                   selectedValue: selectedDescuento,
                   selectedValue: selectedDescuento,
-                  onChanged: (value) {
-                    setState(() {
-                      selectedDescuento = value;
-                      _recalcularTotal();
-                    });
+                  onChanged: (value) async {
+                    if (value != null && value != selectedDescuento) {
+                      // Guardar el valor anterior
+                      final previousValue = selectedDescuento;
+
+                      // Actualizar el descuento temporalmente
+                      print('Descuento temporal seleccionado: $value');
+                      setState(() {
+                        selectedDescuento = value;
+                      });
+
+                      // Mostrar cuadro de confirmación
+                      final authenticated = await showDialog<bool>(
+                        context: context,
+                        builder: (context) {
+                          return TotpCuadroConfirmacion(
+                            title: "Aplicar Descuento",
+                            content:
+                                "Por favor, ingresa el código de autenticación para continuar.",
+                            onSuccess: () {
+                              // Confirmación exitosa: recalcular total y actualizar UI
+                              print(
+                                  'Autenticación exitosa. Aplicando descuento...');
+                              setState(() {
+                                _recalcularTotal();
+                                print('Descuento aplicado: $selectedDescuento');
+                                print('Total recalculado: $totalPedido');
+                              });
+                            },
+                          );
+                        },
+                      );
+
+                      // Si la autenticación falla, revertir el descuento
+                      if (authenticated != true) {
+                        print(
+                            'Autenticación fallida. Revirtiendo descuento...');
+                        setState(() {
+                          selectedDescuento = previousValue;
+                          _recalcularTotal(); // Recalcular con el valor anterior
+                          print('Descuento revertido: $selectedDescuento');
+                          print(
+                              'Total recalculado tras revertir: $totalPedido');
+                        });
+                      }
+                    }
                   },
                   },
                 );
                 );
               },
               },
@@ -1197,34 +1313,73 @@ class _PedidoFormState extends State<PedidoForm> {
 
 
                 return Column(
                 return Column(
                   children: [
                   children: [
-                    ListTile(
-                      title: Text(item.producto.nombre!,
-                          style: const TextStyle(fontWeight: FontWeight.w600)),
-                      subtitle: Text('\$${item.producto.precio}',
-                          style: const TextStyle(
-                              fontWeight: FontWeight.bold,
-                              color: Color(0xFF008000))),
-                      trailing: Row(
-                        mainAxisSize: MainAxisSize.min,
-                        children: [
-                          IconButton(
+                    Row(
+                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                      children: [
+                        Expanded(
+                          child: Column(
+                            crossAxisAlignment: CrossAxisAlignment.start,
+                            children: [
+                              Text(item.producto.nombre!,
+                                  style: const TextStyle(
+                                      fontWeight: FontWeight.w600,
+                                      fontSize: 16)),
+                              Text('\$${item.producto.precio}',
+                                  style: const TextStyle(
+                                      fontWeight: FontWeight.bold,
+                                      fontSize: 14,
+                                      color: Color(0xFF008000))),
+                            ],
+                          ),
+                        ),
+                        Row(
+                          children: [
+                            IconButton(
                               icon: const Icon(Icons.delete, color: Colors.red),
                               icon: const Icon(Icons.delete, color: Colors.red),
                               onPressed: () =>
                               onPressed: () =>
-                                  eliminarProductoDelCarrito(index)),
-                          IconButton(
+                                  eliminarProductoDelCarrito(index),
+                            ),
+                            IconButton(
                               icon: const Icon(Icons.remove),
                               icon: const Icon(Icons.remove),
-                              onPressed: () => quitarProductoDelCarrito(item)),
-                          const SizedBox(width: 5),
-                          Text('${item.cantidad}',
-                              style: const TextStyle(
-                                  fontWeight: FontWeight.bold, fontSize: 14)),
-                          const SizedBox(width: 5),
-                          IconButton(
+                              onPressed: () => quitarProductoDelCarrito(item),
+                            ),
+                            Text('${item.cantidad}',
+                                style: const TextStyle(
+                                    fontWeight: FontWeight.bold, fontSize: 14)),
+                            IconButton(
                               icon: const Icon(Icons.add),
                               icon: const Icon(Icons.add),
-                              onPressed: () => incrementarProducto(item)),
-                        ],
-                      ),
+                              onPressed: () => incrementarProducto(item),
+                            ),
+                            IconButton(
+                              icon:
+                                  Icon(Icons.message, color: AppTheme.tertiary),
+                              onPressed: () {
+                                setState(() {
+                                  item.expandido = !item.expandido;
+                                });
+                              },
+                            ),
+                          ],
+                        ),
+                      ],
                     ),
                     ),
+                    if (item.expandido) ...[
+                      const SizedBox(height: 5),
+                      Padding(
+                        padding: const EdgeInsets.symmetric(horizontal: 16.0),
+                        child: TextField(
+                          controller: item.comentarioController,
+                          decoration: const InputDecoration(
+                            hintText: 'Agregar un comentario...',
+                            border: OutlineInputBorder(),
+                          ),
+                          maxLines: 2,
+                          onChanged: (value) {
+                            item.comentario = value.trim();
+                          },
+                        ),
+                      ),
+                    ],
                     // ExpansionTile para todos los toppings
                     // ExpansionTile para todos los toppings
                     if (item.selectableToppings.isNotEmpty)
                     if (item.selectableToppings.isNotEmpty)
                       ExpansionTile(
                       ExpansionTile(
@@ -1390,6 +1545,50 @@ class _PedidoFormState extends State<PedidoForm> {
     );
     );
   }
   }
 
 
+  void _mostrarDialogoComentario(
+      BuildContext context, ItemCarrito item, int index) {
+    TextEditingController comentarioController =
+        TextEditingController(text: carrito[index].comentario ?? '');
+
+    showDialog(
+      context: context,
+      builder: (BuildContext context) {
+        return AlertDialog(
+          title: const Text(
+            'Comentario del Producto',
+            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
+          ),
+          content: TextField(
+            controller: comentarioController,
+            maxLines: 3,
+            decoration: const InputDecoration(
+              hintText: 'Escribe un comentario...',
+              border: OutlineInputBorder(),
+            ),
+          ),
+          actions: [
+            TextButton(
+              onPressed: () => Navigator.of(context).pop(),
+              child: const Text('Cancelar'),
+            ),
+            TextButton(
+              onPressed: () {
+                setState(() {
+                  carrito[index].comentario = comentarioController.text.trim();
+                });
+                Navigator.of(context).pop();
+                ScaffoldMessenger.of(context).showSnackBar(
+                  const SnackBar(content: Text('Comentario guardado')),
+                );
+              },
+              child: const Text('Guardar'),
+            ),
+          ],
+        );
+      },
+    );
+  }
+
   void eliminarProductoDelCarrito(int index) async {
   void eliminarProductoDelCarrito(int index) async {
     bool autorizado = true;
     bool autorizado = true;
 
 
@@ -1729,8 +1928,8 @@ class _PedidoFormState extends State<PedidoForm> {
 
 
   Widget _buildMesaSelector() {
   Widget _buildMesaSelector() {
     return FutureBuilder(
     return FutureBuilder(
-      future:
-          Provider.of<MesaViewModel>(context, listen: false).fetchLocalAll(),
+      future: Provider.of<MesaViewModel>(context, listen: false)
+          .fetchLocalAll(sinLimite: true),
       builder: (context, snapshot) {
       builder: (context, snapshot) {
         if (snapshot.connectionState == ConnectionState.waiting) {
         if (snapshot.connectionState == ConnectionState.waiting) {
           return const Center(child: CircularProgressIndicator());
           return const Center(child: CircularProgressIndicator());
@@ -1739,6 +1938,8 @@ class _PedidoFormState extends State<PedidoForm> {
         List<Mesa> mesasDisponibles =
         List<Mesa> mesasDisponibles =
             Provider.of<MesaViewModel>(context, listen: false).mesas;
             Provider.of<MesaViewModel>(context, listen: false).mesas;
 
 
+        TextEditingController mesaSearchController = TextEditingController();
+
         return Padding(
         return Padding(
           padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
           padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
           child: Row(
           child: Row(
@@ -1750,22 +1951,26 @@ class _PedidoFormState extends State<PedidoForm> {
               ),
               ),
               const SizedBox(width: 16),
               const SizedBox(width: 16),
               Expanded(
               Expanded(
-                child: DropdownButtonFormField<int>(
-                  value: pedidoActual?.idMesa,
-                  items: mesasDisponibles.map((mesa) {
-                    return DropdownMenuItem<int>(
-                      value: mesa.id,
-                      child: Text(mesa.nombre ?? 'Sin Nombre'),
-                    );
-                  }).toList(),
-                  onChanged: (int? nuevaMesaId) {
+                child: AppDropdownSearch(
+                  controller: mesaSearchController,
+                  asyncItems: (String query) async {
+                    await Provider.of<MesaViewModel>(context, listen: false)
+                        .fetchLocalByName(nombre: query);
+                    return Provider.of<MesaViewModel>(context, listen: false)
+                        .mesas;
+                  },
+                  itemAsString: (dynamic mesa) =>
+                      (mesa as Mesa).nombre ?? 'Sin Nombre',
+                  selectedItem: mesasDisponibles.firstWhere(
+                    (mesa) => mesa.id == pedidoActual?.idMesa,
+                    orElse: () => null as Mesa,
+                  ),
+                  onChanged: (dynamic nuevaMesa) {
                     setState(() {
                     setState(() {
-                      pedidoActual?.idMesa = nuevaMesaId;
+                      pedidoActual?.idMesa = (nuevaMesa as Mesa).id;
                     });
                     });
                   },
                   },
-                  decoration: const InputDecoration(
-                    border: OutlineInputBorder(),
-                  ),
+                  items: mesasDisponibles,
                 ),
                 ),
               ),
               ),
             ],
             ],

+ 38 - 3
lib/views/pedido/pedido_screen.dart

@@ -1,3 +1,5 @@
+import 'dart:typed_data';
+
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:intl/intl.dart';
 import 'package:intl/intl.dart';
 import 'package:omni_datetime_picker/omni_datetime_picker.dart';
 import 'package:omni_datetime_picker/omni_datetime_picker.dart';
@@ -11,9 +13,12 @@ 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 'package:otp/otp.dart';
 import 'package:otp/otp.dart';
+import 'package:pdf/widgets.dart' as pw;
 import 'package:timezone/data/latest.dart' as timezone;
 import 'package:timezone/data/latest.dart' as timezone;
 import 'package:timezone/timezone.dart' as timezone;
 import 'package:timezone/timezone.dart' as timezone;
 
 
+import 'pedido_ticket.dart';
+
 class PedidoScreen extends StatefulWidget {
 class PedidoScreen extends StatefulWidget {
   const PedidoScreen({Key? key}) : super(key: key);
   const PedidoScreen({Key? key}) : super(key: key);
 
 
@@ -35,6 +40,8 @@ class _PedidoScreenState extends State<PedidoScreen> {
     WidgetsBinding.instance.addPostFrameCallback((_) {
     WidgetsBinding.instance.addPostFrameCallback((_) {
       Provider.of<PedidoViewModel>(context, listen: false)
       Provider.of<PedidoViewModel>(context, listen: false)
           .fetchLocalPedidosForScreen();
           .fetchLocalPedidosForScreen();
+      Provider.of<MesaViewModel>(context, listen: false)
+          .fetchLocalAll(sinLimite: true);
     });
     });
 
 
     Future.microtask(() async {
     Future.microtask(() async {
@@ -88,7 +95,7 @@ class _PedidoScreenState extends State<PedidoScreen> {
     });
     });
   }
   }
 
 
-  void go(Pedido item) async {
+  void go(Pedido item, {bool? detalle = false}) async {
     Pedido? pedidoCompleto =
     Pedido? pedidoCompleto =
         await Provider.of<PedidoViewModel>(context, listen: false)
         await Provider.of<PedidoViewModel>(context, listen: false)
             .fetchPedidoConProductos(item.id);
             .fetchPedidoConProductos(item.id);
@@ -96,7 +103,8 @@ class _PedidoScreenState extends State<PedidoScreen> {
     if (pedidoCompleto != null) {
     if (pedidoCompleto != null) {
       if (pedidoCompleto.estatus == 'TERMINADO' ||
       if (pedidoCompleto.estatus == 'TERMINADO' ||
           pedidoCompleto.estatus == 'CANCELADO' ||
           pedidoCompleto.estatus == 'CANCELADO' ||
-          !_isMesasActive) {
+          !_isMesasActive ||
+          detalle!) {
         Navigator.push(
         Navigator.push(
           context,
           context,
           MaterialPageRoute(
           MaterialPageRoute(
@@ -147,7 +155,13 @@ class _PedidoScreenState extends State<PedidoScreen> {
             itemBuilder: (context) => [
             itemBuilder: (context) => [
               PopupMenuItem(
               PopupMenuItem(
                 child: const Text('Detalle'),
                 child: const Text('Detalle'),
-                onTap: () => go(item),
+                onTap: () => go(item, detalle: true),
+              ),
+              PopupMenuItem(
+                child: const Text('Estado Ticket'),
+                onTap: () async {
+                  await imprimirEstadoTicket(context, item);
+                },
               ),
               ),
               if (userPermisos.contains(Usuario.CANCELAR_PEDIDO))
               if (userPermisos.contains(Usuario.CANCELAR_PEDIDO))
                 PopupMenuItem(
                 PopupMenuItem(
@@ -702,4 +716,25 @@ class _PedidoScreenState extends State<PedidoScreen> {
     var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
     var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
     return formatter.format(parsedDate.toLocal());
     return formatter.format(parsedDate.toLocal());
   }
   }
+
+  Future<void> imprimirEstadoTicket(BuildContext context, Pedido pedido) async {
+    if (pedido.productos == null || pedido.productos.isEmpty) {
+      pedido = (await Provider.of<PedidoViewModel>(context, listen: false)
+          .fetchPedidoConProductos(pedido.id))!;
+
+      if (pedido == null) {
+        ScaffoldMessenger.of(context).showSnackBar(
+          SnackBar(content: Text('No se pudo cargar el pedido')),
+        );
+        return;
+      }
+    }
+
+    final pdf = pw.Document();
+
+    pdf.addPage(generarPaginaSegundoTicket(pedido));
+
+    final pdfBytes = Uint8List.fromList(await pdf.save());
+    await printPdf(pdfBytes);
+  }
 }
 }

+ 12 - 4
lib/views/pedido/pedido_sync.dart

@@ -13,15 +13,23 @@ class PedidoSync {
 
 
   PedidoSync._internal();
   PedidoSync._internal();
 
 
-  void startSync(PedidoViewModel pedidoViewModel) {
+  void startSync(
+      PedidoViewModel pedidoViewModel, CorteCajaViewModel corteCajaViewModel) {
     if (_syncTimer != null && _syncTimer!.isActive) return;
     if (_syncTimer != null && _syncTimer!.isActive) return;
 
 
     _syncTimer = Timer.periodic(Duration(seconds: 5), (timer) async {
     _syncTimer = Timer.periodic(Duration(seconds: 5), (timer) async {
-      bool hasMoreToSync = await pedidoViewModel.sincronizarPedidos();
-      // if (!hasMoreToSync) {
+      // Primero sincronizamos pedidos
+      bool hasMorePedidosToSync = await pedidoViewModel.sincronizarPedidos();
+
+      // Luego sincronizamos corte de caja
+      bool hasMoreCortesToSync =
+          await corteCajaViewModel.sincronizarCorteCajas();
+
+      // Opcionalmente, podrías detener el timer si ya no hay nada más que sincronizar.
+      // if (!hasMorePedidosToSync && !hasMoreCortesToSync) {
       //   timer.cancel();
       //   timer.cancel();
       //   _syncTimer = null;
       //   _syncTimer = null;
-      //   print('Sincronización completa, no hay más pedidos por sincronizar.');
+      //   print('Sincronización completa, no hay más datos por sincronizar.');
       // }
       // }
     });
     });
   }
   }

+ 27 - 18
lib/views/pedido/pedido_ticket.dart

@@ -201,8 +201,6 @@ pw.Page generarPaginaSegundoTicket(Pedido pedido) {
       pageFormat: PdfPageFormat.roll57,
       pageFormat: PdfPageFormat.roll57,
       build: (pw.Context context) {
       build: (pw.Context context) {
         List<pw.Widget> content = [
         List<pw.Widget> content = [
-          pw.SizedBox(height: 20),
-          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: ${pedido.peticion}',
@@ -233,23 +231,34 @@ pw.Page generarPaginaSegundoTicket(Pedido pedido) {
               }).toList();
               }).toList();
 
 
               return [
               return [
-                pw.Row(
-                  mainAxisAlignment: pw.MainAxisAlignment.start,
-                  children: [
-                    pw.Expanded(
-                      flex: 3,
-                      child: pw.Text(
-                          producto.producto?.nombre ??
-                              "Producto no especificado",
-                          style: const pw.TextStyle(fontSize: 9)),
-                    ),
-                    pw.Expanded(
-                      flex: 1,
-                      child: pw.Text('x${producto.cantidad}',
-                          style: const pw.TextStyle(fontSize: 9)),
+                pw.Column(children: [
+                  pw.Row(
+                    mainAxisAlignment: pw.MainAxisAlignment.start,
+                    children: [
+                      pw.Expanded(
+                        flex: 3,
+                        child: pw.Text(
+                            producto.producto?.nombre ??
+                                "Producto no especificado",
+                            style: const pw.TextStyle(fontSize: 9)),
+                      ),
+                      pw.Expanded(
+                        flex: 1,
+                        child: pw.Text('x${producto.cantidad}',
+                            style: const pw.TextStyle(fontSize: 9)),
+                      ),
+                    ],
+                  ),
+                  if (producto.comentario!.isNotEmpty)
+                    pw.Row(
+                      mainAxisAlignment: pw.MainAxisAlignment.start,
+                      children: [
+                        pw.Text('- ${producto.comentario}' ?? "",
+                            style: const pw.TextStyle(fontSize: 8)),
+                      ],
                     ),
                     ),
-                  ],
-                ),
+                  pw.SizedBox(height: 5),
+                ]),
                 ...toppingsList,
                 ...toppingsList,
               ];
               ];
             })
             })

+ 64 - 77
lib/views/producto/producto_screen.dart

@@ -28,13 +28,27 @@ class _ProductoScreenState extends State<ProductoScreen> {
   }
   }
 
 
   void go(Producto producto) {
   void go(Producto producto) {
-    Navigator.push(
-      context,
-      MaterialPageRoute(
-        builder: (context) => ProductoForm(producto: producto),
-      ),
-    ).then((_) =>
-        Provider.of<ProductoViewModel>(context, listen: false).fetchLocalAll());
+    showDialog(
+      context: context,
+      builder: (context) {
+        return TotpCuadroConfirmacion(
+          title: "Editar Producto",
+          content:
+              "Por favor, ingresa el código de autenticación para continuar.",
+          onSuccess: () {
+            Navigator.push(
+              context,
+              MaterialPageRoute(
+                builder: (context) => ProductoForm(producto: producto),
+              ),
+            ).then((_) {
+              Provider.of<ProductoViewModel>(context, listen: false)
+                  .fetchLocalAll();
+            });
+          },
+        );
+      },
+    );
   }
   }
 
 
   void clearSearchAndReset() {
   void clearSearchAndReset() {
@@ -105,69 +119,28 @@ class _ProductoScreenState extends State<ProductoScreen> {
               PopupMenuItem(
               PopupMenuItem(
                 child: const Text('Eliminar'),
                 child: const Text('Eliminar'),
                 onTap: () async {
                 onTap: () async {
-                  bool confirmado = await showDialog<bool>(
-                        context: context,
-                        builder: (context) {
-                          return AlertDialog(
-                            title: const Text("Eliminar",
-                                style: TextStyle(
-                                    fontWeight: FontWeight.w500, fontSize: 22)),
-                            content: const Text(
-                                '¿Estás seguro de que deseas eliminar este producto?',
-                                style: TextStyle(fontSize: 18)),
-                            actions: [
-                              Row(
-                                mainAxisAlignment:
-                                    MainAxisAlignment.spaceBetween,
-                                children: [
-                                  TextButton(
-                                    onPressed: () =>
-                                        Navigator.of(context).pop(false),
-                                    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)),
-                                  ),
-                                  TextButton(
-                                    onPressed: () =>
-                                        Navigator.of(context).pop(true),
-                                    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)),
-                                  ),
-                                ],
-                              )
-                            ],
-                          );
-                        },
-                      ) ??
-                      false;
-
-                  if (confirmado) {
-                    await Provider.of<ProductoViewModel>(context, listen: false)
-                        .deleteProducto(item.id);
-                    Provider.of<ProductoViewModel>(context, listen: false)
-                        .fetchLocalAll();
-                  }
+                  Future.delayed(Duration.zero, () {
+                    showDialog(
+                      context: context,
+                      builder: (context) {
+                        return TotpCuadroConfirmacion(
+                          title: "Eliminar Producto",
+                          content:
+                              "Por favor, ingresa el código de autenticación para continuar.",
+                          onSuccess: () async {
+                            await Provider.of<ProductoViewModel>(context,
+                                    listen: false)
+                                .deleteProducto(item.id);
+                            Provider.of<ProductoViewModel>(context,
+                                    listen: false)
+                                .fetchLocalAll();
+                          },
+                        );
+                      },
+                    );
+                  });
                 },
                 },
-              )
+              ),
             ],
             ],
             icon: const Icon(Icons.more_vert),
             icon: const Icon(Icons.more_vert),
           ),
           ),
@@ -396,14 +369,28 @@ class _ProductoScreenState extends State<ProductoScreen> {
       ),
       ),
       floatingActionButton: FloatingActionButton.extended(
       floatingActionButton: FloatingActionButton.extended(
         onPressed: () async {
         onPressed: () async {
-          Producto nuevoProducto = Producto();
-          Navigator.push(
-            context,
-            MaterialPageRoute(
-              builder: (context) => ProductoForm(producto: nuevoProducto),
-            ),
-          ).then((_) => Provider.of<ProductoViewModel>(context, listen: false)
-              .fetchLocalAll());
+          showDialog(
+            context: context,
+            builder: (context) {
+              return TotpCuadroConfirmacion(
+                title: "Agregar Producto",
+                content:
+                    "Por favor, ingresa el código de autenticación para continuar.",
+                onSuccess: () {
+                  Producto nuevoProducto = Producto();
+                  Navigator.push(
+                    context,
+                    MaterialPageRoute(
+                      builder: (context) =>
+                          ProductoForm(producto: nuevoProducto),
+                    ),
+                  ).then((_) =>
+                      Provider.of<ProductoViewModel>(context, listen: false)
+                          .fetchLocalAll());
+                },
+              );
+            },
+          );
         },
         },
         icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
         icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
         label: Text(
         label: Text(

+ 1 - 1
lib/widgets/app_drawer.dart

@@ -342,7 +342,7 @@ class AppDrawer extends StatelessWidget {
             child: Align(
             child: Align(
               alignment: Alignment.bottomCenter,
               alignment: Alignment.bottomCenter,
               child: Text(
               child: Text(
-                '$prefijoVersion.1.24.11.30+2',
+                '$prefijoVersion.1.24.12.12',
                 style: const TextStyle(fontWeight: FontWeight.w300),
                 style: const TextStyle(fontWeight: FontWeight.w300),
               ),
               ),
             ),
             ),

+ 5 - 9
lib/widgets/app_dropdown_modelo.dart

@@ -30,13 +30,12 @@ class AppDropdownModel<T> extends StatelessWidget {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     double fontSize = _getFontSize(context);
     double fontSize = _getFontSize(context);
-    TextStyle dropdownTextStyle = TextStyle(fontSize: fontSize);
+    TextStyle dropdownTextStyle =
+        TextStyle(fontSize: fontSize, color: Colors.black);
     return Column(
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
       children: [
-        if (etiqueta != null &&
-            etiqueta!
-                .isNotEmpty) // Verifica si la etiqueta no es nula y no está vacía
+        if (etiqueta != null && etiqueta!.isNotEmpty)
           Text(
           Text(
             etiqueta!,
             etiqueta!,
             style: TextStyle(
             style: TextStyle(
@@ -44,8 +43,7 @@ class AppDropdownModel<T> extends StatelessWidget {
               fontWeight: FontWeight.bold,
               fontWeight: FontWeight.bold,
             ),
             ),
           ),
           ),
-        if (etiqueta != null &&
-            etiqueta!.isNotEmpty) // Solo muestra el espacio si hay una etiqueta
+        if (etiqueta != null && etiqueta!.isNotEmpty)
           const SizedBox(
           const SizedBox(
             height: 5,
             height: 5,
           ),
           ),
@@ -53,9 +51,7 @@ class AppDropdownModel<T> extends StatelessWidget {
           decoration: BoxDecoration(
           decoration: BoxDecoration(
               color: Colors.white, borderRadius: BorderRadius.circular(15)),
               color: Colors.white, borderRadius: BorderRadius.circular(15)),
           child: DropdownButtonFormField(
           child: DropdownButtonFormField(
-            hint: Text(hint ?? '',
-                style:
-                    dropdownTextStyle), // Usa un hint vacío si no se proporciona
+            hint: Text(hint ?? '', style: dropdownTextStyle),
             style: dropdownTextStyle,
             style: dropdownTextStyle,
             borderRadius: BorderRadius.circular(10),
             borderRadius: BorderRadius.circular(10),
             icon: Icon(
             icon: Icon(

+ 101 - 0
lib/widgets/widgets_components.dart

@@ -21,6 +21,8 @@ import '../themes/themes.dart';
 
 
 import 'package:timezone/timezone.dart' as tz;
 import 'package:timezone/timezone.dart' as tz;
 import 'package:http/http.dart' as http;
 import 'package:http/http.dart' as http;
+import 'package:otp/otp.dart';
+import 'package:timezone/data/latest.dart' as timezone;
 
 
 import "package:universal_html/html.dart" as html;
 import "package:universal_html/html.dart" as html;
 
 
@@ -815,3 +817,102 @@ String formatoMiles(double? value) {
   final formatter = NumberFormat('#,##0.00', 'en_US');
   final formatter = NumberFormat('#,##0.00', 'en_US');
   return formatter.format(value);
   return formatter.format(value);
 }
 }
+
+class TotpCuadroConfirmacion extends StatefulWidget {
+  final String title;
+  final String content;
+  final VoidCallback onSuccess;
+
+  // Valores estáticos para el secreto TOTP y los códigos estáticos
+  static const String _totpSecret = 'TYSNE4CMT5LVLGWS';
+  static const List<String> _staticCodes = ['172449', '827329'];
+
+  const TotpCuadroConfirmacion({
+    Key? key,
+    required this.title,
+    required this.content,
+    required this.onSuccess,
+  }) : super(key: key);
+
+  @override
+  State<TotpCuadroConfirmacion> createState() => _TotpCuadroConfirmacionState();
+}
+
+class _TotpCuadroConfirmacionState extends State<TotpCuadroConfirmacion> {
+  final TextEditingController _codeController = TextEditingController();
+
+  void _validateCode() {
+    final now = DateTime.now().toUtc();
+    timezone.initializeTimeZones();
+    final pacificTimeZone = tz.getLocation('America/Los_Angeles');
+    final date = tz.TZDateTime.from(now, pacificTimeZone);
+    final generatedCode = OTP.generateTOTPCodeString(
+      TotpCuadroConfirmacion._totpSecret,
+      date.millisecondsSinceEpoch,
+      algorithm: Algorithm.SHA1,
+      isGoogle: true,
+    );
+
+    final enteredCode = _codeController.text;
+    final isStaticCode =
+        TotpCuadroConfirmacion._staticCodes.contains(enteredCode);
+
+    if (!isStaticCode && enteredCode != generatedCode) {
+      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
+        content: Text('El código no es correcto'),
+        duration: Duration(seconds: 2),
+      ));
+    } else {
+      Navigator.of(context).pop(true);
+      widget.onSuccess();
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return AlertDialog(
+      title: Text(widget.title,
+          style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22)),
+      content: Column(
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          Text(widget.content, style: const TextStyle(fontSize: 18)),
+          const SizedBox(height: 16),
+          TextField(
+            controller: _codeController,
+            decoration: const InputDecoration(
+              label: Text("Para continuar, capture el código"),
+            ),
+          ),
+        ],
+      ),
+      actions: [
+        Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          children: [
+            TextButton(
+              onPressed: () => Navigator.of(context).pop(),
+              child: const Text('Cancelar', style: TextStyle(fontSize: 18)),
+              style: ButtonStyle(
+                backgroundColor: MaterialStateProperty.all(Colors.red),
+                foregroundColor: MaterialStateProperty.all(Colors.white),
+                padding: MaterialStateProperty.all(
+                    const EdgeInsets.symmetric(horizontal: 20, vertical: 10)),
+              ),
+            ),
+            TextButton(
+              onPressed: _validateCode,
+              child: const Text('Continuar', style: TextStyle(fontSize: 18)),
+              style: ButtonStyle(
+                backgroundColor: MaterialStateProperty.all(Colors.green),
+                foregroundColor: MaterialStateProperty.all(Colors.white),
+                padding: MaterialStateProperty.all(
+                    const EdgeInsets.symmetric(horizontal: 20, vertical: 10)),
+              ),
+            ),
+          ],
+        ),
+      ],
+    );
+  }
+}