瀏覽代碼

Descuentos, tipos de pago y variables

OscarGil03 8 月之前
父節點
當前提交
663978bdeb

+ 2 - 0
lib/main.dart

@@ -43,6 +43,8 @@ void main() async {
       ChangeNotifierProvider(create: (_) => MediaViewModel()),
       ChangeNotifierProvider(create: (_) => TopingViewModel()),
       ChangeNotifierProvider(create: (_) => PedidoViewModel()),
+      ChangeNotifierProvider(create: (_) => DescuentoViewModel()),
+      ChangeNotifierProvider(create: (_) => VariableViewModel()),
       // Agrega aquí cualquier otro provider que necesites
     ], child: const MyApp()));
   });

+ 20 - 0
lib/models/descuento_model.dart

@@ -0,0 +1,20 @@
+class Descuento {
+  int? id;
+  int porcentaje;
+
+  Descuento({this.id, required this.porcentaje});
+
+  factory Descuento.fromJson(Map<String, dynamic> json) {
+    return Descuento(
+      id: json['id'],
+      porcentaje: json['porcentaje'],
+    );
+  }
+
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'porcentaje': porcentaje,
+    };
+  }
+}

+ 2 - 0
lib/models/models.dart

@@ -16,3 +16,5 @@ export '../models/item_carrito_model.dart';
 export '../models/gasto_model.dart';
 export '../models/deposito_model.dart';
 export '../models/corte_caja_model.dart';
+export '../models/descuento_model.dart';
+export '../models/variable_model.dart';

+ 21 - 1
lib/models/pedido_model.dart

