pedido_mesa_form.dart 55 KB

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