import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import '../pedido/pedido_ticket.dart'; import '../../themes/themes.dart'; import '../../models/models.dart'; import '../../viewmodels/viewmodels.dart'; import 'package:collection/collection.dart'; import 'package:flutter/services.dart'; import '../../widgets/widgets.dart'; class PedidoForm extends StatefulWidget { @override _PedidoFormState createState() => _PedidoFormState(); } class _PedidoFormState extends State { final _busqueda = TextEditingController(text: ''); final TextEditingController _descuentoController = TextEditingController(); CategoriaProductoViewModel cvm = CategoriaProductoViewModel(); ProductoViewModel pvm = ProductoViewModel(); PedidoViewModel pedvm = PedidoViewModel(); bool _isLoading = false; CategoriaProducto? categoriaSeleccionada; List categorias = []; List productos = []; List carrito = []; Producto? _productoActual; bool _estadoBusqueda = false; Pedido? pedidoActual; ScrollController _listViewController = ScrollController(); ScrollController _categoryScrollController = 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; for (var item in carrito) { total += double.parse(item.producto.precio!) * item.cantidad; item.selectedToppings.forEach((categoryId, selectedToppingIds) { for (int toppingId in selectedToppingIds) { Producto? topping = item.selectableToppings[categoryId]?.firstWhere( (topping) => topping.id == toppingId, orElse: () => Producto(precio: '0')); if (topping != null) { total += (double.tryParse(topping.precio!) ?? 0) * item.cantidad; } } }); } 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(); cargarCategoriasIniciales().then((_) { if (categorias.isNotEmpty) { categoriaSeleccionada = categorias.first; cargarProductosPorCategoria(categoriaSeleccionada!.id); } }); Provider.of(context, listen: false).cargarDescuentos(); } void _onSearchChanged(String value) { if (value.isEmpty) { // Si no hay valor de búsqueda, cargar los productos iniciales y restaurar la categoría seleccionada. cargarProductosPorCategoria( categoriaSeleccionada?.id ?? categorias.first.id); } else { // Realiza la búsqueda y desactiva la categoría seleccionada para mostrar productos de todas las categorías. setState(() { _estadoBusqueda = true; // Indicamos que hay una búsqueda activa categoriaSeleccionada = null; // Ignoramos la categoría seleccionada }); Provider.of(context, listen: false) .fetchLocalByName(nombre: value); } } 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( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Pedido vacío', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22)), content: const Text( 'No puedes finalizar un pedido sin productos. Por favor, agrega al menos un producto.', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 18)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), actions: [ TextButton( style: TextButton.styleFrom( padding: EdgeInsets.fromLTRB(30, 20, 30, 20), foregroundColor: AppTheme.quaternary, backgroundColor: AppTheme.tertiary), onPressed: () { Navigator.of(context).pop(); }, child: const Text('Aceptar', style: TextStyle(fontSize: 18)), ), ], ); }, ); return; } await _promptForCustomerName(); } Future _promptForCustomerName() async { TextEditingController nombreController = TextEditingController(); TextEditingController comentarioController = TextEditingController(); String errorMessage = ''; double faltante = totalPedido; bool totalCompletado = false; bool efectivoCompleto = false; bool tarjetaCompleto = false; bool transferenciaCompleto = 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; } // Si el total ha sido alcanzado o excedido, desactivar otros métodos de pago if (totalPagado >= totalPedido) { if (!efectivoSeleccionado) efectivoSeleccionado = false; if (!tarjetaSeleccionada) tarjetaSeleccionada = false; if (!transferenciaSeleccionada) transferenciaSeleccionada = false; } }); } 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 _isPaymentOptionEnabled(bool isSelected) { return !totalCompletado || isSelected; } bool? shouldSave = await showDialog( context: context, builder: (BuildContext context) { return StatefulBuilder( builder: (context, setState) { return RawKeyboardListener( focusNode: FocusNode(), onKey: (RawKeyEvent event) { if (event.isKeyPressed(LogicalKeyboardKey.enter) && totalCompletado) { Navigator.of(context).pop(true); } }, child: AlertDialog( actionsPadding: EdgeInsets.fromLTRB(50, 10, 50, 30), title: const Text( 'Finalizar Pedido', style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500), ), content: Container( height: 600, child: Column( children: [ Expanded( child: SingleChildScrollView( child: Column( children: [ AppTextField( controller: nombreController, etiqueta: 'Nombre', hintText: "Nombre del Cliente", ), const SizedBox(height: 10), AppTextField( controller: comentarioController, etiqueta: 'Comentarios (opcional)', hintText: 'Comentarios', maxLines: 2, ), const SizedBox(height: 10), Align( alignment: Alignment.center, child: Text( 'Métodos de pago', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20), ), ), const SizedBox(height: 10), // Efectivo GestureDetector( onTap: () { if (_isPaymentOptionEnabled( efectivoSeleccionado)) { setState(() { efectivoSeleccionado = !efectivoSeleccionado; if (!efectivoSeleccionado) { efectivoCompleto = false; efectivoController.clear(); _calcularCambio(setState); } else if (efectivoCompleto) { efectivoController.text = totalPedido.toStringAsFixed(2); _calcularCambio(setState); } }); } }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( children: [ Checkbox( activeColor: AppTheme.primary, value: efectivoSeleccionado, onChanged: _isPaymentOptionEnabled( efectivoSeleccionado) ? (bool? value) { setState(() { efectivoSeleccionado = value ?? false; if (!efectivoSeleccionado) { efectivoCompleto = false; efectivoController .clear(); _calcularCambio( setState); } else if (efectivoCompleto) { efectivoController .text = totalPedido .toStringAsFixed( 2); _calcularCambio( setState); } }); } : null, ), const Text( "Efectivo", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold), ), ], ), if (efectivoSeleccionado) SizedBox( width: 180, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ const Text('Exacto', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black)), const SizedBox( height: 17, ), Checkbox( activeColor: AppTheme.primary, value: efectivoCompleto, onChanged: efectivoSeleccionado ? (bool? value) { setState(() { efectivoCompleto = value ?? false; if (efectivoCompleto) { efectivoController .text = totalPedido .toStringAsFixed(2); _calcularCambio( setState); } else { efectivoController .clear(); _calcularCambio( setState); } }); } : null, ), ], ), const SizedBox( width: 5, ), Expanded( child: AppTextField( controller: efectivoController, etiqueta: 'Cantidad', hintText: '0.00', keyboardType: TextInputType.number, onChanged: (value) => _calcularCambio(setState), ), ), ], ), ), ], ), ), const SizedBox(height: 10), // Tarjeta GestureDetector( onTap: () { if (_isPaymentOptionEnabled( tarjetaSeleccionada)) { setState(() { tarjetaSeleccionada = !tarjetaSeleccionada; if (!tarjetaSeleccionada) { tarjetaController.clear(); _calcularCambio(setState); } }); } }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( children: [ Checkbox( activeColor: AppTheme.primary, value: tarjetaSeleccionada, onChanged: _isPaymentOptionEnabled( tarjetaSeleccionada) ? (bool? value) { setState(() { tarjetaSeleccionada = value ?? false; if (!tarjetaSeleccionada) { tarjetaController .clear(); _calcularCambio( setState); } }); } : null, ), const Text( "Tarjeta", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold), ), ], ), if (tarjetaSeleccionada) SizedBox( width: 180, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ const Text('Exacto', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black)), const SizedBox( height: 17, ), Checkbox( activeColor: AppTheme.primary, value: tarjetaCompleto, onChanged: tarjetaSeleccionada ? (bool? value) { setState(() { tarjetaCompleto = value ?? false; if (tarjetaCompleto) { tarjetaController .text = totalPedido .toStringAsFixed(2); _calcularCambio( setState); } else { tarjetaController .clear(); _calcularCambio( setState); } }); } : null, ), ], ), const SizedBox( width: 5, ), Expanded( child: AppTextField( controller: tarjetaController, etiqueta: 'Cantidad', hintText: '0.00', keyboardType: TextInputType.number, onChanged: (value) { _validarCantidad(setState, tarjetaController); }, ), ), ], ), ), ], ), ), const SizedBox(height: 10), // Transferencia GestureDetector( onTap: () { if (_isPaymentOptionEnabled( transferenciaSeleccionada)) { setState(() { transferenciaSeleccionada = !transferenciaSeleccionada; if (!transferenciaSeleccionada) { transferenciaController.clear(); _calcularCambio(setState); } }); } }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( children: [ Checkbox( activeColor: AppTheme.primary, value: transferenciaSeleccionada, onChanged: _isPaymentOptionEnabled( transferenciaSeleccionada) ? (bool? value) { setState(() { transferenciaSeleccionada = value ?? false; if (!transferenciaSeleccionada) { transferenciaController .clear(); _calcularCambio( setState); } }); } : null, ), const Text( "Transferencia", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold), ), ], ), if (transferenciaSeleccionada) SizedBox( width: 180, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ const Text('Exacto', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black)), const SizedBox( height: 17, ), Checkbox( activeColor: AppTheme.primary, value: transferenciaCompleto, onChanged: transferenciaSeleccionada ? (bool? value) { setState(() { transferenciaCompleto = value ?? false; if (transferenciaCompleto) { transferenciaController .text = totalPedido .toStringAsFixed(2); _calcularCambio( setState); } else { transferenciaController .clear(); _calcularCambio( setState); } }); } : null, ), ], ), const SizedBox( width: 5, ), Expanded( child: AppTextField( controller: transferenciaController, etiqueta: 'Cantidad', hintText: '0.00', keyboardType: TextInputType.number, onChanged: (value) { _validarCantidad(setState, transferenciaController); }, ), ), ], ), ), ], ), ), const SizedBox(height: 10), // Mostrar el total del pedido y la cantidad faltante Align( alignment: Alignment.centerRight, child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( 'Total del pedido: \$${totalPedido.toStringAsFixed(2)}', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18), ), if (faltante > 0) Text( 'Faltante: \$${faltante.toStringAsFixed(2)}', style: const TextStyle( color: Colors.red, fontSize: 18, fontWeight: FontWeight.bold), ) else if (cambio > 0) Text( 'Cambio: \$${cambio.toStringAsFixed(2)}', style: const TextStyle( color: Colors.green, fontSize: 18, fontWeight: FontWeight.bold)), ]), ), ], ), ), ), // Aquí mantenemos los botones fijos Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( child: const Text('Cancelar', style: TextStyle(fontSize: 18)), onPressed: () { Navigator.of(context).pop(false); }, style: ButtonStyle( padding: MaterialStatePropertyAll( EdgeInsets.fromLTRB(30, 20, 30, 20)), backgroundColor: MaterialStatePropertyAll(Colors.red), foregroundColor: MaterialStatePropertyAll( AppTheme.secondary)), ), const SizedBox(width: 100), TextButton( child: const Text('Guardar', style: TextStyle(fontSize: 18)), onPressed: totalCompletado ? () { Navigator.of(context).pop(true); } : null, style: ButtonStyle( padding: MaterialStatePropertyAll( EdgeInsets.fromLTRB(30, 20, 30, 20)), backgroundColor: MaterialStatePropertyAll( totalCompletado ? AppTheme.tertiary : Colors.grey), foregroundColor: MaterialStatePropertyAll( AppTheme.quaternary)), ), ], ), ], ), ), )); }, ); }, ); if (shouldSave ?? false) { prepararPedidoActual(nombreController.text, comentarioController.text); } } void prepararPedidoActual(String nombreCliente, String comentarios) async { DateTime now = DateTime.now(); String formattedDate = DateFormat('dd-MM-yyyy kk:mm:ss').format(now); 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 listaPedidoProducto = carrito.map((item) { List selectedToppings = []; item.selectedToppings.forEach((categoryId, selectedToppingIds) { for (int toppingId in selectedToppingIds) { selectedToppings.add(PedidoProductoTopping( idTopping: toppingId, )); } }); return PedidoProducto( idProducto: item.producto.id, producto: item.producto, costoUnitario: item.producto.precio, cantidad: item.cantidad, comentario: comentarios, toppings: selectedToppings, ); }).toList(); nuevoPedido.productos = listaPedidoProducto; bool result = await Provider.of(context, listen: false) .guardarPedidoLocal(pedido: nuevoPedido); if (!mounted) return; if (result) { Pedido? pedidoCompleto = await Provider.of(context, listen: false) .fetchPedidoConProductos(nuevoPedido.id!); if (pedidoCompleto != null) { imprimirTicketsJuntos(context, pedidoCompleto); } Navigator.of(context).pop(); } else { print("Error al guardar el pedido"); } } String _obtenerTipoPago() { List 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 = ''; _estadoBusqueda = false; }); await cargarCategoriasIniciales(); } Future cargarProductosIniciales() async { setState(() => _isLoading = true); await Provider.of(context, listen: false) .fetchLocalAll(); productos = Provider.of(context, listen: false).productos; setState(() => _isLoading = false); } @override void dispose() { _listViewController.dispose(); _searchController.dispose(); _categoryScrollController.dispose(); super.dispose(); } Future cargarCategoriasIniciales() async { setState(() => _isLoading = true); await Provider.of(context, listen: false) .fetchLocalAll(); categorias = Provider.of(context, listen: false) .categoriaProductos; if (categorias.isNotEmpty) { categoriaSeleccionada = categorias.first; cargarProductosPorCategoria(categoriaSeleccionada!.id); } setState(() => _isLoading = false); if (categorias.isNotEmpty) { categoriaSeleccionada = categorias.first; } } void cargarProductosPorCategoria(int categoriaId) async { setState(() => _isLoading = true); await Provider.of(context, listen: false) .fetchAllByCategory(categoriaId); productos = Provider.of(context, listen: false).productos; setState(() => _isLoading = false); } void agregarAlCarrito(Producto producto) async { var existente = carrito.firstWhereOrNull((item) => item.producto.id == producto.id && mapEquals(item.selectedToppings, {})); if (existente != null) { setState(() { existente.cantidad++; }); } else { Map> toppingsSeleccionables = await obtenerToppingsSeleccionables(producto); setState(() { carrito.add(ItemCarrito( producto: producto, cantidad: 1, selectableToppings: toppingsSeleccionables, )); }); } _recalcularTotal(); } Future>> obtenerToppingsSeleccionables( Producto producto) async { Map> toppingsSeleccionables = {}; final toppingCategories = await pvm.obtenerToppingsPorProducto(producto.id!); for (int toppingId in toppingCategories) { Producto? topping = await pvm.obtenerProductoPorId(toppingId); if (topping != null && topping.idCategoria != null) { toppingsSeleccionables[topping.idCategoria!] ??= []; toppingsSeleccionables[topping.idCategoria]!.add(topping); } } return toppingsSeleccionables; } void quitarDelCarrito(Producto producto) { setState(() { var indice = carrito.indexWhere((item) => item.producto.id == producto.id); if (indice != -1) { if (carrito[indice].cantidad > 1) { carrito[indice].cantidad--; } else { carrito.removeAt(indice); } } }); } void addToCart(Producto producto) { var existingIndex = carrito.indexWhere((item) => item.producto.id == producto.id && mapEquals(item.selectedToppings, {})); if (existingIndex != -1) { setState(() { carrito[existingIndex].cantidad++; }); } else { setState(() { carrito.add(ItemCarrito(producto: producto, cantidad: 1)); }); } } void finalizeCustomization() { if (_productoActual != null) { addToCart(_productoActual!); setState(() { _productoActual = null; }); } } 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( builder: (context, viewModel, child) { return AppDropdownModel( hint: 'Seleccionar', items: viewModel.descuentos .map( (descuento) => DropdownMenuItem( 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( appBar: AppBar( title: Text("Crear Pedido", style: TextStyle(color: AppTheme.secondary)), iconTheme: IconThemeData(color: AppTheme.secondary)), body: Row( children: [ Flexible( flex: 3, child: _buildCartSection(), ), SizedBox(width: 35), Flexible(flex: 7, child: _buildProductsSection()), ], ), ); } Widget _buildCartSection() { return Card( margin: const EdgeInsets.all(8.0), child: Column( children: [ const Padding( padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Producto', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), Text('Cantidad', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), ], ), ), Expanded( child: ListView.builder( itemCount: carrito.length, itemBuilder: (context, index) { final item = carrito[index]; return Column( children: [ ListTile( title: Text(item.producto.nombre!, style: const TextStyle(fontWeight: FontWeight.w600)), subtitle: Text('\$${item.producto.precio}', style: const TextStyle( fontWeight: FontWeight.bold, color: Color(0xFF008000))), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => eliminarProductoDelCarrito(index)), IconButton( icon: const Icon(Icons.remove), onPressed: () => quitarProductoDelCarrito(item)), const SizedBox(width: 5), Text('${item.cantidad}', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(width: 5), IconButton( icon: const Icon(Icons.add), onPressed: () => incrementarProducto(item)), ], ), ), if (item.selectableToppings.isNotEmpty) ExpansionTile( title: Text('Toppings', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16)), children: item.selectableToppings.entries.map((entry) { final categoryId = entry.key; final availableToppings = entry.value; final categoria = categorias.firstWhere((c) => c.id == categoryId); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(left: 16.0), child: Text( categoria.descripcion!, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16), ), ), ...availableToppings.map((topping) { ValueNotifier isSelectedNotifier = ValueNotifier( item.selectedToppings[categoryId] ?.contains(topping.id) ?? false, ); return ValueListenableBuilder( valueListenable: isSelectedNotifier, builder: (context, isSelected, _) { return CheckboxListTile( activeColor: AppTheme.primary, title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(topping.nombre!), if (double.tryParse( topping.precio!) != 0.0) Text( '+\$${topping.precio}', style: TextStyle( color: Colors.black, fontSize: 13, ), ), ], ), value: isSelected, onChanged: (bool? value) { final maximoToppings = categoria.maximo ?? 0; if (value == true) { if ((item.selectedToppings[categoryId] ?.length ?? 0) >= maximoToppings) { item.selectedToppings[categoryId]! .remove(item .selectedToppings[ categoryId]! .first); } item.selectedToppings[categoryId] ??= {}; item.selectedToppings[categoryId]! .add(topping.id!); } else { item.selectedToppings[categoryId] ?.remove(topping.id!); if (item.selectedToppings[categoryId] ?.isEmpty ?? false) { item.selectedToppings .remove(categoryId); } } setState(() {}); _recalcularTotal(); }, ); }, ); }).toList(), ], ); }).toList(), ), Divider(), ], ); }, ), ), _buildDiscountSection(), const Divider(thickness: 5), _buildTotalSection(), const SizedBox(height: 25), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: _finalizeOrder, style: ElevatedButton.styleFrom( primary: AppTheme.tertiary, textStyle: const TextStyle(fontSize: 22), fixedSize: const Size(250, 50), ), child: Text('Finalizar Pedido', style: TextStyle(color: AppTheme.quaternary)), ), ), ], ), ); } void eliminarProductoDelCarrito(int index) { setState(() { carrito.removeAt(index); }); _recalcularTotal(); } void incrementarProducto(ItemCarrito item) { setState(() { item.cantidad++; }); _recalcularTotal(); } void quitarProductoDelCarrito(ItemCarrito item) { setState(() { if (item.cantidad > 1) { item.cantidad--; } else { carrito.remove(item); } }); _recalcularTotal(); } Widget _buildProductsSection() { return Column( children: [ const SizedBox(height: 5), _buildSearchBar(), const SizedBox(height: 10), _buildCategoryButtons(), const SizedBox(height: 15), Expanded( child: Consumer(builder: (context, model, child) { productos = model.productos; return Scrollbar( controller: _listViewController, thumbVisibility: true, trackVisibility: true, child: ListView.builder( controller: _listViewController, itemCount: productos.length, itemBuilder: (context, index) { final producto = productos[index]; return Card( child: ListTile( title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( producto.nombre ?? '', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), textAlign: TextAlign.left, ), Text( '\$${producto.precio}', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.green, ), textAlign: TextAlign.right, ), ], ), onTap: () => agregarAlCarrito(producto), ), ); }, ), ); }), ), ], ); } Widget _buildCategoryButtons() { List categoriasFiltradas = categorias.where((categoria) => categoria.esToping == 0).toList(); return Container( height: 50, child: Scrollbar( thumbVisibility: true, trackVisibility: true, interactive: true, controller: _categoryScrollController, child: ListView.builder( controller: _categoryScrollController, scrollDirection: Axis.horizontal, itemCount: categoriasFiltradas.length, itemBuilder: (context, index) { final categoria = categoriasFiltradas[index]; bool isSelected = categoriaSeleccionada?.id == categoria.id; return Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: ElevatedButton( onPressed: () { cargarProductosPorCategoria(categoria.id); setState(() { categoriaSeleccionada = categoria; }); }, style: ElevatedButton.styleFrom( primary: isSelected ? AppTheme.tertiary : Colors.grey, foregroundColor: isSelected ? AppTheme.quaternary : AppTheme.secondary, onPrimary: AppTheme.secondary, ), child: Text(categoria.nombre!), ), ); }, ), ), ); } Widget _buildSearchBar() { return Padding( padding: const EdgeInsets.all(8.0), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Buscar producto...', prefixIcon: const Icon(Icons.search), suffixIcon: IconButton( icon: Icon(Icons.clear), onPressed: () { _searchController.clear(); _onSearchChanged(''); }, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0), borderSide: BorderSide(width: 1.5)), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.black), borderRadius: BorderRadius.circular(12.0), ), ), onChanged: _onSearchChanged, ), ); } }