Selaa lähdekoodia

Topings y categoria de topings

OscarGil03 1 vuosi sitten
vanhempi
commit
1c10fe75bd

+ 3 - 0
lib/main.dart

@@ -25,6 +25,9 @@ void main() {
       ChangeNotifierProvider(create: (_) => ProfileViewModel()),
       ChangeNotifierProvider(create: (_) => CategoriaProductoViewModel()),
       ChangeNotifierProvider(create: (_) => ProductoViewModel()),
+      ChangeNotifierProvider(create: (_) => TopingCategoriaViewModel()),
+      ChangeNotifierProvider(create: (_) => MediaViewModel()),
+      ChangeNotifierProvider(create: (_) => TopingViewModel()),
       // Agrega aquí cualquier otro provider que necesites
     ], child: const MyApp()));
   });

+ 37 - 0
lib/models/media_toping_categoria_model.dart

@@ -0,0 +1,37 @@
+import 'media_model.dart'; // Asegúrate de importar tu modelo Media
+
+class MediaTopingCategoria {
+  int? idTopingCategoria;
+  int? idMedia;
+  bool? principal;
+  String? tipo;
+  Media? media;
+
+  MediaTopingCategoria({
+    this.idTopingCategoria,
+    this.idMedia,
+    this.principal,
+    this.tipo,
+    this.media,
+  });
+
+  factory MediaTopingCategoria.fromJson(Map<String, dynamic> json) {
+    return MediaTopingCategoria(
+      idTopingCategoria: json['idTopingCategoria'] as int?,
+      idMedia: json['idMedia'] as int?,
+      principal: json['principal'] as bool?,
+      tipo: json['tipo'] as String?,
+      media: json['media'] != null
+          ? Media.fromJson(json['media'] as Map<String, dynamic>)
+          : null,
+    );
+  }
+
+  Map<String, dynamic> toJson() => {
+        'idTopingCategoria': idTopingCategoria,
+        'idMedia': idMedia,
+        'principal': principal,
+        'tipo': tipo,
+        'media': media?.toJson(),
+      };
+}

+ 37 - 0
lib/models/media_toping_model.dart

@@ -0,0 +1,37 @@
+import 'media_model.dart'; // Asegúrate de importar tu modelo Media
+
+class MediaToping {
+  int? idToping;
+  int? idMedia;
+  bool? principal;
+  String? tipo;
+  Media? media;
+
+  MediaToping({
+    this.idToping,
+    this.idMedia,
+    this.principal,
+    this.tipo,
+    this.media,
+  });
+
+  factory MediaToping.fromJson(Map<String, dynamic> json) {
+    return MediaToping(
+      idToping: json['idToping'] as int?,
+      idMedia: json['idMedia'] as int?,
+      principal: json['principal'] as bool?,
+      tipo: json['tipo'] as String?,
+      media: json['media'] != null
+          ? Media.fromJson(json['media'] as Map<String, dynamic>)
+          : null,
+    );
+  }
+
+  Map<String, dynamic> toJson() => {
+        'idToping': idToping,
+        'idMedia': idMedia,
+        'principal': principal,
+        'tipo': tipo,
+        'media': media?.toJson(),
+      };
+}

+ 4 - 0
lib/models/models.dart

@@ -2,7 +2,11 @@ export '../models/basico_model.dart';
 export '../models/login_model.dart';
 export '../models/usuario_model.dart';
 export '../models/media_model.dart';
+export '../models/media_toping_categoria_model.dart';
+export '../models/media_toping_model.dart';
 export '../models/profile_model.dart';
 export '../models/categoria_producto_model.dart';
 export '../models/pedido_model.dart';
 export '../models/producto_model.dart';
+export '../models/toping_categoria_model.dart';
+export '../models/toping_model.dart';

+ 52 - 0
lib/models/toping_categoria_model.dart

@@ -0,0 +1,52 @@
+import 'package:yoshi_papas_app/models/models.dart';
+import 'media_toping_categoria_model.dart';
+import 'basico_model.dart';
+
+class TopingCategoria extends Basico {
+  String? clave;
+  String? nombre;
+  String? descripcion;
+  int? activo;
+  int? orden;
+  int? cantidad;
+  List<MediaTopingCategoria> mediaTopingCategoria = [];
+
+  TopingCategoria({
+    super.id,
+    this.clave,
+    this.nombre,
+    this.descripcion,
+    this.activo,
+    this.orden,
+    this.cantidad,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'clave': clave,
+      'nombre': nombre,
+      'descripcion': descripcion,
+      'activo': activo,
+      'orden': orden,
+      'cantidad': cantidad,
+    }..addAll(super.toJson());
+  }
+
+  TopingCategoria.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    clave = Basico.parseString(json['clave']);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    activo = Basico.parseInt(json['activo']);
+    orden = Basico.parseInt(json['orden']);
+    if (json['mediaTopingCategoria'] != null) {
+      var mediaTCList = json['mediaTopingCategoria'] as List;
+      mediaTopingCategoria = mediaTCList
+          .map((i) => MediaTopingCategoria.fromJson(i as Map<String, dynamic>))
+          .toList();
+    }
+    cantidad = Basico.parseInt(json['cantidad']);
+  }
+}

+ 60 - 0
lib/models/toping_model.dart

@@ -0,0 +1,60 @@
+import 'package:yoshi_papas_app/models/models.dart';
+import 'media_toping_categoria_model.dart';
+import 'basico_model.dart';
+
+class Toping extends Basico {
+  int? idCategoria;
+  String? clave;
+  String? nombre;
+  String? descripcion;
+  String? costo;
+  int? activo;
+  String? imagenPrincipal;
+  List<MediaToping> mediaToping = [];
+
+  Toping({
+    super.id,
+    this.idCategoria,
+    this.clave,
+    this.nombre,
+    this.descripcion,
+    this.costo,
+    this.activo,
+    this.imagenPrincipal,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idCategoria': idCategoria,
+      'clave': clave,
+      'nombre': nombre,
+      'descripcion': descripcion,
+      'costo': costo,
+      'activo': activo,
+      'imagenPrincipal': imagenPrincipal,
+    }..addAll(super.toJson());
+  }
+
+  Toping.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idCategoria = Basico.parseInt(json['idCategoria']);
+    clave = Basico.parseString(json['clave']);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    costo = Basico.parseString(json['costo']);
+    if (json['activo'] is bool) {
+      activo = json['activo'] ? 1 : 0;
+    } else {
+      activo = Basico.parseInt(json['activo']);
+    }
+    imagenPrincipal = Basico.parseString(json['imagenPrincipal']);
+    if (json['mediaToping'] != null) {
+      var mediaTCList = json['mediaToping'] as List;
+      mediaToping = mediaTCList
+          .map((i) => MediaToping.fromJson(i as Map<String, dynamic>))
+          .toList();
+    }
+  }
+}

+ 12 - 8
lib/services/general_service.dart

