pedido_mesa_form.dart 55 KB

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