123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389 |
- import 'dart:async';
- import 'dart:io';
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:intl/intl.dart';
- import 'package:provider/provider.dart';
- import 'package:yoshi_papas_app/views/pedido/pedido_ticket.dart';
- import '../../themes/themes.dart';
- import '../../models/models.dart';
- import '../../viewmodels/viewmodels.dart';
- import 'package:collection/collection.dart';
- import '../../widgets/widgets.dart';
- class PedidoForm extends StatefulWidget {
- @override
- _PedidoFormState createState() => _PedidoFormState();
- }
- class _PedidoFormState extends State<PedidoForm> {
- final _busqueda = TextEditingController(text: '');
- final TextEditingController _descuentoController = TextEditingController();
- CategoriaProductoViewModel cvm = CategoriaProductoViewModel();
- ProductoViewModel pvm = ProductoViewModel();
- PedidoViewModel pedvm = PedidoViewModel();
- bool _isLoading = false;
- CategoriaProducto? categoriaSeleccionada;
- List<CategoriaProducto> categorias = [];
- List<Producto> productos = [];
- List<ItemCarrito> carrito = [];
- Producto? _productoActual;
- bool _estadoBusqueda = false;
- Pedido? pedidoActual;
- ScrollController _gridViewController = 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 += 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 += (topping.precio ?? 0.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<DescuentoViewModel>(context, listen: false).cargarDescuentos();
- }
- void _onSearchChanged(String value) async {
- if (value.isEmpty) {
- cargarProductosPorCategoria(
- categoriaSeleccionada?.id ?? categorias.first.id);
- } else {
- setState(() {
- _estadoBusqueda = true;
- });
- await Provider.of<ProductoViewModel>(context, listen: false)
- .fetchLocalByName(nombre: value);
- setState(() {
- productos =
- Provider.of<ProductoViewModel>(context, listen: false).productos;
- });
- }
- }
- 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: <Widget>[
- 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<void> _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<bool>(
- 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 {
- String now = DateTime.now().toUtc().toIso8601String();
- Pedido nuevoPedido = Pedido(
- peticion: now,
- nombreCliente: nombreCliente,
- comentarios: comentarios,
- estatus: "TERMINADO",
- 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) {
- List<PedidoProductoTopping> selectedToppings = [];
- item.selectedToppings.forEach((categoryId, selectedToppingIds) {
- for (int toppingId in selectedToppingIds) {
- selectedToppings.add(PedidoProductoTopping(
- idCategoria: categoryId,
- idTopping: toppingId,
- ));
- }
- });
- return PedidoProducto(
- idProducto: item.producto.id,
- producto: item.producto,
- costoUnitario: item.producto.precio.toString(),
- cantidad: item.cantidad,
- comentario: comentarios,
- toppings: selectedToppings,
- );
- }).toList();
- nuevoPedido.productos = listaPedidoProducto;
- bool result = await Provider.of<PedidoViewModel>(context, listen: false)
- .guardarPedidoLocal(pedido: nuevoPedido);
- if (!mounted) return;
- if (result) {
- Pedido? pedidoCompleto =
- await Provider.of<PedidoViewModel>(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<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 = '';
- _estadoBusqueda = false;
- });
- await cargarCategoriasIniciales();
- }
- Future<void> cargarProductosIniciales() async {
- setState(() => _isLoading = true);
- await Provider.of<ProductoViewModel>(context, listen: false)
- .fetchLocalAll();
- productos =
- Provider.of<ProductoViewModel>(context, listen: false).productos;
- setState(() => _isLoading = false);
- }
- @override
- void dispose() {
- _gridViewController.dispose();
- _searchController.dispose();
- _categoryScrollController.dispose();
- super.dispose();
- }
- Future<void> cargarCategoriasIniciales() async {
- setState(() => _isLoading = true);
- await Provider.of<CategoriaProductoViewModel>(context, listen: false)
- .fetchLocalAll();
- categorias = Provider.of<CategoriaProductoViewModel>(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<ProductoViewModel>(context, listen: false)
- .fetchAllByCategory(categoriaId);
- productos =
- Provider.of<ProductoViewModel>(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<int, List<Producto>> toppingsSeleccionables =
- await obtenerToppingsSeleccionables(producto);
- setState(() {
- carrito.add(ItemCarrito(
- producto: producto,
- cantidad: 1,
- selectableToppings: toppingsSeleccionables,
- ));
- });
- }
- _recalcularTotal();
- }
- Future<Map<int, List<Producto>>> obtenerToppingsSeleccionables(
- Producto producto) async {
- Map<int, List<Producto>> 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<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(
- 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)),
- ],
- ),
- ),
- // ExpansionTile para todos los toppings
- if (item.selectableToppings.isNotEmpty)
- ExpansionTile(
- initiallyExpanded: true,
- title: const Text(
- 'Toppings',
- style: 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 ExpansionTile(
- initiallyExpanded: true,
- title: Text(
- '${categoria.nombre} (Hasta ${categoria.maximo ?? 0})',
- style: const TextStyle(
- fontWeight: FontWeight.bold, fontSize: 16),
- ),
- children: availableToppings.map((topping) {
- bool isSelected = item
- .selectedToppings[categoryId]
- ?.contains(topping.id) ??
- false;
- return CheckboxListTile(
- activeColor: AppTheme.primary,
- title: Row(
- mainAxisAlignment:
- MainAxisAlignment.spaceBetween,
- children: [
- Text(topping.nombre!),
- if (topping.precio != 0.0)
- Text(
- '+\$${topping.precio}',
- style: const TextStyle(
- color: Colors.black,
- fontSize: 13,
- ),
- ),
- ],
- ),
- value: isSelected,
- onChanged: (bool? value) {
- final maximoToppings = categoria.maximo ?? 0;
- setState(() {
- if (value == true) {
- if ((item.selectedToppings[categoryId]
- ?.length ??
- 0) >=
- maximoToppings) {
- item.selectedToppings[categoryId]!
- .remove(item
- .selectedToppings[categoryId]!
- .first);
- }
- item.selectedToppings[categoryId] ??=
- <int>{};
- item.selectedToppings[categoryId]!
- .add(topping.id!);
- } else {
- item.selectedToppings[categoryId]
- ?.remove(topping.id!);
- if (item.selectedToppings[categoryId]
- ?.isEmpty ??
- false) {
- item.selectedToppings
- .remove(categoryId);
- }
- }
- _recalcularTotal();
- });
- },
- );
- }).toList(),
- );
- }).toList(),
- ),
- const 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(
- backgroundColor: 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<ProductoViewModel>(builder: (context, model, child) {
- productos = model.productos;
- return GridView.builder(
- controller: _gridViewController,
- key: ValueKey<int>(categoriaSeleccionada?.id ?? 0),
- gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 3,
- childAspectRatio: 3 / 2,
- ),
- itemCount: productos.length,
- itemBuilder: (context, index) {
- final producto = productos[index];
- // Si no se está buscando, aplicar el filtro de categoría
- if (!_estadoBusqueda &&
- producto.idCategoria != categoriaSeleccionada?.id) {
- return Container();
- }
- return Card(
- child: InkWell(
- onTap: () => agregarAlCarrito(producto),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- if (producto.imagen != null &&
- File(producto.imagen!).existsSync())
- Image.file(
- File(producto.imagen!),
- height: 120,
- fit: BoxFit.cover,
- )
- else
- const Icon(Icons.fastfood, size: 80),
- const SizedBox(height: 8),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8.0),
- child: Text(
- producto.nombre ?? '',
- style: const TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.bold,
- ),
- textAlign: TextAlign.center,
- ),
- ),
- const SizedBox(height: 8),
- Text(
- '\$${producto.precio}',
- style: const TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.bold,
- color: Color(0xFF008000),
- ),
- ),
- ],
- ),
- ),
- );
- },
- );
- }),
- )
- ],
- );
- }
- Widget _buildCategoryButtons() {
- List<CategoriaProducto> 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(
- backgroundColor: isSelected ? AppTheme.tertiary : Colors.grey,
- foregroundColor:
- isSelected ? AppTheme.quaternary : 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,
- ),
- );
- }
- }
|