pedido_mesa_form.dart 55 KB

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