@@ -6,22 +6,26 @@ import '../services/services.dart';
 import 'package:http/http.dart' as http;
 
 class GeneralService extends BaseService {
-  String endPoint = '/servicios/visita';
-  String postdistancia = '/servicios/visita/calcular-distancia';
-  Map<String, String> defaultQueryParameters = {"ordenar":"id-desc"};
+  Map<String, String> defaultQueryParameters = {"ordenar": "id-desc"};
 
-  Future<http.StreamedResponse> enviarMedia({required int id, required XFile media, String? modulo, String? tipo, String? etiquetaID}) async {
+  Future<http.StreamedResponse> enviarMedia(
+      {required int id,
+      required XFile media,
+      String? modulo,
+      String? tipo,
+      String? etiquetaID}) async {
     //ejemplo: tipo = imagen, modulo = Visita
     final token = await SessionStorage().getToken();
     String completo = "$base_url/admin/subir-archivo/guardar";
     String nombre = "generico";
-    http.MultipartRequest request = http.MultipartRequest('POST', Uri.parse(completo));
+    http.MultipartRequest request =
+        http.MultipartRequest('POST', Uri.parse(completo));
     request.headers.addAll({
       'Content-type': 'multipart/form-data',
       'Accept': 'application/json',
       'Authorization': "Bearer $token"
     });
-    if(tipo == "archivo"){
+    if (tipo == "archivo") {
       final random = Random();
       final randomInt = random.nextInt(99999);
       int lastIndex = media.name.lastIndexOf('.');
@@ -32,12 +36,12 @@ class GeneralService extends BaseService {
       }
       nombre = media.name.toString();
     }
-    if(tipo == "imagen"){
+    if (tipo == "imagen") {
       final random = Random();
       final randomInt = random.nextInt(99999);
       nombre = "$randomInt.jpg";
     }
-    if(tipo == "audio"){
+    if (tipo == "audio") {
       final random = Random();
       final randomInt = random.nextInt(99999);
       nombre = "$randomInt.wav";

+ 115 - 0
lib/viewmodels/media_view_model.dart

@@ -0,0 +1,115 @@
+import 'dart:convert';
+import 'dart:ui' as ui;
+import 'dart:io';
+import 'package:image/image.dart' as img;
+
+import 'package:camera/camera.dart';
+import 'package:file_picker/file_picker.dart';
+import 'package:flutter/foundation.dart';
+import "package:universal_html/html.dart" as html;
+import 'package:flutter/foundation.dart' show kIsWeb;
+
+import '../models/media_model.dart';
+import '../services/base_service.dart';
+
+//import 'dart:html' as html show window;
+//import 'dart:html' as html;
+
+class MediaViewModel extends ChangeNotifier {
+  List<XFile> _audios = [];
+  List<XFile> get audios => _audios;
+
+  List<XFile> _temporales = [];
+  List<XFile> get temporales => _temporales;
+
+  List<XFile> _archivos = [];
+  List<XFile> get archivos => _archivos;
+
+  bool _isLoading = false;
+  bool get isLoading => _isLoading;
+
+  Future<void> fetchAudio() async {
+    _temporales = [];
+    notifyListeners();
+  }
+
+  Future agregarAudio(XFile value) async {
+    _audios.add(value);
+    notifyListeners();
+  }
+
+  Future limpiar() async {
+    _audios = [];
+    _temporales = [];
+    _archivos = [];
+    notifyListeners();
+  }
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  Future guardarAudio(
+      {required int idModuloLocal,
+      File? audio,
+      required String nombreModulo}) async {}
+
+  Future<XFile?> convertirPlatformFileAXFile(PlatformFile platformFile) async {
+    if (!kIsWeb) {
+      final XFile xFile = XFile(platformFile.path.toString(),
+          name: platformFile.name, mimeType: "archivo");
+      return xFile;
+    }
+
+    try {
+      final Uint8List bytes = platformFile.bytes!;
+      final html.Blob blob = html.Blob([bytes], 'application/octet-stream');
+      String url = html.Url.createObjectUrlFromBlob(blob);
+
+      final XFile xFile =
+          XFile(url.toString(), name: platformFile.name, mimeType: "archivo");
+      int lastIndex = xFile.name.lastIndexOf('.');
+      if (lastIndex != -1 && lastIndex < xFile.name.length - 1) {
+        String ext = xFile.name.substring(lastIndex + 1);
+      }
+      return xFile;
+    } catch (e) {
+      print('Error al convertir PlatformFile a XFile: $e');
+      return null;
+    }
+  }
+
+  agregarArchivo(XFile? value) {
+    _archivos.add(value!);
+    notifyListeners();
+  }
+
+  Future<XFile> convertirAImagePickerWebFile(ui.Image image) async {
+    // Convertir ui.Image a lista de bytes (Uint8List)
+    ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
+    Uint8List pngBytes = byteData!.buffer.asUint8List();
+
+    // Crear imagen con el paquete 'image'
+    img.Image imgImage = img.decodeImage(pngBytes)!;
+
+    // Convertir imagen a lista de bytes (Uint8List)
+    Uint8List imgBytes = img.encodePng(imgImage) as Uint8List;
+
+    // Crear archivo temporal
+    final tempDir = Directory.systemTemp;
+
+    final tempFile =
+        await File('${tempDir.path}/temp_image.png').writeAsBytes(imgBytes);
+
+    // Crear XFile a partir del archivo temporal
+    return XFile(tempFile.path);
+  }
+
+  Future<void> eliminar(Media m) async {
+    var r = await BaseService()
+        .delete("admin/media", body: {"id": m.id.toString()});
+    Map<String, dynamic> resJson = jsonDecode(r.body);
+    if (r.statusCode == 200) {}
+  }
+}

+ 111 - 0
lib/viewmodels/toping_categoria_view_model.dart

@@ -0,0 +1,111 @@
+import 'package:camera/camera.dart';
+import 'package:flutter/material.dart';
+import 'package:yoshi_papas_app/models/toping_categoria_model.dart';
+
+import '../data/api_response.dart';
+import '../services/base_service.dart';
+import '../models/models.dart';
+import '../services/services.dart';
+
+class TopingCategoriaViewModel extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<TopingCategoria> _registros = [];
+  TopingCategoria? _selectedModelo;
+  bool _isLoading = false;
+
+  List<TopingCategoria> get registros => _registros;
+  TopingCategoria? get selectedTopingCategoria => _selectedModelo;
+  bool get isLoading => _isLoading;
+
+  int pagina = 1;
+  int totalPaginas = 1;
+  int limite = 10;
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  Future<List<TopingCategoria>> fetchRegistros(
+      {String? q, bool segmentar = false}) async {
+    Map<String, String> parametros = {
+      "ordenar": "id-desc",
+      "pagina": "$pagina",
+      "limite": "$limite",
+      "expand": "media,mediaTopingCategoria"
+    };
+    if (_busqueda.isNotEmpty) {
+      parametros["q"] = _busqueda;
+    }
+    if (segmentar) {
+      parametros['segmentar'] = "1";
+    }
+    var r = ApiResponse(await BaseService()
+        .get("admin/toping-categoria", queryParameters: parametros));
+    pagina = r.paginacion!.pagina;
+    var total = r.paginacion!.total / r.paginacion!.limite;
+    totalPaginas = total > 0 ? total.ceil() : 1;
+    List<TopingCategoria> aux = [];
+    if (r.isOk) {
+      for (var x in r.resultados!) {
+        TopingCategoria modelo = TopingCategoria.fromJson(x);
+        aux.add(modelo);
+      }
+    }
+    _registros = aux;
+    notifyListeners();
+    return _registros;
+  }
+
+  selectModelo(TopingCategoria TopingCategoria) {
+    _selectedModelo = TopingCategoria;
+    notifyListeners();
+  }
+
+  setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  cambiarPagina(int nuevaPagina, {bool segmentar = false}) {
+    pagina = nuevaPagina;
+    fetchRegistros(segmentar: segmentar);
+  }
+
+  Future eliminarMedia(Media m) async {
+    _selectedModelo!.mediaTopingCategoria.removeWhere(
+        (mediaTopingCategoria) => mediaTopingCategoria.media?.id == m.id);
+    notifyListeners();
+  }
+
+  Future<void> guardarModelo({
+    required TopingCategoria modelo,
+    required String clave,
+    required String nombre,
+    required String descripcion,
+    List<XFile>? fotos,
+  }) async {
+    modelo.clave = clave;
+    modelo.nombre = nombre;
+    modelo.descripcion = descripcion;
+    var r = ApiResponse(await BaseService()
+        .post("admin/toping-categoria", body: modelo.toJson()));
+    int id = int.parse(r.detalle!["id"].toString());
+    if (id > 0 && fotos != null && fotos.isNotEmpty) {
+      for (var x in fotos) {
+        await GeneralService().enviarMedia(
+            id: id,
+            media: x,
+            modulo: "Autotanque",
+            etiquetaID: "idInspeccion",
+            tipo: "imagen");
+      }
+    }
+    var s = await BaseService()
+        .post("admin/toping-categoria/guardar", body: modelo.toJson());
+    if (r.statusCode == 200) {}
+    notifyListeners();
+  }
+}

+ 111 - 0
lib/viewmodels/toping_view_model.dart

@@ -0,0 +1,111 @@
+import 'package:camera/camera.dart';
+import 'package:flutter/material.dart';
+import 'package:yoshi_papas_app/models/toping_categoria_model.dart';
+
+import '../data/api_response.dart';
+import '../services/base_service.dart';
+import '../models/models.dart';
+import '../services/services.dart';
+
+class TopingViewModel extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<Toping> _registros = [];
+  Toping? _selectedModelo;
+  bool _isLoading = false;
+
+  List<Toping> get registros => _registros;
+  Toping? get selectedToping => _selectedModelo;
+  bool get isLoading => _isLoading;
+
+  int pagina = 1;
+  int totalPaginas = 1;
+  int limite = 10;
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  Future<List<Toping>> fetchRegistros(
+      {String? q, bool segmentar = false}) async {
+    Map<String, String> parametros = {
+      "ordenar": "id-desc",
+      "pagina": "$pagina",
+      "limite": "$limite",
+      "expand": "mediaToping,categoriaToping"
+    };
+    if (_busqueda.isNotEmpty) {
+      parametros["q"] = _busqueda;
+    }
+    if (segmentar) {
+      parametros['segmentar'] = "1";
+    }
+    var r = ApiResponse(
+        await BaseService().get("admin/toping", queryParameters: parametros));
+    pagina = r.paginacion!.pagina;
+    var total = r.paginacion!.total / r.paginacion!.limite;
+    totalPaginas = total > 0 ? total.ceil() : 1;
+    List<Toping> aux = [];
+    if (r.isOk) {
+      for (var x in r.resultados!) {
+        Toping modelo = Toping.fromJson(x);
+        aux.add(modelo);
+      }
+    }
+    _registros = aux;
+    notifyListeners();
+    return _registros;
+  }
+
+  selectModelo(Toping Toping) {
+    _selectedModelo = Toping;
+    notifyListeners();
+  }
+
+  setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  cambiarPagina(int nuevaPagina, {bool segmentar = false}) {
+    pagina = nuevaPagina;
+    fetchRegistros(segmentar: segmentar);
+  }
+
+  Future eliminarMedia(Media m) async {
+    _selectedModelo!.mediaToping
+        .removeWhere((mediaToping) => mediaToping.media?.id == m.id);
+    notifyListeners();
+  }
+
+  Future<void> guardarModelo({
+    required Toping modelo,
+    required String clave,
+    required String nombre,
+    required String descripcion,
+    List<XFile>? fotos,
+  }) async {
+    modelo.clave = clave;
+    modelo.nombre = nombre;
+    modelo.descripcion = descripcion;
+    var r = ApiResponse(
+        await BaseService().post("admin/toping", body: modelo.toJson()));
+    int id = int.parse(r.detalle!["id"].toString());
+    if (id > 0 && fotos != null && fotos.isNotEmpty) {
+      for (var x in fotos) {
+        await GeneralService().enviarMedia(
+            id: id,
+            media: x,
+            modulo: "Autotanque",
+            etiquetaID: "idInspeccion",
+            tipo: "imagen");
+      }
+    }
+    var s =
+        await BaseService().post("admin/toping/guardar", body: modelo.toJson());
+    if (r.statusCode == 200) {}
+    notifyListeners();
+  }
+}

+ 3 - 0
lib/viewmodels/viewmodels.dart

@@ -3,3 +3,6 @@ export '../viewmodels/usuarios_view_model.dart';
 export '../viewmodels/profile_view_model.dart';
 export '../viewmodels/categoria_producto_view_model.dart';
 export '../viewmodels/producto_view_model.dart';
+export '../viewmodels/toping_categoria_view_model.dart';
+export '../viewmodels/toping_view_model.dart';
+export '../viewmodels/media_view_model.dart';

+ 703 - 0
lib/views/pedido/pedido_form copy.dart

@@ -0,0 +1,703 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:yoshi_papas_app/widgets/widgets.dart';
+import '../../themes/themes.dart';
+
+class PedidoForm extends StatefulWidget {
+  @override
+  _PedidoFormState createState() => _PedidoFormState();
+}
+
+enum CustomizationStep { Base, Salsa, Aderezo, Toppings }
+
+class _PedidoFormState extends State<PedidoForm> {
+  Map<String, bool> baseOptions = {};
+  Map<String, bool> sauceOptions = {};
+  Map<String, bool> dressingOptions = {};
+  Map<String, bool> toppingOptions = {};
+  bool isCustomizingProduct = false;
+  Map<String, dynamic>? currentProductForCustomization;
+  String? selectedBase;
+  List<String> selectedSauce = [];
+  List<String> selectedDressing = [];
+  List<String> selectedToppings = [];
+  List<Map<String, dynamic>> productsInCart = [];
+  String currentCategory = 'Hamburguesa de pollo';
+
+  Map<String, List<Map<String, dynamic>>> categoryProducts = {
+    'Hamburguesa de pollo': [
+      {
+        'name': 'Rebanada de queso',
+        'price': 25,
+        'category': 'Hamburguesa de pollo'
+      },
+      {
+        'name': 'Porción de queso',
+        'price': 15,
+        'category': 'Hamburguesa de pollo'
+      },
+      {
+        'name': 'Hamburguesa de pollo',
+        'price': 110,
+        'category': 'Hamburguesa de pollo'
+      },
+    ],
+    'Postres': [
+      {'name': 'Muffin', 'price': 35, 'category': 'Postres'},
+      {'name': 'Rebanada de Pay de Nuez', 'price': 65, 'category': 'Postres'},
+    ],
+    'Cono de papas': [
+      {
+        'name': 'Cono de papas grande',
+        'price': 120,
+        'category': 'Cono de papas'
+      },
+      {
+        'name': 'Cono de papas mediano',
+        'price': 85,
+        'category': 'Cono de papas'
+      },
+    ],
+  };
+  List<Map<String, dynamic>> products = [];
+
+  CustomizationStep _currentStep = CustomizationStep.Base;
+  late Map<CustomizationStep, Widget> _stepWidgets;
+
+  @override
+  void initState() {
+    super.initState();
+    // Inicializa con la categoría actual
+    products = categoryProducts[currentCategory]!;
+    initializeCheckboxStates();
+    _stepWidgets = {
+      CustomizationStep.Base: _buildBaseOptions(),
+      CustomizationStep.Salsa: _buildSauceOptions(),
+      CustomizationStep.Aderezo: _buildDressingOptions(),
+      CustomizationStep.Toppings: _buildToppingsOptions(),
+    };
+  }
+
+  void customizeProduct(Map<String, dynamic> product) {
+    if (product['category'] == 'Cono de papas') {
+      setState(() {
+        isCustomizingProduct = true;
+        currentProductForCustomization = product;
+      });
+    } else {
+      addToCart(product);
+    }
+  }
+
+  void initializeCheckboxStates() {
+    // Inicializa los mapas de opciones con valores false
+    for (var base in [
+      'Papa Gajo',
+      'Papa Regilla',
+      'Papa Curly',
+      'Papa Smile',
+      'Papa Francesa'
+    ]) {
+      baseOptions[base] = false;
+    }
+    for (var sauce in [
+      'BBQ',
+      'HOTBBQ',
+      'BUFFALO',
+      'TERIYAKI',
+      'PARMESAN GARLIC',
+      'MANGO HABANERO'
+    ]) {
+      sauceOptions[sauce] = false;
+    }
+    for (var dressing in ['QUESO AMARILLO', 'RANCH', 'CHIPOTLE', 'KETCHUP']) {
+      dressingOptions[dressing] = false;
+    }
+    for (var topping in [
+      'JALAPEÑO',
+      'QUESO BLANCO',
+      'TAKIS',
+      'RUFFLES',
+      'QUESO PARMESANO',
+      'ELOTE'
+    ]) {
+      toppingOptions[topping] = false;
+    }
+  }
+
+  void addToCart(Map<String, dynamic> product) {
+    // Si es un "Cono de papas" y estamos personalizando
+    if (product['category'] == 'Cono de papas' && isCustomizingProduct) {
+      final Map<String, dynamic> customizedProduct = {
+        ...product,
+        'customizations': {
+          'base': selectedBase,
+          'sauce': selectedSauce,
+          'dressing': selectedDressing,
+          'toppings': selectedToppings,
+        },
+        'quantity':
+            1, // Asegúrate de que cada producto personalizado tenga una cantidad inicial de 1
+      };
+
+      setState(() {
+        productsInCart
+            .add(customizedProduct); // Añade el producto personalizado
+        isCustomizingProduct = false; // Termina la personalización
+        resetCustomizations(); // Llama a un método que restablecerá las personalizaciones
+      });
+    } else {
+      // Si no es un "Cono de papas" o no estamos personalizando, añade directamente al carrito
+      setState(() {
+        int index = productsInCart.indexWhere((p) =>
+            p['name'] == product['name'] &&
+            p['customizations'] ==
+                product[
+                    'customizations']); // Comparar también las personalizaciones
+        if (index != -1) {
+          productsInCart[index]['quantity'] += 1;
+        } else {
+          productsInCart.add(
+              {...product, 'quantity': 1}); // Añade con cantidad inicial de 1
+        }
+      });
+    }
+  }
+
+  void resetCustomizations() {
+    // Restablece las variables de estado de las personalizaciones
+    selectedBase = null;
+    selectedSauce = [];
+    selectedDressing = [];
+    selectedToppings = [];
+    // Restablecer cualquier otro estado de personalización aquí
+  }
+
+  void finalizeCustomization() {
+    // Aquí debes construir el producto basado en las selecciones de personalización
+
+    selectedBase = baseOptions.entries
+        .firstWhere((entry) => entry.value, orElse: () => MapEntry('', false))
+        .key;
+    selectedSauce = sauceOptions.entries
+        .where((entry) => entry.value)
+        .map((e) => e.key)
+        .toList();
+    selectedDressing = dressingOptions.entries
+        .where((entry) => entry.value)
+        .map((e) => e.key)
+        .toList();
+    selectedToppings = toppingOptions.entries
+        .where((entry) => entry.value)
+        .map((e) => e.key)
+        .toList();
+
+    Map<String, dynamic> customizedProduct = {
+      ...currentProductForCustomization!,
+      'customizations': {
+        'base': selectedBase,
+        'sauce': selectedSauce,
+        'dressing': selectedDressing,
+        'toppings': selectedToppings,
+      }
+    };
+
+    addToCart(customizedProduct);
+    setState(() {
+      isCustomizingProduct = false;
+      currentProductForCustomization = null;
+      initializeCheckboxStates(); // Reinicia los estados de los checkbox
+    });
+  }
+
+  Widget buildCategoryButtons() {
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.center,
+      children: categoryProducts.keys.map((String key) {
+        return Padding(
+          padding: const EdgeInsets.symmetric(horizontal: 4.0),
+          child: OutlinedButton(
+            onPressed: () {
+              setState(() {
+                currentCategory = key;
+                products = categoryProducts[key]!;
+              });
+            },
+            style: OutlinedButton.styleFrom(
+              backgroundColor: Colors.white,
+              foregroundColor: Colors.black,
+              side: BorderSide(
+                width: 2.0,
+                color: currentCategory == key ? AppTheme.primary : Colors.grey,
+              ),
+            ),
+            child: Text(key),
+          ),
+        );
+      }).toList(),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: encabezado(
+        titulo: "CREAR PEDIDO",
+      ),
+      body: Row(
+        children: [
+          // Sección izquierda con la lista del carrito - 30%
+          Flexible(
+            flex: 3,
+            child: tarjeta(
+              Column(
+                children: [
+                  // Encabezados de la lista
+                  const Padding(
+                    padding:
+                        EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
+                    child: Row(
+                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                      children: [
+                        Text('Producto',
+                            style: TextStyle(
+                                fontWeight: FontWeight.bold, fontSize: 18)),
+                        Text('Cantidad',
+                            style: TextStyle(
+                                fontWeight: FontWeight.bold, fontSize: 18)),
+                      ],
+                    ),
+                  ),
+                  // Lista de productos
+                  Expanded(
+                    child: ListView.builder(
+                      itemCount: productsInCart.length,
+                      itemBuilder: (context, index) {
+                        var product = productsInCart[index];
+                        return ListTile(
+                          title: _buildProductItem(product),
+                          trailing: Row(
+                            mainAxisSize: MainAxisSize.min,
+                            children: [
+                              IconButton(
+                                icon: const Icon(Icons.remove),
+                                onPressed: () {
+                                  // Lógica para disminuir la cantidad
+                                  setState(() {
+                                    if (product['quantity'] > 1) {
+                                      productsInCart[index]['quantity'] -= 1;
+                                    } else {
+                                      productsInCart.removeAt(index);
+                                    }
+                                  });
+                                },
+                              ),
+                              Text('${product['quantity'] ?? 1}'),
+                              IconButton(
+                                icon: const Icon(Icons.add),
+                                onPressed: () {
+                                  // Lógica para aumentar la cantidad
+                                  setState(() {
+                                    productsInCart[index]['quantity'] += 1;
+                                  });
+                                },
+                              ),
+                            ],
+                          ),
+                        );
+                      },
+                    ),
+                  ),
+                  ElevatedButton(
+                    style: ButtonStyle(
+                        backgroundColor:
+                            MaterialStatePropertyAll(AppTheme.primary),
+                        textStyle: const MaterialStatePropertyAll(
+                            TextStyle(fontSize: 22)),
+                        foregroundColor:
+                            const MaterialStatePropertyAll(Colors.black),
+                        padding: const MaterialStatePropertyAll(
+                            EdgeInsets.fromLTRB(80, 20, 80, 20))),
+                    onPressed: () {
+                      // Aquí agregarías la lógica para guardar el pedido
+                    },
+                    child: const Text('Guardar'),
+                  ),
+                ],
+              ),
+              color: Colors.white,
+              padding: 8.0,
+            ),
+          ),
+
+          const SizedBox(
+            width: 35,
+          ),
+
+          // Sección derecha con los productos disponibles - 70%
+          Flexible(
+            flex: 7,
+            child: Column(
+              children: [
+                // Botones de categorías de productos
+                buildCategoryButtons(),
+
+                // GridView de productos
+                Expanded(
+                  child: isCustomizingProduct
+                      ? buildCustomizationOptions()
+                      : buildProductGrid(),
+                ),
+              ],
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildProductItem(Map<String, dynamic> product) {
+    List<Widget> customizationWidgets = [];
+    if (product.containsKey('customizations')) {
+      Map customizations = product['customizations'];
+      customizationWidgets.addAll([
+        Text(' - Base: ${customizations['base']}'),
+        ...customizations['sauce']
+            .map<Widget>((sauce) => Text(' - Salsa: $sauce'))
+            .toList(),
+        ...customizations['dressing']
+            .map<Widget>((dressing) => Text(' - Aderezo: $dressing'))
+            .toList(),
+        ...customizations['toppings']
+            .map<Widget>((topping) => Text(' - Topping: $topping'))
+            .toList(),
+      ]);
+    }
+
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Text(product['name']),
+        ...customizationWidgets,
+      ],
+    );
+  }
+
+  Widget buildProductGrid() {
+    return tarjeta(
+      GridView.builder(
+        itemCount: products.length,
+        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+          crossAxisCount: 2, // Número de columnas
+          childAspectRatio: 3 / 2, // Proporción de cada tarjeta
+          crossAxisSpacing: 10, // Espaciado horizontal
+          mainAxisSpacing: 10, // Espaciado vertical
+        ),
+        itemBuilder: (context, index) {
+          final product = products[index];
+          return tarjeta(
+            InkWell(
+              onTap: () => customizeProduct(product),
+              child: Column(
+                mainAxisAlignment: MainAxisAlignment.center,
+                children: [
+                  // Añade el ícono aquí
+                  const Icon(Icons.fastfood,
+                      size: 80), // Tamaño del ícono ajustable
+                  const SizedBox(height: 8), // Espacio entre ícono y texto
+                  Text(
+                    product['name'],
+                    style: const TextStyle(fontSize: 16),
+                    textAlign: TextAlign.center,
+                  ),
+                  const SizedBox(height: 8), // Espacio entre texto y precio
+                  Text(
+                    '\$${product['price']}',
+                    style:
+                        const TextStyle(fontSize: 24, color: Color(0xFF008000)),
+                    textAlign: TextAlign.center,
+                  ),
+                ],
+              ),
+            ),
+            color: const Color(0xFFF4F4F4),
+            padding: 8.0,
+          );
+        },
+      ),
+      color: Colors.white,
+      padding: 8.0,
+    );
+  }
+
+  Widget buildCustomizationOptions() {
+    Widget currentStepWidget;
+    switch (_currentStep) {
+      case CustomizationStep.Base:
+        currentStepWidget = _buildBaseOptions();
+        break;
+      case CustomizationStep.Salsa:
+        currentStepWidget = _buildSauceOptions();
+        break;
+      case CustomizationStep.Aderezo:
+        currentStepWidget = _buildDressingOptions();
+        break;
+      case CustomizationStep.Toppings:
+        currentStepWidget = _buildToppingsOptions();
+        break;
+      default:
+        currentStepWidget = SizedBox.shrink();
+    }
+
+    // Solo muestra el botón de confirmación si isCustomizingProduct es true.
+    Widget confirmButton = isCustomizingProduct
+        ? Padding(
+            padding: const EdgeInsets.symmetric(vertical: 16.0),
+            child: _buildConfirmButton(),
+          )
+        : SizedBox.shrink();
+
+    return tarjeta(
+      Column(
+        children: [
+          Expanded(
+            child: Row(
+              children: [
+                Flexible(
+                  flex: 3,
+                  child: ListView(
+                    children: CustomizationStep.values.map((step) {
+                      // Verifica si el paso es Salsa, Aderezo o Toppings para añadir el texto " (Max 2)"
+                      String stepName = describeEnum(step);
+                      if (step == CustomizationStep.Salsa ||
+                          step == CustomizationStep.Aderezo ||
+                          step == CustomizationStep.Toppings) {
+                        stepName +=
+                            " (Max 2)"; // Agrega " (Max 2)" al nombre del paso+
+                      }
+
+                      return ListTile(
+                        title: Text(stepName),
+                        selected: _currentStep == step,
+                        onTap: () => setState(() => _currentStep = step),
+                      );
+                    }).toList(),
+                  ),
+                ),
+                Flexible(
+                  flex: 7,
+                  child: currentStepWidget,
+                ),
+              ],
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.symmetric(vertical: 16.0),
+            child: _buildConfirmButton(),
+          ), // Incluir el botón de confirmación aquí
+        ],
+      ),
+      color: Colors.white,
+      padding: 8.0,
+    );
+  }
+
+  Widget buildCheckboxListTile({
+    required String title,
+    required Map<String, bool> optionsMap,
+    required Function(String, bool?) onChanged,
+  }) {
+    return CheckboxListTile(
+      title: Text(title),
+      value: optionsMap[title] ?? false,
+      onChanged: (bool? value) {
+        setState(() {
+          onChanged(title, value);
+        });
+      },
+      secondary: Image.asset('assets/JoshiLogo.png', width: 30),
+    );
+  }
+
+  Widget _buildBaseOptions() {
+    return GridView.count(
+      crossAxisCount: 3,
+      children: baseOptions.keys.map((String key) {
+        bool isSelected =
+            baseOptions[key] ?? false; // Determina si está seleccionado
+        return GestureDetector(
+          onTap: () {
+            setState(() {
+              baseOptions.keys.forEach(
+                  (k) => baseOptions[k] = false); // Desmarca todos primero
+              baseOptions[key] = true; // Marca el seleccionado
+            });
+          },
+          child: Container(
+            decoration: BoxDecoration(
+              color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
+              border: Border.all(color: isSelected ? Colors.red : Colors.grey),
+            ),
+            child: Center(
+              child: Text(key,
+                  style: TextStyle(
+                      color: isSelected ? Colors.white : Colors.black)),
+            ),
+          ),
+        );
+      }).toList(),
+    );
+  }
+
+  Widget _buildSauceOptions() {
+    return GridView.count(
+      crossAxisCount: 3,
+      children: sauceOptions.keys.map((String key) {
+        bool isSelected = sauceOptions[key] ?? false;
+        return GestureDetector(
+          onTap: () {
+            int selectedCount = sauceOptions.values.where((b) => b).length;
+            setState(() {
+              // Si la salsa ya está seleccionada, la deselecciona.
+              if (isSelected) {
+                sauceOptions[key] = false;
+              } else {
+                // Si se están seleccionando menos de 2 salsas, permite esta selección.
+                if (selectedCount < 2) {
+                  sauceOptions[key] = true;
+                } else {
+                  // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada.
+                  final firstSelected = sauceOptions.keys.firstWhere(
+                    (k) => sauceOptions[k]!,
+                    orElse: () => '',
+                  );
+                  if (firstSelected.isNotEmpty) {
+                    sauceOptions[firstSelected] = false;
+                    sauceOptions[key] = true;
+                  }
+                }
+              }
+            });
+          },
+          child: Container(
+            decoration: BoxDecoration(
+              color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
+              border: Border.all(color: Colors.grey),
+            ),
+            child: Center(
+              child: Text(key,
+                  style: TextStyle(
+                      color: isSelected ? Colors.white : Colors.black)),
+            ),
+          ),
+        );
+      }).toList(),
+    );
+  }
+
+  Widget _buildDressingOptions() {
+    return GridView.count(
+      crossAxisCount: 3,
+      children: dressingOptions.keys.map((String key) {
+        bool isSelected = dressingOptions[key] ?? false;
+        return GestureDetector(
+          onTap: () {
+            int selectedCount = dressingOptions.values.where((b) => b).length;
+            setState(() {
+              // Si la salsa ya está seleccionada, la deselecciona.
+              if (isSelected) {
+                dressingOptions[key] = false;
+              } else {
+                // Si se están seleccionando menos de 2 salsas, permite esta selección.
+                if (selectedCount < 2) {
+                  dressingOptions[key] = true;
+                } else {
+                  // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada.
+                  final firstSelected = dressingOptions.keys.firstWhere(
+                    (k) => dressingOptions[k]!,
+                    orElse: () => '',
+                  );
+                  if (firstSelected.isNotEmpty) {
+                    dressingOptions[firstSelected] = false;
+                    dressingOptions[key] = true;
+                  }
+                }
+              }
+            });
+          },
+          child: Container(
+            decoration: BoxDecoration(
+              color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
+              border: Border.all(color: Colors.grey),
+            ),
+            child: Center(
+              child: Text(key,
+                  style: TextStyle(
+                      color: isSelected ? Colors.white : Colors.black)),
+            ),
+          ),
+        );
+      }).toList(),
+    );
+  }
+
+  Widget _buildToppingsOptions() {
+    return GridView.count(
+      crossAxisCount: 3,
+      children: toppingOptions.keys.map((String key) {
+        bool isSelected = toppingOptions[key] ?? false;
+        return GestureDetector(
+          onTap: () {
+            int selectedCount = toppingOptions.values.where((b) => b).length;
+            setState(() {
+              // Si la salsa ya está seleccionada, la deselecciona.
+              if (isSelected) {
+                toppingOptions[key] = false;
+              } else {
+                // Si se están seleccionando menos de 2 salsas, permite esta selección.
+                if (selectedCount < 2) {
+                  toppingOptions[key] = true;
+                } else {
+                  // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada.
+                  final firstSelected = toppingOptions.keys.firstWhere(
+                    (k) => toppingOptions[k]!,
+                    orElse: () => '',
+                  );
+                  if (firstSelected.isNotEmpty) {
+                    toppingOptions[firstSelected] = false;
+                    toppingOptions[key] = true;
+                  }
+                }
+              }
+            });
+          },
+          child: Container(
+            decoration: BoxDecoration(
+              color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
+              border: Border.all(color: Colors.grey),
+            ),
+            child: Center(
+              child: Text(key,
+                  style: TextStyle(
+                      color: isSelected ? Colors.white : Colors.black)),
+            ),
+          ),
+        );
+      }).toList(),
+    );
+  }
+
+  Widget _buildConfirmButton() {
+    return ElevatedButton(
+      style: ElevatedButton.styleFrom(
+          primary: AppTheme.primary, // Color del botón
+          onPrimary: Colors.black,
+          textStyle: const TextStyle(fontSize: 22),
+          padding: const EdgeInsets.fromLTRB(80, 20, 80, 20)),
+      onPressed: () {
+        finalizeCustomization(); // Este método creará el pedido personalizado
+      },
+      child: Text('Confirmar Pedido'),
+    );
+  }
+}

+ 254 - 631
lib/views/pedido/pedido_form.dart

@@ -1,703 +1,326 @@
-import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
-import 'package:yoshi_papas_app/widgets/widgets.dart';
-import '../../themes/themes.dart';
+import '../../themes/themes.dart'; // Asegúrate de tener este archivo con los temas correctos
+import '../../models/models.dart'; // Tus modelos de datos
+import '../../viewmodels/viewmodels.dart'; // Tus ViewModels
 
 class PedidoForm extends StatefulWidget {
   @override
   _PedidoFormState createState() => _PedidoFormState();
 }
 
-enum CustomizationStep { Base, Salsa, Aderezo, Toppings }
+class ItemCarrito {
+  Producto producto;
+  int cantidad;
 
-class _PedidoFormState extends State<PedidoForm> {
-  Map<String, bool> baseOptions = {};
-  Map<String, bool> sauceOptions = {};
-  Map<String, bool> dressingOptions = {};
-  Map<String, bool> toppingOptions = {};
-  bool isCustomizingProduct = false;
-  Map<String, dynamic>? currentProductForCustomization;
-  String? selectedBase;
-  List<String> selectedSauce = [];
-  List<String> selectedDressing = [];
-  List<String> selectedToppings = [];
-  List<Map<String, dynamic>> productsInCart = [];
-  String currentCategory = 'Hamburguesa de pollo';
-
-  Map<String, List<Map<String, dynamic>>> categoryProducts = {
-    'Hamburguesa de pollo': [
-      {
-        'name': 'Rebanada de queso',
-        'price': 25,
-        'category': 'Hamburguesa de pollo'
-      },
-      {
-        'name': 'Porción de queso',
-        'price': 15,
-        'category': 'Hamburguesa de pollo'
-      },
-      {
-        'name': 'Hamburguesa de pollo',
-        'price': 110,
-        'category': 'Hamburguesa de pollo'
-      },
-    ],
-    'Postres': [
-      {'name': 'Muffin', 'price': 35, 'category': 'Postres'},
-      {'name': 'Rebanada de Pay de Nuez', 'price': 65, 'category': 'Postres'},
-    ],
-    'Cono de papas': [
-      {
-        'name': 'Cono de papas grande',
-        'price': 120,
-        'category': 'Cono de papas'
-      },
-      {
-        'name': 'Cono de papas mediano',
-        'price': 85,
-        'category': 'Cono de papas'
-      },
-    ],
-  };
-  List<Map<String, dynamic>> products = [];
+  ItemCarrito({required this.producto, this.cantidad = 1});
+}
 
-  CustomizationStep _currentStep = CustomizationStep.Base;
-  late Map<CustomizationStep, Widget> _stepWidgets;
+class _PedidoFormState extends State<PedidoForm> {
+  CategoriaProductoViewModel cvm = CategoriaProductoViewModel();
+  ProductoViewModel pvm = ProductoViewModel();
+  bool _isLoading = false;
+  ScrollController horizontalScrollController = ScrollController();
+  CategoriaProducto? categoriaSeleccionada;
+  List<CategoriaProducto> categorias = [];
+  List<Producto> productos = [];
+  List<ItemCarrito> carrito = [];
+
+  double calcularTotalPedido() {
+    double total = 0;
+    for (var item in carrito) {
+      total += double.parse(item.producto.precio!) * item.cantidad;
+    }
+    return total;
+  }
 
   @override
   void initState() {
     super.initState();
-    // Inicializa con la categoría actual
-    products = categoryProducts[currentCategory]!;
-    initializeCheckboxStates();
-    _stepWidgets = {
-      CustomizationStep.Base: _buildBaseOptions(),
-      CustomizationStep.Salsa: _buildSauceOptions(),
-      CustomizationStep.Aderezo: _buildDressingOptions(),
-      CustomizationStep.Toppings: _buildToppingsOptions(),
-    };
+    cargarCategoriasIniciales();
   }
 
-  void customizeProduct(Map<String, dynamic> product) {
-    if (product['category'] == 'Cono de papas') {
-      setState(() {
-        isCustomizingProduct = true;
-        currentProductForCustomization = product;
-      });
-    } else {
-      addToCart(product);
-    }
-  }
-
-  void initializeCheckboxStates() {
-    // Inicializa los mapas de opciones con valores false
-    for (var base in [
-      'Papa Gajo',
-      'Papa Regilla',
-      'Papa Curly',
-      'Papa Smile',
-      'Papa Francesa'
-    ]) {
-      baseOptions[base] = false;
-    }
-    for (var sauce in [
-      'BBQ',
-      'HOTBBQ',
-      'BUFFALO',
-      'TERIYAKI',
-      'PARMESAN GARLIC',
-      'MANGO HABANERO'
-    ]) {
-      sauceOptions[sauce] = false;
-    }
-    for (var dressing in ['QUESO AMARILLO', 'RANCH', 'CHIPOTLE', 'KETCHUP']) {
-      dressingOptions[dressing] = false;
-    }
-    for (var topping in [
-      'JALAPEÑO',
-      'QUESO BLANCO',
-      'TAKIS',
-      'RUFFLES',
-      'QUESO PARMESANO',
-      'ELOTE'
-    ]) {
-      toppingOptions[topping] = false;
-    }
+  @override
+  void dispose() {
+    horizontalScrollController.dispose();
+    super.dispose();
   }
 
-  void addToCart(Map<String, dynamic> product) {
-    // Si es un "Cono de papas" y estamos personalizando
-    if (product['category'] == 'Cono de papas' && isCustomizingProduct) {
-      final Map<String, dynamic> customizedProduct = {
-        ...product,
-        'customizations': {
-          'base': selectedBase,
-          'sauce': selectedSauce,
-          'dressing': selectedDressing,
-          'toppings': selectedToppings,
-        },
-        'quantity':
-            1, // Asegúrate de que cada producto personalizado tenga una cantidad inicial de 1
-      };
-
-      setState(() {
-        productsInCart
-            .add(customizedProduct); // Añade el producto personalizado
-        isCustomizingProduct = false; // Termina la personalización
-        resetCustomizations(); // Llama a un método que restablecerá las personalizaciones
-      });
-    } else {
-      // Si no es un "Cono de papas" o no estamos personalizando, añade directamente al carrito
-      setState(() {
-        int index = productsInCart.indexWhere((p) =>
-            p['name'] == product['name'] &&
-            p['customizations'] ==
-                product[
-                    'customizations']); // Comparar también las personalizaciones
-        if (index != -1) {
-          productsInCart[index]['quantity'] += 1;
-        } else {
-          productsInCart.add(
-              {...product, 'quantity': 1}); // Añade con cantidad inicial de 1
-        }
-      });
-    }
+  Future<void> cargarCategoriasIniciales() async {
+    setState(() => _isLoading = true);
+    var categoriasObtenidas = await cvm.getCategoriaProducto();
+    setState(() {
+      categorias = categoriasObtenidas;
+      _isLoading = false;
+      // Selecciona la primera categoría por defecto si hay categorías disponibles
+      if (categorias.isNotEmpty) {
+        categoriaSeleccionada = categorias.first;
+        seleccionarCategoria(categoriaSeleccionada!);
+      }
+    });
   }
 
-  void resetCustomizations() {
-    // Restablece las variables de estado de las personalizaciones
-    selectedBase = null;
-    selectedSauce = [];
-    selectedDressing = [];
-    selectedToppings = [];
-    // Restablecer cualquier otro estado de personalización aquí
+  Future<void> seleccionarCategoria(CategoriaProducto categoria) async {
+    if (!mounted) return;
+    setState(() {
+      _isLoading = true;
+      categoriaSeleccionada = categoria;
+    });
+    productos = await pvm.fetchRegistros(categoriaProducto: categoria);
+    if (!mounted) return;
+    setState(() => _isLoading = false);
   }
 
-  void finalizeCustomization() {
-    // Aquí debes construir el producto basado en las selecciones de personalización
-
-    selectedBase = baseOptions.entries
-        .firstWhere((entry) => entry.value, orElse: () => MapEntry('', false))
-        .key;
-    selectedSauce = sauceOptions.entries
-        .where((entry) => entry.value)
-        .map((e) => e.key)
-        .toList();
-    selectedDressing = dressingOptions.entries
-        .where((entry) => entry.value)
-        .map((e) => e.key)
-        .toList();
-    selectedToppings = toppingOptions.entries
-        .where((entry) => entry.value)
-        .map((e) => e.key)
-        .toList();
-
-    Map<String, dynamic> customizedProduct = {
-      ...currentProductForCustomization!,
-      'customizations': {
-        'base': selectedBase,
-        'sauce': selectedSauce,
-        'dressing': selectedDressing,
-        'toppings': selectedToppings,
-      }
-    };
-
-    addToCart(customizedProduct);
+  void agregarAlCarrito(Producto producto) {
     setState(() {
-      isCustomizingProduct = false;
-      currentProductForCustomization = null;
-      initializeCheckboxStates(); // Reinicia los estados de los checkbox
+      var indice =
+          carrito.indexWhere((item) => item.producto.id == producto.id);
+      if (indice != -1) {
+        carrito[indice].cantidad++;
+      } else {
+        // Se agrega el nuevo producto al carrito con cantidad inicial de 1
+        carrito.add(ItemCarrito(producto: producto));
+      }
     });
   }
 
-  Widget buildCategoryButtons() {
-    return Row(
-      mainAxisAlignment: MainAxisAlignment.center,
-      children: categoryProducts.keys.map((String key) {
-        return Padding(
-          padding: const EdgeInsets.symmetric(horizontal: 4.0),
-          child: OutlinedButton(
-            onPressed: () {
-              setState(() {
-                currentCategory = key;
-                products = categoryProducts[key]!;
-              });
-            },
-            style: OutlinedButton.styleFrom(
-              backgroundColor: Colors.white,
-              foregroundColor: Colors.black,
-              side: BorderSide(
-                width: 2.0,
-                color: currentCategory == key ? AppTheme.primary : Colors.grey,
-              ),
-            ),
-            child: Text(key),
-          ),
-        );
-      }).toList(),
-    );
+  void quitarDelCarrito(Producto producto) {
+    setState(() {
+      // Comienza con setState por la misma razón
+      var indice =
+          carrito.indexWhere((item) => item.producto.id == producto.id);
+      if (indice != -1) {
+        if (carrito[indice].cantidad > 1) {
+          carrito[indice].cantidad--;
+        } else {
+          carrito.removeAt(indice);
+        }
+      }
+    });
   }
 
   @override
   Widget build(BuildContext context) {
     return Scaffold(
-      appBar: encabezado(
-        titulo: "CREAR PEDIDO",
+      appBar: AppBar(
+        title: Text("Crear Pedido"),
       ),
       body: Row(
         children: [
-          // Sección izquierda con la lista del carrito - 30%
           Flexible(
             flex: 3,
-            child: tarjeta(
-              Column(
-                children: [
-                  // Encabezados de la lista
-                  const Padding(
-                    padding:
-                        EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
-                    child: Row(
-                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                      children: [
-                        Text('Producto',
-                            style: TextStyle(
-                                fontWeight: FontWeight.bold, fontSize: 18)),
-                        Text('Cantidad',
-                            style: TextStyle(
-                                fontWeight: FontWeight.bold, fontSize: 18)),
-                      ],
-                    ),
-                  ),
-                  // Lista de productos
-                  Expanded(
-                    child: ListView.builder(
-                      itemCount: productsInCart.length,
-                      itemBuilder: (context, index) {
-                        var product = productsInCart[index];
-                        return ListTile(
-                          title: _buildProductItem(product),
-                          trailing: Row(
-                            mainAxisSize: MainAxisSize.min,
-                            children: [
-                              IconButton(
-                                icon: const Icon(Icons.remove),
-                                onPressed: () {
-                                  // Lógica para disminuir la cantidad
-                                  setState(() {
-                                    if (product['quantity'] > 1) {
-                                      productsInCart[index]['quantity'] -= 1;
-                                    } else {
-                                      productsInCart.removeAt(index);
-                                    }
-                                  });
-                                },
-                              ),
-                              Text('${product['quantity'] ?? 1}'),
-                              IconButton(
-                                icon: const Icon(Icons.add),
-                                onPressed: () {
-                                  // Lógica para aumentar la cantidad
-                                  setState(() {
-                                    productsInCart[index]['quantity'] += 1;
-                                  });
-                                },
-                              ),
-                            ],
-                          ),
-                        );
-                      },
-                    ),
-                  ),
-                  ElevatedButton(
-                    style: ButtonStyle(
-                        backgroundColor:
-                            MaterialStatePropertyAll(AppTheme.primary),
-                        textStyle: const MaterialStatePropertyAll(
-                            TextStyle(fontSize: 22)),
-                        foregroundColor:
-                            const MaterialStatePropertyAll(Colors.black),
-                        padding: const MaterialStatePropertyAll(
-                            EdgeInsets.fromLTRB(80, 20, 80, 20))),
-                    onPressed: () {
-                      // Aquí agregarías la lógica para guardar el pedido
-                    },
-                    child: const Text('Guardar'),
-                  ),
-                ],
-              ),
-              color: Colors.white,
-              padding: 8.0,
-            ),
+            child: _buildCartSection(),
           ),
-
-          const SizedBox(
-            width: 35,
-          ),
-
-          // Sección derecha con los productos disponibles - 70%
+          SizedBox(width: 35),
           Flexible(
             flex: 7,
-            child: Column(
-              children: [
-                // Botones de categorías de productos
-                buildCategoryButtons(),
-
-                // GridView de productos
-                Expanded(
-                  child: isCustomizingProduct
-                      ? buildCustomizationOptions()
-                      : buildProductGrid(),
-                ),
-              ],
-            ),
+            child: _buildProductsSection(),
           ),
         ],
       ),
     );
   }
 
-  Widget _buildProductItem(Map<String, dynamic> product) {
-    List<Widget> customizationWidgets = [];
-    if (product.containsKey('customizations')) {
-      Map customizations = product['customizations'];
-      customizationWidgets.addAll([
-        Text(' - Base: ${customizations['base']}'),
-        ...customizations['sauce']
-            .map<Widget>((sauce) => Text(' - Salsa: $sauce'))
-            .toList(),
-        ...customizations['dressing']
-            .map<Widget>((dressing) => Text(' - Aderezo: $dressing'))
-            .toList(),
-        ...customizations['toppings']
-            .map<Widget>((topping) => Text(' - Topping: $topping'))
-            .toList(),
-      ]);
-    }
-
-    return Column(
-      crossAxisAlignment: CrossAxisAlignment.start,
-      children: [
-        Text(product['name']),
-        ...customizationWidgets,
-      ],
-    );
-  }
-
-  Widget buildProductGrid() {
-    return tarjeta(
-      GridView.builder(
-        itemCount: products.length,
-        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
-          crossAxisCount: 2, // Número de columnas
-          childAspectRatio: 3 / 2, // Proporción de cada tarjeta
-          crossAxisSpacing: 10, // Espaciado horizontal
-          mainAxisSpacing: 10, // Espaciado vertical
-        ),
-        itemBuilder: (context, index) {
-          final product = products[index];
-          return tarjeta(
-            InkWell(
-              onTap: () => customizeProduct(product),
-              child: Column(
-                mainAxisAlignment: MainAxisAlignment.center,
-                children: [
-                  // Añade el ícono aquí
-                  const Icon(Icons.fastfood,
-                      size: 80), // Tamaño del ícono ajustable
-                  const SizedBox(height: 8), // Espacio entre ícono y texto
-                  Text(
-                    product['name'],
-                    style: const TextStyle(fontSize: 16),
-                    textAlign: TextAlign.center,
-                  ),
-                  const SizedBox(height: 8), // Espacio entre texto y precio
-                  Text(
-                    '\$${product['price']}',
-                    style:
-                        const TextStyle(fontSize: 24, color: Color(0xFF008000)),
-                    textAlign: TextAlign.center,
-                  ),
-                ],
-              ),
-            ),
-            color: const Color(0xFFF4F4F4),
-            padding: 8.0,
-          );
-        },
+  Widget _buildTotalSection() {
+    double total =
+        calcularTotalPedido(); // Aquí llamarías a la función calcularTotalPedido
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 8.0),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          const Text('Total',
+              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
+          Text("\$${total.toStringAsFixed(2)}",
+              style:
+                  const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
+        ],
       ),
-      color: Colors.white,
-      padding: 8.0,
     );
   }
 
-  Widget buildCustomizationOptions() {
-    Widget currentStepWidget;
-    switch (_currentStep) {
-      case CustomizationStep.Base:
-        currentStepWidget = _buildBaseOptions();
-        break;
-      case CustomizationStep.Salsa:
-        currentStepWidget = _buildSauceOptions();
-        break;
-      case CustomizationStep.Aderezo:
-        currentStepWidget = _buildDressingOptions();
-        break;
-      case CustomizationStep.Toppings:
-        currentStepWidget = _buildToppingsOptions();
-        break;
-      default:
-        currentStepWidget = SizedBox.shrink();
-    }
-
-    // Solo muestra el botón de confirmación si isCustomizingProduct es true.
-    Widget confirmButton = isCustomizingProduct
-        ? Padding(
-            padding: const EdgeInsets.symmetric(vertical: 16.0),
-            child: _buildConfirmButton(),
-          )
-        : SizedBox.shrink();
-
-    return tarjeta(
-      Column(
+  Widget _buildCartSection() {
+    return Card(
+      margin: const EdgeInsets.all(8.0),
+      child: Column(
         children: [
-          Expanded(
+          const Padding(
+            padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
             child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
               children: [
-                Flexible(
-                  flex: 3,
-                  child: ListView(
-                    children: CustomizationStep.values.map((step) {
-                      // Verifica si el paso es Salsa, Aderezo o Toppings para añadir el texto " (Max 2)"
-                      String stepName = describeEnum(step);
-                      if (step == CustomizationStep.Salsa ||
-                          step == CustomizationStep.Aderezo ||
-                          step == CustomizationStep.Toppings) {
-                        stepName +=
-                            " (Max 2)"; // Agrega " (Max 2)" al nombre del paso+
-                      }
-
-                      return ListTile(
-                        title: Text(stepName),
-                        selected: _currentStep == step,
-                        onTap: () => setState(() => _currentStep = step),
-                      );
-                    }).toList(),
-                  ),
-                ),
-                Flexible(
-                  flex: 7,
-                  child: currentStepWidget,
-                ),
+                Text('Producto',
+                    style:
+                        TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
+                Text('Cantidad',
+                    style:
+                        TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
               ],
             ),
           ),
-          Padding(
-            padding: const EdgeInsets.symmetric(vertical: 16.0),
-            child: _buildConfirmButton(),
-          ), // Incluir el botón de confirmación aquí
-        ],
-      ),
-      color: Colors.white,
-      padding: 8.0,
-    );
-  }
-
-  Widget buildCheckboxListTile({
-    required String title,
-    required Map<String, bool> optionsMap,
-    required Function(String, bool?) onChanged,
-  }) {
-    return CheckboxListTile(
-      title: Text(title),
-      value: optionsMap[title] ?? false,
-      onChanged: (bool? value) {
-        setState(() {
-          onChanged(title, value);
-        });
-      },
-      secondary: Image.asset('assets/JoshiLogo.png', width: 30),
-    );
-  }
-
-  Widget _buildBaseOptions() {
-    return GridView.count(
-      crossAxisCount: 3,
-      children: baseOptions.keys.map((String key) {
-        bool isSelected =
-            baseOptions[key] ?? false; // Determina si está seleccionado
-        return GestureDetector(
-          onTap: () {
-            setState(() {
-              baseOptions.keys.forEach(
-                  (k) => baseOptions[k] = false); // Desmarca todos primero
-              baseOptions[key] = true; // Marca el seleccionado
-            });
-          },
-          child: Container(
-            decoration: BoxDecoration(
-              color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
-              border: Border.all(color: isSelected ? Colors.red : Colors.grey),
+          Expanded(
+            child: ListView.builder(
+              itemCount: carrito.length,
+              itemBuilder: (context, index) {
+                final item = carrito[index];
+                return ListTile(
+                  title: Text(item.producto.nombre!),
+                  subtitle: Text('\$${item.producto.precio}'),
+                  trailing: Row(
+                    mainAxisSize: MainAxisSize.min,
+                    children: [
+                      IconButton(
+                        icon: const Icon(Icons.delete, color: Colors.red),
+                        onPressed: () => eliminarProductoDelCarrito(index),
+                      ),
+                      IconButton(
+                        icon: const Icon(Icons.remove),
+                        onPressed: () => quitarDelCarrito(item.producto),
+                      ),
+                      const SizedBox(width: 5),
+                      Text(
+                        '${item.cantidad}',
+                        style: const TextStyle(
+                            fontWeight: FontWeight.bold, fontSize: 14),
+                      ),
+                      const SizedBox(width: 5),
+                      IconButton(
+                        icon: const Icon(Icons.add),
+                        onPressed: () => agregarAlCarrito(item.producto),
+                      ),
+                      // Botón para eliminar el producto del carrito
+                    ],
+                  ),
+                );
+              },
             ),
-            child: Center(
-              child: Text(key,
-                  style: TextStyle(
-                      color: isSelected ? Colors.white : Colors.black)),
+          ),
+          const Divider(
+            thickness: 5,
+          ),
+          _buildTotalSection(),
+          const SizedBox(height: 25),
+          Padding(
+            padding: const EdgeInsets.all(8.0),
+            child: ElevatedButton(
+              onPressed: () {
+                // Aquí implementarías la lógica para finalizar el pedido
+              },
+              style: ElevatedButton.styleFrom(
+                  primary: AppTheme.primary,
+                  onPrimary: AppTheme.secondary,
+                  textStyle: const TextStyle(fontSize: 22),
+                  fixedSize: const Size(250, 50)),
+              child: const Text('Finalizar Pedido'),
             ),
           ),
-        );
-      }).toList(),
+        ],
+      ),
     );
   }
 
-  Widget _buildSauceOptions() {
-    return GridView.count(
-      crossAxisCount: 3,
-      children: sauceOptions.keys.map((String key) {
-        bool isSelected = sauceOptions[key] ?? false;
-        return GestureDetector(
-          onTap: () {
-            int selectedCount = sauceOptions.values.where((b) => b).length;
-            setState(() {
-              // Si la salsa ya está seleccionada, la deselecciona.
-              if (isSelected) {
-                sauceOptions[key] = false;
-              } else {
-                // Si se están seleccionando menos de 2 salsas, permite esta selección.
-                if (selectedCount < 2) {
-                  sauceOptions[key] = true;
-                } else {
-                  // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada.
-                  final firstSelected = sauceOptions.keys.firstWhere(
-                    (k) => sauceOptions[k]!,
-                    orElse: () => '',
-                  );
-                  if (firstSelected.isNotEmpty) {
-                    sauceOptions[firstSelected] = false;
-                    sauceOptions[key] = true;
-                  }
-                }
-              }
-            });
-          },
-          child: Container(
-            decoration: BoxDecoration(
-              color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
-              border: Border.all(color: Colors.grey),
-            ),
-            child: Center(
-              child: Text(key,
-                  style: TextStyle(
-                      color: isSelected ? Colors.white : Colors.black)),
-            ),
-          ),
-        );
-      }).toList(),
-    );
+  void eliminarProductoDelCarrito(int index) {
+    setState(() {
+      carrito.removeAt(index);
+    });
   }
 
-  Widget _buildDressingOptions() {
-    return GridView.count(
-      crossAxisCount: 3,
-      children: dressingOptions.keys.map((String key) {
-        bool isSelected = dressingOptions[key] ?? false;
-        return GestureDetector(
-          onTap: () {
-            int selectedCount = dressingOptions.values.where((b) => b).length;
-            setState(() {
-              // Si la salsa ya está seleccionada, la deselecciona.
-              if (isSelected) {
-                dressingOptions[key] = false;
-              } else {
-                // Si se están seleccionando menos de 2 salsas, permite esta selección.
-                if (selectedCount < 2) {
-                  dressingOptions[key] = true;
-                } else {
-                  // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada.
-                  final firstSelected = dressingOptions.keys.firstWhere(
-                    (k) => dressingOptions[k]!,
-                    orElse: () => '',
-                  );
-                  if (firstSelected.isNotEmpty) {
-                    dressingOptions[firstSelected] = false;
-                    dressingOptions[key] = true;
-                  }
-                }
-              }
-            });
-          },
-          child: Container(
-            decoration: BoxDecoration(
-              color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
-              border: Border.all(color: Colors.grey),
-            ),
-            child: Center(
-              child: Text(key,
-                  style: TextStyle(
-                      color: isSelected ? Colors.white : Colors.black)),
+  Widget _buildProductsSection() {
+    // Asumiendo que tienes un método para obtener los productos según la categoría seleccionada
+    return Column(
+      children: [
+        _buildCategoryButtons(),
+        const SizedBox(height: 10),
+        Expanded(
+          child: GridView.builder(
+            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+              crossAxisCount: 3, // Número de columnas
+              childAspectRatio: 3 / 2, // Proporción de cada tarjeta
             ),
+            itemCount: productos.length,
+            itemBuilder: (context, index) {
+              final producto = productos[index];
+              return Card(
+                child: InkWell(
+                  onTap: () {
+                    setState(() {
+                      // Aquí manejas la adición de productos al carrito
+                      agregarAlCarrito(producto);
+                    });
+                  },
+                  child: Column(
+                    mainAxisAlignment: MainAxisAlignment.center,
+                    children: [
+                      const Icon(Icons.fastfood, size: 80),
+                      const SizedBox(height: 8),
+                      Padding(
+                          padding: EdgeInsets.fromLTRB(30, 0, 30, 0),
+                          child: Text(
+                            producto.nombre ?? '',
+                            style: const TextStyle(
+                                fontSize: 16, fontWeight: FontWeight.bold),
+                          )),
+                      const SizedBox(height: 8),
+                      Text(
+                        '\$${producto.precio}',
+                        style: const TextStyle(
+                            fontSize: 16,
+                            fontWeight: FontWeight.bold,
+                            color: Color(0xFF008000)),
+                      ),
+                    ],
+                  ),
+                ),
+              );
+            },
           ),
-        );
-      }).toList(),
+        ),
+      ],
     );
   }
 
-  Widget _buildToppingsOptions() {
-    return GridView.count(
-      crossAxisCount: 3,
-      children: toppingOptions.keys.map((String key) {
-        bool isSelected = toppingOptions[key] ?? false;
-        return GestureDetector(
-          onTap: () {
-            int selectedCount = toppingOptions.values.where((b) => b).length;
-            setState(() {
-              // Si la salsa ya está seleccionada, la deselecciona.
-              if (isSelected) {
-                toppingOptions[key] = false;
-              } else {
-                // Si se están seleccionando menos de 2 salsas, permite esta selección.
-                if (selectedCount < 2) {
-                  toppingOptions[key] = true;
-                } else {
-                  // Si ya hay 2 salsas seleccionadas, primero deselecciona la primera seleccionada.
-                  final firstSelected = toppingOptions.keys.firstWhere(
-                    (k) => toppingOptions[k]!,
-                    orElse: () => '',
-                  );
-                  if (firstSelected.isNotEmpty) {
-                    toppingOptions[firstSelected] = false;
-                    toppingOptions[key] = true;
-                  }
-                }
-              }
-            });
-          },
-          child: Container(
-            decoration: BoxDecoration(
-              color: isSelected ? AppTheme.primary : const Color(0xFFF4F4F4),
-              border: Border.all(color: Colors.grey),
-            ),
-            child: Center(
-              child: Text(key,
-                  style: TextStyle(
-                      color: isSelected ? Colors.white : Colors.black)),
+  Widget _buildCategoryButtons() {
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      children: [
+        SizedBox(
+          height: 50, // Altura para los botones
+          child: Scrollbar(
+            controller: horizontalScrollController,
+            thumbVisibility: true,
+            thickness: 5.0,
+            child: ListView.builder(
+              scrollDirection: Axis.horizontal,
+              itemCount: categorias.length,
+              controller: horizontalScrollController,
+              itemBuilder: (context, index) {
+                final categoria = categorias[index];
+                bool esSeleccionada = categoriaSeleccionada?.id == categoria.id;
+                return Padding(
+                  padding: const EdgeInsets.symmetric(horizontal: 4.0),
+                  child: ElevatedButton(
+                    onPressed: () => seleccionarCategoria(categoria),
+                    style: ElevatedButton.styleFrom(
+                      primary: esSeleccionada
+                          ? const Color(0xFFFF848F)
+                          : Colors.white,
+                      onPrimary: Colors.black,
+                      textStyle: const TextStyle(
+                        fontWeight: FontWeight.bold,
+                      ),
+                      shape: RoundedRectangleBorder(
+                        borderRadius: BorderRadius.circular(18.0),
+                        side: BorderSide(
+                            color: esSeleccionada ? Colors.black : Colors.grey),
+                      ),
+                    ),
+                    child: Text(categoria.nombre ?? 'Sin nombre'),
+                  ),
+                );
+              },
             ),
           ),
-        );
-      }).toList(),
-    );
-  }
-
-  Widget _buildConfirmButton() {
-    return ElevatedButton(
-      style: ElevatedButton.styleFrom(
-          primary: AppTheme.primary, // Color del botón
-          onPrimary: Colors.black,
-          textStyle: const TextStyle(fontSize: 22),
-          padding: const EdgeInsets.fromLTRB(80, 20, 80, 20)),
-      onPressed: () {
-        finalizeCustomization(); // Este método creará el pedido personalizado
-      },
-      child: Text('Confirmar Pedido'),
+        ),
+      ],
     );
   }
 }

+ 230 - 0
lib/views/toping/toping_form.dart

@@ -0,0 +1,230 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'package:camera/camera.dart';
+import 'package:file_picker/file_picker.dart';
+import 'package:flutter/material.dart';
+import 'package:yoshi_papas_app/views/home/home_screen.dart';
+import 'package:yoshi_papas_app/widgets/widgets.dart';
+import 'package:provider/provider.dart';
+
+import '../../models/models.dart';
+import '../../viewmodels/viewmodels.dart';
+
+class TopingForm extends StatefulWidget {
+  const TopingForm({Key? key}) : super(key: key);
+
+  @override
+  State<TopingForm> createState() => Formulario();
+}
+
+class Formulario extends State<TopingForm> {
+  final _clave = TextEditingController();
+  final _nombre = TextEditingController();
+  final _descripcion = TextEditingController();
+  final _costo = TextEditingController();
+  final _categoria = TextEditingController();
+  final _imagen = TextEditingController();
+
+  @override
+  void initState() {
+    super.initState();
+    Future(() async {
+      Provider.of<TopingViewModel>(context, listen: false).setIsLoading(true);
+      final mvm = Provider.of<TopingViewModel>(context, listen: false);
+      Toping? modelo = mvm.selectedToping;
+      if (modelo != null && modelo.id > 0) {
+        setState(() {
+          _clave.text = modelo.clave.toString();
+          _nombre.text = modelo.nombre.toString();
+          _descripcion.text = modelo.descripcion.toString();
+          _costo.text = modelo.costo.toString();
+          _categoria.text = modelo.idCategoria.toString();
+        });
+      }
+      Provider.of<TopingViewModel>(context, listen: false).setIsLoading(false);
+    });
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    _descripcion.dispose();
+    _nombre.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final mvm = Provider.of<TopingViewModel>(context);
+    final modelo = mvm.selectedToping;
+    final mediavm = Provider.of<MediaViewModel>(context);
+    if (mvm.isLoading) return const Cargando();
+    final Toping = mvm.selectedToping;
+
+    List<Widget> _registros = [];
+    if (modelo!.mediaToping.isNotEmpty) {
+      for (int x = 0; x <= modelo.mediaToping.length - 1; x++) {
+        _registros.add(itemMedia(context, x));
+      }
+    }
+    Size s = MediaQuery.of(context).size;
+    int axiscount = obtenerAxiscount(s);
+
+    return Scaffold(
+      appBar: encabezado(titulo: "Toping Categoria"),
+      body: SingleChildScrollView(
+        padding: const EdgeInsets.all(8),
+        child: Column(
+          children: [
+            tarjeta(
+              Padding(
+                padding: const EdgeInsets.all(8),
+                child: Column(
+                  children: [
+                    Row(
+                      children: [
+                        Expanded(
+                          child: AppTextField(
+                            maxLength: 100,
+                            etiqueta: 'Clave',
+                            controller: _clave,
+                            hintText: 'Clave Toping',
+                          ),
+                        ),
+                      ],
+                    ),
+                    Row(
+                      children: [
+                        Expanded(
+                          child: AppTextField(
+                            maxLength: 100,
+                            etiqueta: 'Nombre',
+                            controller: _nombre,
+                            hintText: 'Nombre Toping',
+                          ),
+                        ),
+                      ],
+                    ),
+                    Row(
+                      children: [
+                        Expanded(
+                          child: AppTextField(
+                            maxLength: 100,
+                            etiqueta: 'Categoria',
+                            controller: _categoria,
+                            hintText: 'Nombre Categoria',
+                          ),
+                        ),
+                      ],
+                    ),
+                    Row(
+                      children: [
+                        Expanded(
+                          child: AppTextField(
+                            maxLength: 100,
+                            etiqueta: 'Precio',
+                            controller: _costo,
+                            hintText: 'Precio Toping',
+                          ),
+                        ),
+                      ],
+                    ),
+                    Row(
+                      children: [
+                        Expanded(
+                          child: AppTextField(
+                            maxLength: 1000,
+                            maxLines: 3,
+                            etiqueta: 'Descripción',
+                            controller: _descripcion,
+                            hintText: 'Descripción de Toping',
+                          ),
+                        ),
+                      ],
+                    ),
+                  ],
+                ),
+              ),
+            ),
+            const SizedBox(height: 15),
+            tarjeta(Column(children: [
+              GridView.count(
+                key: const Key("contenedor-1"),
+                physics: const NeverScrollableScrollPhysics(),
+                shrinkWrap: true,
+                padding: const EdgeInsets.all(4),
+                crossAxisCount: axiscount,
+                crossAxisSpacing: 2,
+                mainAxisSpacing: 1,
+                children: [
+                  ..._registros,
+                  agregarMedia(accion: () async {
+                    Navigator.push(
+                      context,
+                      MaterialPageRoute(
+                          builder: (context) => const HomeScreen()),
+                    );
+                  }),
+                ],
+              ),
+              const SizedBox(height: 30),
+            ])),
+            boton("Guardar", () async {
+              Provider.of<TopingViewModel>(context, listen: false)
+                  .setIsLoading(true);
+              await mvm.guardarModelo(
+                modelo: Toping!,
+                clave: _clave.text,
+                nombre: _nombre.text,
+                descripcion: _descripcion.text,
+              );
+              Provider.of<TopingViewModel>(context, listen: false)
+                  .setIsLoading(false);
+              if (context.mounted) {
+                Navigator.pop(context);
+              }
+            }),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget itemMedia(BuildContext context, int index) {
+    MediaToping mediaTC = Provider.of<TopingViewModel>(context, listen: false)
+        .selectedToping!
+        .mediaToping[index];
+
+    // Asegúrate de que el objeto media no sea nulo
+    if (mediaTC.media == null || mediaTC.media!.ruta!.isEmpty) {
+      // Si es nulo, muestra un widget de placeholder
+      return Container(
+        alignment: Alignment.center,
+        padding: EdgeInsets.all(8.0),
+        child: Text('Imagen no disponible'),
+      );
+    }
+
+    // Utiliza el objeto media para mostrar la imagen
+    return Card(
+      clipBehavior: Clip.antiAlias,
+      child: Column(
+        children: [
+          Expanded(
+            child: Image.network(
+              mediaTC.media!.ruta!,
+              fit: BoxFit.cover,
+              errorBuilder: (context, error, stackTrace) {
+                // Muestra un mensaje de error o imagen de placeholder
+                return Center(child: Text('Error al cargar la imagen'));
+              },
+            ),
+          ),
+          Padding(
+            padding: EdgeInsets.all(8.0),
+            child: Text(mediaTC.media!.nombre ?? 'Sin nombre'),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 210 - 0
lib/views/toping/toping_screen.dart

@@ -0,0 +1,210 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import '../../models/models.dart';
+import '../../themes/themes.dart';
+import '../../viewmodels/viewmodels.dart';
+import '../../widgets/app_textfield.dart';
+import '../../widgets/pagination_buttons.dart';
+import '../../widgets/widgets_components.dart';
+import 'toping_form.dart';
+
+class TopingScreen extends StatefulWidget {
+  const TopingScreen({Key? key}) : super(key: key);
+
+  @override
+  State<TopingScreen> createState() => Formulario();
+}
+
+class Formulario extends State<TopingScreen> {
+  final _busqueda = TextEditingController(text: '');
+
+  @override
+  void initState() {
+    super.initState();
+    Future(() async {
+      await Provider.of<TopingViewModel>(context, listen: false)
+          .fetchRegistros();
+    });
+  }
+
+  go(Toping item) async {
+    Provider.of<TopingViewModel>(context, listen: false).selectModelo(item);
+    Navigator.push(
+      context,
+      MaterialPageRoute(
+        builder: (context) => const TopingForm(),
+      ),
+    ).then((value) async {
+      await Provider.of<TopingViewModel>(context, listen: false)
+          .fetchRegistros();
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final mvm = Provider.of<TopingViewModel>(context);
+    TextStyle estilo = const TextStyle(fontWeight: FontWeight.bold);
+    int vuelta = 0;
+    List<DataRow> registros = [];
+    if (mvm.registros.isNotEmpty) {
+      for (Toping item in mvm.registros) {
+        var _tipo = vuelta % 2;
+        vuelta++;
+        registros.add(DataRow(selected: _tipo > 0, cells: [
+          DataCell(
+            Text(item.clave.toString()),
+            onTap: () => go(item),
+          ),
+          DataCell(
+            Text(item.nombre.toString()),
+            onTap: () => go(item),
+          ),
+          DataCell(
+            Text(item.descripcion.toString()),
+            onTap: () => go(item),
+          ),
+          DataCell(
+              Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+            PopupMenuButton(
+              surfaceTintColor: AppTheme.primary,
+              itemBuilder: (context) => [
+                PopupMenuItem(
+                  child: const Text('Editar'),
+                  onTap: () => go(item),
+                ),
+                PopupMenuItem(
+                  child: const Text(
+                    'Eliminar',
+                  ),
+                  onTap: () async {
+                    return showDialog(
+                      context: context,
+                      builder: (context) {
+                        return AlertDialog(
+                          title: const Text("Eliminar registro"),
+                          content: const Text('¿Desea eliminar el registro?'),
+                          actions: [
+                            Row(children: [
+                              Expanded(
+                                  child: TextButton(
+                                onPressed: () {
+                                  Navigator.pop(context);
+                                },
+                                child: const Text('Cancelar'),
+                              )),
+                              Expanded(
+                                  child: TextButton(
+                                onPressed: () async {
+                                  Navigator.pop(context);
+                                },
+                                child: const Text('Continuar'),
+                              ))
+                            ])
+                          ],
+                        );
+                      },
+                    );
+                  },
+                )
+              ],
+              icon: const Icon(Icons.more_vert),
+              shape: RoundedRectangleBorder(
+                  borderRadius: BorderRadius.circular(15)),
+            )
+          ]))
+        ]));
+      }
+    }
+
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text('Toping Categorias'),
+      ),
+      floatingActionButton: FloatingActionButton(
+        onPressed: () {
+          mvm.selectModelo(Toping());
+          Navigator.push(
+            context,
+            MaterialPageRoute(
+              builder: (context) => const TopingForm(),
+            ),
+          ).then((value) async {
+            await Provider.of<TopingViewModel>(context, listen: false)
+                .fetchRegistros();
+          });
+        },
+        child: const Icon(Icons.add),
+      ),
+      body: Column(
+        children: [
+          Expanded(
+            child: ListView(
+              padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
+              children: [
+                const SizedBox(height: 8),
+                tarjeta(Padding(
+                    padding: const EdgeInsets.all(10),
+                    child: Row(
+                      children: [
+                        Expanded(
+                          flex: 8,
+                          child: AppTextField(
+                            prefixIcon: const Icon(Icons.search),
+                            maxLength: 100,
+                            etiqueta: 'Búsqueda por nombre...',
+                            controller: _busqueda,
+                            hintText: 'Búsqueda por nombre...',
+                          ),
+                        ),
+                        const SizedBox(width: 5),
+                        Expanded(
+                          flex: 2,
+                          child: botonElevated(
+                            accion: () async {
+                              _busqueda.text = _busqueda.text.trim();
+                              await Provider.of<TopingViewModel>(context,
+                                      listen: false)
+                                  .setIsLoading(true);
+                              await Provider.of<TopingViewModel>(context,
+                                      listen: false)
+                                  .setBusqueda(_busqueda.text);
+                              await Provider.of<TopingViewModel>(context,
+                                      listen: false)
+                                  .fetchRegistros();
+                              await Provider.of<TopingViewModel>(context,
+                                      listen: false)
+                                  .setBusqueda("");
+                              await Provider.of<TopingViewModel>(context,
+                                      listen: false)
+                                  .setIsLoading(false);
+                            },
+                          ),
+                        ),
+                      ],
+                    ))),
+                const SizedBox(height: 8),
+                tarjeta(DataTable(
+                    sortAscending: true,
+                    sortColumnIndex: 1,
+                    columns: [
+                      DataColumn(label: Text("CLAVE", style: estilo)),
+                      DataColumn(label: Text("NOMBRE", style: estilo)),
+                      DataColumn(label: Text("DESCRIPCIÓN", style: estilo)),
+                      DataColumn(label: Text("", style: estilo)),
+                    ],
+                    rows: registros)),
+                PaginationButtons(
+                  currentPage: mvm.pagina,
+                  totalPages: mvm.totalPaginas,
+                  onPageChanged: (i) => mvm.cambiarPagina(i),
+                )
+              ],
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 205 - 0
lib/views/toping_categoria/toping_categoria_form.dart

@@ -0,0 +1,205 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'package:camera/camera.dart';
+import 'package:file_picker/file_picker.dart';
+import 'package:flutter/material.dart';
+import 'package:yoshi_papas_app/views/home/home_screen.dart';
+import 'package:yoshi_papas_app/widgets/widgets.dart';
+import 'package:provider/provider.dart';
+
+import '../../models/models.dart';
+import '../../viewmodels/viewmodels.dart';
+
+class TopingCategoriaForm extends StatefulWidget {
+  const TopingCategoriaForm({Key? key}) : super(key: key);
+
+  @override
+  State<TopingCategoriaForm> createState() => Formulario();
+}
+
+class Formulario extends State<TopingCategoriaForm> {
+  final _clave = TextEditingController();
+  final _nombre = TextEditingController();
+  final _descripcion = TextEditingController();
+  final _imagen = TextEditingController();
+
+  @override
+  void initState() {
+    super.initState();
+    Future(() async {
+      Provider.of<TopingCategoriaViewModel>(context, listen: false)
+          .setIsLoading(true);
+      final mvm = Provider.of<TopingCategoriaViewModel>(context, listen: false);
+      TopingCategoria? modelo = mvm.selectedTopingCategoria;
+      if (modelo != null && modelo.id > 0) {
+        setState(() {
+          _clave.text = modelo.clave.toString();
+          _nombre.text = modelo.nombre.toString();
+          _descripcion.text = modelo.descripcion.toString();
+        });
+      }
+      Provider.of<TopingCategoriaViewModel>(context, listen: false)
+          .setIsLoading(false);
+    });
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    _descripcion.dispose();
+    _nombre.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final mvm = Provider.of<TopingCategoriaViewModel>(context);
+    final modelo = mvm.selectedTopingCategoria;
+    final mediavm = Provider.of<MediaViewModel>(context);
+    if (mvm.isLoading) return const Cargando();
+    final TopingCategoria = mvm.selectedTopingCategoria;
+
+    List<Widget> _registros = [];
+    if (modelo!.mediaTopingCategoria.isNotEmpty) {
+      for (int x = 0; x <= modelo.mediaTopingCategoria.length - 1; x++) {
+        _registros.add(itemMedia(context, x));
+      }
+    }
+    Size s = MediaQuery.of(context).size;
+    int axiscount = obtenerAxiscount(s);
+
+    return Scaffold(
+      appBar: encabezado(titulo: "Toping Categoria"),
+      body: SingleChildScrollView(
+        padding: const EdgeInsets.all(8),
+        child: Column(
+          children: [
+            tarjeta(
+              Padding(
+                padding: const EdgeInsets.all(8),
+                child: Column(
+                  children: [
+                    Row(
+                      children: [
+                        Expanded(
+                          child: AppTextField(
+                            maxLength: 100,
+                            etiqueta: 'Clave',
+                            controller: _clave,
+                            hintText: 'Clave Toping Categoria',
+                          ),
+                        ),
+                      ],
+                    ),
+                    Row(
+                      children: [
+                        Expanded(
+                          child: AppTextField(
+                            maxLength: 100,
+                            etiqueta: 'Nombre',
+                            controller: _nombre,
+                            hintText: 'Nombre Toping Categoria',
+                          ),
+                        ),
+                      ],
+                    ),
+                    Row(
+                      children: [
+                        Expanded(
+                          child: AppTextField(
+                            maxLength: 1000,
+                            maxLines: 3,
+                            etiqueta: 'Descripción',
+                            controller: _descripcion,
+                            hintText: 'Descripción de Toping Categoria',
+                          ),
+                        ),
+                      ],
+                    ),
+                  ],
+                ),
+              ),
+            ),
+            const SizedBox(height: 15),
+            tarjeta(Column(children: [
+              GridView.count(
+                key: const Key("contenedor-1"),
+                physics: const NeverScrollableScrollPhysics(),
+                shrinkWrap: true,
+                padding: const EdgeInsets.all(4),
+                crossAxisCount: axiscount,
+                crossAxisSpacing: 2,
+                mainAxisSpacing: 1,
+                children: [
+                  ..._registros,
+                  agregarMedia(accion: () async {
+                    Navigator.push(
+                      context,
+                      MaterialPageRoute(
+                          builder: (context) => const HomeScreen()),
+                    );
+                  }),
+                ],
+              ),
+              const SizedBox(height: 30),
+            ])),
+            boton("Guardar", () async {
+              Provider.of<TopingCategoriaViewModel>(context, listen: false)
+                  .setIsLoading(true);
+              await mvm.guardarModelo(
+                modelo: TopingCategoria!,
+                clave: _clave.text,
+                nombre: _nombre.text,
+                descripcion: _descripcion.text,
+              );
+              Provider.of<TopingCategoriaViewModel>(context, listen: false)
+                  .setIsLoading(false);
+              if (context.mounted) {
+                Navigator.pop(context);
+              }
+            }),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget itemMedia(BuildContext context, int index) {
+    MediaTopingCategoria mediaTC =
+        Provider.of<TopingCategoriaViewModel>(context, listen: false)
+            .selectedTopingCategoria!
+            .mediaTopingCategoria[index];
+
+    // Asegúrate de que el objeto media no sea nulo
+    if (mediaTC.media == null || mediaTC.media!.ruta!.isEmpty) {
+      // Si es nulo, muestra un widget de placeholder
+      return Container(
+        alignment: Alignment.center,
+        padding: EdgeInsets.all(8.0),
+        child: Text('Imagen no disponible'),
+      );
+    }
+
+    // Utiliza el objeto media para mostrar la imagen
+    return Card(
+      clipBehavior: Clip.antiAlias,
+      child: Column(
+        children: [
+          Expanded(
+            child: Image.network(
+              mediaTC.media!.ruta!,
+              fit: BoxFit.cover,
+              errorBuilder: (context, error, stackTrace) {
+                // Muestra un mensaje de error o imagen de placeholder
+                return Center(child: Text('Error al cargar la imagen'));
+              },
+            ),
+          ),
+          Padding(
+            padding: EdgeInsets.all(8.0),
+            child: Text(mediaTC.media!.nombre ?? 'Sin nombre'),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 216 - 0
lib/views/toping_categoria/toping_categoria_screen.dart

@@ -0,0 +1,216 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import '../../models/models.dart';
+import '../../themes/themes.dart';
+import '../../viewmodels/viewmodels.dart';
+import '../../widgets/app_textfield.dart';
+import '../../widgets/pagination_buttons.dart';
+import '../../widgets/widgets_components.dart';
+import 'toping_categoria_form.dart';
+
+class TopingCategoriaScreen extends StatefulWidget {
+  const TopingCategoriaScreen({Key? key}) : super(key: key);
+
+  @override
+  State<TopingCategoriaScreen> createState() => Formulario();
+}
+
+class Formulario extends State<TopingCategoriaScreen> {
+  final _busqueda = TextEditingController(text: '');
+
+  @override
+  void initState() {
+    super.initState();
+    Future(() async {
+      await Provider.of<TopingCategoriaViewModel>(context, listen: false)
+          .fetchRegistros();
+    });
+  }
+
+  go(TopingCategoria item) async {
+    Provider.of<TopingCategoriaViewModel>(context, listen: false)
+        .selectModelo(item);
+    Navigator.push(
+      context,
+      MaterialPageRoute(
+        builder: (context) => const TopingCategoriaForm(),
+      ),
+    ).then((value) async {
+      await Provider.of<TopingCategoriaViewModel>(context, listen: false)
+          .fetchRegistros();
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final mvm = Provider.of<TopingCategoriaViewModel>(context);
+    TextStyle estilo = const TextStyle(fontWeight: FontWeight.bold);
+    int vuelta = 0;
+    List<DataRow> registros = [];
+    if (mvm.registros.isNotEmpty) {
+      for (TopingCategoria item in mvm.registros) {
+        var _tipo = vuelta % 2;
+        vuelta++;
+        registros.add(DataRow(selected: _tipo > 0, cells: [
+          DataCell(
+            Text(item.clave.toString()),
+            onTap: () => go(item),
+          ),
+          DataCell(
+            Text(item.nombre.toString()),
+            onTap: () => go(item),
+          ),
+          DataCell(
+            Text(item.descripcion.toString()),
+            onTap: () => go(item),
+          ),
+          DataCell(
+              Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+            PopupMenuButton(
+              surfaceTintColor: AppTheme.primary,
+              itemBuilder: (context) => [
+                PopupMenuItem(
+                  child: const Text('Editar'),
+                  onTap: () => go(item),
+                ),
+                PopupMenuItem(
+                  child: const Text(
+                    'Eliminar',
+                  ),
+                  onTap: () async {
+                    return showDialog(
+                      context: context,
+                      builder: (context) {
+                        return AlertDialog(
+                          title: const Text("Eliminar registro"),
+                          content: const Text('¿Desea eliminar el registro?'),
+                          actions: [
+                            Row(children: [
+                              Expanded(
+                                  child: TextButton(
+                                onPressed: () {
+                                  Navigator.pop(context);
+                                },
+                                child: const Text('Cancelar'),
+                              )),
+                              Expanded(
+                                  child: TextButton(
+                                onPressed: () async {
+                                  Navigator.pop(context);
+                                },
+                                child: const Text('Continuar'),
+                              ))
+                            ])
+                          ],
+                        );
+                      },
+                    );
+                  },
+                )
+              ],
+              icon: const Icon(Icons.more_vert),
+              shape: RoundedRectangleBorder(
+                  borderRadius: BorderRadius.circular(15)),
+            )
+          ]))
+        ]));
+      }
+    }
+
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text('Toping Categorias'),
+      ),
+      floatingActionButton: FloatingActionButton(
+        onPressed: () {
+          mvm.selectModelo(TopingCategoria());
+          Navigator.push(
+            context,
+            MaterialPageRoute(
+              builder: (context) => const TopingCategoriaForm(),
+            ),
+          ).then((value) async {
+            await Provider.of<TopingCategoriaViewModel>(context, listen: false)
+                .fetchRegistros();
+          });
+        },
+        child: const Icon(Icons.add),
+      ),
+      body: Column(
+        children: [
+          Expanded(
+            child: ListView(
+              padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
+              children: [
+                const SizedBox(height: 8),
+                tarjeta(Padding(
+                    padding: const EdgeInsets.all(10),
+                    child: Row(
+                      children: [
+                        Expanded(
+                          flex: 8,
+                          child: AppTextField(
+                            prefixIcon: const Icon(Icons.search),
+                            maxLength: 100,
+                            etiqueta: 'Búsqueda por nombre...',
+                            controller: _busqueda,
+                            hintText: 'Búsqueda por nombre...',
+                          ),
+                        ),
+                        const SizedBox(width: 5),
+                        Expanded(
+                          flex: 2,
+                          child: botonElevated(
+                            accion: () async {
+                              _busqueda.text = _busqueda.text.trim();
+                              await Provider.of<TopingCategoriaViewModel>(
+                                      context,
+                                      listen: false)
+                                  .setIsLoading(true);
+                              await Provider.of<TopingCategoriaViewModel>(
+                                      context,
+                                      listen: false)
+                                  .setBusqueda(_busqueda.text);
+                              await Provider.of<TopingCategoriaViewModel>(
+                                      context,
+                                      listen: false)
+                                  .fetchRegistros();
+                              await Provider.of<TopingCategoriaViewModel>(
+                                      context,
+                                      listen: false)
+                                  .setBusqueda("");
+                              await Provider.of<TopingCategoriaViewModel>(
+                                      context,
+                                      listen: false)
+                                  .setIsLoading(false);
+                            },
+                          ),
+                        ),
+                      ],
+                    ))),
+                const SizedBox(height: 8),
+                tarjeta(DataTable(
+                    sortAscending: true,
+                    sortColumnIndex: 1,
+                    columns: [
+                      DataColumn(label: Text("CLAVE", style: estilo)),
+                      DataColumn(label: Text("NOMBRE", style: estilo)),
+                      DataColumn(label: Text("DESCRIPCIÓN", style: estilo)),
+                      DataColumn(label: Text("", style: estilo)),
+                    ],
+                    rows: registros)),
+                PaginationButtons(
+                  currentPage: mvm.pagina,
+                  totalPages: mvm.totalPaginas,
+                  onPageChanged: (i) => mvm.cambiarPagina(i),
+                )
+              ],
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 4 - 2
lib/widgets/app_drawer.dart

@@ -7,6 +7,8 @@ import 'package:yoshi_papas_app/views/home/home_screen.dart';
 import 'package:yoshi_papas_app/views/perfil/perfil_screen.dart';
 import 'package:yoshi_papas_app/views/pedido/pedido_screen.dart';
 import 'package:yoshi_papas_app/views/producto/producto_screen.dart';
+import 'package:yoshi_papas_app/views/toping/toping_screen.dart';
+import 'package:yoshi_papas_app/views/toping_categoria/toping_categoria_screen.dart';
 import '../models/usuario_model.dart';
 import 'package:provider/provider.dart';
 import '../themes/themes.dart';
@@ -170,7 +172,7 @@ class AppDrawer extends StatelessWidget {
                 Navigator.pop(context),
                 Navigator.of(context).push(
                   MaterialPageRoute(
-                    builder: (context) => const HomeScreen(),
+                    builder: (context) => const TopingScreen(),
                   ),
                 ),
               },
@@ -182,7 +184,7 @@ class AppDrawer extends StatelessWidget {
                 Navigator.pop(context),
                 Navigator.of(context).push(
                   MaterialPageRoute(
-                    builder: (context) => const HomeScreen(),
+                    builder: (context) => const TopingCategoriaScreen(),
                   ),
                 ),
               },