@@ -9,6 +9,7 @@ class Pedido extends Basico {
   String? peticion;
   String? nombreCliente;
   double? totalPedido;
+  int? descuento;
   int? idCliente;
   int? idMesa;
   String? terminado;
@@ -16,6 +17,10 @@ class Pedido extends Basico {
   int? idUsuario;
   int? idModificador;
   int? idCancelado;
+  String? tipoPago;
+  double? cantEfectivo;
+  double? cantTarjeta;
+  double? cantTransferencia;
   List<PedidoProducto> productos = [];
 
   Pedido({
@@ -26,6 +31,7 @@ class Pedido extends Basico {
     this.peticion,
     this.nombreCliente,
     this.totalPedido,
+    this.descuento,
     this.idCliente,
     this.idMesa,
     this.terminado,
@@ -33,6 +39,10 @@ class Pedido extends Basico {
     this.idUsuario,
     this.idModificador,
     this.idCancelado,
+    this.tipoPago,
+    this.cantEfectivo,
+    this.cantTarjeta,
+    this.cantTransferencia,
     this.productos = const [],
   });
 
@@ -46,6 +56,7 @@ class Pedido extends Basico {
       'peticion': peticion,
       'nombreCliente': nombreCliente,
       'totalPedido': totalPedido,
+      'descuento': descuento,
       'idCliente': idCliente,
       'idMesa': idMesa,
       'terminado': terminado,
@@ -53,7 +64,10 @@ class Pedido extends Basico {
       'idUsuario': idUsuario,
       'idModificador': idModificador,
       'idCancelado': idCancelado,
-      //'productos': productos.map((producto) => producto.toJson()).toList(),
+      'tipoPago': tipoPago,
+      'cantEfectivo': cantEfectivo,
+      'cantTarjeta': cantTarjeta,
+      'cantTransferencia': cantTransferencia,
     }..addAll(super.toJson());
   }
 
@@ -66,6 +80,7 @@ class Pedido extends Basico {
     peticion = Basico.parseString(json['peticion']);
     nombreCliente = Basico.parseString(json['nombreCliente']);
     totalPedido = Basico.parseDouble(json['totalPedido']);
+    descuento = Basico.parseInt(json['descuento']);
     idCliente = Basico.parseInt(json['idCliente']);
     idMesa = Basico.parseInt(json['idMesa']);
     terminado = Basico.parseString(json['terminado']);
@@ -73,6 +88,11 @@ class Pedido extends Basico {
     idUsuario = Basico.parseInt(json['idUsuario']);
     idModificador = Basico.parseInt(json['idModificador']);
     idCancelado = Basico.parseInt(json['idCancelado']);
+    tipoPago = Basico.parseString(json['tipoPago']);
+    cantEfectivo = Basico.parseDouble(json['cantEfectivo']);
+    cantTarjeta = Basico.parseDouble(json['cantTarjeta']);
+    cantTransferencia = Basico.parseDouble(json['cantTransferencia']);
+
     List<PedidoProducto> _productos = [];
     if (json["productos"] != null && (json["productos"] as List).isNotEmpty) {
       for (var i in (json["productos"] as List)) {

+ 40 - 0
lib/models/variable_model.dart

@@ -0,0 +1,40 @@
+import 'basico_model.dart';
+import '../services/services.dart';
+
+class Variable extends Basico {
+  String? nombre;
+  String? clave;
+  String? descripcion;
+  bool? activo;
+
+  Variable({
+    super.id,
+    this.nombre,
+    this.clave,
+    this.descripcion,
+    this.activo = true,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'nombre': nombre,
+      'clave': clave,
+      'descripcion': descripcion,
+      'activo': activo == true ? 1 : 0,
+    }..addAll(super.toJson());
+  }
+
+  Variable.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    nombre = Basico.parseString(json['nombre']);
+    clave = Basico.parseString(json['clave']);
+    descripcion = Basico.parseString(json['descripcion']);
+    activo = Basico.parseInt(json['activo']) == 1;
+  }
+
+  Future<void> guardar() async {
+    idLocal = await RepoService().guardar(this);
+  }
+}

+ 133 - 13
lib/services/repo_service.dart

@@ -6,7 +6,7 @@ import 'package:sqflite/sqflite.dart';
 import '../models/models.dart';
 
 class RepoService<T> {
-  static int dbVersion = 4;
+  static int dbVersion = 8;
   static String dbName = 'joshipos026.db';
   static const String id = Basico.identificadorWeb;
   static const String idLocal = Basico.identificadorLocal;
@@ -73,6 +73,26 @@ class RepoService<T> {
           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('''
@@ -84,6 +104,18 @@ class RepoService<T> {
         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
+      )
+    ''');
   }
 
   void _onUpgrade(Database db, int oldVersion, int newVersion) async {
@@ -257,6 +289,58 @@ class RepoService<T> {
             )
           ''');
           break;
+
+        case 4:
+          await db.execute('''
+          ALTER TABLE Pedido ADD COLUMN descuento INTEGER DEFAULT 0
+        ''');
+          break;
+
+        case 5:
+          await db.execute('''
+            CREATE TABLE IF NOT EXISTS 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});
+          break;
+
+        case 6:
+          await db.execute('''
+          ALTER TABLE Pedido ADD COLUMN tipoPago TEXT DEFAULT '';
+        ''');
+          await db.execute('''
+          ALTER TABLE Pedido ADD COLUMN cantEfectivo REAL DEFAULT 0;
+        ''');
+          await db.execute('''
+          ALTER TABLE Pedido ADD COLUMN cantTarjeta REAL DEFAULT 0;
+        ''');
+          await db.execute('''
+          ALTER TABLE Pedido ADD COLUMN cantTransferencia REAL DEFAULT 0;
+        ''');
+          break;
+
+        case 7:
+          await db.execute('''
+            CREATE TABLE Variable (
+              id INTEGER PRIMARY KEY AUTOINCREMENT,
+              nombre TEXT,
+              clave TEXT,
+              descripcion TEXT,
+              activo BOOLEAN,
+              idLocal INTEGER,
+              eliminado TEXT
+            )
+          ''');
+          break;
       }
       oldVersion++;
     }
@@ -267,10 +351,15 @@ class RepoService<T> {
       var dbClient = await db;
       String nombreTabla = model.runtimeType.toString();
       String modelo = json.encode(model, toEncodable: toEncodable);
+
+      print("Guardando en tabla: $nombreTabla");
+      print("Modelo serializado: $modelo");
+
       Map<String, dynamic> modelMap = json.decode(modelo);
 
       int id = 0;
       if (modelMap['id'] != null && modelMap['id'] != 0) {
+        print("Actualizando el registro con ID: ${modelMap['id']}");
         await dbClient!.update(
           nombreTabla,
           modelMap,
@@ -279,17 +368,14 @@ class RepoService<T> {
         );
         id = modelMap['id'];
       } else {
+        print("Insertando nuevo registro");
         modelMap.remove('id');
         id = await dbClient!.insert(nombreTabla, modelMap);
       }
 
-      if (model is Producto) {
-        await _guardarToppings(dbClient, id, model.topings);
-      }
-
       return id;
     } catch (e) {
-      print('Error al guardar en $T: $e');
+      print('Error al guardar en dynamic: $e');
       return 0;
     }
   }
@@ -356,6 +442,7 @@ class RepoService<T> {
   }
 
   dynamic toEncodable(dynamic item) {
+    print("Serializando objeto: $item");
     if (item is Pedido) {
       return item.toJson();
     } else if (item is PedidoProducto) {
@@ -366,6 +453,8 @@ class RepoService<T> {
       return item.toJson();
     } else if (item is CategoriaProducto) {
       return item.toJson();
+    } else if (item is Variable) {
+      return item.toJson();
     }
     throw UnsupportedError(
         'Type not supported for serialization: ${item.runtimeType}');
@@ -479,14 +568,19 @@ class RepoService<T> {
   Future<List<Pedido>> buscarPorFecha(
       DateTime startDate, DateTime endDate) async {
     var dbClient = await db;
-    String startDateString = DateFormat('dd-MM-yyyy').format(startDate);
-    String endDateString = DateFormat('dd-MM-yyyy 23:59:59').format(endDate);
 
-    List<Map<String, dynamic>> maps = await dbClient!.query(
-      'Pedido',
-      where: 'peticion BETWEEN ? AND ?',
-      whereArgs: [startDateString, endDateString],
-    );
+    String startDateString =
+        DateFormat('yyyy-MM-dd 00:00:00').format(startDate);
+    String endDateString = DateFormat('yyyy-MM-dd 23:59:59').format(endDate);
+
+    List<Map<String, dynamic>> maps = await dbClient!.rawQuery('''
+    SELECT * FROM Pedido 
+    WHERE 
+      (datetime(substr(peticion, 7, 4) || '-' || substr(peticion, 4, 2) || '-' || substr(peticion, 1, 2) || ' ' || substr(peticion, 12)) BETWEEN ? AND ?)
+    OR 
+      (datetime(substr(peticion, 7, 4) || '-' || substr(peticion, 1, 2) || '-' || substr(peticion, 4, 2) || ' ' || substr(peticion, 12)) BETWEEN ? AND ?)
+  ''', [startDateString, endDateString, startDateString, endDateString]);
+
     return maps.map((map) => Pedido.fromJson(map)).toList();
   }
 
@@ -509,4 +603,30 @@ class RepoService<T> {
     String tableName = T.toString();
     await dbClient!.delete(tableName, where: 'id = ?', whereArgs: [id]);
   }
+
+  Future<List<Descuento>> obtenerTodosDescuentos() async {
+    var dbClient = await db;
+    var result = await dbClient!.query('Descuento', orderBy: 'porcentaje ASC');
+    return result.map((map) => Descuento.fromJson(map)).toList();
+  }
+
+  Future<int> guardarDescuento(Descuento descuento) async {
+    var dbClient = await db;
+    if (descuento.id != null && descuento.id! > 0) {
+      return await dbClient!.update(
+        'Descuento',
+        descuento.toJson(),
+        where: 'id = ?',
+        whereArgs: [descuento.id],
+      );
+    } else {
+      return await dbClient!.insert('Descuento', descuento.toJson());
+    }
+  }
+
+  Future<int> eliminarDescuento(int id) async {
+    var dbClient = await db;
+    return await dbClient!
+        .delete('Descuento', where: 'id = ?', whereArgs: [id]);
+  }
 }

+ 23 - 0
lib/viewmodels/descuento_view_model.dart

@@ -0,0 +1,23 @@
+import '../models/models.dart';
+import '../services/repo_service.dart';
+import 'package:flutter/material.dart';
+
+class DescuentoViewModel extends ChangeNotifier {
+  List<Descuento> descuentos = [];
+  final RepoService repoService = RepoService<Descuento>();
+
+  Future<void> cargarDescuentos() async {
+    descuentos = await repoService.obtenerTodosDescuentos();
+    notifyListeners();
+  }
+
+  Future<void> guardarDescuento(Descuento descuento) async {
+    await repoService.guardarDescuento(descuento);
+    await cargarDescuentos();
+  }
+
+  Future<void> eliminarDescuento(int id) async {
+    await repoService.eliminarDescuento(id);
+    await cargarDescuentos();
+  }
+}

+ 17 - 0
lib/viewmodels/pedido_view_model.dart

@@ -127,6 +127,23 @@ class PedidoViewModel extends ChangeNotifier {
     notifyListeners();
   }
 
+  Future<List<Pedido>> fetchAllLocalPedidos() async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+    List<Pedido> allPedidos = await repoPedido.obtenerTodos();
+    setIsLoading(false);
+    return allPedidos;
+  }
+
+  Future<List<Pedido>> fetchPedidosPorFechaSinLimit(
+      DateTime startDate, DateTime endDate) async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+    List<Pedido> pedidos = await repoPedido.buscarPorFecha(startDate, endDate);
+    setIsLoading(false);
+    return pedidos;
+  }
+
   Future<Pedido?> fetchPedidoConProductos(int idPedido) async {
     RepoService<Pedido> repoPedido = RepoService<Pedido>();
     Pedido? pedido = await repoPedido.obtenerPorId(idPedido);

+ 121 - 0
lib/viewmodels/variable_view_model.dart

@@ -0,0 +1,121 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import '../services/services.dart';
+import '../models/models.dart';
+
+class VariableViewModel extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<Variable> _variables = [];
+  bool _isLoading = false;
+  Variable? _selectedVariable;
+
+  List<Variable> get variables => _variables;
+  Variable? get selectedVariable => _selectedVariable;
+  bool get isLoading => _isLoading;
+
+  int _currentPage = 1;
+  int _totalVariables = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalVariables => _totalVariables;
+  int get totalPages => (_totalVariables / _limit).ceil();
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  void selectVariable(Variable variable) {
+    _selectedVariable = variable;
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalAll({int page = 1}) async {
+    _currentPage = page;
+    var db = await RepoService().db;
+
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM Variable'));
+    _totalVariables = count ?? 0;
+
+    int offset = (_limit * (page - 1));
+
+    var query = await db.query('Variable',
+        orderBy: 'id asc', limit: _limit, offset: offset);
+    _variables = query.map((element) => Variable.fromJson(element)).toList();
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalByName({required String nombre}) async {
+    var db = await RepoService().db;
+    var query = await db!.query(
+      'Variable',
+      where: 'nombre LIKE "%$nombre%"',
+      orderBy: 'id asc',
+    );
+    List<Variable> aux = [];
+    for (var element in query) {
+      Variable variable = Variable.fromJson(element);
+      aux.add(variable);
+    }
+    _variables = aux;
+    notifyListeners();
+  }
+
+  Future<void> addVariable(Variable variable) async {
+    await RepoService().guardar(variable);
+    fetchLocalAll();
+  }
+
+  Future<void> updateVariable(Variable variable) async {
+    setIsLoading(true);
+    try {
+      await RepoService().guardar(variable);
+      fetchLocalAll();
+    } catch (e) {
+      print('Error updating variable: $e');
+    }
+    setIsLoading(false);
+  }
+
+  Future<void> deleteVariable(int id) async {
+    await RepoService().eliminar<Variable>(id);
+    fetchLocalAll();
+  }
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchLocalAll(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchLocalAll(page: _currentPage - 1);
+    }
+  }
+
+  Future<bool> isVariableActive(String clave) async {
+    var db = await RepoService().db;
+    var result = await db!.query(
+      'Variable',
+      where: 'clave = ?',
+      whereArgs: [clave],
+    );
+
+    if (result.isNotEmpty) {
+      var variable = Variable.fromJson(result.first);
+      return variable.activo == true;
+    }
+
+    return false;
+  }
+}

+ 2 - 0
lib/viewmodels/viewmodels.dart

@@ -7,3 +7,5 @@ export '../viewmodels/toping_categoria_view_model.dart';
 export '../viewmodels/toping_view_model.dart';
 export '../viewmodels/media_view_model.dart';
 export '../viewmodels/pedido_view_model.dart';
+export '../viewmodels/descuento_view_model.dart';
+export '../viewmodels/variable_view_model.dart';

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

@@ -67,7 +67,6 @@ class _CategoriaProductoScreenState extends State<CategoriaProductoScreen> {
               PopupMenuItem(
                 child: const Text('Eliminar'),
                 onTap: () async {
-                  // Retrasa la ejecución para permitir que se cierre el menú popup.
                   await Future.delayed(Duration(milliseconds: 100));
                   bool confirmado = await showDialog<bool>(
                         context: context,
@@ -160,7 +159,7 @@ class _CategoriaProductoScreenState extends State<CategoriaProductoScreen> {
     return Scaffold(
       appBar: AppBar(
         title: Text(
-          'Categoria Producto',
+          'Categoría Producto',
           style: TextStyle(color: AppTheme.secondary),
         ),
         iconTheme: IconThemeData(color: AppTheme.secondary),

+ 150 - 0
lib/views/descuento/descuento_screen.dart

@@ -0,0 +1,150 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import '../../models/models.dart';
+import '../../viewmodels/viewmodels.dart';
+
+class DescuentoScreen extends StatefulWidget {
+  DescuentoScreen({super.key});
+
+  @override
+  _DescuentoScreenState createState() => _DescuentoScreenState();
+}
+
+class _DescuentoScreenState extends State<DescuentoScreen> {
+  final TextEditingController _porcentajeController = TextEditingController();
+  String? _errorText;
+
+  @override
+  void initState() {
+    super.initState();
+
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      Provider.of<DescuentoViewModel>(context, listen: false)
+          .cargarDescuentos();
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text("Gestionar Descuentos"),
+      ),
+      body: Consumer<DescuentoViewModel>(
+        builder: (context, viewModel, child) {
+          return Column(
+            children: [
+              Padding(
+                padding: const EdgeInsets.all(16.0),
+                child: Card(
+                  elevation: 4,
+                  shape: RoundedRectangleBorder(
+                    borderRadius: BorderRadius.circular(12),
+                  ),
+                  child: Padding(
+                    padding: const EdgeInsets.all(12.0),
+                    child: Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        Text(
+                          "Agregar Nuevo Descuento",
+                          style: TextStyle(
+                            fontWeight: FontWeight.bold,
+                            fontSize: 18,
+                          ),
+                        ),
+                        const SizedBox(height: 10),
+                        Row(
+                          children: [
+                            Expanded(
+                              child: TextField(
+                                controller: _porcentajeController,
+                                decoration: InputDecoration(
+                                  labelText: "Porcentaje (%)",
+                                  errorText: _errorText,
+                                  border: OutlineInputBorder(
+                                    borderRadius: BorderRadius.circular(8.0),
+                                  ),
+                                ),
+                                keyboardType: TextInputType.number,
+                              ),
+                            ),
+                            const SizedBox(width: 10),
+                            ElevatedButton.icon(
+                              icon: Icon(Icons.add),
+                              label: Text("Agregar"),
+                              onPressed: () {
+                                setState(() {
+                                  _errorText = null;
+                                });
+
+                                if (_porcentajeController.text.isNotEmpty) {
+                                  final int porcentaje =
+                                      int.parse(_porcentajeController.text);
+
+                                  if (porcentaje > 100) {
+                                    setState(() {
+                                      _errorText =
+                                          'El descuento no puede ser mayor a 100%';
+                                    });
+                                  } else {
+                                    viewModel.guardarDescuento(
+                                        Descuento(porcentaje: porcentaje));
+                                    _porcentajeController.clear();
+                                  }
+                                }
+                              },
+                              style: ElevatedButton.styleFrom(
+                                backgroundColor: Colors.black,
+                                foregroundColor: Colors.white,
+                                padding: const EdgeInsets.symmetric(
+                                    vertical: 15, horizontal: 20),
+                                shape: RoundedRectangleBorder(
+                                  borderRadius: BorderRadius.circular(8),
+                                ),
+                              ),
+                            ),
+                          ],
+                        ),
+                      ],
+                    ),
+                  ),
+                ),
+              ),
+              const SizedBox(height: 10),
+              Expanded(
+                child: Card(
+                  elevation: 2,
+                  margin: const EdgeInsets.symmetric(horizontal: 16.0),
+                  shape: RoundedRectangleBorder(
+                    borderRadius: BorderRadius.circular(12),
+                  ),
+                  child: ListView.builder(
+                    itemCount: viewModel.descuentos.length,
+                    itemBuilder: (context, index) {
+                      final descuento = viewModel.descuentos[index];
+                      return ListTile(
+                        title: Text(
+                          '${descuento.porcentaje}%',
+                          style: TextStyle(
+                            fontSize: 18,
+                            fontWeight: FontWeight.w500,
+                          ),
+                        ),
+                        trailing: IconButton(
+                          icon: const Icon(Icons.delete, color: Colors.red),
+                          onPressed: () =>
+                              viewModel.eliminarDescuento(descuento.id!),
+                        ),
+                      );
+                    },
+                  ),
+                ),
+              ),
+            ],
+          );
+        },
+      ),
+    );
+  }
+}

+ 55 - 5
lib/views/pedido/pedido_csv.dart

@@ -1,3 +1,4 @@
+import 'dart:convert';
 import 'package:csv/csv.dart';
 import 'package:intl/intl.dart';
 import 'package:path_provider/path_provider.dart';
@@ -13,31 +14,80 @@ Future<void> exportarPedidosACSV(List<Pedido> pedidos, String fileName) async {
       "Producto",
       "Cantidad",
       "Precio Unitario",
+      "Toppings",
+      "Descuento (%)",
       "Estado",
-      "Fecha"
+      "Fecha",
+      "Total con Descuento",
+      "Tipo de Pago",
+      "Cantidad Efectivo",
+      "Cantidad Tarjeta",
+      "Cantidad Transferencia"
     ]
   ];
 
   for (var pedido in pedidos) {
     for (var producto in pedido.productos) {
+      // Convertir toppings a una cadena de texto y calcular el total adicional de los toppings
+      double totalToppingsPrecio = 0.0;
+      String toppingsText = producto.toppings.isNotEmpty
+          ? producto.toppings.map((t) {
+              String toppingNombre =
+                  t.topping?.nombre ?? 'Topping no especificado';
+              double toppingPrecio =
+                  double.tryParse(t.topping?.precio ?? '0') ?? 0.0;
+
+              if (toppingPrecio > 0) {
+                toppingNombre += "(+\$${formatCurrency(toppingPrecio)})";
+                totalToppingsPrecio += toppingPrecio;
+              }
+
+              return toppingNombre;
+            }).join(', ')
+          : 'Sin toppings';
+
+      // Calcular el total con descuento para este producto
+      double precioUnitario =
+          double.tryParse(producto.producto?.precio ?? '0') ?? 0.0;
+      double subtotal =
+          (precioUnitario + totalToppingsPrecio) * (producto.cantidad ?? 1);
+      double descuento = pedido.descuento?.toDouble() ?? 0.0;
+      double precioDescuento = subtotal * (descuento / 100);
+      double totalConDescuento = subtotal - precioDescuento;
+
       List<dynamic> row = [
-        pedido.id,
+        pedido.folio,
         pedido.nombreCliente,
         producto.producto?.nombre ?? 'No especificado',
         producto.cantidad,
-        producto.producto?.precio ?? '0.0',
+        formatCurrency(precioUnitario),
+        toppingsText,
+        descuento,
         pedido.estatus,
-        pedido.peticion ?? ''
+        pedido.peticion ?? '',
+        formatCurrency(totalConDescuento),
+        pedido.tipoPago ?? 'No especificado',
+        formatCurrency(pedido.cantEfectivo ?? 0.0),
+        formatCurrency(pedido.cantTarjeta ?? 0.0),
+        formatCurrency(pedido.cantTransferencia ?? 0.0),
       ];
       rows.add(row);
     }
   }
 
   String csv = const ListToCsvConverter().convert(rows);
+
   final directory = await getApplicationDocumentsDirectory();
   final path = p.join(directory.path, fileName);
   final file = File(path);
 
-  await file.writeAsString(csv);
+  final utf8Csv = utf8.encode('\uFEFF' + csv);
+  await file.writeAsBytes(utf8Csv, flush: true);
+
   print('Archivo CSV guardado en $path');
 }
+
+String formatCurrency(double amount) {
+  final format = NumberFormat("#,##0.00", "es_MX");
+  return format.format(amount);
+}

+ 265 - 159
lib/views/pedido/pedido_detalle_screen.dart

@@ -7,16 +7,17 @@ import '../../models/models.dart';
 class PedidoDetalleScreen extends StatelessWidget {
   final Pedido pedido;
 
+  const PedidoDetalleScreen({Key? key, required this.pedido}) : super(key: key);
+
   String formatCurrency(double amount) {
     final format = NumberFormat("#,##0.00", "es_MX");
     return format.format(amount);
   }
 
-  const PedidoDetalleScreen({Key? key, required this.pedido}) : super(key: key);
-
   @override
   Widget build(BuildContext context) {
-    double total = pedido.productos.fold(0, (previousValue, element) {
+    double totalSinDescuento =
+        pedido.productos.fold(0, (previousValue, element) {
       double productTotal = element.cantidad! *
           (double.tryParse(element.producto?.precio ?? '') ?? 0.0);
 
@@ -29,6 +30,10 @@ class PedidoDetalleScreen extends StatelessWidget {
       return previousValue + productTotal + toppingsTotal;
     });
 
+    double descuento = pedido.descuento?.toDouble() ?? 0.0;
+    double precioDescuento = totalSinDescuento * (descuento / 100);
+    double totalConDescuento = totalSinDescuento - precioDescuento;
+
     return Scaffold(
       appBar: AppBar(
         title: Text(
@@ -37,174 +42,275 @@ class PedidoDetalleScreen extends StatelessWidget {
         ),
       ),
       body: SingleChildScrollView(
-          padding: const EdgeInsets.all(12.0),
-          child: Column(
-            children: [
-              Card(
-                  elevation: 4,
-                  color: Colors.white,
-                  child: Column(
-                    children: [
-                      ListTile(
-                        title: Text(
-                          'Cliente: ${pedido.nombreCliente}',
-                          style: TextStyle(
-                              fontWeight: FontWeight.bold, fontSize: 22),
-                        ),
-                        subtitle: Text(
-                          'Comentarios: ${pedido.comentarios}',
-                          style: TextStyle(
-                              fontSize: 20, fontWeight: FontWeight.w500),
-                        ),
+        padding: const EdgeInsets.all(12.0),
+        child: Column(
+          children: [
+            Card(
+              elevation: 4,
+              color: Colors.white,
+              child: Column(
+                children: [
+                  ListTile(
+                    title: Text(
+                      'Cliente: ${pedido.nombreCliente}',
+                      style:
+                          TextStyle(fontWeight: FontWeight.bold, fontSize: 22),
+                    ),
+                    subtitle: Text(
+                      'Comentarios: ${pedido.comentarios}',
+                      style:
+                          TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
+                    ),
+                  ),
+                  ListTile(
+                    title: Text(
+                      'Estado del Pedido: ${pedido.estatus}',
+                      style: TextStyle(
+                        fontSize: 22,
+                        fontWeight: FontWeight.bold,
                       ),
-                      ListTile(
-                        title: Text(
-                          'Estado del Pedido: ${pedido.estatus}',
-                          style: TextStyle(
-                            fontSize: 22,
-                            fontWeight: FontWeight.bold,
-                          ),
-                        ),
-                      )
-                    ],
-                  )),
-              SizedBox(height: 10),
-              Card(
-                elevation: 4,
-                color: Colors.white,
-                child: Padding(
-                  padding: const EdgeInsets.all(8.0),
-                  child: Column(
-                    crossAxisAlignment: CrossAxisAlignment.start,
-                    children: [
-                      Text('Productos',
-                          style: TextStyle(
-                              fontSize: 22, fontWeight: FontWeight.bold)),
-                      const SizedBox(height: 15),
-                      ListView.builder(
-                        shrinkWrap: true,
-                        physics: NeverScrollableScrollPhysics(),
-                        itemCount: pedido.productos.length,
-                        itemBuilder: (context, index) {
-                          final producto = pedido.productos[index];
-                          return Padding(
-                            padding: const EdgeInsets.symmetric(vertical: 4.0),
-                            child: Column(
-                              crossAxisAlignment: CrossAxisAlignment.start,
-                              children: [
-                                Row(
-                                  children: [
-                                    Expanded(
-                                      flex: 6,
-                                      child: Text(
-                                        producto.producto?.nombre ??
-                                            "Producto no especificado",
-                                        style: TextStyle(
-                                            fontWeight: FontWeight.bold,
-                                            fontSize: 17),
-                                        overflow: TextOverflow.ellipsis,
-                                      ),
-                                    ),
-                                    Expanded(
-                                      flex: 1,
-                                      child: Text(
-                                        'x${producto.cantidad}',
-                                        style: TextStyle(
-                                            fontWeight: FontWeight.w500,
-                                            fontSize: 17),
-                                        textAlign: TextAlign.center,
-                                      ),
+                    ),
+                  )
+                ],
+              ),
+            ),
+            SizedBox(height: 10),
+            Card(
+              elevation: 4,
+              color: Colors.white,
+              child: Padding(
+                padding: const EdgeInsets.all(8.0),
+                child: Column(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  children: [
+                    Text('Productos',
+                        style: TextStyle(
+                            fontSize: 22, fontWeight: FontWeight.bold)),
+                    const SizedBox(height: 15),
+                    ListView.builder(
+                      shrinkWrap: true,
+                      physics: NeverScrollableScrollPhysics(),
+                      itemCount: pedido.productos.length,
+                      itemBuilder: (context, index) {
+                        final producto = pedido.productos[index];
+                        return Padding(
+                          padding: const EdgeInsets.symmetric(vertical: 4.0),
+                          child: Column(
+                            crossAxisAlignment: CrossAxisAlignment.start,
+                            children: [
+                              Row(
+                                children: [
+                                  Expanded(
+                                    flex: 6,
+                                    child: Text(
+                                      producto.producto?.nombre ??
+                                          "Producto no especificado",
+                                      style: TextStyle(
+                                          fontWeight: FontWeight.bold,
+                                          fontSize: 17),
+                                      overflow: TextOverflow.ellipsis,
                                     ),
-                                    Expanded(
-                                      flex: 2,
-                                      child: Text(
-                                        '\$${formatCurrency(double.tryParse(producto.producto?.precio ?? '0.0') ?? 0.0)}',
-                                        style: TextStyle(
-                                            fontWeight: FontWeight.w500,
-                                            fontSize: 17),
-                                        textAlign: TextAlign.right,
-                                      ),
+                                  ),
+                                  Expanded(
+                                    flex: 1,
+                                    child: Text(
+                                      'x${producto.cantidad}',
+                                      style: TextStyle(
+                                          fontWeight: FontWeight.w500,
+                                          fontSize: 17),
+                                      textAlign: TextAlign.center,
                                     ),
-                                  ],
-                                ),
-                                if (producto.toppings.isNotEmpty)
-                                  Padding(
-                                    padding: const EdgeInsets.only(top: 4.0),
-                                    child: Column(
-                                      crossAxisAlignment:
-                                          CrossAxisAlignment.start,
-                                      children:
-                                          producto.toppings.map((topping) {
-                                        return Padding(
-                                          padding: const EdgeInsets.symmetric(
-                                              vertical: 2.0),
-                                          child: Row(
-                                            children: [
-                                              Text(
-                                                '- ${topping.topping?.nombre ?? "Topping no especificado"}',
-                                                style: TextStyle(
-                                                    fontSize: 15,
-                                                    color: Colors.grey[600]),
-                                              ),
-                                              Spacer(),
-                                              Text(
-                                                '\$${formatCurrency(double.tryParse(topping.topping?.precio ?? '0.0') ?? 0.0)}',
-                                                style: TextStyle(
-                                                    fontSize: 15,
-                                                    color: Colors.grey[600]),
-                                              ),
-                                            ],
-                                          ),
-                                        );
-                                      }).toList(),
+                                  ),
+                                  Expanded(
+                                    flex: 2,
+                                    child: Text(
+                                      '\$${formatCurrency(double.tryParse(producto.producto?.precio ?? '0.0') ?? 0.0)}',
+                                      style: TextStyle(
+                                          fontWeight: FontWeight.w500,
+                                          fontSize: 17),
+                                      textAlign: TextAlign.right,
                                     ),
                                   ),
+                                ],
+                              ),
+                              if (producto.toppings.isNotEmpty)
+                                Padding(
+                                  padding: const EdgeInsets.only(top: 4.0),
+                                  child: Column(
+                                    crossAxisAlignment:
+                                        CrossAxisAlignment.start,
+                                    children: producto.toppings.map((topping) {
+                                      return Padding(
+                                        padding: const EdgeInsets.symmetric(
+                                            vertical: 2.0),
+                                        child: Row(
+                                          children: [
+                                            Text(
+                                              '- ${topping.topping?.nombre ?? "Topping no especificado"}',
+                                              style: TextStyle(
+                                                  fontSize: 15,
+                                                  color: Colors.grey[600]),
+                                            ),
+                                            Spacer(),
+                                            Text(
+                                              '\$${formatCurrency(double.tryParse(topping.topping?.precio ?? '0.0') ?? 0.0)}',
+                                              style: TextStyle(
+                                                  fontSize: 15,
+                                                  color: Colors.grey[600]),
+                                            ),
+                                          ],
+                                        ),
+                                      );
+                                    }).toList(),
+                                  ),
+                                ),
+                            ],
+                          ),
+                        );
+                      },
+                    ),
+                    Divider(),
+                    Padding(
+                      padding: const EdgeInsets.symmetric(vertical: 8.0),
+                      child: Column(
+                        crossAxisAlignment: CrossAxisAlignment.end,
+                        children: [
+                          Row(
+                            mainAxisAlignment: MainAxisAlignment.end,
+                            children: [
+                              const Text('Subtotal:',
+                                  style: TextStyle(
+                                      fontSize: 16,
+                                      fontWeight: FontWeight.bold)),
+                              const SizedBox(width: 5),
+                              Text('\$${formatCurrency(totalSinDescuento)}',
+                                  style: const TextStyle(
+                                      fontSize: 16,
+                                      fontWeight: FontWeight.bold)),
+                            ],
+                          ),
+                          if (descuento > 0) ...[
+                            Row(
+                              mainAxisAlignment: MainAxisAlignment.end,
+                              children: [
+                                Text(
+                                    'Descuento (${descuento.toStringAsFixed(0)}%):',
+                                    style: const TextStyle(
+                                        fontSize: 16,
+                                        fontWeight: FontWeight.bold)),
+                                const SizedBox(width: 8),
+                                Text('-\$${formatCurrency(precioDescuento)}',
+                                    style: const TextStyle(
+                                        fontSize: 16,
+                                        fontWeight: FontWeight.bold)),
                               ],
                             ),
-                          );
-                        },
-                      ),
-                      Divider(),
-                      Padding(
-                        padding: const EdgeInsets.symmetric(vertical: 8.0),
-                        child: Row(
-                          mainAxisAlignment: MainAxisAlignment.end,
-                          children: [
-                            Text('Total: \$${formatCurrency(total)}',
-                                style: TextStyle(
-                                    fontSize: 16, fontWeight: FontWeight.bold)),
                           ],
-                        ),
+                          Row(
+                            mainAxisAlignment: MainAxisAlignment.end,
+                            children: [
+                              const Text('Total:',
+                                  style: TextStyle(
+                                      fontSize: 16,
+                                      fontWeight: FontWeight.bold)),
+                              const SizedBox(width: 5),
+                              Text('\$${formatCurrency(totalConDescuento)}',
+                                  style: const TextStyle(
+                                      fontSize: 16,
+                                      fontWeight: FontWeight.bold)),
+                            ],
+                          ),
+                        ],
                       ),
-                    ],
-                  ),
+                    ),
+                  ],
                 ),
               ),
-              const SizedBox(height: 20),
-              Align(
-                alignment: Alignment.centerLeft,
-                child: ElevatedButton.icon(
-                  icon: Icon(
-                    Icons.receipt_long_outlined,
-                    color: AppTheme.quaternary,
-                    size: 30,
-                  ),
-                  onPressed: () => imprimirTicketsJuntos(pedido),
-                  label: Text(
-                    'Imprimir Ticket',
-                    style: TextStyle(
-                        fontWeight: FontWeight.w500,
-                        fontSize: 18,
-                        color: AppTheme.quaternary),
-                  ),
-                  style: ElevatedButton.styleFrom(
-                    padding: EdgeInsets.fromLTRB(50, 20, 50, 20),
-                    primary: AppTheme.tertiary,
-                  ),
+            ),
+            const SizedBox(height: 10),
+            Card(
+              elevation: 4,
+              color: Colors.white,
+              child: Padding(
+                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(),
+                  ],
+                ),
+              ),
+            ),
+            const SizedBox(height: 20),
+            Align(
+              alignment: Alignment.centerLeft,
+              child: ElevatedButton.icon(
+                icon: Icon(
+                  Icons.receipt_long_outlined,
+                  color: AppTheme.quaternary,
+                  size: 30,
                 ),
-              )
-            ],
-          )),
+                onPressed: () => imprimirTicketsJuntos(context, pedido),
+                label: Text(
+                  'Imprimir Ticket',
+                  style: TextStyle(
+                      fontWeight: FontWeight.w500,
+                      fontSize: 18,
+                      color: AppTheme.quaternary),
+                ),
+                style: ElevatedButton.styleFrom(
+                  padding: const EdgeInsets.fromLTRB(50, 20, 50, 20),
+                  primary: AppTheme.tertiary,
+                ),
+              ),
+            )
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildPaymentDetails() {
+    List<Widget> paymentDetails = [];
+
+    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])));
+    }
+
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: paymentDetails,
+    );
+  }
+
+  Widget _buildPaymentRow(String paymentType, double amount) {
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.spaceBetween,
+      children: [
+        Text(
+          paymentType,
+          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+        ),
+        Text(
+          '\$${formatCurrency(amount)}',
+          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+        ),
+      ],
     );
   }
 }

+ 380 - 36
lib/views/pedido/pedido_form.dart

@@ -18,6 +18,7 @@ class PedidoForm extends StatefulWidget {
 
 class _PedidoFormState extends State<PedidoForm> {
   final _busqueda = TextEditingController(text: '');
+  final TextEditingController _descuentoController = TextEditingController();
   CategoriaProductoViewModel cvm = CategoriaProductoViewModel();
   ProductoViewModel pvm = ProductoViewModel();
   PedidoViewModel pedvm = PedidoViewModel();
@@ -32,6 +33,18 @@ class _PedidoFormState extends State<PedidoForm> {
   ScrollController _gridViewController = ScrollController();
   final _searchController = TextEditingController();
   final NumberFormat _numberFormat = NumberFormat.decimalPattern('es_MX');
+  int? selectedDescuento = 0;
+  double subtotal = 0;
+  double precioDescuento = 0;
+  double totalPedido = 0;
+
+  bool efectivoSeleccionado = false;
+  bool tarjetaSeleccionada = false;
+  bool transferenciaSeleccionada = false;
+  TextEditingController efectivoController = TextEditingController();
+  TextEditingController tarjetaController = TextEditingController();
+  TextEditingController transferenciaController = TextEditingController();
+  double cambio = 0.0;
 
   double calcularTotalPedido() {
     double total = 0;
@@ -53,6 +66,17 @@ class _PedidoFormState extends State<PedidoForm> {
     return total;
   }
 
+  double aplicarDescuento(double total, int? descuento) {
+    if (descuento != null && descuento > 0) {
+      double totalPedido = total * (1 - descuento / 100);
+      // print(
+      //     'Total con descuento: $totalPedido (Descuento aplicado: $descuento%)');
+      return totalPedido;
+    }
+    // print('Sin descuento, total: $total');
+    return total;
+  }
+
   @override
   void initState() {
     super.initState();
@@ -62,6 +86,8 @@ class _PedidoFormState extends State<PedidoForm> {
         cargarProductosPorCategoria(categoriaSeleccionada!.id);
       }
     });
+
+    Provider.of<DescuentoViewModel>(context, listen: false).cargarDescuentos();
   }
 
   _onSearchChanged(String value) {
@@ -73,6 +99,24 @@ class _PedidoFormState extends State<PedidoForm> {
     }
   }
 
+  void _recalcularTotal() {
+    subtotal = calcularTotalPedido();
+    // print('Subtotal: $subtotal');
+    // print('Descuento seleccionado: $selectedDescuento%');
+
+    precioDescuento = subtotal * (selectedDescuento! / 100);
+    totalPedido = subtotal - precioDescuento;
+
+    setState(() {
+      pedidoActual = pedidoActual ?? Pedido();
+      pedidoActual!.totalPedido = totalPedido;
+      pedidoActual!.descuento = selectedDescuento;
+    });
+
+    // print('Precio descuento: $precioDescuento');
+    // print('Total con descuento aplicado: $totalPedido');
+  }
+
   void _finalizeOrder() async {
     if (carrito.isEmpty) {
       showDialog(
@@ -102,12 +146,8 @@ class _PedidoFormState extends State<PedidoForm> {
         },
       );
       return;
-    } else {
-      int? pedidoId = pedidoActual?.id;
-      if (pedidoId != null) {
-        Navigator.of(context).pop();
-      }
     }
+
     await _promptForCustomerName();
   }
 
@@ -115,6 +155,38 @@ class _PedidoFormState extends State<PedidoForm> {
     TextEditingController nombreController = TextEditingController();
     TextEditingController comentarioController = TextEditingController();
     String errorMessage = '';
+    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;
+        if (cambio < 0) {
+          faltante = totalPedido - totalPagado;
+          cambio = 0;
+          totalCompletado = false;
+        } else {
+          faltante = 0;
+          totalCompletado = true;
+        }
+      });
+    }
+
+    void _validarCantidad(
+        StateSetter setState, TextEditingController controller) {
+      double cantidad = double.tryParse(controller.text) ?? 0;
+      if (cantidad > totalPedido) {
+        setState(() {
+          controller.text = totalPedido.toStringAsFixed(2);
+        });
+      }
+      _calcularCambio(setState);
+    }
+
     bool? shouldSave = await showDialog<bool>(
       context: context,
       builder: (BuildContext context) {
@@ -135,12 +207,11 @@ class _PedidoFormState extends State<PedidoForm> {
                     hintText: "Nombre del Cliente",
                     errorText: errorMessage.isEmpty ? null : errorMessage,
                     onChanged: (value) {
-                      if (value.trim().isEmpty) {
-                        setState(() => errorMessage =
-                            "El nombre del cliente es obligatorio.");
-                      } else {
-                        setState(() => errorMessage = '');
-                      }
+                      setState(() {
+                        errorMessage = value.trim().isEmpty
+                            ? "El nombre del cliente es obligatorio."
+                            : '';
+                      });
                     },
                   ),
                   const SizedBox(height: 10),
@@ -150,6 +221,177 @@ class _PedidoFormState extends State<PedidoForm> {
                     hintText: 'Comentarios',
                     maxLines: 3,
                   ),
+                  const SizedBox(height: 10),
+                  Align(
+                    alignment: Alignment.center,
+                    child: Text(
+                      'Métodos de pago',
+                      style:
+                          TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
+                    ),
+                  ),
+                  const SizedBox(height: 10),
+                  // Efectivo
+                  Row(
+                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                    children: [
+                      Row(
+                        children: [
+                          Checkbox(
+                            activeColor: AppTheme.primary,
+                            value: efectivoSeleccionado,
+                            onChanged:
+                                (totalCompletado && !efectivoSeleccionado)
+                                    ? null
+                                    : (bool? value) {
+                                        setState(() {
+                                          efectivoSeleccionado = value ?? false;
+                                          if (!efectivoSeleccionado) {
+                                            efectivoController.clear();
+                                            _calcularCambio(setState);
+                                          }
+                                        });
+                                      },
+                          ),
+                          const Text(
+                            "Efectivo",
+                            style: TextStyle(
+                                fontSize: 18, fontWeight: FontWeight.bold),
+                          ),
+                        ],
+                      ),
+                      if (efectivoSeleccionado)
+                        SizedBox(
+                          width: 150,
+                          child: AppTextField(
+                            controller: efectivoController,
+                            etiqueta: 'Cantidad',
+                            hintText: '0.00',
+                            keyboardType: TextInputType.number,
+                            onChanged: (value) => _calcularCambio(setState),
+                          ),
+                        ),
+                    ],
+                  ),
+                  const SizedBox(height: 10),
+                  // Tarjeta
+                  Row(
+                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                    children: [
+                      Row(
+                        children: [
+                          Checkbox(
+                            activeColor: AppTheme.primary,
+                            value: tarjetaSeleccionada,
+                            onChanged: (totalCompletado && !tarjetaSeleccionada)
+                                ? null
+                                : (bool? value) {
+                                    setState(() {
+                                      tarjetaSeleccionada = value ?? false;
+                                      if (!tarjetaSeleccionada) {
+                                        tarjetaController.clear();
+                                        _calcularCambio(setState);
+                                      }
+                                    });
+                                  },
+                          ),
+                          const Text(
+                            "Tarjeta",
+                            style: TextStyle(
+                                fontSize: 18, fontWeight: FontWeight.bold),
+                          ),
+                        ],
+                      ),
+                      if (tarjetaSeleccionada)
+                        SizedBox(
+                          width: 150,
+                          child: AppTextField(
+                            controller: tarjetaController,
+                            etiqueta: 'Cantidad',
+                            hintText: '0.00',
+                            keyboardType: TextInputType.number,
+                            onChanged: (value) {
+                              _validarCantidad(setState, tarjetaController);
+                            },
+                          ),
+                        ),
+                    ],
+                  ),
+                  const SizedBox(height: 10),
+                  // Transferencia
+                  Row(
+                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                    children: [
+                      Row(
+                        children: [
+                          Checkbox(
+                            activeColor: AppTheme.primary,
+                            value: transferenciaSeleccionada,
+                            onChanged:
+                                (totalCompletado && !transferenciaSeleccionada)
+                                    ? null
+                                    : (bool? value) {
+                                        setState(() {
+                                          transferenciaSeleccionada =
+                                              value ?? false;
+                                          if (!transferenciaSeleccionada) {
+                                            transferenciaController.clear();
+                                            _calcularCambio(setState);
+                                          }
+                                        });
+                                      },
+                          ),
+                          const Text(
+                            "Transferencia",
+                            style: TextStyle(
+                                fontSize: 18, fontWeight: FontWeight.bold),
+                          ),
+                        ],
+                      ),
+                      if (transferenciaSeleccionada)
+                        SizedBox(
+                          width: 150,
+                          child: AppTextField(
+                            controller: transferenciaController,
+                            etiqueta: 'Cantidad',
+                            hintText: '0.00',
+                            keyboardType: TextInputType.number,
+                            onChanged: (value) {
+                              _validarCantidad(
+                                  setState, transferenciaController);
+                            },
+                          ),
+                        ),
+                    ],
+                  ),
+                  const SizedBox(height: 10),
+                  // Mostrar el total del pedido y la cantidad faltante
+                  Align(
+                    alignment: Alignment.centerRight,
+                    child: Column(
+                        crossAxisAlignment: CrossAxisAlignment.end,
+                        children: [
+                          Text(
+                            'Total del pedido: \$${totalPedido.toStringAsFixed(2)}',
+                            style: const TextStyle(
+                                fontWeight: FontWeight.bold, fontSize: 18),
+                          ),
+                          if (faltante > 0)
+                            Text(
+                              'Faltante: \$${faltante.toStringAsFixed(2)}',
+                              style: const TextStyle(
+                                  color: Colors.red,
+                                  fontSize: 18,
+                                  fontWeight: FontWeight.bold),
+                            )
+                          else if (cambio > 0)
+                            Text('Cambio: \$${cambio.toStringAsFixed(2)}',
+                                style: const TextStyle(
+                                    color: Colors.green,
+                                    fontSize: 18,
+                                    fontWeight: FontWeight.bold)),
+                        ]),
+                  ),
                 ],
               ),
               actions: <Widget>[
@@ -165,8 +407,7 @@ class _PedidoFormState extends State<PedidoForm> {
                       style: ButtonStyle(
                           padding: MaterialStatePropertyAll(
                               EdgeInsets.fromLTRB(30, 20, 30, 20)),
-                          backgroundColor:
-                              MaterialStatePropertyAll(AppTheme.primary),
+                          backgroundColor: MaterialStatePropertyAll(Colors.red),
                           foregroundColor:
                               MaterialStatePropertyAll(AppTheme.secondary)),
                     ),
@@ -208,14 +449,21 @@ class _PedidoFormState extends State<PedidoForm> {
     DateTime now = DateTime.now();
     String formattedDate = DateFormat('dd-MM-yyyy kk:mm:ss').format(now);
 
-    double totalPedido = calcularTotalPedido();
-
     Pedido nuevoPedido = Pedido(
       peticion: formattedDate,
       nombreCliente: nombreCliente,
       comentarios: comentarios,
       estatus: "NUEVO",
       totalPedido: totalPedido,
+      descuento: pedidoActual?.descuento,
+      tipoPago: _obtenerTipoPago(),
+      cantEfectivo:
+          efectivoSeleccionado ? double.tryParse(efectivoController.text) : 0,
+      cantTarjeta:
+          tarjetaSeleccionada ? double.tryParse(tarjetaController.text) : 0,
+      cantTransferencia: transferenciaSeleccionada
+          ? double.tryParse(transferenciaController.text)
+          : 0,
     );
 
     List<PedidoProducto> listaPedidoProducto = carrito.map((item) {
@@ -244,14 +492,15 @@ class _PedidoFormState extends State<PedidoForm> {
     bool result = await Provider.of<PedidoViewModel>(context, listen: false)
         .guardarPedidoLocal(pedido: nuevoPedido);
 
+    if (!mounted) return;
+
     if (result) {
-      // Recuperar el pedido completo con detalles antes de imprimir
       Pedido? pedidoCompleto =
           await Provider.of<PedidoViewModel>(context, listen: false)
               .fetchPedidoConProductos(nuevoPedido.id!);
 
       if (pedidoCompleto != null) {
-        imprimirTicketsJuntos(pedidoCompleto);
+        imprimirTicketsJuntos(context, pedidoCompleto);
       }
       Navigator.of(context).pop();
     } else {
@@ -259,6 +508,14 @@ class _PedidoFormState extends State<PedidoForm> {
     }
   }
 
+  String _obtenerTipoPago() {
+    List<String> tiposPago = [];
+    if (efectivoSeleccionado) tiposPago.add('Efectivo');
+    if (tarjetaSeleccionada) tiposPago.add('Tarjeta');
+    if (transferenciaSeleccionada) tiposPago.add('Transferencia');
+    return tiposPago.join(',');
+  }
+
   void _limpiarBusqueda() async {
     setState(() {
       _busqueda.text = '';
@@ -329,6 +586,7 @@ class _PedidoFormState extends State<PedidoForm> {
         ));
       });
     }
+    _recalcularTotal();
   }
 
   Future<Map<int, List<Producto>>> obtenerToppingsSeleccionables(
@@ -386,6 +644,106 @@ class _PedidoFormState extends State<PedidoForm> {
     }
   }
 
+  Widget _buildDiscountSection() {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 8.0),
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: [
+          const Text(
+            'Descuento',
+            style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
+          ),
+          const Spacer(),
+          ConstrainedBox(
+            constraints: const BoxConstraints(
+              maxWidth: 150,
+            ),
+            child: Consumer<DescuentoViewModel>(
+              builder: (context, viewModel, child) {
+                return AppDropdownModel<int>(
+                  hint: 'Seleccionar',
+                  items: viewModel.descuentos
+                      .map(
+                        (descuento) => DropdownMenuItem<int>(
+                          value: descuento.porcentaje,
+                          child: Text(
+                            '${descuento.porcentaje}%',
+                            style: const TextStyle(color: Colors.black),
+                          ),
+                        ),
+                      )
+                      .toList(),
+                  selectedValue: selectedDescuento,
+                  onChanged: (value) {
+                    setState(() {
+                      selectedDescuento = value;
+                      _recalcularTotal();
+                    });
+                  },
+                );
+              },
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildTotalSection() {
+    String formattedsubtotal = _numberFormat.format(subtotal);
+    String formattedPrecioDescuento = _numberFormat.format(precioDescuento);
+    String formattedtotalPedido = _numberFormat.format(totalPedido);
+
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 8.0),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          if (precioDescuento > 0)
+            Column(
+              children: [
+                Row(
+                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                  children: [
+                    const Text('Subtotal',
+                        style: TextStyle(
+                            fontWeight: FontWeight.bold, fontSize: 18)),
+                    Text("\$$formattedsubtotal",
+                        style: const TextStyle(
+                            fontWeight: FontWeight.bold, fontSize: 18)),
+                  ],
+                ),
+                const SizedBox(height: 10),
+                Row(
+                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                  children: [
+                    const Text('Descuento',
+                        style: TextStyle(
+                            fontWeight: FontWeight.bold, fontSize: 18)),
+                    Text("-\$$formattedPrecioDescuento",
+                        style: const TextStyle(
+                            fontWeight: FontWeight.bold, fontSize: 18)),
+                  ],
+                ),
+              ],
+            ),
+          const SizedBox(height: 10),
+          Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              const Text('Total',
+                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
+              Text("\$$formattedtotalPedido",
+                  style: const TextStyle(
+                      fontWeight: FontWeight.bold, fontSize: 18)),
+            ],
+          ),
+        ],
+      ),
+    );
+  }
+
   @override
   Widget build(BuildContext context) {
     return Scaffold(
@@ -406,24 +764,6 @@ class _PedidoFormState extends State<PedidoForm> {
     );
   }
 
-  Widget _buildTotalSection() {
-    double total = calcularTotalPedido();
-    String formattedTotal = _numberFormat.format(total);
-    return Padding(
-      padding: const EdgeInsets.symmetric(horizontal: 8.0),
-      child: Row(
-        mainAxisAlignment: MainAxisAlignment.spaceBetween,
-        children: [
-          const Text('Total',
-              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
-          Text("\$$formattedTotal",
-              style:
-                  const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
-        ],
-      ),
-    );
-  }
-
   Widget _buildCartSection() {
     return Card(
       margin: const EdgeInsets.all(8.0),
@@ -563,6 +903,7 @@ class _PedidoFormState extends State<PedidoForm> {
                                           }
                                         }
                                         setState(() {});
+                                        _recalcularTotal();
                                       },
                                     );
                                   },
@@ -578,6 +919,7 @@ class _PedidoFormState extends State<PedidoForm> {
               },
             ),
           ),
+          _buildDiscountSection(),
           const Divider(thickness: 5),
           _buildTotalSection(),
           const SizedBox(height: 25),
@@ -603,12 +945,14 @@ class _PedidoFormState extends State<PedidoForm> {
     setState(() {
       carrito.removeAt(index);
     });
+    _recalcularTotal();
   }
 
   void incrementarProducto(ItemCarrito item) {
     setState(() {
       item.cantidad++;
     });
+    _recalcularTotal();
   }
 
   void quitarProductoDelCarrito(ItemCarrito item) {
@@ -619,12 +963,12 @@ class _PedidoFormState extends State<PedidoForm> {
         carrito.remove(item);
       }
     });
+    _recalcularTotal();
   }
 
   Widget _buildProductsSection() {
     return Column(
       children: [
-        // _buildSearchBar(),
         const SizedBox(height: 10),
         _buildCategoryButtons(),
         const SizedBox(height: 15),

+ 18 - 6
lib/views/pedido/pedido_screen.dart

@@ -36,17 +36,29 @@ class _PedidoScreenState extends State<PedidoScreen> {
   void exportCSV() async {
     final pedidosViewModel =
         Provider.of<PedidoViewModel>(context, listen: false);
-    List<Pedido> pedidosConProductos = [];
 
-    for (Pedido pedido in pedidosViewModel.pedidos) {
+    List<Pedido> pedidosConProductos;
+
+    // Si hay un rango de fechas seleccionado, obtenemos los pedidos de ese rango
+    if (fechaInicio != null && fechaFin != null) {
+      pedidosConProductos = await pedidosViewModel.fetchPedidosPorFechaSinLimit(
+          fechaInicio!, fechaFin!);
+    } else {
+      // De lo contrario, obtenemos todos los pedidos
+      pedidosConProductos = await pedidosViewModel.fetchAllLocalPedidos();
+    }
+
+    List<Pedido> pedidosConDetalles = [];
+
+    for (Pedido pedido in pedidosConProductos) {
       Pedido? pedidoConProductos =
           await pedidosViewModel.fetchPedidoConProductos(pedido.id);
       if (pedidoConProductos != null) {
-        pedidosConProductos.add(pedidoConProductos);
+        pedidosConDetalles.add(pedidoConProductos);
       }
     }
 
-    if (pedidosConProductos.isNotEmpty) {
+    if (pedidosConDetalles.isNotEmpty) {
       String fileName = 'Pedidos_JoshiPapas_POS';
       if (fechaInicio != null && fechaFin != null) {
         String startDateStr = DateFormat('dd-MM-yyyy').format(fechaInicio!);
@@ -55,7 +67,7 @@ class _PedidoScreenState extends State<PedidoScreen> {
       }
       fileName += '.csv';
 
-      await exportarPedidosACSV(pedidosConProductos, fileName);
+      await exportarPedidosACSV(pedidosConDetalles, fileName);
       ScaffoldMessenger.of(context).showSnackBar(SnackBar(
           content: Text('Archivo CSV descargado! Archivo: $fileName')));
     } else {
@@ -105,7 +117,7 @@ class _PedidoScreenState extends State<PedidoScreen> {
           PopupMenuButton(
             itemBuilder: (context) => [
               PopupMenuItem(
-                child: const Text('Editar'),
+                child: const Text('Detalle'),
                 onTap: () => go(item),
               ),
               PopupMenuItem(

+ 113 - 71
lib/views/pedido/pedido_ticket.dart

@@ -3,11 +3,17 @@ import 'package:flutter/material.dart';
 import 'package:intl/intl.dart';
 import 'package:pdf/pdf.dart';
 import 'package:pdf/widgets.dart' as pw;
+import 'package:provider/provider.dart';
 import '../../models/models.dart';
 import 'package:printing/printing.dart';
 import 'package:flutter/services.dart' show rootBundle;
 
-Future<void> imprimirTicketsJuntos(Pedido pedido) async {
+import '../../viewmodels/viewmodels.dart';
+
+Future<void> imprimirTicketsJuntos(BuildContext context, Pedido pedido) async {
+  bool ticketCocinaActivo =
+      await Provider.of<VariableViewModel>(context, listen: false)
+          .isVariableActive('ticket_cocina');
   final pdf = pw.Document();
   final image = pw.MemoryImage(
     (await rootBundle.load('assets/JoshiLogo-BN.png')).buffer.asUint8List(),
@@ -17,9 +23,11 @@ Future<void> imprimirTicketsJuntos(Pedido pedido) async {
     generarPaginaPrimerTicket(pedido, image),
   );
 
-  pdf.addPage(
-    generarPaginaSegundoTicket(pedido),
-  );
+  if (ticketCocinaActivo) {
+    pdf.addPage(
+      generarPaginaSegundoTicket(pedido),
+    );
+  }
 
   await printPdf(Uint8List.fromList(await pdf.save()));
 }
@@ -27,75 +35,76 @@ Future<void> imprimirTicketsJuntos(Pedido pedido) async {
 pw.Page generarPaginaPrimerTicket(Pedido pedido, pw.MemoryImage image) {
   final numberFormat = NumberFormat('#,##0.00', 'es_MX');
 
+  double totalSinDescuento = 0;
+  double totalConDescuento = 0;
+  double descuento = pedido.descuento?.toDouble() ?? 0.0;
+  double precioDescuento = 0;
+
+  final productList = pedido.productos
+      .map(
+        (producto) {
+          final productPrice = double.parse(producto.producto?.precio ?? '0');
+          final productTotal = productPrice * (producto.cantidad ?? 1);
+
+          totalSinDescuento += productTotal;
+
+          final toppingsList = producto.toppings.where((topping) {
+            final toppingPrice = double.parse(topping.topping?.precio ?? '0');
+            return toppingPrice > 0;
+          }).map((topping) {
+            final toppingPrice = double.parse(topping.topping?.precio ?? '0');
+            totalSinDescuento += toppingPrice * (producto.cantidad ?? 1);
+
+            return pw.Row(
+              children: [
+                pw.Text(
+                    '- ${topping.topping?.nombre ?? "Topping no especificado"}',
+                    style: const pw.TextStyle(fontSize: 7)),
+                pw.Spacer(),
+                pw.Text('\$${numberFormat.format(toppingPrice)}',
+                    style: const pw.TextStyle(fontSize: 7)),
+              ],
+            );
+          }).toList();
+
+          return [
+            pw.Row(
+              mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
+              children: [
+                pw.Expanded(
+                  flex: 2,
+                  child: pw.Text(
+                      producto.producto?.nombre ?? "Producto no especificado",
+                      style: const pw.TextStyle(fontSize: 7)),
+                ),
+                pw.Expanded(
+                  flex: 1,
+                  child: pw.Text('x${producto.cantidad}',
+                      style: const pw.TextStyle(fontSize: 7)),
+                ),
+                pw.Padding(
+                  padding: pw.EdgeInsets.only(right: 2),
+                  child: pw.Expanded(
+                    flex: 1,
+                    child: pw.Text('\$${numberFormat.format(productPrice)}',
+                        style: const pw.TextStyle(fontSize: 8)),
+                  ),
+                )
+              ],
+            ),
+            ...toppingsList,
+          ];
+        },
+      )
+      .expand((e) => e)
+      .toList();
+
+  precioDescuento = totalSinDescuento * (descuento / 100);
+  totalConDescuento = totalSinDescuento - precioDescuento;
+
   return pw.Page(
       pageFormat: PdfPageFormat.roll57,
       build: (pw.Context context) {
-        double total = 0;
-
-        final productList = pedido.productos
-            .map(
-              (producto) {
-                final productPrice =
-                    double.parse(producto.producto?.precio ?? '0');
-                final productTotal = productPrice * (producto.cantidad ?? 1);
-
-                final toppingsList = producto.toppings.where((topping) {
-                  final toppingPrice =
-                      double.parse(topping.topping?.precio ?? '0');
-                  return toppingPrice > 0;
-                }).map((topping) {
-                  final toppingPrice =
-                      double.parse(topping.topping?.precio ?? '0');
-                  total += toppingPrice * (producto.cantidad ?? 1);
-
-                  return pw.Row(
-                    children: [
-                      pw.Text(
-                          '- ${topping.topping?.nombre ?? "Topping no especificado"}',
-                          style: const pw.TextStyle(fontSize: 7)),
-                      pw.Spacer(),
-                      pw.Text('\$${numberFormat.format(toppingPrice)}',
-                          style: const pw.TextStyle(fontSize: 7)),
-                    ],
-                  );
-                }).toList();
-
-                total += productTotal;
-
-                return [
-                  pw.Row(
-                    mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
-                    children: [
-                      pw.Expanded(
-                        flex: 2,
-                        child: pw.Text(
-                            producto.producto?.nombre ??
-                                "Producto no especificado",
-                            style: const pw.TextStyle(fontSize: 7)),
-                      ),
-                      pw.Expanded(
-                        flex: 1,
-                        child: pw.Text('x${producto.cantidad}',
-                            style: const pw.TextStyle(fontSize: 7)),
-                      ),
-                      pw.Padding(
-                        padding: pw.EdgeInsets.only(right: 2),
-                        child: pw.Expanded(
-                          flex: 1,
-                          child: pw.Text(
-                              '\$${numberFormat.format(productPrice)}',
-                              style: const pw.TextStyle(fontSize: 8)),
-                        ),
-                      )
-                    ],
-                  ),
-                  ...toppingsList,
-                ];
-              },
-            )
-            .expand((e) => e)
-            .toList();
-
         return pw.Column(
             crossAxisAlignment: pw.CrossAxisAlignment.center,
             children: [
@@ -132,12 +141,45 @@ pw.Page generarPaginaPrimerTicket(Pedido pedido, pw.MemoryImage image) {
               pw.Row(
                 mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
                 children: [
+                  pw.Text('Subtotal:',
+                      style: pw.TextStyle(
+                          fontWeight: pw.FontWeight.bold, fontSize: 9)),
+                  pw.Padding(
+                    padding: const pw.EdgeInsets.only(right: 30),
+                    child: pw.Text(
+                        '\$${numberFormat.format(totalSinDescuento)}',
+                        style: pw.TextStyle(
+                            fontWeight: pw.FontWeight.bold, fontSize: 9)),
+                  ),
+                ],
+              ),
+              if (descuento > 0) ...[
+                pw.Row(
+                  mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
+                  children: [
+                    pw.Text('Descuento:',
+                        style: pw.TextStyle(
+                            fontWeight: pw.FontWeight.bold, fontSize: 9)),
+                    pw.Padding(
+                      padding: const pw.EdgeInsets.only(right: 30),
+                      child: pw.Text(
+                          '-\$${numberFormat.format(precioDescuento)}',
+                          style: pw.TextStyle(
+                              fontWeight: pw.FontWeight.bold, fontSize: 9)),
+                    ),
+                  ],
+                ),
+              ],
+              pw.Row(
+                mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
+                children: [
                   pw.Text('Total:',
                       style: pw.TextStyle(
                           fontWeight: pw.FontWeight.bold, fontSize: 9)),
                   pw.Padding(
                     padding: const pw.EdgeInsets.only(right: 30),
-                    child: pw.Text('\$${numberFormat.format(total)}',
+                    child: pw.Text(
+                        '\$${numberFormat.format(totalConDescuento)}',
                         style: pw.TextStyle(
                             fontWeight: pw.FontWeight.bold, fontSize: 9)),
                   ),

+ 2 - 3
lib/views/producto/producto_form.dart

@@ -79,8 +79,6 @@ class Formulario extends State<ProductoForm> {
     if (idProducto != null && idProducto > 0) {
       List<int> toppingIds =
           await productoVM.obtenerToppingsPorProducto(idProducto);
-
-      // Asignar toppings a las categorías
       for (var toppingId in toppingIds) {
         Producto? topping = await productoVM.obtenerProductoPorId(toppingId);
         if (topping != null && topping.idCategoria != null) {
@@ -348,13 +346,14 @@ class Formulario extends State<ProductoForm> {
       return;
     }
 
-    // Recopilar los toppings seleccionados
     List<Producto> selectedToppings = [];
     selectedToppingsByCategory.forEach((categoryId, productIds) {
       selectedToppings
           .addAll(productIds.map((productId) => Producto(id: productId)));
     });
 
+    if (_precio.text.isEmpty) _precio.text = '0';
+
     Producto productToUpdate = Producto(
       id: widget.producto.id,
       nombre: _nombre.text,

+ 115 - 0
lib/views/variable/variable_form.dart

@@ -0,0 +1,115 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import '../../models/models.dart';
+import '../../viewmodels/variable_view_model.dart';
+import '../../themes/themes.dart';
+import '../../widgets/widgets.dart';
+
+class VariableForm extends StatefulWidget {
+  final Variable variable;
+
+  const VariableForm({Key? key, required this.variable}) : super(key: key);
+
+  @override
+  _VariableFormState createState() => _VariableFormState();
+}
+
+class _VariableFormState extends State<VariableForm> {
+  final _nombre = TextEditingController();
+  final _clave = TextEditingController();
+  final _descripcion = TextEditingController();
+  bool _activo = true;
+
+  @override
+  void initState() {
+    super.initState();
+    _nombre.text = widget.variable.nombre ?? "";
+    _clave.text = widget.variable.clave ?? "";
+    _descripcion.text = widget.variable.descripcion ?? "";
+    _activo = widget.variable.activo ?? true;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(
+          widget.variable.id == null ? 'Nueva Variable' : 'Editar Variable',
+          style: TextStyle(color: AppTheme.secondary),
+        ),
+        iconTheme: IconThemeData(color: AppTheme.secondary),
+      ),
+      body: SingleChildScrollView(
+        padding: EdgeInsets.all(8),
+        child: Column(
+          children: [
+            tarjeta(
+              Padding(
+                padding: const EdgeInsets.all(8),
+                child: Column(
+                  children: [
+                    AppTextField(
+                      maxLength: 100,
+                      etiqueta: 'Nombre',
+                      controller: _nombre,
+                      hintText: 'Nombre de la variable',
+                    ),
+                    AppTextField(
+                      maxLength: 100,
+                      etiqueta: 'Clave',
+                      controller: _clave,
+                      hintText: 'Clave de la variable',
+                    ),
+                    AppTextField(
+                      maxLength: 200,
+                      etiqueta: 'Descripción',
+                      controller: _descripcion,
+                      hintText: 'Descripción de la variable',
+                    ),
+                    SwitchListTile(
+                      title: Text("Activo"),
+                      value: _activo,
+                      onChanged: (bool value) {
+                        setState(() {
+                          _activo = value;
+                        });
+                      },
+                    ),
+                  ],
+                ),
+              ),
+            ),
+            SizedBox(height: 15),
+            boton("Guardar", () async {
+              Provider.of<VariableViewModel>(context, listen: false)
+                  .setIsLoading(true);
+
+              widget.variable.nombre = _nombre.text;
+              widget.variable.clave = _clave.text;
+              widget.variable.descripcion = _descripcion.text;
+              widget.variable.activo = _activo;
+
+              await Provider.of<VariableViewModel>(context, listen: false)
+                  .updateVariable(widget.variable);
+
+              Provider.of<VariableViewModel>(context, listen: false)
+                  .setIsLoading(false);
+
+              if (context.mounted) {
+                Navigator.pop(context);
+              }
+            })
+          ],
+        ),
+      ),
+    );
+  }
+
+  @override
+  void dispose() {
+    _nombre.dispose();
+    _clave.dispose();
+    _descripcion.dispose();
+    super.dispose();
+  }
+}

+ 410 - 0
lib/views/variable/variable_screen.dart

@@ -0,0 +1,410 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import '../../viewmodels/variable_view_model.dart';
+import '../../models/models.dart';
+import 'variable_form.dart';
+import '../../themes/themes.dart';
+import '../../widgets/app_textfield.dart';
+import '../../widgets/widgets_components.dart' as clase;
+
+class VariablesScreen extends StatefulWidget {
+  @override
+  _VariablesScreenState createState() => _VariablesScreenState();
+}
+
+class _VariablesScreenState extends State<VariablesScreen> {
+  final _busqueda = TextEditingController(text: '');
+  ScrollController horizontalScrollController = ScrollController();
+
+  @override
+  void initState() {
+    super.initState();
+    Provider.of<VariableViewModel>(context, listen: false).fetchLocalAll();
+  }
+
+  void go(Variable variable) {
+    Navigator.push(
+      context,
+      MaterialPageRoute(
+        builder: (context) => VariableForm(variable: variable),
+      ),
+    ).then((_) =>
+        Provider.of<VariableViewModel>(context, listen: false).fetchLocalAll());
+  }
+
+  void clearSearchAndReset() {
+    setState(() {
+      _busqueda.clear();
+      Provider.of<VariableViewModel>(context, listen: false).fetchLocalAll();
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final model = Provider.of<VariableViewModel>(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 (Variable item in model.variables) {
+      registros.add(DataRow(cells: [
+        DataCell(
+            Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+          PopupMenuButton(
+            itemBuilder: (context) => [
+              PopupMenuItem(
+                child: const Text('Editar'),
+                onTap: () => go(item),
+              ),
+              PopupMenuItem(
+                child: const Text('Eliminar'),
+                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.deleteVariable(item.id!);
+                    model.fetchLocalAll();
+                  }
+                },
+              )
+            ],
+            icon: const Icon(Icons.more_vert),
+          ),
+        ])),
+        DataCell(
+          Text(item.id.toString()),
+          onTap: () {
+            Provider.of<VariableViewModel>(context, listen: false)
+                .selectVariable(item);
+            go(item);
+          },
+        ),
+        DataCell(
+          Text(item.nombre.toString()),
+          onTap: () {
+            Provider.of<VariableViewModel>(context, listen: false)
+                .selectVariable(item);
+            go(item);
+          },
+        ),
+      ]));
+    }
+
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(
+          'Variables',
+          style: TextStyle(color: AppTheme.secondary),
+        ),
+        iconTheme: IconThemeData(color: AppTheme.secondary),
+      ),
+      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(
+                            children: [
+                              Expanded(
+                                flex: 7,
+                                child: busquedaTextField(),
+                              ),
+                              SizedBox(width: 5),
+                              botonBuscar()
+                            ],
+                          );
+                        } else {
+                          return Column(
+                            children: [
+                              Row(
+                                children: [busquedaTextField()],
+                              ),
+                              SizedBox(height: 15),
+                              Row(
+                                children: [botonBuscar()],
+                              ),
+                            ],
+                          );
+                        }
+                      },
+                    ),
+                  ),
+                ),
+                const SizedBox(height: 8),
+                model.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("ID", style: estilo)),
+                                    DataColumn(
+                                        label: Text("NOMBRE", style: estilo)),
+                                  ],
+                                  rows: registros,
+                                ),
+                              ),
+                            ),
+                          ),
+                        );
+                      }),
+                    ],
+                  ),
+                ),
+                const SizedBox(
+                  height: 15,
+                ),
+                if (!model.isLoading) ...[
+                  Row(
+                    mainAxisAlignment: MainAxisAlignment.center,
+                    children: [
+                      TextButton(
+                        onPressed:
+                            model.currentPage > 1 ? model.previousPage : null,
+                        child: Text('Anterior'),
+                        style: ButtonStyle(
+                          backgroundColor:
+                              MaterialStateProperty.resolveWith<Color?>(
+                            (Set<MaterialState> states) {
+                              if (states.contains(MaterialState.disabled)) {
+                                return Colors.grey;
+                              }
+                              return AppTheme.tertiary;
+                            },
+                          ),
+                          foregroundColor:
+                              MaterialStateProperty.resolveWith<Color?>(
+                            (Set<MaterialState> states) {
+                              if (states.contains(MaterialState.disabled)) {
+                                return Colors.black;
+                              }
+                              return Colors.white;
+                            },
+                          ),
+                        ),
+                      ),
+                      SizedBox(width: 15),
+                      Text(
+                          'Página ${model.currentPage} de ${model.totalPages}'),
+                      SizedBox(width: 15),
+                      TextButton(
+                        onPressed: model.currentPage < model.totalPages
+                            ? model.nextPage
+                            : null,
+                        child: Text('Siguiente'),
+                        style: ButtonStyle(
+                          backgroundColor:
+                              MaterialStateProperty.resolveWith<Color?>(
+                            (Set<MaterialState> states) {
+                              if (states.contains(MaterialState.disabled)) {
+                                return Colors.grey;
+                              }
+                              return AppTheme.tertiary;
+                            },
+                          ),
+                          foregroundColor:
+                              MaterialStateProperty.resolveWith<Color?>(
+                            (Set<MaterialState> states) {
+                              if (states.contains(MaterialState.disabled)) {
+                                return Colors.black;
+                              }
+                              return Colors.white;
+                            },
+                          ),
+                        ),
+                      ),
+                    ],
+                  ),
+                ],
+                const SizedBox(
+                  height: 15,
+                ),
+              ],
+            ),
+          ),
+        ],
+      ),
+      floatingActionButton: FloatingActionButton.extended(
+        onPressed: () async {
+          Variable nuevaVariable = Variable();
+          Navigator.push(
+            context,
+            MaterialPageRoute(
+              builder: (context) => VariableForm(variable: nuevaVariable),
+            ),
+          ).then((_) => Provider.of<VariableViewModel>(context, listen: false)
+              .fetchLocalAll());
+        },
+        icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
+        label: Text(
+          "Agregar Variable",
+          style: TextStyle(fontSize: 18, color: AppTheme.quaternary),
+        ),
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.circular(8),
+        ),
+        materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+        backgroundColor: AppTheme.tertiary,
+      ),
+    );
+  }
+
+  Widget busquedaTextField() {
+    return Row(
+      children: [
+        Expanded(
+          flex: 3,
+          child: AppTextField(
+            prefixIcon: const Icon(Icons.search),
+            etiqueta: 'Búsqueda por nombre...',
+            controller: _busqueda,
+            hintText: 'Búsqueda por nombre...',
+          ),
+        ),
+        const SizedBox(width: 5),
+      ],
+    );
+  }
+
+  Widget botonBuscar() {
+    return Expanded(
+        flex: 2,
+        child: Row(
+          children: [
+            Expanded(
+              flex: 2,
+              child: Padding(
+                padding: const EdgeInsets.only(top: 30),
+                child: ElevatedButton(
+                  onPressed: clearSearchAndReset,
+                  style: ElevatedButton.styleFrom(
+                    shape: RoundedRectangleBorder(
+                      borderRadius: BorderRadius.circular(20.0),
+                    ),
+                    primary: AppTheme.tertiary,
+                    padding: const EdgeInsets.symmetric(vertical: 25),
+                  ),
+                  child: Text('Limpiar',
+                      style: TextStyle(color: AppTheme.quaternary)),
+                ),
+              ),
+            ),
+            const SizedBox(width: 8),
+            Expanded(
+              flex: 2,
+              child: Padding(
+                padding: const EdgeInsets.only(top: 30),
+                child: ElevatedButton(
+                  onPressed: () async {
+                    if (_busqueda.text.isNotEmpty) {
+                      await Provider.of<VariableViewModel>(context,
+                              listen: false)
+                          .fetchLocalByName(nombre: _busqueda.text.trim());
+                    } else {
+                      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
+                          content: Text('Introduce un nombre para buscar.')));
+                    }
+                  },
+                  style: ElevatedButton.styleFrom(
+                    shape: RoundedRectangleBorder(
+                      borderRadius: BorderRadius.circular(20.0),
+                    ),
+                    primary: AppTheme.tertiary,
+                    padding: const EdgeInsets.symmetric(vertical: 25),
+                  ),
+                  child: Text('Buscar',
+                      style: TextStyle(color: AppTheme.quaternary)),
+                ),
+              ),
+            ),
+          ],
+        ));
+  }
+}

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

@@ -21,6 +21,10 @@ class _VentaScreenState extends State<VentaScreen> {
   double totalDelDia = 0.0;
   double totalCancelados = 0.0;
 
+  double totalEfectivoDelDia = 0.0;
+  double totalTarjetaDelDia = 0.0;
+  double totalTransferenciaDelDia = 0.0;
+
   String formatCurrency(double amount) {
     final format = NumberFormat("#,##0.00", "es_MX");
     return format.format(amount);
@@ -195,6 +199,33 @@ class _VentaScreenState extends State<VentaScreen> {
                             fontSize: 20, fontWeight: FontWeight.bold),
                       ),
                     ),
+                    if (totalEfectivoDelDia > 0)
+                      Padding(
+                        padding: const EdgeInsets.all(16.0),
+                        child: Text(
+                          "Total en Efectivo: \$${formatCurrency(totalEfectivoDelDia)}",
+                          style: TextStyle(
+                              fontSize: 20, fontWeight: FontWeight.bold),
+                        ),
+                      ),
+                    if (totalTarjetaDelDia > 0)
+                      Padding(
+                        padding: const EdgeInsets.all(16.0),
+                        child: Text(
+                          "Total en Tarjeta: \$${formatCurrency(totalTarjetaDelDia)}",
+                          style: TextStyle(
+                              fontSize: 20, fontWeight: FontWeight.bold),
+                        ),
+                      ),
+                    if (totalTransferenciaDelDia > 0)
+                      Padding(
+                        padding: const EdgeInsets.all(16.0),
+                        child: Text(
+                          "Total en Transferencia: \$${formatCurrency(totalTransferenciaDelDia)}",
+                          style: TextStyle(
+                              fontSize: 20, fontWeight: FontWeight.bold),
+                        ),
+                      ),
                     if (totalCancelados > 0)
                       Padding(
                         padding: const EdgeInsets.all(16.0),
@@ -227,8 +258,19 @@ class _VentaScreenState extends State<VentaScreen> {
     final pedidosCancelados =
         pedidos.where((p) => p.estatus == "CANCELADO").toList();
 
-    totalDelDia = pedidosNoCancelados.fold(
-        0.0, (sum, current) => sum + (current.totalPedido ?? 0.0));
+    totalDelDia = 0.0;
+    totalCancelados = 0.0;
+    totalEfectivoDelDia = 0.0;
+    totalTarjetaDelDia = 0.0;
+    totalTransferenciaDelDia = 0.0;
+
+    for (var pedido in pedidosNoCancelados) {
+      totalDelDia += pedido.totalPedido ?? 0.0;
+      totalEfectivoDelDia += pedido.cantEfectivo ?? 0.0;
+      totalTarjetaDelDia += pedido.cantTarjeta ?? 0.0;
+      totalTransferenciaDelDia += pedido.cantTransferencia ?? 0.0;
+    }
+
     totalCancelados = pedidosCancelados.fold(
         0.0, (sum, current) => sum + (current.totalPedido ?? 0.0));
 

+ 25 - 4
lib/views/venta/venta_ticket.dart

@@ -31,12 +31,23 @@ class VentaTicket {
     double totalCancelados =
         pedidosCancelados.fold(0, (sum, p) => sum + (p.totalPedido ?? 0));
 
+    double totalEfectivo = pedidosNoCancelados.fold(
+        0.0, (sum, p) => sum + (p.cantEfectivo ?? 0.0));
+    double totalTarjeta =
+        pedidosNoCancelados.fold(0.0, (sum, p) => sum + (p.cantTarjeta ?? 0.0));
+    double totalTransferencia = pedidosNoCancelados.fold(
+        0.0, (sum, p) => sum + (p.cantTransferencia ?? 0.0));
+
     final spelling = SpellingNumber(lang: 'es');
     String totalEnLetras = toTitleCase(spelling.convert(totalNoCancelados));
 
     final numberFormat = NumberFormat('#,##0.00', 'en_US');
     String formattedTotalNoCancelados = numberFormat.format(totalNoCancelados);
     String formattedTotalCancelados = numberFormat.format(totalCancelados);
+    String formattedTotalEfectivo = numberFormat.format(totalEfectivo);
+    String formattedTotalTarjeta = numberFormat.format(totalTarjeta);
+    String formattedTotalTransferencia =
+        numberFormat.format(totalTransferencia);
 
     int centavos =
         ((totalNoCancelados - totalNoCancelados.floor()) * 100).round();
@@ -83,9 +94,19 @@ class VentaTicket {
                 child: pw.Column(
                   crossAxisAlignment: pw.CrossAxisAlignment.start,
                   children: [
-                    pw.Text("Total: \$${formattedTotalNoCancelados}",
+                    pw.Text("- Total en Efectivo: \$${formattedTotalEfectivo}",
+                        style: pw.TextStyle(
+                            fontWeight: pw.FontWeight.bold, fontSize: 11)),
+                    pw.Text("- Total en Tarjeta: \$${formattedTotalTarjeta}",
+                        style: pw.TextStyle(
+                            fontWeight: pw.FontWeight.bold, fontSize: 11)),
+                    pw.Text(
+                        "- Total en Transferencia: \$${formattedTotalTransferencia}",
+                        style: pw.TextStyle(
+                            fontWeight: pw.FontWeight.bold, fontSize: 11)),
+                    pw.Text("- Total General: \$${formattedTotalNoCancelados}",
                         style: pw.TextStyle(
-                            fontWeight: pw.FontWeight.bold, fontSize: 12)),
+                            fontWeight: pw.FontWeight.bold, fontSize: 11)),
                     pw.Text("Son: $totalEnLetras Pesos $centavosEnLetras",
                         style: pw.TextStyle(fontSize: 10))
                   ],
@@ -117,9 +138,9 @@ class VentaTicket {
                 pw.Padding(
                   padding: const pw.EdgeInsets.only(right: 15),
                   child: pw.Text(
-                      "Total Cancelados: \$${formattedTotalCancelados}",
+                      "- Total Cancelados: \$${formattedTotalCancelados}",
                       style: pw.TextStyle(
-                          fontWeight: pw.FontWeight.bold, fontSize: 12)),
+                          fontWeight: pw.FontWeight.bold, fontSize: 11)),
                 ),
               ],
               pw.SizedBox(height: 40),

+ 92 - 72
lib/widgets/app_drawer.dart

@@ -9,11 +9,13 @@ import 'package:yoshi_papas_app/views/pedido/pedido_screen.dart';
 import 'package:yoshi_papas_app/views/producto/producto_screen.dart';
 import 'package:yoshi_papas_app/views/toping/toping_screen.dart';
 import 'package:yoshi_papas_app/views/toping_categoria/toping_categoria_screen.dart';
+import 'package:yoshi_papas_app/views/variable/variable_screen.dart';
 import 'package:yoshi_papas_app/views/venta/venta_screen.dart';
 import '../models/usuario_model.dart';
 import 'package:provider/provider.dart';
 import '../themes/themes.dart';
 import '../viewmodels/login_view_model.dart';
+import '../views/descuento/descuento_screen.dart';
 import 'widgets_components.dart';
 
 class AppDrawer extends StatelessWidget {
@@ -52,8 +54,6 @@ class AppDrawer extends StatelessWidget {
   Widget build(BuildContext context) {
     String? nombre = Provider.of<LoginViewModel>(context).nombre.toString();
     String? correo = Provider.of<LoginViewModel>(context).correo.toString();
-    //final avm = Provider.of<AdministracionViewModel>(context);
-    //List<String> permisos = avm.lospermisos;
     return Drawer(
       surfaceTintColor: Colors.white,
       backgroundColor: Colors.white,
@@ -79,88 +79,108 @@ class AppDrawer extends StatelessWidget {
                 SizedBox(
                   height: 10,
                 ),
-                // Text(
-                //   nombre.toString(),
-                //   style: const TextStyle(
-                //     fontSize: 18,
-                //     fontWeight: FontWeight.bold,
-                //   ),
-                // ),
-                // const SizedBox(
-                //   height: 10,
-                // ),
-                // Text(
-                //   correo.toString(),
-                //   style: const TextStyle(
-                //     fontSize: 15,
-                //     fontWeight: FontWeight.bold,
-                //   ),
-                // ),
                 SizedBox(
                   height: 10,
                 ),
               ],
             ),
           ),
-          //HEADER
+          // HEADER
           Expanded(
-              child: ListView(children: [
-            ListTile(
-              leading: circulo(const Icon(Icons.restaurant_menu)),
-              title: const Text('Pedidos'),
-              onTap: () => {
-                Navigator.pop(context),
-                Navigator.of(context).push(
-                  MaterialPageRoute(
-                    builder: (context) => const PedidoScreen(),
-                  ),
+            child: ListView(
+              children: [
+                ListTile(
+                  leading: circulo(const Icon(Icons.restaurant_menu)),
+                  title: const Text('Pedidos'),
+                  onTap: () => {
+                    Navigator.pop(context),
+                    Navigator.of(context).push(
+                      MaterialPageRoute(
+                        builder: (context) => const PedidoScreen(),
+                      ),
+                    ),
+                  },
                 ),
-              },
-            ),
-            ListTile(
-              leading: circulo(const Icon(Icons.menu_book_rounded)),
-              title: const Text('Productos'),
-              onTap: () => {
-                Navigator.pop(context),
-                Navigator.of(context).push(
-                  MaterialPageRoute(
-                    builder: (context) => ProductoScreen(),
-                  ),
+                ListTile(
+                  leading: circulo(const Icon(Icons.menu_book_rounded)),
+                  title: const Text('Productos'),
+                  onTap: () => {
+                    Navigator.pop(context),
+                    Navigator.of(context).push(
+                      MaterialPageRoute(
+                        builder: (context) => ProductoScreen(),
+                      ),
+                    ),
+                  },
                 ),
-              },
-            ),
-            ListTile(
-              leading: circulo(const Icon(Icons.format_list_bulleted_rounded)),
-              title: const Text('Categoria Producto'),
-              onTap: () => {
-                Navigator.pop(context),
-                Navigator.of(context).push(
-                  MaterialPageRoute(
-                    builder: (context) => CategoriaProductoScreen(),
-                  ),
+                ListTile(
+                  leading:
+                      circulo(const Icon(Icons.format_list_bulleted_rounded)),
+                  title: const Text('Categoría Producto'),
+                  onTap: () => {
+                    Navigator.pop(context),
+                    Navigator.of(context).push(
+                      MaterialPageRoute(
+                        builder: (context) => CategoriaProductoScreen(),
+                      ),
+                    ),
+                  },
                 ),
-              },
-            ),
-            ListTile(
-              leading: circulo(const Icon(Icons.receipt_long_outlined)),
-              title: const Text('Pedidos Por Día'),
-              onTap: () => {
-                Navigator.pop(context),
-                Navigator.of(context).push(
-                  MaterialPageRoute(
-                    builder: (context) => VentaScreen(),
-                  ),
+                ListTile(
+                  leading: circulo(const Icon(Icons.receipt_long_outlined)),
+                  title: const Text('Pedidos Por Día'),
+                  onTap: () => {
+                    Navigator.pop(context),
+                    Navigator.of(context).push(
+                      MaterialPageRoute(
+                        builder: (context) => VentaScreen(),
+                      ),
+                    ),
+                  },
+                ),
+                ExpansionTile(
+                  leading: circulo(const Icon(Icons.admin_panel_settings)),
+                  title: const Text('Administración'),
+                  children: [
+                    ListTile(
+                      leading: circulo(const Icon(Icons.discount)),
+                      title: const Text('Descuentos'),
+                      onTap: () => {
+                        Navigator.pop(context),
+                        Navigator.of(context).push(
+                          MaterialPageRoute(
+                            builder: (context) => DescuentoScreen(),
+                          ),
+                        ),
+                      },
+                    ),
+                    ListTile(
+                      leading: circulo(const Icon(Icons.discount)),
+                      title: const Text('Variables'),
+                      onTap: () => {
+                        Navigator.pop(context),
+                        Navigator.of(context).push(
+                          MaterialPageRoute(
+                            builder: (context) => VariablesScreen(),
+                          ),
+                        ),
+                      },
+                    ),
+                  ],
                 ),
-              },
+              ],
             ),
-            // ListTile(
-            //   leading: const Icon(Icons.logout),
-            //   title: const Text('Cerrar sesión'),
-            //   onTap: () {
-            //     _showExitConfirmationDialog(context);
-            //   },
-            // ),
-          ]))
+          ),
+          Padding(
+            padding: const EdgeInsets.only(bottom: 10),
+            child: Align(
+              alignment: Alignment.bottomCenter,
+              child: Text(
+                'v1.24.08.13',
+                style: const TextStyle(fontWeight: FontWeight.w300),
+              ),
+            ),
+          ),
         ],
       ),
     );

+ 17 - 12
lib/widgets/app_dropdown_modelo.dart

@@ -34,22 +34,28 @@ class AppDropdownModel<T> extends StatelessWidget {
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
-        Text(
-          etiqueta.toString(),
-          style: TextStyle(
-            fontSize: fontSize,
-            fontWeight: FontWeight.bold,
+        if (etiqueta != null &&
+            etiqueta!
+                .isNotEmpty) // Verifica si la etiqueta no es nula y no está vacía
+          Text(
+            etiqueta!,
+            style: TextStyle(
+              fontSize: fontSize,
+              fontWeight: FontWeight.bold,
+            ),
+          ),
+        if (etiqueta != null &&
+            etiqueta!.isNotEmpty) // Solo muestra el espacio si hay una etiqueta
+          const SizedBox(
+            height: 5,
           ),
-        ),
-        const SizedBox(
-          height: 5,
-        ),
         Container(
           decoration: BoxDecoration(
               color: Colors.white, borderRadius: BorderRadius.circular(15)),
-          // width: size.width,
           child: DropdownButtonFormField(
-            hint: Text('$hint', style: dropdownTextStyle),
+            hint: Text(hint ?? '',
+                style:
+                    dropdownTextStyle), // Usa un hint vacío si no se proporciona
             style: dropdownTextStyle,
             borderRadius: BorderRadius.circular(10),
             icon: Icon(
@@ -57,7 +63,6 @@ class AppDropdownModel<T> extends StatelessWidget {
               color: AppTheme.primary,
             ),
             decoration: InputDecoration(
-              // labelText: 'Seleccione una salida reciente',
               floatingLabelStyle: TextStyle(
                 color: AppTheme.primary,
                 fontSize: fontSize,