pedido_mesa_form.dart 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:conalep_pos/viewmodels/mesa_view_model.dart';
  4. import 'package:flutter/foundation.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/services.dart';
  7. import 'package:intl/intl.dart';
  8. import 'package:provider/provider.dart';
  9. import '../pedido/pedido_ticket.dart';
  10. import '../../themes/themes.dart';
  11. import '../../models/models.dart';
  12. import '../../viewmodels/viewmodels.dart';
  13. import 'package:collection/collection.dart';
  14. import '../../widgets/widgets.dart';
  15. class PedidoMesaForm extends StatefulWidget {
  16. final Pedido? pedido;
  17. const PedidoMesaForm({Key? key, Pedido? this.pedido}) : super(key: key);
  18. @override
  19. _PedidoMesaFormState createState() => _PedidoMesaFormState();
  20. }
  21. class _PedidoMesaFormState extends State<PedidoMesaForm> {
  22. final _busqueda = TextEditingController(text: '');
  23. final TextEditingController _descuentoController = TextEditingController();
  24. CategoriaProductoViewModel cvm = CategoriaProductoViewModel();
  25. ProductoViewModel pvm = ProductoViewModel();
  26. PedidoViewModel pedvm = PedidoViewModel();
  27. bool _isLoading = false;
  28. CategoriaProducto? categoriaSeleccionada;
  29. List<CategoriaProducto> categorias = [];
  30. List<Producto> productos = [];
  31. List<ItemCarrito> carrito = [];
  32. List<DropdownMenuItem<int>> listaMesas = [];
  33. Producto? _productoActual;
  34. bool _estadoBusqueda = false;
  35. Pedido? pedidoActual;
  36. ScrollController _listViewController = ScrollController();
  37. ScrollController _categoryScrollController = ScrollController();
  38. ScrollController _gridViewController = ScrollController();
  39. final _searchController = TextEditingController();
  40. final NumberFormat _numberFormat = NumberFormat.decimalPattern('es_MX');
  41. int? selectedDescuento = 0;
  42. int? selectedMesa = 0;
  43. double subtotal = 0;
  44. double precioDescuento = 0;
  45. double totalPedido = 0;
  46. bool efectivoSeleccionado = false;
  47. bool tarjetaSeleccionada = false;
  48. bool transferenciaSeleccionada = false;
  49. TextEditingController efectivoController = TextEditingController();
  50. TextEditingController tarjetaController = TextEditingController();
  51. TextEditingController transferenciaController = TextEditingController();
  52. double cambio = 0.0;
  53. double calcularTotalPedido() {
  54. double total = 0;
  55. for (var item in carrito) {
  56. total += item.producto.precio! * item.cantidad;
  57. item.selectedToppings.forEach((categoryId, selectedToppingIds) {
  58. for (int toppingId in selectedToppingIds) {
  59. Producto? topping = item.selectableToppings[categoryId]?.firstWhere(
  60. (topping) => topping.id == toppingId,
  61. orElse: () => Producto(precio: 0));
  62. if (topping != null) {
  63. total += (topping.precio ?? 0.0) * item.cantidad;
  64. }
  65. }
  66. });
  67. }
  68. return total;
  69. }
  70. double aplicarDescuento(double total, int? descuento) {
  71. if (descuento != null && descuento > 0) {
  72. double totalPedido = total * (1 - descuento / 100);
  73. // print(
  74. // 'Total con descuento: $totalPedido (Descuento aplicado: $descuento%)');
  75. return totalPedido;
  76. }
  77. // print('Sin descuento, total: $total');
  78. return total;
  79. }
  80. @override
  81. void initState() {
  82. super.initState();
  83. final mvm = Provider.of<MesaViewModel>(context, listen: false);
  84. cargarCategoriasIniciales().then((_) {
  85. if (categorias.isNotEmpty) {
  86. categoriaSeleccionada = categorias.first;
  87. cargarProductosPorCategoria(categoriaSeleccionada!.id);
  88. calcularTotalPedido();
  89. if (selectedDescuento != null) {
  90. _recalcularTotal();
  91. }
  92. }
  93. });
  94. final mesas = mvm.mesas;
  95. if (mesas.isNotEmpty) {
  96. mesas.sort((a, b) => a.nombre!.compareTo(b.nombre!));
  97. }
  98. listaMesas = mesas
  99. .map(
  100. (mesa) => DropdownMenuItem<int>(
  101. value: mesa.id,
  102. child: Text(
  103. '${mesa.clave} - ${mesa.nombre}',
  104. style: const TextStyle(color: Colors.black),
  105. ),
  106. ),
  107. )
  108. .toList();
  109. Provider.of<DescuentoViewModel>(context, listen: false).cargarDescuentos();
  110. if (widget.pedido != null && widget.pedido!.id != null) {
  111. if (widget.pedido!.idMesa != null && listaMesas.isNotEmpty) {
  112. selectedMesa = widget.pedido!.idMesa;
  113. }
  114. Future(() async {
  115. for (var item in widget.pedido!.productos) {
  116. Map<int, List<Producto>> toppingsSeleccionables =
  117. await obtenerToppingsSeleccionables(item.producto!);
  118. carrito.add(ItemCarrito(
  119. producto: item.producto!,
  120. cantidad: item.cantidad!,
  121. selectableToppings: toppingsSeleccionables));
  122. }
  123. });
  124. }
  125. listaMesas.add(DropdownMenuItem<int>(
  126. value: 0,
  127. child:
  128. Text('Seleccionar', style: const TextStyle(color: Colors.black))));
  129. }
  130. _onSearchChanged(String value) {
  131. if (value.isEmpty) {
  132. cargarProductosIniciales();
  133. } else {
  134. Provider.of<ProductoViewModel>(context, listen: false)
  135. .fetchLocalByName(nombre: value);
  136. }
  137. }
  138. void _recalcularTotal() {
  139. subtotal = calcularTotalPedido();
  140. // print('Subtotal: $subtotal');
  141. // print('Descuento seleccionado: $selectedDescuento%');
  142. precioDescuento = subtotal * (selectedDescuento! / 100);
  143. totalPedido = subtotal - precioDescuento;
  144. setState(() {
  145. pedidoActual = pedidoActual ?? Pedido();
  146. pedidoActual!.totalPedido = totalPedido;
  147. pedidoActual!.descuento = selectedDescuento;
  148. });
  149. // print('Precio descuento: $precioDescuento');
  150. // print('Total con descuento aplicado: $totalPedido');
  151. }
  152. void _finalizeOrder() async {
  153. if (carrito.isEmpty) {
  154. showDialog(
  155. context: context,
  156. builder: (BuildContext context) {
  157. return AlertDialog(
  158. title: const Text('Pedido vacío',
  159. style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22)),
  160. content: const Text(
  161. 'No puedes finalizar un pedido sin productos. Por favor, agrega al menos un producto.',
  162. style: TextStyle(fontWeight: FontWeight.w500, fontSize: 18)),
  163. shape:
  164. RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
  165. actions: <Widget>[
  166. TextButton(
  167. style: TextButton.styleFrom(
  168. padding: EdgeInsets.fromLTRB(30, 20, 30, 20),
  169. foregroundColor: AppTheme.quaternary,
  170. backgroundColor: AppTheme.tertiary),
  171. onPressed: () {
  172. Navigator.of(context).pop();
  173. },
  174. child: const Text('Aceptar', style: TextStyle(fontSize: 18)),
  175. ),
  176. ],
  177. );
  178. },
  179. );
  180. return;
  181. }
  182. await _promptForCustomerName();
  183. }
  184. void _guardarOrden() async {
  185. if (carrito.isEmpty) {
  186. showDialog(
  187. context: context,
  188. builder: (BuildContext context) {
  189. return AlertDialog(
  190. title: const Text('Pedido vacío',
  191. style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22)),
  192. content: const Text(
  193. 'No puedes finalizar un pedido sin productos. Por favor, agrega al menos un producto.',
  194. style: TextStyle(fontWeight: FontWeight.w500, fontSize: 18)),
  195. shape:
  196. RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
  197. actions: <Widget>[
  198. TextButton(
  199. style: TextButton.styleFrom(
  200. padding: EdgeInsets.fromLTRB(30, 20, 30, 20),
  201. foregroundColor: AppTheme.quaternary,
  202. backgroundColor: AppTheme.tertiary),
  203. onPressed: () {
  204. Navigator.of(context).pop();
  205. },
  206. child: const Text('Aceptar', style: TextStyle(fontSize: 18)),
  207. ),
  208. ],
  209. );
  210. },
  211. );
  212. return;
  213. }
  214. await _promptForCustomerName();
  215. }
  216. Future<void> _promptForCustomerName() async {
  217. TextEditingController nombreController = TextEditingController();
  218. TextEditingController comentarioController = TextEditingController();
  219. String errorMessage = '';
  220. double faltante = totalPedido;
  221. bool totalCompletado = false;
  222. bool efectivoCompleto = false;
  223. bool tarjetaCompleto = false;
  224. bool transferenciaCompleto = false;
  225. void _calcularCambio(StateSetter setState) {
  226. double totalPagado = (double.tryParse(efectivoController.text) ?? 0) +
  227. (double.tryParse(tarjetaController.text) ?? 0) +
  228. (double.tryParse(transferenciaController.text) ?? 0);
  229. setState(() {
  230. cambio = totalPagado - totalPedido;
  231. if (cambio < 0) {
  232. faltante = totalPedido - totalPagado;
  233. cambio = 0;
  234. totalCompletado = false;
  235. } else {
  236. faltante = 0;
  237. totalCompletado = true;
  238. }
  239. // Si el total ha sido alcanzado o excedido, desactivar otros métodos de pago
  240. if (totalPagado >= totalPedido) {
  241. if (!efectivoSeleccionado) efectivoSeleccionado = false;
  242. if (!tarjetaSeleccionada) tarjetaSeleccionada = false;
  243. if (!transferenciaSeleccionada) transferenciaSeleccionada = false;
  244. }
  245. });
  246. }
  247. void _validarCantidad(
  248. StateSetter setState, TextEditingController controller) {
  249. double cantidad = double.tryParse(controller.text) ?? 0;
  250. if (cantidad > totalPedido) {
  251. setState(() {
  252. controller.text = totalPedido.toStringAsFixed(2);
  253. });
  254. }
  255. _calcularCambio(setState);
  256. }
  257. bool _isPaymentOptionEnabled(bool isSelected) {
  258. return !totalCompletado || isSelected;
  259. }
  260. bool? shouldSave = await showDialog<bool>(
  261. context: context,
  262. builder: (BuildContext context) {
  263. return StatefulBuilder(
  264. builder: (context, setState) {
  265. return RawKeyboardListener(
  266. focusNode: FocusNode(),
  267. onKey: (RawKeyEvent event) {
  268. if (event.isKeyPressed(LogicalKeyboardKey.enter) &&
  269. totalCompletado) {
  270. Navigator.of(context).pop(true);
  271. }
  272. },
  273. child: AlertDialog(
  274. actionsPadding: EdgeInsets.fromLTRB(50, 10, 50, 30),
  275. title: const Text(
  276. 'Finalizar Pedido',
  277. style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
  278. ),
  279. content: Container(
  280. height: 600,
  281. child: Column(
  282. children: [
  283. Expanded(
  284. child: SingleChildScrollView(
  285. child: Column(
  286. children: [
  287. AppTextField(
  288. controller: nombreController,
  289. etiqueta: 'Nombre',
  290. hintText: "Nombre del Cliente",
  291. ),
  292. const SizedBox(height: 10),
  293. AppTextField(
  294. controller: comentarioController,
  295. etiqueta: 'Comentarios (opcional)',
  296. hintText: 'Comentarios',
  297. maxLines: 2,
  298. ),
  299. const SizedBox(height: 10),
  300. Align(
  301. alignment: Alignment.center,
  302. child: Text(
  303. 'Métodos de pago',
  304. style: TextStyle(
  305. fontWeight: FontWeight.bold,
  306. fontSize: 20),
  307. ),
  308. ),
  309. const SizedBox(height: 10),
  310. // Efectivo
  311. GestureDetector(
  312. onTap: () {
  313. if (_isPaymentOptionEnabled(
  314. efectivoSeleccionado)) {
  315. setState(() {
  316. efectivoSeleccionado =
  317. !efectivoSeleccionado;
  318. if (!efectivoSeleccionado) {
  319. efectivoCompleto = false;
  320. efectivoController.clear();
  321. _calcularCambio(setState);
  322. } else if (efectivoCompleto) {
  323. efectivoController.text =
  324. totalPedido.toStringAsFixed(2);
  325. _calcularCambio(setState);
  326. }
  327. });
  328. }
  329. },
  330. child: Row(
  331. mainAxisAlignment:
  332. MainAxisAlignment.spaceBetween,
  333. crossAxisAlignment:
  334. CrossAxisAlignment.center,
  335. children: [
  336. Row(
  337. children: [
  338. Checkbox(
  339. activeColor: AppTheme.primary,
  340. value: efectivoSeleccionado,
  341. onChanged: _isPaymentOptionEnabled(
  342. efectivoSeleccionado)
  343. ? (bool? value) {
  344. setState(() {
  345. efectivoSeleccionado =
  346. value ?? false;
  347. if (!efectivoSeleccionado) {
  348. efectivoCompleto =
  349. false;
  350. efectivoController
  351. .clear();
  352. _calcularCambio(
  353. setState);
  354. } else if (efectivoCompleto) {
  355. efectivoController
  356. .text =
  357. totalPedido
  358. .toStringAsFixed(
  359. 2);
  360. _calcularCambio(
  361. setState);
  362. }
  363. });
  364. }
  365. : null,
  366. ),
  367. const Text(
  368. "Efectivo",
  369. style: TextStyle(
  370. fontSize: 18,
  371. fontWeight: FontWeight.bold),
  372. ),
  373. ],
  374. ),
  375. if (efectivoSeleccionado)
  376. SizedBox(
  377. width: 180,
  378. child: Row(
  379. crossAxisAlignment:
  380. CrossAxisAlignment.start,
  381. children: [
  382. Column(
  383. children: [
  384. const Text('Exacto',
  385. style: TextStyle(
  386. fontSize: 18,
  387. fontWeight:
  388. FontWeight.bold,
  389. color: Colors.black)),
  390. const SizedBox(
  391. height: 17,
  392. ),
  393. Checkbox(
  394. activeColor:
  395. AppTheme.primary,
  396. value: efectivoCompleto,
  397. onChanged:
  398. efectivoSeleccionado
  399. ? (bool? value) {
  400. setState(() {
  401. efectivoCompleto =
  402. value ??
  403. false;
  404. if (efectivoCompleto) {
  405. efectivoController
  406. .text =
  407. totalPedido
  408. .toStringAsFixed(2);
  409. _calcularCambio(
  410. setState);
  411. } else {
  412. efectivoController
  413. .clear();
  414. _calcularCambio(
  415. setState);
  416. }
  417. });
  418. }
  419. : null,
  420. ),
  421. ],
  422. ),
  423. const SizedBox(
  424. width: 5,
  425. ),
  426. Expanded(
  427. child: AppTextField(
  428. controller:
  429. efectivoController,
  430. etiqueta: 'Cantidad',
  431. hintText: '0.00',
  432. keyboardType:
  433. TextInputType.number,
  434. onChanged: (value) =>
  435. _calcularCambio(setState),
  436. ),
  437. ),
  438. ],
  439. ),
  440. ),
  441. ],
  442. ),
  443. ),
  444. const SizedBox(height: 10),
  445. // Tarjeta
  446. GestureDetector(
  447. onTap: () {
  448. if (_isPaymentOptionEnabled(
  449. tarjetaSeleccionada)) {
  450. setState(() {
  451. tarjetaSeleccionada =
  452. !tarjetaSeleccionada;
  453. if (!tarjetaSeleccionada) {
  454. tarjetaController.clear();
  455. _calcularCambio(setState);
  456. }
  457. });
  458. }
  459. },
  460. child: Row(
  461. mainAxisAlignment:
  462. MainAxisAlignment.spaceBetween,
  463. crossAxisAlignment:
  464. CrossAxisAlignment.center,
  465. children: [
  466. Row(
  467. children: [
  468. Checkbox(
  469. activeColor: AppTheme.primary,
  470. value: tarjetaSeleccionada,
  471. onChanged: _isPaymentOptionEnabled(
  472. tarjetaSeleccionada)
  473. ? (bool? value) {
  474. setState(() {
  475. tarjetaSeleccionada =
  476. value ?? false;
  477. if (!tarjetaSeleccionada) {
  478. tarjetaController
  479. .clear();
  480. _calcularCambio(
  481. setState);
  482. }
  483. });
  484. }
  485. : null,
  486. ),
  487. const Text(
  488. "Tarjeta",
  489. style: TextStyle(
  490. fontSize: 18,
  491. fontWeight: FontWeight.bold),
  492. ),
  493. ],
  494. ),
  495. if (tarjetaSeleccionada)
  496. SizedBox(
  497. width: 180,
  498. child: Row(
  499. crossAxisAlignment:
  500. CrossAxisAlignment.start,
  501. children: [
  502. Column(
  503. children: [
  504. const Text('Exacto',
  505. style: TextStyle(
  506. fontSize: 18,
  507. fontWeight:
  508. FontWeight.bold,
  509. color: Colors.black)),
  510. const SizedBox(
  511. height: 17,
  512. ),
  513. Checkbox(
  514. activeColor:
  515. AppTheme.primary,
  516. value: tarjetaCompleto,
  517. onChanged:
  518. tarjetaSeleccionada
  519. ? (bool? value) {
  520. setState(() {
  521. tarjetaCompleto =
  522. value ??
  523. false;
  524. if (tarjetaCompleto) {
  525. tarjetaController
  526. .text =
  527. totalPedido
  528. .toStringAsFixed(2);
  529. _calcularCambio(
  530. setState);
  531. } else {
  532. tarjetaController
  533. .clear();
  534. _calcularCambio(
  535. setState);
  536. }
  537. });
  538. }
  539. : null,
  540. ),
  541. ],
  542. ),
  543. const SizedBox(
  544. width: 5,
  545. ),
  546. Expanded(
  547. child: AppTextField(
  548. controller: tarjetaController,
  549. etiqueta: 'Cantidad',
  550. hintText: '0.00',
  551. keyboardType:
  552. TextInputType.number,
  553. onChanged: (value) {
  554. _validarCantidad(setState,
  555. tarjetaController);
  556. },
  557. ),
  558. ),
  559. ],
  560. ),
  561. ),
  562. ],
  563. ),
  564. ),
  565. const SizedBox(height: 10),
  566. // Transferencia
  567. GestureDetector(
  568. onTap: () {
  569. if (_isPaymentOptionEnabled(
  570. transferenciaSeleccionada)) {
  571. setState(() {
  572. transferenciaSeleccionada =
  573. !transferenciaSeleccionada;
  574. if (!transferenciaSeleccionada) {
  575. transferenciaController.clear();
  576. _calcularCambio(setState);
  577. }
  578. });
  579. }
  580. },
  581. child: Row(
  582. mainAxisAlignment:
  583. MainAxisAlignment.spaceBetween,
  584. crossAxisAlignment:
  585. CrossAxisAlignment.center,
  586. children: [
  587. Row(
  588. children: [
  589. Checkbox(
  590. activeColor: AppTheme.primary,
  591. value: transferenciaSeleccionada,
  592. onChanged: _isPaymentOptionEnabled(
  593. transferenciaSeleccionada)
  594. ? (bool? value) {
  595. setState(() {
  596. transferenciaSeleccionada =
  597. value ?? false;
  598. if (!transferenciaSeleccionada) {
  599. transferenciaController
  600. .clear();
  601. _calcularCambio(
  602. setState);
  603. }
  604. });
  605. }
  606. : null,
  607. ),
  608. const Text(
  609. "Transferencia",
  610. style: TextStyle(
  611. fontSize: 18,
  612. fontWeight: FontWeight.bold),
  613. ),
  614. ],
  615. ),
  616. if (transferenciaSeleccionada)
  617. SizedBox(
  618. width: 180,
  619. child: Row(
  620. crossAxisAlignment:
  621. CrossAxisAlignment.start,
  622. children: [
  623. Column(
  624. children: [
  625. const Text('Exacto',
  626. style: TextStyle(
  627. fontSize: 18,
  628. fontWeight:
  629. FontWeight.bold,
  630. color: Colors.black)),
  631. const SizedBox(
  632. height: 17,
  633. ),
  634. Checkbox(
  635. activeColor:
  636. AppTheme.primary,
  637. value:
  638. transferenciaCompleto,
  639. onChanged:
  640. transferenciaSeleccionada
  641. ? (bool? value) {
  642. setState(() {
  643. transferenciaCompleto =
  644. value ??
  645. false;
  646. if (transferenciaCompleto) {
  647. transferenciaController
  648. .text =
  649. totalPedido
  650. .toStringAsFixed(2);
  651. _calcularCambio(
  652. setState);
  653. } else {
  654. transferenciaController
  655. .clear();
  656. _calcularCambio(
  657. setState);
  658. }
  659. });
  660. }
  661. : null,
  662. ),
  663. ],
  664. ),
  665. const SizedBox(
  666. width: 5,
  667. ),
  668. Expanded(
  669. child: AppTextField(
  670. controller:
  671. transferenciaController,
  672. etiqueta: 'Cantidad',
  673. hintText: '0.00',
  674. keyboardType:
  675. TextInputType.number,
  676. onChanged: (value) {
  677. _validarCantidad(setState,
  678. transferenciaController);
  679. },
  680. ),
  681. ),
  682. ],
  683. ),
  684. ),
  685. ],
  686. ),
  687. ),
  688. const SizedBox(height: 10),
  689. // Mostrar el total del pedido y la cantidad faltante
  690. Align(
  691. alignment: Alignment.centerRight,
  692. child: Column(
  693. crossAxisAlignment:
  694. CrossAxisAlignment.end,
  695. children: [
  696. Text(
  697. 'Total del pedido: \$${totalPedido.toStringAsFixed(2)}',
  698. style: const TextStyle(
  699. fontWeight: FontWeight.bold,
  700. fontSize: 18),
  701. ),
  702. if (faltante > 0)
  703. Text(
  704. 'Faltante: \$${faltante.toStringAsFixed(2)}',
  705. style: const TextStyle(
  706. color: Colors.red,
  707. fontSize: 18,
  708. fontWeight: FontWeight.bold),
  709. )
  710. else if (cambio > 0)
  711. Text(
  712. 'Cambio: \$${cambio.toStringAsFixed(2)}',
  713. style: const TextStyle(
  714. color: Colors.green,
  715. fontSize: 18,
  716. fontWeight: FontWeight.bold)),
  717. ]),
  718. ),
  719. ],
  720. ),
  721. ),
  722. ),
  723. // Aquí mantenemos los botones fijos
  724. Row(
  725. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  726. children: [
  727. TextButton(
  728. child: const Text('Cancelar',
  729. style: TextStyle(fontSize: 18)),
  730. onPressed: () {
  731. Navigator.of(context).pop(false);
  732. },
  733. style: ButtonStyle(
  734. padding: MaterialStatePropertyAll(
  735. EdgeInsets.fromLTRB(30, 20, 30, 20)),
  736. backgroundColor:
  737. MaterialStatePropertyAll(Colors.red),
  738. foregroundColor: MaterialStatePropertyAll(
  739. AppTheme.secondary)),
  740. ),
  741. const SizedBox(width: 100),
  742. TextButton(
  743. child: const Text('Guardar',
  744. style: TextStyle(fontSize: 18)),
  745. onPressed: totalCompletado
  746. ? () {
  747. Navigator.of(context).pop(true);
  748. }
  749. : null,
  750. style: ButtonStyle(
  751. padding: MaterialStatePropertyAll(
  752. EdgeInsets.fromLTRB(30, 20, 30, 20)),
  753. backgroundColor: MaterialStatePropertyAll(
  754. totalCompletado
  755. ? AppTheme.tertiary
  756. : Colors.grey),
  757. foregroundColor: MaterialStatePropertyAll(
  758. AppTheme.quaternary)),
  759. ),
  760. ],
  761. ),
  762. ],
  763. ),
  764. ),
  765. ));
  766. },
  767. );
  768. },
  769. );
  770. if (shouldSave ?? false) {
  771. prepararPedidoActual(nombreController.text, comentarioController.text);
  772. }
  773. }
  774. void prepararPedidoActual(String nombreCliente, String comentarios) async {
  775. DateTime now = DateTime.now();
  776. String formattedDate = DateTime.now().toUtc().toIso8601String();
  777. Pedido nuevoPedido = Pedido(
  778. peticion: formattedDate,
  779. nombreCliente: nombreCliente,
  780. comentarios: comentarios,
  781. estatus: "FINALIZADO",
  782. totalPedido: totalPedido,
  783. descuento: pedidoActual?.descuento,
  784. idMesa: selectedMesa,
  785. tipoPago: _obtenerTipoPago(),
  786. cantEfectivo:
  787. efectivoSeleccionado ? double.tryParse(efectivoController.text) : 0,
  788. cantTarjeta:
  789. tarjetaSeleccionada ? double.tryParse(tarjetaController.text) : 0,
  790. cantTransferencia: transferenciaSeleccionada
  791. ? double.tryParse(transferenciaController.text)
  792. : 0,
  793. );
  794. List<PedidoProducto> listaPedidoProducto = carrito.map((item) {
  795. List<PedidoProductoTopping> selectedToppings = [];
  796. item.selectedToppings.forEach((categoryId, selectedToppingIds) {
  797. for (int toppingId in selectedToppingIds) {
  798. selectedToppings.add(PedidoProductoTopping(
  799. idTopping: toppingId,
  800. ));
  801. }
  802. });
  803. return PedidoProducto(
  804. idProducto: item.producto.id,
  805. producto: item.producto,
  806. costoUnitario: item.producto.precio.toString(),
  807. cantidad: item.cantidad,
  808. comentario: comentarios,
  809. toppings: selectedToppings,
  810. );
  811. }).toList();
  812. nuevoPedido.productos = listaPedidoProducto;
  813. if (widget.pedido != null && widget.pedido!.id > 0) {
  814. nuevoPedido.id = widget.pedido!.id;
  815. }
  816. bool result = await Provider.of<PedidoViewModel>(context, listen: false)
  817. .guardarPedidoLocal(pedido: nuevoPedido);
  818. if (!mounted) return;
  819. if (result) {
  820. Pedido? pedidoCompleto =
  821. await Provider.of<PedidoViewModel>(context, listen: false)
  822. .fetchPedidoConProductos(nuevoPedido.id!);
  823. if (pedidoCompleto != null) {
  824. imprimirTicketsJuntos(context, pedidoCompleto);
  825. }
  826. Navigator.of(context).pop();
  827. } else {
  828. print("Error al guardar el pedido");
  829. }
  830. }
  831. void guardarPedidoActual() async {
  832. DateTime now = DateTime.now();
  833. String formattedDate = DateTime.now().toUtc().toIso8601String();
  834. Pedido nuevoPedido = Pedido(
  835. peticion: formattedDate,
  836. // nombreCliente: nombreCliente,
  837. // comentarios: comentarios,
  838. estatus: "EN PROCESO",
  839. // totalPedido: totalPedido,
  840. descuento: pedidoActual?.descuento,
  841. idMesa: selectedMesa
  842. // tipoPago: _obtenerTipoPago(),
  843. // cantEfectivo:
  844. // efectivoSeleccionado ? double.tryParse(efectivoController.text) : 0,
  845. // cantTarjeta:
  846. // tarjetaSeleccionada ? double.tryParse(tarjetaController.text) : 0,
  847. // cantTransferencia: transferenciaSeleccionada
  848. // ? double.tryParse(transferenciaController.text)
  849. // : 0,
  850. );
  851. List<PedidoProducto> listaPedidoProducto = carrito.map((item) {
  852. List<PedidoProductoTopping> selectedToppings = [];
  853. item.selectedToppings.forEach((categoryId, selectedToppingIds) {
  854. for (int toppingId in selectedToppingIds) {
  855. selectedToppings.add(PedidoProductoTopping(
  856. idTopping: toppingId,
  857. ));
  858. }
  859. });
  860. return PedidoProducto(
  861. idProducto: item.producto.id,
  862. producto: item.producto,
  863. costoUnitario: item.producto.precio.toString(),
  864. cantidad: item.cantidad,
  865. // comentario: comentarios,
  866. toppings: selectedToppings,
  867. );
  868. }).toList();
  869. nuevoPedido.productos = listaPedidoProducto;
  870. if (widget.pedido != null && widget.pedido!.id != null) {
  871. nuevoPedido.id = widget.pedido!.id;
  872. }
  873. bool result = await Provider.of<PedidoViewModel>(context, listen: false)
  874. .guardarPedidoLocal(pedido: nuevoPedido);
  875. if (!mounted) return;
  876. if (result) {
  877. Pedido? pedidoCompleto =
  878. await Provider.of<PedidoViewModel>(context, listen: false)
  879. .fetchPedidoConProductos(nuevoPedido.id!);
  880. /* if (pedidoCompleto != null) {
  881. imprimirTicketsJuntos(context, pedidoCompleto);
  882. } */
  883. Navigator.of(context).pop();
  884. } else {
  885. print("Error al guardar el pedido");
  886. }
  887. }
  888. void cancelarPedidoActual() async {
  889. if (widget.pedido == null) {
  890. Navigator.of(context).pop();
  891. } else {
  892. DateTime now = DateTime.now();
  893. String formattedDate = DateTime.now().toUtc().toIso8601String();
  894. Pedido nuevoPedido = Pedido(
  895. peticion: formattedDate,
  896. // nombreCliente: nombreCliente,
  897. // comentarios: comentarios,
  898. estatus: "CANCELADO",
  899. // totalPedido: totalPedido,
  900. descuento: pedidoActual?.descuento,
  901. idMesa: selectedMesa
  902. // tipoPago: _obtenerTipoPago(),
  903. // cantEfectivo:
  904. // efectivoSeleccionado ? double.tryParse(efectivoController.text) : 0,
  905. // cantTarjeta:
  906. // tarjetaSeleccionada ? double.tryParse(tarjetaController.text) : 0,
  907. // cantTransferencia: transferenciaSeleccionada
  908. // ? double.tryParse(transferenciaController.text)
  909. // : 0,
  910. );
  911. List<PedidoProducto> listaPedidoProducto = carrito.map((item) {
  912. List<PedidoProductoTopping> selectedToppings = [];
  913. item.selectedToppings.forEach((categoryId, selectedToppingIds) {
  914. for (int toppingId in selectedToppingIds) {
  915. selectedToppings.add(PedidoProductoTopping(
  916. idTopping: toppingId,
  917. ));
  918. }
  919. });
  920. return PedidoProducto(
  921. idProducto: item.producto.id,
  922. producto: item.producto,
  923. costoUnitario: item.producto.precio.toString(),
  924. cantidad: item.cantidad,
  925. // comentario: comentarios,
  926. toppings: selectedToppings,
  927. );
  928. }).toList();
  929. nuevoPedido.productos = listaPedidoProducto;
  930. if (widget.pedido != null && widget.pedido!.id != null) {
  931. nuevoPedido.id = widget.pedido!.id;
  932. }
  933. bool result = await Provider.of<PedidoViewModel>(context, listen: false)
  934. .guardarPedidoLocal(pedido: nuevoPedido);
  935. if (!mounted) return;
  936. if (result) {
  937. Pedido? pedidoCompleto =
  938. await Provider.of<PedidoViewModel>(context, listen: false)
  939. .fetchPedidoConProductos(nuevoPedido.id!);
  940. // if (pedidoCompleto != null) {
  941. // imprimirTicketsJuntos(context, pedidoCompleto);
  942. // }
  943. Navigator.of(context).pop();
  944. } else {
  945. print("Error al guardar el pedido");
  946. }
  947. }
  948. }
  949. String _obtenerTipoPago() {
  950. List<String> tiposPago = [];
  951. if (efectivoSeleccionado) tiposPago.add('Efectivo');
  952. if (tarjetaSeleccionada) tiposPago.add('Tarjeta');
  953. if (transferenciaSeleccionada) tiposPago.add('Transferencia');
  954. return tiposPago.join(',');
  955. }
  956. void _limpiarBusqueda() async {
  957. setState(() {
  958. _busqueda.text = '';
  959. _estadoBusqueda = false;
  960. });
  961. await cargarCategoriasIniciales();
  962. }
  963. Future<void> cargarProductosIniciales() async {
  964. setState(() => _isLoading = true);
  965. await Provider.of<ProductoViewModel>(context, listen: false)
  966. .fetchLocalAll();
  967. productos =
  968. Provider.of<ProductoViewModel>(context, listen: false).productos;
  969. setState(() => _isLoading = false);
  970. }
  971. @override
  972. void dispose() {
  973. _listViewController.dispose();
  974. _categoryScrollController.dispose();
  975. _gridViewController.dispose();
  976. _searchController.dispose();
  977. super.dispose();
  978. }
  979. Future<void> cargarCategoriasIniciales() async {
  980. setState(() => _isLoading = true);
  981. await Provider.of<CategoriaProductoViewModel>(context, listen: false)
  982. .fetchLocalAll();
  983. categorias = Provider.of<CategoriaProductoViewModel>(context, listen: false)
  984. .categoriaProductos;
  985. if (categorias.isNotEmpty) {
  986. categoriaSeleccionada = categorias.first;
  987. cargarProductosPorCategoria(categoriaSeleccionada!.id);
  988. }
  989. setState(() => _isLoading = false);
  990. if (categorias.isNotEmpty) {
  991. categoriaSeleccionada = categorias.first;
  992. }
  993. }
  994. void cargarProductosPorCategoria(int categoriaId) async {
  995. setState(() => _isLoading = true);
  996. await Provider.of<ProductoViewModel>(context, listen: false)
  997. .fetchAllByCategory(categoriaId);
  998. productos =
  999. Provider.of<ProductoViewModel>(context, listen: false).productos;
  1000. setState(() => _isLoading = false);
  1001. }
  1002. void agregarAlCarrito(Producto producto) async {
  1003. var existente = carrito.firstWhereOrNull((item) =>
  1004. item.producto.id == producto.id &&
  1005. mapEquals(item.selectedToppings, {}));
  1006. if (existente != null) {
  1007. setState(() {
  1008. existente.cantidad++;
  1009. });
  1010. } else {
  1011. Map<int, List<Producto>> toppingsSeleccionables =
  1012. await obtenerToppingsSeleccionables(producto);
  1013. setState(() {
  1014. carrito.add(ItemCarrito(
  1015. producto: producto,
  1016. cantidad: 1,
  1017. selectableToppings: toppingsSeleccionables,
  1018. ));
  1019. });
  1020. }
  1021. _recalcularTotal();
  1022. }
  1023. Future<Map<int, List<Producto>>> obtenerToppingsSeleccionables(
  1024. Producto producto) async {
  1025. Map<int, List<Producto>> toppingsSeleccionables = {};
  1026. final toppingCategories =
  1027. await pvm.obtenerToppingsPorProducto(producto.id!);
  1028. for (int toppingId in toppingCategories) {
  1029. Producto? topping = await pvm.obtenerProductoPorId(toppingId);
  1030. if (topping != null && topping.idCategoria != null) {
  1031. toppingsSeleccionables[topping.idCategoria!] ??= [];
  1032. toppingsSeleccionables[topping.idCategoria]!.add(topping);
  1033. }
  1034. }
  1035. return toppingsSeleccionables;
  1036. }
  1037. void quitarDelCarrito(Producto producto) {
  1038. setState(() {
  1039. var indice =
  1040. carrito.indexWhere((item) => item.producto.id == producto.id);
  1041. if (indice != -1) {
  1042. if (carrito[indice].cantidad > 1) {
  1043. carrito[indice].cantidad--;
  1044. } else {
  1045. carrito.removeAt(indice);
  1046. }
  1047. }
  1048. });
  1049. }
  1050. void addToCart(Producto producto) {
  1051. var existingIndex = carrito.indexWhere((item) =>
  1052. item.producto.id == producto.id &&
  1053. mapEquals(item.selectedToppings, {}));
  1054. if (existingIndex != -1) {
  1055. setState(() {
  1056. carrito[existingIndex].cantidad++;
  1057. });
  1058. } else {
  1059. setState(() {
  1060. carrito.add(ItemCarrito(producto: producto, cantidad: 1));
  1061. });
  1062. }
  1063. }
  1064. void finalizeCustomization() {
  1065. if (_productoActual != null) {
  1066. addToCart(_productoActual!);
  1067. setState(() {
  1068. _productoActual = null;
  1069. });
  1070. }
  1071. }
  1072. Widget _buildMesaSection() {
  1073. return Padding(
  1074. padding: const EdgeInsets.symmetric(horizontal: 8.0),
  1075. child: Column(
  1076. children: [
  1077. Row(
  1078. crossAxisAlignment: CrossAxisAlignment.center,
  1079. children: [
  1080. const Text(
  1081. 'Mesa',
  1082. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
  1083. ),
  1084. const Spacer(),
  1085. ConstrainedBox(
  1086. constraints: const BoxConstraints(
  1087. maxWidth: 150,
  1088. ),
  1089. child: Consumer<MesaViewModel>(
  1090. builder: (context, viewModel, child) {
  1091. return AppDropdownModel<int>(
  1092. hint: 'Seleccionar',
  1093. items:
  1094. listaMesas /* viewModel.mesas
  1095. .map(
  1096. (mesa) => DropdownMenuItem<int>(
  1097. value: mesa.id,
  1098. child: Text(
  1099. '${mesa.clave} - ${mesa.nombre}',
  1100. style: const TextStyle(color: Colors.black),
  1101. ),
  1102. ),
  1103. )
  1104. .toList() */
  1105. ,
  1106. selectedValue: selectedMesa,
  1107. onChanged: (value) {
  1108. setState(() {
  1109. selectedMesa = value;
  1110. });
  1111. },
  1112. );
  1113. },
  1114. ),
  1115. ),
  1116. ],
  1117. )
  1118. ],
  1119. ),
  1120. );
  1121. }
  1122. Widget _buildDiscountSection() {
  1123. return Padding(
  1124. padding: const EdgeInsets.symmetric(horizontal: 8.0),
  1125. child: Column(
  1126. children: [
  1127. Row(
  1128. crossAxisAlignment: CrossAxisAlignment.center,
  1129. children: [
  1130. const Text(
  1131. 'Descuento',
  1132. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
  1133. ),
  1134. const Spacer(),
  1135. ConstrainedBox(
  1136. constraints: const BoxConstraints(
  1137. maxWidth: 150,
  1138. ),
  1139. child: Consumer<DescuentoViewModel>(
  1140. builder: (context, viewModel, child) {
  1141. return AppDropdownModel<int>(
  1142. hint: 'Seleccionar',
  1143. items: viewModel.descuentos
  1144. .map(
  1145. (descuento) => DropdownMenuItem<int>(
  1146. value: descuento.porcentaje,
  1147. child: Text(
  1148. '${descuento.porcentaje}%',
  1149. style: const TextStyle(color: Colors.black),
  1150. ),
  1151. ),
  1152. )
  1153. .toList(),
  1154. selectedValue: selectedDescuento,
  1155. onChanged: (value) {
  1156. setState(() {
  1157. selectedDescuento = value;
  1158. _recalcularTotal();
  1159. });
  1160. },
  1161. );
  1162. },
  1163. ),
  1164. ),
  1165. ],
  1166. ),
  1167. ],
  1168. ),
  1169. );
  1170. }
  1171. Widget _buildTotalSection() {
  1172. String formattedsubtotal = _numberFormat.format(subtotal);
  1173. String formattedPrecioDescuento = _numberFormat.format(precioDescuento);
  1174. String formattedtotalPedido = _numberFormat.format(totalPedido);
  1175. return Padding(
  1176. padding: const EdgeInsets.symmetric(horizontal: 8.0),
  1177. child: Column(
  1178. crossAxisAlignment: CrossAxisAlignment.start,
  1179. children: [
  1180. if (precioDescuento > 0)
  1181. Column(
  1182. children: [
  1183. Row(
  1184. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  1185. children: [
  1186. const Text('Subtotal',
  1187. style: TextStyle(
  1188. fontWeight: FontWeight.bold, fontSize: 18)),
  1189. Text("\$$formattedsubtotal",
  1190. style: const TextStyle(
  1191. fontWeight: FontWeight.bold, fontSize: 18)),
  1192. ],
  1193. ),
  1194. const SizedBox(height: 10),
  1195. Row(
  1196. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  1197. children: [
  1198. const Text('Descuento',
  1199. style: TextStyle(
  1200. fontWeight: FontWeight.bold, fontSize: 18)),
  1201. Text("-\$$formattedPrecioDescuento",
  1202. style: const TextStyle(
  1203. fontWeight: FontWeight.bold, fontSize: 18)),
  1204. ],
  1205. ),
  1206. ],
  1207. ),
  1208. const SizedBox(height: 10),
  1209. Row(
  1210. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  1211. children: [
  1212. const Text('Total',
  1213. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  1214. Text("\$$formattedtotalPedido",
  1215. style: const TextStyle(
  1216. fontWeight: FontWeight.bold, fontSize: 18)),
  1217. ],
  1218. ),
  1219. ],
  1220. ),
  1221. );
  1222. }
  1223. @override
  1224. Widget build(BuildContext context) {
  1225. return Scaffold(
  1226. appBar: AppBar(
  1227. title:
  1228. Text("Crear Pedido", style: TextStyle(color: AppTheme.secondary)),
  1229. iconTheme: IconThemeData(color: AppTheme.secondary)),
  1230. body: Row(
  1231. children: [
  1232. Flexible(
  1233. flex: 3,
  1234. child: _buildCartSection(),
  1235. ),
  1236. SizedBox(width: 35),
  1237. Flexible(flex: 7, child: _buildProductsSection()),
  1238. ],
  1239. ),
  1240. );
  1241. }
  1242. Widget _buildCartSection() {
  1243. return Card(
  1244. margin: const EdgeInsets.all(8.0),
  1245. child: Column(
  1246. children: [
  1247. const Padding(
  1248. padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
  1249. child: Row(
  1250. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  1251. children: [
  1252. Text('Producto',
  1253. style:
  1254. TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  1255. Text('Cantidad',
  1256. style:
  1257. TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  1258. ],
  1259. ),
  1260. ),
  1261. Expanded(
  1262. child: ListView.builder(
  1263. itemCount: carrito.length,
  1264. itemBuilder: (context, index) {
  1265. final item = carrito[index];
  1266. return Column(
  1267. children: [
  1268. ListTile(
  1269. title: Text(item.producto.nombre!,
  1270. style: const TextStyle(fontWeight: FontWeight.w600)),
  1271. subtitle: Text('\$${item.producto.precio}',
  1272. style: const TextStyle(
  1273. fontWeight: FontWeight.bold,
  1274. color: Color(0xFF008000))),
  1275. trailing: Row(
  1276. mainAxisSize: MainAxisSize.min,
  1277. children: [
  1278. IconButton(
  1279. icon: const Icon(Icons.delete, color: Colors.red),
  1280. onPressed: () =>
  1281. eliminarProductoDelCarrito(index)),
  1282. IconButton(
  1283. icon: const Icon(Icons.remove),
  1284. onPressed: () => quitarProductoDelCarrito(item)),
  1285. const SizedBox(width: 5),
  1286. Text('${item.cantidad}',
  1287. style: const TextStyle(
  1288. fontWeight: FontWeight.bold, fontSize: 14)),
  1289. const SizedBox(width: 5),
  1290. IconButton(
  1291. icon: const Icon(Icons.add),
  1292. onPressed: () => incrementarProducto(item)),
  1293. ],
  1294. ),
  1295. ),
  1296. if (item.selectableToppings.isNotEmpty)
  1297. ExpansionTile(
  1298. title: Text('Toppings',
  1299. style: const TextStyle(
  1300. fontWeight: FontWeight.bold, fontSize: 16)),
  1301. children: item.selectableToppings.entries.map((entry) {
  1302. final categoryId = entry.key;
  1303. final availableToppings = entry.value;
  1304. final categoria =
  1305. categorias.firstWhere((c) => c.id == categoryId);
  1306. return Column(
  1307. crossAxisAlignment: CrossAxisAlignment.start,
  1308. children: [
  1309. Padding(
  1310. padding: const EdgeInsets.only(left: 16.0),
  1311. child: Text(
  1312. categoria.descripcion!,
  1313. style: const TextStyle(
  1314. fontWeight: FontWeight.bold,
  1315. fontSize: 16),
  1316. ),
  1317. ),
  1318. ...availableToppings.map((topping) {
  1319. ValueNotifier<bool> isSelectedNotifier =
  1320. ValueNotifier<bool>(
  1321. item.selectedToppings[categoryId]
  1322. ?.contains(topping.id) ??
  1323. false,
  1324. );
  1325. return ValueListenableBuilder<bool>(
  1326. valueListenable: isSelectedNotifier,
  1327. builder: (context, isSelected, _) {
  1328. return CheckboxListTile(
  1329. activeColor: AppTheme.primary,
  1330. title: Row(
  1331. mainAxisAlignment:
  1332. MainAxisAlignment.spaceBetween,
  1333. children: [
  1334. Text(topping.nombre!),
  1335. if (topping.precio != 0.0)
  1336. Text(
  1337. '+\$${topping.precio}',
  1338. style: TextStyle(
  1339. color: Colors.black,
  1340. fontSize: 13,
  1341. ),
  1342. ),
  1343. ],
  1344. ),
  1345. value: isSelected,
  1346. onChanged: (bool? value) {
  1347. final maximoToppings =
  1348. categoria.maximo ?? 0;
  1349. if (value == true) {
  1350. if ((item.selectedToppings[categoryId]
  1351. ?.length ??
  1352. 0) >=
  1353. maximoToppings) {
  1354. item.selectedToppings[categoryId]!
  1355. .remove(item
  1356. .selectedToppings[
  1357. categoryId]!
  1358. .first);
  1359. }
  1360. item.selectedToppings[categoryId] ??=
  1361. <int>{};
  1362. item.selectedToppings[categoryId]!
  1363. .add(topping.id!);
  1364. } else {
  1365. item.selectedToppings[categoryId]
  1366. ?.remove(topping.id!);
  1367. if (item.selectedToppings[categoryId]
  1368. ?.isEmpty ??
  1369. false) {
  1370. item.selectedToppings
  1371. .remove(categoryId);
  1372. }
  1373. }
  1374. setState(() {});
  1375. _recalcularTotal();
  1376. },
  1377. );
  1378. },
  1379. );
  1380. }).toList(),
  1381. ],
  1382. );
  1383. }).toList(),
  1384. ),
  1385. Divider(),
  1386. ],
  1387. );
  1388. },
  1389. ),
  1390. ),
  1391. _buildMesaSection(),
  1392. const SizedBox(height: 5),
  1393. _buildDiscountSection(),
  1394. const Divider(thickness: 5),
  1395. _buildTotalSection(),
  1396. const SizedBox(height: 25),
  1397. Row(
  1398. children: [
  1399. Column(
  1400. children: [
  1401. Padding(
  1402. padding: const EdgeInsets.all(8.0),
  1403. child: ElevatedButton(
  1404. onPressed: guardarPedidoActual,
  1405. style: ElevatedButton.styleFrom(
  1406. backgroundColor: AppTheme.tertiary,
  1407. textStyle: const TextStyle(fontSize: 22),
  1408. fixedSize: const Size(165, 50),
  1409. ),
  1410. child: Text('Guardar',
  1411. style: TextStyle(color: AppTheme.quaternary)),
  1412. ),
  1413. ),
  1414. ],
  1415. ),
  1416. Column(
  1417. children: [
  1418. Padding(
  1419. padding: const EdgeInsets.all(8.0),
  1420. child: ElevatedButton(
  1421. onPressed: _finalizeOrder,
  1422. style: ElevatedButton.styleFrom(
  1423. backgroundColor: AppTheme.tertiary,
  1424. textStyle: const TextStyle(fontSize: 22),
  1425. fixedSize: const Size(165, 50),
  1426. ),
  1427. child: Text('Finalizar',
  1428. style: TextStyle(color: AppTheme.quaternary)),
  1429. ),
  1430. ),
  1431. ],
  1432. ),
  1433. Column(
  1434. children: [
  1435. Padding(
  1436. padding: const EdgeInsets.all(8.0),
  1437. child: ElevatedButton(
  1438. onPressed: cancelarPedidoActual,
  1439. style: ElevatedButton.styleFrom(
  1440. backgroundColor: AppTheme.tertiary,
  1441. textStyle: const TextStyle(fontSize: 22),
  1442. fixedSize: const Size(165, 50),
  1443. ),
  1444. child: Text('Cancelar',
  1445. style: TextStyle(color: AppTheme.quaternary)),
  1446. ),
  1447. ),
  1448. ],
  1449. ),
  1450. ],
  1451. ),
  1452. ],
  1453. ),
  1454. );
  1455. }
  1456. void eliminarProductoDelCarrito(int index) {
  1457. setState(() {
  1458. carrito.removeAt(index);
  1459. });
  1460. _recalcularTotal();
  1461. }
  1462. void incrementarProducto(ItemCarrito item) {
  1463. setState(() {
  1464. item.cantidad++;
  1465. });
  1466. _recalcularTotal();
  1467. }
  1468. void quitarProductoDelCarrito(ItemCarrito item) {
  1469. setState(() {
  1470. if (item.cantidad > 1) {
  1471. item.cantidad--;
  1472. } else {
  1473. carrito.remove(item);
  1474. }
  1475. });
  1476. _recalcularTotal();
  1477. }
  1478. Widget _buildProductsSection() {
  1479. return Column(
  1480. children: [
  1481. const SizedBox(height: 5),
  1482. _buildSearchBar(),
  1483. const SizedBox(height: 10),
  1484. _buildCategoryButtons(),
  1485. const SizedBox(height: 15),
  1486. Expanded(
  1487. child: Consumer<ProductoViewModel>(builder: (context, model, child) {
  1488. productos = model.productos;
  1489. return Scrollbar(
  1490. controller: _listViewController,
  1491. thumbVisibility: true,
  1492. trackVisibility: true,
  1493. child: ListView.builder(
  1494. controller: _listViewController,
  1495. itemCount: productos.length,
  1496. itemBuilder: (context, index) {
  1497. final producto = productos[index];
  1498. return Card(
  1499. child: ListTile(
  1500. title: Row(
  1501. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  1502. children: [
  1503. Text(
  1504. producto.nombre ?? '',
  1505. style: const TextStyle(
  1506. fontSize: 16,
  1507. fontWeight: FontWeight.bold,
  1508. ),
  1509. textAlign: TextAlign.left,
  1510. ),
  1511. Text(
  1512. '\$${producto.precio}',
  1513. style: const TextStyle(
  1514. fontSize: 16,
  1515. fontWeight: FontWeight.bold,
  1516. color: Colors.green,
  1517. ),
  1518. textAlign: TextAlign.right,
  1519. ),
  1520. ],
  1521. ),
  1522. onTap: () => agregarAlCarrito(producto),
  1523. ),
  1524. );
  1525. },
  1526. ),
  1527. );
  1528. }),
  1529. ),
  1530. ],
  1531. );
  1532. }
  1533. Widget _buildCategoryButtons() {
  1534. List<CategoriaProducto> categoriasFiltradas =
  1535. categorias.where((categoria) => categoria.esToping == 0).toList();
  1536. return Column(
  1537. children: [
  1538. Container(
  1539. height: 50,
  1540. child: Stack(
  1541. children: [
  1542. ListView.builder(
  1543. controller: _categoryScrollController,
  1544. scrollDirection: Axis.horizontal,
  1545. itemCount: categoriasFiltradas.length,
  1546. itemBuilder: (context, index) {
  1547. final categoria = categoriasFiltradas[index];
  1548. bool isSelected = categoriaSeleccionada?.id == categoria.id;
  1549. return Padding(
  1550. padding: const EdgeInsets.symmetric(horizontal: 4.0),
  1551. child: ElevatedButton(
  1552. onPressed: () {
  1553. cargarProductosPorCategoria(categoria.id);
  1554. setState(() {
  1555. categoriaSeleccionada = categoria;
  1556. });
  1557. },
  1558. style: ElevatedButton.styleFrom(
  1559. backgroundColor:
  1560. isSelected ? AppTheme.tertiary : Colors.grey,
  1561. foregroundColor: isSelected
  1562. ? AppTheme.quaternary
  1563. : AppTheme.secondary,
  1564. ),
  1565. child: Text(categoria.nombre!),
  1566. ),
  1567. );
  1568. },
  1569. ),
  1570. ],
  1571. ),
  1572. ),
  1573. ],
  1574. );
  1575. }
  1576. Widget _buildSearchBar() {
  1577. return Padding(
  1578. padding: const EdgeInsets.all(8.0),
  1579. child: TextField(
  1580. controller: _searchController,
  1581. decoration: InputDecoration(
  1582. hintText: 'Buscar producto...',
  1583. prefixIcon: const Icon(Icons.search),
  1584. suffixIcon: IconButton(
  1585. icon: Icon(Icons.clear),
  1586. onPressed: () {
  1587. _searchController.clear();
  1588. _onSearchChanged('');
  1589. },
  1590. ),
  1591. border: OutlineInputBorder(
  1592. borderRadius: BorderRadius.circular(12.0),
  1593. ),
  1594. enabledBorder: OutlineInputBorder(
  1595. borderRadius: BorderRadius.circular(12.0),
  1596. borderSide: BorderSide(width: 1.5)),
  1597. focusedBorder: OutlineInputBorder(
  1598. borderSide: BorderSide(color: Colors.black),
  1599. borderRadius: BorderRadius.circular(12.0),
  1600. ),
  1601. ),
  1602. onChanged: _onSearchChanged,
  1603. ),
  1604. );
  1605. }
  1606. }