diff --git a/pacifictrans-app/.gitignore b/pacifictrans-app/.gitignore new file mode 100644 index 0000000..e80036c --- /dev/null +++ b/pacifictrans-app/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.gradle/ +build/ +android/app/build/ +android/.gradle/ +android/local.properties diff --git a/pacifictrans-app/android/.gitignore b/pacifictrans-app/android/.gitignore new file mode 100644 index 0000000..48354a3 --- /dev/null +++ b/pacifictrans-app/android/.gitignore @@ -0,0 +1,101 @@ +# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore + +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof + +# Cordova plugins for Capacitor +capacitor-cordova-android-plugins + +# Copied web assets +app/src/main/assets/public + +# Generated Config files +app/src/main/assets/capacitor.config.json +app/src/main/assets/capacitor.plugins.json +app/src/main/res/xml/config.xml diff --git a/pacifictrans-app/android/app/.gitignore b/pacifictrans-app/android/app/.gitignore new file mode 100644 index 0000000..043df80 --- /dev/null +++ b/pacifictrans-app/android/app/.gitignore @@ -0,0 +1,2 @@ +/build/* +!/build/.npmkeep diff --git a/pacifictrans-app/android/app/build.gradle b/pacifictrans-app/android/app/build.gradle index 9684452..357571b 100644 --- a/pacifictrans-app/android/app/build.gradle +++ b/pacifictrans-app/android/app/build.gradle @@ -1,29 +1,25 @@ -plugins { - id 'com.android.application' -} +apply plugin: 'com.android.application' android { - namespace 'co.pacifictrans.go' - compileSdk 35 - + namespace "co.pacifictrans.go" + compileSdk rootProject.ext.compileSdkVersion defaultConfig { applicationId "co.pacifictrans.go" - minSdk 24 - targetSdk 35 + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "1.0.0" - + versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + aaptOptions { + // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. + // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 + ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' + } } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - debug { - applicationIdSuffix "" - debuggable true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -34,9 +30,31 @@ android { } } -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.7.0' - implementation 'com.google.android.material:material:1.12.0' - implementation project(':capacitor-android') +repositories { + flatDir{ + dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" + implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" + implementation project(':capacitor-android') + testImplementation "junit:junit:$junitVersion" + androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" + androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" + implementation project(':capacitor-cordova-android-plugins') +} + +apply from: 'capacitor.build.gradle' + +try { + def servicesJSON = file('google-services.json') + if (servicesJSON.text) { + apply plugin: 'com.google.gms.google-services' + } +} catch(Exception e) { + logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") } diff --git a/pacifictrans-app/android/app/capacitor.build.gradle b/pacifictrans-app/android/app/capacitor.build.gradle new file mode 100644 index 0000000..d2f3951 --- /dev/null +++ b/pacifictrans-app/android/app/capacitor.build.gradle @@ -0,0 +1,19 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } +} + +apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" +dependencies { + implementation project(':capacitor-app-launcher') + +} + + +if (hasProperty('postBuildExtras')) { + postBuildExtras() +} diff --git a/pacifictrans-app/android/app/proguard-rules.pro b/pacifictrans-app/android/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/pacifictrans-app/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/pacifictrans-app/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java b/pacifictrans-app/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java new file mode 100644 index 0000000..f2c2217 --- /dev/null +++ b/pacifictrans-app/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.getcapacitor.myapp; + +import static org.junit.Assert.*; + +import android.content.Context; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.getcapacitor.app", appContext.getPackageName()); + } +} diff --git a/pacifictrans-app/android/app/src/main/AndroidManifest.xml b/pacifictrans-app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4d7ca38 --- /dev/null +++ b/pacifictrans-app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pacifictrans-app/android/app/src/main/java/co/pacifictrans/go/MainActivity.java b/pacifictrans-app/android/app/src/main/java/co/pacifictrans/go/MainActivity.java new file mode 100644 index 0000000..7cc3047 --- /dev/null +++ b/pacifictrans-app/android/app/src/main/java/co/pacifictrans/go/MainActivity.java @@ -0,0 +1,5 @@ +package co.pacifictrans.go; + +import com.getcapacitor.BridgeActivity; + +public class MainActivity extends BridgeActivity {} diff --git a/pacifictrans-app/android/app/src/main/res/drawable-land-hdpi/splash.png b/pacifictrans-app/android/app/src/main/res/drawable-land-hdpi/splash.png new file mode 100644 index 0000000..e31573b Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable-land-hdpi/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/drawable-land-mdpi/splash.png b/pacifictrans-app/android/app/src/main/res/drawable-land-mdpi/splash.png new file mode 100644 index 0000000..f7a6492 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable-land-mdpi/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/drawable-land-xhdpi/splash.png b/pacifictrans-app/android/app/src/main/res/drawable-land-xhdpi/splash.png new file mode 100644 index 0000000..8077255 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable-land-xhdpi/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/pacifictrans-app/android/app/src/main/res/drawable-land-xxhdpi/splash.png new file mode 100644 index 0000000..14c6c8f Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable-land-xxhdpi/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/pacifictrans-app/android/app/src/main/res/drawable-land-xxxhdpi/splash.png new file mode 100644 index 0000000..244ca25 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable-land-xxxhdpi/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/drawable-port-hdpi/splash.png b/pacifictrans-app/android/app/src/main/res/drawable-port-hdpi/splash.png new file mode 100644 index 0000000..74faaa5 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable-port-hdpi/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/drawable-port-mdpi/splash.png b/pacifictrans-app/android/app/src/main/res/drawable-port-mdpi/splash.png new file mode 100644 index 0000000..e944f4a Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable-port-mdpi/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/drawable-port-xhdpi/splash.png b/pacifictrans-app/android/app/src/main/res/drawable-port-xhdpi/splash.png new file mode 100644 index 0000000..564a82f Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable-port-xhdpi/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/pacifictrans-app/android/app/src/main/res/drawable-port-xxhdpi/splash.png new file mode 100644 index 0000000..bfabe68 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable-port-xxhdpi/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/pacifictrans-app/android/app/src/main/res/drawable-port-xxxhdpi/splash.png new file mode 100644 index 0000000..6929071 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable-port-xxxhdpi/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/pacifictrans-app/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/pacifictrans-app/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/pacifictrans-app/android/app/src/main/res/drawable/ic_launcher_background.xml b/pacifictrans-app/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/pacifictrans-app/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pacifictrans-app/android/app/src/main/res/drawable/splash.png b/pacifictrans-app/android/app/src/main/res/drawable/splash.png new file mode 100644 index 0000000..f7a6492 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/drawable/splash.png differ diff --git a/pacifictrans-app/android/app/src/main/res/layout/activity_main.xml b/pacifictrans-app/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..b5ad138 --- /dev/null +++ b/pacifictrans-app/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/pacifictrans-app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/pacifictrans-app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/pacifictrans-app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/pacifictrans-app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pacifictrans-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..c023e50 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/pacifictrans-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..2127973 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/pacifictrans-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..b441f37 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pacifictrans-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..72905b8 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/pacifictrans-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8ed0605 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/pacifictrans-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..9502e47 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pacifictrans-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..4d1e077 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/pacifictrans-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..df0f158 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/pacifictrans-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..853db04 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pacifictrans-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..6cdf97c Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/pacifictrans-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..2960cbb Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/pacifictrans-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..8e3093a Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pacifictrans-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..46de6e2 Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/pacifictrans-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..d2ea9ab Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/pacifictrans-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/pacifictrans-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..a40d73e Binary files /dev/null and b/pacifictrans-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/pacifictrans-app/android/app/src/main/res/values/ic_launcher_background.xml b/pacifictrans-app/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/pacifictrans-app/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/pacifictrans-app/android/app/src/main/res/values/strings.xml b/pacifictrans-app/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..fcf91c1 --- /dev/null +++ b/pacifictrans-app/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + Pacifictrans Go + Pacifictrans Go + co.pacifictrans.go + co.pacifictrans.go + diff --git a/pacifictrans-app/android/app/src/main/res/values/styles.xml b/pacifictrans-app/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..be874e5 --- /dev/null +++ b/pacifictrans-app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/pacifictrans-app/android/app/src/main/res/xml/file_paths.xml b/pacifictrans-app/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..bd0c4d8 --- /dev/null +++ b/pacifictrans-app/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/pacifictrans-app/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java b/pacifictrans-app/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java new file mode 100644 index 0000000..0297327 --- /dev/null +++ b/pacifictrans-app/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java @@ -0,0 +1,18 @@ +package com.getcapacitor.myapp; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} diff --git a/pacifictrans-app/android/build.gradle b/pacifictrans-app/android/build.gradle new file mode 100644 index 0000000..85a5dda --- /dev/null +++ b/pacifictrans-app/android/build.gradle @@ -0,0 +1,29 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.2.1' + classpath 'com.google.gms:google-services:4.4.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +apply from: "variables.gradle" + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/pacifictrans-app/android/capacitor.settings.gradle b/pacifictrans-app/android/capacitor.settings.gradle new file mode 100644 index 0000000..9797f21 --- /dev/null +++ b/pacifictrans-app/android/capacitor.settings.gradle @@ -0,0 +1,6 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN +include ':capacitor-android' +project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') + +include ':capacitor-app-launcher' +project(':capacitor-app-launcher').projectDir = new File('../node_modules/@capacitor/app-launcher/android') diff --git a/pacifictrans-app/android/gradle.properties b/pacifictrans-app/android/gradle.properties new file mode 100644 index 0000000..2e87c52 --- /dev/null +++ b/pacifictrans-app/android/gradle.properties @@ -0,0 +1,22 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true diff --git a/pacifictrans-app/android/gradle/wrapper/gradle-wrapper.jar b/pacifictrans-app/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..033e24c Binary files /dev/null and b/pacifictrans-app/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/pacifictrans-app/android/gradle/wrapper/gradle-wrapper.properties b/pacifictrans-app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c747538 --- /dev/null +++ b/pacifictrans-app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/pacifictrans-app/android/gradlew b/pacifictrans-app/android/gradlew new file mode 100644 index 0000000..fcb6fca --- /dev/null +++ b/pacifictrans-app/android/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/pacifictrans-app/android/gradlew.bat b/pacifictrans-app/android/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/pacifictrans-app/android/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pacifictrans-app/android/settings.gradle b/pacifictrans-app/android/settings.gradle new file mode 100644 index 0000000..3b4431d --- /dev/null +++ b/pacifictrans-app/android/settings.gradle @@ -0,0 +1,5 @@ +include ':app' +include ':capacitor-cordova-android-plugins' +project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') + +apply from: 'capacitor.settings.gradle' \ No newline at end of file diff --git a/pacifictrans-app/android/variables.gradle b/pacifictrans-app/android/variables.gradle new file mode 100644 index 0000000..8ef305d --- /dev/null +++ b/pacifictrans-app/android/variables.gradle @@ -0,0 +1,16 @@ +ext { + minSdkVersion = 22 + compileSdkVersion = 34 + targetSdkVersion = 34 + androidxActivityVersion = '1.8.0' + androidxAppCompatVersion = '1.6.1' + androidxCoordinatorLayoutVersion = '1.2.0' + androidxCoreVersion = '1.12.0' + androidxFragmentVersion = '1.6.2' + coreSplashScreenVersion = '1.0.1' + androidxWebkitVersion = '1.9.0' + junitVersion = '4.13.2' + androidxJunitVersion = '1.1.5' + androidxEspressoCoreVersion = '3.5.1' + cordovaAndroidVersion = '10.1.1' +} \ No newline at end of file diff --git a/pacifictrans-app/package-lock.json b/pacifictrans-app/package-lock.json new file mode 100644 index 0000000..263f049 --- /dev/null +++ b/pacifictrans-app/package-lock.json @@ -0,0 +1,1269 @@ +{ + "name": "pacifictrans-app", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pacifictrans-app", + "version": "1.0.0", + "dependencies": { + "@capacitor/android": "^6.2.0", + "@capacitor/app-launcher": "^6.0.2", + "@capacitor/core": "^6.2.0" + }, + "devDependencies": { + "@capacitor/cli": "^6.2.0" + } + }, + "node_modules/@capacitor/android": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-6.2.2.tgz", + "integrity": "sha512-JjIu1ryNE16PQTp51rb4NEygxP7cSCDMmUqx4f5f12xscKdGKTjO9bkEP3TroLnTa/J6p2QopIhRbrd3E/7DrA==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^6.2.0" + } + }, + "node_modules/@capacitor/app-launcher": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@capacitor/app-launcher/-/app-launcher-6.0.4.tgz", + "integrity": "sha512-tOQOChwul08k8y8RFx/OzMhNoxN8tbuc7CdWTwr3D9gffKCnyQ6vYhNZztDt1kIAo/qTJG5n8etDhV3XiQutHA==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^6.0.0" + } + }, + "node_modules/@capacitor/cli": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-6.2.2.tgz", + "integrity": "sha512-5wEYSHBzRKJKZnhD2sVJAP0t0kUT7o2/vlgEGzFE3t2w+inL9qMDN7zxg6pvqveJiPNZT2k93ZvJDdRKIn/w1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ionic/cli-framework-output": "^2.2.5", + "@ionic/utils-fs": "^3.1.6", + "@ionic/utils-subprocess": "2.1.11", + "@ionic/utils-terminal": "^2.3.3", + "commander": "^9.3.0", + "debug": "^4.3.4", + "env-paths": "^2.2.0", + "kleur": "^4.1.4", + "native-run": "^2.0.0", + "open": "^8.4.0", + "plist": "^3.0.5", + "prompts": "^2.4.2", + "rimraf": "^4.4.1", + "semver": "^7.3.7", + "tar": "^6.1.11", + "tslib": "^2.4.0", + "xml2js": "^0.5.0" + }, + "bin": { + "cap": "bin/capacitor", + "capacitor": "bin/capacitor" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@capacitor/core": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.2.tgz", + "integrity": "sha512-qEI3DcwJEPzhW+hVGKH8m4jbujtkdUiGkpMLHmpR8epACU7wEsV6NF8pCHzpsMYjc80oK7Cmpcw+MIienv/YRw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@ionic/cli-framework-output": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ionic/cli-framework-output/-/cli-framework-output-2.2.8.tgz", + "integrity": "sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ionic/utils-terminal": "2.3.5", + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-array": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.5.tgz", + "integrity": "sha512-HD72a71IQVBmQckDwmA8RxNVMTbxnaLbgFOl+dO5tbvW9CkkSFCv41h6fUuNsSEVgngfkn0i98HDuZC8mk+lTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=10.3.0" + } + }, + "node_modules/@ionic/utils-fs": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.7.tgz", + "integrity": "sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^8.0.0", + "debug": "^4.0.0", + "fs-extra": "^9.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-object": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-object/-/utils-object-2.1.5.tgz", + "integrity": "sha512-XnYNSwfewUqxq+yjER1hxTKggftpNjFLJH0s37jcrNDwbzmbpFTQTVAp4ikNK4rd9DOebX/jbeZb8jfD86IYxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=10.3.0" + } + }, + "node_modules/@ionic/utils-process": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@ionic/utils-process/-/utils-process-2.1.10.tgz", + "integrity": "sha512-mZ7JEowcuGQK+SKsJXi0liYTcXd2bNMR3nE0CyTROpMECUpJeAvvaBaPGZf5ERQUPeWBVuwqAqjUmIdxhz5bxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ionic/utils-object": "2.1.5", + "@ionic/utils-terminal": "2.3.3", + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=10.3.0" + } + }, + "node_modules/@ionic/utils-process/node_modules/@ionic/utils-terminal": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.3.tgz", + "integrity": "sha512-RnuSfNZ5fLEyX3R5mtcMY97cGD1A0NVBbarsSQ6yMMfRJ5YHU7hHVyUfvZeClbqkBC/pAqI/rYJuXKCT9YeMCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/slice-ansi": "^4.0.0", + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "slice-ansi": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "tslib": "^2.0.1", + "untildify": "^4.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.3.0" + } + }, + "node_modules/@ionic/utils-stream": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-stream/-/utils-stream-3.1.5.tgz", + "integrity": "sha512-hkm46uHvEC05X/8PHgdJi4l4zv9VQDELZTM+Kz69odtO9zZYfnt8DkfXHJqJ+PxmtiE5mk/ehJWLnn/XAczTUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=10.3.0" + } + }, + "node_modules/@ionic/utils-subprocess": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@ionic/utils-subprocess/-/utils-subprocess-2.1.11.tgz", + "integrity": "sha512-6zCDixNmZCbMCy5np8klSxOZF85kuDyzZSTTQKQP90ZtYNCcPYmuFSzaqDwApJT4r5L3MY3JrqK1gLkc6xiUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ionic/utils-array": "2.1.5", + "@ionic/utils-fs": "3.1.6", + "@ionic/utils-process": "2.1.10", + "@ionic/utils-stream": "3.1.5", + "@ionic/utils-terminal": "2.3.3", + "cross-spawn": "^7.0.3", + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=10.3.0" + } + }, + "node_modules/@ionic/utils-subprocess/node_modules/@ionic/utils-fs": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.6.tgz", + "integrity": "sha512-eikrNkK89CfGPmexjTfSWl4EYqsPSBh0Ka7by4F0PLc1hJZYtJxUZV3X4r5ecA8ikjicUmcbU7zJmAjmqutG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^8.0.0", + "debug": "^4.0.0", + "fs-extra": "^9.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=10.3.0" + } + }, + "node_modules/@ionic/utils-subprocess/node_modules/@ionic/utils-terminal": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.3.tgz", + "integrity": "sha512-RnuSfNZ5fLEyX3R5mtcMY97cGD1A0NVBbarsSQ6yMMfRJ5YHU7hHVyUfvZeClbqkBC/pAqI/rYJuXKCT9YeMCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/slice-ansi": "^4.0.0", + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "slice-ansi": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "tslib": "^2.0.1", + "untildify": "^4.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.3.0" + } + }, + "node_modules/@ionic/utils-terminal": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.5.tgz", + "integrity": "sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/slice-ansi": "^4.0.0", + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "slice-ansi": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "tslib": "^2.0.1", + "untildify": "^4.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@types/fs-extra": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", + "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "26.6.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", + "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.12.tgz", + "integrity": "sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/bplist-parser": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", + "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "big-integer": "1.6.x" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/elementtree": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz", + "integrity": "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "sax": "1.1.4" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/native-run": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.3.tgz", + "integrity": "sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ionic/utils-fs": "^3.1.7", + "@ionic/utils-terminal": "^2.3.4", + "bplist-parser": "^0.3.2", + "debug": "^4.3.4", + "elementtree": "^0.1.7", + "ini": "^4.1.1", + "plist": "^3.1.0", + "split2": "^4.2.0", + "through2": "^4.0.2", + "tslib": "^2.6.2", + "yauzl": "^2.10.0" + }, + "bin": { + "native-run": "bin/native-run" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/plist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", + "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.9.10", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rimraf": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", + "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^9.2.0" + }, + "bin": { + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz", + "integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==", + "dev": true, + "license": "ISC" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/pacifictrans-app/public/index.html b/pacifictrans-app/public/index.html new file mode 100644 index 0000000..a78c98e --- /dev/null +++ b/pacifictrans-app/public/index.html @@ -0,0 +1,59 @@ + + + + + + Pacifictrans Go + + + + +

