Browse Source

Actualizacion version con corte de caja y ajuste en la creacion de pedido

OscarGil03 5 months ago
parent
commit
166db8d37a

+ 1 - 0
lib/main.dart

@@ -46,6 +46,7 @@ void main() async {
       ChangeNotifierProvider(create: (_) => VariableViewModel()),
       ChangeNotifierProvider(create: (_) => SucursalViewModel()),
       ChangeNotifierProvider(create: (_) => PermisoViewModel()),
+      ChangeNotifierProvider(create: (_) => CorteCajaViewModel()),
       // Agrega aquí cualquier otro provider que necesites
     ], child: const MyApp()));
   });

+ 93 - 19
lib/models/corte_caja_model.dart

@@ -1,41 +1,115 @@
 import 'basico_model.dart';
 import '../services/services.dart';
 
-class CorteCaja extends Basico {
+class CorteCaja {
+  String? id;
+  DateTime? fechaApertura;
+  DateTime? fechaCorte;
+  int? idUsuario;
   double? fondo;
-  double? fondo_ds;
-  double? ventas;
-  String? fecha;
+  int? idSucursal;
+  double? fondoDiaSig;
+  double? ventaPuntos;
+  double? ventaEfe;
+  double? ventaTrans;
+  double? ventaTarj;
+  double? gasto;
+  double? retiro;
+  double? deposito;
+  double? corteFinal;
+  DateTime? creado;
+  DateTime? eliminado;
+  DateTime? modificado;
 
   CorteCaja({
-    super.id,
+    this.id,
+    this.fechaApertura,
+    this.fechaCorte,
+    this.idUsuario,
+    this.idSucursal,
     this.fondo,
-    this.fondo_ds,
-    this.ventas,
-    this.fecha,
+    this.fondoDiaSig,
+    this.ventaPuntos,
+    this.ventaEfe,
+    this.ventaTrans,
+    this.ventaTarj,
+    this.gasto,
+    this.retiro,
+    this.deposito,
+    this.corteFinal,
+    this.creado,
+    this.modificado,
+    this.eliminado,
   });
 
-  @override
   Map<String, dynamic> toJson() {
     return {
       'id': id,
+      'fechaApertura': fechaApertura?.toIso8601String(),
+      'fechaCorte': fechaCorte?.toIso8601String(),
+      'idUsuario': idUsuario ?? 0,
+      'idSucursal': idSucursal ?? 0,
+      'fondo': fondo ?? 0.0,
+      'fondoDiaSig': fondoDiaSig ?? 0.0,
+      'ventaPuntos': ventaPuntos ?? 0.0,
+      'ventaEfe': ventaEfe ?? 0.0,
+      'ventaTrans': ventaTrans ?? 0.0,
+      'ventaTarj': ventaTarj ?? 0.0,
+      'gasto': gasto ?? 0.0,
+      'retiro': retiro ?? 0.0,
+      'deposito': deposito ?? 0.0,
+      'corteFinal': corteFinal ?? 0.0,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'id': id,
+      'fechaApertura': fechaApertura,
+      'fechaCorte': fechaCorte,
+      'idUsuario': idUsuario,
+      'idSucursal': idSucursal,
       'fondo': fondo,
-      'fondo_ds': fondo_ds,
-      'ventas': ventas,
-      'fecha': fecha,
-    }..addAll(super.toJson());
+      'fondoDiaSig': fondoDiaSig,
+      'ventaPuntos': ventaPuntos,
+      'ventaEfe': ventaEfe,
+      'ventaTrans': ventaTrans,
+      'ventaTarj': ventaTarj,
+      'gasto': gasto,
+      'retiro': retiro,
+      'deposito': deposito,
+      'corteFinal': corteFinal,
+      'creado': creado,
+      'modificado': modificado,
+      'eliminado': eliminado,
+    };
   }
 
   CorteCaja.fromJson(Map<String, dynamic> json) {
-    super.parseJson(json);
+    id = Basico.parseString(json['id']);
+    fechaApertura = Basico.parseDate(json['fechaApertura']);
+    fechaCorte = Basico.parseDate(json['fechaCorte']);
+    idUsuario = Basico.parseInt(json['idUsuario']);
+    idSucursal = Basico.parseInt(json['idSucursal']);
     fondo = Basico.parseDouble(json['fondo']);
-    fondo_ds = Basico.parseDouble(json['fondo_ds']);
-    fondo_ds = Basico.parseDouble(json['fondo_ds']);
-    ventas = Basico.parseDouble(json['ventas']);
-    fecha = Basico.parseString(json['fecha']);
+    fondoDiaSig = Basico.parseDouble(json['fondoDiaSig']);
+    ventaPuntos = Basico.parseDouble(json['ventaPuntos']);
+    ventaEfe = Basico.parseDouble(json['ventaEfe']);
+    ventaTrans = Basico.parseDouble(json['ventaTrans']);
+    ventaTarj = Basico.parseDouble(json['ventaTarj']);
+    gasto = Basico.parseDouble(json['gasto']);
+    retiro = Basico.parseDouble(json['retiro']);
+    deposito = Basico.parseDouble(json['deposito']);
+    corteFinal = Basico.parseDouble(json['corteFinal']);
+    creado = Basico.parseDate(json['creado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+    modificado = Basico.parseDate(json['modificado']);
   }
 
   Future<void> guardar() async {
-    idLocal = await RepoService().guardar(this);
+    await RepoService().guardar(this);
   }
 }

+ 58 - 9
lib/models/deposito_model.dart

@@ -1,31 +1,80 @@
 import 'basico_model.dart';
 import '../services/services.dart';
 
-class Deposito extends Basico {
+class Deposito {
+  String? id;
+  String? idCorteCaja;
+  int? idSucursal;
+  int? idUsuario;
+  DateTime? fechaDeposito;
   double? monto;
+  String? persona;
   String? descripcion;
-  int? idCorteCaja;
+  DateTime? creado;
+  DateTime? modificado;
+  DateTime? eliminado;
 
-  Deposito({super.id, this.monto, this.descripcion, this.idCorteCaja});
+  Deposito({
+    this.id,
+    this.idCorteCaja,
+    this.idSucursal,
+    this.idUsuario,
+    this.fechaDeposito,
+    this.monto,
+    this.persona,
+    this.descripcion,
+    this.creado,
+    this.modificado,
+    this.eliminado,
+  });
 
-  @override
   Map<String, dynamic> toJson() {
     return {
       'id': id,
+      'idCorteCaja': idCorteCaja ?? 0,
+      'idSucursal': idSucursal ?? 0,
+      'idUsuario': idUsuario ?? 0,
+      'fechaDeposito': fechaDeposito?.toIso8601String(),
+      'monto': monto ?? 0.0,
+      'persona': persona ?? '',
+      'descripcion': descripcion ?? '',
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'id': id,
+      'idCorteCaja': idCorteCaja,
+      'idSucursal': idSucursal,
+      'idUsuario': idUsuario,
+      'fechaDeposito': fechaDeposito,
       'monto': monto,
+      'persona': persona,
       'descripcion': descripcion,
-      'idCorteCaja': idCorteCaja,
-    }..addAll(super.toJson());
+      'creado': creado,
+      'modificado': modificado,
+      'eliminado': eliminado,
+    };
   }
 
   Deposito.fromJson(Map<String, dynamic> json) {
-    super.parseJson(json);
+    id = Basico.parseString(json['id']);
+    idCorteCaja = Basico.parseString(json['idCorteCaja']);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    idUsuario = Basico.parseInt(json['idUsuario']);
+    fechaDeposito = Basico.parseDate(json['fechaDeposito']);
     monto = Basico.parseDouble(json['monto']);
+    persona = Basico.parseString(json['persona']);
     descripcion = Basico.parseString(json['descripcion']);
-    idCorteCaja = Basico.parseInt(json['idCorteCaja']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
   }
 
   Future<void> guardar() async {
-    idLocal = await RepoService().guardar(this);
+    await RepoService().guardar(this);
   }
 }

+ 7 - 7
lib/models/descuento_model.dart

@@ -1,14 +1,14 @@
+import 'basico_model.dart';
+
 class Descuento {
   int? id;
-  int porcentaje;
+  int? porcentaje;
 
-  Descuento({this.id, required this.porcentaje});
+  Descuento({this.id, this.porcentaje});
 
-  factory Descuento.fromJson(Map<String, dynamic> json) {
-    return Descuento(
-      id: json['id'],
-      porcentaje: json['porcentaje'],
-    );
+  Descuento.fromJson(Map<String, dynamic> json) {
+    id = Basico.parseInt(json['id']);
+    porcentaje = Basico.parseInt(json['porcentaje']);
   }
 
   Map<String, dynamic> toJson() {

+ 58 - 9
lib/models/gasto_model.dart

@@ -1,31 +1,80 @@
 import 'basico_model.dart';
 import '../services/services.dart';
 
-class Gasto extends Basico {
+class Gasto {
+  String? id;
+  String? idCorteCaja;
+  int? idSucursal;
+  int? idUsuario;
+  DateTime? fechaGasto;
   double? monto;
+  String? persona;
   String? descripcion;
-  int? idCorteCaja;
+  DateTime? creado;
+  DateTime? modificado;
+  DateTime? eliminado;
 
-  Gasto({super.id, this.monto, this.descripcion, this.idCorteCaja});
+  Gasto({
+    this.id,
+    this.idCorteCaja,
+    this.idSucursal,
+    this.idUsuario,
+    this.fechaGasto,
+    this.monto,
+    this.persona,
+    this.descripcion,
+    this.creado,
+    this.modificado,
+    this.eliminado,
+  });
 
-  @override
   Map<String, dynamic> toJson() {
     return {
       'id': id,
+      'idCorteCaja': idCorteCaja ?? 0,
+      'idSucursal': idSucursal ?? 0,
+      'idUsuario': idUsuario ?? 0,
+      'fechaGasto': fechaGasto?.toIso8601String(),
+      'monto': monto ?? 0.0,
+      'persona': persona ?? '',
+      'descripcion': descripcion ?? '',
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'id': id,
+      'idCorteCaja': idCorteCaja,
+      'idSucursal': idSucursal,
+      'idUsuario': idUsuario,
+      'fechaGasto': fechaGasto,
       'monto': monto,
+      'persona': persona,
       'descripcion': descripcion,
-      'idCorteCaja': idCorteCaja,
-    }..addAll(super.toJson());
+      'creado': creado,
+      'modificado': modificado,
+      'eliminado': eliminado,
+    };
   }
 
   Gasto.fromJson(Map<String, dynamic> json) {
-    super.parseJson(json);
+    id = Basico.parseString(json['id']);
+    idCorteCaja = Basico.parseString(json['idCorteCaja']);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    idUsuario = Basico.parseInt(json['idUsuario']);
+    fechaGasto = Basico.parseDate(json['fechaGasto']);
     monto = Basico.parseDouble(json['monto']);
+    persona = Basico.parseString(json['persona']);
     descripcion = Basico.parseString(json['descripcion']);
-    idCorteCaja = Basico.parseInt(json['idCorteCaja']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
   }
 
   Future<void> guardar() async {
-    idLocal = await RepoService().guardar(this);
+    await RepoService().guardar(this);
   }
 }

+ 1 - 0
lib/models/models.dart

@@ -22,3 +22,4 @@ export '../models/variable_model.dart';
 export '../models/sucursal_model.dart';
 export '../models/permiso_model.dart';
 export '../models/usuario_permiso_model.dart';
+export '../models/retiro_model.dart';

+ 2 - 0
lib/models/pedido_model.dart

@@ -75,6 +75,8 @@ class Pedido extends Basico {
       'cantEfectivo': cantEfectivo,
       'cantTarjeta': cantTarjeta,
       'cantTransferencia': cantTransferencia,
+      'sincronizado': sincronizado,
+      'idWeb': idWeb,
     }..addAll(super.toJson());
   }
 

+ 2 - 0
lib/models/pedido_producto_model.dart

@@ -39,6 +39,8 @@ class PedidoProducto extends Basico {
       'cantidad': cantidad,
       'terminar': terminar,
       'comentario': comentario,
+      'idWeb': idWeb,
+      'sincronizado': sincronizado,
     }..addAll(super.toJson());
   }
 

+ 80 - 0
lib/models/retiro_model.dart

@@ -0,0 +1,80 @@
+import 'basico_model.dart';
+import '../services/services.dart';
+
+class Retiro {
+  String? id;
+  String? idCorteCaja;
+  int? idSucursal;
+  int? idUsuario;
+  DateTime? fechaRetiro;
+  double? monto;
+  String? persona;
+  String? descripcion;
+  DateTime? creado;
+  DateTime? modificado;
+  DateTime? eliminado;
+
+  Retiro({
+    this.id,
+    this.idCorteCaja,
+    this.idSucursal,
+    this.idUsuario,
+    this.fechaRetiro,
+    this.monto,
+    this.persona,
+    this.descripcion,
+    this.creado,
+    this.modificado,
+    this.eliminado,
+  });
+
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idCorteCaja': idCorteCaja ?? 0,
+      'idSucursal': idSucursal ?? 0,
+      'idUsuario': idUsuario ?? 0,
+      'fechaRetiro': fechaRetiro?.toIso8601String(),
+      'monto': monto ?? 0.0,
+      'persona': persona ?? '',
+      'descripcion': descripcion ?? '',
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'id': id,
+      'idCorteCaja': idCorteCaja,
+      'idSucursal': idSucursal,
+      'idUsuario': idUsuario,
+      'fechaRetiro': fechaRetiro,
+      'monto': monto,
+      'persona': persona,
+      'descripcion': descripcion,
+      'creado': creado,
+      'modificado': modificado,
+      'eliminado': eliminado,
+    };
+  }
+
+  Retiro.fromJson(Map<String, dynamic> json) {
+    id = Basico.parseString(json['id']);
+    idCorteCaja = Basico.parseString(json['idCorteCaja']);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    idUsuario = Basico.parseInt(json['idUsuario']);
+    fechaRetiro = Basico.parseDate(json['fechaRetiro']);
+    monto = Basico.parseDouble(json['monto']);
+    persona = Basico.parseString(json['persona']);
+    descripcion = Basico.parseString(json['descripcion']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
+  Future<void> guardar() async {
+    await RepoService().guardar(this);
+  }
+}

+ 3 - 3
lib/services/base_service.dart

@@ -9,10 +9,10 @@ class BaseService {
   int total = 0;
   //String baseUrl = 'hermogas.est.api.rdsistemas.app';
   //produccion: joshipapas.api.edesarrollos.info
-  //prueba: joshipapas.test.edesarrollos.info
+  //prueba: pos.test.turquessacoffee.com
 
-  String base_url = 'https://joshipapas.test.edesarrollos.info/';
-  String baseUrl = 'joshipapas.test.edesarrollos.info';
+  String base_url = 'https://pos.api.turquessacoffee.com/';
+  String baseUrl = 'pos.api.turquessacoffee.com';
   Future<Map<String, String>> getDefaultHeaders({withAuth = true}) async {
     Map<String, String> defaultHeaders = {'Content-Type': 'application/json'};
 

+ 280 - 239
lib/services/repo_service.dart

@@ -9,19 +9,23 @@ import 'services.dart';
 import '../views/producto/producto_imagen.dart';
 
 class RepoService<T> {
-  static int dbVersion = 19;
-  static String dbName = 'joshipos026.db';
+  static int dbVersion = 21;
+  static String dbName = 'posTurquessa.db';
   static const String id = Basico.identificadorWeb;
   static const String idLocal = Basico.identificadorLocal;
   static Database? _db;
   final Map<String, dynamic> contexto = {
     'Categoria': CategoriaProducto(),
     'Producto': Producto(),
+    'ProductoTopping': ProductoTopping(),
     'Pedido': Pedido(productos: []),
     'PedidoProducto': PedidoProducto(),
-    'Gasto': Gasto(),
-    'Deposito': Deposito(),
-    'CorteCaja': CorteCaja(),
+    'PedidoProductoTopping': PedidoProductoTopping(),
+    'Sucursal': Sucursal(),
+    'Usuario': Usuario(),
+    'UsuarioPermiso': UsuarioPermiso(),
+    'Variable': Variable(),
+    'Descuento': Descuento(),
   };
 
   Future<Database?> get db async {
@@ -66,73 +70,8 @@ class RepoService<T> {
       });
       sql += ")";
       await db.execute(sql);
-
-      await db.execute('''
-        CREATE TABLE PedidoProductoTopping (
-          id INTEGER PRIMARY KEY AUTOINCREMENT,
-          idPedidoProducto INTEGER,
-          idTopping INTEGER,
-          FOREIGN KEY (idPedidoProducto) REFERENCES PedidoProducto(id),
-          FOREIGN KEY (idTopping) REFERENCES Producto(id)
-        )
-        ''');
-
-      if (modelo.runtimeType.toString() == 'Pedido') {
-        await db.execute(
-            'ALTER TABLE Pedido ADD COLUMN descuento INTEGER DEFAULT 0');
-      }
-
-      await db.execute('''
-    CREATE TABLE Descuento (
-      id INTEGER PRIMARY KEY AUTOINCREMENT,
-      porcentaje INTEGER
-    )
-  ''');
-
-      await db.insert('Descuento', {'porcentaje': 0});
-      await db.insert('Descuento', {'porcentaje': 5});
-      await db.insert('Descuento', {'porcentaje': 10});
-      await db.insert('Descuento', {'porcentaje': 15});
-      await db.insert('Descuento', {'porcentaje': 20});
-      await db.insert('Descuento', {'porcentaje': 25});
-      await db.insert('Descuento', {'porcentaje': 30});
     });
 
-    await db.execute('''
-      CREATE TABLE ProductoTopping (
-        id INTEGER PRIMARY KEY AUTOINCREMENT,
-        idProducto INTEGER,
-        idTopping INTEGER,
-        FOREIGN KEY (idProducto) REFERENCES Producto(id),
-        FOREIGN KEY (idTopping) REFERENCES Producto(id)
-      )
-    ''');
-
-    await db.execute('''
-      CREATE TABLE Variable (
-        id INTEGER PRIMARY KEY AUTOINCREMENT,
-        nombre TEXT,
-        clave TEXT,
-        descripcion TEXT,
-        activo BOOLEAN,
-        idLocal INTEGER,
-        eliminado TEXT
-      )
-    ''');
-
-    await db.execute('''
-          ALTER TABLE Pedido ADD COLUMN idWeb INTEGER;
-        ''');
-    await db.execute('''
-          ALTER TABLE Pedido ADD COLUMN sincronizado TEXT;
-        ''');
-    await db.execute('''
-          ALTER TABLE PedidoProducto ADD COLUMN idWeb INTEGER;
-        ''');
-    await db.execute('''
-          ALTER TABLE PedidoProducto ADD COLUMN sincronizado TEXT;
-        ''');
-
     await db.insert('Variable', {
       'nombre': 'Imprimir Ticket Cocina',
       'clave': 'ticket_cocina',
@@ -151,13 +90,103 @@ class RepoService<T> {
       'idLocal': -1,
     });
 
-    await db.insert('Variable', {
-      'nombre': 'Sucursal',
-      'clave': 'PRUEBA',
-      'descripcion': 'Sucursal Prueba',
-      'activo': 1,
-      'idLocal': -1,
-    });
+    await db.insert('Descuento', {'porcentaje': 0});
+    await db.insert('Descuento', {'porcentaje': 5});
+    await db.insert('Descuento', {'porcentaje': 10});
+    await db.insert('Descuento', {'porcentaje': 15});
+    await db.insert('Descuento', {'porcentaje': 20});
+    await db.insert('Descuento', {'porcentaje': 25});
+    await db.insert('Descuento', {'porcentaje': 30});
+
+    await db.execute('''
+          CREATE TABLE Permiso (
+            id TEXT PRIMARY KEY,
+            idModulo TEXT,
+            nombre TEXT,
+            descripcion TEXT,
+            eliminado TEXT,
+            creado TEXT,
+            modificado TEXT
+          )
+        ''');
+
+    await db.execute('''
+          CREATE TABLE CorteCaja (
+            id TEXT PRIMARY KEY,
+            fechaApertura TEXT,
+            fechaCorte TEXT,
+            idUsuario INTEGER,
+            idSucursal INTEGER,
+            fondo REAL,
+            fondoDiaSig REAL,
+            ventaPuntos REAL,
+            ventaEfe REAL,
+            ventaTrans REAL,
+            ventaTarj REAL,
+            gasto REAL,
+            retiro REAL,
+            deposito REAL,
+            corteFinal REAL,
+            creado TEXT,
+            modificado TEXT,
+            eliminado TEXT,
+            idWeb TEXT,
+            sincronizado TEXT
+          )
+        ''');
+
+    await db.execute('''
+          CREATE TABLE Deposito (
+            id TEXT PRIMARY KEY,
+            idCorteCaja TEXT,
+            idSucursal INTEGER,
+            idUsuario INTEGER,
+            fechaDeposito TEXT,
+            monto REAL,
+            persona TEXT,
+            descripcion TEXT,
+            creado TEXT,
+            modificado TEXT,
+            eliminado TEXT,
+            idWeb TEXT
+          )
+        ''');
+
+    await db.execute('''
+          CREATE TABLE Retiro (
+            id TEXT PRIMARY KEY,
+            idCorteCaja TEXT,
+            idSucursal INTEGER,
+            idUsuario INTEGER,
+            fechaRetiro TEXT,
+            monto REAL,
+            persona TEXT,
+            descripcion TEXT,
+            creado TEXT,
+            modificado TEXT,
+            eliminado TEXT,
+            sincronizado TEXT,
+            idWeb TEXT
+          )
+        ''');
+
+    await db.execute('''
+          CREATE TABLE Gasto (
+            id TEXT PRIMARY KEY,
+            idCorteCaja TEXT,
+            idSucursal INTEGER,
+            idUsuario INTEGER,
+            fechaGasto TEXT,
+            monto REAL,
+            persona TEXT,
+            descripcion TEXT,
+            creado TEXT,
+            modificado TEXT,
+            eliminado TEXT,
+            idWeb TEXT,
+            sincronizado TEXT
+          )
+        ''');
   }
 
   void _onUpgrade(Database db, int oldVersion, int newVersion) async {
@@ -173,139 +202,6 @@ class RepoService<T> {
           await db.execute(
               "ALTER TABLE CategoriaProducto ADD COLUMN maximo INTEGER");
 
-          await db.insert('CategoriaProducto', {
-            'nombre': 'BASE PRODUCTO',
-            'descripcion': 'Base del producto',
-            'esToping': 1,
-            'maximo': 1,
-          });
-
-          await db.insert('CategoriaProducto', {
-            'nombre': 'SALSAS PRODUCTO',
-            'descripcion': 'Elige tus salsas (Máx. 2)',
-            'esToping': 1,
-            'maximo': 2,
-          });
-
-          await db.insert('CategoriaProducto', {
-            'nombre': 'ADEREZO PRODUCTO',
-            'descripcion': 'Elige tu aderezo (Máx. 2)',
-            'esToping': 1,
-            'maximo': 2,
-          });
-
-          await db.insert('CategoriaProducto', {
-            'nombre': 'TOPPING PRODUCTO',
-            'descripcion': 'Elige tus toppings (Máx. 2)',
-            'esToping': 1,
-            'maximo': 2,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'Papa Gajo',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'Papa Regilla',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'Papa Curly',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'Papa Smile',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'Papa Francesa',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'BBQ',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'HOTBBQ',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'BUFFALO',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'TERIYAKI',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'PARMESAN GARLIC',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'QUESO AMARILLO',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'RANCH',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'CHIPOTLE',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'ADEREZO JALAPEÑO',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'KETCHUP',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'JALAPEÑO',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'QUESO BLANCO',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'TAKIS',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'RUFFLES',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'QUESO PARMESANO',
-            'precio': 0,
-          });
-
-          await db.insert('Producto', {
-            'nombre': 'ELOTE',
-            'precio': 0,
-          });
-
           break;
 
         case 2:
@@ -581,12 +477,129 @@ class RepoService<T> {
 
         case 18:
           await db.execute('''
-
-
             ALTER TABLE Usuario ADD COLUMN clave TEXT;
           ''');
 
           break;
+
+        case 19:
+          await db.execute('DROP TABLE CorteCaja');
+          await db.execute('DROP TABLE Deposito');
+          await db.execute('DROP TABLE Gasto');
+
+          await db.execute('''
+          CREATE TABLE CorteCaja (
+            id TEXT PRIMARY KEY,
+            fechaApertura TEXT,
+            fechaCorte TEXT,
+            idUsuario INTEGER,
+            idSucursal INTEGER,
+            fondo REAL,
+            fondoDiaSig REAL,
+            ventaPuntos REAL,
+            ventaEfe REAL,
+            ventaTrans REAL,
+            ventaTarj REAL,
+            gasto REAL,
+            retiro REAL,
+            corteFinal REAL,
+            creado TEXT,
+            modificado TEXT,
+            eliminado TEXT
+          )
+        ''');
+
+          await db.execute('''
+          CREATE TABLE Deposito (
+            id TEXT PRIMARY KEY,
+            idCorteCaja TEXT,
+            idSucursal INTEGER,
+            idUsuario INTEGER,
+            fechaDeposito TEXT,
+            monto REAL,
+            persona TEXT,
+            descripcion TEXT,
+            creado TEXT,
+            modificado TEXT,
+            eliminado TEXT
+          )
+        ''');
+
+          await db.execute('''
+          CREATE TABLE Retiro (
+            id TEXT PRIMARY KEY,
+            idCorteCaja TEXT,
+            idSucursal INTEGER,
+            idUsuario INTEGER,
+            fechaRetiro TEXT,
+            monto REAL,
+            persona TEXT,
+            descripcion TEXT,
+            creado TEXT,
+            modificado TEXT,
+            eliminado TEXT
+          )
+        ''');
+
+          await db.execute('''
+          CREATE TABLE Gasto (
+            id TEXT PRIMARY KEY,
+            idCorteCaja TEXT,
+            idSucursal INTEGER,
+            idUsuario INTEGER,
+            fechaGasto TEXT,
+            monto REAL,
+            persona TEXT,
+            descripcion TEXT,
+            creado TEXT,
+            modificado TEXT,
+            eliminado TEXT
+          )
+        ''');
+
+          await db.execute('''
+          update  Pedido set sincronizado = null where estatus = 'CANCELADO'
+        ''');
+
+          await db.insert('Variable', {
+            'nombre': 'Imprimir ticket PC/Tablet',
+            'clave': 'ticket_pc',
+            'descripcion':
+                'Al estar activo imprime ticket para pc, el estar desactivado imprime ticket para tablet',
+            'activo': 1,
+            'idLocal': -1,
+          });
+
+          break;
+
+        case 20:
+          await db.execute('''
+            ALTER TABLE CorteCaja ADD COLUMN deposito REAL;
+          ''');
+          await db.execute('''
+          ALTER TABLE CorteCaja ADD COLUMN idWeb TEXT;
+        ''');
+          await db.execute('''
+          ALTER TABLE CorteCaja ADD COLUMN sincronizado TEXT;
+        ''');
+          await db.execute('''
+          ALTER TABLE Gasto ADD COLUMN idWeb TEXT;
+        ''');
+          await db.execute('''
+          ALTER TABLE Gasto ADD COLUMN sincronizado TEXT;
+        ''');
+          await db.execute('''
+          ALTER TABLE Retiro ADD COLUMN idWeb TEXT;
+        ''');
+          await db.execute('''
+          ALTER TABLE Retiro ADD COLUMN sincronizado TEXT;
+        ''');
+          await db.execute('''
+          ALTER TABLE Deposito ADD COLUMN idWeb TEXT;
+        ''');
+          await db.execute('''
+          ALTER TABLE Deposito ADD COLUMN sincronizado TEXT;
+        ''');
       }
       oldVersion++;
     }
@@ -594,24 +607,24 @@ class RepoService<T> {
 
   Future<int> guardar(T model) async {
     try {
-      //print("Guardando modelo en la base de datos: ${model.runtimeType}");
-
-      // Convertir el modelo a JSON para la base de datos
+      // Convert the model to JSON for the database
       String modelo = json.encode(model, toEncodable: toEncodable);
-      //print("Modelo convertido a JSON: $modelo");
-
       Map<String, dynamic> modelMap = json.decode(modelo);
       var dbClient = await db;
       String nombreTabla = model.runtimeType.toString();
 
-      // Verificar si el modelo es de tipo Permiso (con id de tipo String)
-      if (model is Permiso) {
-        String? id = modelMap['id'];
+      // Check if the model has a String ID or an int ID
+      dynamic id = modelMap['id'];
+      bool isStringId = id is String;
+      bool isIntId = id is int;
 
-        if (id == null || id.isEmpty) {
-          throw Exception('El ID del permiso no puede ser nulo o vacío');
+      // If id is of type String (e.g., Permiso)
+      if (isStringId) {
+        if (id == null || (id as String).isEmpty) {
+          throw Exception('El ID del modelo no puede ser nulo o vacío');
         }
 
+        // Check for existing record with the String ID
         List<Map> existing = await dbClient!.query(
           nombreTabla,
           where: 'id = ?',
@@ -619,8 +632,7 @@ class RepoService<T> {
         );
 
         if (existing.isNotEmpty) {
-          //print("Actualizando registro existente con ID: $id");
-          await dbClient!.update(
+          await dbClient.update(
             nombreTabla,
             modelMap,
             where: 'id = ?',
@@ -629,25 +641,23 @@ class RepoService<T> {
         } else {
           print(
               "Insertando nuevo registro en la tabla $nombreTabla con ID: $id");
-          await dbClient!.insert(nombreTabla, modelMap);
+          await dbClient.insert(nombreTabla, modelMap);
         }
-
         return 1;
-      } else {
-        int? id = modelMap['id'];
-
+      } else if (isIntId) {
+        // If id is of type int (e.g., other models)
         if (id == null || id == 0) {
-          modelMap.remove('id');
+          modelMap
+              .remove('id'); // Remove id if it's null or 0 for auto-increment
         }
 
-        List<Map> existing = id != null && id > 0
+        List<Map> existing = (id != null && id > 0)
             ? await dbClient!
                 .query(nombreTabla, where: 'id = ?', whereArgs: [id])
             : [];
 
         if (existing.isNotEmpty) {
-          print(
-              "Actualizando registro existente con ID: $id y modificado: ${modelMap['modificado']}");
+          print("Actualizando registro existente con ID: $id");
           await dbClient!.update(
             nombreTabla,
             modelMap,
@@ -658,8 +668,9 @@ class RepoService<T> {
           print("Insertando nuevo registro en la tabla $nombreTabla");
           id = await dbClient!.insert(nombreTabla, modelMap);
         }
-
-        return id!;
+        return id as int;
+      } else {
+        throw Exception('Tipo de ID no soportado');
       }
     } catch (e) {
       print('Error al guardar en dynamic: $e');
@@ -756,6 +767,8 @@ class RepoService<T> {
         return Producto.fromJson(map) as T;
       case Usuario:
         return Usuario.fromJson(map) as T;
+      case CorteCaja:
+        return CorteCaja.fromJson(map) as T;
       default:
         throw Exception('Tipo no soportado');
     }
@@ -780,21 +793,34 @@ class RepoService<T> {
         .toList();
   }
 
-  Future<List<Deposito>> obtenerDepositosPorIdCorteCaja(int idCorteCaja) async {
+  //CORTE CAJA
+
+  Future<List<Deposito>> obtenerDepositosPorIdCorteCaja(
+      String idCorteCaja) async {
     var dbClient = await db;
     List<Map<String, dynamic>> maps = await dbClient!.query(
       'Deposito',
-      where: 'idCorteCaja = ?',
+      where: 'idCorteCaja = ? AND eliminado IS NULL',
       whereArgs: [idCorteCaja],
     );
     return maps.map((map) => Deposito.fromJson(map)).toList();
   }
 
-  Future<List<Gasto>> obtenerGastosPorIdCorteCaja(int idCorteCaja) async {
+  Future<List<Retiro>> obtenerRetirosPorIdCorteCaja(String idCorteCaja) async {
+    var dbClient = await db;
+    List<Map<String, dynamic>> maps = await dbClient!.query(
+      'Retiro',
+      where: 'idCorteCaja = ? AND eliminado IS NULL',
+      whereArgs: [idCorteCaja],
+    );
+    return maps.map((map) => Retiro.fromJson(map)).toList();
+  }
+
+  Future<List<Gasto>> obtenerGastosPorIdCorteCaja(String idCorteCaja) async {
     var dbClient = await db;
     List<Map<String, dynamic>> maps = await dbClient!.query(
       'Gasto',
-      where: 'idCorteCaja = ?',
+      where: 'idCorteCaja = ? AND eliminado IS NULL',
       whereArgs: [idCorteCaja],
     );
     return maps.map((map) => Gasto.fromJson(map)).toList();
@@ -806,6 +832,21 @@ class RepoService<T> {
     return Sqflite.firstIntValue(result) ?? 0;
   }
 
+  Future<double?> obtenerFondoDiaSigDelUltimoCorte() async {
+    var dbClient = await db;
+    List<Map<String, dynamic>> result = await dbClient!.query(
+      'CorteCaja',
+      orderBy: 'fechaApertura DESC',
+      limit: 1,
+    );
+
+    if (result.isNotEmpty) {
+      return result.first['fondoDiaSig'] as double?;
+    }
+
+    return null;
+  }
+
   Future<List<Pedido>> obtenerPedidosPaginados(int limit, int offset) async {
     Database? dbClient = await db;
     List<Map<String, dynamic>> result = await dbClient!

+ 3 - 1
lib/themes/themes.dart

@@ -2,10 +2,12 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
 class AppTheme {
-  static Color primary = Color.fromRGBO(242, 75, 89, 1.000);
+  static Color primary = Color(0xFF32D7ED);
   static Color secondary = Colors.black;
   static Color tertiary = const Color(0xFF242424);
   static Color quaternary = const Color(0xFFF1F1F3);
+  static Color verde = const Color(0xff248f83);
+  static Color rojo = const Color(0xFFF24B59);
   static ThemeData lightTheme = ThemeData.light().copyWith(
     useMaterial3: true,
     //Scaffold

+ 426 - 0
lib/viewmodels/corte_caja_view_model.dart

@@ -0,0 +1,426 @@
+import '../../widgets/widgets.dart';
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+import 'package:uuid/uuid.dart';
+import '../models/models.dart';
+import '../services/repo_service.dart';
+import '../views/corte_caja/corte_caja_ticket.dart';
+
+class CorteCajaViewModel extends ChangeNotifier {
+  List<CorteCaja> _cortes = [];
+  List<CorteCaja> get cortes => _cortes;
+
+  bool _isLoading = false;
+  bool get isLoading => _isLoading;
+  CorteCaja? _selectedCorte;
+
+  double corteFinal = 0.0;
+  double totalDepositos = 0.0;
+  double totalRetiros = 0.0;
+  double totalGastos = 0.0;
+  List<Deposito> depositos = [];
+  List<Retiro> retiros = [];
+  List<Gasto> gastos = [];
+  double _ventaEfe = 0.0;
+  double _ventaTarj = 0.0;
+  double _ventaTransf = 0.0;
+  double _totalVenta = 0.0;
+
+  CorteCaja? get selectedCorte => _selectedCorte;
+
+  int _currentPage = 1;
+  int _totalCortes = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalPedidos => _totalCortes;
+  int get totalPages => (_totalCortes / _limit).ceil();
+
+  double get ventaEfe => _ventaEfe;
+  double get ventaTarj => _ventaTarj;
+  double get ventaTransf => _ventaTransf;
+  double get totalVenta => _totalVenta;
+  double get salidasTotales => totalRetiros + totalGastos;
+
+  final TextEditingController _ventaEfeController = TextEditingController();
+  final TextEditingController _ventaTarjController = TextEditingController();
+  final TextEditingController _ventaTransfController = TextEditingController();
+  final TextEditingController _totalVentaController = TextEditingController();
+  final TextEditingController _fondoController = TextEditingController();
+  final TextEditingController _fondoDiaSigController = TextEditingController();
+  final TextEditingController _ventaPuntosController = TextEditingController();
+
+  TextEditingController get ventaEfeController => _ventaEfeController;
+  TextEditingController get ventaTarjController => _ventaTarjController;
+  TextEditingController get ventaTransfController => _ventaTransfController;
+  TextEditingController get totalVentaController => _totalVentaController;
+  TextEditingController get fondoController => _fondoController;
+  TextEditingController get fondoDiaSigController => _fondoDiaSigController;
+  TextEditingController get ventaPuntosController => ventaPuntosController;
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchCortes(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchCortes(page: _currentPage - 1);
+    }
+  }
+
+  bool isCorteAbierto() {
+    return _selectedCorte != null && _selectedCorte!.fechaCorte == null;
+  }
+
+  void actualizarVentaEfectivo(String value) {
+    _ventaEfe = double.tryParse(value.replaceAll(',', '')) ?? 0.0;
+    _ventaEfeController.text = formatoMiles(_ventaEfe);
+    _calcularTotalVenta();
+    notifyListeners();
+  }
+
+  void actualizarVentaTarjeta(String value) {
+    _ventaTarj = double.tryParse(value.replaceAll(',', '')) ?? 0.0;
+    _ventaTarjController.text = formatoMiles(_ventaTarj);
+    _calcularTotalVenta();
+    notifyListeners();
+  }
+
+  void actualizarVentaTransferencia(String value) {
+    _ventaTransf = double.tryParse(value.replaceAll(',', '')) ?? 0.0;
+    _ventaTransfController.text = formatoMiles(_ventaTransf);
+    _calcularTotalVenta();
+    notifyListeners();
+  }
+
+  void _calcularTotalVenta() {
+    _totalVenta = _ventaEfe + _ventaTarj + _ventaTransf;
+    _totalVentaController.text = formatoMiles(_totalVenta);
+  }
+
+  Future<String?> createCorteCaja() async {
+    RepoService<CorteCaja> repoCorte = RepoService<CorteCaja>();
+
+    double? fondoDiaSigAnterior =
+        await repoCorte.obtenerFondoDiaSigDelUltimoCorte();
+
+    _selectedCorte = CorteCaja(
+      id: Uuid().v4(),
+      fechaApertura: DateTime.now().toUtc(),
+      creado: DateTime.now().toUtc(),
+      fondo: fondoDiaSigAnterior ?? 0.0,
+    );
+
+    await _selectedCorte!.guardar();
+    notifyListeners();
+    return _selectedCorte!.id;
+  }
+
+  Future<void> eliminarDeposito(String id) async {
+    final deposito = depositos.firstWhere((d) => d.id == id);
+    deposito.eliminado = DateTime.now().toUtc();
+    await deposito.guardar();
+    calcularTotalDeposito();
+    notifyListeners();
+  }
+
+  Future<void> eliminarRetiro(String id) async {
+    final retiro = retiros.firstWhere((d) => d.id == id);
+    retiro.eliminado = DateTime.now().toUtc();
+    await retiro.guardar();
+    calcularTotalRetiro();
+    notifyListeners();
+  }
+
+  Future<void> eliminarGasto(String id) async {
+    final gasto = gastos.firstWhere((d) => d.id == id);
+    gasto.eliminado = DateTime.now().toUtc();
+    await gasto.guardar();
+    calcularTotalGasto();
+    notifyListeners();
+  }
+
+  Future<void> fetchCortes({int page = 1}) async {
+    setIsLoading(true);
+    RepoService<CorteCaja> repoCorte = RepoService<CorteCaja>();
+    _currentPage = page;
+    _cortes = await repoCorte.obtenerTodos(orderBy: 'fechaApertura DESC');
+    setIsLoading(false);
+    notifyListeners();
+  }
+
+  Future<void> fetchDepositosAndRetiros(String corteCajaId) async {
+    RepoService<Deposito> depositoRepo = RepoService<Deposito>();
+    RepoService<Retiro> retiroRepo = RepoService<Retiro>();
+    RepoService<Gasto> gastoRepo = RepoService<Gasto>();
+    depositos = await depositoRepo.obtenerDepositosPorIdCorteCaja(corteCajaId);
+    retiros = await retiroRepo.obtenerRetirosPorIdCorteCaja(corteCajaId);
+    gastos = await gastoRepo.obtenerGastosPorIdCorteCaja(corteCajaId);
+
+    print('Depósitos obtenidos: ${depositos.length}');
+    print('Retiros obtenidos: ${retiros.length}');
+    print('Gastos obtenidos: ${gastos.length}');
+
+    calcularTotalDeposito();
+    calcularTotalRetiro();
+    calcularTotalGasto();
+    notifyListeners();
+  }
+
+  void selectCorte(CorteCaja corte) {
+    _selectedCorte = corte;
+    notifyListeners();
+  }
+
+  Future<void> addDeposito(double monto, String descripcion, String? persona,
+      String corteCajaId) async {
+    Deposito nuevoDeposito = Deposito(
+      id: Uuid().v4(),
+      idCorteCaja: corteCajaId,
+      fechaDeposito: DateTime.now().toUtc(),
+      creado: DateTime.now().toUtc(),
+      monto: monto,
+      descripcion: descripcion,
+      persona: persona,
+    );
+    await nuevoDeposito.guardar();
+    depositos.add(nuevoDeposito);
+    calcularTotalDeposito();
+
+    calcularCorteFinal();
+
+    notifyListeners();
+  }
+
+  Future<void> addRetiro(double monto, String descripcion, String? persona,
+      String corteCajaId) async {
+    Retiro nuevoRetiro = Retiro(
+      id: Uuid().v4(),
+      idCorteCaja: corteCajaId,
+      fechaRetiro: DateTime.now().toUtc(),
+      creado: DateTime.now().toUtc(),
+      monto: monto,
+      descripcion: descripcion,
+      persona: persona,
+    );
+    await nuevoRetiro.guardar();
+    retiros.add(nuevoRetiro);
+    calcularTotalRetiro();
+
+    calcularCorteFinal();
+
+    notifyListeners();
+  }
+
+  Future<void> addGasto(double monto, String descripcion, String? persona,
+      String corteCajaId) async {
+    Gasto nuevoGasto = Gasto(
+      id: Uuid().v4(),
+      idCorteCaja: corteCajaId,
+      fechaGasto: DateTime.now().toUtc(),
+      creado: DateTime.now().toUtc(),
+      monto: monto,
+      descripcion: descripcion,
+      persona: persona,
+    );
+    await nuevoGasto.guardar();
+    gastos.add(nuevoGasto);
+    calcularTotalGasto();
+
+    calcularCorteFinal();
+
+    notifyListeners();
+  }
+
+  Future<void> cargarVentasDesdeFechaApertura(DateTime fechaApertura) async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+
+    DateTime fechaFin = _selectedCorte?.fechaCorte ?? DateTime.now().toUtc();
+
+    List<Pedido> pedidos =
+        (await repoPedido.buscarPorFecha(fechaApertura, fechaFin))
+            .where((pedido) =>
+                pedido.estatus == 'NUEVO' || pedido.estatus == 'TERMINADO')
+            .toList();
+
+    // Calcula los totales
+    double totalEfectivo = 0;
+    double totalTarjeta = 0;
+    double totalTransferencia = 0;
+    double totalDia = 0.0;
+    double cambio = 0.0;
+    double efeSinCambio = 0.0;
+    double totalSinCambio = 0.0;
+
+    for (var pedido in pedidos) {
+      totalEfectivo += pedido.cantEfectivo ?? 0;
+      totalTarjeta += pedido.cantTarjeta ?? 0;
+      totalTransferencia += pedido.cantTransferencia ?? 0;
+      totalSinCambio += pedido.totalPedido ?? 0;
+    }
+
+    totalDia = totalEfectivo + totalTarjeta + totalTransferencia;
+    cambio = totalDia - totalSinCambio;
+    efeSinCambio = totalEfectivo - cambio;
+
+    _ventaEfeController.text = formatoMiles(efeSinCambio);
+    _ventaTarjController.text = formatoMiles(totalTarjeta);
+    _ventaTransfController.text = formatoMiles(totalTransferencia);
+    _totalVentaController.text =
+        formatoMiles(efeSinCambio + totalTarjeta + totalTransferencia);
+
+    print(
+        'Total efectivo: $totalEfectivo, Total tarjeta: $totalTarjeta, Total transferencia: $totalTransferencia');
+    print('Pedidos encontrados: ${pedidos.length}');
+
+    setIsLoading(false);
+    notifyListeners();
+  }
+
+  bool hasOpenCorteCaja() {
+    return _cortes.any((corte) => corte.fechaCorte == null);
+  }
+
+  Future<void> guardarCorte({bool esCorte = false}) async {
+    double fondo =
+        double.tryParse(_fondoController.text.replaceAll(',', '')) ?? 0;
+    double fondoDiaSig =
+        double.tryParse(_fondoDiaSigController.text.replaceAll(',', '')) ?? 0;
+    double ventaPuntos =
+        double.tryParse(_ventaPuntosController.text.replaceAll(',', '')) ?? 0;
+    double ventaEfe =
+        double.tryParse(_ventaEfeController.text.replaceAll(',', '')) ?? 0;
+    double ventaTrans =
+        double.tryParse(_ventaTransfController.text.replaceAll(',', '')) ?? 0;
+    double ventaTarj =
+        double.tryParse(_ventaTarjController.text.replaceAll(',', '')) ?? 0;
+    double gasto = totalGastos;
+    double retiro = totalRetiros;
+    double deposito = totalDepositos;
+
+    _selectedCorte?.fondo = fondo;
+    _selectedCorte?.fondoDiaSig = fondoDiaSig;
+    _selectedCorte?.ventaPuntos = ventaPuntos;
+    _selectedCorte?.ventaEfe = ventaEfe;
+    _selectedCorte?.ventaTrans = ventaTrans;
+    _selectedCorte?.ventaTarj = ventaTarj;
+    _selectedCorte?.gasto = gasto;
+    _selectedCorte?.retiro = retiro;
+    _selectedCorte?.deposito = deposito;
+
+    calcularCorteFinal();
+
+    _selectedCorte?.corteFinal = corteFinal;
+    _selectedCorte?.modificado = DateTime.now().toUtc();
+
+    if (esCorte) {
+      _selectedCorte?.fechaCorte = DateTime.now().toUtc();
+    }
+
+    await _selectedCorte?.guardar();
+    notifyListeners();
+  }
+
+  void calcularTotalDeposito() {
+    totalDepositos = depositos
+        .where((deposito) => deposito.eliminado == null)
+        .fold(0, (sum, item) => sum + (item.monto ?? 0));
+  }
+
+  void calcularTotalRetiro() {
+    totalRetiros = retiros
+        .where((retiro) => retiro.eliminado == null)
+        .fold(0, (sum, item) => sum + (item.monto ?? 0));
+  }
+
+  void calcularTotalGasto() {
+    totalGastos = gastos
+        .where((gasto) => gasto.eliminado == null)
+        .fold(0, (sum, item) => sum + (item.monto ?? 0));
+  }
+
+  Future<void> buscarPorFecha(DateTime startDate, DateTime endDate) async {
+    setIsLoading(true);
+    RepoService<CorteCaja> repoCorte = RepoService<CorteCaja>();
+    _cortes = await repoCorte.buscarPorFechaCorte(startDate, endDate);
+    setIsLoading(false);
+    notifyListeners();
+  }
+
+  void calcularCorteFinal() {
+    final valores = obtenerValoresFondo();
+    corteFinal = (valores['fondo']! + totalDepositos + valores['ventaEfe']!) -
+        (valores['fondoDiaSig']! + totalRetiros + totalGastos);
+    notifyListeners();
+  }
+
+  Future<void> imprimirCorteCajaTicket({String? corteCajaId}) async {
+    if (corteCajaId != null) {
+      await fetchDepositosAndRetiros(corteCajaId);
+      _selectedCorte = _cortes.firstWhere((corte) => corte.id == corteCajaId);
+    }
+
+    if (_selectedCorte == null) {
+      print("CorteCaja no encontrado para impresión.");
+      return;
+    }
+
+    double fondo = _selectedCorte!.fondo ?? 0;
+    double fondoDiaSig = _selectedCorte!.fondoDiaSig ?? 0;
+    double? ventaEfe = _selectedCorte!.ventaEfe;
+    double? ventaTarj = _selectedCorte!.ventaTarj;
+    double? ventaTransf = _selectedCorte!.ventaTrans;
+    double deposito = _selectedCorte!.deposito!;
+    double retiro = _selectedCorte!.retiro!;
+    double gasto = _selectedCorte!.gasto!;
+    double corteFinal = _selectedCorte!.corteFinal!;
+    DateTime? fechaApertura = _selectedCorte!.fechaApertura;
+
+    await CorteCajaTicket.imprimirTicket(
+      fondo: fondo,
+      fondoDiaSig: fondoDiaSig,
+      ventaEfe: ventaEfe!,
+      ventaTarj: ventaTarj!,
+      ventaTrans: ventaTransf!,
+      deposito: deposito,
+      retiro: retiro,
+      gasto: gasto,
+      corteFinal: corteFinal,
+      fechaApertura: fechaApertura,
+    );
+  }
+
+  @override
+  void dispose() {
+    _ventaEfeController.dispose();
+    _ventaTarjController.dispose();
+    _ventaTransfController.dispose();
+    _totalVentaController.dispose();
+    _fondoController.dispose();
+    corteFinal = 0.0;
+    super.dispose();
+  }
+
+  Map<String, double> obtenerValoresFondo() {
+    double fondo =
+        double.tryParse(fondoController.text.replaceAll(',', '')) ?? 0;
+    double fondoDiaSig =
+        double.tryParse(fondoDiaSigController.text.replaceAll(',', '')) ?? 0;
+    double ventaEfe =
+        (double.tryParse(ventaEfeController.text.replaceAll(',', '')) ?? 0);
+
+    return {
+      'fondo': fondo,
+      'fondoDiaSig': fondoDiaSig,
+      'ventaEfe': ventaEfe,
+    };
+  }
+}

+ 1 - 0
lib/viewmodels/viewmodels.dart

@@ -11,3 +11,4 @@ export '../viewmodels/descuento_view_model.dart';
 export '../viewmodels/variable_view_model.dart';
 export '../viewmodels/sucursal_view_model.dart';
 export '../viewmodels/permiso_view_model.dart';
+export '../viewmodels/corte_caja_view_model.dart';

+ 211 - 0
lib/views/corte_caja/corte_caja_finalizado_screen.dart

@@ -0,0 +1,211 @@
+import '../../widgets/widgets.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:intl/intl.dart';
+import '../../viewmodels/corte_caja_view_model.dart';
+import '../../themes/themes.dart';
+
+class CorteCajaFinalizadoScreen extends StatefulWidget {
+  final String? corteCajaId;
+
+  const CorteCajaFinalizadoScreen({this.corteCajaId, Key? key})
+      : super(key: key);
+
+  @override
+  _CorteCajaFinalizadoScreenState createState() =>
+      _CorteCajaFinalizadoScreenState();
+}
+
+class _CorteCajaFinalizadoScreenState extends State<CorteCajaFinalizadoScreen> {
+  Map<String, bool> _expandedSections = {};
+
+  @override
+  void initState() {
+    super.initState();
+    final viewModel = Provider.of<CorteCajaViewModel>(context, listen: false);
+    viewModel.fetchDepositosAndRetiros(widget.corteCajaId!);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final viewModel = Provider.of<CorteCajaViewModel>(context);
+    double totalEfe = viewModel.selectedCorte!.ventaEfe!;
+    double totalTarj = viewModel.selectedCorte!.ventaTarj!;
+    double totalTrans = viewModel.selectedCorte!.ventaTrans!;
+    double totalRetiro = viewModel.selectedCorte!.retiro!;
+    double totalGasto = viewModel.selectedCorte!.gasto!;
+    double totalSalidas = totalRetiro + totalGasto;
+    double totalVentas = totalEfe + totalTrans + totalTarj;
+
+    return Scaffold(
+      appBar: AppBar(
+          title: Text('Resumen Corte de Caja',
+              style: TextStyle(
+                  color: AppTheme.secondary, fontWeight: FontWeight.w500)),
+          iconTheme: IconThemeData(color: AppTheme.secondary)),
+      body: SingleChildScrollView(
+        child: Padding(
+          padding: EdgeInsets.symmetric(horizontal: 250, vertical: 20),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Text(
+                "Fecha: ${DateFormat('dd/MM/yyyy').format(viewModel.selectedCorte!.fechaApertura!)}",
+                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
+              ),
+              SizedBox(height: 20),
+              tarjeta(Column(
+                children: [
+                  Table(
+                    defaultVerticalAlignment: TableCellVerticalAlignment.middle,
+                    columnWidths: {
+                      0: FlexColumnWidth(4),
+                      1: FlexColumnWidth(2)
+                    },
+                    children: [
+                      _buildTableRow("Fondo", viewModel.selectedCorte?.fondo),
+                      _buildTableRow("Fondo día siguiente",
+                          viewModel.selectedCorte?.fondoDiaSig),
+                      _buildTableRow("Ventas Efectivo", totalEfe),
+                      _buildTableRow("Ventas Tarjeta", totalTarj),
+                      _buildTableRow("Ventas Transferencia", totalTrans),
+                    ],
+                  ),
+                  _buildExpandableTable("Depósitos", viewModel.depositos,
+                      viewModel.selectedCorte?.deposito),
+                  _buildExpandableTable(
+                      "Retiros", viewModel.retiros, totalRetiro),
+                  _buildExpandableTable("Gastos", viewModel.gastos, totalGasto),
+                  Table(
+                    defaultVerticalAlignment: TableCellVerticalAlignment.middle,
+                    columnWidths: {
+                      0: FlexColumnWidth(4),
+                      1: FlexColumnWidth(2)
+                    },
+                    children: [
+                      _buildTableRow('', null),
+                      _buildTableRow('Venta total del día', totalVentas),
+                      _buildTableRow("Efectivo en caja",
+                          viewModel.selectedCorte?.corteFinal),
+                      _buildTableRow("Salidas totales del día", totalSalidas),
+                    ],
+                  ),
+                ],
+              )),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  TableRow _buildTableRow(String label, double? value) {
+    return TableRow(
+      children: [
+        Container(
+          decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
+          padding: const EdgeInsets.all(8.5),
+          child: Text(label,
+              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
+        ),
+        Container(
+          decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
+          height: 45,
+          padding: const EdgeInsets.all(8.0),
+          child: Text(
+            value != null ? "\$${value.toStringAsFixed(2)}" : "",
+            style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
+          ),
+        ),
+      ],
+    );
+  }
+
+  Widget _buildExpandableTable(
+      String title, List<dynamic> items, double? total) {
+    final isExpanded = _expandedSections[title] ?? true;
+
+    return Column(
+      children: [
+        GestureDetector(
+          onTap: () {
+            setState(() {
+              _expandedSections[title] = !isExpanded;
+            });
+          },
+          child: Container(
+            child: Table(
+              defaultVerticalAlignment: TableCellVerticalAlignment.middle,
+              columnWidths: {0: FlexColumnWidth(4), 1: FlexColumnWidth(2)},
+              children: [
+                TableRow(
+                  children: [
+                    Container(
+                      padding: const EdgeInsets.all(8.5),
+                      decoration:
+                          BoxDecoration(border: Border.all(color: Colors.grey)),
+                      child: Text(
+                        title,
+                        style: TextStyle(
+                            fontWeight: FontWeight.bold, fontSize: 18),
+                      ),
+                    ),
+                    Container(
+                      padding: const EdgeInsets.all(8.5),
+                      decoration:
+                          BoxDecoration(border: Border.all(color: Colors.grey)),
+                      child: Row(
+                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                        children: [
+                          Text(
+                            total != null
+                                ? "\$${total.toStringAsFixed(2)}"
+                                : "",
+                            style: TextStyle(
+                                fontWeight: FontWeight.bold, fontSize: 18),
+                          ),
+                          Icon(isExpanded
+                              ? Icons.expand_less
+                              : Icons.expand_more),
+                        ],
+                      ),
+                    ),
+                  ],
+                ),
+              ],
+            ),
+          ),
+        ),
+        if (isExpanded)
+          Table(
+            defaultVerticalAlignment: TableCellVerticalAlignment.middle,
+            columnWidths: {0: FlexColumnWidth(4), 1: FlexColumnWidth(2)},
+            children: items.map((item) {
+              return TableRow(
+                children: [
+                  Container(
+                    padding: const EdgeInsets.all(8.5),
+                    decoration:
+                        BoxDecoration(border: Border.all(color: Colors.grey)),
+                    child: Text(" -${item.descripcion}",
+                        style: TextStyle(
+                            fontSize: 16, fontWeight: FontWeight.w600)),
+                  ),
+                  Container(
+                    padding: const EdgeInsets.all(8.5),
+                    decoration:
+                        BoxDecoration(border: Border.all(color: Colors.grey)),
+                    child: Text(
+                      "\$${item.monto?.toStringAsFixed(2)}",
+                      style:
+                          TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
+                    ),
+                  ),
+                ],
+              );
+            }).toList(),
+          ),
+      ],
+    );
+  }
+}

File diff suppressed because it is too large
+ 1029 - 0
lib/views/corte_caja/corte_caja_form.dart


+ 403 - 0
lib/views/corte_caja/corte_caja_screen.dart

@@ -0,0 +1,403 @@
+import '../../views/corte_caja/corte_caja_form.dart';
+import '../../widgets/widgets.dart';
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+import 'package:provider/provider.dart';
+import '../../themes/themes.dart';
+import '../../models/models.dart';
+import '../../viewmodels/viewmodels.dart';
+import '../../widgets/widgets_components.dart' as clase;
+import 'corte_caja_finalizado_screen.dart';
+
+class CorteCajaScreen extends StatefulWidget {
+  const CorteCajaScreen({Key? key}) : super(key: key);
+
+  @override
+  State<CorteCajaScreen> createState() => _CorteCajaScreenState();
+}
+
+class _CorteCajaScreenState extends State<CorteCajaScreen> {
+  final _busqueda = TextEditingController(text: '');
+  DateTime? fechaInicio;
+  DateTime? fechaFin;
+  ScrollController horizontalScrollController = ScrollController();
+
+  @override
+  void initState() {
+    super.initState();
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      Provider.of<CorteCajaViewModel>(context, listen: false).fetchCortes();
+    });
+  }
+
+  void clearSearchAndReset() {
+    setState(() {
+      _busqueda.clear();
+      fechaInicio = null;
+      fechaFin = null;
+      Provider.of<CorteCajaViewModel>(context, listen: false).fetchCortes();
+    });
+  }
+
+  void go(CorteCaja item) async {
+    final viewModel = Provider.of<CorteCajaViewModel>(context, listen: false);
+    viewModel.selectCorte(item);
+
+    await Navigator.push(
+      context,
+      MaterialPageRoute(
+        builder: (context) => item.fechaCorte != null
+            ? CorteCajaFinalizadoScreen(corteCajaId: item.id)
+            : CorteCajaForm(corteCajaId: item.id),
+      ),
+    ).then((_) => viewModel.fetchCortes());
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final pvm = Provider.of<CorteCajaViewModel>(context);
+    double screenWidth = MediaQuery.of(context).size.width;
+    final isMobile = screenWidth < 1250;
+    final double? columnSpacing = isMobile ? null : 0;
+    TextStyle estilo = const TextStyle(fontWeight: FontWeight.bold);
+    List<DataRow> registros = [];
+
+    for (CorteCaja item in pvm.cortes) {
+      registros.add(DataRow(cells: [
+        DataCell(
+            Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+          PopupMenuButton(
+            itemBuilder: (context) => [
+              PopupMenuItem(
+                child: const Text('Editar'),
+                onTap: () => go(item),
+              ),
+              if (item.fechaCorte != null)
+                PopupMenuItem(
+                  child: const Text('Imprimir Ticket'),
+                  onTap: () async {
+                    final viewModel =
+                        Provider.of<CorteCajaViewModel>(context, listen: false);
+                    viewModel.selectCorte(item);
+                    await viewModel.imprimirCorteCajaTicket(
+                        corteCajaId: item.id);
+                  },
+                ),
+            ],
+            icon: const Icon(Icons.more_vert),
+          ),
+        ])),
+        DataCell(
+          Text(item.fechaApertura != null
+              ? DateFormat("dd/MM/yyyy HH:mm:ss")
+                  .format(item.fechaApertura!.toLocal())
+              : "Sin fecha"),
+          onTap: () => go(item),
+        ),
+        DataCell(
+          Text(item.fechaCorte != null
+              ? DateFormat("dd/MM/yyyy HH:mm:ss")
+                  .format(item.fechaCorte!.toLocal())
+              : "Sin fecha"),
+          onTap: () => go(item),
+        ),
+        DataCell(
+          Text('\$${formatoMiles(item.corteFinal)}'),
+          onTap: () => go(item),
+        ),
+      ]));
+    }
+
+    return Scaffold(
+      appBar: AppBar(
+          title: Text(
+            'Corte De Caja',
+            style: TextStyle(
+                color: AppTheme.secondary, fontWeight: FontWeight.w500),
+          ),
+          iconTheme: IconThemeData(color: AppTheme.secondary)),
+      floatingActionButton: FloatingActionButton.extended(
+        onPressed: () async {
+          final viewModel =
+              Provider.of<CorteCajaViewModel>(context, listen: false);
+
+          if (viewModel.hasOpenCorteCaja()) {
+            alerta(context,
+                etiqueta:
+                    'No se puede crear un nuevo corte hasta que el anterior esté cerrado.');
+            return;
+          }
+
+          String? nuevoCorteId = await viewModel.createCorteCaja();
+
+          await viewModel.fetchCortes();
+
+          await Navigator.push(
+            context,
+            MaterialPageRoute(
+              builder: (context) => CorteCajaForm(corteCajaId: nuevoCorteId),
+            ),
+          ).then((_) => viewModel.fetchCortes());
+        },
+        icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
+        label: Text(
+          "Nuevo Corte De Caja",
+          style: TextStyle(fontSize: 20, color: AppTheme.quaternary),
+        ),
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.circular(8),
+        ),
+        materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+        backgroundColor: AppTheme.secondary,
+        foregroundColor: AppTheme.quaternary,
+      ),
+      body: Column(
+        children: [
+          Expanded(
+            child: ListView(
+              padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
+              children: [
+                const SizedBox(height: 8),
+                clase.tarjeta(
+                  Padding(
+                    padding: const EdgeInsets.all(8.0),
+                    child: LayoutBuilder(
+                      builder: (context, constraints) {
+                        if (screenWidth > 1000) {
+                          return Row(
+                            crossAxisAlignment: CrossAxisAlignment.end,
+                            children: [
+                              Expanded(
+                                flex: 7,
+                                child: _buildDateRangePicker(),
+                              ),
+                              SizedBox(width: 5),
+                              botonBuscar()
+                            ],
+                          );
+                        } else {
+                          return Column(
+                            children: [
+                              Row(
+                                children: [_buildDateRangePicker()],
+                              ),
+                              SizedBox(height: 15),
+                              Row(
+                                children: [botonBuscar()],
+                              ),
+                            ],
+                          );
+                        }
+                      },
+                    ),
+                  ),
+                ),
+                const SizedBox(height: 8),
+                pvm.isLoading
+                    ? const Center(child: CircularProgressIndicator())
+                    : Container(),
+                clase.tarjeta(
+                  Column(
+                    children: [
+                      LayoutBuilder(builder: (context, constraints) {
+                        return SingleChildScrollView(
+                          scrollDirection: Axis.vertical,
+                          child: Scrollbar(
+                            controller: horizontalScrollController,
+                            interactive: true,
+                            thumbVisibility: true,
+                            thickness: 10.0,
+                            child: SingleChildScrollView(
+                              controller: horizontalScrollController,
+                              scrollDirection: Axis.horizontal,
+                              child: ConstrainedBox(
+                                constraints: BoxConstraints(
+                                    minWidth: isMobile
+                                        ? constraints.maxWidth
+                                        : screenWidth),
+                                child: DataTable(
+                                  columnSpacing: columnSpacing,
+                                  sortAscending: true,
+                                  sortColumnIndex: 1,
+                                  columns: [
+                                    DataColumn(label: Text(" ", style: estilo)),
+                                    DataColumn(
+                                        label: Text("Fecha Apertura",
+                                            style: estilo)),
+                                    DataColumn(
+                                        label: Text("Fecha Cierre",
+                                            style: estilo)),
+                                    DataColumn(
+                                        label: Text("Efectivo En Caja",
+                                            style: estilo)),
+                                  ],
+                                  rows: registros,
+                                ),
+                              ),
+                            ),
+                          ),
+                        );
+                      }),
+                    ],
+                  ),
+                ),
+                const SizedBox(height: 15),
+                if (!pvm.isLoading)
+                  Row(
+                    mainAxisAlignment: MainAxisAlignment.center,
+                    children: [
+                      TextButton(
+                        onPressed:
+                            pvm.currentPage > 1 ? pvm.previousPage : null,
+                        child: Text('Anterior'),
+                        style: ButtonStyle(
+                          backgroundColor:
+                              MaterialStateProperty.resolveWith<Color?>(
+                            (Set<MaterialState> states) {
+                              if (states.contains(MaterialState.disabled)) {
+                                return Colors.grey;
+                              }
+                              return AppTheme.primary;
+                            },
+                          ),
+                          foregroundColor:
+                              MaterialStateProperty.resolveWith<Color?>(
+                            (Set<MaterialState> states) {
+                              if (states.contains(MaterialState.disabled)) {
+                                return Colors.black;
+                              }
+                              return Colors.white;
+                            },
+                          ),
+                        ),
+                      ),
+                      SizedBox(width: 15),
+                      Text('Página ${pvm.currentPage} de ${pvm.totalPages}'),
+                      SizedBox(width: 15),
+                      TextButton(
+                        onPressed: pvm.currentPage < pvm.totalPages
+                            ? pvm.nextPage
+                            : null,
+                        child: Text('Siguiente'),
+                        style: ButtonStyle(
+                          backgroundColor:
+                              MaterialStateProperty.resolveWith<Color?>(
+                            (Set<MaterialState> states) {
+                              if (states.contains(MaterialState.disabled)) {
+                                return Colors.grey;
+                              }
+                              return AppTheme.primary;
+                            },
+                          ),
+                          foregroundColor:
+                              MaterialStateProperty.resolveWith<Color?>(
+                            (Set<MaterialState> states) {
+                              if (states.contains(MaterialState.disabled)) {
+                                return Colors.black;
+                              }
+                              return Colors.white;
+                            },
+                          ),
+                        ),
+                      ),
+                    ],
+                  ),
+                const SizedBox(height: 15),
+              ],
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildDateRangePicker() {
+    return Row(
+      children: [
+        Expanded(
+          flex: 3,
+          child: clase.FechaSelectWidget(
+            fecha: fechaInicio,
+            onFechaChanged: (d) {
+              setState(() {
+                fechaInicio = d;
+              });
+            },
+            etiqueta: "Fecha Inicial",
+            context: context,
+          ),
+        ),
+        const SizedBox(width: 5),
+        Expanded(
+          flex: 3,
+          child: clase.FechaSelectWidget(
+            fecha: fechaFin,
+            onFechaChanged: (d) {
+              setState(() {
+                fechaFin = d;
+              });
+            },
+            etiqueta: "Fecha Final",
+            context: context,
+          ),
+        ),
+      ],
+    );
+  }
+
+  Widget botonBuscar() {
+    return Expanded(
+        flex: 2,
+        child: Row(
+          children: [
+            Expanded(
+              flex: 2,
+              child: Padding(
+                padding: const EdgeInsets.only(top: 0),
+                child: ElevatedButton(
+                  onPressed: clearSearchAndReset,
+                  style: ElevatedButton.styleFrom(
+                    shape: RoundedRectangleBorder(
+                      borderRadius: BorderRadius.circular(20.0),
+                    ),
+                    backgroundColor: Colors.redAccent,
+                    padding: const EdgeInsets.symmetric(vertical: 25),
+                  ),
+                  child: Text('Limpiar',
+                      style: TextStyle(color: AppTheme.quaternary)),
+                ),
+              ),
+            ),
+            const SizedBox(width: 8),
+            Expanded(
+              flex: 2,
+              child: Padding(
+                padding: const EdgeInsets.only(top: 0),
+                child: ElevatedButton(
+                  onPressed: () async {
+                    if (fechaInicio != null && fechaFin != null) {
+                      await Provider.of<CorteCajaViewModel>(context,
+                              listen: false)
+                          .buscarPorFecha(fechaInicio!, fechaFin!);
+                    } else {
+                      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
+                          content: Text(
+                              'Selecciona un rango de fechas para buscar.')));
+                    }
+                  },
+                  style: ElevatedButton.styleFrom(
+                    shape: RoundedRectangleBorder(
+                      borderRadius: BorderRadius.circular(20.0),
+                    ),
+                    backgroundColor: AppTheme.tertiary,
+                    padding: const EdgeInsets.symmetric(vertical: 25),
+                  ),
+                  child: Text('Buscar',
+                      style: TextStyle(color: AppTheme.quaternary)),
+                ),
+              ),
+            ),
+          ],
+        ));
+  }
+}

+ 65 - 0
lib/views/corte_caja/corte_caja_ticket.dart

@@ -0,0 +1,65 @@
+import 'package:intl/intl.dart';
+import 'package:pdf/pdf.dart';
+import 'package:pdf/widgets.dart' as pw;
+import 'package:printing/printing.dart';
+
+class CorteCajaTicket {
+  static Future<void> imprimirTicket({
+    required double fondo,
+    required double fondoDiaSig,
+    required double ventaEfe,
+    required double ventaTarj,
+    required double ventaTrans,
+    required double deposito,
+    required double retiro,
+    required double gasto,
+    required double corteFinal,
+    DateTime? fechaApertura,
+  }) async {
+    final pdf = pw.Document();
+    final numberFormat = NumberFormat('#,##0.00', 'en_US');
+
+    pdf.addPage(pw.Page(
+      pageFormat: PdfPageFormat.roll57,
+      build: (pw.Context context) {
+        return pw.Column(
+          crossAxisAlignment: pw.CrossAxisAlignment.start,
+          children: [
+            pw.Text(
+              "Fecha: ${DateFormat('dd/MM/yyyy').format(fechaApertura!)}",
+              style: pw.TextStyle(fontSize: 11, fontWeight: pw.FontWeight.bold),
+            ),
+            _crearLinea("Fondo Inicial:", numberFormat.format(fondo)),
+            _crearLinea("Fondo Siguiente:", numberFormat.format(fondoDiaSig)),
+            _crearLinea("Ventas Efe:", numberFormat.format(ventaEfe)),
+            _crearLinea("Ventas Tarj:", numberFormat.format(ventaTarj)),
+            _crearLinea("Venta Trans:", numberFormat.format(ventaTrans)),
+            _crearLinea("Depositos:", numberFormat.format(deposito)),
+            _crearLinea("Retiros:", numberFormat.format(retiro)),
+            _crearLinea("Gastos:", numberFormat.format(gasto)),
+            pw.Divider(),
+            _crearLinea("Venta Total del día:",
+                numberFormat.format(ventaEfe + ventaTarj + ventaTrans)),
+            _crearLinea("Efectivo en caja:", numberFormat.format(corteFinal)),
+            _crearLinea(
+                "Salidas totales día:", numberFormat.format(retiro + gasto)),
+          ],
+        );
+      },
+    ));
+
+    await Printing.layoutPdf(
+      onLayout: (PdfPageFormat format) async => pdf.save(),
+    );
+  }
+
+  static pw.Widget _crearLinea(String texto, String valor) {
+    return pw.Row(
+      mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
+      children: [
+        pw.Text(texto, style: pw.TextStyle(fontSize: 10)),
+        pw.Text("\$$valor", style: pw.TextStyle(fontSize: 10)),
+      ],
+    );
+  }
+}

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

@@ -33,6 +33,8 @@ class Formulario extends State<HomeScreen> {
       if (permisoViewModel.userPermisos.isEmpty) {
         permisoViewModel.fetchUserPermisos();
       }
+
+      Provider.of<CorteCajaViewModel>(context, listen: false).fetchCortes();
     });
   }
 
@@ -50,7 +52,7 @@ class Formulario extends State<HomeScreen> {
       ),
       body: const Center(
         child: Image(
-          image: AssetImage('assets/JoshiLogo.png'),
+          image: AssetImage('assets/logo.png'),
           height: 200,
         ),
       ),

+ 1 - 1
lib/views/login/login_screen.dart

@@ -92,7 +92,7 @@ class _LoginScreenState extends State<LoginScreen> {
             children: [
               SizedBox(height: size.width < 1200 ? 20 : 100),
               const Image(
-                image: AssetImage('assets/JoshiLogoHorizontal.png'),
+                image: AssetImage('assets/logo.png'),
                 height: 250,
               ),
               const Text(

File diff suppressed because it is too large
+ 364 - 560
lib/views/pedido/pedido_form.dart


+ 17 - 9
lib/views/pedido/pedido_screen.dart

@@ -47,7 +47,7 @@ class _PedidoScreenState extends State<PedidoScreen> {
     }
 
     if (pedidosConProductos.isNotEmpty) {
-      String fileName = 'Pedidos_JoshiPapas_POS';
+      String fileName = 'Pedidos_Turquessa_POS';
       if (fechaInicio != null && fechaFin != null) {
         String startDateStr = DateFormat('dd-MM-yyyy').format(fechaInicio!);
         String endDateStr = DateFormat('dd-MM-yyyy').format(fechaFin!);
@@ -405,14 +405,22 @@ class _PedidoScreenState extends State<PedidoScreen> {
             child: FloatingActionButton.extended(
               heroTag: 'addPedido',
               onPressed: () async {
-                await Navigator.push(
-                  context,
-                  MaterialPageRoute(
-                    builder: (context) => PedidoForm(),
-                  ),
-                ).then((_) =>
-                    Provider.of<PedidoViewModel>(context, listen: false)
-                        .fetchLocalPedidosForScreen());
+                final corteCajaViewModel =
+                    Provider.of<CorteCajaViewModel>(context, listen: false);
+                if (corteCajaViewModel.hasOpenCorteCaja()) {
+                  await Navigator.push(
+                    context,
+                    MaterialPageRoute(
+                      builder: (context) => PedidoForm(),
+                    ),
+                  ).then((_) =>
+                      Provider.of<PedidoViewModel>(context, listen: false)
+                          .fetchLocalPedidosForScreen());
+                } else {
+                  alerta(context,
+                      etiqueta:
+                          'Solo se pueden realizar pedidos cuando se encuentre la caja abierta.');
+                }
               },
               icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
               label: Text(

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

@@ -16,7 +16,7 @@ Future<void> imprimirTicketsJuntos(BuildContext context, Pedido pedido) async {
           .isVariableActive('ticket_cocina');
   final pdf = pw.Document();
   final image = pw.MemoryImage(
-    (await rootBundle.load('assets/JoshiLogo-BN.png')).buffer.asUint8List(),
+    (await rootBundle.load('assets/logo.png')).buffer.asUint8List(),
   );
 
   pdf.addPage(
@@ -118,15 +118,13 @@ pw.Page generarPaginaPrimerTicket(Pedido pedido, pw.MemoryImage image) {
                   child: pw.Column(children: [
                     pw.Padding(
                         padding: pw.EdgeInsets.only(left: 10),
-                        child: pw.Text('Joshi Papas Tu Sabor tu Estilo',
+                        child: pw.Text('Turquessa Coffee',
                             style: pw.TextStyle(
                                 fontSize: 12, fontWeight: pw.FontWeight.bold))),
                     pw.SizedBox(height: 10),
                     pw.Text('Fecha: ${pedido.peticion}',
                         style: const pw.TextStyle(fontSize: 9)),
-                    pw.Text('JoshiPapas',
-                        style: const pw.TextStyle(fontSize: 9)),
-                    pw.Text('Chihuahua',
+                    pw.Text('Hermosillo',
                         style: const pw.TextStyle(fontSize: 9)),
                   ])),
               pw.SizedBox(height: 10),

+ 2 - 2
lib/views/producto/producto_imagen.dart

@@ -9,7 +9,7 @@ import 'package:file_picker/file_picker.dart';
 Future<String?> pickAndStoreImage(File file, int productId) async {
   Directory appDocDir = await getApplicationDocumentsDirectory();
   String baseDirPath =
-      path.join(appDocDir.path, 'JoshiPos', 'media', 'producto');
+      path.join(appDocDir.path, 'TurquessaPos', 'media', 'producto');
   await Directory(baseDirPath).create(recursive: true);
   String fileExtension = path.extension(file.path);
   String newFileName =
@@ -26,7 +26,7 @@ Future<String?> downloadAndStoreImage(
     String imageUrl, int productId, String imageName) async {
   Directory appDocDir = await getApplicationDocumentsDirectory();
   String baseDirPath =
-      path.join(appDocDir.path, 'JoshiPos', 'media', 'producto');
+      path.join(appDocDir.path, 'TurquessaPos', 'media', 'producto');
   await Directory(baseDirPath).create(recursive: true);
 
   String fileExtension = path.extension(imageUrl);

+ 15 - 2
lib/widgets/app_drawer.dart

@@ -13,6 +13,7 @@ import '../services/services.dart';
 import '../themes/themes.dart';
 import '../viewmodels/login_view_model.dart';
 import '../viewmodels/viewmodels.dart';
+import '../views/corte_caja/corte_caja_screen.dart';
 import '../views/descuento/descuento_screen.dart';
 import 'widgets_components.dart';
 
@@ -72,7 +73,7 @@ class AppDrawer extends StatelessWidget {
                 Padding(
                   padding: EdgeInsets.all(8.0),
                   child: Image(
-                    image: AssetImage('assets/JoshiLogoHorizontal.png'),
+                    image: AssetImage('assets/logo-BN.png'),
                     height: 150,
                   ),
                 ),
@@ -145,6 +146,18 @@ class AppDrawer extends StatelessWidget {
                     title: const Text('Administración'),
                     children: [
                       ListTile(
+                        leading: circulo(const Icon(Icons.point_of_sale)),
+                        title: const Text('Corte de caja'),
+                        onTap: () => {
+                          Navigator.pop(context),
+                          Navigator.of(context).push(
+                            MaterialPageRoute(
+                              builder: (context) => CorteCajaScreen(),
+                            ),
+                          ),
+                        },
+                      ),
+                      ListTile(
                         leading: circulo(const Icon(Icons.discount)),
                         title: const Text('Descuentos'),
                         onTap: () => {
@@ -316,7 +329,7 @@ class AppDrawer extends StatelessWidget {
             child: Align(
               alignment: Alignment.bottomCenter,
               child: Text(
-                '$prefijoVersion.1.24.10.28',
+                '$prefijoVersion.1.24.11.13',
                 style: const TextStyle(fontWeight: FontWeight.w300),
               ),
             ),

+ 68 - 29
lib/widgets/app_textfield.dart

@@ -2,9 +2,11 @@
 
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
+import 'package:intl/intl.dart';
 import '../themes/themes.dart';
 
-class AppTextField extends StatelessWidget {
+class AppTextField extends StatefulWidget {
+  final bool separarMiles;
   final Icon? prefixIcon;
   final Widget? suffixIcon;
   final String? labelText;
@@ -32,6 +34,7 @@ class AppTextField extends StatelessWidget {
 
   AppTextField({
     super.key,
+    this.separarMiles = false,
     this.etiqueta,
     this.labelText,
     this.prefixIcon,
@@ -58,6 +61,13 @@ class AppTextField extends StatelessWidget {
     this.onSubmitted,
   });
 
+  @override
+  _AppTextFieldState createState() => _AppTextFieldState();
+}
+
+class _AppTextFieldState extends State<AppTextField> {
+  bool isFirst = true;
+
   double _getFontSize(BuildContext context) {
     double screenWidth = MediaQuery.of(context).size.width;
     if (screenWidth < 480) {
@@ -71,47 +81,76 @@ class AppTextField extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     double fontSize = _getFontSize(context);
-    etiqueta ??= "";
+    widget.etiqueta ??= "";
+
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
-        if (etiqueta != '')
+        if (widget.etiqueta != '')
           Text(
-            etiqueta.toString(),
+            widget.etiqueta!,
             style: TextStyle(
                 fontSize: fontSize,
                 fontWeight: FontWeight.bold,
-                color: enabled ? Colors.black : Colors.grey),
+                color: widget.enabled ? Colors.black : Colors.grey[700]),
           ),
-        if (etiqueta != '')
+        if (widget.etiqueta != '')
           const SizedBox(
             height: 5,
           ),
         TextFormField(
-          onFieldSubmitted: onSubmitted,
-          autofillHints: autofillHints,
-          validator: validator,
-          enabled: enabled,
+          onFieldSubmitted: widget.onSubmitted,
+          autofillHints: widget.autofillHints,
+          validator: widget.validator,
+          enabled: widget.enabled,
           style: TextStyle(
             fontSize: fontSize,
           ),
-          maxLength: maxLength,
-          maxLines: maxLines,
-          inputFormatters: inputFormatters,
-          initialValue: initialValue,
-          controller: controller,
-          onTap: onTap,
-          readOnly: readOnly,
-          keyboardType: keyboardType,
-          textCapitalization: textCapitalization,
+          maxLength: widget.maxLength,
+          maxLines: widget.maxLines,
+          inputFormatters: widget.inputFormatters ??
+              (widget.separarMiles
+                  ? [FilteringTextInputFormatter.digitsOnly]
+                  : null),
+          initialValue: widget.initialValue,
+          controller: widget.controller,
+          onTap: widget.onTap,
+          readOnly: widget.readOnly,
+          keyboardType: widget.keyboardType,
+          textCapitalization: widget.textCapitalization,
           cursorColor: AppTheme.tertiary,
-          obscureText: obscureText,
+          obscureText: widget.obscureText,
           autocorrect: true,
-          onChanged: onChanged,
+          onChanged: (value) {
+            if (widget.separarMiles) {
+              String newValue = value.replaceAll(',', '').replaceAll('.', '');
+
+              if (value.isEmpty || newValue == '00') {
+                widget.controller?.clear();
+                isFirst = true;
+                return;
+              }
+
+              double value1 = double.parse(newValue);
+
+              if (!isFirst) value1 = value1 * 100;
+              value = NumberFormat.currency(customPattern: '###,###.##')
+                  .format(value1 / 100);
+
+              widget.controller?.value = TextEditingValue(
+                text: value,
+                selection: TextSelection.collapsed(offset: value.length),
+              );
+            }
+
+            if (widget.onChanged != null) {
+              widget.onChanged!(value);
+            }
+          },
           decoration: InputDecoration(
-            contentPadding: vertical == null
+            contentPadding: widget.vertical == null
                 ? null
-                : EdgeInsets.symmetric(vertical: vertical!),
+                : EdgeInsets.symmetric(vertical: widget.vertical!),
             focusedBorder: OutlineInputBorder(
               borderRadius: BorderRadius.circular(10),
               borderSide: BorderSide(
@@ -142,18 +181,18 @@ class AppTextField extends StatelessWidget {
                 color: Colors.grey[200]!,
               ),
             ),
-            errorText: errorText,
-            labelText: labelText,
-            hintText: hintText,
+            errorText: widget.errorText,
+            labelText: widget.labelText,
+            hintText: widget.hintText,
             floatingLabelStyle: TextStyle(
               color: AppTheme.tertiary,
               fontSize: fontSize,
             ),
             filled: true,
-            fillColor: fillColor,
-            prefixIcon: prefixIcon,
+            fillColor: widget.fillColor,
+            prefixIcon: widget.prefixIcon,
             prefixIconColor: AppTheme.tertiary,
-            suffixIcon: suffixIcon,
+            suffixIcon: widget.suffixIcon,
             suffixIconColor: AppTheme.tertiary,
             border: OutlineInputBorder(
               borderRadius: BorderRadius.circular(15),

+ 62 - 4
lib/widgets/widgets_components.dart

@@ -49,11 +49,11 @@ encabezado(
       backgroundColor: backgroundColor,
       centerTitle: centerTitle,
       title: titulo == null
-          ? Image.asset("assets/JoshiLogoHorizontal.png", width: 100)
+          ? Image.asset("assets/logo.png", width: 100)
           : Text(titulo.toString(), style: estilo),
       actions: [
         ...acciones,
-        Image.asset("assets/JoshiLogoHorizontal.png", width: 100),
+        Image.asset("assets/logo.png", width: 100),
       ],
       bottom: bottom);
 }
@@ -94,7 +94,7 @@ class Cargando extends StatelessWidget {
           child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
             Padding(
                 padding: const EdgeInsets.fromLTRB(16, 0, 16, 10),
-                child: Image.asset("assets/JoshiLogo.png", height: 200)),
+                child: Image.asset("assets/logo.png", height: 200)),
             const CircularProgressIndicator(backgroundColor: Colors.grey),
             Container(margin: const EdgeInsets.only(bottom: 8.0)),
             const Text("Cargando contenido...",
@@ -330,10 +330,16 @@ alerta(BuildContext context, {String? etiqueta = "Capturar búsqueda"}) {
             Row(children: [
               Expanded(
                   child: TextButton(
+                style: ButtonStyle(
+                    backgroundColor:
+                        MaterialStatePropertyAll(AppTheme.tertiary)),
                 onPressed: () async {
                   Navigator.pop(context);
                 },
-                child: const Text('Continuar'),
+                child: Text(
+                  'Continuar',
+                  style: TextStyle(color: AppTheme.quaternary),
+                ),
               ))
             ])
           ],
@@ -341,6 +347,53 @@ alerta(BuildContext context, {String? etiqueta = "Capturar búsqueda"}) {
       });
 }
 
+cuadroConfirmacion(BuildContext context,
+    {String? etiqueta = "¿Estás seguro?", required VoidCallback onConfirm}) {
+  return showDialog(
+    context: context,
+    builder: (context) {
+      return AlertDialog(
+        title: Text(etiqueta.toString()),
+        actions: [
+          Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              Expanded(
+                child: TextButton(
+                  style: ButtonStyle(
+                      backgroundColor: MaterialStatePropertyAll(Colors.red)),
+                  onPressed: () {
+                    Navigator.pop(context);
+                  },
+                  child: const Text(
+                    'No',
+                    style: TextStyle(color: Colors.white),
+                  ),
+                ),
+              ),
+              SizedBox(width: 10),
+              Expanded(
+                child: TextButton(
+                  style: ButtonStyle(
+                      backgroundColor: MaterialStatePropertyAll(Colors.green)),
+                  onPressed: () {
+                    onConfirm();
+                    Navigator.pop(context);
+                  },
+                  child: const Text(
+                    'Sí',
+                    style: TextStyle(color: Colors.white),
+                  ),
+                ),
+              ),
+            ],
+          ),
+        ],
+      );
+    },
+  );
+}
+
 botonElevated(
     {Function()? accion,
     String? titulo = "Buscar",
@@ -757,3 +810,8 @@ Future eliminarMedia(
     },
   );
 }
+
+String formatoMiles(double? value) {
+  final formatter = NumberFormat('#,##0.00', 'en_US');
+  return formatter.format(value);
+}