56 İşlemeler d57b1093d4 ... 35d5bc8b8b

Yazar SHA1 Mesaj Tarih
  ElPoteito 35d5bc8b8b Avance pedidos por día 2 ay önce
  ElPoteito bbeb86fdc6 15358: Poner rango en selector de Pedidos de dia , ajustar dos columnas incluirlas, fecha y propina, cambiar etiqueta del final de dia por rango y en ticket agregar total propinas https://prnt.sc/RFz4_6F7uIHS 2 ay önce
  OscarGil03 7e01fdcb7d Cambio version sdk 2 ay önce
  OscarGil03 1d6c3d91b8 Propina en Corte de caja 2 ay önce
  OscarGil03 509e6b87e7 Instalador 3 ay önce
  OscarGil03 8f381b1e1e Actualización configuración 3 ay önce
  OscarGil03 92ded34352 Pedidos ahora llevan corte caja id y se pueden pasar de un corte de caja a otro 3 ay önce
  OscarGil03 eda469f3f5 Cambio version 4 ay önce
  OscarGil03 0e18511ca2 Modulo corte de caja, pedidos con mesa y token totp seguridad 4 ay önce
  OscarGil03 b0e13e5055 Script generacion setup 4 ay önce
  OscarGil03 35d6312c17 Eliminacion de pedidos y mostrar mesa en grid 5 ay önce
  OscarGil03 9e2916fd6d Validacion mesas e impresion de ticket 5 ay önce
  OscarGil03 e937f2d6b8 Pedido Mesas 5 ay önce
  OscarGil03 319ceeed89 Actualizacion mesas 5 ay önce
  OscarGil03 166db8d37a Actualizacion version con corte de caja y ajuste en la creacion de pedido 5 ay önce
  OscarGil03 ac5bef4da5 Sincronizacion forzosa con imagenes 6 ay önce
  OscarGil03 777cedeceb Boton para mostrar contraseña 6 ay önce
  OscarGil03 e4e8ac923f cambio versión 6 ay önce
  OscarGil03 9bea99c012 Se guarda idUsuario en pedido y se manda a sincronización 6 ay önce
  OscarGil03 f059d41e6f Validación de clave en login 6 ay önce
  OscarGil03 e9fde029c1 Forzar sincronización y sincronizacion de clave usuario 6 ay önce
  OscarGil03 a79399eb4d Campo minimo para validar un minimo obligatorio al generar un pedido 6 ay önce
  OscarGil03 4e9cf6c077 Actualizacion de pantalla al sincronizar productos y categorias 6 ay önce
  OscarGil03 bf6b1deab3 Sobreescribe la la sincronizacion de toppings para evitar duplicados 6 ay önce
  OscarGil03 7318f130d9 Lista de toppings descolapsada por default y muestra el maximo por categoria 6 ay önce
  OscarGil03 5d8fea1497 Cambio de version 6 ay önce
  OscarGil03 a0e47e578f Clave sucursal cambia dependiendo del seleccionado en modulo de sucursales 6 ay önce
  OscarGil03 ffa2a4cf95 Prefijo en la versión dependiendo a donde apunte la API 6 ay önce
  OscarGil03 47472236c7 Permiso de administracion, cancelar pedido y ver reportes 6 ay önce
  OscarGil03 be2fb5533f Sincronizacion de toppings seleccionables y sincronizacion de toppings del pedido seleccionados 6 ay önce
  OscarGil03 a21320fb30 Sincronizacion de toppings e imagenes 6 ay önce
  OscarGil03 5df5fa09c2 Se empezó a implementar las validaciones con permisos 6 ay önce
  OscarGil03 db2b9090e7 Actualizacion de permisos, usuarios y usuarios permisos cuando la fecha de modificado sea mas reciente 6 ay önce
  OscarGil03 fd910044da Utilización SessionStorage para mantener sesión iniciada 6 ay önce
  OscarGil03 d78be1be14 Login Local con correo y selector de sucursal 6 ay önce
  OscarGil03 009baec027 Sincronizacion de permisos, usuarios y usuarios permisos 6 ay önce
  OscarGil03 a221a44d4f Selector de sucursales 6 ay önce
  OscarGil03 89eba6770c Selector de sucursales 6 ay önce
  OscarGil03 6c117ee208 claveSucursal en parametros de productos y categorias y sincronizacion de sucursales 6 ay önce
  OscarGil03 afb9bf17cf Iconos App 6 ay önce
  OscarGil03 20aca5b4ce Cambio nombre 6 ay önce
  OscarGil03 2ea88fb8e7 Sincronizacion productos y ajustes variables 6 ay önce
  OscarGil03 b62a3ab36c Ajustes variables, pedidos, productos y categorias 7 ay önce
  OscarGil03 549cf23782 Sincronizacion de pedidos, productos y categorías 7 ay önce
  OscarGil03 663978bdeb Descuentos, tipos de pago y variables 8 ay önce
  OscarGil03 ca5ce8da9c ticket pedido 8 ay önce
  OscarGil03 7f6d87310c Version nueva 8 ay önce
  OscarGil03 2eca21dfd2 local bd y pedidos 1 yıl önce
  OscarGil03 06d2d3edd2 Se arregló limite de obtencion de topings 1 yıl önce
  OscarGil03 5fd9286b68 Se arregló el limite en la obtencion de topings 1 yıl önce
  OscarGil03 23d40ed69d Buscador productos en pedido form 1 yıl önce
  OscarGil03 bf87d5e4e6 Forms de topings y categoria topings actualizado 1 yıl önce
  OscarGil03 750f798fb5 Categoría de topings y topings en pedido form 1 yıl önce
  OscarGil03 1c10fe75bd Topings y categoria de topings 1 yıl önce
  OscarGil03 a65237adcf Productos y categoría de productos 1 yıl önce
  OscarGil03 db179bdc59 Categoria Productos 1 yıl önce
100 değiştirilmiş dosya ile 6524 ekleme ve 280 silme
  1. 25 0
      .vscode/launch.json
  2. 1 1
      README.md
  3. 9 5
      android/app/build.gradle
  4. 2 2
      android/app/src/main/AndroidManifest.xml
  5. 1 1
      android/app/src/main/kotlin/com/example/yoshi_papas_app/MainActivity.kt
  6. BIN
      android/app/src/main/res/mipmap-hdpi/launcher_icon.png
  7. BIN
      android/app/src/main/res/mipmap-mdpi/launcher_icon.png
  8. BIN
      android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
  9. BIN
      android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
  10. BIN
      android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
  11. 91 1
      android/build.gradle
  12. 3 1
      android/gradle.properties
  13. 1 1
      android/gradle/wrapper/gradle-wrapper.properties
  14. 1 0
      android/settings.gradle
  15. BIN
      assets/JoshiLogo-BN.png
  16. BIN
      assets/icono.ico
  17. BIN
      assets/logo-BN.png
  18. BIN
      assets/logo.png
  19. 26 0
      flutter_launcher_icons.yaml
  20. 52 0
      installers/Turquesa_POS_Inno_Setup.iss
  21. BIN
      installers/TurquessaPOS_Setup_24.12.04.exe
  22. BIN
      installers/TurquessaPOS_Setup_24.12.11.1.exe
  23. BIN
      installers/TurquessaPOS_Setup_24.12.11.exe
  24. BIN
      installers/TurquessaPOS_Setup_24.12.27.exe
  25. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
  26. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  27. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  28. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
  29. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  30. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
  31. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
  32. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  33. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
  34. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
  35. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
  36. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
  37. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
  38. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
  39. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
  40. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
  41. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
  42. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
  43. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
  44. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
  45. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
  46. 2 2
      ios/Runner/Info.plist
  47. 6 1
      lib/data/session/session_storage.dart
  48. 43 13
      lib/main.dart
  49. 29 16
      lib/models/basico_model.dart
  50. 67 0
      lib/models/categoria_producto_model.dart
  51. 134 0
      lib/models/corte_caja_model.dart
  52. 80 0
      lib/models/deposito_model.dart
  53. 20 0
      lib/models/descuento_model.dart
  54. 80 0
      lib/models/gasto_model.dart
  55. 27 0
      lib/models/item_carrito_model.dart
  56. 2 2
      lib/models/login_model.dart
  57. 37 0
      lib/models/media_toping_categoria_model.dart
  58. 37 0
      lib/models/media_toping_model.dart
  59. 66 0
      lib/models/mesa_model.dart
  60. 22 0
      lib/models/models.dart
  61. 99 10
      lib/models/pedido_model.dart
  62. 81 0
      lib/models/pedido_producto_model.dart
  63. 43 0
      lib/models/pedido_producto_toping_model.dart
  64. 75 0
      lib/models/permiso_model.dart
  65. 155 0
      lib/models/producto_model.dart
  66. 44 0
      lib/models/producto_topping_model.dart
  67. 54 0
      lib/models/propina_model.dart
  68. 80 0
      lib/models/retiro_model.dart
  69. 79 0
      lib/models/sucursal_model.dart
  70. 52 0
      lib/models/toping_categoria_model.dart
  71. 55 0
      lib/models/toping_model.dart
  72. 148 18
      lib/models/usuario_model.dart
  73. 55 0
      lib/models/usuario_permiso_model.dart
  74. 40 0
      lib/models/variable_model.dart
  75. 13 4
      lib/services/base_service.dart
  76. 77 0
      lib/services/categoria_producto_service.dart
  77. 13 9
      lib/services/general_service.dart
  78. 11 12
      lib/services/login_service.dart
  79. 96 0
      lib/services/producto_service.dart
  80. 63 0
      lib/services/productos_service.dart
  81. 1 1
      lib/services/profile_service.dart
  82. 1707 0
      lib/services/repo_service.dart
  83. 2 0
      lib/services/services.dart
  84. 6 3
      lib/themes/themes.dart
  85. 140 0
      lib/viewmodels/categoria_producto_view_model.dart
  86. 588 0
      lib/viewmodels/corte_caja_view_model.dart
  87. 23 0
      lib/viewmodels/descuento_view_model.dart
  88. 51 44
      lib/viewmodels/login_view_model.dart
  89. 115 0
      lib/viewmodels/media_view_model.dart
  90. 174 0
      lib/viewmodels/mesa_view_model.dart
  91. 442 0
      lib/viewmodels/pedido_view_model.dart
  92. 101 0
      lib/viewmodels/permiso_view_model.dart
  93. 440 0
      lib/viewmodels/producto_view_model.dart
  94. 27 0
      lib/viewmodels/propina_view_model.dart
  95. 80 0
      lib/viewmodels/sucursal_view_model.dart
  96. 130 0
      lib/viewmodels/toping_categoria_view_model.dart
  97. 138 0
      lib/viewmodels/toping_view_model.dart
  98. 41 133
      lib/viewmodels/usuarios_view_model.dart
  99. 121 0
      lib/viewmodels/variable_view_model.dart
  100. 0 0
      lib/viewmodels/viewmodels.dart

+ 25 - 0
.vscode/launch.json

@@ -0,0 +1,25 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "turquessa_app",
+            "request": "launch",
+            "type": "dart"
+        },
+        {
+            "name": "turquessa_app (profile mode)",
+            "request": "launch",
+            "type": "dart",
+            "flutterMode": "profile"
+        },
+        {
+            "name": "turquessa_app (release mode)",
+            "request": "launch",
+            "type": "dart",
+            "flutterMode": "release"
+        }
+    ]
+}

+ 1 - 1
README.md

@@ -1,4 +1,4 @@
-# yoshi_papas_app
+# turquessa_app
 
 A new Flutter project.
 

+ 9 - 5
android/app/build.gradle