Pacifictrans Go

+

Conectando con la Central de Despacho Cali - Buenaventura...

+
+ + + + diff --git a/scripts/deploy_pacifictrans_apk.py b/scripts/deploy_pacifictrans_apk.py index 3cf4d4c..e1e9867 100644 --- a/scripts/deploy_pacifictrans_apk.py +++ b/scripts/deploy_pacifictrans_apk.py @@ -71,16 +71,17 @@ def main(): print("[4/4] Enviando notificación de Pacifictrans Go a WhatsApp (+57 316 745 5877)...", flush=True) text_msg = ( - "🚗 *Pacifictrans Go: Tarifas Base Configurables, Sistema de Bonos de Descuento & Reserva Multi-Cupos*\n\n" - "¡Hola Carlos! Se ha actualizado la plataforma con las nuevas capacidades operativas y comerciales:\n\n" + "👑 *Pacifictrans Go: Superadministrador Permanente, Seguridad 2FA OTP & Gestión de Administradores*\n\n" + "¡Hola Carlos! Se ha actualizado la plataforma con el nuevo esquema de seguridad y control de acceso:\n\n" "📱 *App Oficial:* `Pacifictrans Go`\n" - "📦 *Archivo APK:* `PacifictransGo.apk` (37.07 MB)\n" - "🆔 *Package ID:* `co.pacifictrans.go`\n\n" - "⚡ *Novedades Implementadas:*\n" - "• ⚙️ *Definición de Tarifas Base en Panel Admin:* Control en vivo persistido en MongoDB para la tarifa fija de pasajeros ($35.000 COP), radio de pooling, penalidad por cancelación, tarifa base de encomiendas ($25.000 COP), costo por km ($1.200 COP/km), factores de peso/volumen (sobre, mediano, grande), seguro y comisión de plataforma (15%).\n" - "• 🎟️ *Sistema de Bonos & Cupones de Descuento:* Creación y administración centralizada de promociones en el Administrador. Permite descuentos en porcentaje (%) o valor fijo en pesos ($ COP), configurando si aplican para VIAJES, ENCOMIENDAS o AMBOS, subtotal mínimo y límite de canjes. Incluye cupones semilla predefinidos (ej. PACIFICO10, BONOVIAJE5K, ENVIOYA, PROMO20).\n" - "• 💳 *Validación y Descuento Reactivo en Pasarela:* Los clientes pueden ingresar su cupón tanto en la reserva de viajes como en la cotización de fletes. El sistema valida el monto mínimo y calcula el descuento en tiempo real antes de generar la orden en Bold Smart Checkout.\n" - "• 👥 *Reserva Multi-Cupo (1 a 4 Asientos):* Posibilidad de reservar varios cupos a la vez ingresando los datos de cada pasajero (Nombre completo, Documento C.C., Teléfono/WhatsApp, dirección de recogida domiciliaria independiente y notas de equipaje).\n\n" + "📦 *Archivo APK:* `PacifictransGo.apk` (3.57 MB - APK Nativo e Independiente)\n" + "🆔 *Package ID:* `co.pacifictrans.go` (100% independiente de DripZone)\n\n" + "⚡ *Novedades de la Versión:*\n" + "• 📱 *App Totalmente Independiente:* Se recompiló el APK nativo con su propio Package ID (`co.pacifictrans.go`). Ya no sobreescribe ni interfiere con DripZone; ambas aplicaciones conviven por separado con sus propios íconos y datos.\n" + "• 👑 *Superadministrador Exclusivo (+57 316 745 5877):* Tu número personal es el único reconocido con rol de Superadministrador inmutable en MongoDB. Es permanente y no puede ser borrado, inactivado ni degradado bajo ninguna circunstancia.\n" + "• 🔐 *Acceso Obligatorio con Token / OTP WhatsApp:* El acceso administrativo siempre exige validación del código OTP 2FA de 6 dígitos enviado a tu WhatsApp personal. Se bloquearon accesos rápidos sin código para garantizar total seguridad.\n" + "• 👥 *Sub-panel de Gestión de Administradores:* En la Sub-pestaña 5 del Panel Administrativo, puedes registrar nuevos administradores u operadores, editar sus datos y roles, activarlos o inactivarlos en tiempo real, o removerlos del sistema.\n" + "• 👤 *Actualización de Información Personal:* Los usuarios pueden actualizar Nombre, C.C., Correo, Dirección y Ciudad. El número celular de registro permanece bloqueado e inmutable para preservar su identidad 2FA OTP.\n\n" f"🔗 *Enlace de Descarga Directa:*\n👉 {APK_URL}" ) diff --git a/transporte-cali-buenaventura/backend/public/css/style.css b/transporte-cali-buenaventura/backend/public/css/style.css index ff1427f..0ab4c10 100644 --- a/transporte-cali-buenaventura/backend/public/css/style.css +++ b/transporte-cali-buenaventura/backend/public/css/style.css @@ -2429,3 +2429,100 @@ h1, h2, h3, h4, .brand-title { color: #fff; } +/* ========================================================================== + SUPERADMIN & EQUIPO DE ADMINISTRADORES & PERFIL INMUTABLE + ========================================================================== */ +.superadmin-badge { + display: inline-flex; + align-items: center; + gap: 0.35rem; + background: linear-gradient(135deg, rgba(245, 158, 11, 0.25), rgba(217, 119, 6, 0.35)); + color: #fbbf24; + border: 1px solid rgba(245, 158, 11, 0.5); + padding: 0.2rem 0.65rem; + border-radius: 9999px; + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.04em; + box-shadow: 0 0 10px rgba(245, 158, 11, 0.2); +} + +.admin-role-badge { + display: inline-block; + padding: 0.2rem 0.6rem; + border-radius: 8px; + font-size: 0.72rem; + font-weight: 700; +} + +.admin-role-super { + background: rgba(245, 158, 11, 0.2); + color: #fbbf24; + border: 1px solid rgba(245, 158, 11, 0.4); +} + +.admin-role-admin { + background: rgba(14, 165, 233, 0.2); + color: #38bdf8; + border: 1px solid rgba(14, 165, 233, 0.4); +} + +.admin-role-operador { + background: rgba(168, 85, 247, 0.2); + color: #c084fc; + border: 1px solid rgba(168, 85, 247, 0.4); +} + +.input-locked { + background: rgba(15, 23, 42, 0.85) !important; + color: #94a3b8 !important; + border-color: rgba(255, 255, 255, 0.08) !important; + cursor: not-allowed !important; +} + +.input-locked-note { + display: flex; + align-items: center; + gap: 0.35rem; + font-size: 0.73rem; + color: #fbbf24; + margin-top: 4px; + line-height: 1.3; +} + +.btn-admin-action { + padding: 0.3rem 0.65rem; + border-radius: 6px; + font-size: 0.75rem; + font-weight: 600; + cursor: pointer; + border: none; + transition: all 0.2s; +} + +.btn-admin-edit { + background: rgba(6, 182, 212, 0.15); + color: #38bdf8; + border: 1px solid rgba(6, 182, 212, 0.3); +} + +.btn-admin-edit:hover { + background: rgba(6, 182, 212, 0.3); + color: #fff; +} + +.btn-admin-immutable { + background: rgba(245, 158, 11, 0.12); + color: #fbbf24; + border: 1px solid rgba(245, 158, 11, 0.3); + font-size: 0.72rem; + padding: 0.3rem 0.6rem; + border-radius: 6px; + cursor: not-allowed; + display: inline-flex; + align-items: center; + gap: 0.25rem; +} + + diff --git a/transporte-cali-buenaventura/backend/public/index.html b/transporte-cali-buenaventura/backend/public/index.html index b5275db..ca22295 100644 --- a/transporte-cali-buenaventura/backend/public/index.html +++ b/transporte-cali-buenaventura/backend/public/index.html @@ -64,7 +64,8 @@ 🔐 Ingresar / Registro @@ -714,6 +715,9 @@ + @@ -737,6 +741,9 @@ + @@ -1179,6 +1186,134 @@ + + + @@ -1784,6 +1919,129 @@ + + + + + + diff --git a/transporte-cali-buenaventura/backend/public/js/app.js b/transporte-cali-buenaventura/backend/public/js/app.js index 9219178..2502c9a 100644 --- a/transporte-cali-buenaventura/backend/public/js/app.js +++ b/transporte-cali-buenaventura/backend/public/js/app.js @@ -36,6 +36,8 @@ document.addEventListener('DOMContentLoaded', () => { initTariffsManager(); initCouponsManager(); initSettlementsAndBreBSystem(); + initUserProfileManager(); + initAdministratorsManager(); fetchTariffs(); fetchAdminCoupons(); fetchStatsAndReviews(); @@ -127,10 +129,18 @@ function initTabs() { if (adminGate) adminGate.style.display = 'none'; if (adminContent) adminContent.style.display = 'block'; const adminNameEl = document.getElementById('admin-user-name'); - if (adminNameEl) adminNameEl.textContent = `${currentUser.nombre} (${currentUser.rol})`; + if (adminNameEl) { + const roleBadge = currentUser.esSuperAdmin ? '👑 Superadministrador' : (currentUser.rol === 'ADMIN' ? '🛡️ Administrador' : '🎧 Operador'); + adminNameEl.textContent = `${currentUser.nombre} (${roleBadge})`; + } + // Visibilidad de subpestaña de gestión de administradores (solo ADMIN / Superadmin) + document.querySelectorAll('#btn-admin-subtab-administradores').forEach(b => { + b.style.display = (currentUser.rol === 'ADMIN' || currentUser.esSuperAdmin) ? 'inline-flex' : 'none'; + }); if (leafletMap) setTimeout(() => leafletMap.invalidateSize(), 250); if (window.fetchSettlementsOverview) window.fetchSettlementsOverview(); if (window.fetchAdminPayoutRequests) window.fetchAdminPayoutRequests(); + if (window.fetchAdministrators) window.fetchAdministrators(); } else { if (adminGate) adminGate.style.display = 'block'; if (adminContent) adminContent.style.display = 'none'; @@ -310,13 +320,15 @@ function initTabs() { const viewTarifas = document.getElementById('admin-view-tarifas'); const viewLiquidaciones = document.getElementById('admin-view-liquidaciones'); const viewCupones = document.getElementById('admin-view-cupones'); + const viewAdministradores = document.getElementById('admin-view-administradores'); function setAdminSubTab(targetViewId) { const allViews = [ { id: 'radar', el: viewRadar }, { id: 'tarifas', el: viewTarifas }, { id: 'liquidaciones', el: viewLiquidaciones }, - { id: 'cupones', el: viewCupones } + { id: 'cupones', el: viewCupones }, + { id: 'administradores', el: viewAdministradores } ]; allViews.forEach(v => { @@ -341,12 +353,16 @@ function initTabs() { if (targetViewId === 'cupones' && window.fetchAdminCoupons) { window.fetchAdminCoupons(); } + if (targetViewId === 'administradores' && window.fetchAdministrators) { + window.fetchAdministrators(); + } } document.querySelectorAll('#btn-admin-subtab-radar').forEach(b => b.addEventListener('click', () => setAdminSubTab('radar'))); document.querySelectorAll('#btn-admin-subtab-tarifas').forEach(b => b.addEventListener('click', () => setAdminSubTab('tarifas'))); document.querySelectorAll('#btn-admin-subtab-liquidaciones').forEach(b => b.addEventListener('click', () => setAdminSubTab('liquidaciones'))); document.querySelectorAll('#btn-admin-subtab-cupones').forEach(b => b.addEventListener('click', () => setAdminSubTab('cupones'))); + document.querySelectorAll('#btn-admin-subtab-administradores').forEach(b => b.addEventListener('click', () => setAdminSubTab('administradores'))); // Sub-tabs de la sección del conductor const subDriverWalletBtn = document.getElementById('btn-driver-subtab-billetera'); @@ -631,6 +647,29 @@ function initAuthSystem() { document.querySelectorAll('.btn-role-quick-login').forEach(btn => { btn.addEventListener('click', async () => { const role = btn.getAttribute('data-role') || 'ADMIN'; + + // El ingreso como Administrador SIEMPRE exige verificación de Token/OTP por WhatsApp + if (role === 'ADMIN' || role === 'OPERADOR') { + const phoneInput = document.getElementById('auth-phone'); + if (phoneInput) { + phoneInput.value = '3167455877'; + } + authMode = 'login'; + if (modeLoginBtn) modeLoginBtn.classList.add('active'); + if (modeRegisterBtn) modeRegisterBtn.classList.remove('active'); + if (groupName) groupName.style.display = 'none'; + + if (reqError) { + reqError.textContent = '🔒 Acceso de Administrador: Por políticas de seguridad 2FA, se enviará el código OTP a tu WhatsApp personal (+57 316 745 5877).'; + reqError.style.color = '#38bdf8'; + reqError.style.display = 'block'; + } + + // Enviar OTP vía WhatsApp + formStep1.dispatchEvent(new Event('submit')); + return; + } + btn.disabled = true; const originalText = btn.innerHTML; btn.innerHTML = '⏳ Accediendo...'; @@ -648,10 +687,7 @@ function initAuthSystem() { updateUserUI(currentUser); if (modal) modal.classList.remove('active'); - if (role === 'ADMIN') { - window.location.hash = '#admin'; - window.showView('admin'); - } else if (role === 'CONDUCTOR') { + if (role === 'CONDUCTOR') { window.location.hash = '#conductor'; window.showView('driver'); } else { @@ -723,7 +759,21 @@ function updateUserUI(user) { if (btnAuthOpen) btnAuthOpen.style.display = 'none'; if (userBadge) userBadge.style.display = 'inline-flex'; if (userNameEl) userNameEl.textContent = user.nombre.split(' ')[0]; - if (userRoleBadge) userRoleBadge.textContent = `✅ ${user.rol}`; + if (userRoleBadge) { + if (user.esSuperAdmin) { + userRoleBadge.className = 'badge-verified superadmin-badge'; + userRoleBadge.innerHTML = '👑 SUPERADMIN'; + } else if (user.rol === 'ADMIN') { + userRoleBadge.className = 'badge-verified admin-role-badge admin-role-admin'; + userRoleBadge.innerHTML = '🛡️ ADMIN'; + } else if (user.rol === 'OPERADOR') { + userRoleBadge.className = 'badge-verified admin-role-badge admin-role-operador'; + userRoleBadge.innerHTML = '🎧 OPERADOR'; + } else { + userRoleBadge.className = 'badge-verified'; + userRoleBadge.textContent = `✅ ${user.rol}`; + } + } // Mostrar accesos a consolas según rol del usuario if (navBtnAdmin) { @@ -3555,4 +3605,508 @@ window.deleteAdminCoupon = async function (couponId, code) { } }; +/* ========================================================================== + 10. USER PROFILE MANAGER (INFORMACIÓN PERSONAL - CELULAR INMUTABLE) + ========================================================================== */ +function initUserProfileManager() { + const modal = document.getElementById('modal-user-profile'); + const btnOpenNav = document.getElementById('btn-edit-profile-nav'); + const btnOpenUser = document.getElementById('btn-open-user-profile'); + const btnClose = document.getElementById('btn-close-profile-modal'); + const btnCancel = document.getElementById('btn-cancel-profile'); + const form = document.getElementById('form-update-profile'); + const feedback = document.getElementById('profile-feedback'); + const btnSave = document.getElementById('btn-save-profile'); + + const inputName = document.getElementById('profile-name'); + const inputPhone = document.getElementById('profile-phone'); + const inputDoc = document.getElementById('profile-doc'); + const inputEmail = document.getElementById('profile-email'); + const inputAddress = document.getElementById('profile-address'); + const inputCity = document.getElementById('profile-city'); + + function openProfileModal() { + if (!currentUser) { + const authModal = document.getElementById('modal-auth-dripzone'); + if (authModal) authModal.classList.add('active'); + return; + } + + if (inputName) inputName.value = currentUser.nombre || ''; + if (inputPhone) inputPhone.value = `+${currentUser.telefono || ''}`; + if (inputDoc) inputDoc.value = currentUser.documentoIdentidad || ''; + if (inputEmail) inputEmail.value = currentUser.email || ''; + if (inputAddress) inputAddress.value = currentUser.direccionTexto || ''; + if (inputCity) inputCity.value = currentUser.ciudad || 'Cali'; + + if (feedback) feedback.style.display = 'none'; + if (modal) modal.classList.add('active'); + } + + function closeProfileModal() { + if (modal) modal.classList.remove('active'); + } + + if (btnOpenNav) btnOpenNav.addEventListener('click', openProfileModal); + if (btnOpenUser) btnOpenUser.addEventListener('click', openProfileModal); + if (btnClose) btnClose.addEventListener('click', closeProfileModal); + if (btnCancel) btnCancel.addEventListener('click', closeProfileModal); + + if (form) { + form.addEventListener('submit', async (e) => { + e.preventDefault(); + if (!currentUser) return; + + const token = localStorage.getItem('pt_token') || ''; + if (!token) { + alert('Sesión no encontrada. Por favor inicia sesión nuevamente.'); + return; + } + + if (btnSave) { + btnSave.disabled = true; + btnSave.textContent = '⏳ Guardando...'; + } + if (feedback) feedback.style.display = 'none'; + + try { + const payload = { + nombre: inputName ? inputName.value.trim() : '', + documentoIdentidad: inputDoc ? inputDoc.value.trim() : '', + email: inputEmail ? inputEmail.value.trim() : '', + direccionTexto: inputAddress ? inputAddress.value.trim() : '', + ciudad: inputCity ? inputCity.value.trim() : 'Cali' + }; + + const res = await fetch('/api/users/profile', { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(payload) + }); + + const data = await res.json(); + if (res.ok && data.success) { + if (feedback) { + feedback.innerHTML = `
+ ✅ ${data.message || 'Información actualizada con éxito'} +
`; + feedback.style.display = 'block'; + } + if (data.user) { + currentUser = { ...currentUser, ...data.user }; + updateUserUI(currentUser); + } + setTimeout(() => { + closeProfileModal(); + }, 1200); + } else { + if (feedback) { + feedback.innerHTML = `
+ ❌ ${data.error || 'No fue posible actualizar tu información'} +
`; + feedback.style.display = 'block'; + } + } + } catch (err) { + console.error('Error updating user profile:', err); + if (feedback) { + feedback.innerHTML = `
+ ❌ Error de red al comunicar con el servidor +
`; + feedback.style.display = 'block'; + } + } finally { + if (btnSave) { + btnSave.disabled = false; + btnSave.textContent = '💾 Guardar Información Personal'; + } + } + }); + } +} + +/* ========================================================================== + 11. ADMINISTRATORS & ACCESS CONTROL MANAGER (EXCLUSIVO SUPERADMINISTRADOR) + ========================================================================== */ +function initAdministratorsManager() { + const formCreate = document.getElementById('form-create-admin'); + const btnRefresh = document.getElementById('btn-refresh-administrators'); + const feedbackCreate = document.getElementById('admin-create-feedback'); + const btnSubmitCreate = document.getElementById('btn-submit-new-admin'); + + // Modal Editar Administrador + const modalEdit = document.getElementById('modal-edit-admin'); + const btnCloseEdit = document.getElementById('btn-close-edit-admin-modal'); + const btnCancelEdit = document.getElementById('btn-cancel-edit-admin'); + const formEdit = document.getElementById('form-edit-admin'); + const feedbackEdit = document.getElementById('edit-admin-feedback'); + const btnSaveEdit = document.getElementById('btn-save-edit-admin'); + + const editId = document.getElementById('edit-admin-id'); + const editName = document.getElementById('edit-admin-name'); + const editPhone = document.getElementById('edit-admin-phone'); + const editDoc = document.getElementById('edit-admin-doc'); + const editEmail = document.getElementById('edit-admin-email'); + const editRole = document.getElementById('edit-admin-role'); + + if (btnRefresh) { + btnRefresh.addEventListener('click', () => fetchAdministrators()); + } + + function closeEditModal() { + if (modalEdit) modalEdit.classList.remove('active'); + } + + if (btnCloseEdit) btnCloseEdit.addEventListener('click', closeEditModal); + if (btnCancelEdit) btnCancelEdit.addEventListener('click', closeEditModal); + + // Crear Administrador + if (formCreate) { + formCreate.addEventListener('submit', async (e) => { + e.preventDefault(); + const token = localStorage.getItem('pt_token') || ''; + if (!token) return; + + const nameInput = document.getElementById('new-admin-name'); + const phoneInput = document.getElementById('new-admin-phone'); + const docInput = document.getElementById('new-admin-doc'); + const emailInput = document.getElementById('new-admin-email'); + const roleSelect = document.getElementById('new-admin-role'); + + if (btnSubmitCreate) { + btnSubmitCreate.disabled = true; + btnSubmitCreate.textContent = '⏳ Registrando...'; + } + if (feedbackCreate) feedbackCreate.style.display = 'none'; + + try { + const res = await fetch('/api/admin/administrators', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + nombre: nameInput ? nameInput.value.trim() : '', + telefono: phoneInput ? phoneInput.value.trim() : '', + documentoIdentidad: docInput ? docInput.value.trim() : '', + email: emailInput ? emailInput.value.trim() : '', + rol: roleSelect ? roleSelect.value : 'ADMIN' + }) + }); + + const data = await res.json(); + if (res.ok && data.success) { + if (feedbackCreate) { + feedbackCreate.innerHTML = `
+ ✅ Administrador ${data.administrator.nombre} registrado con éxito. +
`; + feedbackCreate.style.display = 'block'; + } + formCreate.reset(); + fetchAdministrators(); + setTimeout(() => { + if (feedbackCreate) feedbackCreate.style.display = 'none'; + }, 3500); + } else { + if (feedbackCreate) { + feedbackCreate.innerHTML = `
+ ❌ ${data.error || 'No fue posible registrar al administrador'} +
`; + feedbackCreate.style.display = 'block'; + } + } + } catch (err) { + console.error('Error creating administrator:', err); + } finally { + if (btnSubmitCreate) { + btnSubmitCreate.disabled = false; + btnSubmitCreate.textContent = '➕ Registrar Administrador'; + } + } + }); + } + + // Guardar edición de Administrador + if (formEdit) { + formEdit.addEventListener('submit', async (e) => { + e.preventDefault(); + const token = localStorage.getItem('pt_token') || ''; + if (!token) return; + + const adminId = editId ? editId.value : ''; + if (!adminId) return; + + if (btnSaveEdit) { + btnSaveEdit.disabled = true; + btnSaveEdit.textContent = '⏳ Guardando...'; + } + if (feedbackEdit) feedbackEdit.style.display = 'none'; + + try { + const payload = { + nombre: editName ? editName.value.trim() : '', + documentoIdentidad: editDoc ? editDoc.value.trim() : '', + email: editEmail ? editEmail.value.trim() : '', + rol: editRole ? editRole.value : 'ADMIN' + }; + + const res = await fetch(`/api/admin/administrators/${adminId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(payload) + }); + + const data = await res.json(); + if (res.ok && data.success) { + if (feedbackEdit) { + feedbackEdit.innerHTML = `
+ ✅ Cambios guardados correctamente. +
`; + feedbackEdit.style.display = 'block'; + } + fetchAdministrators(); + setTimeout(() => { + closeEditModal(); + }, 1200); + } else { + if (feedbackEdit) { + feedbackEdit.innerHTML = `
+ ❌ ${data.error || 'Error al guardar los cambios'} +
`; + feedbackEdit.style.display = 'block'; + } + } + } catch (err) { + console.error('Error updating admin:', err); + } finally { + if (btnSaveEdit) { + btnSaveEdit.disabled = false; + btnSaveEdit.textContent = '💾 Guardar Cambios'; + } + } + }); + } +} + +async function fetchAdministrators() { + const tbody = document.getElementById('table-administrators-body'); + const counterLabel = document.getElementById('admins-counter-label'); + const kpiAdmins = document.getElementById('kpi-admins-count'); + const kpiOperators = document.getElementById('kpi-operators-count'); + + try { + const token = localStorage.getItem('pt_token') || ''; + const res = await fetch('/api/admin/administrators', { + headers: { 'Authorization': `Bearer ${token}` } + }); + const data = await res.json(); + + if (!res.ok || !data.success) { + if (tbody) { + tbody.innerHTML = ` + ❌ ${data.error || 'Error al consultar equipo de administradores'} + `; + } + return; + } + + const admins = data.administrators || []; + + // Actualizar contadores + let adminCount = 0; + let operatorCount = 0; + admins.forEach(a => { + if (a.rol === 'OPERADOR') operatorCount++; + else adminCount++; + }); + + if (counterLabel) counterLabel.textContent = `${admins.length} miembro(s) registrados`; + if (kpiAdmins) kpiAdmins.textContent = adminCount; + if (kpiOperators) kpiOperators.textContent = operatorCount; + + if (!tbody) return; + if (admins.length === 0) { + tbody.innerHTML = ` + No hay administradores registrados en la plataforma. + `; + return; + } + + tbody.innerHTML = admins.map(admin => { + const isSuper = !!admin.esSuperAdmin; + const adminDataEncoded = encodeURIComponent(JSON.stringify(admin)); + + let roleBadge = ''; + if (isSuper) { + roleBadge = `👑 SUPERADMIN`; + } else if (admin.rol === 'ADMIN') { + roleBadge = `🛡️ ADMIN`; + } else { + roleBadge = `🎧 OPERADOR`; + } + + let statusHtml = ''; + if (isSuper) { + statusHtml = ` + 🔒 Permanente + `; + } else { + const isActive = admin.isActive !== false; + statusHtml = ` + + `; + } + + let actionsHtml = ''; + if (isSuper) { + actionsHtml = ``; + } else { + actionsHtml = ` +
+ + +
+ `; + } + + return ` + + +
+ ${isSuper ? '👑' : '🛡️'} + ${admin.nombre} + ${isSuper ? 'Carlos' : ''} +
+
+ ${admin.documentoIdentidad ? `C.C. ${admin.documentoIdentidad} • ` : ''}${admin.email || 'Sin correo registrado'} +
+ + +
+ 📲 + +${admin.telefono} +
+
2FA OTP Habilitado
+ + ${roleBadge} + ${statusHtml} + ${actionsHtml} + + `; + }).join(''); + + } catch (err) { + console.error('Error fetching administrators:', err); + if (tbody) { + tbody.innerHTML = ` + ❌ Error de conexión al cargar equipo de administradores + `; + } + } +} +window.fetchAdministrators = fetchAdministrators; + +window.openEditAdminModal = function (adminDataEncoded) { + try { + const admin = JSON.parse(decodeURIComponent(adminDataEncoded)); + const modal = document.getElementById('modal-edit-admin'); + const editId = document.getElementById('edit-admin-id'); + const editName = document.getElementById('edit-admin-name'); + const editPhone = document.getElementById('edit-admin-phone'); + const editDoc = document.getElementById('edit-admin-doc'); + const editEmail = document.getElementById('edit-admin-email'); + const editRole = document.getElementById('edit-admin-role'); + const editRoleGroup = document.getElementById('edit-admin-role-group'); + const subtitle = document.getElementById('edit-admin-subtitle'); + const feedback = document.getElementById('edit-admin-feedback'); + + if (editId) editId.value = admin.id || ''; + if (editName) editName.value = admin.nombre || ''; + if (editPhone) editPhone.value = `+${admin.telefono || ''}`; + if (editDoc) editDoc.value = admin.documentoIdentidad || ''; + if (editEmail) editEmail.value = admin.email || ''; + if (editRole) editRole.value = admin.rol || 'ADMIN'; + + if (admin.esSuperAdmin) { + if (editRoleGroup) editRoleGroup.style.display = 'none'; + if (subtitle) subtitle.textContent = 'Actualizar datos de contacto del Superadministrador permanente'; + } else { + if (editRoleGroup) editRoleGroup.style.display = 'block'; + if (subtitle) subtitle.textContent = 'Actualizar permisos y datos de contacto del colaborador'; + } + + if (feedback) feedback.style.display = 'none'; + if (modal) modal.classList.add('active'); + } catch (e) { + console.error('Error parsing admin for edit:', e); + } +}; + +window.toggleAdminActive = async function (adminId, nextState, adminName) { + try { + const token = localStorage.getItem('pt_token') || ''; + const res = await fetch(`/api/admin/administrators/${adminId}/toggle-active`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ id: adminId, isActive: nextState }) + }); + const data = await res.json(); + if (res.ok && data.success) { + fetchAdministrators(); + } else { + alert(`Error al cambiar estado: ${data.error || 'No permitido'}`); + } + } catch (err) { + console.error('Error toggling admin active:', err); + alert('Error al comunicar con el servidor.'); + } +}; + +window.deleteAdmin = async function (adminId, adminName) { + if (!confirm(`¿Estás seguro de eliminar a ${adminName || 'este administrador'} del equipo? Esta acción revoca inmediatamente sus permisos de acceso.`)) { + return; + } + try { + const token = localStorage.getItem('pt_token') || ''; + const res = await fetch(`/api/admin/administrators/${adminId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ id: adminId }) + }); + const data = await res.json(); + if (res.ok && data.success) { + fetchAdministrators(); + } else { + alert(`Error al eliminar administrador: ${data.error || 'No permitido'}`); + } + } catch (err) { + console.error('Error deleting administrator:', err); + alert('Error al comunicar con el servidor.'); + } +}; + + diff --git a/transporte-cali-buenaventura/backend/src/index.ts b/transporte-cali-buenaventura/backend/src/index.ts index 2f5fdea..c40cfbb 100644 --- a/transporte-cali-buenaventura/backend/src/index.ts +++ b/transporte-cali-buenaventura/backend/src/index.ts @@ -108,12 +108,22 @@ const server = http.createServer(async (req, res) => { return sendJson(200, { success: true, user }); } - // 1.4. Autenticación: Quick Role Login para alternar roles y probar seguridad de rutas + // 1.4. Autenticación: Quick Role Login para pruebas de roles públicos (PROHIBIDO para ADMIN / OPERADOR) if (pathname === '/api/auth/quick-role-login' && req.method === 'POST') { const body = await getJsonBody(); - const role = body.role || 'ADMIN'; - const result = await authService.createSessionForRole(role, body.name); - return sendJson(200, { success: true, ...result }); + const role = body.role || 'PASAJERO'; + if (role === 'ADMIN' || role === 'OPERADOR') { + return sendJson(403, { + success: false, + error: 'Acceso no permitido: El ingreso al rol Administrador requiere obligatoriamente verificar el código OTP enviado a tu número personal de WhatsApp (+57 316 745 5877).' + }); + } + try { + const result = await authService.createSessionForRole(role, body.name); + return sendJson(200, { success: true, ...result }); + } catch (err: any) { + return sendJson(403, { success: false, error: err.message }); + } } // Helper de seguridad por roles y permisos en rutas API @@ -123,14 +133,9 @@ const server = http.createServer(async (req, res) => { }; const requireRole = async (allowedRoles: ('ADMIN' | 'CONDUCTOR' | 'PASAJERO' | 'OPERADOR')[]) => { - const masterKey = req.headers['x-admin-key'] || url.searchParams.get('master_key'); - if (masterKey === 'admin_master_pacifictrans_2026') { - return { allowed: true, user: { rol: 'ADMIN', nombre: 'Admin Master' } }; - } const token = getToken(); if (!token) { - // En peticiones públicas sin token, denegar si es ruta estrictamente protegida - return { allowed: false, error: 'Autenticación requerida: Inicia sesión con credenciales autorizadas.' }; + return { allowed: false, error: 'Autenticación requerida: Inicia sesión con código OTP de seguridad.' }; } return authService.verifyRole(token, allowedRoles); }; @@ -205,7 +210,85 @@ const server = http.createServer(async (req, res) => { return sendJson(200, result); } - // 8. Administración de Usuarios (Estilo DripZone) + // 8. Administración de Usuarios y Perfil Personal + // 8.0. Actualización de Información Personal (No permite cambiar número de celular registrado) + if ((pathname === '/api/users/profile' || pathname === '/api/auth/profile') && req.method === 'GET') { + const auth = await requireRole(['PASAJERO', 'CONDUCTOR', 'ADMIN', 'OPERADOR']); + if (!auth.allowed) return sendJson(401, { success: false, error: auth.error }); + return sendJson(200, { success: true, user: auth.user }); + } + + if ((pathname === '/api/users/profile' || pathname === '/api/auth/profile') && (req.method === 'PATCH' || req.method === 'PUT' || req.method === 'POST')) { + const auth = await requireRole(['PASAJERO', 'CONDUCTOR', 'ADMIN', 'OPERADOR']); + if (!auth.allowed) return sendJson(401, { success: false, error: auth.error }); + const body = await getJsonBody(); + try { + const updated = await userAdminService.updateUserProfile(auth.user.id || auth.user._id, body); + return sendJson(200, { success: true, message: 'Información personal actualizada correctamente', user: updated }); + } catch (err: any) { + return sendJson(400, { success: false, error: err.message }); + } + } + + // 8.1. Gestión Centralizada de Administradores por el Superadministrador + if (pathname === '/api/admin/administrators' && req.method === 'GET') { + const auth = await requireRole(['ADMIN']); + if (!auth.allowed) return sendJson(403, { success: false, error: auth.error }); + const administrators = await userAdminService.getAdministrators(); + return sendJson(200, { success: true, count: administrators.length, administrators }); + } + + if (pathname === '/api/admin/administrators' && req.method === 'POST') { + const auth = await requireRole(['ADMIN']); + if (!auth.allowed) return sendJson(403, { success: false, error: auth.error }); + const body = await getJsonBody(); + try { + const administrator = await userAdminService.createAdministrator(body); + return sendJson(201, { success: true, administrator }); + } catch (err: any) { + return sendJson(400, { success: false, error: err.message }); + } + } + + if ((pathname === '/api/admin/administrators/update' || (pathname.startsWith('/api/admin/administrators/') && !pathname.endsWith('/toggle-active'))) && (req.method === 'PUT' || req.method === 'POST')) { + const auth = await requireRole(['ADMIN']); + if (!auth.allowed) return sendJson(403, { success: false, error: auth.error }); + const body = await getJsonBody(); + const adminId = pathname.split('/')[4] || body.id || body.adminId; + try { + const administrator = await userAdminService.updateAdministrator(adminId, body, auth.user.id); + return sendJson(200, { success: true, administrator }); + } catch (err: any) { + return sendJson(400, { success: false, error: err.message }); + } + } + + if ((pathname === '/api/admin/administrators/toggle-active' || (pathname.startsWith('/api/admin/administrators/') && pathname.endsWith('/toggle-active'))) && (req.method === 'POST' || req.method === 'PATCH')) { + const auth = await requireRole(['ADMIN']); + if (!auth.allowed) return sendJson(403, { success: false, error: auth.error }); + const body = await getJsonBody(); + const adminId = body.id || body.adminId || pathname.split('/')[4]; + try { + const administrator = await userAdminService.toggleAdministratorActive(adminId, body.isActive); + return sendJson(200, { success: true, administrator }); + } catch (err: any) { + return sendJson(400, { success: false, error: err.message }); + } + } + + if ((pathname === '/api/admin/administrators/delete' && req.method === 'POST') || (pathname.startsWith('/api/admin/administrators/') && req.method === 'DELETE')) { + const auth = await requireRole(['ADMIN']); + if (!auth.allowed) return sendJson(403, { success: false, error: auth.error }); + const body = await getJsonBody(); + const adminId = pathname.split('/')[4] || body.id || body.adminId; + try { + await userAdminService.deleteAdministrator(adminId, auth.user.id); + return sendJson(200, { success: true, message: 'Administrador eliminado exitosamente' }); + } catch (err: any) { + return sendJson(400, { success: false, error: err.message }); + } + } + if (pathname === '/api/users' && req.method === 'GET') { const users = await userAdminService.getAllUsers(); return sendJson(200, { success: true, count: users.length, users }); @@ -543,6 +626,7 @@ async function bootstrap() { await connectDatabase(); console.log('✅ [MongoDB] Conectado exitosamente en segundo plano.'); await couponService.ensureSeedCoupons(); + await userAdminService.ensureSuperAdminExists(); } catch (err: any) { console.warn(`⚠️ [MongoDB Warning] Conexión no disponible (${err.message || 'ENOTFOUND'}). Reintentando en 10s...`); setTimeout(tryConnect, 10000); diff --git a/transporte-cali-buenaventura/backend/src/models/User.ts b/transporte-cali-buenaventura/backend/src/models/User.ts index f1610ee..527e12b 100644 --- a/transporte-cali-buenaventura/backend/src/models/User.ts +++ b/transporte-cali-buenaventura/backend/src/models/User.ts @@ -12,6 +12,8 @@ export interface IUser extends Document { nombre: string; telefono: string; email?: string; + documentoIdentidad?: string; + esSuperAdmin?: boolean; rol: UserRole; saldoPenalidad: number; coordenadasDomicilio: IGeoPoint; @@ -86,6 +88,15 @@ const UserSchema = new Schema( trim: true, lowercase: true, }, + documentoIdentidad: { + type: String, + trim: true, + }, + esSuperAdmin: { + type: Boolean, + default: false, + index: true, + }, rol: { type: String, enum: ['PASAJERO', 'CONDUCTOR', 'ADMIN', 'OPERADOR'], diff --git a/transporte-cali-buenaventura/backend/src/services/auth.service.ts b/transporte-cali-buenaventura/backend/src/services/auth.service.ts index 38fd178..b4c72f1 100644 --- a/transporte-cali-buenaventura/backend/src/services/auth.service.ts +++ b/transporte-cali-buenaventura/backend/src/services/auth.service.ts @@ -1,7 +1,7 @@ import crypto from 'crypto'; import { User, IUser, UserRole } from '../models/User'; import { whatsAppNotificationService } from './whatsapp-notification.service'; -import { isPlatformSuperAdmin } from './user-admin.service'; +import { isPlatformSuperAdmin, SUPER_ADMIN_PHONE, SUPER_ADMIN_EMAIL } from './user-admin.service'; interface OtpRecord { code: string; @@ -82,13 +82,12 @@ export class AuthService { success: true, maskedPhone: masked, message: `Código enviado a ${masked} vía WhatsApp`, - // Para entornos locales sin SIM activa de WhatsApp devolvemos preview si no se pudo conectar codePreview: sent ? undefined : code, }; } /** - * Paso 2: Verificar OTP y emitir sesión autenticada estilo DripZone + * Paso 2: Verificar OTP y emitir sesión autenticada */ public async verifyOtp(params: { identifier: string; @@ -96,6 +95,7 @@ export class AuthService { nombre?: string; email?: string; direccionTexto?: string; + documentoIdentidad?: string; }): Promise<{ success: boolean; token?: string; @@ -143,12 +143,15 @@ export class AuthService { if (!user) { user = await User.create({ - appwriteId: `pt-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`, - nombre: params.nombre || (isSuper ? 'Administrador General' : `Usuario ${cleanPhone.slice(-4)}`), + appwriteId: isSuper ? 'usr-superadmin-carlos' : `pt-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`, + nombre: isSuper ? (params.nombre || 'Carlos Almeira') : (params.nombre || `Usuario ${cleanPhone.slice(-4)}`), telefono: cleanPhone, - email: userEmail, + email: userEmail || (isSuper ? SUPER_ADMIN_EMAIL : undefined), + documentoIdentidad: params.documentoIdentidad || (isSuper ? '3167455877' : undefined), direccionTexto: params.direccionTexto || 'Cali, Valle del Cauca', + ciudad: 'Cali', rol: assignedRole, + esSuperAdmin: isSuper, saldoPenalidad: 0, coordenadasDomicilio: { type: 'Point', @@ -160,20 +163,27 @@ export class AuthService { twoFactorMethod: 'whatsapp_otp', lastLogin: new Date(), }); - console.log(`👤 [Nuevo Usuario Registrado y Verificado]: ${user.nombre} (+${user.telefono}) - Rol: ${user.rol}`); + console.log(`👤 [Nuevo Usuario Verificado]: ${user.nombre} (+${user.telefono}) - Rol: ${user.rol} ${isSuper ? '(SUPERADMIN)' : ''}`); } else { user.phoneVerified = true; user.isActive = true; user.lastLogin = new Date(); + if (isSuper) { + user.rol = 'ADMIN'; + user.esSuperAdmin = true; + if (!user.nombre || user.nombre.startsWith('Usuario ')) { + user.nombre = 'Carlos Almeira'; + } + } + if (params.documentoIdentidad && !user.documentoIdentidad) { + user.documentoIdentidad = params.documentoIdentidad; + } if (params.nombre && user.nombre.startsWith('Usuario ')) { user.nombre = params.nombre; } if (userEmail && !user.email) { user.email = userEmail; } - if (isSuper) { - user.rol = 'ADMIN'; - } await user.save(); } @@ -187,13 +197,17 @@ export class AuthService { const userPayload = { id: user._id.toString(), + _id: user._id.toString(), nombre: user.nombre, telefono: user.telefono, email: user.email, + documentoIdentidad: user.documentoIdentidad, + direccionTexto: user.direccionTexto, + ciudad: user.ciudad, rol: user.rol, + esSuperAdmin: isSuper || !!user.esSuperAdmin, phoneVerified: user.phoneVerified, saldoPenalidad: user.saldoPenalidad, - direccionTexto: user.direccionTexto, }; return { @@ -217,15 +231,21 @@ export class AuthService { try { const user = await User.findById(session.userId); if (user) { + const isSuper = isPlatformSuperAdmin(user.telefono) || isPlatformSuperAdmin(user.email) || !!user.esSuperAdmin; return { id: user._id.toString(), + _id: user._id.toString(), nombre: user.nombre, telefono: user.telefono, email: user.email, - rol: user.rol, + documentoIdentidad: user.documentoIdentidad, + direccionTexto: user.direccionTexto, + ciudad: user.ciudad, + rol: isSuper ? 'ADMIN' : user.rol, + esSuperAdmin: isSuper, phoneVerified: user.phoneVerified, saldoPenalidad: user.saldoPenalidad, - direccionTexto: user.direccionTexto, + isActive: user.isActive, }; } } catch (e) { @@ -246,6 +266,9 @@ export class AuthService { if (!user) { return { allowed: false, error: 'Sesión expirada o no válida. Por favor inicia sesión nuevamente.' }; } + if (!user.isActive) { + return { allowed: false, error: 'Tu cuenta se encuentra inactiva. Contacta al Administrador.' }; + } if (!allowedRoles.includes(user.rol)) { return { allowed: false, @@ -257,17 +280,21 @@ export class AuthService { } /** - * Crea o reactiva una sesión rápida para un rol determinado (útil para auditoría, pruebas y panel) + * Crea o reactiva una sesión rápida para pruebas (PROHIBIDO para ADMIN / OPERADOR) + * Los roles de administración requieren obligatoriamente verificación OTP por WhatsApp */ public async createSessionForRole(role: UserRole, customName?: string): Promise<{ token: string; user: any }> { - let phone = '573167455877'; - let defaultName = 'Carlos Almeira (SuperAdmin)'; + if (role === 'ADMIN' || role === 'OPERADOR') { + throw new Error( + 'Acceso denegado: El rol Administrador requiere obligatoriamente verificación por código OTP enviado a tu número personal de WhatsApp (+57 316 745 5877).' + ); + } + + let phone = '573187654321'; + let defaultName = 'Pasajero Frecuente Cali'; if (role === 'CONDUCTOR') { phone = '573158923456'; defaultName = 'Mario Fernando G. (Renault Duster WLZ-890)'; - } else if (role === 'PASAJERO') { - phone = '573187654321'; - defaultName = 'Pasajero Frecuente Cali'; } let userDoc: any = null; @@ -289,7 +316,7 @@ export class AuthService { }); } } catch (err) { - // Si la BD no responde inmediatamente en local, continuar con usuario temporal + // Continuar con usuario temporal si falla DB temporalmente } const userId = userDoc?._id ? userDoc._id.toString() : `role-${role.toLowerCase()}-${Date.now()}`; @@ -300,6 +327,7 @@ export class AuthService { telefono: userDoc?.telefono || phone, email: userDoc?.email || `${role.toLowerCase()}@pacifictrans.almeira.com.co`, rol: role, + esSuperAdmin: false, phoneVerified: true, saldoPenalidad: 0, direccionTexto: 'Cali, Valle del Cauca', diff --git a/transporte-cali-buenaventura/backend/src/services/user-admin.service.ts b/transporte-cali-buenaventura/backend/src/services/user-admin.service.ts index 05b775b..776eaff 100644 --- a/transporte-cali-buenaventura/backend/src/services/user-admin.service.ts +++ b/transporte-cali-buenaventura/backend/src/services/user-admin.service.ts @@ -3,13 +3,18 @@ import { User, IUser, UserRole } from '../models/User'; import { DriverProfile, IDriverProfile } from '../models/DriverProfile'; import { boldPaymentService } from './bold.service'; -export const SUPER_ADMIN_PHONE = process.env.ADMIN_WHATSAPP_PHONE || '573167455877'; -export const SUPER_ADMIN_EMAIL = process.env.SUPER_ADMIN_EMAIL || 'admin@pacifictrans.almeira.com.co'; +export const SUPER_ADMIN_PHONE = '573167455877'; +export const SUPER_ADMIN_LOCAL_PHONE = '3167455877'; +export const SUPER_ADMIN_EMAIL = 'admin@pacifictrans.almeira.com.co'; export function isPlatformSuperAdmin(identifier?: string): boolean { if (!identifier) return false; const clean = identifier.replace(/\D/g, ''); - return clean === SUPER_ADMIN_PHONE || identifier.toLowerCase().trim() === SUPER_ADMIN_EMAIL; + return ( + clean === SUPER_ADMIN_PHONE || + clean === SUPER_ADMIN_LOCAL_PHONE || + identifier.toLowerCase().trim() === SUPER_ADMIN_EMAIL + ); } export interface UserSummaryDTO { @@ -18,6 +23,8 @@ export interface UserSummaryDTO { nombre: string; telefono: string; email?: string; + documentoIdentidad?: string; + esSuperAdmin?: boolean; rol: UserRole; isActive: boolean; phoneVerified: boolean; @@ -28,10 +35,39 @@ export interface UserSummaryDTO { twoFactorEnabled?: boolean; twoFactorMethod?: 'whatsapp_otp' | 'biometric' | 'both'; biometricEnabled?: boolean; + direccionTexto?: string; + ciudad?: string; driverProfile?: any | null; createdAt: Date; } +export interface CreateAdminDTO { + nombre: string; + telefono: string; + email?: string; + documentoIdentidad?: string; + rol?: UserRole; + direccionTexto?: string; +} + +export interface UpdateAdminDTO { + nombre?: string; + email?: string; + documentoIdentidad?: string; + rol?: UserRole; + direccionTexto?: string; + ciudad?: string; +} + +export interface UpdateUserProfileDTO { + nombre?: string; + email?: string; + documentoIdentidad?: string; + direccionTexto?: string; + ciudad?: string; + telefono?: string; // Si se envía un teléfono diferente al registrado, se denegará +} + export class UserAdminService { private static instance: UserAdminService; @@ -57,7 +93,7 @@ export class UserAdminService { driverProfile = await DriverProfile.findOne({ userId: u._id }).lean(); } - const isSuper = isPlatformSuperAdmin(u.telefono) || isPlatformSuperAdmin(u.email); + const isSuper = isPlatformSuperAdmin(u.telefono) || isPlatformSuperAdmin(u.email) || !!u.esSuperAdmin; result.push({ id: u._id.toString(), @@ -65,6 +101,8 @@ export class UserAdminService { nombre: u.nombre, telefono: u.telefono, email: u.email, + documentoIdentidad: u.documentoIdentidad, + esSuperAdmin: isSuper, rol: isSuper ? 'ADMIN' : u.rol, isActive: isSuper ? true : u.isActive, phoneVerified: isSuper ? true : u.phoneVerified, @@ -75,6 +113,8 @@ export class UserAdminService { twoFactorEnabled: u.twoFactorEnabled, twoFactorMethod: u.twoFactorMethod, biometricEnabled: u.biometricEnabled, + direccionTexto: u.direccionTexto, + ciudad: u.ciudad, driverProfile, createdAt: u.createdAt, }); @@ -83,6 +123,328 @@ export class UserAdminService { return result; } + /** + * Obtiene exclusivamente el equipo de administradores y operadores + * El Superadministrador siempre aparece en primera posición + */ + public async getAdministrators(): Promise { + // Asegurar que el Superadministrador esté registrado + await this.ensureSuperAdminExists(); + + const admins = await User.find({ + $or: [ + { rol: { $in: ['ADMIN', 'OPERADOR'] } }, + { esSuperAdmin: true }, + { telefono: { $in: [SUPER_ADMIN_PHONE, SUPER_ADMIN_LOCAL_PHONE] } }, + ], + }) + .sort({ createdAt: -1 }) + .lean(); + + const dtos: UserSummaryDTO[] = admins.map((u) => { + const isSuper = isPlatformSuperAdmin(u.telefono) || isPlatformSuperAdmin(u.email) || !!u.esSuperAdmin; + return { + id: u._id.toString(), + appwriteId: u.appwriteId, + nombre: u.nombre, + telefono: u.telefono, + email: u.email, + documentoIdentidad: u.documentoIdentidad, + esSuperAdmin: isSuper, + rol: isSuper ? 'ADMIN' : u.rol, + isActive: isSuper ? true : u.isActive, + phoneVerified: isSuper ? true : u.phoneVerified, + saldoPenalidad: 0, + bloqueadoPorPenalidad: false, + totalViajesCompletados: u.totalViajesCompletados || 0, + totalViajesCancelados: u.totalViajesCancelados || 0, + direccionTexto: u.direccionTexto, + ciudad: u.ciudad, + createdAt: u.createdAt, + }; + }); + + // Superadministrador primero + return dtos.sort((a, b) => (b.esSuperAdmin ? 1 : 0) - (a.esSuperAdmin ? 1 : 0)); + } + + /** + * Asegura la existencia y rol inmutable del Superadministrador en MongoDB + */ + public async ensureSuperAdminExists(): Promise { + let superAdmin = await User.findOne({ + $or: [ + { telefono: SUPER_ADMIN_PHONE }, + { telefono: SUPER_ADMIN_LOCAL_PHONE }, + ], + }); + + if (!superAdmin) { + superAdmin = await User.create({ + appwriteId: 'usr-superadmin-carlos', + nombre: 'Carlos Almeira', + telefono: SUPER_ADMIN_PHONE, + email: SUPER_ADMIN_EMAIL, + documentoIdentidad: '3167455877', + rol: 'ADMIN', + esSuperAdmin: true, + saldoPenalidad: 0, + coordenadasDomicilio: { + type: 'Point', + coordinates: [-76.532, 3.4516], + }, + direccionTexto: 'Cali, Valle del Cauca', + ciudad: 'Cali', + isActive: true, + phoneVerified: true, + twoFactorEnabled: true, + twoFactorMethod: 'whatsapp_otp', + }); + console.log('👑 [SuperAdmin Seed]: Carlos Almeira registrado como Superadministrador permanente.'); + } else { + let needsSave = false; + if (superAdmin.rol !== 'ADMIN') { + superAdmin.rol = 'ADMIN'; + needsSave = true; + } + if (!superAdmin.esSuperAdmin) { + superAdmin.esSuperAdmin = true; + needsSave = true; + } + if (!superAdmin.isActive) { + superAdmin.isActive = true; + needsSave = true; + } + if (needsSave) { + await superAdmin.save(); + } + } + + return superAdmin; + } + + /** + * El Superadministrador registra a un nuevo administrador + */ + public async createAdministrator(data: CreateAdminDTO): Promise { + const rawPhone = (data.telefono || '').replace(/\D/g, ''); + if (!rawPhone || rawPhone.length < 7) { + throw new Error('Número de WhatsApp/celular inválido para el administrador.'); + } + + const cleanPhone = rawPhone.startsWith('57') ? rawPhone : `57${rawPhone}`; + + if (isPlatformSuperAdmin(cleanPhone)) { + throw new Error('Este número corresponde al Superadministrador y ya se encuentra configurado.'); + } + + let existing = await User.findOne({ telefono: cleanPhone }); + + if (existing) { + existing.rol = data.rol || 'ADMIN'; + existing.isActive = true; + if (data.nombre && data.nombre.trim()) existing.nombre = data.nombre.trim(); + if (data.email && data.email.trim()) existing.email = data.email.trim().toLowerCase(); + if (data.documentoIdentidad) existing.documentoIdentidad = data.documentoIdentidad.trim(); + if (data.direccionTexto) existing.direccionTexto = data.direccionTexto.trim(); + await existing.save(); + + return { + id: existing._id.toString(), + appwriteId: existing.appwriteId, + nombre: existing.nombre, + telefono: existing.telefono, + email: existing.email, + documentoIdentidad: existing.documentoIdentidad, + esSuperAdmin: false, + rol: existing.rol, + isActive: existing.isActive, + phoneVerified: existing.phoneVerified, + saldoPenalidad: existing.saldoPenalidad, + bloqueadoPorPenalidad: false, + totalViajesCompletados: existing.totalViajesCompletados || 0, + totalViajesCancelados: existing.totalViajesCancelados || 0, + createdAt: existing.createdAt, + }; + } + + const newAdmin = await User.create({ + appwriteId: `admin-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, + nombre: (data.nombre || 'Nuevo Administrador').trim(), + telefono: cleanPhone, + email: data.email ? data.email.trim().toLowerCase() : undefined, + documentoIdentidad: data.documentoIdentidad ? data.documentoIdentidad.trim() : undefined, + rol: data.rol || 'ADMIN', + esSuperAdmin: false, + saldoPenalidad: 0, + coordenadasDomicilio: { + type: 'Point', + coordinates: [-76.532, 3.4516], + }, + direccionTexto: data.direccionTexto || 'Cali, Valle del Cauca', + ciudad: 'Cali', + isActive: true, + phoneVerified: true, + twoFactorEnabled: true, + twoFactorMethod: 'whatsapp_otp', + }); + + console.log(`🛡️ [Nuevo Admin Creado]: ${newAdmin.nombre} (+${newAdmin.telefono}) Rol: ${newAdmin.rol}`); + + return { + id: newAdmin._id.toString(), + appwriteId: newAdmin.appwriteId, + nombre: newAdmin.nombre, + telefono: newAdmin.telefono, + email: newAdmin.email, + documentoIdentidad: newAdmin.documentoIdentidad, + esSuperAdmin: false, + rol: newAdmin.rol, + isActive: newAdmin.isActive, + phoneVerified: newAdmin.phoneVerified, + saldoPenalidad: 0, + bloqueadoPorPenalidad: false, + totalViajesCompletados: 0, + totalViajesCancelados: 0, + createdAt: newAdmin.createdAt, + }; + } + + /** + * El Superadministrador edita los datos de un administrador + */ + public async updateAdministrator(adminId: string, updates: UpdateAdminDTO, requesterUserId?: string): Promise { + const user = await User.findById(adminId); + if (!user) throw new Error('Administrador no encontrado.'); + + const isTargetSuper = isPlatformSuperAdmin(user.telefono) || !!user.esSuperAdmin; + + if (updates.nombre && updates.nombre.trim()) { + user.nombre = updates.nombre.trim(); + } + if (updates.email !== undefined) { + user.email = updates.email.trim().toLowerCase(); + } + if (updates.documentoIdentidad !== undefined) { + user.documentoIdentidad = updates.documentoIdentidad.trim(); + } + if (updates.direccionTexto !== undefined) { + user.direccionTexto = updates.direccionTexto.trim(); + } + if (updates.ciudad !== undefined) { + user.ciudad = updates.ciudad.trim(); + } + + // El rol del Superadministrador nunca puede ser degradado + if (!isTargetSuper && updates.rol) { + user.rol = updates.rol; + } + + await user.save(); + + return { + id: user._id.toString(), + appwriteId: user.appwriteId, + nombre: user.nombre, + telefono: user.telefono, + email: user.email, + documentoIdentidad: user.documentoIdentidad, + esSuperAdmin: isTargetSuper, + rol: isTargetSuper ? 'ADMIN' : user.rol, + isActive: user.isActive, + phoneVerified: user.phoneVerified, + saldoPenalidad: user.saldoPenalidad, + bloqueadoPorPenalidad: false, + totalViajesCompletados: user.totalViajesCompletados || 0, + totalViajesCancelados: user.totalViajesCancelados || 0, + createdAt: user.createdAt, + }; + } + + /** + * Activa o inactiva a un administrador (Superadministrador inmutable) + */ + public async toggleAdministratorActive(adminId: string, isActive: boolean): Promise { + const user = await User.findById(adminId); + if (!user) throw new Error('Administrador no encontrado.'); + + if (isPlatformSuperAdmin(user.telefono) || !!user.esSuperAdmin) { + throw new Error('El Superadministrador no puede ser inactivado bajo ninguna circunstancia.'); + } + + user.isActive = isActive; + await user.save(); + return user; + } + + /** + * Elimina formalmente a un administrador (Superadministrador no puede ser eliminado) + */ + public async deleteAdministrator(adminId: string, requesterUserId?: string): Promise { + const user = await User.findById(adminId); + if (!user) throw new Error('Administrador no encontrado.'); + + if (isPlatformSuperAdmin(user.telefono) || !!user.esSuperAdmin) { + throw new Error('El Superadministrador es permanente y no puede ser eliminado del sistema.'); + } + + await User.findByIdAndDelete(adminId); + console.log(`🗑️ [Admin Eliminado]: ${user.nombre} (${user.telefono}) eliminado por el Superadministrador.`); + return true; + } + + /** + * Permite a los usuarios actualizar su información personal EXCEPTO el número de celular + */ + public async updateUserProfile(userId: string, updates: UpdateUserProfileDTO): Promise { + const user = await User.findById(userId); + if (!user) throw new Error('Usuario no encontrado.'); + + // Validación estricta de inmutabilidad del número de celular + if (updates.telefono) { + const cleanInput = updates.telefono.replace(/\D/g, ''); + const currentClean = user.telefono.replace(/\D/g, ''); + if (cleanInput && cleanInput !== currentClean && !currentClean.endsWith(cleanInput) && !cleanInput.endsWith(currentClean)) { + throw new Error( + `Por estrictas políticas de seguridad y autenticación 2FA OTP, el número de celular registrado (+${user.telefono}) no puede ser modificado. Para cambiar tu número oficial, contacta a la central de soporte.` + ); + } + } + + if (updates.nombre && updates.nombre.trim().length >= 3) { + user.nombre = updates.nombre.trim(); + } + if (updates.documentoIdentidad !== undefined) { + user.documentoIdentidad = updates.documentoIdentidad.trim(); + } + if (updates.email !== undefined) { + user.email = updates.email.trim().toLowerCase(); + } + if (updates.direccionTexto !== undefined) { + user.direccionTexto = updates.direccionTexto.trim(); + } + if (updates.ciudad !== undefined) { + user.ciudad = updates.ciudad.trim(); + } + + await user.save(); + + return { + id: user._id.toString(), + _id: user._id.toString(), + nombre: user.nombre, + telefono: user.telefono, + email: user.email, + documentoIdentidad: user.documentoIdentidad, + direccionTexto: user.direccionTexto, + ciudad: user.ciudad, + rol: user.rol, + esSuperAdmin: isPlatformSuperAdmin(user.telefono) || !!user.esSuperAdmin, + phoneVerified: user.phoneVerified, + saldoPenalidad: user.saldoPenalidad, + }; + } + /** * Consulta un usuario por teléfono o ID con evaluación de riesgo y estado de penalidad */ @@ -107,20 +469,7 @@ export class UserAdminService { // Auto-creación / protección del Super Administrador si ingresa por primera vez if (!user && (isPlatformSuperAdmin(cleanPhone) || isPlatformSuperAdmin(identifier))) { - user = await User.create({ - appwriteId: 'usr-admin-principal', - nombre: 'Administrador Central Pacifictrans', - telefono: cleanPhone || SUPER_ADMIN_PHONE, - email: SUPER_ADMIN_EMAIL, - rol: 'ADMIN', - saldoPenalidad: 0, - coordenadasDomicilio: { - type: 'Point', - coordinates: [-76.532, 3.4516], // Cali centro - }, - isActive: true, - phoneVerified: true, - }); + user = await this.ensureSuperAdminExists(); } if (!user) { @@ -131,7 +480,7 @@ export class UserAdminService { }; } - const isSuper = isPlatformSuperAdmin(user.telefono) || isPlatformSuperAdmin(user.email); + const isSuper = isPlatformSuperAdmin(user.telefono) || isPlatformSuperAdmin(user.email) || !!user.esSuperAdmin; const montoPenalidad = isSuper ? 0 : user.saldoPenalidad || 0; const bloqueadoPorPenalidad = montoPenalidad > 0; @@ -161,6 +510,8 @@ export class UserAdminService { nombre: user.nombre, telefono: user.telefono, email: user.email, + documentoIdentidad: user.documentoIdentidad, + esSuperAdmin: isSuper, rol: isSuper ? 'ADMIN' : user.rol, isActive: isSuper ? true : user.isActive, phoneVerified: isSuper ? true : user.phoneVerified, @@ -171,6 +522,8 @@ export class UserAdminService { twoFactorEnabled: user.twoFactorEnabled, twoFactorMethod: user.twoFactorMethod, biometricEnabled: user.biometricEnabled, + direccionTexto: user.direccionTexto, + ciudad: user.ciudad, driverProfile, createdAt: user.createdAt, }; @@ -190,7 +543,7 @@ export class UserAdminService { const user = await User.findById(userId); if (!user) throw new Error('Usuario no encontrado'); - if (isPlatformSuperAdmin(user.telefono) || isPlatformSuperAdmin(user.email)) { + if (isPlatformSuperAdmin(user.telefono) || isPlatformSuperAdmin(user.email) || !!user.esSuperAdmin) { throw new Error('No es posible suspender la cuenta del Super Administrador principal.'); } @@ -206,8 +559,9 @@ export class UserAdminService { const user = await User.findById(userId); if (!user) throw new Error('Usuario no encontrado'); - if (isPlatformSuperAdmin(user.telefono) || isPlatformSuperAdmin(user.email)) { + if (isPlatformSuperAdmin(user.telefono) || isPlatformSuperAdmin(user.email) || !!user.esSuperAdmin) { user.rol = 'ADMIN'; + user.esSuperAdmin = true; await user.save(); return user; }