1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999 |
- import 'dart:async';
- import 'dart:io';
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:intl/intl.dart';
- import 'package:otp/otp.dart';
- import 'package:provider/provider.dart';
- import '/data/session/session_storage.dart';
- import '/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';
- import 'package:uuid/uuid.dart';
- import '../../services/services.dart';
- import 'package:timezone/data/latest.dart' as timezone;
- import 'package:timezone/timezone.dart' as timezone;
- class PedidoForm extends StatefulWidget {
- final Pedido? pedidoExistente;
- const PedidoForm({Key? key, this.pedidoExistente}) : super(key: key);
- @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();
- TextEditingController _propinaCantidadController = TextEditingController();
- TextEditingController _propinaComentarioController = TextEditingController();
- double cambio = 0.0;
- double faltante = 0.0;
- bool totalCompletado = false;
- bool _isMesasActive = false;
- 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();
- pedidoActual = widget.pedidoExistente ?? Pedido(id: 0);
- Future.microtask(() async {
- bool isMesasActive =
- await Provider.of<VariableViewModel>(context, listen: false)
- .isVariableActive('MESAS');
- setState(() {
- _isMesasActive = isMesasActive;
- });
- if (pedidoActual != null && pedidoActual!.id! > 0) {
- await _cargarPedidoExistente(pedidoActual!.id!);
- }
- });
- cargarCategoriasIniciales().then((_) {
- if (categorias.isNotEmpty) {
- categoriaSeleccionada = categorias.first;
- cargarProductosPorCategoria(categoriaSeleccionada!.id);
- }
- });
- Provider.of<DescuentoViewModel>(context, listen: false).cargarDescuentos();
- }
- Future<void> _cargarPedidoExistente(int pedidoId) async {
- Pedido? pedidoCompleto =
- await Provider.of<PedidoViewModel>(context, listen: false)
- .fetchPedidoConProductos(pedidoId);
- if (pedidoCompleto != null) {
- setState(() {
- pedidoActual = pedidoCompleto;
- carrito = pedidoCompleto.productos.map((producto) {
- return ItemCarrito(
- producto: producto.producto!,
- cantidad: producto.cantidad!,
- comentario: producto.comentario,
- selectedToppings: producto.toppings.fold<Map<int, Set<int>>>(
- {},
- (acc, topping) {
- acc[topping.idCategoria!] ??= {};
- acc[topping.idCategoria]!.add(topping.idTopping!);
- return acc;
- },
- ),
- );
- }).toList();
- });
- selectedDescuento = pedidoCompleto.descuento ?? 0;
- _recalcularTotal();
- }
- }
- 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');
- }
- bool validarMinimosSeleccionados() {
- for (var item in carrito) {
- for (var categoriaId in item.selectableToppings.keys) {
- final categoria = categorias.firstWhere((c) => c.id == categoriaId);
- final minimoRequerido = categoria.minimo ?? 0;
- final seleccionados = item.selectedToppings[categoriaId]?.length ?? 0;
- if (minimoRequerido > 0 && seleccionados < minimoRequerido) {
- showDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog(
- title: const Text('Faltan Toppings',
- style:
- TextStyle(fontWeight: FontWeight.w500, fontSize: 22)),
- content: Text(
- 'El producto ${item.producto.nombre} requiere que seleccione al menos $minimoRequerido topping en la categoría ${categoria.nombre}.',
- style: TextStyle(fontSize: 18)),
- actions: <Widget>[
- TextButton(
- onPressed: () => Navigator.of(context).pop(),
- child: const Text('Aceptar'),
- style: ButtonStyle(
- padding: WidgetStatePropertyAll(
- EdgeInsets.fromLTRB(20, 10, 20, 10)),
- backgroundColor:
- WidgetStatePropertyAll(AppTheme.tertiary),
- foregroundColor:
- WidgetStatePropertyAll(AppTheme.quaternary))),
- ],
- );
- },
- );
- return false;
- }
- }
- }
- return true;
- }
- 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;
- }
- if (!validarMinimosSeleccionados()) {
- return;
- }
- await _promptForCustomerName();
- }
- void _mostrarDialogoPedidoVacio() {
- 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 crear un pedido sin productos. Por favor, agrega al menos un producto.',
- style: TextStyle(fontSize: 18)),
- shape:
- RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
- actions: <Widget>[
- TextButton(
- style: TextButton.styleFrom(
- padding: const 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)),
- ),
- ],
- );
- },
- );
- }
- Future<void> _crearPedidoConModal() async {
- if (carrito.isEmpty) {
- _mostrarDialogoPedidoVacio();
- return;
- }
- TextEditingController nombreController = TextEditingController();
- TextEditingController descripcionController = TextEditingController();
- TextEditingController mesaSearchController = TextEditingController();
- Mesa? mesaSeleccionada;
- // Inicializa las mesas disponibles
- await Provider.of<MesaViewModel>(context, listen: false).fetchLocalAll();
- List<Mesa> mesasDisponibles =
- Provider.of<MesaViewModel>(context, listen: false).mesas;
- bool? shouldSave = await showDialog<bool>(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog(
- title: const Text(
- 'Crear Pedido',
- style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
- ),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- AppTextField(
- controller: nombreController,
- etiqueta: 'Nombre del Cliente',
- hintText: 'Nombre del Cliente',
- ),
- const SizedBox(height: 10),
- AppTextField(
- controller: descripcionController,
- etiqueta: 'Descripción',
- hintText: 'Descripción del Pedido',
- ),
- const SizedBox(height: 10),
- AppDropdownSearch(
- controller: mesaSearchController,
- etiqueta: 'Seleccionar Mesa',
- asyncItems: (String query) async {
- await Provider.of<MesaViewModel>(context, listen: false)
- .fetchLocalByName(nombre: query);
- return Provider.of<MesaViewModel>(context, listen: false)
- .mesas;
- },
- itemAsString: (dynamic mesa) =>
- (mesa as Mesa).nombre ?? 'Sin Nombre',
- selectedItem: mesaSeleccionada,
- onChanged: (dynamic nuevaMesa) {
- mesaSeleccionada = nuevaMesa as Mesa;
- },
- ),
- ],
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.of(context).pop(false),
- child: const Text('Cancelar'),
- style: ButtonStyle(
- padding: WidgetStatePropertyAll(
- EdgeInsets.fromLTRB(20, 10, 20, 10),
- ),
- backgroundColor: WidgetStatePropertyAll(Colors.red),
- foregroundColor: WidgetStatePropertyAll(AppTheme.secondary),
- ),
- ),
- const SizedBox(width: 10),
- TextButton(
- onPressed: () {
- if (mesaSeleccionada != null) {
- Navigator.of(context).pop(true);
- } else {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('Seleccione una mesa por favor'),
- ),
- );
- }
- },
- child: const Text('Guardar'),
- style: ButtonStyle(
- padding: WidgetStatePropertyAll(
- EdgeInsets.fromLTRB(20, 10, 20, 10),
- ),
- backgroundColor: WidgetStatePropertyAll(Colors.black),
- foregroundColor: WidgetStatePropertyAll(AppTheme.quaternary),
- ),
- ),
- ],
- );
- },
- );
- if (shouldSave ?? false) {
- Pedido nuevoPedido = Pedido(
- peticion: DateTime.now().toUtc().toIso8601String(),
- nombreCliente: nombreController.text,
- comentarios: descripcionController.text,
- estatus: 'NUEVO',
- idMesa: mesaSeleccionada?.id,
- totalPedido: totalPedido,
- descuento: selectedDescuento,
- uuid: Uuid().v4(),
- idUsuario: await SessionStorage().getId(),
- productos: carrito.map((item) {
- return PedidoProducto(
- idProducto: item.producto.id,
- producto: item.producto,
- costoUnitario: item.producto.precio.toString(),
- cantidad: item.cantidad,
- comentario: item.comentario,
- toppings: item.selectedToppings.entries.expand((entry) {
- return entry.value.map((toppingId) {
- return PedidoProductoTopping(
- idCategoria: entry.key,
- idTopping: toppingId,
- );
- });
- }).toList(),
- );
- }).toList(),
- );
- final corteViewModel =
- Provider.of<CorteCajaViewModel>(context, listen: false);
- CorteCaja? corteActivo = corteViewModel.cortes.firstWhereOrNull(
- (corte) => corte.fechaCorte == null,
- );
- if (corteActivo != null) {
- nuevoPedido.idCorteCaja = corteActivo.id;
- }
- bool result = await Provider.of<PedidoViewModel>(context, listen: false)
- .guardarPedidoLocal(pedido: nuevoPedido);
- if (result) {
- Navigator.of(context).pop();
- } else {
- print('Error al guardar el pedido');
- }
- }
- }
- Future<void> _guardarPedidoExistente({
- double? cantEfectivo,
- double? cantTarjeta,
- double? cantTransferencia,
- String? tipoPago,
- String? estatus,
- }) async {
- if (pedidoActual == null) return;
- DateTime now = DateTime.now().toUtc();
- pedidoActual!.totalPedido = totalPedido;
- pedidoActual!.descuento = selectedDescuento;
- pedidoActual!.modificado = now;
- pedidoActual!.sincronizado = null;
- if (cantEfectivo != null) pedidoActual!.cantEfectivo = cantEfectivo;
- if (cantTarjeta != null) pedidoActual!.cantTarjeta = cantTarjeta;
- if (cantTransferencia != null)
- pedidoActual!.cantTransferencia = cantTransferencia;
- if (tipoPago != null) pedidoActual!.tipoPago = tipoPago;
- if (estatus != null) pedidoActual!.estatus = estatus;
- bool result = await Provider.of<PedidoViewModel>(context, listen: false)
- .guardarPedidoConProductos(
- pedido: pedidoActual!,
- carrito: carrito,
- );
- if (result) {
- Pedido? pedidoCompleto =
- await Provider.of<PedidoViewModel>(context, listen: false)
- .fetchPedidoConProductos(pedidoActual!.id);
- if (estatus == "TERMINADO") {
- imprimirTicketsJuntos(context, pedidoCompleto!);
- }
- print("Pedido actualizado correctamente");
- Navigator.of(context).pop();
- } else {
- print("Error al actualizar el pedido");
- }
- }
- Future<void> _promptForCustomerName() async {
- TextEditingController efectivoController = TextEditingController();
- TextEditingController tarjetaController = TextEditingController();
- TextEditingController transferenciaController = TextEditingController();
- TextEditingController? nombreController;
- TextEditingController? comentarioController;
- if (pedidoActual == null || pedidoActual!.id == 0) {
- nombreController = TextEditingController();
- comentarioController = TextEditingController();
- }
- faltante = totalPedido;
- bool totalCompletado = false;
- bool efectivoCompleto = false;
- bool tarjetaCompleto = false;
- bool transferenciaCompleto = false;
- bool propinaExpandida = false;
- void _calcularCambio(StateSetter setState) {
- double totalPagado = (double.tryParse(efectivoController.text) ?? 0) +
- (double.tryParse(tarjetaController.text) ?? 0) +
- (double.tryParse(transferenciaController.text) ?? 0);
- setState(() {
- cambio = totalPagado - totalPedido;
- faltante = cambio < 0 ? totalPedido - totalPagado : 0;
- totalCompletado = cambio >= 0;
- });
- }
- bool? shouldSave = await showDialog<bool>(
- context: context,
- builder: (BuildContext context) {
- return StatefulBuilder(
- builder: (context, setState) {
- return AlertDialog(
- actionsPadding: EdgeInsets.fromLTRB(50, 10, 50, 30),
- title: const Text(
- 'Finalizar Pedido',
- style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
- ),
- content: SingleChildScrollView(
- child: AnimatedSize(
- duration: const Duration(milliseconds: 300),
- curve: Curves.easeInOut,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- if (nombreController != null)
- AppTextField(
- controller: nombreController,
- etiqueta: 'Nombre',
- hintText: "Nombre del Cliente",
- ),
- if (comentarioController != null) ...[
- const SizedBox(height: 10),
- AppTextField(
- controller: comentarioController,
- etiqueta: 'Comentarios (opcional)',
- hintText: 'Comentarios',
- maxLines: 2,
- ),
- ],
- const SizedBox(height: 10),
- Align(
- alignment: Alignment.center,
- child: Text(
- 'Métodos de pago',
- style: TextStyle(
- fontWeight: FontWeight.bold, fontSize: 20),
- ),
- ),
- const SizedBox(height: 10),
- // Efectivo
- _buildPaymentMethodRow(
- setState,
- label: 'Efectivo',
- selected: efectivoSeleccionado,
- exactSelected: efectivoCompleto,
- controller: efectivoController,
- onSelected: (value) {
- setState(() {
- efectivoSeleccionado = value;
- if (!efectivoSeleccionado) {
- efectivoCompleto = false;
- efectivoController.clear();
- }
- _calcularCambio(setState);
- });
- },
- onExactSelected: (value) {
- setState(() {
- efectivoCompleto = value;
- if (efectivoCompleto) {
- efectivoController.text =
- totalPedido.toStringAsFixed(2);
- efectivoSeleccionado = true;
- tarjetaSeleccionada = false;
- transferenciaSeleccionada = false;
- tarjetaController.clear();
- transferenciaController.clear();
- } else {
- efectivoController.clear();
- }
- _calcularCambio(setState);
- });
- },
- disableOtherMethods:
- tarjetaCompleto || transferenciaCompleto,
- onChangedMonto: () => _calcularCambio(setState),
- ),
- // Tarjeta
- _buildPaymentMethodRow(
- setState,
- label: 'Tarjeta',
- selected: tarjetaSeleccionada,
- exactSelected: tarjetaCompleto,
- controller: tarjetaController,
- sinCambio: true,
- onSelected: (value) {
- setState(() {
- tarjetaSeleccionada = value;
- if (!tarjetaSeleccionada) {
- tarjetaCompleto = false;
- tarjetaController.clear();
- }
- _calcularCambio(setState);
- });
- },
- onExactSelected: (value) {
- setState(() {
- tarjetaCompleto = value;
- if (tarjetaCompleto) {
- tarjetaController.text =
- totalPedido.toStringAsFixed(2);
- tarjetaSeleccionada = true;
- efectivoSeleccionado = false;
- transferenciaSeleccionada = false;
- efectivoController.clear();
- transferenciaController.clear();
- } else {
- tarjetaController.clear();
- }
- _calcularCambio(setState);
- });
- },
- disableOtherMethods:
- efectivoCompleto || transferenciaCompleto,
- onChangedMonto: () => _calcularCambio(setState),
- ),
- // Transferencia
- _buildPaymentMethodRow(
- setState,
- label: 'Transferencia',
- selected: transferenciaSeleccionada,
- exactSelected: transferenciaCompleto,
- controller: transferenciaController,
- sinCambio: true,
- onSelected: (value) {
- setState(() {
- transferenciaSeleccionada = value;
- if (!transferenciaSeleccionada) {
- transferenciaCompleto = false;
- transferenciaController.clear();
- }
- _calcularCambio(setState);
- });
- },
- onExactSelected: (value) {
- setState(() {
- transferenciaCompleto = value;
- if (transferenciaCompleto) {
- transferenciaController.text =
- totalPedido.toStringAsFixed(2);
- transferenciaSeleccionada = true;
- efectivoSeleccionado = false;
- tarjetaSeleccionada = false;
- efectivoController.clear();
- tarjetaController.clear();
- } else {
- transferenciaController.clear();
- }
- _calcularCambio(setState);
- });
- },
- disableOtherMethods:
- efectivoCompleto || tarjetaCompleto,
- onChangedMonto: () => _calcularCambio(setState),
- ),
- // Propina Expandable
- ExpansionTile(
- title: const Text(
- 'Agregar Propina',
- style: TextStyle(
- fontSize: 18, fontWeight: FontWeight.bold),
- ),
- initiallyExpanded: propinaExpandida,
- onExpansionChanged: (isExpanded) {
- setState(() {
- propinaExpandida = isExpanded;
- });
- },
- children: [
- AppTextField(
- separarMiles: true,
- controller: _propinaCantidadController,
- etiqueta: 'Propina',
- hintText: '0.00',
- keyboardType: TextInputType.number,
- ),
- const SizedBox(height: 10),
- AppTextField(
- controller: _propinaComentarioController,
- etiqueta: 'Comentario',
- hintText: 'Comentario',
- maxLines: 2,
- ),
- ],
- ),
- const SizedBox(height: 10),
- Align(
- alignment: Alignment.centerRight,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- Text(
- 'Total del pedido: \$${totalPedido.toStringAsFixed(2)}',
- style: const TextStyle(
- fontWeight: FontWeight.bold, fontSize: 18),
- ),
- if (faltante > 0)
- Text(
- 'Faltante: \$${faltante.toStringAsFixed(2)}',
- style: const TextStyle(
- color: Colors.red,
- fontSize: 18,
- fontWeight: FontWeight.bold),
- )
- else if (cambio > 0)
- Text(
- 'Cambio: \$${cambio.toStringAsFixed(2)}',
- style: const TextStyle(
- color: Colors.green,
- fontSize: 18,
- fontWeight: FontWeight.bold),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- actions: [
- TextButton(
- child: const Text('Cancelar', style: TextStyle(fontSize: 18)),
- onPressed: () => Navigator.of(context).pop(false),
- style: ButtonStyle(
- padding: WidgetStatePropertyAll(
- EdgeInsets.fromLTRB(30, 20, 30, 20)),
- backgroundColor: WidgetStatePropertyAll(Colors.red),
- foregroundColor: WidgetStatePropertyAll(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: WidgetStatePropertyAll(
- EdgeInsets.fromLTRB(30, 20, 30, 20)),
- backgroundColor: WidgetStatePropertyAll(
- totalCompletado ? AppTheme.tertiary : Colors.grey),
- foregroundColor:
- WidgetStatePropertyAll(AppTheme.quaternary),
- ),
- ),
- ],
- );
- },
- );
- },
- );
- if (shouldSave ?? false) {
- if (pedidoActual != null && pedidoActual!.id != 0) {
- await _guardarPedidoExistente(
- cantEfectivo: double.tryParse(efectivoController.text),
- cantTarjeta: double.tryParse(tarjetaController.text),
- cantTransferencia: double.tryParse(transferenciaController.text),
- tipoPago: _obtenerTipoPago(),
- estatus: "TERMINADO",
- );
- await _guardarPropina(pedidoActual!.id!);
- } else {
- prepararPedidoActual(
- nombreController?.text ?? '',
- comentarioController?.text ?? '',
- efectivoController,
- tarjetaController,
- transferenciaController,
- );
- }
- }
- }
- Future<void> _guardarPropina(int idPedido) async {
- double? cantidad =
- double.tryParse(_propinaCantidadController.text.replaceAll(',', '')) ??
- 0.0;
- String comentario = _propinaComentarioController.text.trim();
- if (cantidad != null && cantidad > 0) {
- Propinas nuevaPropina = Propinas(
- idPedido: idPedido,
- cantidad: cantidad,
- comentario: comentario,
- sincronizado: null,
- creado: DateTime.now().toUtc(),
- );
- await Provider.of<PropinaViewModel>(context, listen: false)
- .guardarPropina(nuevaPropina);
- print('Propina guardada correctamente');
- } else {
- print('Propina no guardada (cantidad inválida)');
- }
- }
- Widget _buildPaymentMethodRow(
- StateSetter setState, {
- required String label,
- required bool selected,
- required bool exactSelected,
- required TextEditingController controller,
- required Function(bool) onSelected,
- required Function(bool) onExactSelected,
- required bool disableOtherMethods,
- required Function() onChangedMonto,
- bool sinCambio = false,
- }) {
- return Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Row(
- children: [
- Checkbox(
- activeColor: AppTheme.primary,
- value: selected,
- onChanged: disableOtherMethods
- ? null
- : (value) {
- onSelected(value ?? false);
- },
- ),
- GestureDetector(
- onTap: disableOtherMethods
- ? null
- : () {
- onSelected(!selected);
- },
- child: Text(
- label,
- style:
- const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- ),
- ),
- ],
- ),
- SizedBox(
- width: 180,
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Column(
- children: [
- const Text(
- 'Exacto',
- style: TextStyle(
- fontSize: 18,
- fontWeight: FontWeight.bold,
- color: Colors.black),
- ),
- const SizedBox(height: 17),
- Checkbox(
- activeColor: AppTheme.primary,
- value: exactSelected,
- onChanged: !disableOtherMethods
- ? (value) {
- onExactSelected(value ?? false);
- if (value == true) {
- setState(() {
- disableOtherMethods = true;
- });
- }
- }
- : null,
- ),
- ],
- ),
- const SizedBox(width: 5),
- Expanded(
- child: AppTextField(
- controller: controller,
- enabled: selected,
- etiqueta: 'Cantidad',
- hintText: '0.00',
- keyboardType: TextInputType.number,
- onChanged: (value) {
- if (sinCambio) {
- double? input = double.tryParse(value) ?? 0;
- if (input > totalPedido) {
- controller.text = totalPedido.toStringAsFixed(2);
- controller.selection = TextSelection.fromPosition(
- TextPosition(offset: controller.text.length),
- );
- }
- }
- onChangedMonto();
- },
- ),
- ),
- ],
- ),
- ),
- ],
- );
- }
- void prepararPedidoActual(
- String nombreCliente,
- String comentarios,
- TextEditingController efectivoController,
- TextEditingController tarjetaController,
- TextEditingController transferenciaController,
- ) async {
- String now = DateTime.now().toUtc().toIso8601String();
- int? idUsuario = await SessionStorage().getId();
- double cantEfectivo = efectivoSeleccionado
- ? double.tryParse(efectivoController.text) ?? 0
- : 0;
- double cantTarjeta =
- tarjetaSeleccionada ? double.tryParse(tarjetaController.text) ?? 0 : 0;
- double cantTransferencia = transferenciaSeleccionada
- ? double.tryParse(transferenciaController.text) ?? 0
- : 0;
- Pedido nuevoPedido = Pedido(
- peticion: now,
- nombreCliente: nombreCliente,
- comentarios: comentarios,
- estatus: "TERMINADO",
- totalPedido: totalPedido,
- descuento: pedidoActual?.descuento,
- idUsuario: idUsuario,
- tipoPago: _obtenerTipoPago(),
- cantEfectivo: cantEfectivo,
- cantTarjeta: cantTarjeta,
- cantTransferencia: cantTransferencia,
- uuid: Uuid().v4(),
- );
- final corteViewModel =
- Provider.of<CorteCajaViewModel>(context, listen: false);
- CorteCaja? corteActivo = corteViewModel.cortes.firstWhereOrNull(
- (corte) => corte.fechaCorte == null,
- );
- if (corteActivo != null) {
- nuevoPedido.idCorteCaja = corteActivo.id;
- }
- 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: item.comentario,
- 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.isNotEmpty ? tiposPago.join(',') : 'No Definido';
- }
- 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) async {
- if (value != null && value != selectedDescuento) {
- // Guardar el valor anterior
- final previousValue = selectedDescuento;
- // Actualizar el descuento temporalmente
- print('Descuento temporal seleccionado: $value');
- setState(() {
- selectedDescuento = value;
- });
- // Mostrar cuadro de confirmación
- final authenticated = await showDialog<bool>(
- context: context,
- builder: (context) {
- return TotpCuadroConfirmacion(
- title: "Aplicar Descuento",
- content:
- "Por favor, ingresa el código de autenticación para continuar.",
- onSuccess: () {
- // Confirmación exitosa: recalcular total y actualizar UI
- print(
- 'Autenticación exitosa. Aplicando descuento...');
- setState(() {
- _recalcularTotal();
- print('Descuento aplicado: $selectedDescuento');
- print('Total recalculado: $totalPedido');
- });
- },
- );
- },
- );
- // Si la autenticación falla, revertir el descuento
- if (authenticated != true) {
- print(
- 'Autenticación fallida. Revirtiendo descuento...');
- setState(() {
- selectedDescuento = previousValue;
- _recalcularTotal(); // Recalcular con el valor anterior
- print('Descuento revertido: $selectedDescuento');
- print(
- 'Total recalculado tras revertir: $totalPedido');
- });
- }
- }
- },
- );
- },
- ),
- ),
- ],
- ),
- );
- }
- 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: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(item.producto.nombre!,
- style: const TextStyle(
- fontWeight: FontWeight.w600,
- fontSize: 16)),
- Text('\$${item.producto.precio}',
- style: const TextStyle(
- fontWeight: FontWeight.bold,
- fontSize: 14,
- color: Color(0xFF008000))),
- ],
- ),
- ),
- Row(
- children: [
- IconButton(
- icon: const Icon(Icons.delete, color: Colors.red),
- onPressed: () =>
- eliminarProductoDelCarrito(index),
- ),
- IconButton(
- icon: const Icon(Icons.remove),
- onPressed: () => quitarProductoDelCarrito(item),
- ),
- Text('${item.cantidad}',
- style: const TextStyle(
- fontWeight: FontWeight.bold, fontSize: 14)),
- IconButton(
- icon: const Icon(Icons.add),
- onPressed: () => incrementarProducto(item),
- ),
- IconButton(
- icon:
- Icon(Icons.message, color: AppTheme.tertiary),
- onPressed: () {
- setState(() {
- item.expandido = !item.expandido;
- });
- },
- ),
- ],
- ),
- ],
- ),
- if (item.expandido) ...[
- const SizedBox(height: 5),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16.0),
- child: TextField(
- controller: item.comentarioController,
- decoration: const InputDecoration(
- hintText: 'Agregar un comentario...',
- border: OutlineInputBorder(),
- ),
- maxLines: 2,
- onChanged: (value) {
- item.comentario = value.trim();
- },
- ),
- ),
- ],
- // ExpansionTile para todos los toppings
- 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})'
- ' ${(categoria.minimo ?? 0) > 0 ? " (Mínimo ${categoria.minimo})" : ""}',
- 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(),
- ],
- );
- },
- ),
- ),
- if (pedidoActual != null && pedidoActual!.id! > 0)
- _buildMesaSelector(),
- if (pedidoActual != null && pedidoActual!.id! > 0)
- const SizedBox(height: 5),
- _buildDiscountSection(),
- const Divider(thickness: 5),
- _buildTotalSection(),
- const SizedBox(height: 25),
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Column(
- children: [
- if (pedidoActual != null && pedidoActual!.id! > 0) ...[
- ElevatedButton(
- onPressed: () => _guardarPedidoExistente(),
- style: ElevatedButton.styleFrom(
- backgroundColor: AppTheme.tertiary,
- textStyle: const TextStyle(fontSize: 22),
- fixedSize: const Size(250, 50),
- ),
- child: Text(
- 'Actualizar Pedido',
- style: TextStyle(color: AppTheme.quaternary),
- ),
- ),
- const SizedBox(height: 10),
- ElevatedButton(
- onPressed: () => _promptForCustomerName(),
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.green,
- textStyle: const TextStyle(fontSize: 22),
- fixedSize: const Size(250, 50),
- ),
- child: Text(
- 'Finalizar Pedido',
- style: TextStyle(color: Colors.white),
- ),
- ),
- ] else ...[
- if (_isMesasActive)
- ElevatedButton(
- onPressed: () => _crearPedidoConModal(),
- style: ElevatedButton.styleFrom(
- backgroundColor: AppTheme.tertiary,
- textStyle: const TextStyle(fontSize: 22),
- fixedSize: const Size(250, 50),
- ),
- child: Text(
- 'Iniciar Pedido',
- style: TextStyle(color: AppTheme.quaternary),
- ),
- ),
- const SizedBox(height: 5),
- ElevatedButton(
- onPressed: () => _promptForCustomerName(),
- style: ElevatedButton.styleFrom(
- backgroundColor: AppTheme.rojo,
- textStyle: const TextStyle(fontSize: 18),
- fixedSize: const Size(250, 50),
- ),
- child: Text(
- 'Finalizar Pedido Sin Mesa',
- style: TextStyle(color: AppTheme.quaternary),
- ),
- ),
- ]
- ],
- ),
- ),
- ],
- ),
- );
- }
- void _mostrarDialogoComentario(
- BuildContext context, ItemCarrito item, int index) {
- TextEditingController comentarioController =
- TextEditingController(text: carrito[index].comentario ?? '');
- showDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog(
- title: const Text(
- 'Comentario del Producto',
- style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
- ),
- content: TextField(
- controller: comentarioController,
- maxLines: 3,
- decoration: const InputDecoration(
- hintText: 'Escribe un comentario...',
- border: OutlineInputBorder(),
- ),
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.of(context).pop(),
- child: const Text('Cancelar'),
- ),
- TextButton(
- onPressed: () {
- setState(() {
- carrito[index].comentario = comentarioController.text.trim();
- });
- Navigator.of(context).pop();
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(content: Text('Comentario guardado')),
- );
- },
- child: const Text('Guardar'),
- ),
- ],
- );
- },
- );
- }
- void eliminarProductoDelCarrito(int index) async {
- bool autorizado = true;
- // Solicitar autenticación solo si el pedido tiene un ID mayor a 0
- if (pedidoActual != null && pedidoActual!.id! > 0) {
- autorizado = await autenticarConCodigo(context);
- }
- if (autorizado) {
- final producto = carrito[index].producto;
- // Verificar si el pedido actual tiene un ID mayor a 0
- if (pedidoActual != null && pedidoActual!.id! > 0) {
- final pedidoProducto = pedidoActual!.productos
- .firstWhereOrNull((p) => p.idProducto == producto.id);
- if (pedidoProducto != null) {
- // Marcar como eliminado en la base de datos
- await Provider.of<PedidoViewModel>(context, listen: false)
- .eliminarProductoDelPedido(producto.id!, pedidoActual!.id!);
- }
- }
- // Remover el producto del carrito en la UI
- setState(() {
- carrito.removeAt(index);
- });
- // Recalcular el total
- _recalcularTotal();
- // Mostrar mensaje de confirmación
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(content: Text("Producto eliminado del carrito.")),
- );
- }
- }
- void incrementarProducto(ItemCarrito item) {
- setState(() {
- item.cantidad++;
- });
- _recalcularTotal();
- }
- void quitarProductoDelCarrito(ItemCarrito item) async {
- bool autorizado = true;
- if (pedidoActual != null && pedidoActual!.id! > 0) {
- autorizado = await autenticarConCodigo(context);
- }
- if (autorizado) {
- setState(() {
- if (item.cantidad > 1) {
- item.cantidad--;
- } else {
- if (pedidoActual != null && pedidoActual!.id! > 0) {
- final pedidoProducto = pedidoActual!.productos
- .firstWhereOrNull((p) => p.idProducto == item.producto.id);
- if (pedidoProducto != null) {
- Provider.of<PedidoViewModel>(context, listen: false)
- .eliminarProductoDelPedido(
- item.producto.id!, pedidoActual!.id!);
- }
- }
- carrito.remove(item);
- }
- });
- _recalcularTotal();
- // Mostrar mensaje de confirmación
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text(item.cantidad > 0
- ? "Cantidad del producto actualizada."
- : "Producto marcado como eliminado."),
- ),
- );
- }
- }
- 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: 65,
- child: Scrollbar(
- thumbVisibility: true,
- trackVisibility: true,
- interactive: true,
- controller: _categoryScrollController,
- child: Padding(
- padding: EdgeInsets.fromLTRB(0, 0, 0, 15),
- 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,
- ),
- );
- }
- Future<bool> autenticarConCodigo(BuildContext context) async {
- final TextEditingController codeController = TextEditingController();
- return await showDialog<bool>(
- context: context,
- builder: (context) {
- return AlertDialog(
- title: const Text("Autenticación requerida",
- style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22)),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Text(
- "Por favor, introduce el código de autenticación generado.",
- style: TextStyle(fontSize: 18),
- ),
- const SizedBox(height: 16),
- TextField(
- controller: codeController,
- decoration: const InputDecoration(
- labelText: "Código de autenticación",
- ),
- ),
- ],
- ),
- actions: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- TextButton(
- onPressed: () => Navigator.of(context).pop(false),
- child: Text('Cancelar',
- style: TextStyle(
- fontSize: 18, color: AppTheme.secondary)),
- style: ButtonStyle(
- padding: WidgetStatePropertyAll(
- EdgeInsets.fromLTRB(20, 10, 20, 10)),
- backgroundColor: WidgetStatePropertyAll(Colors.red),
- foregroundColor:
- WidgetStatePropertyAll(AppTheme.secondary)),
- ),
- TextButton(
- onPressed: () {
- final now = DateTime.now().toUtc();
- timezone.initializeTimeZones();
- final pacificTimeZone =
- timezone.getLocation('America/Los_Angeles');
- final date =
- timezone.TZDateTime.from(now, pacificTimeZone);
- final codigoTotp = OTP.generateTOTPCodeString(
- 'TYSNE4CMT5LVLGWS',
- date.millisecondsSinceEpoch,
- algorithm: Algorithm.SHA1,
- isGoogle: true,
- );
- String codigo = codeController.text.trim();
- List<String> codigosEstaticos = [
- '172449',
- '827329',
- // Agregar más códigos estáticos si es necesario
- ];
- bool esCodigoValido =
- codigosEstaticos.contains(codigo) ||
- codigo == codigoTotp;
- if (!esCodigoValido) {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('El código no es correcto'),
- duration: Duration(seconds: 2),
- ),
- );
- return;
- }
- Navigator.of(context).pop(true);
- },
- child: Text('Confirmar',
- style: TextStyle(
- fontSize: 18, color: AppTheme.quaternary)),
- style: ButtonStyle(
- padding: WidgetStatePropertyAll(
- EdgeInsets.fromLTRB(20, 10, 20, 10)),
- backgroundColor:
- WidgetStatePropertyAll(AppTheme.secondary),
- foregroundColor:
- WidgetStatePropertyAll(AppTheme.secondary)),
- ),
- ],
- )
- ],
- );
- },
- ) ??
- false;
- }
- Widget _buildMesaSelector() {
- return FutureBuilder(
- future: Provider.of<MesaViewModel>(context, listen: false)
- .fetchLocalAll(sinLimite: true),
- builder: (context, snapshot) {
- if (snapshot.connectionState == ConnectionState.waiting) {
- return const Center(child: CircularProgressIndicator());
- }
- List<Mesa> mesasDisponibles =
- Provider.of<MesaViewModel>(context, listen: false).mesas;
- TextEditingController mesaSearchController = TextEditingController();
- return Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- const Text(
- 'Mesa:',
- style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
- ),
- const SizedBox(width: 16),
- Expanded(
- child: AppDropdownSearch(
- controller: mesaSearchController,
- asyncItems: (String query) async {
- await Provider.of<MesaViewModel>(context, listen: false)
- .fetchLocalByName(nombre: query);
- return Provider.of<MesaViewModel>(context, listen: false)
- .mesas;
- },
- itemAsString: (dynamic mesa) =>
- (mesa as Mesa).nombre ?? 'Sin Nombre',
- selectedItem: mesasDisponibles.firstWhere(
- (mesa) => mesa.id == pedidoActual?.idMesa,
- orElse: () => null as Mesa,
- ),
- onChanged: (dynamic nuevaMesa) {
- setState(() {
- pedidoActual?.idMesa = (nuevaMesa as Mesa).id;
- });
- },
- items: mesasDisponibles,
- ),
- ),
- ],
- ),
- );
- },
- );
- }
- }
|