@@ -23,17 +23,21 @@ if (flutterVersionName == null) {
 }
 
 android {
-    namespace "com.example.yoshi_papas_app"
+    namespace "com.example.turquessa_app"
     compileSdkVersion 34
     ndkVersion flutter.ndkVersion
 
     compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
     }
 
     kotlinOptions {
-        jvmTarget = '1.8'
+        jvmTarget = JavaVersion.VERSION_17
+    }
+
+    kotlin {
+        jvmToolchain(17)
     }
 
     sourceSets {
@@ -42,7 +46,7 @@ android {
 
     defaultConfig {
         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
-        applicationId "com.example.yoshi_papas_app"
+        applicationId "com.example.turquessa_app"
         // You can update the following values to match your application needs.
         // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
         minSdkVersion 24

+ 2 - 2
android/app/src/main/AndroidManifest.xml

@@ -5,9 +5,9 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
     <application
-        android:label="yoshi_papas_app"
+        android:label="turquessa_coffee_app"
         android:name="${applicationName}"
-        android:icon="@mipmap/ic_launcher">
+        android:icon="@mipmap/launcher_icon">
         <activity
             android:name=".MainActivity"
             android:exported="true"

+ 1 - 1
android/app/src/main/kotlin/com/example/yoshi_papas_app/MainActivity.kt

@@ -1,4 +1,4 @@
-package com.example.yoshi_papas_app
+package com.example.turquessa_app
 
 import io.flutter.embedding.android.FlutterActivity
 

BIN
android/app/src/main/res/mipmap-hdpi/launcher_icon.png


BIN
android/app/src/main/res/mipmap-mdpi/launcher_icon.png


BIN
android/app/src/main/res/mipmap-xhdpi/launcher_icon.png


BIN
android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png


BIN
android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png


+ 91 - 1
android/build.gradle

@@ -1,11 +1,12 @@
 buildscript {
-    ext.kotlin_version = '1.7.10'
+    ext.kotlin_version = '1.8.22'
     repositories {
         google()
         mavenCentral()
     }
 
     dependencies {
+        classpath 'com.android.tools.build:gradle:8.1.0'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
     }
 }
@@ -20,7 +21,96 @@ allprojects {
 rootProject.buildDir = '../build'
 subprojects {
     project.buildDir = "${rootProject.buildDir}/${project.name}"
+
+    // Ajuste de dependencias
+    project.configurations.all {
+        resolutionStrategy.eachDependency { details ->
+            if (details.requested.group == 'com.android.support' && !details.requested.name.contains('multidex')) {
+                details.useVersion "27.1.1"
+            }
+            if (details.requested.group == "org.jetbrains.kotlin") {
+                details.useVersion "1.8.22"
+            }
+        }
+    }
+
+    // Ajuste del namespace y eliminación del atributo 'package' en manifests
+    project.plugins.withId("com.android.library") {
+        project.android {
+            if (namespace == null || namespace.isEmpty()) {
+                namespace = "${project.group}.${project.name}".replace('-', '_')
+            }
+
+            project.tasks.configureEach { task ->
+                if (task.name.contains("processDebugManifest") || task.name.contains("processReleaseManifest")) {
+                    task.doFirst {
+                        File manifestFile = file("${projectDir}/src/main/AndroidManifest.xml")
+                        if (manifestFile.exists()) {
+                            String manifestContent = manifestFile.text
+                            if (manifestContent.contains('package=')) {
+                                manifestContent = manifestContent.replaceAll(/package="[^"]*"/, "")
+                                manifestFile.write(manifestContent)
+                                println "Removed 'package' attribute from ${manifestFile}"
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // Fuerza Java 17 en proyectos Android
+    project.plugins.withId("com.android.application") {
+        project.android {
+            compileOptions {
+                sourceCompatibility JavaVersion.VERSION_17
+                targetCompatibility JavaVersion.VERSION_17
+            }
+        }
+    }
+
+    project.plugins.withId("com.android.library") {
+        project.android {
+            compileOptions {
+                sourceCompatibility JavaVersion.VERSION_17
+                targetCompatibility JavaVersion.VERSION_17
+            }
+        }
+    }
+
+    // Fuerza Kotlin JVM target a 17
+    project.plugins.withId("org.jetbrains.kotlin.jvm") {
+        project.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+            kotlinOptions {
+                jvmTarget = "17"
+            }
+        }
+    }
+
+    project.plugins.withId("org.jetbrains.kotlin.android") {
+        project.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+            kotlinOptions {
+                jvmTarget = "17"
+            }
+        }
+    }
+
+    // Fuerza Java para proyectos con plugin 'java'
+    project.plugins.withId("java") {
+        java {
+            toolchain {
+                languageVersion = JavaLanguageVersion.of(17)
+            }
+        }
+    }
+
+    // Fuerza nivel de compatibilidad en tareas Java
+    project.tasks.withType(JavaCompile).configureEach {
+        sourceCompatibility = JavaVersion.VERSION_17
+        targetCompatibility = JavaVersion.VERSION_17
+    }
 }
+
 subprojects {
     project.evaluationDependsOn(':app')
 }

+ 3 - 1
android/gradle.properties

@@ -1,3 +1,5 @@
-org.gradle.jvmargs=-Xmx4G
+org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
 android.useAndroidX=true
 android.enableJetifier=true
+org.gradle.java.installations.auto-download=true
+org.gradle.java.installations.auto-detect=true

+ 1 - 1
android/gradle/wrapper/gradle-wrapper.properties

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip

+ 1 - 0
android/settings.gradle

@@ -18,6 +18,7 @@ pluginManagement {
 
     plugins {
         id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
+        id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0'
     }
 }
 

BIN
assets/JoshiLogo-BN.png


BIN
assets/icono.ico


BIN
assets/logo-BN.png


BIN
assets/logo.png


+ 26 - 0
flutter_launcher_icons.yaml

@@ -0,0 +1,26 @@
+# flutter pub run flutter_launcher_icons
+flutter_launcher_icons:
+  image_path: "assets/logo.png"
+
+  android: "launcher_icon"
+  # image_path_android: "assets/icon/icon.png"
+  min_sdk_android: 21 # android min sdk min:16, default 21
+  # adaptive_icon_background: "assets/icon/background.png"
+  # adaptive_icon_foreground: "assets/icon/foreground.png"
+
+  ios: true
+  # image_path_ios: "assets/icon/icon.png"
+  remove_alpha_channel_ios: true
+
+  web:
+      generate: true
+      image_path: "assets/logo.png"
+
+  windows:
+      generate: true
+      image_path: "assets/logo.png"
+      icon_size: 256
+
+  macos:
+      generate: true
+      image_path: "assets/logo.png"

+ 52 - 0
installers/Turquesa_POS_Inno_Setup.iss

@@ -0,0 +1,52 @@
+; Script generated by the Inno Setup Script Wizard.
+; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
+
+#define MyAppName "TurquessaPOS"
+#define MyAppVersion "1.24.12.27"
+#define MyAppExeName "turquessa_app.exe"
+
+[Setup]
+; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
+; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
+AppId={{FDA490E5-50B7-47B4-BE58-38ED1486304C}
+AppName={#MyAppName}
+AppVersion={#MyAppVersion}
+;AppVerName={#MyAppName} {#MyAppVersion}
+DefaultDirName={autopf}\{#MyAppName}
+DisableProgramGroupPage=yes
+; Uncomment the following line to run in non administrative install mode (install for current user only.)
+;PrivilegesRequired=lowest
+OutputDir=C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\installers
+OutputBaseFilename=TurquessaPOS_Setup_24.12.27
+SetupIconFile=C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\assets\icono.ico
+Compression=lzma
+SolidCompression=yes
+WizardStyle=modern
+
+[Languages]
+Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl"
+
+[Tasks]
+Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
+
+[Files]
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\flutter_windows.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\pdfium.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\permission_handler_windows_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\printing_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\sqlite3.def"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\sqlite3.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\url_launcher_windows_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\vcruntime140_1.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\msvcp140.dll"; DestDir: "{app}"; Flags: ignoreversion
+Source: "C:\Users\gusos\OneDrive\Escritorio\eDesarrollos\Turquesa\pos_turquessa_flutter\build\windows\x64\runner\Release\data\*"; DestDir: "{app}\data"; Flags: ignoreversion recursesubdirs createallsubdirs
+; NOTE: Don't use "Flags: ignoreversion" on any shared system files
+
+[Icons]
+Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
+Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
+
+[Run]
+Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
+

BIN
installers/TurquessaPOS_Setup_24.12.04.exe


BIN
installers/TurquessaPOS_Setup_24.12.11.1.exe


BIN
installers/TurquessaPOS_Setup_24.12.11.exe


BIN
installers/TurquessaPOS_Setup_24.12.27.exe


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png


+ 2 - 2
ios/Runner/Info.plist

@@ -5,7 +5,7 @@
 	<key>CFBundleDevelopmentRegion</key>
 	<string>$(DEVELOPMENT_LANGUAGE)</string>
 	<key>CFBundleDisplayName</key>
-	<string>Yoshi Papas App</string>
+	<string>Turquessa App</string>
 	<key>CFBundleExecutable</key>
 	<string>$(EXECUTABLE_NAME)</string>
 	<key>CFBundleIdentifier</key>
@@ -13,7 +13,7 @@
 	<key>CFBundleInfoDictionaryVersion</key>
 	<string>6.0</string>
 	<key>CFBundleName</key>
-	<string>yoshi_papas_app</string>
+	<string>turquessa_app</string>
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>

+ 6 - 1
lib/data/session/session_storage.dart

@@ -14,7 +14,7 @@ class SessionStorage {
     final preferences = await _getPreferences();
     await preferences.setInt('id', id);
   }
-  
+
   Future<void> saveCorreo(String value) async {
     final preferences = await _getPreferences();
     await preferences.setString('correo', value);
@@ -59,4 +59,9 @@ class SessionStorage {
     final preferences = await _getPreferences();
     await preferences.remove('token');
   }
+
+  Future<void> clearId() async {
+    final preferences = await _getPreferences();
+    await preferences.remove('id');
+  }
 }

+ 43 - 13
lib/main.dart

@@ -1,28 +1,60 @@
-//import 'package:fluent_ui/fluent_ui.dart';
 import 'package:flutter/material.dart';
-//import 'package:hermogas_ecosistema_web/viewmodels/codigo_postal_view_model.dart';
-import 'package:yoshi_papas_app/views/perfil/perfil_screen.dart';
-import 'package:provider/provider.dart';
-import '../themes/themes.dart';
 import 'package:flutter/services.dart';
+import 'package:provider/provider.dart';
+import 'package:sqflite_common_ffi/sqflite_ffi.dart';
+import 'dart:io';
+import 'package:flutter_single_instance/flutter_single_instance.dart';
 
-import 'viewmodels/viewmodels.dart';
-import 'views/main/main_screen.dart';
+import 'services/productos_service.dart';
 import 'views/home/home_screen.dart';
 import 'views/login/login_screen.dart';
+import 'views/perfil/perfil_screen.dart';
+import 'themes/themes.dart';
+import 'viewmodels/viewmodels.dart';
 import 'package:timezone/data/latest.dart' as tzdata;
 
-void main() {
+void main() async {
   WidgetsFlutterBinding.ensureInitialized();
   tzdata.initializeTimeZones();
+
+  if (!await FlutterSingleInstance.platform.isFirstInstance()) {
+    print("La aplicación ya se está ejecutando");
+    exit(0);
+  }
+
+  // Inicialización de la base de datos para plataformas de escritorio con FFI
+  if (Platform.isWindows || Platform.isLinux) {
+    sqfliteFfiInit();
+    databaseFactory = databaseFactoryFfi;
+  }
+
+  final productosService = ProductosService();
+  if (await productosService.isDatabaseEmpty()) {
+    await productosService.fillCategoriaBD();
+    await productosService.fillProductoBD();
+  }
+
   SystemChrome.setPreferredOrientations([
     DeviceOrientation.landscapeRight,
     DeviceOrientation.landscapeLeft,
   ]).then((_) {
     runApp(MultiProvider(providers: [
       ChangeNotifierProvider(create: (_) => LoginViewModel()),
-      ChangeNotifierProvider(create: (_) => UsuariosViewModel()),
+      ChangeNotifierProvider(create: (_) => UsuarioViewModel()),
       ChangeNotifierProvider(create: (_) => ProfileViewModel()),
+      ChangeNotifierProvider(create: (_) => CategoriaProductoViewModel()),
+      ChangeNotifierProvider(create: (_) => ProductoViewModel()),
+      ChangeNotifierProvider(create: (_) => TopingCategoriaViewModel()),
+      ChangeNotifierProvider(create: (_) => MediaViewModel()),
+      ChangeNotifierProvider(create: (_) => TopingViewModel()),
+      ChangeNotifierProvider(create: (_) => PedidoViewModel()),
+      ChangeNotifierProvider(create: (_) => DescuentoViewModel()),
+      ChangeNotifierProvider(create: (_) => VariableViewModel()),
+      ChangeNotifierProvider(create: (_) => SucursalViewModel()),
+      ChangeNotifierProvider(create: (_) => PermisoViewModel()),
+      ChangeNotifierProvider(create: (_) => CorteCajaViewModel()),
+      ChangeNotifierProvider(create: (_) => MesaViewModel()),
+      ChangeNotifierProvider(create: (_) => PropinaViewModel()),
       // Agrega aquí cualquier otro provider que necesites
     ], child: const MyApp()));
   });
@@ -30,18 +62,16 @@ void main() {
 
 class MyApp extends StatelessWidget {
   const MyApp({super.key});
-  //ChangeNotifierProvider(create: (_) => AppTheme()),
 
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       locale: const Locale("en", "ES"),
       debugShowCheckedModeBanner: false,
-      title: 'Yoshi Papas',
+      title: 'Turquessa',
       theme: AppTheme.lightTheme,
-      initialRoute: 'main',
+      initialRoute: 'login',
       routes: {
-        'main': (context) => const MainScreen(),
         'login': (context) => const LoginScreen(),
         'home': (context) => const HomeScreen(),
         'perfil': (context) => const PerfilScreen(),

+ 29 - 16
lib/models/basico_model.dart

@@ -18,11 +18,15 @@ class Basico {
   });
 
   static parseDate(origen) {
-    if (origen == "null") return null;
-    if (origen == null) return null;
-    if (origen == '') return null;
-    if (origen == "null") return null;
-    return DateTime.parse(origen);
+    if (origen == null || origen == "null" || origen == '') {
+      return null;
+    }
+    try {
+      return DateTime.parse(origen);
+    } catch (e) {
+      print("Error al parsear fecha: $e");
+      return null;
+    }
   }
 
   static double parseDouble(origen) {
@@ -50,28 +54,37 @@ class Basico {
     return origen.toString();
   }
 
-  static bool parseBolean(origen) {
-    if (origen == null) return false;
-    if (origen == "") return false;
-    if (origen == "null") return false;
-    if (origen == "false") return false;
-    if (origen.runtimeType.toString() == 'bool') return origen;
-    return bool.parse(origen.toString());
+  static bool? parseBolean(dynamic value) {
+    if (value == null) {
+      return null;
+    }
+    if (value is bool) {
+      return value;
+    }
+    if (value is int) {
+      return value == 1;
+    }
+    if (value is String) {
+      return value.toLowerCase() == 'true' || value == '1';
+    }
+    return null;
   }
 
   Map<String, dynamic> toJson() {
     return {
       'id': id,
       'idLocal': idLocal,
-      'eliminado': eliminado,
+      'eliminado': eliminado?.toIso8601String(),
     };
   }
 
   parseJson(Map<String, dynamic> json) {
     id = json['id'] != null ? int.parse(json['id'].toString()) : 0;
     idLocal = json['idLocal'] ?? -1;
-    eliminado = parseDate(json['eliminado']);
-    creado = parseDate(json['creado']);
-    modificado = parseDate(json['modificado']);
+    creado = json['creado'] != null ? parseDate(json['creado']) : creado;
+    modificado =
+        json['modificado'] != null ? parseDate(json['modificado']) : modificado;
+    eliminado =
+        json['eliminado'] != null ? parseDate(json['eliminado']) : eliminado;
   }
 }

+ 67 - 0
lib/models/categoria_producto_model.dart

@@ -0,0 +1,67 @@
+import 'basico_model.dart';
+import '../services/services.dart';
+
+class CategoriaProducto extends Basico {
+  String? nombre;
+  String? descripcion;
+  int? esToping;
+  int? maximo;
+  int? minimo;
+  int? idWeb;
+  String? sincronizado;
+
+  CategoriaProducto({
+    super.id,
+    this.nombre,
+    this.descripcion,
+    this.esToping,
+    this.maximo,
+    this.minimo,
+    this.idWeb,
+    this.sincronizado,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    // print("Convirtiendo Producto a JSON");
+    // print("ID: $id, descripcion: $descripcion, Nombre: $nombre");
+    // print("creado: $creado, modificado: $modificado, eliminado: $eliminado");
+
+    return {
+      'id': id,
+      'nombre': nombre ?? '',
+      'descripcion': descripcion ?? '',
+      'esToping': esToping ?? 0,
+      'maximo': maximo ?? 0,
+      'minimo': minimo ?? 0,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    }..addAll(super.toJson());
+  }
+
+  CategoriaProducto.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    esToping = Basico.parseInt(json['esToping']);
+    maximo = Basico.parseInt(json['maximo']);
+    minimo = Basico.parseInt(json['minimo']);
+  }
+
+  CategoriaProducto.fromApi(Map<String, dynamic> json) {
+    super.parseJson(json);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    esToping = Basico.parseInt(json['esToping']);
+    maximo = Basico.parseInt(json['maximo']);
+    minimo = Basico.parseInt(json['minimo']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
+  Future<void> guardar() async {
+    idLocal = await RepoService().guardar(this);
+  }
+}

+ 134 - 0
lib/models/corte_caja_model.dart

@@ -0,0 +1,134 @@
+import 'package:turquessa_app/models/models.dart';
+
+import '../services/services.dart';
+
+class CorteCaja {
+  String? id;
+  DateTime? fechaApertura;
+  DateTime? fechaCorte;
+  int? idUsuario;
+  double? fondo;
+  int? idSucursal;
+  double? fondoDiaSig;
+  double? ventaPuntos;
+  double? ventaEfe;
+  double? ventaTrans;
+  double? ventaTarj;
+  double? gasto;
+  double? retiro;
+  double? deposito;
+  double? corteFinal;
+  DateTime? creado;
+  DateTime? eliminado;
+  DateTime? modificado;
+  String? idWeb;
+  String? sincronizado;
+
+  List<Deposito> depositos = [];
+  List<Retiro> retiros = [];
+  List<Gasto> gastos = [];
+
+  CorteCaja({
+    this.id,
+    this.fechaApertura,
+    this.fechaCorte,
+    this.idUsuario,
+    this.idSucursal,
+    this.fondo,
+    this.fondoDiaSig,
+    this.ventaPuntos,
+    this.ventaEfe,
+    this.ventaTrans,
+    this.ventaTarj,
+    this.gasto,
+    this.retiro,
+    this.deposito,
+    this.corteFinal,
+    this.creado,
+    this.modificado,
+    this.eliminado,
+    this.idWeb,
+    this.sincronizado,
+    this.depositos = const [],
+    this.retiros = const [],
+    this.gastos = const [],
+  });
+
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'fechaApertura': fechaApertura?.toIso8601String(),
+      'fechaCorte': fechaCorte?.toIso8601String(),
+      'idUsuario': idUsuario ?? 0,
+      'idSucursal': idSucursal ?? 0,
+      'fondo': fondo ?? 0.0,
+      'fondoDiaSig': fondoDiaSig ?? 0.0,
+      'ventaPuntos': ventaPuntos ?? 0.0,
+      'ventaEfe': ventaEfe ?? 0.0,
+      'ventaTrans': ventaTrans ?? 0.0,
+      'ventaTarj': ventaTarj ?? 0.0,
+      'gasto': gasto ?? 0.0,
+      'retiro': retiro ?? 0.0,
+      'deposito': deposito ?? 0.0,
+      'corteFinal': corteFinal ?? 0.0,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+      'sincronizado': sincronizado,
+      'idWeb': idWeb,
+    };
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'id': id,
+      'fechaApertura': fechaApertura,
+      'fechaCorte': fechaCorte,
+      'idUsuario': idUsuario,
+      'idSucursal': idSucursal,
+      'fondo': fondo,
+      'fondoDiaSig': fondoDiaSig,
+      'ventaPuntos': ventaPuntos,
+      'ventaEfe': ventaEfe,
+      'ventaTrans': ventaTrans,
+      'ventaTarj': ventaTarj,
+      'gasto': gasto,
+      'retiro': retiro,
+      'deposito': deposito,
+      'corteFinal': corteFinal,
+      'creado': creado,
+      'modificado': modificado,
+      'eliminado': eliminado,
+      'depositos': depositos.map((d) => d.toApi()).toList(),
+      'retiros': retiros.map((r) => r.toApi()).toList(),
+      'gastos': gastos.map((g) => g.toApi()).toList(),
+    };
+  }
+
+  CorteCaja.fromJson(Map<String, dynamic> json) {
+    id = Basico.parseString(json['id']);
+    fechaApertura = Basico.parseDate(json['fechaApertura']);
+    fechaCorte = Basico.parseDate(json['fechaCorte']);
+    idUsuario = Basico.parseInt(json['idUsuario']);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    fondo = Basico.parseDouble(json['fondo']);
+    fondoDiaSig = Basico.parseDouble(json['fondoDiaSig']);
+    ventaPuntos = Basico.parseDouble(json['ventaPuntos']);
+    ventaEfe = Basico.parseDouble(json['ventaEfe']);
+    ventaTrans = Basico.parseDouble(json['ventaTrans']);
+    ventaTarj = Basico.parseDouble(json['ventaTarj']);
+    gasto = Basico.parseDouble(json['gasto']);
+    retiro = Basico.parseDouble(json['retiro']);
+    deposito = Basico.parseDouble(json['deposito']);
+    corteFinal = Basico.parseDouble(json['corteFinal']);
+    creado = Basico.parseDate(json['creado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+    modificado = Basico.parseDate(json['modificado']);
+    idWeb = Basico.parseString(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
+  }
+
+  Future<void> guardar() async {
+    await RepoService().guardar(this);
+  }
+}

+ 80 - 0
lib/models/deposito_model.dart

@@ -0,0 +1,80 @@
+import 'basico_model.dart';
+import '../services/services.dart';
+
+class Deposito {
+  String? id;
+  String? idCorteCaja;
+  int? idSucursal;
+  int? idUsuario;
+  DateTime? fechaDeposito;
+  double? monto;
+  String? persona;
+  String? descripcion;
+  DateTime? creado;
+  DateTime? modificado;
+  DateTime? eliminado;
+
+  Deposito({
+    this.id,
+    this.idCorteCaja,
+    this.idSucursal,
+    this.idUsuario,
+    this.fechaDeposito,
+    this.monto,
+    this.persona,
+    this.descripcion,
+    this.creado,
+    this.modificado,
+    this.eliminado,
+  });
+
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idCorteCaja': idCorteCaja ?? 0,
+      'idSucursal': idSucursal ?? 0,
+      'idUsuario': idUsuario ?? 0,
+      'fechaDeposito': fechaDeposito?.toIso8601String(),
+      'monto': monto ?? 0.0,
+      'persona': persona ?? '',
+      'descripcion': descripcion ?? '',
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'id': id,
+      'idCorteCaja': idCorteCaja,
+      'idSucursal': idSucursal,
+      'idUsuario': idUsuario,
+      'fechaDeposito': fechaDeposito,
+      'monto': monto,
+      'persona': persona,
+      'descripcion': descripcion,
+      'creado': creado,
+      'modificado': modificado,
+      'eliminado': eliminado,
+    };
+  }
+
+  Deposito.fromJson(Map<String, dynamic> json) {
+    id = Basico.parseString(json['id']);
+    idCorteCaja = Basico.parseString(json['idCorteCaja']);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    idUsuario = Basico.parseInt(json['idUsuario']);
+    fechaDeposito = Basico.parseDate(json['fechaDeposito']);
+    monto = Basico.parseDouble(json['monto']);
+    persona = Basico.parseString(json['persona']);
+    descripcion = Basico.parseString(json['descripcion']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
+  Future<void> guardar() async {
+    await RepoService().guardar(this);
+  }
+}

+ 20 - 0
lib/models/descuento_model.dart

@@ -0,0 +1,20 @@
+import 'basico_model.dart';
+
+class Descuento {
+  int? id;
+  int? porcentaje;
+
+  Descuento({this.id, this.porcentaje});
+
+  Descuento.fromJson(Map<String, dynamic> json) {
+    id = Basico.parseInt(json['id']);
+    porcentaje = Basico.parseInt(json['porcentaje']);
+  }
+
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'porcentaje': porcentaje,
+    };
+  }
+}

+ 80 - 0
lib/models/gasto_model.dart

@@ -0,0 +1,80 @@
+import 'basico_model.dart';
+import '../services/services.dart';
+
+class Gasto {
+  String? id;
+  String? idCorteCaja;
+  int? idSucursal;
+  int? idUsuario;
+  DateTime? fechaGasto;
+  double? monto;
+  String? persona;
+  String? descripcion;
+  DateTime? creado;
+  DateTime? modificado;
+  DateTime? eliminado;
+
+  Gasto({
+    this.id,
+    this.idCorteCaja,
+    this.idSucursal,
+    this.idUsuario,
+    this.fechaGasto,
+    this.monto,
+    this.persona,
+    this.descripcion,
+    this.creado,
+    this.modificado,
+    this.eliminado,
+  });
+
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idCorteCaja': idCorteCaja ?? 0,
+      'idSucursal': idSucursal ?? 0,
+      'idUsuario': idUsuario ?? 0,
+      'fechaGasto': fechaGasto?.toIso8601String(),
+      'monto': monto ?? 0.0,
+      'persona': persona ?? '',
+      'descripcion': descripcion ?? '',
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'id': id,
+      'idCorteCaja': idCorteCaja,
+      'idSucursal': idSucursal,
+      'idUsuario': idUsuario,
+      'fechaGasto': fechaGasto,
+      'monto': monto,
+      'persona': persona,
+      'descripcion': descripcion,
+      'creado': creado,
+      'modificado': modificado,
+      'eliminado': eliminado,
+    };
+  }
+
+  Gasto.fromJson(Map<String, dynamic> json) {
+    id = Basico.parseString(json['id']);
+    idCorteCaja = Basico.parseString(json['idCorteCaja']);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    idUsuario = Basico.parseInt(json['idUsuario']);
+    fechaGasto = Basico.parseDate(json['fechaGasto']);
+    monto = Basico.parseDouble(json['monto']);
+    persona = Basico.parseString(json['persona']);
+    descripcion = Basico.parseString(json['descripcion']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
+  Future<void> guardar() async {
+    await RepoService().guardar(this);
+  }
+}

+ 27 - 0
lib/models/item_carrito_model.dart

@@ -0,0 +1,27 @@
+import 'package:flutter/material.dart';
+import 'producto_model.dart';
+
+class ItemCarrito {
+  Producto producto;
+  int cantidad;
+  Map<int, Set<int>> selectedToppings;
+  Map<int, List<Producto>> selectableToppings;
+  String? comentario;
+  bool expandido;
+  TextEditingController comentarioController;
+
+  ItemCarrito({
+    required this.producto,
+    this.cantidad = 1,
+    this.comentario,
+    this.expandido = false,
+    Map<int, Set<int>>? selectedToppings,
+    Map<int, List<Producto>>? selectableToppings,
+  })  : selectedToppings = selectedToppings ?? {},
+        selectableToppings = selectableToppings ?? {},
+        comentarioController = TextEditingController(text: comentario ?? '');
+
+  void dispose() {
+    comentarioController.dispose();
+  }
+}

+ 2 - 2
lib/models/login_model.dart

@@ -1,10 +1,10 @@
 class Login {
   String username;
-  String password;
+  String? password;
 
   Login({
     required this.username,
-    required this.password,
+    this.password,
   });
 
   Map<String, dynamic> toJson() {

+ 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(),
+      };
+}

+ 66 - 0
lib/models/mesa_model.dart

@@ -0,0 +1,66 @@
+import '/models/basico_model.dart';
+import '../services/services.dart';
+
+class Mesa extends Basico {
+  int? idSucursal;
+  String? nombre;
+  String? clave;
+  String? posicion;
+  bool? activa;
+
+  Mesa(
+      {super.id,
+      super.idLocal,
+      this.idSucursal,
+      this.nombre,
+      this.clave,
+      this.posicion,
+      this.activa,
+      super.creado,
+      super.modificado,
+      super.eliminado});
+
+  @override
+  Map<String, dynamic> toJson() {
+    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(),
+    }..addAll(super.toJson());
+  }
+
+  Mesa.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    nombre = Basico.parseString(json['nombre']);
+    clave = Basico.parseString(json['clave']);
+    posicion = Basico.parseString(json['posicion']);
+    activa = Basico.parseInt(json['activa']) == 1;
+  }
+
+  Mesa.fromApi(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    nombre = Basico.parseString(json['nombre']);
+    clave = Basico.parseString(json['clave']);
+    posicion = Basico.parseString(json['posicion']);
+    if (json['activo'] is bool) {
+      activa = json['activo'];
+    } else {
+      activa = Basico.parseInt(json['activo']) == 1;
+    }
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
+  Future<void> guardar() async {
+    idLocal = await RepoService().guardar(this);
+  }
+}

+ 22 - 0
lib/models/models.dart

@@ -2,4 +2,26 @@ 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/pedido_producto_model.dart';
+export '../models/pedido_producto_toping_model.dart';
+export '../models/producto_model.dart';
+export '../models/producto_topping_model.dart';
+export '../models/toping_categoria_model.dart';
+export '../models/toping_model.dart';
+export '../models/item_carrito_model.dart';
+export '../models/gasto_model.dart';
+export '../models/deposito_model.dart';
+export '../models/corte_caja_model.dart';
+export '../models/descuento_model.dart';
+export '../models/variable_model.dart';
+export '../models/sucursal_model.dart';
+export '../models/permiso_model.dart';
+export '../models/usuario_permiso_model.dart';
+export '../models/retiro_model.dart';
+export '../models/mesa_model.dart';
+export '../models/propina_model.dart';

+ 99 - 10
lib/models/pedido_model.dart

@@ -1,28 +1,41 @@
-import 'dart:ffi';
+import 'models.dart';
 
-import 'basico_model.dart';
-
-class Usuario extends Basico {
+class Pedido extends Basico {
   int? folio;
-  int? estatus;
+  String? estatus;
   String? comentarios;
   String? peticion;
   String? nombreCliente;
+  double? totalPedido;
+  int? descuento;
   int? idCliente;
   int? idMesa;
-  int? terminado;
+  String? terminado;
   int? cerrar;
   int? idUsuario;
   int? idModificador;
   int? idCancelado;
+  String? tipoPago;
+  double? cantEfectivo;
+  double? cantTarjeta;
+  double? cantTransferencia;
+  List<PedidoProducto> productos = [];
+  int? idWeb;
+  String? uuid;
+  String? idCorteCaja;
+  List<Propinas> propinas = [];
+
+  String? sincronizado;
 
-  Usuario({
+  Pedido({
     super.id,
     this.folio,
     this.estatus,
     this.comentarios,
     this.peticion,
     this.nombreCliente,
+    this.totalPedido,
+    this.descuento,
     this.idCliente,
     this.idMesa,
     this.terminado,
@@ -30,6 +43,15 @@ class Usuario extends Basico {
     this.idUsuario,
     this.idModificador,
     this.idCancelado,
+    this.tipoPago,
+    this.cantEfectivo,
+    this.cantTarjeta,
+    this.cantTransferencia,
+    this.productos = const [],
+    this.idWeb,
+    this.sincronizado,
+    this.uuid,
+    this.idCorteCaja,
   });
 
   @override
@@ -41,6 +63,8 @@ class Usuario extends Basico {
       'comentarios': comentarios,
       'peticion': peticion,
       'nombreCliente': nombreCliente,
+      'totalPedido': totalPedido,
+      'descuento': descuento,
       'idCliente': idCliente,
       'idMesa': idMesa,
       'terminado': terminado,
@@ -48,22 +72,87 @@ class Usuario extends Basico {
       'idUsuario': idUsuario,
       'idModificador': idModificador,
       'idCancelado': idCancelado,
+      'tipoPago': tipoPago,
+      'cantEfectivo': cantEfectivo,
+      'cantTarjeta': cantTarjeta,
+      'cantTransferencia': cantTransferencia,
+      'sincronizado': sincronizado,
+      'idWeb': idWeb,
+      'uuid': uuid,
+      'idCorteCaja': idCorteCaja,
     }..addAll(super.toJson());
   }
 
-  Usuario.fromJson(Map<String, dynamic> json) {
+  Map<String, dynamic> toApi() {
+    idLocal = id;
+
+    Map<String, dynamic> apiMap = {
+      'idLocal': idLocal,
+      'folio': folio,
+      'estatus': estatus,
+      'comentarios': comentarios,
+      'nombreCliente': nombreCliente,
+      'creado': peticion,
+      'idUsuario': idUsuario,
+      'tipoPago': tipoPago,
+      'totalPedido': totalPedido,
+      'descuento': descuento,
+      'cantEfectivo': cantEfectivo,
+      'cantTarjeta': cantTarjeta,
+      'cantTransferencia': cantTransferencia,
+      'idMesa': idMesa,
+      'uuid': uuid,
+      'idCorteCaja': idCorteCaja,
+      'productos': productos.map((producto) => producto.toApi()).toList(),
+      'propinas': propinas.map((propina) => propina.toApi()).toList(),
+    };
+    Map<String, dynamic> basicoMap = super.toJson();
+    basicoMap.remove('id');
+    basicoMap.remove('eliminado');
+    apiMap.addAll(basicoMap);
+
+    return apiMap;
+  }
+
+  Pedido.fromJson(Map<String, dynamic> json) {
     super.parseJson(json);
+    id = (json['id'] as int?)!;
     folio = Basico.parseInt(json['folio']);
-    estatus = Basico.parseInt(json['estatus']);
+    estatus = Basico.parseString(json['estatus']);
     comentarios = Basico.parseString(json['comentarios']);
     peticion = Basico.parseString(json['peticion']);
     nombreCliente = Basico.parseString(json['nombreCliente']);
+    totalPedido = Basico.parseDouble(json['totalPedido']);
+    descuento = Basico.parseInt(json['descuento']);
     idCliente = Basico.parseInt(json['idCliente']);
     idMesa = Basico.parseInt(json['idMesa']);
-    terminado = Basico.parseInt(json['terminado']);
+    terminado = Basico.parseString(json['terminado']);
     cerrar = Basico.parseInt(json['cerrar']);
     idUsuario = Basico.parseInt(json['idUsuario']);
     idModificador = Basico.parseInt(json['idModificador']);
     idCancelado = Basico.parseInt(json['idCancelado']);
+    tipoPago = Basico.parseString(json['tipoPago']);
+    cantEfectivo = Basico.parseDouble(json['cantEfectivo']);
+    cantTarjeta = Basico.parseDouble(json['cantTarjeta']);
+    cantTransferencia = Basico.parseDouble(json['cantTransferencia']);
+    uuid = Basico.parseString(json['uuid']);
+    idWeb = Basico.parseInt(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
+    idCorteCaja = Basico.parseString(json['idCorteCaja']);
+
+    List<PedidoProducto> _productos = [];
+    if (json["productos"] != null && (json["productos"] as List).isNotEmpty) {
+      for (var i in (json["productos"] as List)) {
+        PedidoProducto m = PedidoProducto.fromJson(i);
+        _productos.add(m);
+      }
+    }
+    productos = _productos;
+
+    if (json["propinas"] != null && (json["propinas"] as List).isNotEmpty) {
+      propinas = (json["propinas"] as List)
+          .map((item) => Propinas.fromJson(item))
+          .toList();
+    }
   }
 }

+ 81 - 0
lib/models/pedido_producto_model.dart

@@ -0,0 +1,81 @@
+import 'models.dart';
+
+class PedidoProducto extends Basico {
+  int? idPedido;
+  int? idProducto;
+  Producto? producto;
+  String? costoUnitario;
+  String? descuento;
+  int? cantidad;
+  int? terminar;
+  String? comentario;
+  List<PedidoProductoTopping> toppings = [];
+  int? idWeb;
+  String? sincronizado;
+
+  PedidoProducto({
+    super.id,
+    this.idPedido,
+    this.idProducto,
+    this.producto,
+    this.costoUnitario,
+    this.descuento,
+    this.cantidad,
+    this.terminar,
+    this.comentario,
+    this.toppings = const [],
+    this.idWeb,
+    this.sincronizado,
+    super.eliminado,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idPedido': idPedido,
+      'idProducto': idProducto,
+      'costoUnitario': costoUnitario,
+      'descuento': descuento,
+      'cantidad': cantidad,
+      'terminar': terminar,
+      'comentario': comentario,
+      'idWeb': idWeb,
+      'sincronizado': sincronizado,
+      'eliminado': eliminado?.toIso8601String(),
+    }..addAll(super.toJson());
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'idLocal': idProducto,
+      'nombre': producto?.nombre ?? '',
+      'costoUnitario': costoUnitario,
+      'descuento': descuento,
+      'cantidad': cantidad,
+      'comentario': comentario,
+      'eliminado': eliminado,
+      'toppings': toppings.map((topping) => topping.toApi()).toList(),
+    };
+  }
+
+  PedidoProducto.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idPedido = Basico.parseInt(json['idPedido']);
+    idProducto = Basico.parseInt(json['idProducto']);
+    producto =
+        json['producto'] != null ? Producto.fromJson(json['producto']) : null;
+    costoUnitario = Basico.parseString(json['costoUnitario']);
+    descuento = Basico.parseString(json['descuento']);
+    cantidad = Basico.parseInt(json['cantidad']);
+    terminar = Basico.parseInt(json['terminar']);
+    comentario = Basico.parseString(json['comentario']);
+    idWeb = Basico.parseInt(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
+    toppings = (json['toppings'] as List<dynamic>?)
+            ?.map((topingJson) => PedidoProductoTopping.fromJson(
+                topingJson as Map<String, dynamic>))
+            .toList() ??
+        [];
+  }
+}

+ 43 - 0
lib/models/pedido_producto_toping_model.dart

@@ -0,0 +1,43 @@
+import '/models/producto_model.dart';
+import 'basico_model.dart';
+
+class PedidoProductoTopping extends Basico {
+  int? idPedidoProducto;
+  int? idTopping;
+  int? idCategoria;
+  Producto? topping;
+
+  PedidoProductoTopping({
+    super.id,
+    this.idPedidoProducto,
+    this.idTopping,
+    this.idCategoria,
+    this.topping,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idPedidoProducto': idPedidoProducto,
+      'idTopping': idTopping,
+      'idCategoria': idCategoria,
+    }..addAll(super.toJson());
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'idTopping': idTopping,
+      'idCategoria': idCategoria,
+      'nombreTopping': topping?.nombre ?? '',
+      'precio': topping?.precio ?? 0.0,
+    };
+  }
+
+  PedidoProductoTopping.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idPedidoProducto = Basico.parseInt(json['idPedidoProducto']);
+    idTopping = Basico.parseInt(json['idTopping']);
+    idCategoria = Basico.parseInt(json['idCategoria']);
+  }
+}

+ 75 - 0
lib/models/permiso_model.dart

@@ -0,0 +1,75 @@
+import '/models/models.dart';
+
+import '../services/services.dart';
+import 'basico_model.dart';
+
+class Permiso {
+  String? id;
+  String? idModulo;
+  String? nombre;
+  String? descripcion;
+  DateTime? eliminado;
+  DateTime? creado;
+  DateTime? modificado;
+
+  Permiso(
+      {this.id,
+      this.idModulo,
+      this.nombre,
+      this.descripcion,
+      this.eliminado,
+      this.creado,
+      this.modificado});
+
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idModulo': idModulo ?? '',
+      'nombre': nombre ?? '',
+      'descripcion': descripcion ?? '',
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  Map<String, dynamic> toMap() {
+    return {
+      'id': id,
+      'idModulo': idModulo,
+      'nombre': nombre,
+      'descripcion': descripcion,
+      'creado': creado != null ? creado!.toIso8601String() : null,
+      'modificado': modificado != null ? modificado!.toIso8601String() : null,
+      'eliminado': eliminado != null ? eliminado!.toIso8601String() : null,
+    };
+  }
+
+  Permiso.fromJson(Map<String, dynamic> json) {
+    id = Basico.parseString(json['id']);
+    idModulo = Basico.parseString(json['idModulo']);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    creado = json['creado'] != null ? Basico.parseDate(json['creado']) : creado;
+    eliminado = json['eliminado'] != null
+        ? Basico.parseDate(json['eliminado'])
+        : eliminado;
+    modificado = json['modificado'] != null
+        ? Basico.parseDate(json['modificado'])
+        : modificado;
+  }
+
+  Permiso.fromApi(Map<String, dynamic> json) {
+    id = Basico.parseString(json['id']);
+    idModulo = Basico.parseString(json['idModulo']);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
+  Future<void> guardar() async {
+    await RepoService().guardar(this);
+  }
+}

+ 155 - 0
lib/models/producto_model.dart

@@ -0,0 +1,155 @@
+import '/models/models.dart';
+
+import '../services/services.dart';
+import 'basico_model.dart';
+
+class Producto extends Basico {
+  int? idCategoria;
+  CategoriaProducto? categoria;
+  String? nombre;
+  String? descripcion;
+  String? imagen;
+  int? venta;
+  int? existencia;
+  double? precio;
+  int? verMenu;
+  String? codigo;
+  String? descuento;
+  int? toping;
+  List<Producto>? topings;
+  int? activo;
+  List<Media>? media;
+  int? idWeb;
+  String? sincronizado;
+
+  Producto({
+    super.id,
+    this.idCategoria,
+    this.nombre,
+    this.descripcion,
+    this.imagen,
+    this.venta,
+    this.existencia,
+    this.precio,
+    this.verMenu,
+    this.codigo,
+    this.descuento,
+    this.toping,
+    this.topings,
+    this.activo,
+    this.media,
+    this.idWeb,
+    this.sincronizado,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    // print("Convirtiendo Producto a JSON");
+    // print("ID: $id, Categoria: $idCategoria, Nombre: $nombre");
+    // print("Descripcion: $descripcion, imagen: $imagen, venta: $venta");
+    // print("existencia: $existencia, precio: $precio, verMenu: $verMenu");
+    // print("codigo: $codigo, descuento: $descuento, creado: $creado");
+    // print("eliminado: $eliminado, modificado: $modificado");
+
+    Map<String, dynamic> data = {
+      'id': id,
+      'idCategoria': idCategoria ?? 0,
+      'nombre': nombre ?? '',
+      'descripcion': descripcion ?? '',
+      'imagen': imagen ?? '',
+      'venta': venta ?? 0,
+      'existencia': existencia ?? 0,
+      'precio': precio ?? 0.0,
+      'verMenu': verMenu ?? 0,
+      'codigo': codigo ?? '',
+      'descuento': descuento ?? '',
+      'toping': toping ?? 0,
+      'activo': activo ?? 0,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+      'sincronizado': sincronizado,
+      'idWeb': idWeb,
+    };
+
+    return data..addAll(super.toJson());
+  }
+
+  Map<String, dynamic> toMap() {
+    return {
+      'id': id,
+      'idCategoria': idCategoria,
+      'nombre': nombre,
+      'descripcion': descripcion,
+      'imagen': imagen,
+      'venta': venta,
+      'existencia': existencia,
+      'precio': precio,
+      'verMenu': verMenu,
+      'codigo': codigo,
+      'descuento': descuento,
+      'toping': toping,
+      'activo': activo,
+      'creado': creado != null ? creado!.toIso8601String() : null,
+      'modificado': modificado != null ? modificado!.toIso8601String() : null,
+      'eliminado': eliminado != null ? eliminado!.toIso8601String() : null,
+    };
+  }
+
+  Producto.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idCategoria = Basico.parseInt(json['idCategoria']);
+    // categoria = json["categoria"] != null
+    //     ? CategoriaProducto.fromJson(json["categoria"])
+    //     : null;
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    imagen = Basico.parseString(json['imagen']);
+    venta = Basico.parseInt(json['venta']);
+    existencia = Basico.parseInt(json['existencia']);
+    precio = Basico.parseDouble(json['precio']);
+    verMenu = Basico.parseInt(json['verMenu']);
+    codigo = Basico.parseString(json['codigo']);
+    descuento = Basico.parseString(json['descuento']);
+    activo = Basico.parseInt(json['activo']);
+    idWeb = Basico.parseInt(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
+    if (json['toping'] is bool) {
+      toping = json['toping'] ? 1 : 0;
+    } else {
+      toping = Basico.parseInt(json['toping']);
+    }
+    if (json['toppings'] != null) {
+      topings = (json['toppings'] as List)
+          .map((id) => Producto(id: id as int))
+          .toList();
+    }
+  }
+
+  Producto.fromApi(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idCategoria = Basico.parseInt(json['idCategoria']);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    imagen = Basico.parseString(json['imagen']);
+    venta = Basico.parseInt(json['venta']);
+    existencia = Basico.parseInt(json['existencia']);
+    precio = Basico.parseDouble(json['precio']);
+    verMenu = Basico.parseInt(json['verEnMenu']);
+    codigo = Basico.parseString(json['codigo']);
+    descuento = Basico.parseString(json['descuento']);
+    activo = Basico.parseInt(json['activo']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+    idWeb = Basico.parseInt(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
+    if (json['media'] != null) {
+      media = (json['media'] as List).map((i) => Media.fromJson(i)).toList();
+    }
+  }
+
+  Future<void> guardar() async {
+    idLocal = await RepoService().guardar(this);
+  }
+}

+ 44 - 0
lib/models/producto_topping_model.dart

@@ -0,0 +1,44 @@
+import 'basico_model.dart';
+
+class ProductoTopping extends Basico {
+  int? idProducto;
+  int? idTopping;
+  int? idCategoria;
+
+  ProductoTopping({
+    super.id,
+    this.idProducto,
+    this.idTopping,
+    this.idCategoria,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idProducto': idProducto,
+      'idTopping': idTopping,
+      'idCategoria': idCategoria,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    }..addAll(super.toJson());
+  }
+
+  ProductoTopping.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idProducto = Basico.parseInt(json['idProducto']);
+    idTopping = Basico.parseInt(json['idTopping']);
+    idCategoria = Basico.parseInt(json['idCategoria']);
+  }
+
+  ProductoTopping.fromApi(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idProducto = Basico.parseInt(json['idProducto']);
+    idTopping = Basico.parseInt(json['idProductoTopping']);
+    idCategoria = Basico.parseInt(json['idCategoriaTopping']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+}

+ 54 - 0
lib/models/propina_model.dart

@@ -0,0 +1,54 @@
+import 'models.dart';
+
+class Propinas extends Basico {
+  int? idPedido;
+  double? cantidad;
+  String? comentario;
+  String? sincronizado;
+  int? idWeb;
+
+  Propinas({
+    super.id,
+    this.idPedido,
+    this.cantidad,
+    this.comentario,
+    this.sincronizado,
+    this.idWeb,
+    super.eliminado,
+    super.creado,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idPedido': idPedido,
+      'cantidad': cantidad,
+      'comentario': comentario,
+      'sincronizado': sincronizado,
+      'creado': creado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+      'idWeb': idWeb,
+    }..addAll(super.toJson());
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'idPedido': idPedido,
+      'cantidad': cantidad,
+      'comentario': comentario,
+      'sincronizado': sincronizado,
+      'creado': creado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  Propinas.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idPedido = Basico.parseInt(json['idPedido']);
+    cantidad = Basico.parseDouble(json['cantidad']);
+    comentario = Basico.parseString(json['comentario']);
+    idWeb = Basico.parseInt(json['idWeb']);
+    sincronizado = Basico.parseString(json['sincronizado']);
+  }
+}

+ 80 - 0
lib/models/retiro_model.dart

@@ -0,0 +1,80 @@
+import 'basico_model.dart';
+import '../services/services.dart';
+
+class Retiro {
+  String? id;
+  String? idCorteCaja;
+  int? idSucursal;
+  int? idUsuario;
+  DateTime? fechaRetiro;
+  double? monto;
+  String? persona;
+  String? descripcion;
+  DateTime? creado;
+  DateTime? modificado;
+  DateTime? eliminado;
+
+  Retiro({
+    this.id,
+    this.idCorteCaja,
+    this.idSucursal,
+    this.idUsuario,
+    this.fechaRetiro,
+    this.monto,
+    this.persona,
+    this.descripcion,
+    this.creado,
+    this.modificado,
+    this.eliminado,
+  });
+
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idCorteCaja': idCorteCaja ?? 0,
+      'idSucursal': idSucursal ?? 0,
+      'idUsuario': idUsuario ?? 0,
+      'fechaRetiro': fechaRetiro?.toIso8601String(),
+      'monto': monto ?? 0.0,
+      'persona': persona ?? '',
+      'descripcion': descripcion ?? '',
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    };
+  }
+
+  Map<String, dynamic> toApi() {
+    return {
+      'id': id,
+      'idCorteCaja': idCorteCaja,
+      'idSucursal': idSucursal,
+      'idUsuario': idUsuario,
+      'fechaRetiro': fechaRetiro,
+      'monto': monto,
+      'persona': persona,
+      'descripcion': descripcion,
+      'creado': creado,
+      'modificado': modificado,
+      'eliminado': eliminado,
+    };
+  }
+
+  Retiro.fromJson(Map<String, dynamic> json) {
+    id = Basico.parseString(json['id']);
+    idCorteCaja = Basico.parseString(json['idCorteCaja']);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    idUsuario = Basico.parseInt(json['idUsuario']);
+    fechaRetiro = Basico.parseDate(json['fechaRetiro']);
+    monto = Basico.parseDouble(json['monto']);
+    persona = Basico.parseString(json['persona']);
+    descripcion = Basico.parseString(json['descripcion']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
+  Future<void> guardar() async {
+    await RepoService().guardar(this);
+  }
+}

+ 79 - 0
lib/models/sucursal_model.dart

@@ -0,0 +1,79 @@
+import 'basico_model.dart';
+import '../services/services.dart';
+
+class Sucursal extends Basico {
+  String? nombre;
+  String? descripcion;
+  String? direccion;
+  String? ciudad;
+  String? clave;
+  int? activo;
+  int? seleccionado;
+
+  Sucursal({
+    super.id,
+    this.nombre,
+    this.descripcion,
+    this.direccion,
+    this.ciudad,
+    this.clave,
+    this.activo,
+    this.seleccionado,
+  });
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    return other is Sucursal && other.id == id;
+  }
+
+  @override
+  Map<String, dynamic> toJson() {
+    print("Convirtiendo Sucursal a JSON");
+    print(
+        "ID: $id, Nombre: $nombre, Descripción: $descripcion, Dirección: $direccion, Ciudad: $ciudad, Activo: $activo, Clave: $clave");
+    print("Creado: $creado, Modificado: $modificado, Eliminado: $eliminado");
+
+    return {
+      'id': id,
+      'nombre': nombre ?? '',
+      'descripcion': descripcion ?? '',
+      'direccion': direccion ?? '',
+      'ciudad': ciudad ?? '',
+      'clave': clave ?? '',
+      'activo': activo ?? 0,
+      'seleccionado': seleccionado ?? 0,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    }..addAll(super.toJson());
+  }
+
+  Sucursal.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    direccion = Basico.parseString(json['direccion']);
+    ciudad = Basico.parseString(json['ciudad']);
+    activo = Basico.parseInt(json['activo']);
+    seleccionado = Basico.parseInt(json['seleccionado']);
+    clave = Basico.parseString(json['clave']);
+  }
+
+  Sucursal.fromApi(Map<String, dynamic> json) {
+    super.parseJson(json);
+    nombre = Basico.parseString(json['nombre']);
+    descripcion = Basico.parseString(json['descripcion']);
+    direccion = Basico.parseString(json['direccion']);
+    ciudad = Basico.parseString(json['ciudad']);
+    activo = json['activo'] == 1 ? 1 : 0;
+    clave = Basico.parseString(json['clave']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
+  Future<void> guardar() async {
+    idLocal = await RepoService().guardar(this);
+  }
+}

+ 52 - 0
lib/models/toping_categoria_model.dart

@@ -0,0 +1,52 @@
+import '/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']);
+  }
+}

+ 55 - 0
lib/models/toping_model.dart

@@ -0,0 +1,55 @@
+import '/models/models.dart';
+import 'basico_model.dart';
+
+class Toping extends Basico {
+  int? idCategoria;
+  String? clave;
+  String? nombre;
+  String? descripcion;
+  String? costo;
+  bool? activo = false;
+  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']);
+    activo = Basico.parseBolean(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();
+    }
+  }
+}

+ 148 - 18
lib/models/usuario_model.dart

@@ -1,47 +1,177 @@
+import '../models/models.dart';
+import '../services/repo_service.dart';
 import 'basico_model.dart';
 
 class Usuario extends Basico {
+  String? nombre;
+  String? apellidos;
   String? correo;
+  String? celular;
+  String? celularPersonal;
+  int? rol;
+  bool? genero;
+  int? estatus;
+  String? imagen;
+  String? rfc;
+  String? razonSocial;
+  String? calle;
+  String? numeroExterior;
+  String? colonia;
+  String? codigoPostal;
+  int? idCiudad;
+  int? idEstado;
+  int? idSucursal;
+  String? turno;
   String? clave;
-  String? nombre;
-  String? estatus;
-  String? telefono;
-  String? empresa;
-  String? rol;
+  String? token;
+  List<String>? permisos;
+  static const VER_CATEGORIAS = 'VER_CATEGORIAS';
+  static const VER_DESC_APP = 'VER_DESC_APP';
+  static const VER_SUCURSALES = 'VER_SUCURSALES';
+  static const VER_ADMIN = 'VER_ADMIN';
+  static const CANCELAR_PEDIDO = 'CANCELAR_PEDIDO';
+  static const VER_REPORTE = 'VER_REPORTE';
+  static const FORZAR_SINCRONIZACION = 'FORZAR_SINCRONIZACION';
 
   Usuario({
     super.id,
-    this.correo,
-    this.clave,
     this.nombre,
-    this.estatus,
-    this.telefono,
-    this.empresa,
+    this.apellidos,
+    this.correo,
+    this.celular,
+    this.celularPersonal,
     this.rol,
+    this.genero,
+    this.estatus,
+    this.imagen,
+    this.rfc,
+    this.razonSocial,
+    this.calle,
+    this.numeroExterior,
+    this.colonia,
+    this.codigoPostal,
+    this.idCiudad,
+    this.idEstado,
+    this.idSucursal,
+    this.turno,
+    this.clave,
+    this.token,
+    this.permisos,
   });
 
   @override
   Map<String, dynamic> toJson() {
     return {
       'id': id,
+      'nombre': nombre,
+      'apellidos': apellidos,
       'correo': correo,
+      'celular': celular,
+      'celularPersonal': celularPersonal,
+      'rol': rol,
+      'genero': genero != null ? (genero! ? 1 : 0) : null,
+      'estatus': estatus,
+      'imagen': imagen,
+      'rfc': rfc,
+      'razonSocial': razonSocial,
+      'calle': calle,
+      'numeroExterior': numeroExterior,
+      'colonia': colonia,
+      'codigoPostal': codigoPostal,
+      'idCiudad': idCiudad,
+      'idEstado': idEstado,
+      'idSucursal': idSucursal,
+      'turno': turno,
       'clave': clave,
+      'token': token,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    }..addAll(super.toJson());
+  }
+
+  Map<String, dynamic> toMap() {
+    return {
+      'id': id,
       'nombre': nombre,
-      'estatus': estatus,
-      'telefono': telefono,
-      'empresa': empresa,
+      'apellidos': apellidos,
+      'correo': correo,
+      'celular': celular,
+      'celularPersonal': celularPersonal,
       'rol': rol,
-    }..addAll(super.toJson());
+      'genero': genero,
+      'estatus': estatus,
+      'imagen': imagen,
+      'rfc': rfc,
+      'razonSocial': razonSocial,
+      'calle': calle,
+      'numeroExterior': numeroExterior,
+      'colonia': colonia,
+      'codigoPostal': codigoPostal,
+      'idCiudad': idCiudad,
+      'idEstado': idEstado,
+      'idSucursal': idSucursal,
+      'turno': turno,
+      'clave': clave,
+      'token': token,
+      'creado': creado != null ? creado!.toIso8601String() : null,
+      'modificado': modificado != null ? modificado!.toIso8601String() : null,
+      'eliminado': eliminado != null ? eliminado!.toIso8601String() : null,
+    };
   }
 
   Usuario.fromJson(Map<String, dynamic> json) {
     super.parseJson(json);
+    nombre = Basico.parseString(json['nombre']);
+    apellidos = Basico.parseString(json['apellidos']);
     correo = Basico.parseString(json['correo']);
+    celular = Basico.parseString(json['celular']);
+    celularPersonal = Basico.parseString(json['celularPersonal']);
+    rol = Basico.parseInt(json['rol']);
+    genero = Basico.parseBolean(json['genero']);
+    estatus = Basico.parseInt(json['estatus']);
+    imagen = Basico.parseString(json['imagen']);
+    rfc = Basico.parseString(json['rfc']);
+    razonSocial = Basico.parseString(json['razonSocial']);
+    calle = Basico.parseString(json['calle']);
+    numeroExterior = Basico.parseString(json['numeroExterior']);
+    colonia = Basico.parseString(json['colonia']);
+    codigoPostal = Basico.parseString(json['codigoPostal']);
+    idCiudad = Basico.parseInt(json['idCiudad']);
+    idEstado = Basico.parseInt(json['idEstado']);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    turno = Basico.parseString(json['turno']);
     clave = Basico.parseString(json['clave']);
+    token = Basico.parseString(json['token']);
+  }
+
+  Usuario.fromApi(Map<String, dynamic> json) {
+    super.parseJson(json);
     nombre = Basico.parseString(json['nombre']);
-    estatus = Basico.parseString(json['estatus']);
-    telefono = Basico.parseString(json['telefono']);
-    empresa = Basico.parseString(json['empresa']);
-    rol = Basico.parseString(json['rol']);
+    apellidos = Basico.parseString(json['apellidos']);
+    correo = Basico.parseString(json['correo']);
+    celular = Basico.parseString(json['celular']);
+    celularPersonal = Basico.parseString(json['celularPersonal']);
+    rol = Basico.parseInt(json['rol']);
+    genero = Basico.parseBolean(json['genero']);
+    estatus = Basico.parseInt(json['estatus']);
+    imagen = Basico.parseString(json['imagen']);
+    rfc = Basico.parseString(json['rfc']);
+    razonSocial = Basico.parseString(json['razonSocial']);
+    calle = Basico.parseString(json['calle']);
+    numeroExterior = Basico.parseString(json['numeroExterior']);
+    colonia = Basico.parseString(json['colonia']);
+    codigoPostal = Basico.parseString(json['codigoPostal']);
+    idCiudad = Basico.parseInt(json['idCiudad']);
+    idEstado = Basico.parseInt(json['idEstado']);
+    idSucursal = Basico.parseInt(json['idSucursal']);
+    turno = Basico.parseString(json['turno']);
+    clave = Basico.parseString(json['clave']);
+    token = Basico.parseString(json['token']);
+    permisos = (json['permisos'] as List).map((e) => e.toString()).toList();
+  }
+
+  Future<void> guardar() async {
+    idLocal = await RepoService().guardar(this);
   }
 }

+ 55 - 0
lib/models/usuario_permiso_model.dart

@@ -0,0 +1,55 @@
+import '/models/models.dart';
+
+import '../services/services.dart';
+import 'basico_model.dart';
+
+class UsuarioPermiso extends Basico {
+  int? idUsuario;
+  String? idPermiso;
+  DateTime? asignado;
+
+  UsuarioPermiso({super.id, this.idUsuario, this.idPermiso, this.asignado});
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'idUsuario': idUsuario ?? 0,
+      'idPermiso': idPermiso ?? '',
+      'asignado': asignado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
+    }..addAll(super.toJson());
+  }
+
+  Map<String, dynamic> toMap() {
+    return {
+      'id': id,
+      'idUsuario': idUsuario,
+      'idPermiso': idPermiso,
+      'asignado': asignado != null ? asignado!.toIso8601String() : null,
+      'modificado': modificado != null ? modificado!.toIso8601String() : null,
+      'eliminado': eliminado != null ? eliminado!.toIso8601String() : null,
+    };
+  }
+
+  UsuarioPermiso.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idUsuario = Basico.parseInt(json['idUsuario']);
+    idPermiso = Basico.parseString(json['idPermiso']);
+    asignado = Basico.parseDate(json['asignado']);
+  }
+
+  UsuarioPermiso.fromApi(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idUsuario = Basico.parseInt(json['idUsuario']);
+    idPermiso = Basico.parseString(json['idPermiso']);
+    asignado = Basico.parseDate(json['asignado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
+  }
+
+  Future<void> guardar() async {
+    idLocal = await RepoService().guardar(this);
+  }
+}

+ 40 - 0
lib/models/variable_model.dart

@@ -0,0 +1,40 @@
+import 'basico_model.dart';
+import '../services/services.dart';
+
+class Variable extends Basico {
+  String? nombre;
+  String? clave;
+  String? descripcion;
+  bool? activo;
+
+  Variable({
+    super.id,
+    this.nombre,
+    this.clave,
+    this.descripcion,
+    this.activo = true,
+  });
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'id': id,
+      'nombre': nombre,
+      'clave': clave,
+      'descripcion': descripcion,
+      'activo': activo == true ? 1 : 0,
+    }..addAll(super.toJson());
+  }
+
+  Variable.fromJson(Map<String, dynamic> json) {
+    super.parseJson(json);
+    nombre = Basico.parseString(json['nombre']);
+    clave = Basico.parseString(json['clave']);
+    descripcion = Basico.parseString(json['descripcion']);
+    activo = Basico.parseInt(json['activo']) == 1;
+  }
+
+  Future<void> guardar() async {
+    idLocal = await RepoService().guardar(this);
+  }
+}

+ 13 - 4
lib/services/base_service.dart

@@ -8,11 +8,11 @@ class BaseService {
   int limit = 20;
   int total = 0;
   //String baseUrl = 'hermogas.est.api.rdsistemas.app';
-  //produccion: hermogas.est.api.rdsistemas.app
-  //prueba: hermogas.est.test.rdsistemas.app
+  //produccion: joshipapas.api.edesarrollos.info
+  //prueba: pos.test.turquessacoffee.com
 
-  String base_url = 'https://pos.papas.edesarrollos.info';
-  String baseUrl = 'pos.papas.edesarrollos.info';
+  String base_url = 'https://pos.api.turquessacoffee.com/';
+  String baseUrl = 'pos.api.turquessacoffee.com';
   Future<Map<String, String>> getDefaultHeaders({withAuth = true}) async {
     Map<String, String> defaultHeaders = {'Content-Type': 'application/json'};
 
@@ -55,4 +55,13 @@ class BaseService {
     var head = {...?headers, ...defaultHeaders};
     return await http.delete(uri, body: json.encode(body), headers: head);
   }
+
+  String prefijoVersion() {
+    if (base_url.contains('api')) {
+      return 'vP';
+    } else if (base_url.contains('test')) {
+      return 'vT';
+    }
+    return 'v'; // Versión genérica por si no coincide con api ni test
+  }
 }

+ 77 - 0
lib/services/categoria_producto_service.dart

@@ -0,0 +1,77 @@
+import 'dart:convert';
+
+import '/data/api_response.dart';
+import '/models/models.dart';
+
+import '../services/base_service.dart';
+
+class CategoriaProductoService extends BaseService {
+  String endPoint = '/admin/categoria';
+  //Consulta de la lista de Sala
+  Map<String, String> defaultQueryParameters = {
+    "limite": "10",
+    "ordenar": "id-desc",
+    "pagina": "1"
+  };
+  Future<List<CategoriaProducto>> fetchList(
+      {String? q, bool segmentar = false}) async {
+    if (q != null) {
+      defaultQueryParameters['q'] = q.toString();
+    }
+    if (segmentar) {
+      defaultQueryParameters['segmentar'] = "1";
+    }
+    final response =
+        await get(endPoint, queryParameters: defaultQueryParameters);
+
+    final categoriaProductoJson = jsonDecode(response.body);
+
+    List<CategoriaProducto> categoriaProductos = [];
+
+    for (var x in categoriaProductoJson['resultado']) {
+      CategoriaProducto categoriaProducto = CategoriaProducto.fromJson(x);
+      categoriaProductos.add(categoriaProducto);
+    }
+
+    return categoriaProductos;
+  }
+
+  Future<ApiResponse> postCategoriaProducto({
+    required String nombre,
+  }) async {
+    var response = await post(
+      '$endPoint/guardar',
+      body: {
+        'nombre': nombre,
+      },
+      withAuth: true,
+    );
+    return ApiResponse(response);
+  }
+
+  Future<ApiResponse> editCategoriaProducto({
+    required int id,
+    required String nombre,
+  }) async {
+    var response = await post(
+      endPoint,
+      body: {
+        'id': id,
+        'nombre': nombre,
+      },
+      withAuth: true,
+    );
+    return ApiResponse(response);
+  }
+
+  Future<ApiResponse> deleteCategoriaProducto({required int id}) async {
+    var response = await delete(
+      '$endPoint/eliminar',
+      body: {
+        'id': id,
+      },
+      withAuth: true,
+    );
+    return ApiResponse(response);
+  }
+}

+ 13 - 9
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 completo = "$base_url/admin/media/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";

+ 11 - 12
lib/services/login_service.dart

@@ -1,17 +1,16 @@
-import '../data/api_response.dart';
+import '../models/models.dart';
+import '../services/repo_service.dart';
 
-import 'base_service.dart';
-import '../models/login_model.dart';
+class LoginService {
+  final RepoService<Usuario> _usuarioRepo = RepoService<Usuario>();
 
-class LoginService extends BaseService {
-  final endPoint = "/admin/iniciar-sesion";
+  Future<Usuario?> logIn(String email) async {
+    List<Usuario> usuarios = await _usuarioRepo.obtenerTodos();
+    Usuario? usuario = usuarios.firstWhere(
+      (user) => user.correo?.toLowerCase() == email.toLowerCase(),
+      orElse: () => null as Usuario,
+    );
 
-  Future<ApiResponse> logIn(String username, String password) async {
-    final login = Login(username: username, password: password);
-    var response = await post(endPoint,
-        withAuth: false,
-        body: login.toJson(),
-        headers: {'Content-Type': 'application/json'});
-    return ApiResponse(response);
+    return usuario;
   }
 }

+ 96 - 0
lib/services/producto_service.dart

@@ -0,0 +1,96 @@
+import 'package:sqflite/sqflite.dart';
+import '/data/api_response.dart';
+import '/models/models.dart';
+
+import '../services/base_service.dart';
+
+class ProductoService extends BaseService {
+  String endPoint = '/admin/categoria';
+  //Consulta de la lista de Sala
+  Map<String, String> defaultQueryParameters = {
+    "limite": "10",
+    "ordenar": "id-desc",
+    "pagina": "1"
+  };
+  Future<List<Producto>> fetchList() async {
+    final response =
+        await get(endPoint, queryParameters: defaultQueryParameters);
+
+    final respuesta = ApiResponse(response);
+
+    List<Producto> productos = [];
+
+    if (respuesta.isOk && respuesta.resultados!.isNotEmpty) {
+      for (var x in respuesta.resultados!) {
+        final producto = Producto.fromJson(x);
+
+        productos.add(producto);
+      }
+    }
+
+    return productos;
+  }
+
+  Future<ApiResponse> postProducto({
+    required String nombre,
+    required String descripcion,
+    String? descuento,
+    required int existencia,
+    required int idCategoria,
+    required String precio,
+    String? venceDescuento,
+  }) async {
+    var response = await post(
+      '$endPoint/guardar',
+      body: {
+        'descripcion': descripcion,
+        'nombre': nombre,
+        'descuento': descuento,
+        'existencia': existencia,
+        'idCategoria': idCategoria,
+        'precio': precio,
+        'venceDescuento': venceDescuento,
+      },
+      withAuth: true,
+    );
+    return ApiResponse(response);
+  }
+
+  Future<ApiResponse> editProducto({
+    required int id,
+    required String nombre,
+    required String descripcion,
+    String? descuento,
+    required int existencia,
+    required int idCategoria,
+    required String precio,
+    String? venceDescuento,
+  }) async {
+    var response = await post(
+      endPoint,
+      body: {
+        'id': id,
+        'descripcion': descripcion,
+        'nombre': nombre,
+        'descuento': descuento,
+        'existencia': existencia,
+        'idCategoria': idCategoria,
+        'precio': precio,
+        'venceDescuento': venceDescuento,
+      },
+      withAuth: true,
+    );
+    return ApiResponse(response);
+  }
+
+  Future<ApiResponse> deleteProducto({required int id}) async {
+    var response = await delete(
+      '$endPoint/eliminar',
+      body: {
+        'id': id,
+      },
+      withAuth: true,
+    );
+    return ApiResponse(response);
+  }
+}

+ 63 - 0
lib/services/productos_service.dart

@@ -0,0 +1,63 @@
+import 'package:sqflite/sqflite.dart';
+import '/services/repo_service.dart';
+
+import '../models/models.dart';
+
+class ProductosService {
+  Future<bool> isDatabaseEmpty() async {
+    final dbClient = await RepoService().db;
+    final categoriaCount = Sqflite.firstIntValue(
+      await dbClient!.rawQuery('SELECT COUNT(*) FROM CategoriaProducto'),
+    );
+    final productoCount = Sqflite.firstIntValue(
+      await dbClient.rawQuery('SELECT COUNT(*) FROM Producto'),
+    );
+    print('Categoria Count: $categoriaCount, Producto Count: $productoCount');
+    return categoriaCount == 0 && productoCount == 0;
+  }
+
+  Future<void> fillCategoriaBD() async {
+    // final List<CategoriaProducto> categorias = [
+    //   CategoriaProducto(id: 1, nombre: 'COMIDAS'),
+    //   CategoriaProducto(id: 2, nombre: 'BEBIDAS'),
+    // ];
+
+    // RepoService<CategoriaProducto> repoService =
+    //     RepoService<CategoriaProducto>();
+    // for (var categoria in categorias) {
+    //   await repoService.guardarLocal(categoria);
+    // }
+  }
+
+  Future<void> fillProductoBD() async {
+    // List<Producto> productos = [
+    //   Producto(id: 1, idCategoria: 1, nombre: 'CONO DE PAPAS', precio: '100'),
+    //   Producto(
+    //       id: 2,
+    //       idCategoria: 1,
+    //       nombre: 'CONO DE BONELESS (250 GR)',
+    //       precio: '150'),
+    //   Producto(
+    //       id: 3,
+    //       idCategoria: 1,
+    //       nombre: 'CONO DE PAPAS + BONELESS (CURLY, GAJO O FRANCESA)',
+    //       precio: '180'),
+    //   Producto(id: 4, idCategoria: 1, nombre: 'PIZZARINO', precio: '50'),
+    //   Producto(
+    //       id: 5,
+    //       idCategoria: 1,
+    //       nombre: 'CASCO DE BASEBALL CON PAPAS FRANCESAS',
+    //       precio: '120'),
+    //   Producto(
+    //       id: 6,
+    //       idCategoria: 1,
+    //       nombre: 'EXTRA 2 DEDOS DE QUESOS Y 2 AROS DE CEBOLLA',
+    //       precio: '50'),
+    // ];
+
+    // RepoService<Producto> repoService = RepoService<Producto>();
+    // for (var producto in productos) {
+    //   await repoService.guardarLocal(producto);
+    // }
+  }
+}

+ 1 - 1
lib/services/profile_service.dart

@@ -1,6 +1,6 @@
 import 'dart:convert';
 import 'package:http/http.dart' as http;
-import 'package:yoshi_papas_app/data/session/session_storage.dart';
+import '/data/session/session_storage.dart';
 import '../models/models.dart';
 import '../services/base_service.dart';
 

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1707 - 0
lib/services/repo_service.dart


+ 2 - 0
lib/services/services.dart

@@ -2,3 +2,5 @@ export '../services/base_service.dart';
 export '../services/login_service.dart';
 export '../services/profile_service.dart';
 export '../services/general_service.dart';
+export '../services/categoria_producto_service.dart';
+export '../services/repo_service.dart';

+ 6 - 3
lib/themes/themes.dart

@@ -2,13 +2,16 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
 class AppTheme {
-  static Color primary = Color.fromRGBO(242, 75, 89, 1.000);
+  static Color primary = Color(0xFF32D7ED);
   static Color secondary = Colors.black;
-  static Color tertiary = const Color(0xFF060000);
+  static Color tertiary = const Color(0xFF242424);
+  static Color quaternary = const Color(0xFFF1F1F3);
+  static Color verde = const Color(0xff248f83);
+  static Color rojo = const Color(0xFFF24B59);
   static ThemeData lightTheme = ThemeData.light().copyWith(
     useMaterial3: true,
     //Scaffold
-    scaffoldBackgroundColor: Colors.grey[300],
+    scaffoldBackgroundColor: Color(0xFFE0E0E0),
     //Tema de AppBar
     appBarTheme: AppBarTheme(
       color: primary,

+ 140 - 0
lib/viewmodels/categoria_producto_view_model.dart

@@ -0,0 +1,140 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import '../services/services.dart';
+
+import '../models/models.dart';
+
+class CategoriaProductoViewModel extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<CategoriaProducto> _categoriaProductos = [];
+  bool _isLoading = false;
+  CategoriaProducto? _selectedCategoriaProducto;
+  Map<int, String> _categoriaMap = {};
+
+  List<CategoriaProducto> get categoriaProductos => _categoriaProductos;
+  CategoriaProducto? get selectedCategoriaProducto =>
+      _selectedCategoriaProducto;
+  bool get isLoading => _isLoading;
+
+  Map<int, String> get categoriaMap => _categoriaMap;
+
+  int _currentPage = 1;
+  int _totalProducts = 0;
+  int _limit = 20;
+
+  int get currentPage => _currentPage;
+  int get totalProducts => _totalProducts;
+  int get totalPages => (_totalProducts / _limit).ceil();
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  void selectCategoriaProducto(CategoriaProducto categoriaProducto) {
+    _selectedCategoriaProducto = categoriaProducto;
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalAll({int page = 1}) async {
+    _currentPage = page;
+    var db = await RepoService().db;
+
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM CategoriaProducto'));
+    _totalProducts = count ?? 0;
+
+    int offset = (_limit * (page - 1));
+
+    var query = await db.query('CategoriaProducto',
+        where: 'eliminado IS NULL',
+        orderBy: 'id asc',
+        limit: _limit,
+        offset: offset);
+    _categoriaProductos =
+        query.map((element) => CategoriaProducto.fromJson(element)).toList();
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalCategoria() async {
+    var db = await RepoService().db;
+    var query = await db!.query('CategoriaProducto', orderBy: 'idLocal asc');
+    List<CategoriaProducto> aux = [];
+    _categoriaMap = {};
+    for (var element in query) {
+      CategoriaProducto categoria = CategoriaProducto.fromJson(element);
+      aux.add(categoria);
+      _categoriaMap[categoria.id!] = categoria.nombre!;
+    }
+    _categoriaProductos = aux;
+    notifyListeners();
+  }
+
+  Future<List<CategoriaProducto>> getCategoriaProducto({String q = ''}) async {
+    var db = await RepoService().db;
+    List<Map> results = await db!.query('CategoriaProducto',
+        where: 'nombre LIKE ?', whereArgs: ['%$q%'], orderBy: 'nombre ASC');
+    return results
+        .map(
+            (map) => CategoriaProducto.fromJson(Map<String, dynamic>.from(map)))
+        .toList();
+  }
+
+  Future<void> addCategoriaProducto(CategoriaProducto categoriaProducto) async {
+    await RepoService().guardar(categoriaProducto);
+    fetchLocalAll();
+  }
+
+  Future<void> fetchLocalByName({required String nombre}) async {
+    var db = await RepoService().db;
+    var query = await db!.query(
+      'CategoriaProducto',
+      where: 'nombre LIKE "%$nombre%"',
+      orderBy: 'idLocal asc',
+    );
+    List<CategoriaProducto> aux = [];
+    for (var element in query) {
+      CategoriaProducto categoriaProducto = CategoriaProducto.fromJson(element);
+      aux.add(categoriaProducto);
+    }
+    _categoriaProductos = aux;
+    notifyListeners();
+  }
+
+  Future<void> updateCategoriaProducto(
+      CategoriaProducto categoriaProducto) async {
+    setIsLoading(true);
+    try {
+      int result = await RepoService().guardar(categoriaProducto);
+      print("Update result: $result");
+      fetchLocalAll();
+    } catch (e) {
+      print('Error updating product: $e');
+    }
+    setIsLoading(false);
+  }
+
+  Future<void> deleteCategoriaProducto(int id) async {
+    await RepoService().eliminar<CategoriaProducto>(id);
+    fetchLocalAll();
+  }
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchLocalAll(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchLocalAll(page: _currentPage - 1);
+    }
+  }
+}

+ 588 - 0
lib/viewmodels/corte_caja_view_model.dart

@@ -0,0 +1,588 @@
+import 'package:collection/collection.dart';
+
+import '../../widgets/widgets.dart';
+import 'package:flutter/material.dart';
+import 'package:uuid/uuid.dart';
+import '../data/api_response.dart';
+import '../models/models.dart';
+import '../services/services.dart';
+import '../views/corte_caja/corte_caja_ticket.dart';
+import 'dart:convert';
+import 'package:http/http.dart' as http;
+
+class CorteCajaViewModel extends ChangeNotifier {
+  List<CorteCaja> _cortes = [];
+  List<CorteCaja> get cortes => _cortes;
+
+  bool _isLoading = false;
+  bool get isLoading => _isLoading;
+  CorteCaja? _selectedCorte;
+
+  double corteFinal = 0.0;
+  double totalDepositos = 0.0;
+  double totalRetiros = 0.0;
+  double totalGastos = 0.0;
+  List<Deposito> depositos = [];
+  List<Retiro> retiros = [];
+  List<Gasto> gastos = [];
+  double _ventaEfe = 0.0;
+  double _ventaTarj = 0.0;
+  double _ventaTransf = 0.0;
+  double _totalVenta = 0.0;
+
+  CorteCaja? get selectedCorte => _selectedCorte;
+
+  int _currentPage = 1;
+  int _totalCortes = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalPedidos => _totalCortes;
+  int get totalPages => (_totalCortes / _limit).ceil();
+
+  double get ventaEfe => _ventaEfe;
+  double get ventaTarj => _ventaTarj;
+  double get ventaTransf => _ventaTransf;
+  double get totalVenta => _totalVenta;
+  double get salidasTotales => totalRetiros + totalGastos;
+
+  final TextEditingController _ventaEfeController = TextEditingController();
+  final TextEditingController _ventaTarjController = TextEditingController();
+  final TextEditingController _ventaTransfController = TextEditingController();
+  final TextEditingController _totalVentaController = TextEditingController();
+  final TextEditingController _fondoController = TextEditingController();
+  final TextEditingController _fondoDiaSigController = TextEditingController();
+  final TextEditingController _ventaPuntosController = TextEditingController();
+
+  TextEditingController get ventaEfeController => _ventaEfeController;
+  TextEditingController get ventaTarjController => _ventaTarjController;
+  TextEditingController get ventaTransfController => _ventaTransfController;
+  TextEditingController get totalVentaController => _totalVentaController;
+  TextEditingController get fondoController => _fondoController;
+  TextEditingController get fondoDiaSigController => _fondoDiaSigController;
+  TextEditingController get ventaPuntosController => ventaPuntosController;
+
+  List<Pedido> _pedidosConPropinas = [];
+  double _totalPropinas = 0.0;
+
+  List<Pedido> get pedidosConPropinas => _pedidosConPropinas;
+  double get totalPropinas => _totalPropinas;
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchCortes(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchCortes(page: _currentPage - 1);
+    }
+  }
+
+  bool isCorteAbierto() {
+    return _selectedCorte != null && _selectedCorte!.fechaCorte == null;
+  }
+
+  void actualizarVentaEfectivo(String value) {
+    _ventaEfe = double.tryParse(value.replaceAll(',', '')) ?? 0.0;
+    _ventaEfeController.text = formatoMiles(_ventaEfe);
+    _calcularTotalVenta();
+    notifyListeners();
+  }
+
+  void actualizarVentaTarjeta(String value) {
+    _ventaTarj = double.tryParse(value.replaceAll(',', '')) ?? 0.0;
+    _ventaTarjController.text = formatoMiles(_ventaTarj);
+    _calcularTotalVenta();
+    notifyListeners();
+  }
+
+  void actualizarVentaTransferencia(String value) {
+    _ventaTransf = double.tryParse(value.replaceAll(',', '')) ?? 0.0;
+    _ventaTransfController.text = formatoMiles(_ventaTransf);
+    _calcularTotalVenta();
+    notifyListeners();
+  }
+
+  void _calcularTotalVenta() {
+    _totalVenta = _ventaEfe + _ventaTarj + _ventaTransf;
+    _totalVentaController.text = formatoMiles(_totalVenta);
+  }
+
+  Future<String?> createCorteCaja() async {
+    RepoService<CorteCaja> repoCorte = RepoService<CorteCaja>();
+
+    double? fondoDiaSigAnterior =
+        await repoCorte.obtenerFondoDiaSigDelUltimoCorte();
+
+    _selectedCorte = CorteCaja(
+      id: Uuid().v4(),
+      fechaApertura: DateTime.now().toUtc(),
+      creado: DateTime.now().toUtc(),
+      fondo: fondoDiaSigAnterior ?? 0.0,
+    );
+
+    await _selectedCorte!.guardar();
+    notifyListeners();
+    return _selectedCorte!.id;
+  }
+
+  Future<void> eliminarDeposito(String id) async {
+    final deposito = depositos.firstWhere((d) => d.id == id);
+    deposito.eliminado = DateTime.now().toUtc();
+    await deposito.guardar();
+    calcularTotalDeposito();
+    notifyListeners();
+  }
+
+  Future<void> eliminarRetiro(String id) async {
+    final retiro = retiros.firstWhere((d) => d.id == id);
+    retiro.eliminado = DateTime.now().toUtc();
+    await retiro.guardar();
+    calcularTotalRetiro();
+    notifyListeners();
+  }
+
+  Future<void> eliminarGasto(String id) async {
+    final gasto = gastos.firstWhere((d) => d.id == id);
+    gasto.eliminado = DateTime.now().toUtc();
+    await gasto.guardar();
+    calcularTotalGasto();
+    notifyListeners();
+  }
+
+  Future<void> fetchCortes({int page = 1}) async {
+    setIsLoading(true);
+    RepoService<CorteCaja> repoCorte = RepoService<CorteCaja>();
+    _currentPage = page;
+    _cortes = await repoCorte.obtenerTodos(orderBy: 'fechaApertura DESC');
+    setIsLoading(false);
+    notifyListeners();
+  }
+
+  Future<void> fetchDepositosAndRetiros(String corteCajaId) async {
+    RepoService<Deposito> depositoRepo = RepoService<Deposito>();
+    RepoService<Retiro> retiroRepo = RepoService<Retiro>();
+    RepoService<Gasto> gastoRepo = RepoService<Gasto>();
+    depositos = await depositoRepo.obtenerDepositosPorIdCorteCaja(corteCajaId);
+    retiros = await retiroRepo.obtenerRetirosPorIdCorteCaja(corteCajaId);
+    gastos = await gastoRepo.obtenerGastosPorIdCorteCaja(corteCajaId);
+
+    print('Depósitos obtenidos: ${depositos.length}');
+    print('Retiros obtenidos: ${retiros.length}');
+    print('Gastos obtenidos: ${gastos.length}');
+
+    calcularTotalDeposito();
+    calcularTotalRetiro();
+    calcularTotalGasto();
+    notifyListeners();
+  }
+
+  Future<CorteCaja?> fetchCorteCajaConDetalles(String idCorteCaja) async {
+    // Similar a fetchDepositosAndRetiros, pero devuelve el corte ya cargado
+    await fetchDepositosAndRetiros(idCorteCaja);
+    return _cortes.firstWhereOrNull((c) => c.id == idCorteCaja);
+  }
+
+  void selectCorte(CorteCaja corte) {
+    _selectedCorte = corte;
+    notifyListeners();
+  }
+
+  Future<List<CorteCaja>>
+      fetchAllLocalCortesCajasNoSincronizadosOrdenadosPorFecha() async {
+    setIsLoading(true);
+    var db = await RepoService().db;
+
+    List<Map<String, dynamic>> result = await db!.query('CorteCaja',
+        where: 'sincronizado IS NULL', orderBy: 'fechaApertura ASC');
+
+    List<CorteCaja> cortesNoSync =
+        result.map((map) => CorteCaja.fromJson(map)).toList();
+    setIsLoading(false);
+    return cortesNoSync;
+  }
+
+  Future<bool> sincronizarCorteCajas() async {
+    List<CorteCaja> cortesNoSincronizados =
+        await fetchAllLocalCortesCajasNoSincronizadosOrdenadosPorFecha();
+
+    if (cortesNoSincronizados.isNotEmpty) {
+      CorteCaja corteNoSincronizado = cortesNoSincronizados.first;
+
+      if (corteNoSincronizado.id != null &&
+          corteNoSincronizado.id!.isNotEmpty) {
+        await fetchCorteCajaConDetalles(corteNoSincronizado.id!);
+        corteNoSincronizado =
+            _cortes.firstWhere((c) => c.id == corteNoSincronizado.id);
+
+        corteNoSincronizado.depositos = this.depositos;
+        corteNoSincronizado.retiros = this.retiros;
+        corteNoSincronizado.gastos = this.gastos;
+      }
+
+      Map<String, dynamic> corteJson =
+          await prepararCorteCajaParaApi(corteNoSincronizado);
+
+      print('JSON Corte Caja enviado: $corteJson');
+
+      var response = ApiResponse(await BaseService()
+          .post('/pos/corte-caja/sincronizar2', body: corteJson));
+
+      if (response.isOk && response.detalle != null) {
+        String idWeb = response.detalle!['idWeb'];
+        String sincronizado = response.detalle!['sincronizado'];
+
+        await actualizarCorteCajaSincronizado(
+            corteNoSincronizado.id!, idWeb, sincronizado);
+
+        return true;
+      } else {
+        print(
+            'Error en la sincronización del corte de caja: ${response.mensaje}');
+        return true;
+      }
+    } else {
+      print('No se encontraron cortes de caja no sincronizados.');
+      return false;
+    }
+  }
+
+  Future<void> actualizarCorteCajaSincronizado(
+      String idCorteCaja, String idWeb, String sincronizado) async {
+    var db = await RepoService().db;
+
+    await db!.update(
+      'CorteCaja',
+      {
+        'idWeb': idWeb,
+        'sincronizado': sincronizado,
+      },
+      where: 'id = ?',
+      whereArgs: [idCorteCaja],
+    );
+  }
+
+  Future<Map<String, dynamic>> prepararCorteCajaParaApi(CorteCaja corte) async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+    Map<String, dynamic> apiMap = corte.toApi();
+    apiMap['claveSucursal'] = claveSucursal;
+    if (corte.idWeb != null && corte.idWeb!.isNotEmpty) {
+      apiMap['idWeb'] = corte.idWeb;
+    }
+    return apiMap;
+  }
+
+  Future<void> addDeposito(double monto, String descripcion, String? persona,
+      String corteCajaId) async {
+    Deposito nuevoDeposito = Deposito(
+      id: Uuid().v4(),
+      idCorteCaja: corteCajaId,
+      fechaDeposito: DateTime.now().toUtc(),
+      creado: DateTime.now().toUtc(),
+      monto: monto,
+      descripcion: descripcion,
+      persona: persona,
+    );
+    await nuevoDeposito.guardar();
+    depositos.add(nuevoDeposito);
+    calcularTotalDeposito();
+
+    calcularCorteFinal();
+
+    if (_selectedCorte != null) {
+      _selectedCorte!.sincronizado = null;
+      await _selectedCorte!.guardar();
+    }
+
+    notifyListeners();
+  }
+
+  Future<void> addRetiro(double monto, String descripcion, String? persona,
+      String corteCajaId) async {
+    Retiro nuevoRetiro = Retiro(
+      id: Uuid().v4(),
+      idCorteCaja: corteCajaId,
+      fechaRetiro: DateTime.now().toUtc(),
+      creado: DateTime.now().toUtc(),
+      monto: monto,
+      descripcion: descripcion,
+      persona: persona,
+    );
+    await nuevoRetiro.guardar();
+    retiros.add(nuevoRetiro);
+    calcularTotalRetiro();
+
+    calcularCorteFinal();
+
+    if (_selectedCorte != null) {
+      _selectedCorte!.sincronizado = null;
+      await _selectedCorte!.guardar();
+    }
+
+    notifyListeners();
+  }
+
+  Future<void> addGasto(double monto, String descripcion, String? persona,
+      String corteCajaId) async {
+    Gasto nuevoGasto = Gasto(
+      id: Uuid().v4(),
+      idCorteCaja: corteCajaId,
+      fechaGasto: DateTime.now().toUtc(),
+      creado: DateTime.now().toUtc(),
+      monto: monto,
+      descripcion: descripcion,
+      persona: persona,
+    );
+    await nuevoGasto.guardar();
+    gastos.add(nuevoGasto);
+    calcularTotalGasto();
+
+    calcularCorteFinal();
+
+    if (_selectedCorte != null) {
+      _selectedCorte!.sincronizado = null;
+      await _selectedCorte!.guardar();
+    }
+
+    notifyListeners();
+  }
+
+  Future<void> cargarVentasPorCorteId(String idCorteCaja) async {
+    setIsLoading(true);
+    try {
+      RepoService<Pedido> repoPedido = RepoService<Pedido>();
+      List<Pedido> pedidos =
+          await repoPedido.obtenerPedidosPorCorteCaja(idCorteCaja);
+
+      pedidos = pedidos.where((p) => p.estatus == 'TERMINADO').toList();
+
+      double totalEfectivo = 0;
+      double totalTarjeta = 0;
+      double totalTransferencia = 0;
+      double totalSinCambio = 0;
+
+      for (var pedido in pedidos) {
+        totalEfectivo += pedido.cantEfectivo ?? 0;
+        totalTarjeta += pedido.cantTarjeta ?? 0;
+        totalTransferencia += pedido.cantTransferencia ?? 0;
+        totalSinCambio += pedido.totalPedido ?? 0;
+      }
+
+      double totalDia = totalEfectivo + totalTarjeta + totalTransferencia;
+      double cambio = totalDia - totalSinCambio;
+      double efectivoSinCambio = totalEfectivo - cambio;
+
+      _ventaEfeController.text = formatoMiles(efectivoSinCambio);
+      _ventaTarjController.text = formatoMiles(totalTarjeta);
+      _ventaTransfController.text = formatoMiles(totalTransferencia);
+      _totalVentaController.text =
+          formatoMiles(efectivoSinCambio + totalTarjeta + totalTransferencia);
+    } catch (e) {
+      print("Error al cargar pedidos por idCorteCaja: $e");
+    } finally {
+      setIsLoading(false);
+      notifyListeners();
+    }
+  }
+
+  bool hasOpenCorteCaja() {
+    return _cortes.any((corte) => corte.fechaCorte == null);
+  }
+
+  Future<void> guardarCorte({bool esCorte = false}) async {
+    double fondo =
+        double.tryParse(_fondoController.text.replaceAll(',', '')) ?? 0;
+    double fondoDiaSig =
+        double.tryParse(_fondoDiaSigController.text.replaceAll(',', '')) ?? 0;
+    double ventaPuntos =
+        double.tryParse(_ventaPuntosController.text.replaceAll(',', '')) ?? 0;
+    double ventaEfe =
+        double.tryParse(_ventaEfeController.text.replaceAll(',', '')) ?? 0;
+    double ventaTrans =
+        double.tryParse(_ventaTransfController.text.replaceAll(',', '')) ?? 0;
+    double ventaTarj =
+        double.tryParse(_ventaTarjController.text.replaceAll(',', '')) ?? 0;
+    double gasto = totalGastos;
+    double retiro = totalRetiros;
+    double deposito = totalDepositos;
+
+    _selectedCorte?.fondo = fondo;
+    _selectedCorte?.fondoDiaSig = fondoDiaSig;
+    _selectedCorte?.ventaPuntos = ventaPuntos;
+    _selectedCorte?.ventaEfe = ventaEfe;
+    _selectedCorte?.ventaTrans = ventaTrans;
+    _selectedCorte?.ventaTarj = ventaTarj;
+    _selectedCorte?.gasto = gasto;
+    _selectedCorte?.retiro = retiro;
+    _selectedCorte?.deposito = deposito;
+    _selectedCorte!.sincronizado = null;
+
+    calcularCorteFinal();
+
+    _selectedCorte?.corteFinal = corteFinal;
+    _selectedCorte?.modificado = DateTime.now().toUtc();
+
+    if (esCorte) {
+      _selectedCorte?.fechaCorte = DateTime.now().toUtc();
+    }
+
+    await _selectedCorte?.guardar();
+    notifyListeners();
+  }
+
+  void calcularTotalDeposito() {
+    totalDepositos = depositos
+        .where((deposito) => deposito.eliminado == null)
+        .fold(0, (sum, item) => sum + (item.monto ?? 0));
+  }
+
+  void calcularTotalRetiro() {
+    totalRetiros = retiros
+        .where((retiro) => retiro.eliminado == null)
+        .fold(0, (sum, item) => sum + (item.monto ?? 0));
+  }
+
+  void calcularTotalGasto() {
+    totalGastos = gastos
+        .where((gasto) => gasto.eliminado == null)
+        .fold(0, (sum, item) => sum + (item.monto ?? 0));
+  }
+
+  Future<void> buscarPorFecha(DateTime startDate, DateTime endDate) async {
+    setIsLoading(true);
+    RepoService<CorteCaja> repoCorte = RepoService<CorteCaja>();
+    _cortes = await repoCorte.buscarPorFechaCorte(startDate, endDate);
+    setIsLoading(false);
+    notifyListeners();
+  }
+
+  void calcularCorteFinal() {
+    final valores = obtenerValoresFondo();
+    corteFinal = (valores['fondo']! + totalDepositos + valores['ventaEfe']!) -
+        (valores['fondoDiaSig']! + totalRetiros + totalGastos);
+    notifyListeners();
+  }
+
+  Future<void> imprimirCorteCajaTicket({String? corteCajaId}) async {
+    if (corteCajaId != null) {
+      await fetchDepositosAndRetiros(corteCajaId);
+      _selectedCorte = _cortes.firstWhere((corte) => corte.id == corteCajaId);
+      await cargarVentasPorCorteId(corteCajaId);
+    }
+
+    if (_selectedCorte == null) {
+      print("CorteCaja no encontrado para impresión.");
+      return;
+    }
+
+    double fondo = _selectedCorte!.fondo ?? 0;
+    double fondoDiaSig = _selectedCorte!.fondoDiaSig ?? 0;
+    double? ventaEfe = _selectedCorte!.ventaEfe;
+    double? ventaTarj = _selectedCorte!.ventaTarj;
+    double? ventaTransf = _selectedCorte!.ventaTrans;
+    double deposito = _selectedCorte!.deposito!;
+    double retiro = _selectedCorte!.retiro!;
+    double gasto = _selectedCorte!.gasto!;
+    double corteFinal = _selectedCorte!.corteFinal!;
+    DateTime? fechaApertura = _selectedCorte!.fechaApertura;
+
+    await CorteCajaTicket.imprimirTicket(
+      fondo: fondo,
+      fondoDiaSig: fondoDiaSig,
+      ventaEfe: ventaEfe!,
+      ventaTarj: ventaTarj!,
+      ventaTrans: ventaTransf!,
+      deposito: deposito,
+      retiro: retiro,
+      gasto: gasto,
+      corteFinal: corteFinal,
+      fechaApertura: fechaApertura,
+    );
+  }
+
+  @override
+  void dispose() {
+    _ventaEfeController.dispose();
+    _ventaTarjController.dispose();
+    _ventaTransfController.dispose();
+    _totalVentaController.dispose();
+    _fondoController.dispose();
+    corteFinal = 0.0;
+    super.dispose();
+  }
+
+  Map<String, double> obtenerValoresFondo() {
+    double fondo =
+        double.tryParse(fondoController.text.replaceAll(',', '')) ?? 0;
+    double fondoDiaSig =
+        double.tryParse(fondoDiaSigController.text.replaceAll(',', '')) ?? 0;
+    double ventaEfe =
+        (double.tryParse(ventaEfeController.text.replaceAll(',', '')) ?? 0);
+
+    return {
+      'fondo': fondo,
+      'fondoDiaSig': fondoDiaSig,
+      'ventaEfe': ventaEfe,
+    };
+  }
+
+  Future<List<Pedido>> fetchPedidosCancelados(String idCorteCaja) async {
+    var db = await RepoService().db;
+    List<Map<String, dynamic>> maps = await db!.query(
+      'Pedido',
+      where: 'idCorteCaja = ? AND eliminado IS NULL AND estatus = ?',
+      whereArgs: [idCorteCaja, 'CANCELADO'],
+    );
+    return maps.map((map) => Pedido.fromJson(map)).toList();
+  }
+
+  Future<DateTime?> fetchHoraLocal() async {
+    try {
+      final url = Uri.parse('https://worldtimeapi.org/api/ip');
+      final response = await http.get(url);
+
+      if (response.statusCode == 200) {
+        final data = jsonDecode(response.body);
+        final dateTimeString = data['datetime'] as String;
+        DateTime serverDateTime = DateTime.parse(dateTimeString);
+        final localTime = serverDateTime.toLocal();
+
+        return localTime;
+      } else {
+        print("Error al consultar la hora: ${response.statusCode}");
+        return null;
+      }
+    } catch (e) {
+      print("Excepción al consultar la hora: $e");
+      return null;
+    }
+  }
+
+  Future<void> fetchPedidosConPropinas(String idCorteCaja) async {
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+
+    List<Pedido> pedidosPropina =
+        await repoPedido.obtenerPedidosConPropinasPorCorte(idCorteCaja);
+
+    double sumaPropinas = 0.0;
+    for (var pedido in pedidosPropina) {
+      double sumLocal = pedido.propinas.fold(
+        0.0,
+        (prev, propina) => prev + (propina.cantidad ?? 0.0),
+      );
+      sumaPropinas += sumLocal;
+    }
+    _pedidosConPropinas = pedidosPropina;
+    _totalPropinas = sumaPropinas;
+
+    notifyListeners();
+  }
+}

+ 23 - 0
lib/viewmodels/descuento_view_model.dart

@@ -0,0 +1,23 @@
+import '../models/models.dart';
+import '../services/repo_service.dart';
+import 'package:flutter/material.dart';
+
+class DescuentoViewModel extends ChangeNotifier {
+  List<Descuento> descuentos = [];
+  final RepoService repoService = RepoService<Descuento>();
+
+  Future<void> cargarDescuentos() async {
+    descuentos = await repoService.obtenerTodosDescuentos();
+    notifyListeners();
+  }
+
+  Future<void> guardarDescuento(Descuento descuento) async {
+    await repoService.guardarDescuento(descuento);
+    await cargarDescuentos();
+  }
+
+  Future<void> eliminarDescuento(int id) async {
+    await repoService.eliminarDescuento(id);
+    await cargarDescuentos();
+  }
+}

+ 51 - 44
lib/viewmodels/login_view_model.dart

@@ -1,7 +1,9 @@
 import 'package:flutter/material.dart';
-import '../data/api_response.dart';
 import '../data/session/session_storage.dart';
+import '../models/models.dart';
 import '../services/login_service.dart';
+import '../services/services.dart';
+import 'package:bcrypt/bcrypt.dart';
 
 enum Status { uninitialized, authenticated, authenticating, unauthenticated }
 
@@ -11,65 +13,80 @@ class LoginViewModel extends ChangeNotifier {
   bool hasErrors = false;
   Map<String, dynamic>? _errores = {};
   bool _obscureText = true;
+  Usuario? _usuario;
   int? _idUsuario;
-  Map<String, dynamic>? get errores => _errores;
+  bool _isLoading = true;
+
+  bool get isLoading => _isLoading;
   bool get obscureText => _obscureText;
-  int? get idUsuario => _idUsuario;
-  //List<String> _permisos = [];
-  //List<String> get permisos => _permisos;
 
-  String _nombre = "";
-  String get nombre => _nombre;
+  // Getters
+  Usuario? get usuario => _usuario;
+  Map<String, dynamic>? get errores => _errores;
+
+  Future<void> login(String correo, String contrasena) async {
+    try {
+      print("Iniciando proceso de login para: $correo");
 
-  String _correo = "";
-  String get correo => _correo;
+      List<Usuario> usuarios = await RepoService<Usuario>().obtenerTodos();
+      Usuario? usuario = usuarios.firstWhere(
+        (usuario) => usuario.correo?.toLowerCase() == correo.toLowerCase(),
+        orElse: () => Usuario(),
+      );
 
-  String _error = "";
-  String get error => _error;
+      if (usuario.id != 0 && usuario.clave != null) {
+        bool esContrasenaValida = BCrypt.checkpw(contrasena, usuario.clave!);
 
-  Future login(String username, String password) async {
-    try {
-      ApiResponse apiResponse = await LoginService().logIn(username, password);
-      _errores = {};
-      if (apiResponse.isOk) {
-        _idUsuario = apiResponse.detalle?['id'];
-        String token = apiResponse.detalle?['token'];
-        if (token.isNotEmpty) {
-          SessionStorage().saveToken(apiResponse.detalle?['token']);
-          SessionStorage().saveId(apiResponse.detalle?['id']);
-          SessionStorage().saveCorreo(apiResponse.detalle!['correo']);
-          SessionStorage().saveNombre(apiResponse.detalle!['nombre']);
+        if (esContrasenaValida) {
           _status = Status.authenticated;
+          _errores = null;
+          _idUsuario = usuario.id;
+          await SessionStorage().saveId(_idUsuario!);
+          notifyListeners();
+        } else {
+          _errores = {'contrasena': 'Contraseña incorrecta'};
+          _status = Status.unauthenticated;
           notifyListeners();
         }
-      }
-      if (apiResponse.isError) {
-        hasErrors = true;
-        _errores = apiResponse.errores;
+      } else {
+        _errores = {'correo': 'El correo no existe en el sistema'};
         _status = Status.unauthenticated;
         notifyListeners();
       }
     } catch (e) {
+      print("Error durante el proceso de login: $e");
       _status = Status.unauthenticated;
       notifyListeners();
     }
   }
 
-  void checkSession() async {
-    var token = await SessionStorage().getToken();
-    var id = await SessionStorage().getId();
-    if (token != null && token.isNotEmpty) {
+  Future<void> checkSession() async {
+    _isLoading = true; // Inicia el estado de carga
+    notifyListeners();
+
+    int? idUsuarioGuardado = await SessionStorage().getId();
+    if (idUsuarioGuardado != null) {
+      print("Sesión encontrada para el ID de usuario: $idUsuarioGuardado");
+      _idUsuario = idUsuarioGuardado;
       _status = Status.authenticated;
-      _idUsuario = id;
     } else {
+      print("No se encontró ninguna sesión activa.");
       _status = Status.unauthenticated;
     }
+
+    _isLoading = false;
     notifyListeners();
   }
 
-  logOut() async {
-    await SessionStorage().clearToken();
+  void logOut() async {
+    print("Cerrando sesión...");
+    _usuario = null;
+    _idUsuario = null;
     _status = Status.unauthenticated;
+    _errores = {};
+
+    await SessionStorage().clearId();
+    print("ID de usuario eliminado de SharedPreferences.");
     notifyListeners();
   }
 
@@ -77,14 +94,4 @@ class LoginViewModel extends ChangeNotifier {
     _obscureText = !_obscureText;
     notifyListeners();
   }
-
-  setValores() async {
-    _nombre = (await SessionStorage().getNombre()).toString();
-    _correo = (await SessionStorage().getCorreo()).toString();
-    notifyListeners();
-  }
-
-  bool validarEmail(String email) =>
-      RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
-          .hasMatch(email.trim());
 }

+ 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) {}
+  }
+}

+ 174 - 0
lib/viewmodels/mesa_view_model.dart

@@ -0,0 +1,174 @@
+import '../data/api_response.dart';
+import '/models/mesa_model.dart';
+import 'package:flutter/material.dart';
+import '../services/services.dart';
+import 'package:sqflite/sqflite.dart';
+
+class MesaViewModel extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<Mesa> _mesas = [];
+  bool _isLoading = false;
+  Mesa? _selectedMesa;
+
+  List<Mesa> get mesas => _mesas;
+  bool get isLoading => _isLoading;
+  Mesa? get selectedMesa => _selectedMesa;
+
+  int _currentPage = 1;
+  int _totalMesas = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalMesas => _totalMesas;
+  int get totalPages => (_totalMesas / _limit).ceil();
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  void selectMesa(Mesa mesa) {
+    _selectedMesa = mesa;
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalAll({int page = 1, bool sinLimite = false}) async {
+    _currentPage = page;
+    var db = await RepoService().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 id ASC $limitOffsetClause');
+    _mesas = query.map((element) => Mesa.fromJson(element)).toList();
+
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalByName({required String nombre}) async {
+    var db = await RepoService().db;
+    var query = await db!.query(
+      'Mesa',
+      where: 'nombre LIKE "%$nombre%"',
+      orderBy: 'id asc',
+    );
+    List<Mesa> aux = [];
+    for (var element in query) {
+      Mesa mesa = Mesa.fromJson(element);
+      aux.add(mesa);
+    }
+    _mesas = aux;
+    notifyListeners();
+  }
+
+  Mesa fetchLocalById({required int? idMesa}) {
+    final mesa = mesas.firstWhere((mesa) => mesa.id == idMesa,
+        orElse: () => Mesa(id: 0, nombre: 'Mesa desconocida'));
+    return mesa;
+  }
+
+  Future<void> addMesa(Mesa mesa) async {
+    mesa.creado = DateTime.now().toUtc();
+    await RepoService().guardar(mesa);
+    await fetchLocalAll();
+  }
+
+  Future<void> updateMesa(Mesa mesa) async {
+    setIsLoading(true);
+    try {
+      mesa.modificado = DateTime.now().toUtc();
+      await RepoService().guardar(mesa);
+      await fetchLocalAll();
+    } catch (e) {
+      print('Error updating mesa: $e');
+    }
+    setIsLoading(false);
+  }
+
+  Future<void> deleteMesa(int id) async {
+    await RepoService().eliminar<Mesa>(id);
+    fetchLocalAll();
+  }
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchLocalAll(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchLocalAll(page: _currentPage - 1);
+    }
+  }
+
+  Future<bool> isMesaActive(String clave) async {
+    var db = await RepoService().db;
+    var result = await db!.query(
+      'Mesa',
+      where: 'clave = ?',
+      whereArgs: [clave],
+    );
+
+    if (result.isNotEmpty) {
+      var mesa = Mesa.fromJson(result.first);
+      return mesa.activa == true;
+    }
+
+    return false;
+  }
+
+  Future<bool> sincronizarMesas() async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+
+    try {
+      Map<String, String> parametros = {
+        "claveSucursal": claveSucursal!,
+        "limite": "-1"
+      };
+
+      final response = ApiResponse(await BaseService()
+          .get('/pos/mesa', queryParameters: parametros, withAuth: true));
+
+      if (response.isOk && response.resultados != null) {
+        List<Mesa> mesasApi =
+            response.resultados!.map((json) => Mesa.fromApi(json)).toList();
+
+        if (mesasApi.isNotEmpty) {
+          print("Mesas API obtenidas: ${mesasApi.length}");
+          await RepoService().sincronizarMesas(mesasApi);
+          await fetchLocalAll();
+          return true;
+        } else {
+          print("No se encontraron mesas en la API.");
+        }
+      } else {
+        print("Error en la respuesta de la API o resultados nulos MESAS.");
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar mesas: $e');
+      return false;
+    }
+  }
+}

+ 442 - 0
lib/viewmodels/pedido_view_model.dart

@@ -0,0 +1,442 @@
+import 'package:collection/collection.dart';
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+import 'package:sqflite/sqflite.dart';
+
+import '../data/api_response.dart';
+import '../models/models.dart';
+import '../services/services.dart';
+
+class PedidoViewModel extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<Pedido> _pedidos = [];
+  Pedido? _selectedPedido;
+  bool _isLoading = false;
+
+  int _currentPage = 1;
+  int _totalPedidos = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalPedidos => _totalPedidos;
+  int get totalPages => (_totalPedidos / _limit).ceil();
+
+  List<Pedido> get pedidos => _pedidos;
+  Pedido? get selectedPedido => _selectedPedido;
+  bool get isLoading => _isLoading;
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  Future<bool> guardarPedidoLocal({required Pedido pedido}) async {
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+
+    if (pedido.id == null || pedido.id == 0) {
+      int nextFolio = await repoPedido.obtenerProximoFolio();
+      pedido.folio = nextFolio;
+    }
+
+    try {
+      // Verifica si el pedido ya existe en la base de datos
+      Pedido? pedidoExistente = await repoPedido.obtenerPorId(pedido.id!);
+
+      if (pedidoExistente != null) {
+        // Si existe, actualiza
+        await repoPedido.guardar(pedido);
+      } else {
+        // Si no existe, inserta como nuevo
+        int idPedido = await repoPedido.guardarLocal(pedido);
+        pedido.id = idPedido;
+      }
+
+      RepoService<PedidoProducto> repoPedidoProducto =
+          RepoService<PedidoProducto>();
+      RepoService<PedidoProductoTopping> repoPedidoProductoTopping =
+          RepoService<PedidoProductoTopping>();
+
+      // Manejo de productos asociados al pedido
+      for (var producto in pedido.productos) {
+        PedidoProducto pedidoProducto = PedidoProducto(
+          idPedido: pedido.id,
+          idProducto: producto.idProducto,
+          cantidad: producto.cantidad,
+          costoUnitario: producto.costoUnitario,
+          comentario: producto.comentario,
+        );
+
+        if (producto.id != null && producto.id! > 0) {
+          await repoPedidoProducto.guardarLocal(pedidoProducto);
+        } else {
+          int idPedidoProducto =
+              await repoPedidoProducto.guardarLocal(pedidoProducto);
+
+          for (var topping in producto.toppings) {
+            PedidoProductoTopping pedidoProductoTopping = PedidoProductoTopping(
+              idPedidoProducto: idPedidoProducto,
+              idTopping: topping.idTopping,
+              idCategoria: topping.idCategoria,
+            );
+            await repoPedidoProductoTopping.guardarLocal(pedidoProductoTopping);
+          }
+        }
+      }
+
+      notifyListeners();
+      return true;
+    } catch (e) {
+      print('Error al guardar el pedido: $e');
+      return false;
+    }
+  }
+
+  Future<bool> guardarPedidoConProductos({
+    required Pedido pedido,
+    required List<ItemCarrito> carrito,
+  }) async {
+    try {
+      RepoService<Pedido> repoPedido = RepoService<Pedido>();
+      RepoService<PedidoProducto> repoPedidoProducto =
+          RepoService<PedidoProducto>();
+      RepoService<PedidoProductoTopping> repoPedidoProductoTopping =
+          RepoService<PedidoProductoTopping>();
+
+      // Guardar el pedido en la base de datos
+      if (pedido.id == null || pedido.id == 0) {
+        int nextFolio = await repoPedido.obtenerProximoFolio();
+        pedido.folio = nextFolio;
+        pedido.id = await repoPedido.guardarLocal(pedido);
+      } else {
+        await repoPedido.guardar(pedido);
+      }
+
+      // Manejar los productos del carrito
+      for (var item in carrito) {
+        PedidoProducto pedidoProducto = PedidoProducto(
+          idPedido: pedido.id,
+          idProducto: item.producto.id,
+          cantidad: item.cantidad,
+          costoUnitario: item.producto.precio.toString(),
+          sincronizado: null,
+        );
+
+        // Guardar o actualizar el producto
+        int idPedidoProducto =
+            await repoPedidoProducto.guardarPedidoProducto(pedidoProducto);
+
+        // Manejar los toppings seleccionados
+        for (var entry in item.selectedToppings.entries) {
+          int categoriaId = entry.key;
+          for (int toppingId in entry.value) {
+            PedidoProductoTopping topping = PedidoProductoTopping(
+              idPedidoProducto: idPedidoProducto,
+              idTopping: toppingId,
+              idCategoria: categoriaId,
+            );
+            await repoPedidoProductoTopping.guardarLocal(topping);
+          }
+        }
+      }
+
+      notifyListeners();
+      return true;
+    } catch (e) {
+      print("Error al guardar el pedido con productos: $e");
+      return false;
+    }
+  }
+
+  Future<void> fetchLocalPedidos({int page = 1}) async {
+    _isLoading = true;
+    _currentPage = page;
+    notifyListeners();
+
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+    _totalPedidos = await repoPedido.contarPedidos();
+    int offset = (_limit * (page - 1));
+    List<Pedido> paginatedPedidos =
+        await repoPedido.obtenerPedidosPaginados(_limit, offset);
+
+    _pedidos = paginatedPedidos;
+    _isLoading = false;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchLocalPedidosForScreen(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchLocalPedidosForScreen(page: _currentPage - 1);
+    }
+  }
+
+  Future<void> eliminarProductoDelPedido(int idProducto, int idPedido) async {
+    RepoService<PedidoProducto> repoPedidoProducto =
+        RepoService<PedidoProducto>();
+
+    PedidoProducto? producto =
+        await repoPedidoProducto.obtenerPorIdPedidoYProducto(
+      idPedido: idPedido,
+      idProducto: idProducto,
+    );
+
+    if (producto != null) {
+      producto.eliminado = DateTime.now().toUtc();
+      await repoPedidoProducto.guardar(producto);
+      notifyListeners();
+    }
+  }
+
+  Future<void> fetchLocalPedidosForScreen({int page = 1}) async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+    _currentPage = page;
+    var db = await RepoService().db;
+
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM Pedido'));
+    _totalPedidos = count ?? 0;
+
+    int offset = (_limit * (page - 1));
+
+    List<Pedido> localPedidos =
+        await repoPedido.obtenerPedidosPaginados(_limit, offset);
+    _pedidos = localPedidos;
+
+    setIsLoading(false);
+    notifyListeners();
+  }
+
+  Future<List<Pedido>> fetchAllLocalPedidos() async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+    List<Pedido> allPedidos = await repoPedido.obtenerTodos();
+    setIsLoading(false);
+    return allPedidos;
+  }
+
+  Future<List<Pedido>> fetchPedidosPorFechaSinLimit(
+      DateTime startDate, DateTime endDate) async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+    List<Pedido> pedidos = await repoPedido.buscarPorFecha(startDate, endDate);
+    setIsLoading(false);
+    return pedidos;
+  }
+
+  Future<Pedido?> fetchPedidoConProductos(int idPedido) async {
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+    Pedido? pedido = await repoPedido.obtenerPorId(idPedido);
+
+    if (pedido != null) {
+      RepoService<PedidoProducto> repoProducto = RepoService<PedidoProducto>();
+      RepoService<Producto> repoProductoInfo = RepoService<Producto>();
+      RepoService<Propinas> repoPropina = RepoService<Propinas>();
+      List<PedidoProducto> productos =
+          await repoProducto.obtenerPorIdPedido(idPedido);
+
+      productos = productos.where((p) => p.eliminado == null).toList();
+
+      for (var producto in productos) {
+        Producto? prodInfo =
+            await repoProductoInfo.obtenerProductoPorId(producto.idProducto!);
+        if (prodInfo != null) {
+          producto.producto = prodInfo;
+        }
+
+        RepoService<PedidoProductoTopping> repoTopping =
+            RepoService<PedidoProductoTopping>();
+        List<PedidoProductoTopping> toppings =
+            await repoTopping.obtenerToppingsPorPedidoProducto(producto.id!);
+
+        for (var topping in toppings) {
+          Producto? toppingInfo =
+              await repoProductoInfo.obtenerProductoPorId(topping.idTopping!);
+          if (toppingInfo != null) {
+            topping.topping = toppingInfo;
+          }
+        }
+
+        producto.toppings = toppings;
+        List<Propinas> propinas = await repoPropina.obtenerTodos<Propinas>(
+            where: 'idPedido = ?', whereArgs: [idPedido]);
+        pedido.propinas = propinas;
+      }
+
+      pedido.productos = productos;
+    }
+
+    return pedido;
+  }
+
+  Future<void> buscarPedidosPorFolio(String folio) async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+    List<Pedido> localPedidos = await repoPedido.buscarPorFolio(folio);
+    _pedidos = localPedidos;
+
+    setIsLoading(false);
+    notifyListeners();
+  }
+
+  Future<void> buscarPedidosPorFecha(
+      DateTime startDate, DateTime endDate) async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+
+    List<Pedido> localPedidos =
+        await repoPedido.buscarPorFecha(startDate, endDate);
+    _pedidos = localPedidos;
+
+    setIsLoading(false);
+    notifyListeners();
+  }
+
+  Future<List<Pedido>> buscarPorFecha(
+      DateTime startDate, DateTime endDate) async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+
+    print('Consulta SQL de pedidos desde: $startDate hasta: $endDate');
+
+    List<Pedido> pedidos = await repoPedido.buscarPorFecha(startDate, endDate);
+
+    print('Pedidos obtenidos desde la base de datos: ${pedidos.length}');
+    pedidos.forEach((pedido) => print(
+        'Pedido Folio: ${pedido.folio}, Estatus: ${pedido.estatus}, Total: ${pedido.totalPedido}'));
+
+    setIsLoading(false);
+    notifyListeners();
+    return pedidos;
+  }
+
+  Future<void> cancelarPedido(int idPedido) async {
+    var db = await RepoService().db;
+    await db?.update(
+      'Pedido',
+      {
+        'estatus': 'CANCELADO',
+        'sincronizado': null,
+      },
+      where: 'id = ?',
+      whereArgs: [idPedido],
+    );
+    fetchLocalPedidosForScreen();
+  }
+
+  Future<bool> sincronizarPedidos() async {
+    List<Pedido> pedidosNoSincronizados =
+        await fetchAllLocalPedidosOrdenadosPorFecha();
+
+    if (pedidosNoSincronizados.isNotEmpty) {
+      Pedido pedidoNoSincronizado = pedidosNoSincronizados.first;
+
+      if (pedidoNoSincronizado.productos.isEmpty) {
+        pedidoNoSincronizado =
+            await fetchPedidoConProductos(pedidoNoSincronizado.id) ??
+                pedidoNoSincronizado;
+      }
+
+      Map<String, dynamic> pedidoJson =
+          await prepararPedidoParaApi(pedidoNoSincronizado);
+
+      //print('JSON enviado: $pedidoJson');
+
+      var response = ApiResponse(await BaseService()
+          .post('/pos/pedido/sincronizar', body: pedidoJson));
+
+      if (response.isOk && response.detalle != null) {
+        int idWeb = response.detalle!['id'];
+        String sincronizado = response.detalle!['sincronizado'];
+
+        await actualizarPedidoSincronizado(
+            pedidoNoSincronizado.id!, idWeb, sincronizado);
+
+        return true;
+      } else {
+        print('Error en la sincronización del pedido: ${response.mensaje}');
+        return true;
+      }
+    } else {
+      print('No se encontraron pedidos no sincronizados.');
+      return false;
+    }
+  }
+
+  Future<void> actualizarPedidoSincronizado(
+      int idPedido, int idWeb, String sincronizado) async {
+    var db = await RepoService().db;
+
+    await db!.update(
+      'Pedido',
+      {
+        'idWeb': idWeb,
+        'sincronizado': sincronizado,
+      },
+      where: 'id = ?',
+      whereArgs: [idPedido],
+    );
+  }
+
+  Future<List<Pedido>> fetchAllLocalPedidosOrdenadosPorFecha() async {
+    setIsLoading(true);
+    RepoService<Pedido> repoPedido = RepoService<Pedido>();
+
+    List<Pedido> allPedidos =
+        await repoPedido.obtenerPedidosOrdenadosPorFecha();
+
+    setIsLoading(false);
+    return allPedidos;
+  }
+
+  Future<List<Pedido>> fetchPedidosNuevosByCorteId(String idCorteCaja) async {
+    var db = await RepoService().db;
+
+    List<Map<String, dynamic>> maps = await db!.query(
+      'Pedido',
+      where: 'estatus = ? AND idCorteCaja = ? AND eliminado IS NULL',
+      whereArgs: ['NUEVO', idCorteCaja],
+    );
+
+    if (maps.isNotEmpty) {
+      return maps.map((map) => Pedido.fromJson(map)).toList();
+    }
+    return [];
+  }
+
+  Future<void> actualizarCorteCajaEnPedidos(
+      List<int> idsPedidos, String nuevoIdCorte) async {
+    var db = await RepoService().db;
+
+    for (int idPedido in idsPedidos) {
+      await db!.update(
+        'Pedido',
+        {'idCorteCaja': nuevoIdCorte, 'sincronizado': null},
+        where: 'id = ?',
+        whereArgs: [idPedido],
+      );
+    }
+  }
+
+  Future<Map<String, dynamic>> prepararPedidoParaApi(Pedido pedido) async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+
+    Map<String, dynamic> apiMap = pedido.toApi();
+
+    apiMap['claveSucursal'] = claveSucursal;
+
+    if (pedido.idWeb != null && pedido.idWeb! > 0) {
+      apiMap['idWeb'] = pedido.idWeb;
+    }
+
+    return apiMap;
+  }
+}

+ 101 - 0
lib/viewmodels/permiso_view_model.dart

@@ -0,0 +1,101 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+
+import '../data/api_response.dart';
+import '../data/session/session_storage.dart';
+import '../services/base_service.dart';
+import '../models/models.dart';
+import '../services/services.dart';
+import '../services/repo_service.dart';
+
+class PermisoViewModel extends ChangeNotifier {
+  List<Permiso> _permisos = [];
+  bool _isLoading = false;
+  Permiso? _selectedPermiso;
+
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<Permiso> get permisos => _permisos;
+  bool get isLoading => _isLoading;
+
+  int _currentPage = 1;
+  int _totalPermisos = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalPermisos => _totalPermisos;
+  int get totalPages => (_totalPermisos / _limit).ceil();
+
+  List<String> _userPermisos = [];
+  List<String> get userPermisos => _userPermisos;
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  void selectPermiso(Permiso permiso) {
+    _selectedPermiso = permiso;
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalAll({int page = 1}) async {
+    _currentPage = page;
+    var db = await RepoService().db;
+
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM Permiso'));
+    _totalPermisos = count ?? 0;
+
+    int offset = (_limit * (page - 1));
+
+    var query = await db.query('Permiso',
+        orderBy: 'id asc', limit: _limit, offset: offset);
+    _permisos = query.map((element) => Permiso.fromJson(element)).toList();
+    notifyListeners();
+  }
+
+  Future<void> sincronizarPermisos() async {
+    _isLoading = true;
+    notifyListeners();
+    try {
+      final response = ApiResponse(await BaseService().get('/pos/permiso'));
+      if (response.isOk && response.resultados != null) {
+        _permisos =
+            response.resultados!.map((json) => Permiso.fromJson(json)).toList();
+        await RepoService().sincronizarPermisos(_permisos);
+      }
+    } catch (e) {
+      print('Error fetching permisos: $e');
+    }
+    _isLoading = false;
+    notifyListeners();
+  }
+
+  Future<void> fetchUserPermisos() async {
+    _isLoading = true;
+    notifyListeners();
+
+    try {
+      int? userId = await SessionStorage().getId();
+      if (userId != null) {
+        var db = await RepoService().db;
+
+        var query = await db!.query(
+          'UsuarioPermiso',
+          where: 'idUsuario = ?',
+          whereArgs: [userId],
+        );
+
+        _userPermisos =
+            query.map((row) => row['idPermiso'].toString()).toList();
+      }
+    } catch (e) {
+      print('Error fetching user permisos: $e');
+    } finally {
+      _isLoading = false;
+      notifyListeners();
+    }
+  }
+}

+ 440 - 0
lib/viewmodels/producto_view_model.dart

@@ -0,0 +1,440 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+
+import '../data/api_response.dart';
+import '../services/base_service.dart';
+import '../models/models.dart';
+import '../services/services.dart';
+import '../services/repo_service.dart';
+import '../views/producto/producto_imagen.dart';
+
+class ProductoViewModel<T> extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<Producto> _productos = [];
+  List<CategoriaProducto> _toppingCategories = [];
+  bool _isLoading = false;
+  int? _selectedCategoriaId = 0;
+  Producto? _selectedProducto;
+
+  List<Producto> get productos => _productos;
+  Producto? get selectedProducto => _selectedProducto;
+  bool get isLoading => _isLoading;
+
+  int _currentPage = 1;
+  int _totalProducts = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalProducts => _totalProducts;
+  int get totalPages => (_totalProducts / _limit).ceil();
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  void selectProducto(Producto producto) {
+    _selectedProducto = producto;
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalAll({int page = 1}) async {
+    _currentPage = page;
+    var db = await RepoService().db;
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM Producto'));
+    _totalProducts = count ?? 0;
+
+    int offset = (_limit * (page - 1));
+
+    var query = await db.query('Producto',
+        where: 'eliminado IS NULL',
+        orderBy: 'id asc',
+        limit: _limit,
+        offset: offset);
+    _productos = query.map((element) => Producto.fromJson(element)).toList();
+
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalByID({required int idCategoria}) async {
+    var db = await RepoService().db;
+    var query = await db!.query('Producto',
+        where: 'idCategoria = ?',
+        whereArgs: [idCategoria],
+        orderBy: 'idLocal asc');
+    List<Producto> aux = [];
+    for (var element in query) {
+      Producto producto = Producto.fromJson(element);
+      aux.add(producto);
+    }
+    _productos = aux;
+    notifyListeners();
+  }
+
+  Future<void> fetchAllByCategory(int idCategoria) async {
+    var db = await RepoService().db;
+    var query = await db!.query('Producto',
+        where: 'idCategoria = ? and eliminado IS NULL',
+        whereArgs: [idCategoria],
+        orderBy: 'idLocal asc');
+    _productos = query.map((e) => Producto.fromJson(e)).toList();
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalByName({required String nombre}) async {
+    var db = await RepoService().db;
+    var query = await db!.query(
+      'Producto',
+      where: 'nombre LIKE ?',
+      whereArgs: ['%$nombre%'],
+      orderBy: 'idLocal asc',
+    );
+    _productos = query.map((e) => Producto.fromJson(e)).toList();
+    notifyListeners();
+  }
+
+  Future<int> addProducto(Producto producto) async {
+    setIsLoading(true);
+    try {
+      int id = await RepoService().guardar(producto);
+      fetchLocalAll();
+      return id;
+    } catch (e) {
+      print('Error al agregar producto: $e');
+      return -1;
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  Future<void> updateProducto(Producto producto) async {
+    setIsLoading(true);
+    try {
+      int result = await RepoService().guardar(producto);
+      print("Resultado: $result");
+      fetchLocalAll();
+    } catch (e) {
+      print('Error al actualizar: $e');
+    }
+    setIsLoading(false);
+  }
+
+  Future<void> deleteProducto(int id) async {
+    await RepoService().eliminar<Producto>(id);
+    fetchLocalAll();
+  }
+
+  Future<bool> updateProductImagePath(int productId, String imagePath) async {
+    setIsLoading(true);
+    try {
+      Producto? producto = await RepoService().obtenerProductoPorId(productId);
+      producto!.imagen = imagePath;
+      await RepoService().guardar(producto);
+      notifyListeners();
+      return true;
+    } catch (e) {
+      print('Error al actualizar la imagen del producto: $e');
+      return false;
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  Future<List<CategoriaProducto>> fetchToppingCategories() async {
+    var db = await RepoService().db;
+    var query = await db!.query('CategoriaProducto',
+        where: 'esToping = ?', whereArgs: [1], orderBy: 'id asc');
+    _toppingCategories =
+        query.map((element) => CategoriaProducto.fromJson(element)).toList();
+    return _toppingCategories;
+  }
+
+  Future<List<Producto>> fetchProductsByCategory(int categoryId) async {
+    var db = await RepoService().db;
+    var query = await db!.query('Producto',
+        where: 'idCategoria = ?', whereArgs: [categoryId], orderBy: 'id asc');
+    return query.map((e) => Producto.fromJson(e)).toList();
+  }
+
+  Future<List<int>> obtenerToppingsPorProducto(int idProducto) async {
+    var dbClient = await RepoService().db;
+    var result = await dbClient!.query(
+      'ProductoTopping',
+      where: 'idProducto = ?',
+      whereArgs: [idProducto],
+    );
+    return result.map((map) => map['idTopping'] as int).toList();
+  }
+
+  Future<Producto?> obtenerProductoPorId(int idProducto) async {
+    Database? dbClient = await RepoService().db;
+    List<Map> maps = await dbClient!
+        .query('Producto', where: 'id = ?', whereArgs: [idProducto]);
+    if (maps.isNotEmpty) {
+      return Producto.fromJson(Map<String, dynamic>.from(maps.first));
+    }
+    return null;
+  }
+
+  Future<bool> sincronizarCategorias() async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+
+    try {
+      Map<String, String> parametros = {
+        "claveSucursal": claveSucursal!,
+        "limite": "-1"
+      };
+      final response = ApiResponse(await BaseService()
+          .get('/pos/categoria', queryParameters: parametros));
+
+      //print(response.resultados);
+
+      if (response.isOk && response.resultados != null) {
+        List<CategoriaProducto> categoriasApi = response.resultados!
+            .map((json) => CategoriaProducto.fromApi(json))
+            .toList();
+
+        if (categoriasApi.isNotEmpty) {
+          await RepoService().sincronizarCategorias(categoriasApi);
+          notifyListeners();
+          return true;
+        }
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar categorías: $e');
+      return false;
+    }
+  }
+
+  Future<bool> sincronizarProductos() async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+
+    try {
+      Map<String, String> parametros = {
+        "limite": "-1",
+        "claveSucursal": claveSucursal!,
+        "expand": "media"
+      };
+      final response = ApiResponse(await BaseService()
+          .get('/pos/producto', queryParameters: parametros));
+
+      if (response.isOk && response.resultados != null) {
+        List<Producto> productosApi =
+            response.resultados!.map((json) => Producto.fromApi(json)).toList();
+
+        if (productosApi.isNotEmpty) {
+          print("Productos API obtenidos: ${productosApi.length}");
+
+          // Aquí mantengo tu lógica de descarga de imágenes.
+          for (var productoApi in productosApi) {
+            if (productoApi.media != null && productoApi.media!.isNotEmpty) {
+              for (var media in productoApi.media!) {
+                // Descargar y guardar la imagen localmente
+                String? localImagePath = await downloadAndStoreImage(
+                    media.ruta!, productoApi.id!, media.nombre!);
+
+                if (localImagePath != null) {
+                  productoApi.imagen = localImagePath;
+                }
+              }
+            }
+          }
+
+          // Delegamos la sincronización de los productos al RepoService
+          await RepoService().sincronizarProductos(productosApi);
+          notifyListeners();
+          return true;
+        } else {
+          print("No se encontraron productos en la API.");
+        }
+      } else {
+        print("Error en la respuesta de la API o resultados nulos.");
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar productos: $e');
+      return false;
+    }
+  }
+
+  Future<bool> sincronizarProductoTopping() async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+
+    try {
+      Map<String, String> parametros = {
+        "limite": "-1",
+        "claveSucursal": claveSucursal!,
+      };
+
+      final response = ApiResponse(await BaseService()
+          .get('/pos/producto-topping', queryParameters: parametros));
+
+      if (response.isOk && response.resultados != null) {
+        List<ProductoTopping> toppingApi = response.resultados!
+            .map((json) => ProductoTopping.fromApi(json))
+            .toList();
+
+        if (toppingApi.isNotEmpty) {
+          print("Producto Toppings API obtenidos: ${toppingApi.length}");
+
+          await RepoService().sincronizarProductoTopping(toppingApi);
+          notifyListeners();
+          return true;
+        } else {
+          print("No se encontraron toppings de producto en la API.");
+        }
+      } else {
+        print("Error en la respuesta de la API o resultados nulos.");
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar producto toppings: $e');
+      return false;
+    }
+  }
+
+  Future<void> sincronizarProductosYCategorias() async {
+    setIsLoading(true);
+    try {
+      bool categoriasSincronizadas = await sincronizarCategorias();
+
+      if (categoriasSincronizadas) {
+        bool productosSincronizados = await sincronizarProductos();
+        if (productosSincronizados) {
+          bool toppingsSincronizados = await sincronizarProductoTopping();
+          if (toppingsSincronizados) {
+            await fetchLocalAll();
+          }
+        }
+      }
+      notifyListeners();
+    } catch (e, stackTrace) {
+      throw Exception(
+          "Error al sincronizar productos, categorías y toppings: $e\n$stackTrace");
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  Future<bool> sincronizarProductosLocales() async {
+    List<Producto> productosNoSincronizados =
+        await RepoService().obtenerProductosNoSincronizadosOrdenadosPorFecha();
+
+    if (productosNoSincronizados.isNotEmpty) {
+      Producto productoNoSincronizado = productosNoSincronizados.first;
+
+      Map<String, dynamic> productoJson =
+          await prepararProductoParaApi(productoNoSincronizado);
+
+      print('JSON Producto enviado: $productoJson');
+
+      // Llamada a la API
+      var response = ApiResponse(await BaseService()
+          .post('/pos/producto/sincronizar', body: productoJson));
+
+      if (response.isOk && response.detalle != null) {
+        int idWeb = response.detalle!['idWeb'];
+        String sincronizado = response.detalle!['sincronizado'];
+        await RepoService().actualizarProductoSincronizado(
+            productoNoSincronizado.id!, idWeb, sincronizado);
+        return true;
+      } else {
+        print('Error en la sincronización del producto: ${response.mensaje}');
+        return true;
+      }
+    } else {
+      print('No se encontraron productos no sincronizados.');
+      return false;
+    }
+  }
+
+  Future<Map<String, dynamic>> prepararProductoParaApi(
+      Producto producto) async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+    Map<String, dynamic> apiMap = producto.toJson();
+
+    apiMap['claveSucursal'] = claveSucursal;
+
+    if (producto.idWeb != null && producto.idWeb! > 0) {
+      apiMap['idWeb'] = producto.idWeb;
+    }
+
+    return apiMap;
+  }
+
+  Future<Map<String, dynamic>> prepararCategoriaParaApi(
+      CategoriaProducto categoria) async {
+    String? claveSucursal =
+        await RepoService().obtenerClaveSucursalSeleccionada();
+    Map<String, dynamic> apiMap = categoria.toJson();
+    // Asegúrate que CategoriaProducto tenga un toJson y si necesitas toApi, créalo similar a Pedido.
+
+    apiMap['claveSucursal'] = claveSucursal;
+
+    if (categoria.idWeb != null && categoria.idWeb! > 0) {
+      apiMap['idWeb'] = categoria.idWeb;
+    }
+
+    return apiMap;
+  }
+
+  Future<bool> sincronizarCategoriasLocales() async {
+    // Obtener categorias no sincronizadas
+    List<CategoriaProducto> categoriasNoSincronizadas =
+        await RepoService().obtenerCategoriasNoSincronizadasOrdenadasPorFecha();
+
+    if (categoriasNoSincronizadas.isNotEmpty) {
+      CategoriaProducto categoriaNoSincronizada =
+          categoriasNoSincronizadas.first;
+
+      Map<String, dynamic> categoriaJson =
+          await prepararCategoriaParaApi(categoriaNoSincronizada);
+
+      print('JSON Categoria enviado: $categoriaJson');
+
+      // Llamada a la API
+      var response = ApiResponse(await BaseService()
+          .post('/pos/categoria/sincronizar', body: categoriaJson));
+
+      if (response.isOk && response.detalle != null) {
+        int idWeb = response.detalle!['idWeb'];
+        String sincronizado = response.detalle!['sincronizado'];
+        await RepoService().actualizarCategoriaSincronizada(
+            categoriaNoSincronizada.id!, idWeb, sincronizado);
+        return true;
+      } else {
+        print(
+            'Error en la sincronización de la categoría: ${response.mensaje}');
+        return true;
+      }
+    } else {
+      print('No se encontraron categorias no sincronizadas.');
+      return false;
+    }
+  }
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchLocalAll(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchLocalAll(page: _currentPage - 1);
+    }
+  }
+}

+ 27 - 0
lib/viewmodels/propina_view_model.dart

@@ -0,0 +1,27 @@
+import 'package:flutter/foundation.dart';
+import '/models/models.dart';
+import '/services/repo_service.dart';
+
+class PropinaViewModel extends ChangeNotifier {
+  final RepoService _repoService = RepoService();
+
+  Future<void> guardarPropina(Propinas propina) async {
+    try {
+      await _repoService.guardar(propina);
+    } catch (e) {
+      print('Error al guardar la propina: $e');
+    }
+  }
+
+  Future<List<Propinas>> obtenerPropinasPorPedido(int idPedido) async {
+    try {
+      return await _repoService.obtenerTodos<Propinas>(
+        where: 'idPedido = ?',
+        whereArgs: [idPedido],
+      );
+    } catch (e) {
+      print('Error al obtener propinas: $e');
+      return [];
+    }
+  }
+}

+ 80 - 0
lib/viewmodels/sucursal_view_model.dart

@@ -0,0 +1,80 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+
+import '../data/api_response.dart';
+import '../services/base_service.dart';
+import '../models/models.dart';
+import '../services/services.dart';
+import '../services/repo_service.dart';
+
+class SucursalViewModel<T> extends ChangeNotifier {
+  List<Sucursal> _sucursales = [];
+  bool _isLoading = false;
+
+  List<Sucursal> get sucursales => _sucursales;
+  bool get isLoading => _isLoading;
+
+  Future<void> fetchLocalSucursales() async {
+    var db = await RepoService().db;
+    var query = await db!
+        .query('Sucursal', where: 'eliminado IS NULL', orderBy: 'id asc');
+    _sucursales = query.map((element) => Sucursal.fromJson(element)).toList();
+    notifyListeners();
+  }
+
+  Future<void> setSelectedSucursal(Sucursal sucursal) async {
+    var db = await RepoService().db;
+
+    await db!.update(
+      'Sucursal',
+      {'seleccionado': 0},
+    );
+
+    sucursal.seleccionado = 1;
+    await RepoService().guardar(sucursal);
+
+    await fetchLocalSucursales();
+  }
+
+  Future<bool> sincronizarSucursales() async {
+    try {
+      final response = ApiResponse(await BaseService().get('/pos/sucursal'));
+
+      if (response.isOk && response.resultados != null) {
+        List<Sucursal> sucursalesApi =
+            response.resultados!.map((json) => Sucursal.fromApi(json)).toList();
+
+        if (sucursalesApi.isNotEmpty) {
+          await RepoService().sincronizarSucursales(sucursalesApi);
+          await fetchLocalSucursales();
+          notifyListeners();
+          return true;
+        }
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar sucursales: $e');
+      return false;
+    }
+  }
+
+  Future<void> sincronizarSucursalesDesdeApi() async {
+    setIsLoading(true);
+    try {
+      bool sucursalesSincronizadas = await sincronizarSucursales();
+      if (sucursalesSincronizadas) {
+        await fetchLocalSucursales();
+      }
+      print('Sucursales sincronizadas: $sucursalesSincronizadas');
+    } catch (e, stackTrace) {
+      print("Error al sincronizar sucursales: $e\n$stackTrace");
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+}

+ 130 - 0
lib/viewmodels/toping_categoria_view_model.dart

@@ -0,0 +1,130 @@
+import 'dart:convert';
+
+import 'package:camera/camera.dart';
+import 'package:flutter/material.dart';
+import '/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();
+  }
+
+  TopingCategoria? getById(int id) {
+    try {
+      return _registros
+          .firstWhere((topingCategoria) => topingCategoria.id == id);
+    } catch (e) {
+      // Si no se encuentra ninguna categoría con ese ID, regresa null.
+      return null;
+    }
+  }
+
+  Future<List<TopingCategoria>> fetchRegistros(
+      {String? q, bool segmentar = false}) async {
+    Map<String, String> parametros = {
+      "ordenar": "id-asc",
+      "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();
+  }
+
+  Future<void> eliminar(TopingCategoria m) async {
+    var r = await BaseService()
+        .delete("admin/toping-categoria", body: {"id": m.id.toString()});
+    Map<String, dynamic> resJson = jsonDecode(r.body);
+    if (r.statusCode == 200) {}
+  }
+}

+ 138 - 0
lib/viewmodels/toping_view_model.dart

@@ -0,0 +1,138 @@
+import 'dart:convert';
+
+import 'package:camera/camera.dart';
+import 'package:flutter/material.dart';
+import '/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();
+  }
+
+  Toping? getById(int id) {
+    try {
+      return _registros.firstWhere((toping) => toping.id == id);
+    } catch (e) {
+      // Si no se encuentra ninguna categoría con ese ID, regresa null.
+      return null;
+    }
+  }
+
+  Future<List<Toping>> fetchRegistros(
+      {String? q, bool segmentar = false, int limitee = 10}) async {
+    Map<String, String> parametros = {
+      "ordenar": "id-desc",
+      "pagina": "$pagina",
+      "limite": "$limite",
+      "expand": "mediaToping,categoriaToping"
+    };
+    if (limitee == -1) {
+      parametros['limite'] = limitee.toString();
+    }
+    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,
+    required String costo,
+    required int categoria,
+    required bool activo,
+    List<XFile>? fotos,
+  }) async {
+    modelo.clave = clave;
+    modelo.nombre = nombre;
+    modelo.descripcion = descripcion;
+    modelo.idCategoria = categoria;
+    modelo.costo = costo;
+    modelo.activo = activo;
+    var r = ApiResponse(await BaseService()
+        .post("admin/toping/guardar", 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: "Toping",
+            etiquetaID: "idToping",
+            tipo: "imagen");
+      }
+    }
+    var s =
+        await BaseService().post("admin/toping/guardar", body: modelo.toJson());
+    if (r.statusCode == 200) {}
+    notifyListeners();
+  }
+
+  Future<void> eliminar(Toping m) async {
+    var r = await BaseService()
+        .delete("admin/toping", body: {"id": m.id.toString()});
+    Map<String, dynamic> resJson = jsonDecode(r.body);
+    if (r.statusCode == 200) {}
+  }
+}

+ 41 - 133
lib/viewmodels/usuarios_view_model.dart

@@ -1,158 +1,66 @@
 import 'dart:convert';
 
 import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
 
+import '../data/api_response.dart';
 import '../models/models.dart';
 import '../services/base_service.dart';
+import '../services/repo_service.dart';
 
-class UsuariosViewModel extends ChangeNotifier {
-  String _busqueda = "";
-  String get busqueda => _busqueda;
-  List<Usuario> _registros = [];
-  List<Usuario> get registros => _registros;
-  Usuario? _selectedModelo;
-  Usuario? get selectedModelo => _selectedModelo;
+class UsuarioViewModel extends ChangeNotifier {
+  List<Usuario> _usuarios = [];
   bool _isLoading = false;
-  bool get isLoading => _isLoading;
-
-  Map<String, bool> _selectedPermisos = {};
-  Map<String, bool> get selectedPermisos => _selectedPermisos;
 
-  Future<void> fetchRegistros() async {
-    Map<String, String> parametros = {
-      "ordenar": "id-desc",
-      "expand": "empresas"
-    };
-    if (_busqueda.isNotEmpty) {
-      parametros = {
-        "ordenar": "id-desc",
-        "limite": "-1",
-        "q": _busqueda,
-        "expand": "empresas"
-      };
-    }
-    var r =
-        await BaseService().get("admin/usuario", queryParameters: parametros);
-    Map<String, dynamic> resJson = jsonDecode(r.body);
-    try {
-      List<Usuario> aux = [];
-      if (r.statusCode == 200) {
-        for (var x in resJson['resultado']) {
-          Usuario modelo = Usuario.fromJson(x);
-          aux.add(modelo);
-        }
-      }
-      _registros = aux;
-      notifyListeners();
-    } catch (e) {
-      print(e);
-    }
-  }
+  List<Usuario> get usuarios => _usuarios;
+  bool get isLoading => _isLoading;
 
-  Future<void> cargarPermisosUsuario(int usuarioId) async {
-    setIsLoading(true);
-    try {
-      var response = await BaseService().get("admin/usuario",
-          queryParameters: {'expand': 'permisos', 'id': '$usuarioId'});
-      Map<String, dynamic> resJson = jsonDecode(response.body);
+  int _currentPage = 1;
+  int _totalProducts = 0;
+  int _limit = 10;
 
-      List<dynamic> permisosUsuario = resJson['resultado'][0]['permisos'];
-      Map<String, bool> permisosActualizados = {};
+  int get currentPage => _currentPage;
+  int get totalProducts => _totalProducts;
+  int get totalPages => (_totalProducts / _limit).ceil();
 
-      for (var permiso in permisosUsuario) {
-        String permisoId = permiso.toString();
-        permisosActualizados[permisoId] = true;
-      }
+  Future<void> fetchLocalAll({int page = 1}) async {
+    _currentPage = page;
+    var db = await RepoService().db;
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM Usuario'));
+    _totalProducts = count ?? 0;
 
-      _selectedPermisos = permisosActualizados;
-      // print(_selectedPermisos);
-      notifyListeners(); // Avisar a los widgets que escuchan sobre el cambio
-    } catch (e) {
-      print("Error al cargar los permisos del usuario: $e");
-    } finally {
-      setIsLoading(false);
-    }
-  }
+    int offset = (_limit * (page - 1));
 
-  void setSelectedPermisos(Map<String, bool> permisos) {
-    _selectedPermisos = permisos;
-    notifyListeners();
-  }
+    var query = await db.query('Usuario',
+        where: 'eliminado IS NULL',
+        orderBy: 'id asc',
+        limit: _limit,
+        offset: offset);
+    _usuarios = query.map((element) => Usuario.fromJson(element)).toList();
 
-  Future<void> guardarModelo({
-    required Usuario modelo,
-    required String nombre,
-    required String correo,
-    required String telefono,
-  }) async {
-    //?TEMPORAL
-    modelo.nombre = nombre;
-    modelo.correo = correo;
-    modelo.telefono = telefono;
-    modelo.creado = DateTime.now();
     notifyListeners();
   }
 
-  Future<void> guardarUsuario({
-    required Usuario modelo,
-    required String nombre,
-    required String correo,
-    required List<String> permisosSeleccionados,
-  }) async {
-    setIsLoading(true);
+  Future<bool> sincronizarUsuarios() async {
     try {
-      var requestBody = {
-        'nombre': nombre,
-        'correo': correo,
-        'permisos': permisosSeleccionados,
-      };
-
-      if (modelo.id != null) {
-        requestBody['id'] = modelo.id;
-      }
-
-      var r =
-          await BaseService().post("admin/usuario/guardar", body: requestBody);
-      if (r.statusCode == 200) {
-        print('Usuario guardado exitosamente');
-      } else {
-        throw Exception('Error al guardar el usuario: ${r.statusCode}');
+      Map<String, String> parametros = {"expand": 'permisos'};
+      final response = ApiResponse(
+          await BaseService().get('/pos/usuario', queryParameters: parametros));
+
+      if (response.isOk && response.resultados != null) {
+        List<Usuario> usuariosApi =
+            response.resultados!.map((json) => Usuario.fromApi(json)).toList();
+        if (usuariosApi.isNotEmpty) {
+          await RepoService().sincronizarUsuarios(usuariosApi);
+          notifyListeners();
+          return true;
+        }
       }
+      return false;
     } catch (e) {
-      print('Error al guardar el usuario: $e');
-    } finally {
-      setIsLoading(false);
-    }
-  }
-
-  bool validarUsuario({
-    required String nombre,
-    required String correo,
-  }) {
-    if (nombre.isEmpty || correo.isEmpty) {
+      print('Error al sincronizar usuarios: $e');
       return false;
     }
-    return true;
-  }
-
-  setIsLoading(bool loading) {
-    _isLoading = loading;
-    notifyListeners();
-  }
-
-  selectUsuario(Usuario? usuario) {
-    _selectedModelo = usuario;
-    notifyListeners();
-  }
-
-  setBusqueda(String value) {
-    _busqueda = value;
-    notifyListeners();
-  }
-
-  Future usuarioEmpresa(int idUsuario, String idEmpresa) async {
-    var r = await BaseService().post("admin/usuario/usuario-empresa",
-        body: {"idUsuario": idUsuario, "idEmpresa": idEmpresa});
-    if (r.statusCode == 200) {}
   }
 }

+ 121 - 0
lib/viewmodels/variable_view_model.dart

@@ -0,0 +1,121 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import '../services/services.dart';
+import '../models/models.dart';
+
+class VariableViewModel extends ChangeNotifier {
+  String _busqueda = "";
+  String get busqueda => _busqueda;
+
+  List<Variable> _variables = [];
+  bool _isLoading = false;
+  Variable? _selectedVariable;
+
+  List<Variable> get variables => _variables;
+  Variable? get selectedVariable => _selectedVariable;
+  bool get isLoading => _isLoading;
+
+  int _currentPage = 1;
+  int _totalVariables = 0;
+  int _limit = 10;
+
+  int get currentPage => _currentPage;
+  int get totalVariables => _totalVariables;
+  int get totalPages => (_totalVariables / _limit).ceil();
+
+  setBusqueda(String value) {
+    _busqueda = value;
+    notifyListeners();
+  }
+
+  void selectVariable(Variable variable) {
+    _selectedVariable = variable;
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalAll({int page = 1}) async {
+    _currentPage = page;
+    var db = await RepoService().db;
+
+    int? count = Sqflite.firstIntValue(
+        await db!.rawQuery('SELECT COUNT(*) FROM Variable'));
+    _totalVariables = count ?? 0;
+
+    int offset = (_limit * (page - 1));
+
+    var query = await db.query('Variable',
+        orderBy: 'id asc', limit: _limit, offset: offset);
+    _variables = query.map((element) => Variable.fromJson(element)).toList();
+    notifyListeners();
+  }
+
+  Future<void> fetchLocalByName({required String nombre}) async {
+    var db = await RepoService().db;
+    var query = await db!.query(
+      'Variable',
+      where: 'nombre LIKE "%$nombre%"',
+      orderBy: 'id asc',
+    );
+    List<Variable> aux = [];
+    for (var element in query) {
+      Variable variable = Variable.fromJson(element);
+      aux.add(variable);
+    }
+    _variables = aux;
+    notifyListeners();
+  }
+
+  Future<void> addVariable(Variable variable) async {
+    await RepoService().guardar(variable);
+    fetchLocalAll();
+  }
+
+  Future<void> updateVariable(Variable variable) async {
+    setIsLoading(true);
+    try {
+      await RepoService().guardar(variable);
+      fetchLocalAll();
+    } catch (e) {
+      print('Error updating variable: $e');
+    }
+    setIsLoading(false);
+  }
+
+  Future<void> deleteVariable(int id) async {
+    await RepoService().eliminar<Variable>(id);
+    fetchLocalAll();
+  }
+
+  void setIsLoading(bool loading) {
+    _isLoading = loading;
+    notifyListeners();
+  }
+
+  void nextPage() {
+    if (_currentPage < totalPages) {
+      fetchLocalAll(page: _currentPage + 1);
+    }
+  }
+
+  void previousPage() {
+    if (_currentPage > 1) {
+      fetchLocalAll(page: _currentPage - 1);
+    }
+  }
+
+  Future<bool> isVariableActive(String clave) async {
+    var db = await RepoService().db;
+    var result = await db!.query(
+      'Variable',
+      where: 'clave = ?',
+      whereArgs: [clave],
+    );
+
+    if (result.isNotEmpty) {
+      var variable = Variable.fromJson(result.first);
+      return variable.activo == true;
+    }
+
+    return false;
+  }
+}

+ 0 - 0
lib/viewmodels/viewmodels.dart


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor