3 次代码提交 8251b26aa0 ... fd8f487ac1

作者 SHA1 备注 提交日期
  OscarGil03 fd8f487ac1 Estructura abtstract factory y Cierre de sesion 2 月之前
  OscarGil03 e334a3e9ff Merge branch 'master' into OGdev 2 月之前
  OscarGil03 bc279f38eb Cerrar sesión 2 月之前

二进制
assets/Turquessa.png


+ 36 - 0
lib/core/models/mesa_model.dart

@@ -1,12 +1,20 @@
 import '../models/basico_model.dart';
 import '../services/services.dart';
 
+enum EstadoPedido {
+  disponible,
+  surtida,
+  preparacion,
+  cobrado,
+}
+
 class Mesa extends Basico {
   int? idSucursal;
   String? nombre;
   String? clave;
   String? posicion;
   bool? activa;
+  EstadoPedido? estado;
 
   Mesa(
       {super.id,
@@ -16,6 +24,7 @@ class Mesa extends Basico {
       this.clave,
       this.posicion,
       this.activa,
+      this.estado = EstadoPedido.disponible,
       super.creado,
       super.modificado,
       super.eliminado});
@@ -35,6 +44,33 @@ class Mesa extends Basico {
     }..addAll(super.toJson());
   }
 
+  Map<String, dynamic> toMap() {
+    return {
+      'id': id,
+      'idSucursal': idSucursal ?? 0,
+      'nombre': nombre ?? '',
+      'clave': clave ?? '',
+      'posicion': posicion ?? '',
+      'activa': (activa == true) ? 1 : 0,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  static Mesa fromMap(Map<String, dynamic> map) {
+    // Ejemplo de parseo, usando tu Basico.parseInt, etc. si gustas:
+    final mesa = Mesa(
+      id: map['id'] as int,
+      idSucursal: map['idSucursal'] as int?,
+      nombre: map['nombre'] as String?,
+      clave: map['clave'] as String?,
+      posicion: map['posicion'] as String?,
+      activa: (map['activa'] == 1),
+    );
+    return mesa;
+  }
+
   Mesa.fromJson(Map<String, dynamic> json) {
     super.parseJson(json);
     idSucursal = Basico.parseInt(json['idSucursal']);

+ 31 - 31
lib/core/models/mesas_model.dart

@@ -1,6 +1,6 @@
-import 'package:flutter/material.dart';
-import 'package:turquessa_mesas_hoster/core/models/basico_model.dart';
-import 'package:turquessa_mesas_hoster/core/models/mesa_model.dart';
+// import 'package:flutter/material.dart';
+// import 'package:turquessa_mesas_hoster/core/models/basico_model.dart';
+// import 'package:turquessa_mesas_hoster/core/models/mesa_model.dart';
 
 // enum EstadoPedido {
 //   disponible,
@@ -9,35 +9,35 @@ import 'package:turquessa_mesas_hoster/core/models/mesa_model.dart';
 //   cobrado,
 // }
 
-// class Mesas extends Basico {
-//   String? nombre;
-//   EstadoPedido? tipo;
-//   String? folio;
+// // class Mesas extends Basico {
+// //   String? nombre;
+// //   EstadoPedido? tipo;
+// //   String? folio;
 
-//   Mesas({
-//     this.nombre,
-//     this.tipo,
-//     this.folio,
-//   });
+// //   Mesas({
+// //     this.nombre,
+// //     this.tipo,
+// //     this.folio,
+// //   });
 
-//   @override
-//   Map<String, dynamic> toJson() {
-//     return {
-//       'nombre': nombre ?? '',
-//       'tipo': tipo.toString(),
-//       'folio': folio ?? '',
-//     }..addAll(super.toJson());
-//   }
-// }
-class TableItem extends Basico {
-  final int id;
-  final String name;
+// //   @override
+// //   Map<String, dynamic> toJson() {
+// //     return {
+// //       'nombre': nombre ?? '',
+// //       'tipo': tipo.toString(),
+// //       'folio': folio ?? '',
+// //     }..addAll(super.toJson());
+// //   }
+// // }
 
-  final String status;
+// class TableItem extends Basico {
+//   final int id;
+//   final String name;
+//   final EstadoPedido status;
 
-  TableItem({
-    required this.id,
-    required this.name,
-    required this.status,
-  });
-}
+//   TableItem({
+//     required this.id,
+//     required this.name,
+//     required this.status,
+//   });
+// }

+ 32 - 0
lib/core/services/reposit_factory.dart

@@ -0,0 +1,32 @@
+import 'package:sqflite/sqflite.dart';
+
+import 'repository.dart';
+import 'sqlite_repository.dart';
+
+abstract class RepositoryFactory {
+  Repository<T> getRepository<T>({
+    required String tableName,
+    required T Function(Map<String, dynamic>) fromMap,
+    required Map<String, dynamic> Function(T) toMap,
+  });
+}
+
+class SQLiteRepositoryFactory implements RepositoryFactory {
+  final Database database;
+
+  SQLiteRepositoryFactory(this.database);
+
+  @override
+  Repository<T> getRepository<T>({
+    required String tableName,
+    required T Function(Map<String, dynamic>) fromMap,
+    required Map<String, dynamic> Function(T) toMap,
+  }) {
+    return SQLiteRepository<T>(
+      database: database,
+      tableName: tableName,
+      fromMap: fromMap,
+      toMap: toMap,
+    );
+  }
+}

+ 12 - 0
lib/core/services/repository.dart

@@ -0,0 +1,12 @@
+abstract class Repository<T> {
+  Future<T?> consultarPorId(int id);
+  Future<List<T>> consultarPorCondicion(bool Function(T) condicion);
+  Future<void> guardar(T entidad);
+  Future<void> eliminar(int id);
+  Future<List<T>> consultarConOrden(
+      {String? orderBy,
+      int? limit,
+      int? offset,
+      String? where,
+      List<Object?>? whereArgs});
+}

+ 3 - 0
lib/core/services/services.dart

@@ -4,3 +4,6 @@ export 'profile_service.dart';
 export 'database_service.dart';
 export 'api_response.dart';
 export 'session_storage.dart';
+export 'reposit_factory.dart';
+export 'repository.dart';
+export 'sqlite_repository.dart';

+ 67 - 0
lib/core/services/sqlite_repository.dart

@@ -0,0 +1,67 @@
+import 'repository.dart';
+import 'package:sqflite/sqflite.dart';
+
+class SQLiteRepository<T> implements Repository<T> {
+  final Database database;
+  final String tableName;
+  final T Function(Map<String, dynamic>) fromMap;
+  final Map<String, dynamic> Function(T) toMap;
+
+  SQLiteRepository({
+    required this.database,
+    required this.tableName,
+    required this.fromMap,
+    required this.toMap,
+  });
+
+  @override
+  Future<T?> consultarPorId(int id) async {
+    final maps = await database.query(
+      tableName,
+      where: 'id = ?',
+      whereArgs: [id],
+    );
+
+    if (maps.isNotEmpty) {
+      return fromMap(maps.first);
+    }
+    return null;
+  }
+
+  @override
+  Future<List<T>> consultarPorCondicion(bool Function(T) condicion) async {
+    final maps = await database.query(tableName);
+    return maps.map((map) => fromMap(map)).where(condicion).toList();
+  }
+
+  @override
+  Future<void> guardar(T entidad) async {
+    final map = toMap(entidad);
+    await database.insert(tableName, map,
+        conflictAlgorithm: ConflictAlgorithm.replace);
+  }
+
+  @override
+  Future<void> eliminar(int id) async {
+    await database.delete(tableName, where: 'id = ?', whereArgs: [id]);
+  }
+
+  @override
+  Future<List<T>> consultarConOrden(
+      {String? orderBy,
+      int? limit,
+      int? offset,
+      String? where,
+      List<Object?>? whereArgs}) async {
+    final maps = await database.query(
+      tableName,
+      orderBy: orderBy,
+      limit: limit,
+      offset: offset,
+      where: where,
+      whereArgs: whereArgs,
+    );
+
+    return maps.map(fromMap).toList();
+  }
+}

+ 15 - 1
lib/main.dart

@@ -4,6 +4,8 @@ import 'package:provider/provider.dart';
 import 'package:sqflite_common_ffi/sqflite_ffi.dart';
 import 'dart:io';
 
+import 'core/models/models.dart';
+import 'core/services/services.dart';
 import 'mvvm/views/home/home_screen.dart';
 import 'mvvm/views/login/login_screen.dart';
 import '/utils/themes.dart';
@@ -20,6 +22,17 @@ void main() async {
     databaseFactory = databaseFactoryFfi;
   }
 
+  Database? db = await DatabaseService().db;
+
+  final repositoryFactory = SQLiteRepositoryFactory(db!);
+
+  // 3) Obtener el repositorio de Mesa
+  final mesaRepository = repositoryFactory.getRepository<Mesa>(
+    tableName: 'Mesa',
+    fromMap: Mesa.fromMap,
+    toMap: (m) => m.toMap(),
+  );
+
   SystemChrome.setPreferredOrientations([
     DeviceOrientation.landscapeRight,
     DeviceOrientation.landscapeLeft,
@@ -30,7 +43,8 @@ void main() async {
       ChangeNotifierProvider(create: (_) => PermisoViewModel()),
       ChangeNotifierProvider(create: (_) => UsuarioViewModel()),
       ChangeNotifierProvider(create: (_) => ProductoViewModel()),
-      ChangeNotifierProvider(create: (_) => MesaViewModel()),
+      ChangeNotifierProvider(
+          create: (_) => MesaViewModel(mesaRepository: mesaRepository)),
       ChangeNotifierProvider(create: (_) => CategoriaProductoViewModel()),
       // Agrega aquí cualquier otro provider que necesites
     ], child: const MyApp()));

+ 0 - 11
lib/mvvm/viewmodels/home_view_model.dart

@@ -1,11 +0,0 @@
-import 'package:flutter/material.dart';
-
-class HomeViewModel extends ChangeNotifier {
-  int _selectedIndex = 0;
-  int get selectedIndex => _selectedIndex;
-
-  void setIndex(int index) {
-    _selectedIndex = index;
-    notifyListeners();
-  }
-}

+ 31 - 0
lib/mvvm/viewmodels/login_view_model.dart

@@ -1,4 +1,5 @@
 import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
 import '../../core/services/session_storage.dart';
 import '../../core/models/models.dart';
 import '../../core/services/services.dart';
@@ -93,4 +94,34 @@ class LoginViewModel extends ChangeNotifier {
     _obscureText = !_obscureText;
     notifyListeners();
   }
+
+  Future<void> showExitConfirmationDialog(context) async {
+    final bool? confirm = await showDialog(
+      context: context,
+      builder: (ctx) {
+        return AlertDialog(
+          title: const Text('¿Cerrar sesión?'),
+          content: const Text('¿Estás seguro de que quieres cerrar la sesión?'),
+          actions: [
+            TextButton(
+              onPressed: () => Navigator.of(ctx).pop(false),
+              child:
+                  const Text('Cancelar', style: TextStyle(color: Colors.red)),
+            ),
+            TextButton(
+              onPressed: () async {
+                Provider.of<LoginViewModel>(context, listen: false).logOut();
+                Navigator.of(ctx).pop(true);
+              },
+              child: const Text('Aceptar'),
+            ),
+          ],
+        );
+      },
+    );
+
+    if (confirm == true) {
+      Navigator.of(context).pushNamedAndRemoveUntil('login', (route) => false);
+    }
+  }
 }

+ 45 - 39
lib/mvvm/viewmodels/mesa_view_model.dart

@@ -1,9 +1,14 @@
 import 'package:flutter/material.dart';
 import 'package:sqflite/sqflite.dart';
+import '../../core/services/reposit_factory.dart';
 import '../../core/services/services.dart';
 import '../../core/models/models.dart';
 
 class MesaViewModel extends ChangeNotifier {
+  final Repository<Mesa> mesaRepository;
+
+  MesaViewModel({required this.mesaRepository});
+
   String _busqueda = "";
   String get busqueda => _busqueda;
 
@@ -33,33 +38,15 @@ class MesaViewModel extends ChangeNotifier {
     notifyListeners();
   }
 
-  Future<void> fetchLocalAll({
-    int page = 1,
-    bool sinLimite = false,
-    String orderBy = 'id ASC',
-  }) async {
-    _currentPage = page;
-    var db = await DatabaseService().db;
-
-    if (!sinLimite) {
-      int? count = Sqflite.firstIntValue(
-          await db!.rawQuery('SELECT COUNT(*) FROM Mesa'));
-      _totalMesas = count ?? 0;
-    }
-
-    String? limitOffsetClause;
-    if (!sinLimite) {
-      int offset = (_limit * (page - 1));
-      limitOffsetClause = 'LIMIT $_limit OFFSET $offset';
-    } else {
-      limitOffsetClause = '';
-    }
-
-    var query = await db!
-        .rawQuery('SELECT * FROM Mesa ORDER BY $orderBy $limitOffsetClause');
+  Future<void> fetchLocal() async {
+    _isLoading = true;
+    notifyListeners();
 
-    _mesas = query.map((element) => Mesa.fromJson(element)).toList();
+    final resultado =
+        await mesaRepository.consultarConOrden(orderBy: 'nombre ASC');
 
+    _mesas = resultado;
+    _isLoading = false;
     notifyListeners();
   }
 
@@ -88,7 +75,7 @@ class MesaViewModel extends ChangeNotifier {
   Future<void> addMesa(Mesa mesa) async {
     mesa.creado = DateTime.now().toUtc();
     await DatabaseService().guardar(mesa);
-    await fetchLocalAll();
+    await fetchLocal();
   }
 
   Future<void> updateMesa(Mesa mesa) async {
@@ -96,7 +83,7 @@ class MesaViewModel extends ChangeNotifier {
     try {
       mesa.modificado = DateTime.now().toUtc();
       await DatabaseService().guardar(mesa);
-      await fetchLocalAll();
+      await fetchLocal();
     } catch (e) {
       debugPrint('Error updating mesa: $e');
     }
@@ -105,7 +92,7 @@ class MesaViewModel extends ChangeNotifier {
 
   Future<void> deleteMesa(int id) async {
     await DatabaseService().eliminar<Mesa>(id);
-    fetchLocalAll();
+    fetchLocal();
   }
 
   void setIsLoading(bool loading) {
@@ -113,17 +100,17 @@ class MesaViewModel extends ChangeNotifier {
     notifyListeners();
   }
 
-  void nextPage() {
-    if (_currentPage < totalPages) {
-      fetchLocalAll(page: _currentPage + 1);
-    }
-  }
+  // void nextPage() {
+  //   if (_currentPage < totalPages) {
+  //     fetchLocal(page: _currentPage + 1);
+  //   }
+  // }
 
-  void previousPage() {
-    if (_currentPage > 1) {
-      fetchLocalAll(page: _currentPage - 1);
-    }
-  }
+  // void previousPage() {
+  //   if (_currentPage > 1) {
+  //     fetchLocal(page: _currentPage - 1);
+  //   }
+  // }
 
   Future<bool> isMesaActive(String clave) async {
     var db = await DatabaseService().db;
@@ -140,6 +127,25 @@ class MesaViewModel extends ChangeNotifier {
 
     return false;
   }
+  // TODO: Implementar CambiarEstadoMesa, actualizar modelo
+  // void CambiarEstadoPedidoMesa(Mesa mesa) async {
+  //   var db = await DatabaseService().db;
+  //   await db!.update(
+  //     'Mesa',
+  //     {'estado': mesa.estado.toString()},
+  //     where: 'id = ?',
+  //     whereArgs: [mesa.id],
+  //   );
+  //   fetchLocal();
+  // }
+
+  //todo: Cambio de estado de mesa provisional
+  void CambiarEstadoPedidoMesa(EstadoPedido nuevoestado) {
+    if (_selectedMesa != null) {
+      _selectedMesa!.estado = nuevoestado;
+      notifyListeners();
+    }
+  }
 
   Future<bool> sincronizarMesas() async {
     String? claveSucursal =
@@ -150,7 +156,7 @@ class MesaViewModel extends ChangeNotifier {
         "claveSucursal": claveSucursal!,
         "limite": "-1"
       };
-
+      //? peticion de Mesa
       final response = ApiResponse(await BaseService()
           .get('/pos/mesa', queryParameters: parametros, withAuth: true));
 

+ 83 - 0
lib/mvvm/viewmodels/pedido_view_model.dart

@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+import 'package:turquessa_mesas_hoster/core/services/database_service.dart';
+
+import '../../core/models/pedido_model.dart';
+import '../../core/services/api_response.dart';
+import '../../core/services/base_service.dart';
+
+class PedidoViewModel extends ChangeNotifier {
+  List<Pedido> _pedidos = [];
+  bool _isLoading = false;
+
+  List<Pedido> get pedidos => _pedidos;
+  bool get isLoading => _isLoading;
+
+  Future<void> fetchLocalPedidos() async {
+    var db = await DatabaseService().db;
+    var query = await db!
+        .query('Pedido', where: 'eliminado IS NULL', orderBy: 'id asc');
+    _pedidos = query.map((element) => Pedido.fromJson(element)).toList();
+    notifyListeners();
+  }
+
+  // Future<void> setSelectedPedido(Pedido pedido) async {
+  //   var db = await DatabaseService().db;
+  //   await db!.update(
+  //     'Pedido',
+  //     {'seleccionado': 0},
+  //   );
+  //   pedido.seleccionado = 1;
+  //   await DatabaseService().guardar(pedido);
+
+  //   await fetchLocalPedidos();
+  // }
+
+  Future<bool> sincronizarPedidos() async {
+    try {
+      final response =
+          ApiResponse(await BaseService().get('/pos/mesa?expand=pedidos'));
+      if (response.isOk && response.resultados != null) {
+        List<Pedido> pedidosApi =
+            response.resultados!.map((json) => Pedido.fromJson(json)).toList();
+
+        // if (pedidosApi.isNotEmpty) {
+        //   await DatabaseService().sincronizarPedidos(pedidosApi);
+        //   await fetchLocalPedidos();
+        //   notifyListeners();
+        //   return true;
+        // }
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar pedidos: $e');
+      return false;
+    }
+  }
+
+  // // Future<void> sincronizarPedidosDesdeApi() async {
+  //   setIsLoading(true);
+  //   try {
+  //     final response = ApiResponse(await BaseService().get('/pos/pedido'));
+
+  //     if (response.isOk && response.resultados != null) {
+  //       List<Pedido> pedidosApi =
+  //           response.resultados!.map((json) => Pedido.fromApi(json)).toList();
+
+  //       if (pedidosApi.isNotEmpty) {
+  //         await DatabaseService().sincronizarPedidos(pedidosApi);
+  //         await fetchLocalPedidos();
+  //         notifyListeners();
+  //       }
+  //     }
+  //   } catch (e) {
+  //     print('Error al sincronizar pedidos: $e');
+  //   } finally {
+  //     setIsLoading(false);
+  //   }
+  // }
+
+  void setIsLoading(bool value) {
+    _isLoading = value;
+    notifyListeners();
+  }
+}

+ 0 - 1
lib/mvvm/viewmodels/viewmodels.dart

@@ -1,7 +1,6 @@
 export '../viewmodels/login_view_model.dart';
 export '../viewmodels/sucursal_view_model.dart';
 export '../viewmodels/permiso_view_model.dart';
-export '../viewmodels/home_view_model.dart';
 export '../viewmodels/usuarios_view_model.dart';
 export '../viewmodels/mesa_view_model.dart';
 export '../viewmodels/producto_view_model.dart';

+ 211 - 107
lib/mvvm/views/home/home_screen.dart

@@ -1,8 +1,10 @@
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
+import 'package:turquessa_mesas_hoster/core/models/mesa_model.dart';
 import 'package:turquessa_mesas_hoster/utils/widgets/custom_appbar.dart';
-import 'package:turquessa_mesas_hoster/core/models/mesas_model.dart';
+import 'package:turquessa_mesas_hoster/utils/widgets/navigation_rail.dart';
 
+import '../../../utils/widgets/ordenes_card.dart';
 import '../../viewmodels/viewmodels.dart';
 
 class HomeScreen extends StatefulWidget {
@@ -13,6 +15,7 @@ class HomeScreen extends StatefulWidget {
 }
 
 class Formulario extends State<HomeScreen> {
+  int _selectedIndex = 0;
   @override
   void initState() {
     super.initState();
@@ -23,13 +26,14 @@ class Formulario extends State<HomeScreen> {
           .sincronizarProductosYCategorias();
 
       await mesaViewModel.sincronizarMesas();
-      await mesaViewModel.fetchLocalAll(sinLimite: true, orderBy: 'nombre ASC');
+      await mesaViewModel.fetchLocal();
     });
   }
 
   @override
   Widget build(BuildContext context) {
     final mesaViewModel = Provider.of<MesaViewModel>(context);
+    final loginViewModel = Provider.of<LoginViewModel>(context);
     var _selectedIndex;
     return Scaffold(
       backgroundColor: Colors.grey.shade200,
@@ -38,36 +42,7 @@ class Formulario extends State<HomeScreen> {
       ),
       body: Row(
         children: [
-          NavigationRail(
-            backgroundColor: Color.fromARGB(255, 25, 30, 41),
-            selectedIndex: _selectedIndex,
-            onDestinationSelected: (int index) {
-              setState(() {
-                _selectedIndex = index;
-              });
-            },
-            labelType: NavigationRailLabelType.all,
-            destinations: const [
-              NavigationRailDestination(
-                icon: Icon(Icons.home, color: Colors.white),
-                selectedIcon: Icon(Icons.home_filled),
-                label: Text('Inicio'),
-              ),
-              NavigationRailDestination(
-                icon: Icon(Icons.search),
-                selectedIcon: Icon(
-                  Icons.search_rounded,
-                  color: Colors.white,
-                ),
-                label: Text('Buscar'),
-              ),
-              NavigationRailDestination(
-                icon: Icon(Icons.settings),
-                selectedIcon: Icon(Icons.settings_rounded, color: Colors.white),
-                label: Text('Ajustes'),
-              ),
-            ],
-          ),
+          CustomNavigationRail(selectedIndex: _selectedIndex),
           Expanded(
             child: Center(
               child: GridView.builder(
@@ -83,111 +58,240 @@ class Formulario extends State<HomeScreen> {
                   final mesa = mesaViewModel.mesas[index];
                   return GestureDetector(
                     onTap: () {
-                      mesaViewModel.selectMesa(mesa);
+                      setState(() {
+                        mesaViewModel.selectMesa(mesa);
+                      });
                     },
                     child: TableCard(
-                      icon: Icons.table_chart,
-                      //TODO: Agregar campo de estatus de la mesa para definir los colores
-                      color: (mesa.activa == true) ? Colors.blue : Colors.grey,
-                      title: mesa.nombre ?? 'Mesa sin nombre',
+                      mesa: mesa,
                     ),
                   );
                 },
               ),
             ),
           ),
-          //if (mesaViewModel.selectedMesa != null)
-          Expanded(
-              flex: 1,
-              child: Container(
-                margin: const EdgeInsets.all(10),
-                decoration: BoxDecoration(
-                  color: Colors.white,
-                  borderRadius: BorderRadius.circular(10),
-                  boxShadow: [
-                    BoxShadow(
-                      color: Colors.grey.withOpacity(0.2),
-                      blurRadius: 5,
-                      spreadRadius: 1,
-                    )
-                  ],
-                ),
-                // child: TablaDetalles(table: selectedTable!),
-              )),
+          if (mesaViewModel.selectedMesa != null)
+            Expanded(
+                child: Container(
+              margin: const EdgeInsets.all(10),
+              decoration: BoxDecoration(
+                color: Colors.white,
+                borderRadius: BorderRadius.circular(10),
+                boxShadow: [
+                  BoxShadow(
+                    color: Colors.grey.withOpacity(0.2),
+                    blurRadius: 5,
+                    spreadRadius: 1,
+                  )
+                ],
+              ),
+              child: TablaDetalles(
+                  status: EstadoPedido.disponible,
+                  table: mesaViewModel.selectedMesa ??
+                      Mesa(
+                          activa: false,
+                          id: 0,
+                          nombre: 'Mesa sin nombre',
+                          estado: EstadoPedido.disponible)),
+            )),
         ],
       ),
     );
   }
 }
 
-class TableCard extends StatelessWidget {
-  final IconData icon;
-  final Color color;
-  final String title;
-  const TableCard(
-      {super.key,
-      required this.icon,
-      required this.color,
-      required this.title});
+class TablaDetalles extends StatelessWidget {
+  final Mesa table;
+  final EstadoPedido status;
+
+  const TablaDetalles({
+    Key? key,
+    required this.table,
+    required this.status,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return Card(
-      color: color,
+    return Container(
+      margin: const EdgeInsets.all(10),
+      decoration: BoxDecoration(
+        color: Colors.white,
+        borderRadius: BorderRadius.circular(10),
+        boxShadow: [
+          BoxShadow(
+            color: Colors.grey.withOpacity(0.2),
+            blurRadius: 5,
+            spreadRadius: 1,
+          )
+        ],
+      ),
       child: Column(
-        mainAxisAlignment: MainAxisAlignment.center,
         children: [
-          Icon(
-            icon,
-            size: 50,
-            color: Colors.white,
+          // Header
+          Container(
+            padding: const EdgeInsets.all(16),
+            decoration: BoxDecoration(
+              color: table.activa! ? Colors.blue : Colors.grey,
+              borderRadius:
+                  const BorderRadius.vertical(top: Radius.circular(10)),
+            ),
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: [
+                Row(
+                  children: [
+                    const Icon(Icons.table_restaurant,
+                        color: Colors.white, size: 24),
+                    const SizedBox(width: 8),
+                    Text(
+                      table.nombre ?? 'Mesa sin nombre',
+                      style: const TextStyle(
+                        color: Colors.white,
+                        fontSize: 20,
+                        fontWeight: FontWeight.bold,
+                      ),
+                    ),
+                  ],
+                ),
+                Container(
+                  padding:
+                      const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
+                  decoration: BoxDecoration(
+                    color: Colors.white.withOpacity(0.2),
+                    borderRadius: BorderRadius.circular(20),
+                  ),
+                  child: Text(
+                    table.activa! ? 'Activa' : 'Inactiva',
+                    style: const TextStyle(
+                      color: Colors.white,
+                      fontWeight: FontWeight.bold,
+                    ),
+                  ),
+                ),
+              ],
+            ),
+          ),
+          // Contenido scrolleable
+          Expanded(
+            child: SingleChildScrollView(
+              child: Padding(
+                padding: const EdgeInsets.all(16),
+                child: Column(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  children: [
+                    Text(
+                      'ID: ${table.id}',
+                      style: const TextStyle(fontSize: 16),
+                    ),
+                    const SizedBox(height: 16),
+                    Row(
+                      children: [
+                        const Text('Estado: '),
+                        Text(
+                          status.toString().split('.').last,
+                          style: const TextStyle(
+                            fontWeight: FontWeight.bold,
+                            color: Colors.blue,
+                          ),
+                        ),
+                      ],
+                    ),
+                    const SizedBox(height: 16),
+                    // IconDataByStatus(status: status),
+                    const SizedBox(height: 16),
+                    OrdenesScreen(),
+                  ],
+                ),
+              ),
+            ),
           ),
-          Text(
-            title,
-            style: const TextStyle(color: Colors.white, fontSize: 20),
-          )
         ],
       ),
     );
   }
 }
 
-class TableDetailsPanel extends StatelessWidget {
-  final TableItem table;
-
-  const TableDetailsPanel({
-    Key? key,
-    required this.table,
-  }) : super(key: key);
+class TableCard extends StatelessWidget {
+  final Mesa mesa;
+  const TableCard({
+    super.key,
+    required this.mesa,
+  });
 
   @override
   Widget build(BuildContext context) {
-    return Column(
-      crossAxisAlignment: CrossAxisAlignment.start,
-      children: [
-        // Encabezado del panel
-        Container(
-          padding: const EdgeInsets.all(16),
-          decoration: BoxDecoration(
-            borderRadius: const BorderRadius.vertical(top: Radius.circular(10)),
-          ),
-          child: Row(
-            children: [
-              Icon(Icons.person, color: Colors.white, size: 24),
-              const SizedBox(width: 8),
-              Text(
-                table.name,
-                style: const TextStyle(
-                  color: Colors.white,
-                  fontSize: 20,
-                  fontWeight: FontWeight.bold,
-                ),
-              ),
-            ],
+    final status = mesa.estado ?? EstadoPedido.disponible;
+
+    Color backgroundColor;
+    Color iconColor;
+    IconData icon;
+    Color cardColor;
+
+    switch (status) {
+      case EstadoPedido.disponible:
+        backgroundColor = const Color.fromARGB(255, 220, 252, 232);
+        iconColor = Colors.green;
+        icon = Icons.table_restaurant_rounded;
+        cardColor = const Color.fromARGB(255, 220, 252, 232);
+        break;
+      case EstadoPedido.surtida:
+        backgroundColor = const Color.fromARGB(255, 220, 234, 254);
+        iconColor = Colors.blue;
+        icon = Icons.coffee_rounded;
+        cardColor = const Color.fromARGB(255, 220, 234, 254);
+        break;
+      case EstadoPedido.preparacion:
+        backgroundColor = const Color.fromARGB(255, 243, 232, 255);
+        iconColor = Colors.deepPurple;
+        icon = Icons.kitchen_rounded;
+        cardColor = const Color.fromARGB(255, 243, 232, 255);
+        break;
+      case EstadoPedido.cobrado:
+        backgroundColor = const Color.fromARGB(255, 255, 238, 213);
+        iconColor = Colors.amber;
+        icon = Icons.attach_money_rounded;
+        cardColor = const Color.fromARGB(255, 255, 238, 213);
+        break;
+      default:
+        backgroundColor = Colors.grey.shade200;
+        iconColor = Colors.grey;
+        icon = Icons.settings;
+        cardColor = Colors.white;
+        break;
+    }
+
+    return Card(
+      color: cardColor,
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: [
+          IconButton(
+            onPressed: () {
+              if (status == EstadoPedido.disponible) {
+                final mesaViewModel =
+                    Provider.of<MesaViewModel>(context, listen: false);
+                mesaViewModel.CambiarEstadoPedidoMesa(EstadoPedido.preparacion);
+              }
+            },
+            iconSize: 48,
+            style: ButtonStyle(
+              backgroundColor: MaterialStateProperty.all(backgroundColor),
+            ),
+            icon: Icon(icon, color: iconColor),
           ),
-        ),
-        // Contenido específico según el tipo
-      ],
+          const SizedBox(height: 8),
+          Text(
+            mesa.nombre ?? 'Mesa sin nombre',
+            style: TextStyle(
+              color: status == EstadoPedido.disponible
+                  ? Colors.black
+                  : Colors.black87,
+              fontSize: 20,
+              fontWeight: FontWeight.w500,
+            ),
+          )
+        ],
+      ),
     );
   }
 }

+ 62 - 19
lib/utils/widgets/custom_appbar.dart

@@ -1,4 +1,8 @@
 import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:turquessa_mesas_hoster/core/models/sucursal_model.dart';
+
+import '../../mvvm/viewmodels/sucursal_view_model.dart';
 
 class CustomAppbar extends StatefulWidget {
   const CustomAppbar({super.key});
@@ -9,33 +13,72 @@ class CustomAppbar extends StatefulWidget {
 
 class _CustomAppbarState extends State<CustomAppbar> {
   @override
+  void initState() {
+    super.initState();
+    final _sucursalViewModel =
+        Provider.of<SucursalViewModel>(context, listen: false);
+    _sucursalViewModel.fetchLocalSucursales();
+
+    WidgetsBinding.instance.addPostFrameCallback((_) async {
+      Provider.of<SucursalViewModel>(context, listen: false)
+          .sincronizarSucursalesDesdeApi();
+
+      await _sucursalViewModel.sincronizarSucursales();
+      await _sucursalViewModel.fetchLocalSucursales();
+    });
+  }
+
+  String? _selectedValue;
+
+  @override
   Widget build(BuildContext context) {
+    final sucursalViewModel = Provider.of<SucursalViewModel>(context);
+    final sucursales = sucursalViewModel.sucursales;
     return Row(
       children: [
         Image.asset(
-          'assets/logo.png',
-          height: 40,
+          'assets/Turquessa.png',
+          height: 100,
         ),
         const SizedBox(width: 10),
-        const SizedBox(width: 10),
-        DropdownButton(
-            hint: const Text(
-              "Mesas Interiores",
-              style:
-                  TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
-            ),
-            icon: const Icon(Icons.arrow_drop_down),
-            items: const [
-              DropdownMenuItem(
-                value: "ES",
-                child: Text("Palomeras"),
+        SizedBox(
+          width: 240,
+          child: Expanded(
+            child: DropdownButtonFormField<String>(
+              value: _selectedValue,
+              hint: Text(
+                _selectedValue?.isEmpty ?? true
+                    ? 'Selecciona una sucursal'
+                    : '',
+                style:
+                    TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
               ),
-              DropdownMenuItem(
-                value: "EN",
-                child: Text("Sombrillera"),
+              icon: const Icon(Icons.arrow_drop_down),
+              decoration: InputDecoration(
+                contentPadding:
+                    const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+                border: OutlineInputBorder(
+                  borderRadius: BorderRadius.circular(15),
+                  borderSide: BorderSide.none,
+                ),
+                filled: true,
+                fillColor: Colors.white,
               ),
-            ],
-            onChanged: (e) => {})
+              items: sucursales
+                  .map((e) => DropdownMenuItem(
+                        value: e.nombre,
+                        child: Text(e.nombre ?? ""),
+                      ))
+                  .toList(),
+              onChanged: (value) {
+                setState(() {
+                  _selectedValue = value;
+                  // setSelectedSucursal(value as Sucursal);
+                });
+              },
+            ),
+          ),
+        )
       ],
     );
   }

+ 57 - 0
lib/utils/widgets/navigation_rail.dart

@@ -0,0 +1,57 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+import '../../mvvm/viewmodels/viewmodels.dart';
+
+class CustomNavigationRail extends StatefulWidget {
+  const CustomNavigationRail({super.key, required selectedIndex});
+
+  @override
+  State<CustomNavigationRail> createState() => Custom_NavigationRailState();
+}
+
+class Custom_NavigationRailState extends State<CustomNavigationRail> {
+  int selectedIndex = 0;
+  @override
+  Widget build(BuildContext context) {
+    return NavigationRail(
+      backgroundColor: Color.fromARGB(255, 25, 30, 41),
+      selectedIndex: selectedIndex,
+      onDestinationSelected: (int index) {
+        setState(() {
+          selectedIndex = index;
+        });
+        if (index == 3) {
+          Provider.of<LoginViewModel>(context, listen: false)
+              .showExitConfirmationDialog(context);
+        }
+      },
+      labelType: NavigationRailLabelType.all,
+      destinations: const [
+        NavigationRailDestination(
+          icon: Icon(Icons.home, color: Colors.white),
+          selectedIcon: Icon(Icons.home_filled),
+          label: Text('Inicio'),
+        ),
+        NavigationRailDestination(
+          icon: Icon(Icons.search),
+          selectedIcon: Icon(
+            Icons.search_rounded,
+            color: Colors.white,
+          ),
+          label: Text('Buscar'),
+        ),
+        NavigationRailDestination(
+          icon: Icon(Icons.settings),
+          selectedIcon: Icon(Icons.settings_rounded, color: Colors.white),
+          label: Text('Ajustes'),
+        ),
+        NavigationRailDestination(
+          icon: Icon(Icons.logout),
+          selectedIcon: Icon(Icons.logout, color: Colors.white),
+          label: Text('Cerrar Sesión'),
+        ),
+      ],
+    );
+  }
+}

+ 211 - 0
lib/utils/widgets/ordenes_card.dart

@@ -0,0 +1,211 @@
+import 'package:flutter/material.dart';
+
+class OrdenItem {
+  final int cantidad;
+  final String nombre;
+  final String descripcion;
+  final String? notas;
+  final bool isListo;
+
+  OrdenItem({
+    required this.cantidad,
+    required this.nombre,
+    required this.descripcion,
+    this.notas,
+    this.isListo = false,
+  });
+}
+
+class OrdenesScreen extends StatelessWidget {
+  const OrdenesScreen({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final items = [
+      OrdenItem(
+        cantidad: 1,
+        nombre: 'Chilaquiles Verdes',
+        descripcion: 'Tortillas fritas en salsa verde con pollo',
+        notas: 'Sin cebolla ni tomate',
+      ),
+      OrdenItem(
+        cantidad: 2,
+        nombre: 'Huevos Rancheros',
+        descripcion: 'Huevos fritos sobre tortilla con salsa roja',
+      ),
+    ];
+
+    final items2 = [
+      OrdenItem(
+        cantidad: 1,
+        nombre: 'Club Sandwich',
+        descripcion: 'Sándwich triple con pollo, jamón y tocino',
+        isListo: true,
+      ),
+    ];
+
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      children: [
+        OrdenMesaCard(
+          mesaNumero: '5',
+          ordenNumero: 'A-123',
+          items: items,
+          onLiberarOrden: () {},
+        ),
+        OrdenMesaCard(
+          mesaNumero: '3',
+          ordenNumero: 'A-124',
+          items: items2,
+          tieneItemsListos: true,
+          onLiberarOrden: () {},
+        ),
+      ],
+    );
+  }
+}
+
+class OrdenMesaCard extends StatelessWidget {
+  final String mesaNumero;
+  final String ordenNumero;
+  final List<OrdenItem> items;
+  final bool tieneItemsListos;
+  final VoidCallback onLiberarOrden;
+
+  const OrdenMesaCard({
+    Key? key,
+    required this.mesaNumero,
+    required this.ordenNumero,
+    required this.items,
+    this.tieneItemsListos = false,
+    required this.onLiberarOrden,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Card(
+      margin: const EdgeInsets.all(8.0),
+      child: Column(
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          Padding(
+            padding: const EdgeInsets.all(16.0),
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: [
+                Text(
+                  'Mesa $mesaNumero',
+                  style: const TextStyle(
+                    fontSize: 18,
+                    fontWeight: FontWeight.bold,
+                  ),
+                ),
+                Row(
+                  children: [
+                    const Icon(Icons.access_time, size: 16),
+                    const SizedBox(width: 4),
+                    const Text('15:03'),
+                  ],
+                ),
+              ],
+            ),
+          ),
+          const Divider(height: 1),
+          Padding(
+            padding:
+                const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
+            child: Align(
+              alignment: Alignment.centerLeft,
+              child: Text(
+                'Orden #$ordenNumero',
+                style: TextStyle(
+                  color: Colors.grey[600],
+                  fontSize: 14,
+                ),
+              ),
+            ),
+          ),
+          ...items.map((item) => ListTile(
+                contentPadding: const EdgeInsets.symmetric(horizontal: 16.0),
+                leading: Text(
+                  '${item.cantidad}x',
+                  style: const TextStyle(
+                    fontSize: 16,
+                    fontWeight: FontWeight.bold,
+                  ),
+                ),
+                title: Text(
+                  item.nombre,
+                  style: const TextStyle(
+                    fontSize: 16,
+                    fontWeight: FontWeight.w500,
+                  ),
+                ),
+                subtitle: Column(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  children: [
+                    Text(
+                      item.descripcion,
+                      style: TextStyle(
+                        color: Colors.grey[600],
+                        fontSize: 14,
+                      ),
+                    ),
+                    if (item.notas != null)
+                      Text(
+                        item.notas!,
+                        style: TextStyle(
+                          color: Colors.red[400],
+                          fontSize: 14,
+                        ),
+                      ),
+                  ],
+                ),
+                trailing: item.isListo
+                    ? Container(
+                        padding: const EdgeInsets.symmetric(
+                            horizontal: 12, vertical: 6),
+                        decoration: BoxDecoration(
+                          color: Colors.black,
+                          borderRadius: BorderRadius.circular(20),
+                        ),
+                        child: const Text(
+                          'Listo',
+                          style: TextStyle(
+                            color: Colors.white,
+                            fontSize: 12,
+                          ),
+                        ),
+                      )
+                    : const Icon(Icons.chevron_right),
+              )),
+          Padding(
+            padding: const EdgeInsets.all(16.0),
+            child: ElevatedButton(
+              onPressed: onLiberarOrden,
+              style: ElevatedButton.styleFrom(
+                backgroundColor: const Color(0xFF4CAF50),
+                padding: const EdgeInsets.symmetric(vertical: 16),
+                shape: RoundedRectangleBorder(
+                  borderRadius: BorderRadius.circular(8),
+                ),
+              ),
+              child: const SizedBox(
+                width: double.infinity,
+                child: Text(
+                  'Liberar Orden Completa',
+                  textAlign: TextAlign.center,
+                  style: TextStyle(
+                    color: Colors.white,
+                    fontSize: 16,
+                    fontWeight: FontWeight.w500,
+                  ),
+                ),
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 1 - 0
pubspec.yaml

@@ -76,6 +76,7 @@ flutter:
 
   # To add assets to your application, add an assets section, like this:
   assets:
+    - assets/Turquessa.png
     - assets/logo.png
     - assets/igLogo.png
     - assets/logo-BN.png