From 59bd2d2d8a3fd79a04487c560a83740d5e4d695f Mon Sep 17 00:00:00 2001 From: Alejandro Lara Date: Tue, 9 Jun 2026 16:59:50 -0600 Subject: [PATCH] =?UTF-8?q?Integrar=20autenticaci=C3=B3n=20Cognito=20con?= =?UTF-8?q?=20Floci=20local=20y=20soporte=20AWS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Añade servicios y configuración Cognito para login web (admins) y API (scanners) - Soporta Floci cuando AWS_ENDPOINT_URL está definido; AWS real en caso contrario - Incluye bootstrap Floci, docker-compose.local.yml y carga automática en run-local.sh - Documenta arquitectura, flujos y arranque manual/IntelliJ en README - Actualiza gitignore: excluye Flutter, H2 local y artefactos generados --- .gitignore | 52 +- README.md | 731 +++++++++++++++--- base-admin-web-configuration/pom.xml | 4 + .../security/ApiAuthService.java | 89 +-- .../security/ApiLoginResponse.java | 14 + .../security/ApiSecurityConfig.java | 2 +- .../security/AuthenticatedWebUser.java | 6 + .../security/CognitoAuthService.java | 52 ++ .../security/CognitoClientConfig.java | 33 + .../security/CognitoDisplayNameResolver.java | 31 + .../security/CognitoProperties.java | 102 +++ .../security/CognitoRoleMapper.java | 29 + .../security/CognitoUserAuthenticator.java | 119 +++ .../CognitoWebAuthenticationProvider.java | 63 ++ .../security/CognitoWebSecurityConfig.java | 17 + .../configuration/security/JwtConfig.java | 37 +- .../security/LocalJwtAuthService.java | 76 ++ .../security/SecurityConfig.java | 21 +- base-admin-web-jar/data/h2/baseadminweb.mv.db | Bin 40960 -> 0 bytes .../src/main/resources/application-test.yml | 4 + .../src/main/resources/application.yml | 11 + .../web/controller/AuthApiController.java | 51 +- .../web/controller/DashboardController.java | 10 +- .../web/controller/PassCodeController.java | 8 +- .../baseadminweb/web/support/WebUserView.java | 25 + .../src/main/resources/templates/login.html | 5 +- docker-compose-postgres.local.yml | 18 +- docker-compose.local.yml | 45 ++ docker/floci/cognito.local.env.example | 15 + docker/floci/generated/.gitkeep | 0 .../init/ready.d/10-cognito-bootstrap.sh | 172 +++++ flutter-app/qrscanner/.gitignore | 45 -- flutter-app/qrscanner/.metadata | 33 - flutter-app/qrscanner/README.md | 17 - flutter-app/qrscanner/analysis_options.yaml | 28 - flutter-app/qrscanner/android/.gitignore | 14 - .../qrscanner/android/app/build.gradle.kts | 45 -- .../android/app/src/debug/AndroidManifest.xml | 7 - .../android/app/src/main/AndroidManifest.xml | 50 -- .../com/example/qrscanner/MainActivity.kt | 5 - .../res/drawable-v21/launch_background.xml | 12 - .../main/res/drawable/launch_background.xml | 12 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../app/src/main/res/values-night/styles.xml | 18 - .../app/src/main/res/values/styles.xml | 18 - .../app/src/profile/AndroidManifest.xml | 7 - .../qrscanner/android/build.gradle.kts | 24 - .../qrscanner/android/gradle.properties | 6 - .../gradle/wrapper/gradle-wrapper.properties | 5 - .../qrscanner/android/settings.gradle.kts | 26 - flutter-app/qrscanner/assets/sounds/scan.wav | Bin 5336 -> 0 bytes .../qrscanner/assets/sounds/success.wav | Bin 26504 -> 0 bytes .../qrscanner/assets/sounds/warning.wav | Bin 35324 -> 0 bytes flutter-app/qrscanner/ios/.gitignore | 34 - .../ios/Flutter/AppFrameworkInfo.plist | 24 - .../qrscanner/ios/Flutter/Debug.xcconfig | 1 - .../qrscanner/ios/Flutter/Release.xcconfig | 1 - .../ios/Runner.xcodeproj/project.pbxproj | 644 --------------- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 119 --- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../qrscanner/ios/Runner/AppDelegate.swift | 16 - .../AppIcon.appiconset/Contents.json | 122 --- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../LaunchImage.imageset/Contents.json | 23 - .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - .../Runner/Base.lproj/LaunchScreen.storyboard | 37 - .../ios/Runner/Base.lproj/Main.storyboard | 26 - flutter-app/qrscanner/ios/Runner/Info.plist | 72 -- .../ios/Runner/Runner-Bridging-Header.h | 1 - .../qrscanner/ios/Runner/SceneDelegate.swift | 6 - .../ios/RunnerTests/RunnerTests.swift | 12 - .../qrscanner/lib/config/api_config.dart | 16 - flutter-app/qrscanner/lib/main.dart | 21 - .../qrscanner/lib/models/auth_session.dart | 52 -- .../qrscanner/lib/models/login_response.dart | 54 -- .../qrscanner/lib/models/pass_code.dart | 31 - .../qrscanner/lib/models/scan_response.dart | 23 - .../qrscanner/lib/screens/home_screen.dart | 311 -------- .../qrscanner/lib/screens/login_screen.dart | 440 ----------- .../qrscanner/lib/screens/scanner_screen.dart | 590 -------------- .../qrscanner/lib/services/auth_service.dart | 85 -- .../lib/services/feedback_service.dart | 72 -- .../lib/services/pass_code_service.dart | 27 - .../qrscanner/lib/theme/app_theme.dart | 59 -- flutter-app/qrscanner/pubspec.lock | 634 --------------- flutter-app/qrscanner/pubspec.yaml | 92 --- flutter-app/qrscanner/test/widget_test.dart | 13 - pom.xml | 13 + run-local.sh | 71 +- scripts/floci-up.sh | 44 ++ 116 files changed, 1733 insertions(+), 4318 deletions(-) create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiLoginResponse.java create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/AuthenticatedWebUser.java create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoAuthService.java create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoClientConfig.java create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoDisplayNameResolver.java create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoProperties.java create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoRoleMapper.java create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoUserAuthenticator.java create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoWebAuthenticationProvider.java create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoWebSecurityConfig.java create mode 100644 base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/LocalJwtAuthService.java delete mode 100644 base-admin-web-jar/data/h2/baseadminweb.mv.db create mode 100644 base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/support/WebUserView.java create mode 100644 docker-compose.local.yml create mode 100644 docker/floci/cognito.local.env.example create mode 100644 docker/floci/generated/.gitkeep create mode 100755 docker/floci/init/ready.d/10-cognito-bootstrap.sh delete mode 100644 flutter-app/qrscanner/.gitignore delete mode 100644 flutter-app/qrscanner/.metadata delete mode 100644 flutter-app/qrscanner/README.md delete mode 100644 flutter-app/qrscanner/analysis_options.yaml delete mode 100644 flutter-app/qrscanner/android/.gitignore delete mode 100644 flutter-app/qrscanner/android/app/build.gradle.kts delete mode 100644 flutter-app/qrscanner/android/app/src/debug/AndroidManifest.xml delete mode 100644 flutter-app/qrscanner/android/app/src/main/AndroidManifest.xml delete mode 100644 flutter-app/qrscanner/android/app/src/main/kotlin/com/example/qrscanner/MainActivity.kt delete mode 100644 flutter-app/qrscanner/android/app/src/main/res/drawable-v21/launch_background.xml delete mode 100644 flutter-app/qrscanner/android/app/src/main/res/drawable/launch_background.xml delete mode 100644 flutter-app/qrscanner/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 flutter-app/qrscanner/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 flutter-app/qrscanner/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 flutter-app/qrscanner/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 flutter-app/qrscanner/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 flutter-app/qrscanner/android/app/src/main/res/values-night/styles.xml delete mode 100644 flutter-app/qrscanner/android/app/src/main/res/values/styles.xml delete mode 100644 flutter-app/qrscanner/android/app/src/profile/AndroidManifest.xml delete mode 100644 flutter-app/qrscanner/android/build.gradle.kts delete mode 100644 flutter-app/qrscanner/android/gradle.properties delete mode 100644 flutter-app/qrscanner/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 flutter-app/qrscanner/android/settings.gradle.kts delete mode 100644 flutter-app/qrscanner/assets/sounds/scan.wav delete mode 100644 flutter-app/qrscanner/assets/sounds/success.wav delete mode 100644 flutter-app/qrscanner/assets/sounds/warning.wav delete mode 100644 flutter-app/qrscanner/ios/.gitignore delete mode 100644 flutter-app/qrscanner/ios/Flutter/AppFrameworkInfo.plist delete mode 100644 flutter-app/qrscanner/ios/Flutter/Debug.xcconfig delete mode 100644 flutter-app/qrscanner/ios/Flutter/Release.xcconfig delete mode 100644 flutter-app/qrscanner/ios/Runner.xcodeproj/project.pbxproj delete mode 100644 flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 flutter-app/qrscanner/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 flutter-app/qrscanner/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 flutter-app/qrscanner/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 flutter-app/qrscanner/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 flutter-app/qrscanner/ios/Runner/AppDelegate.swift delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md delete mode 100644 flutter-app/qrscanner/ios/Runner/Base.lproj/LaunchScreen.storyboard delete mode 100644 flutter-app/qrscanner/ios/Runner/Base.lproj/Main.storyboard delete mode 100644 flutter-app/qrscanner/ios/Runner/Info.plist delete mode 100644 flutter-app/qrscanner/ios/Runner/Runner-Bridging-Header.h delete mode 100644 flutter-app/qrscanner/ios/Runner/SceneDelegate.swift delete mode 100644 flutter-app/qrscanner/ios/RunnerTests/RunnerTests.swift delete mode 100644 flutter-app/qrscanner/lib/config/api_config.dart delete mode 100644 flutter-app/qrscanner/lib/main.dart delete mode 100644 flutter-app/qrscanner/lib/models/auth_session.dart delete mode 100644 flutter-app/qrscanner/lib/models/login_response.dart delete mode 100644 flutter-app/qrscanner/lib/models/pass_code.dart delete mode 100644 flutter-app/qrscanner/lib/models/scan_response.dart delete mode 100644 flutter-app/qrscanner/lib/screens/home_screen.dart delete mode 100644 flutter-app/qrscanner/lib/screens/login_screen.dart delete mode 100644 flutter-app/qrscanner/lib/screens/scanner_screen.dart delete mode 100644 flutter-app/qrscanner/lib/services/auth_service.dart delete mode 100644 flutter-app/qrscanner/lib/services/feedback_service.dart delete mode 100644 flutter-app/qrscanner/lib/services/pass_code_service.dart delete mode 100644 flutter-app/qrscanner/lib/theme/app_theme.dart delete mode 100644 flutter-app/qrscanner/pubspec.lock delete mode 100644 flutter-app/qrscanner/pubspec.yaml delete mode 100644 flutter-app/qrscanner/test/widget_test.dart create mode 100755 scripts/floci-up.sh diff --git a/.gitignore b/.gitignore index a4fda30..dbad684 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,55 @@ -/data/ -/target/ +# --- Maven / Java --- +target/ **/target/ -/.idea/ -/.vscode/ +*.class +*.jar +*.war +*.ear +hs_err_pid* +replay_pid* + +# --- Spring Boot / runtime local --- +**/data/h2/ +*.mv.db +*.lock.db +*.trace.db + +# --- Docker / Floci (generado en local) --- +/docker/floci/generated/* +!/docker/floci/generated/.gitkeep + +# --- App Flutter (proyecto aparte, no versionar aquí) --- +/flutter-app/ + +# --- IDE --- +.idea/ *.iml -*.log -/.mvn/ -/.DS_Store +*.ipr +*.iws +.vscode/ .classpath .project .settings/ **/.classpath **/.project **/.settings/ + +# --- Maven wrapper local (si se genera) --- +.mvn/wrapper/maven-wrapper.jar + +# --- Logs y temporales --- +*.log +*.tmp +*.swp +*~ + +# --- OS --- +.DS_Store +Thumbs.db + +# --- Maven auxiliar --- effective-pom.xml **/effective-pom.xml + +# --- Datos locales en raíz (legacy) --- +/data/ \ No newline at end of file diff --git a/README.md b/README.md index 5d0903e..ec35609 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,362 @@ # Base Admin Web -Proyecto base de Spring Boot con Metronic, autenticacion local y estructura modular lista para reutilizar en nuevos paneles administrativos. +Proyecto base de Spring Boot con Metronic, autenticación Cognito (o local), panel administrativo y API de escaneo para la app móvil Flutter. + +Sirve como plantilla reutilizable para nuevos paneles internos: estructura modular, seguridad dual (web + API), módulo de códigos pase con QR y soporte H2/PostgreSQL. + +--- + +## Tabla de contenidos + +- [Vista general](#vista-general) +- [Stack](#stack) +- [Arquitectura del sistema](#arquitectura-del-sistema) +- [Módulos Maven](#módulos-maven) +- [Seguridad y autenticación](#seguridad-y-autenticación) +- [Cognito: local (Floci) vs AWS](#cognito-local-floci-vs-aws) +- [Módulo de códigos pase](#módulo-de-códigos-pase) +- [API REST](#api-rest) +- [App Flutter (escáner)](#app-flutter-escáner) +- [Arranque local](#arranque-local) +- [Arranque manual e IntelliJ IDEA](#arranque-manual-e-intellij-idea) +- [Perfiles Spring](#perfiles-spring) +- [Variables de entorno](#variables-de-entorno) +- [Objetivo de esta base](#objetivo-de-esta-base) + +--- + +## Vista general + +El sistema tiene **dos canales de acceso** con roles distintos: + +| Canal | Usuario | Rol | Autenticación | +|-------|---------|-----|---------------| +| Panel web (Thymeleaf) | `admin` | `ROLE_ADMIN` | Sesión HTTP (form login) | +| API móvil / externa | `scanner` | `ROLE_SCANNER` | JWT Bearer (stateless) | + +Por defecto ambos canales usan **Amazon Cognito** (emulado en local con Floci). Con `APP_COGNITO_ENABLED=false` se usa autenticación local contra la base de datos y JWT firmado con secreto propio. + +```mermaid +flowchart TB + subgraph clients["Clientes"] + Browser["Navegador
Panel admin"] + Flutter["App Flutter
QR Scanner"] + end + + subgraph app["Spring Boot — base-admin-web-jar"] + WebChain["Filter chain web
sesión + CSRF"] + ApiChain["Filter chain API
stateless + JWT"] + PassCodes["PassCodeService"] + DB[("H2 / PostgreSQL")] + end + + subgraph auth["Autenticación"] + Cognito["Cognito
Floci local o AWS"] + LocalAuth["BD local + JWT HS256
APP_COGNITO_ENABLED=false"] + end + + Browser -->|"/login, /admin/**"| WebChain + Flutter -->|"/api/auth/login
/api/pass-codes/scan"| ApiChain + WebChain --> PassCodes + ApiChain --> PassCodes + PassCodes --> DB + WebChain --> Cognito + ApiChain --> Cognito + WebChain -.-> LocalAuth + ApiChain -.-> LocalAuth +``` + +**URLs locales:** + +| Recurso | URL | +|---------|-----| +| Login | http://localhost:8080/login | +| Dashboard | http://localhost:8080/dashboard | +| Códigos pase | http://localhost:8080/admin/pass-codes | +| H2 Console | http://localhost:8080/h2-console | +| Floci (Cognito local) | http://localhost:4566 | + +--- ## Stack -- Spring Boot 4.0.6 -- Java 25 -- Spring Security -- Thymeleaf -- JPA / Hibernate -- H2 en archivo para desarrollo rapido -- PostgreSQL listo por perfil -- JWT para autenticacion API de escaneo -- Assets Metronic reutilizados desde `base-web` +| Capa | Tecnología | +|------|------------| +| Backend | Spring Boot 4.0.6, Java 25 | +| Seguridad | Spring Security, OAuth2 Resource Server, AWS SDK Cognito | +| Vistas | Thymeleaf + Metronic (`base-web`) | +| Persistencia | JPA / Hibernate, H2 (dev), PostgreSQL (perfil `postgres`) | +| Auth | Cognito (default) o BD local + JWT HS256 | +| Móvil | Flutter (`flutter-app/qrscanner`) | +| Local AWS | [Floci](https://floci.io/floci/getting-started/quick-start/) (Cognito en puerto 4566) | -## Estructura +--- -- `base-admin-web-domain`: entidades y repositorios -- `base-admin-web-application`: servicios y carga inicial -- `base-admin-web-configuration`: seguridad, JWT y perfiles -- `base-admin-web-web`: controladores y vistas Thymeleaf -- `base-admin-web-jar`: punto de entrada ejecutable +## Arquitectura del sistema -## Usuarios iniciales +### Capas y dependencias -### Administrador (panel web) +```mermaid +flowchart BT + jar["base-admin-web-jar
punto de entrada + application.yml"] + web["base-admin-web-web
controladores + Thymeleaf"] + config["base-admin-web-configuration
seguridad, JWT, Cognito"] + app["base-admin-web-application
servicios + carga inicial"] + domain["base-admin-web-domain
entidades + repositorios"] -- Usuario: `admin` -- Password: `Admin123*` -- Rol: `ROLE_ADMIN` -- Acceso: login web en `/login`, dashboard y modulo de codigos pase + jar --> web + jar --> config + jar --> app + jar --> domain + web --> config + web --> app + config --> app + app --> domain +``` -Variables de entorno: +| Módulo | Responsabilidad | +|--------|-----------------| +| `base-admin-web-domain` | Entidades JPA (`AppUser`, `PassCode`, `Role`), repositorios | +| `base-admin-web-application` | `PassCodeService`, `AppUserDetailsService`, `InitialDataLoader` | +| `base-admin-web-configuration` | `SecurityConfig`, `ApiSecurityConfig`, Cognito, `JwtConfig` | +| `base-admin-web-web` | Controladores MVC y API, DTOs, plantillas HTML | +| `base-admin-web-jar` | `BaseAdminWebApplication`, perfiles, dependencias runtime (H2, PostgreSQL) | -- `APP_ADMIN_USERNAME` -- `APP_ADMIN_PASSWORD` -- `APP_ADMIN_DISPLAY_NAME` +### Dos cadenas de seguridad -### Escaneador (API) +Spring Security define **dos filter chains** con distinto comportamiento: -- Usuario: `scanner` -- Password: `Scanner123*` -- Rol: `ROLE_SCANNER` -- Acceso: solo login por API y escaneo de codigos pase +```mermaid +flowchart LR + subgraph order1["@Order(1) — ApiSecurityConfig"] + A1["/api/**"] + A2["Stateless"] + A3["JWT Bearer"] + A4["ROLE_SCANNER en /scan"] + end -Variables de entorno: + subgraph order2["@Order(2) — SecurityConfig"] + B1["Todo excepto /api/**"] + B2["Sesión HTTP"] + B3["Form login /login"] + B4["ROLE_ADMIN en /admin/**"] + end -- `APP_SCANNER_USERNAME` -- `APP_SCANNER_PASSWORD` -- `APP_SCANNER_DISPLAY_NAME` + A1 --> A2 --> A3 --> A4 + B1 --> B2 --> B3 --> B4 +``` -## Modulo de codigos pase +| Ruta | Acceso | Mecanismo | +|------|--------|-----------| +| `/login`, `/assets/**`, `/error`, `/h2-console/**` | Público | — | +| `/dashboard`, `/admin/**` | `ROLE_ADMIN` | Sesión web | +| `/api/auth/login`, `/api/auth/refresh` | Público | Emite tokens (solo `scanner`) | +| `/api/pass-codes/scan` | `ROLE_SCANNER` | JWT Bearer | -Panel administrativo para dar de alta y consultar codigos pase. +--- -- URL: `http://localhost:8080/admin/pass-codes` -- Tambien accesible desde el boton **Codigos pase** en el dashboard -- Alta por nombre; el sistema genera un codigo aleatorio de 10 caracteres -- Vista con tabla de estatus, escaneo y fecha de creacion -- Modal Metronic para ver el QR de cada codigo -- Al cerrar el modal, la lista se actualiza automaticamente por si el codigo fue utilizado +## Seguridad y autenticación -Campos del codigo pase: +### Usuarios iniciales -- Nombre -- Codigo aleatorio -- Fecha de creacion -- Estatus (`ACTIVO` / `UTILIZADO`) -- Indicador de escaneado -- Fecha de escaneo +#### Administrador (panel web) -## API +- Usuario: `admin` / `Admin123*` +- Grupo Cognito: `admins` → `ROLE_ADMIN` +- Acceso: login en `/login`, dashboard, módulo de códigos pase +- El usuario `scanner` **no** puede entrar al panel web -### Login escaner +#### Escáner (API / Flutter) + +- Usuario: `scanner` / `Scanner123*` +- Grupo Cognito: `scanners` → `ROLE_SCANNER` +- Acceso: `POST /api/auth/login` y `POST /api/pass-codes/scan` +- El usuario `admin` **no** puede autenticarse por API + +### Flujo de login web (panel admin) + +```mermaid +sequenceDiagram + actor Admin as Navegador (admin) + participant SC as SecurityConfig + participant CP as CognitoWebAuthenticationProvider + participant CA as CognitoUserAuthenticator + participant Cognito as Cognito (Floci/AWS) + + Admin->>SC: POST /login (user, password) + SC->>CP: authenticate() + CP->>CA: admin client + ROLE_ADMIN + CA->>Cognito: InitiateAuth USER_PASSWORD_AUTH + Cognito-->>CA: tokens + cognito:groups + CA-->>CP: AuthenticatedWebUser (grupo admins) + CP-->>SC: sesión HTTP creada + SC-->>Admin: redirect /dashboard +``` + +**Clases clave:** `SecurityConfig`, `CognitoWebSecurityConfig`, `CognitoWebAuthenticationProvider`, `CognitoUserAuthenticator`, `AuthController` + +Con `APP_COGNITO_ENABLED=false`, el flujo usa `DaoAuthenticationProvider` → `AppUserDetailsService` → tabla `users` (BCrypt). + +### Flujo de login y escaneo API + +```mermaid +sequenceDiagram + actor App as Flutter (scanner) + participant API as AuthApiController + participant AS as ApiAuthService + participant Cognito as Cognito + participant Scan as PassCodeApiController + participant Svc as PassCodeService + + App->>API: POST /api/auth/login + API->>AS: login() + AS->>Cognito: scanner client + ROLE_SCANNER + Cognito-->>App: accessToken, refreshToken, idToken + + App->>Scan: POST /api/pass-codes/scan
Authorization: Bearer ... + Scan->>Scan: validar JWT (JWK o HS256) + Scan->>Svc: scan(code) + Svc-->>App: código marcado UTILIZADO +``` + +**Clases clave:** `ApiSecurityConfig`, `AuthApiController`, `ApiAuthService`, `CognitoAuthService`, `PassCodeApiController`, `JwtConfig` + +### Modo local sin Cognito + +Con `APP_COGNITO_ENABLED=false` (perfil `test` lo usa automáticamente): + +```mermaid +flowchart LR + subgraph web["Web"] + W1["Form login"] --> W2["AppUser en BD"] + end + subgraph api["API"] + A1["POST /api/auth/login"] --> A2["BD + JwtEncoder HS256"] + A3["POST /api/auth/refresh"] --> A4["No disponible"] + A5["Bearer token"] --> A6["localJwtDecoder HS256"] + end +``` + +| `APP_COGNITO_ENABLED` | Web | API login | Validación JWT | Refresh | +|----------------------|-----|-----------|----------------|---------| +| `true` (default) | Cognito client admin | Cognito client scanner | JWK Set URI | Sí | +| `false` | BD + BCrypt | BD + JWT local | Secreto HS256 | No | + +Los usuarios se siembran con `InitialDataLoader` usando `APP_ADMIN_*` y `APP_SCANNER_*`. + +--- + +## Cognito: local (Floci) vs AWS + +El SDK de Cognito elige el destino según `AWS_ENDPOINT_URL`: + +```mermaid +flowchart TD + Start["APP_COGNITO_ENABLED=true"] + Check{"¿AWS_ENDPOINT_URL
definido?"} + Floci["Floci localhost:4566
credenciales test/test"] + AWS["Cognito real en AWS
DefaultCredentialsProvider
IAM / perfil / env AWS"] + + Start --> Check + Check -->|Sí| Floci + Check -->|No| AWS +``` + +### Local con Floci + +`./run-local.sh` hace todo el setup: + +1. Levanta Floci (y PostgreSQL si el perfil lo incluye) +2. Espera el bootstrap de Cognito (hasta 120 s) +3. Carga `docker/floci/generated/cognito.local.env` automáticamente + +El script `docker/floci/init/ready.d/10-cognito-bootstrap.sh` crea: + +| Recurso | Valor | +|---------|-------| +| User Pool ID | `us-east-1_BaseAdminWeb` | +| Usuario admin | `admin` / `Admin123*` → grupo `admins` | +| Usuario scanner | `scanner` / `Scanner123*` → grupo `scanners` | +| App client admin | `base-admin-web-admin` | +| App client scanner | `base-admin-web-scanner` | + +Variables generadas en `docker/floci/generated/cognito.local.env` (gitignored). + +**Levantar solo Floci:** + +```bash +./scripts/floci-up.sh +``` + +**Levantar stack Docker completo:** + +```bash +docker compose -f docker-compose.local.yml up -d +``` + +| Servicio | Puerto | Persistencia | +|----------|--------|--------------| +| Floci | 4566 | volumen `base_admin_web_floci_data` | +| PostgreSQL | 5432 | volumen `base_admin_web_postgres_data` | + +### Producción con AWS Cognito + +**No definir** `AWS_ENDPOINT_URL`. Configura el User Pool real: + +```bash +export APP_COGNITO_ENABLED=true +export COGNITO_REGION=us-east-1 +export COGNITO_USER_POOL_ID=us-east-1_XXXXX +export COGNITO_ISSUER_URI=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXX +export COGNITO_JWK_SET_URI=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXX/.well-known/jwks.json +export COGNITO_ADMIN_CLIENT_ID=... +export COGNITO_SCANNER_CLIENT_ID=... +# Credenciales AWS vía IAM role, perfil o AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY +``` + +En AWS replica la misma estructura que Floci: grupos `admins`/`scanners`, clients con `USER_PASSWORD_AUTH` habilitado. + +### Verificar Cognito local + +```bash +export AWS_ENDPOINT_URL=http://localhost:4566 +export AWS_DEFAULT_REGION=us-east-1 +export AWS_ACCESS_KEY_ID=test +export AWS_SECRET_ACCESS_KEY=test + +aws cognito-idp list-user-pools --max-results 10 --endpoint-url $AWS_ENDPOINT_URL +aws cognito-idp list-users --user-pool-id us-east-1_BaseAdminWeb --endpoint-url $AWS_ENDPOINT_URL +``` + +--- + +## Módulo de códigos pase + +Panel para dar de alta y consultar códigos pase con QR. + +```mermaid +stateDiagram-v2 + [*] --> ACTIVO: admin crea código (10 chars) + ACTIVO --> UTILIZADO: scanner escanea QR +``` + +| Campo | Descripción | +|-------|-------------| +| Nombre | Etiqueta descriptiva | +| Código | Aleatorio de 10 caracteres (único) | +| Estatus | `ACTIVO` / `UTILIZADO` | +| Escaneado | Indicador booleano | +| Fechas | Creación y escaneo | + +**Panel web:** `GET /admin/pass-codes` — tabla Metronic, modal QR, refresco automático al cerrar el modal. + +**Entidad:** `PassCode` → **Servicio:** `PassCodeService` → **Controladores:** `PassCodeController` (web), `PassCodeApiController` (API). + +--- + +## API REST + +### Login escáner ```http POST /api/auth/login @@ -84,22 +368,35 @@ Content-Type: application/json } ``` -Respuesta exitosa: +Respuesta (Cognito): ```json { "success": true, "accessToken": "eyJhbG...", + "refreshToken": "dXMtZWFzdC0x...", + "idToken": "eyJhbG...", "tokenType": "Bearer", - "expiresIn": 28800, + "expiresIn": 3600, "username": "scanner", "roles": ["ROLE_SCANNER"] } ``` -Solo usuarios con rol `SCANNER` pueden iniciar sesion por API. El usuario `admin` no puede autenticarse por este endpoint. +### Renovar token -### Escanear codigo pase +```http +POST /api/auth/refresh +Content-Type: application/json + +{ + "refreshToken": "dXMtZWFzdC0x..." +} +``` + +Solo disponible con Cognito habilitado. + +### Escanear código pase ```http POST /api/pass-codes/scan @@ -111,19 +408,48 @@ Content-Type: application/json } ``` -Marca el codigo como escaneado y cambia su estatus a `UTILIZADO`. - -### Listado para el panel (requiere sesion admin) +### Listado JSON (panel admin, requiere sesión) ```http GET /admin/pass-codes/list ``` -Devuelve JSON con los codigos registrados. Se usa internamente para refrescar la tabla del panel. +--- + +## App Flutter (escáner) + +Ubicación: `flutter-app/qrscanner/` + +```mermaid +flowchart LR + Login["LoginScreen"] --> Home["HomeScreen"] + Home --> Scanner["ScannerScreen"] + Scanner -->|QR detectado| API["POST /api/pass-codes/scan"] + Login -->|credenciales| Auth["POST /api/auth/login"] +``` + +| Archivo | Rol | +|---------|-----| +| `lib/screens/login_screen.dart` | Login y sesión | +| `lib/screens/scanner_screen.dart` | Lectura QR + escaneo | +| `lib/services/auth_service.dart` | Login, SharedPreferences | +| `lib/services/pass_code_service.dart` | Scan con Bearer token | +| `lib/config/api_config.dart` | URL base de la API | + +**Ejecutar apuntando al backend local:** + +```bash +cd flutter-app/qrscanner +flutter run --dart-define=API_BASE_URL=http://TU_IP:8080 +``` + +La app valida que el usuario tenga `ROLE_SCANNER` (o grupo `scanners`) antes de permitir el escaneo. + +--- ## Arranque local -Desde la raiz del proyecto: +### Opción recomendada ```bash ./run-local.sh @@ -131,44 +457,39 @@ Desde la raiz del proyecto: El script: -- Detecta automaticamente Temurin/Java 25 -- Compila todos los modulos del proyecto -- Arranca el modulo ejecutable `base-admin-web-jar` -- Muestra el perfil activo y la base de datos en uso +- Detecta Java 25 (Temurin) +- Levanta Floci (y PostgreSQL si aplica) +- Espera y carga `cognito.local.env` +- Compila e inicia `base-admin-web-jar` -Si `SPRING_PROFILES_ACTIVE` incluye `postgres`, el script intenta levantar automaticamente el contenedor definido en `docker-compose-postgres.local.yml` antes de iniciar la aplicacion. +Si ya tienes `AWS_ENDPOINT_URL` exportado, no sobrescribe tus variables (útil para probar contra AWS). -Tambien puedes ejecutar: +### Maven manual (terminal) ```bash -mvn test +# 1. Levantar Floci (Cognito local) +docker compose -f docker-compose.local.yml up -d floci + +# 2. Esperar el bootstrap (genera cognito.local.env) +# Revisa que exista: docker/floci/generated/cognito.local.env + +# 3. Cargar variables Cognito en la shell +set -a && source docker/floci/generated/cognito.local.env && set +a + +# 4. Compilar y arrancar mvn -pl base-admin-web-jar -am -DskipTests install mvn -pl base-admin-web-jar spring-boot:run ``` -Aplicacion: +Si Floci ya estaba levantado de antes, basta con el `source` y el `spring-boot:run`. -- Login: `http://localhost:8080/login` -- Dashboard: `http://localhost:8080/dashboard` -- Codigos pase: `http://localhost:8080/admin/pass-codes` -- H2 Console: `http://localhost:8080/h2-console` - -## Perfiles - -- `dev`: H2 en archivo (`./data/h2/`), consola H2 habilitada, datos persistentes entre reinicios -- `prod`: cache Thymeleaf y H2 console deshabilitada -- `postgres`: datasource PostgreSQL, datos persistentes en el servidor o volumen Docker -- `test`: H2 en memoria con `create-drop`, usado solo por `mvn test` - -Con `postgres`, `run-local.sh` intenta arrancar Docker Compose automaticamente. - -Ejemplo con PostgreSQL local: +### PostgreSQL local ```bash SPRING_PROFILES_ACTIVE=postgres ./run-local.sh ``` -Ejemplo con PostgreSQL remoto: +### PostgreSQL remoto ```bash SPRING_PROFILES_ACTIVE=postgres \ @@ -178,44 +499,230 @@ SPRING_DATASOURCE_PASSWORD=tu_password \ ./run-local.sh ``` -## Variables utiles +--- -### Aplicacion +## Arranque manual e IntelliJ IDEA -- `SERVER_PORT` -- `SPRING_PROFILES_ACTIVE` -- `APP_TITLE` +Si abres el proyecto en IntelliJ (o cualquier IDE) sin usar `run-local.sh`, debes levantar Floci y configurar variables de entorno tú mismo. `run-local.sh` automatiza exactamente esos pasos. + +```mermaid +flowchart TD + A["Abrir proyecto Maven en IntelliJ"] --> B["Java 25 como Project SDK"] + B --> C["docker compose up floci"] + C --> D["Esperar cognito.local.env"] + D --> E["Configurar Run Configuration"] + E --> F["Run BaseAdminWebApplication"] + F --> G["http://localhost:8080/login"] +``` + +### Requisitos previos + +| Requisito | Versión / nota | +|-----------|----------------| +| JDK | **Java 25** (Temurin recomendado) | +| Maven | Incluido en el proyecto (wrapper no obligatorio; usa el Maven del IDE) | +| Docker | Para Cognito local (Floci) en puerto `4566` | +| IntelliJ | Ultimate o Community (con plugin Spring Boot en Community) | + +### 1. Importar el proyecto + +1. **File → Open** y selecciona la carpeta raíz del repo (donde está el `pom.xml` padre). +2. IntelliJ detecta el proyecto **Maven multi-módulo**. Espera a que termine la indexación e importación. +3. **File → Project Structure → Project**: SDK = **Java 25**, language level = **25**. +4. **File → Settings → Build → Build Tools → Maven → Runner**: JRE = **Project SDK (Java 25)**. + +### 2. Levantar Floci (Cognito local) + +En una terminal (puede ser la Terminal integrada de IntelliJ): + +```bash +docker compose -f docker-compose.local.yml up -d floci +``` + +La primera vez Floci ejecuta el bootstrap y escribe: + +``` +docker/floci/generated/cognito.local.env +``` + +Comprueba que el archivo existe (puede tardar ~30–120 s): + +```bash +cat docker/floci/generated/cognito.local.env +``` + +Si no aparece, revisa los logs: + +```bash +docker logs base-admin-web-floci +``` + +**PostgreSQL local:** si usas el perfil `postgres`, levanta también el servicio: + +```bash +docker compose -f docker-compose.local.yml up -d postgres floci +``` + +### 3. Run Configuration en IntelliJ + +Crea una configuración **Spring Boot** (o **Application**): + +| Campo | Valor | +|-------|-------| +| **Name** | `BaseAdminWebApplication` | +| **Main class** | `mx.gob.slp.baseadminweb.jar.BaseAdminWebApplication` | +| **Module** | `base-admin-web-jar` | +| **Working directory** | `$PROJECT_DIR$/base-admin-web-jar` | +| **Active profiles** | `dev` (o `postgres` si aplica) | + +> **Working directory:** H2 guarda datos en `./data/h2/` relativo al directorio de trabajo. Debe ser `base-admin-web-jar` para que coincida con `mvn spring-boot:run` y no mezclar rutas de BD. + +#### Variables de entorno (Cognito local) + +En **Environment variables**, pega el contenido de `docker/floci/generated/cognito.local.env`: + +``` +COGNITO_USER_POOL_ID=us-east-1_BaseAdminWeb +COGNITO_REGION=us-east-1 +COGNITO_ISSUER_URI=http://localhost:4566/us-east-1_BaseAdminWeb +COGNITO_JWK_SET_URI=http://localhost:4566/us-east-1_BaseAdminWeb/.well-known/jwks.json +COGNITO_ADMIN_CLIENT_ID= +COGNITO_SCANNER_CLIENT_ID= +COGNITO_ADMIN_GROUP=admins +COGNITO_SCANNER_GROUP=scanners +AWS_ENDPOINT_URL=http://localhost:4566 +AWS_DEFAULT_REGION=us-east-1 +AWS_ACCESS_KEY_ID=test +AWS_SECRET_ACCESS_KEY=test +``` + +Los `CLIENT_ID` cambian en cada bootstrap; **copia siempre los del archivo generado**, no uses valores fijos de ejemplos antiguos. + +**Alternativa:** plugin [EnvFile](https://plugins.jetbrains.com/plugin/7861-envfile) → añade `docker/floci/generated/cognito.local.env` como archivo de entorno en la Run Configuration. + +#### Perfil `postgres` en IntelliJ + +Además de **Active profiles** = `postgres`, añade en Environment variables: + +``` +SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/base_admin_web +SPRING_DATASOURCE_USERNAME=postgres +SPRING_DATASOURCE_PASSWORD=postgres +``` + +(Y las variables Cognito del paso anterior.) + +### 4. Ejecutar + +1. **Build → Build Project** (o `Ctrl+F9`). +2. Run ▶ en `BaseAdminWebApplication`. +3. Abre http://localhost:8080/login — usuario `admin` / `Admin123*`. + +### 5. Desarrollo sin Docker (modo local puro) + +Si no quieres Floci, desactiva Cognito y usa usuarios de la BD: + +| Variable | Valor | +|----------|-------| +| `APP_COGNITO_ENABLED` | `false` | + +No hace falta `AWS_ENDPOINT_URL` ni levantar Docker. Los usuarios `admin` y `scanner` se crean al arrancar vía `InitialDataLoader`. + +El login API devuelve JWT local (HS256); **refresh no está disponible** en este modo. + +### 6. Checklist de problemas frecuentes + +| Síntoma | Causa probable | Solución | +|---------|----------------|----------| +| Login web falla con Cognito habilitado | Floci no está arriba o faltan env vars | `docker compose up -d floci` + cargar `cognito.local.env` | +| Login va a AWS real en local | Falta `AWS_ENDPOINT_URL` | Definir `AWS_ENDPOINT_URL=http://localhost:4566` | +| `Invalid client id` | Client IDs desactualizados | Regenerar con bootstrap y copiar de `cognito.local.env` | +| H2 vacío o ruta rara | Working directory incorrecto | `$PROJECT_DIR$/base-admin-web-jar` | +| Error de versión Java | SDK distinto a 25 | Project Structure → SDK Java 25 | +| Tests en IDE fallan por Cognito | Perfil `test` desactiva Cognito | Ejecutar tests con perfil `test` (ya configurado en `application-test.yml`) | + +### 7. Comandos útiles desde IntelliJ + +| Acción | Comando Maven (ventana Maven o terminal) | +|--------|------------------------------------------| +| Compilar todo | `mvn -pl base-admin-web-jar -am -DskipTests install` | +| Tests | `mvn test` | +| Solo módulo jar | clic derecho en `base-admin-web-jar` → Run Maven → `spring-boot:run` (tras exportar env vars en la shell) | + +--- + +## Perfiles Spring + +| Perfil | Base de datos | Cognito | Uso | +|--------|---------------|---------|-----| +| `dev` (default) | H2 en archivo `./data/h2/` | Floci (manual o `run-local.sh`) | Desarrollo diario | +| `postgres` | PostgreSQL | Floci | Desarrollo con BD real | +| `prod` | Configurable | AWS | Producción | +| `test` | H2 en memoria `create-drop` | Deshabilitado | `mvn test` | + +--- + +## Variables de entorno + +### Aplicación + +| Variable | Default | Descripción | +|----------|---------|-------------| +| `SERVER_PORT` | `8080` | Puerto HTTP | +| `SPRING_PROFILES_ACTIVE` | `dev` | Perfil Spring | +| `APP_TITLE` | `Base Admin Web` | Título en vistas | + +### Cognito + +| Variable | Local (Floci) | Producción (AWS) | +|----------|---------------|------------------| +| `APP_COGNITO_ENABLED` | `true` | `true` | +| `COGNITO_REGION` | `us-east-1` | tu región | +| `COGNITO_USER_POOL_ID` | `us-east-1_BaseAdminWeb` | `us-east-1_XXXXX` | +| `COGNITO_ISSUER_URI` | `http://localhost:4566/...` | `https://cognito-idp.{region}.amazonaws.com/{poolId}` | +| `COGNITO_JWK_SET_URI` | `http://localhost:4566/.../jwks.json` | `https://cognito-idp.{region}.amazonaws.com/{poolId}/.well-known/jwks.json` | +| `COGNITO_ADMIN_CLIENT_ID` | generado por Floci | client del pool | +| `COGNITO_SCANNER_CLIENT_ID` | generado por Floci | client del pool | +| `COGNITO_ADMIN_GROUP` | `admins` | `admins` | +| `COGNITO_SCANNER_GROUP` | `scanners` | `scanners` | +| `AWS_ENDPOINT_URL` | `http://localhost:4566` | **no definir** | ### Base de datos -- `SPRING_DATASOURCE_URL` -- `SPRING_DATASOURCE_USERNAME` -- `SPRING_DATASOURCE_PASSWORD` -- `SPRING_JPA_HIBERNATE_DDL_AUTO` +| Variable | Descripción | +|----------|-------------| +| `SPRING_DATASOURCE_URL` | JDBC URL | +| `SPRING_DATASOURCE_USERNAME` | Usuario BD | +| `SPRING_DATASOURCE_PASSWORD` | Contraseña BD | +| `SPRING_JPA_HIBERNATE_DDL_AUTO` | `update` por defecto | -### JWT API +### JWT local (solo `APP_COGNITO_ENABLED=false`) -- `APP_JWT_SECRET` (minimo 32 caracteres) -- `APP_JWT_EXPIRATION_HOURS` +| Variable | Descripción | +|----------|-------------| +| `APP_JWT_SECRET` | Secreto HS256 (mín. 32 caracteres) | +| `APP_JWT_EXPIRATION_HOURS` | Expiración del token | -## Seguridad +### Usuarios semilla (modo local sin Cognito) -- Panel web: autenticacion por formulario con usuarios en base de datos -- API de escaneo: autenticacion stateless con JWT -- `/admin/**` y `/dashboard`: requieren rol `ADMIN` -- `/api/pass-codes/scan`: requiere rol `SCANNER` y token Bearer -- `/api/auth/login`: publico, pero solo emite token a usuarios `SCANNER` +| Variable | Default | +|----------|---------| +| `APP_ADMIN_USERNAME` / `APP_ADMIN_PASSWORD` | `admin` / `Admin123*` | +| `APP_SCANNER_USERNAME` / `APP_SCANNER_PASSWORD` | `scanner` / `Scanner123*` | + +--- ## Objetivo de esta base -Esta base deja resuelto lo minimo para arrancar nuevos proyectos internos: +Esta base deja resuelto lo mínimo para arrancar nuevos proyectos internos: -- autenticacion local funcional para administradores -- autenticacion API para escaneo movil o externo -- layout inicial con Metronic -- dashboard simple -- modulo de codigos pase con QR y API de escaneo -- estructura modular limpia -- soporte rapido para H2 y PostgreSQL +- Autenticación Cognito (local y AWS) con fallback a BD local +- Panel web con Metronic y sesión HTTP para administradores +- API stateless con JWT para escaneo móvil o externo +- App Flutter de referencia para escaneo QR +- Módulo de códigos pase con alta, listado, QR y API de escaneo +- Estructura modular Maven limpia +- Soporte rápido para H2 y PostgreSQL +- Floci para desarrollo sin depender de AWS -El dashboard actual se mantiene deliberadamente sencillo para servir como punto de partida para futuras integraciones. \ No newline at end of file +El dashboard se mantiene deliberadamente sencillo como punto de partida para futuras integraciones. \ No newline at end of file diff --git a/base-admin-web-configuration/pom.xml b/base-admin-web-configuration/pom.xml index 6b49d43..640b6c9 100644 --- a/base-admin-web-configuration/pom.xml +++ b/base-admin-web-configuration/pom.xml @@ -27,6 +27,10 @@ org.springframework.boot spring-boot-starter-oauth2-resource-server + + software.amazon.awssdk + cognitoidentityprovider + org.springframework.boot spring-boot-starter-thymeleaf diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiAuthService.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiAuthService.java index 3019404..c12173d 100644 --- a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiAuthService.java +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiAuthService.java @@ -1,77 +1,50 @@ package mx.gob.slp.baseadminweb.configuration.security; -import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.stereotype.Service; -import java.time.Instant; -import java.util.List; - @Service public class ApiAuthService { - private static final String SCANNER_ROLE = "ROLE_SCANNER"; + private final ObjectProvider cognitoAuthService; + private final ObjectProvider localJwtAuthService; - private final AuthenticationManager authenticationManager; - private final JwtEncoder jwtEncoder; - private final JwtProperties jwtProperties; - - public ApiAuthService(AuthenticationManager authenticationManager, - JwtEncoder jwtEncoder, - JwtProperties jwtProperties) { - this.authenticationManager = authenticationManager; - this.jwtEncoder = jwtEncoder; - this.jwtProperties = jwtProperties; + public ApiAuthService(ObjectProvider cognitoAuthService, + ObjectProvider localJwtAuthService) { + this.cognitoAuthService = cognitoAuthService; + this.localJwtAuthService = localJwtAuthService; } public ApiLoginResponse login(String username, String password) { - Authentication authentication = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(username, password)); - - List roles = authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .toList(); - - if (roles.stream().noneMatch(SCANNER_ROLE::equals)) { - throw new BadCredentialsException("El usuario no tiene permisos para escanear codigos."); + CognitoAuthService cognito = cognitoAuthService.getIfAvailable(); + if (cognito != null) { + return cognito.login(username, password); } - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(jwtProperties.getExpirationHours() * 3600L); + LocalJwtAuthService local = localJwtAuthService.getIfAvailable(); + if (local != null) { + return local.login(username, password); + } - JwtClaimsSet claims = JwtClaimsSet.builder() - .subject(authentication.getName()) - .issuedAt(issuedAt) - .expiresAt(expiresAt) - .claim("roles", roles) - .build(); - - JwsHeader header = JwsHeader.with(MacAlgorithm.HS256).build(); - String accessToken = jwtEncoder.encode(JwtEncoderParameters.from(header, claims)).getTokenValue(); - - return new ApiLoginResponse( - accessToken, - "Bearer", - jwtProperties.getExpirationHours() * 3600L, - authentication.getName(), - roles - ); + throw new IllegalStateException("No hay proveedor de autenticacion API configurado."); } - public record ApiLoginResponse( - String accessToken, - String tokenType, - long expiresIn, - String username, - List roles - ) { + public ApiLoginResponse refresh(String refreshToken) { + if (refreshToken == null || refreshToken.isBlank()) { + throw new BadCredentialsException("El refresh token es obligatorio."); + } + + CognitoAuthService cognito = cognitoAuthService.getIfAvailable(); + if (cognito != null) { + return cognito.refresh(refreshToken); + } + + LocalJwtAuthService local = localJwtAuthService.getIfAvailable(); + if (local != null) { + return local.refresh(refreshToken); + } + + throw new IllegalStateException("No hay proveedor de autenticacion API configurado."); } } \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiLoginResponse.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiLoginResponse.java new file mode 100644 index 0000000..0ddcc36 --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiLoginResponse.java @@ -0,0 +1,14 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import java.util.List; + +public record ApiLoginResponse( + String accessToken, + String refreshToken, + String idToken, + String tokenType, + long expiresIn, + String username, + List roles +) { +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiSecurityConfig.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiSecurityConfig.java index ee84e6c..984ebcb 100644 --- a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiSecurityConfig.java +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/ApiSecurityConfig.java @@ -27,7 +27,7 @@ public class ApiSecurityConfig { .csrf(AbstractHttpConfigurer::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/api/auth/login").permitAll() + .requestMatchers("/api/auth/login", "/api/auth/refresh").permitAll() .requestMatchers("/api/pass-codes/scan").hasRole("SCANNER") .anyRequest().authenticated() ) diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/AuthenticatedWebUser.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/AuthenticatedWebUser.java new file mode 100644 index 0000000..4958d01 --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/AuthenticatedWebUser.java @@ -0,0 +1,6 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import java.io.Serializable; + +public record AuthenticatedWebUser(String username, String displayName) implements Serializable { +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoAuthService.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoAuthService.java new file mode 100644 index 0000000..c6aaa30 --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoAuthService.java @@ -0,0 +1,52 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +@Service +@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true") +public class CognitoAuthService { + + private static final String SCANNER_ROLE = "ROLE_SCANNER"; + + private final CognitoUserAuthenticator cognitoUserAuthenticator; + private final CognitoProperties cognitoProperties; + + public CognitoAuthService(CognitoUserAuthenticator cognitoUserAuthenticator, + CognitoProperties cognitoProperties) { + this.cognitoUserAuthenticator = cognitoUserAuthenticator; + this.cognitoProperties = cognitoProperties; + } + + public ApiLoginResponse login(String username, String password) { + CognitoUserAuthenticator.AuthenticationResult result = cognitoUserAuthenticator.authenticate( + username, + password, + cognitoProperties.getScannerClientId(), + SCANNER_ROLE + ); + return toLoginResponse(result); + } + + public ApiLoginResponse refresh(String refreshToken) { + CognitoUserAuthenticator.AuthenticationResult result = cognitoUserAuthenticator.refresh( + refreshToken, + cognitoProperties.getScannerClientId(), + SCANNER_ROLE + ); + return toLoginResponse(result); + } + + private ApiLoginResponse toLoginResponse(CognitoUserAuthenticator.AuthenticationResult result) { + var tokens = result.tokens(); + return new ApiLoginResponse( + tokens.accessToken(), + result.refreshToken(), + tokens.idToken(), + tokens.tokenType() != null ? tokens.tokenType() : "Bearer", + tokens.expiresIn() != null ? tokens.expiresIn() : 0L, + result.username(), + result.roles() + ); + } +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoClientConfig.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoClientConfig.java new file mode 100644 index 0000000..2c503b2 --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoClientConfig.java @@ -0,0 +1,33 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; + +import java.net.URI; + +@Configuration +@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true") +public class CognitoClientConfig { + + @Bean(destroyMethod = "close") + CognitoIdentityProviderClient cognitoIdentityProviderClient(CognitoProperties properties) { + var builder = CognitoIdentityProviderClient.builder() + .region(Region.of(properties.getRegion())); + + if (properties.usesLocalEndpoint()) { + builder.endpointOverride(URI.create(properties.getEndpoint())) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create("test", "test"))); + } else { + builder.credentialsProvider(DefaultCredentialsProvider.create()); + } + + return builder.build(); + } +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoDisplayNameResolver.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoDisplayNameResolver.java new file mode 100644 index 0000000..ff54e64 --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoDisplayNameResolver.java @@ -0,0 +1,31 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import com.nimbusds.jwt.JWTParser; + +final class CognitoDisplayNameResolver { + + private CognitoDisplayNameResolver() { + } + + static String resolve(String idToken, String accessToken, String fallbackUsername) { + String displayName = extractNameClaim(idToken); + if (displayName == null || displayName.isBlank()) { + displayName = extractNameClaim(accessToken); + } + if (displayName == null || displayName.isBlank()) { + return fallbackUsername; + } + return displayName; + } + + private static String extractNameClaim(String token) { + if (token == null || token.isBlank()) { + return null; + } + try { + return JWTParser.parse(token).getJWTClaimsSet().getStringClaim("name"); + } catch (Exception ex) { + return null; + } + } +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoProperties.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoProperties.java new file mode 100644 index 0000000..7c986eb --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoProperties.java @@ -0,0 +1,102 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "app.cognito") +public class CognitoProperties { + + private boolean enabled; + private String region = "us-east-1"; + private String userPoolId = "us-east-1_BaseAdminWeb"; + private String issuerUri = "http://localhost:4566/us-east-1_BaseAdminWeb"; + private String jwkSetUri = "http://localhost:4566/us-east-1_BaseAdminWeb/.well-known/jwks.json"; + private String endpoint; + private String scannerClientId; + private String adminClientId; + private String scannerGroup = "scanners"; + private String adminGroup = "admins"; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getUserPoolId() { + return userPoolId; + } + + public void setUserPoolId(String userPoolId) { + this.userPoolId = userPoolId; + } + + public String getIssuerUri() { + return issuerUri; + } + + public void setIssuerUri(String issuerUri) { + this.issuerUri = issuerUri; + } + + public String getJwkSetUri() { + return jwkSetUri; + } + + public void setJwkSetUri(String jwkSetUri) { + this.jwkSetUri = jwkSetUri; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public boolean usesLocalEndpoint() { + return endpoint != null && !endpoint.isBlank(); + } + + public String getScannerClientId() { + return scannerClientId; + } + + public void setScannerClientId(String scannerClientId) { + this.scannerClientId = scannerClientId; + } + + public String getAdminClientId() { + return adminClientId; + } + + public void setAdminClientId(String adminClientId) { + this.adminClientId = adminClientId; + } + + public String getScannerGroup() { + return scannerGroup; + } + + public void setScannerGroup(String scannerGroup) { + this.scannerGroup = scannerGroup; + } + + public String getAdminGroup() { + return adminGroup; + } + + public void setAdminGroup(String adminGroup) { + this.adminGroup = adminGroup; + } +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoRoleMapper.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoRoleMapper.java new file mode 100644 index 0000000..526f9a6 --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoRoleMapper.java @@ -0,0 +1,29 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +final class CognitoRoleMapper { + + private CognitoRoleMapper() { + } + + static List toRoles(Collection groups, CognitoProperties properties) { + if (groups == null || groups.isEmpty()) { + return List.of(); + } + + List roles = new ArrayList<>(); + for (String group : groups) { + if (properties.getScannerGroup().equalsIgnoreCase(group)) { + roles.add("ROLE_SCANNER"); + } else if (properties.getAdminGroup().equalsIgnoreCase(group)) { + roles.add("ROLE_ADMIN"); + } else { + roles.add("ROLE_" + group.toUpperCase()); + } + } + return roles; + } +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoUserAuthenticator.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoUserAuthenticator.java new file mode 100644 index 0000000..d86748d --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoUserAuthenticator.java @@ -0,0 +1,119 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import com.nimbusds.jwt.JWTParser; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.stereotype.Component; +import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; +import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; +import software.amazon.awssdk.services.cognitoidentityprovider.model.NotAuthorizedException; +import software.amazon.awssdk.services.cognitoidentityprovider.model.UserNotFoundException; + +import java.text.ParseException; +import java.util.List; +import java.util.Map; + +@Component +@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true") +public class CognitoUserAuthenticator { + + private final CognitoIdentityProviderClient cognitoClient; + private final CognitoProperties cognitoProperties; + + public CognitoUserAuthenticator(CognitoIdentityProviderClient cognitoClient, + CognitoProperties cognitoProperties) { + this.cognitoClient = cognitoClient; + this.cognitoProperties = cognitoProperties; + } + + public AuthenticationResult authenticate(String username, + String password, + String clientId, + String requiredRole) { + try { + InitiateAuthResponse response = cognitoClient.initiateAuth(InitiateAuthRequest.builder() + .authFlow(AuthFlowType.USER_PASSWORD_AUTH) + .clientId(clientId) + .authParameters(Map.of( + "USERNAME", username, + "PASSWORD", password + )) + .build()); + + return toAuthenticationResult(response.authenticationResult(), username, requiredRole, null); + } catch (NotAuthorizedException | UserNotFoundException ex) { + throw new BadCredentialsException("Credenciales invalidas."); + } + } + + public AuthenticationResult refresh(String refreshToken, String clientId, String requiredRole) { + try { + InitiateAuthResponse response = cognitoClient.initiateAuth(InitiateAuthRequest.builder() + .authFlow(AuthFlowType.REFRESH_TOKEN_AUTH) + .clientId(clientId) + .authParameters(Map.of("REFRESH_TOKEN", refreshToken)) + .build()); + + return toAuthenticationResult(response.authenticationResult(), null, requiredRole, refreshToken); + } catch (NotAuthorizedException ex) { + throw new BadCredentialsException("Refresh token invalido o expirado."); + } + } + + private AuthenticationResult toAuthenticationResult(AuthenticationResultType authResult, + String fallbackUsername, + String requiredRole, + String fallbackRefreshToken) { + if (authResult == null || authResult.accessToken() == null) { + throw new BadCredentialsException("No se pudo autenticar con Cognito."); + } + + List groups = extractGroups(authResult.accessToken()); + List roles = CognitoRoleMapper.toRoles(groups, cognitoProperties); + if (roles.stream().noneMatch(requiredRole::equals)) { + throw new BadCredentialsException("El usuario no tiene permisos para acceder."); + } + + String username = extractUsername(authResult.accessToken(), fallbackUsername); + String refreshToken = authResult.refreshToken() != null + ? authResult.refreshToken() + : fallbackRefreshToken; + + return new AuthenticationResult(authResult, username, roles, groups, refreshToken); + } + + private String extractUsername(String accessToken, String fallback) { + try { + var claims = JWTParser.parse(accessToken).getJWTClaimsSet(); + String username = claims.getStringClaim("username"); + if (username == null || username.isBlank()) { + username = claims.getStringClaim("cognito:username"); + } + return username != null && !username.isBlank() ? username : fallback; + } catch (ParseException ex) { + throw new BadCredentialsException("No se pudo leer el usuario del token."); + } + } + + private List extractGroups(String accessToken) { + try { + var claims = JWTParser.parse(accessToken).getJWTClaimsSet(); + List groups = claims.getStringListClaim("cognito:groups"); + return groups != null ? groups : List.of(); + } catch (ParseException ex) { + throw new BadCredentialsException("No se pudieron leer los grupos del token."); + } + } + + public record AuthenticationResult( + AuthenticationResultType tokens, + String username, + List roles, + List groups, + String refreshToken + ) { + } +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoWebAuthenticationProvider.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoWebAuthenticationProvider.java new file mode 100644 index 0000000..5d32e0b --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoWebAuthenticationProvider.java @@ -0,0 +1,63 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true") +public class CognitoWebAuthenticationProvider implements AuthenticationProvider { + + private static final String ADMIN_ROLE = "ROLE_ADMIN"; + + private final CognitoUserAuthenticator cognitoUserAuthenticator; + private final CognitoProperties cognitoProperties; + + public CognitoWebAuthenticationProvider(CognitoUserAuthenticator cognitoUserAuthenticator, + CognitoProperties cognitoProperties) { + this.cognitoUserAuthenticator = cognitoUserAuthenticator; + this.cognitoProperties = cognitoProperties; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String username = authentication.getName(); + Object credentials = authentication.getCredentials(); + if (credentials == null) { + throw new BadCredentialsException("La contrasena es obligatoria."); + } + + CognitoUserAuthenticator.AuthenticationResult result = cognitoUserAuthenticator.authenticate( + username, + credentials.toString(), + cognitoProperties.getAdminClientId(), + ADMIN_ROLE + ); + + var authorities = result.roles().stream() + .map(SimpleGrantedAuthority::new) + .toList(); + + String displayName = CognitoDisplayNameResolver.resolve( + result.tokens().idToken(), + result.tokens().accessToken(), + result.username() + ); + + return UsernamePasswordAuthenticationToken.authenticated( + new AuthenticatedWebUser(result.username(), displayName), + null, + authorities + ); + } + + @Override + public boolean supports(Class authentication) { + return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); + } +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoWebSecurityConfig.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoWebSecurityConfig.java new file mode 100644 index 0000000..65d2b03 --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/CognitoWebSecurityConfig.java @@ -0,0 +1,17 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; + +@Configuration +@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true") +public class CognitoWebSecurityConfig { + + @Bean(name = "webAuthenticationManager") + AuthenticationManager webAuthenticationManager(CognitoWebAuthenticationProvider cognitoWebAuthenticationProvider) { + return new ProviderManager(cognitoWebAuthenticationProvider); + } +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/JwtConfig.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/JwtConfig.java index fa2df17..895fe77 100644 --- a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/JwtConfig.java +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/JwtConfig.java @@ -1,9 +1,13 @@ package mx.gob.slp.baseadminweb.configuration.security; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jose.jws.MacAlgorithm; +import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; @@ -19,12 +23,16 @@ import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; @Configuration -@EnableConfigurationProperties(JwtProperties.class) +@EnableConfigurationProperties({JwtProperties.class, CognitoProperties.class}) public class JwtConfig { @Bean + @ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "false") JwtEncoder jwtEncoder(JwtProperties jwtProperties) { OctetSequenceKey jwk = new OctetSequenceKey.Builder(jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8)) .algorithm(JWSAlgorithm.HS256) @@ -33,20 +41,35 @@ public class JwtConfig { } @Bean - JwtDecoder jwtDecoder(JwtProperties jwtProperties) { + @ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "false") + JwtDecoder localJwtDecoder(JwtProperties jwtProperties) { return NimbusJwtDecoder.withSecretKey(secretKey(jwtProperties)) .macAlgorithm(MacAlgorithm.HS256) .build(); } @Bean - JwtAuthenticationConverter jwtAuthenticationConverter() { - JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter(); - authoritiesConverter.setAuthoritiesClaimName("roles"); - authoritiesConverter.setAuthorityPrefix(""); + @ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true") + JwtDecoder cognitoJwtDecoder(CognitoProperties cognitoProperties) { + return NimbusJwtDecoder.withJwkSetUri(cognitoProperties.getJwkSetUri()).build(); + } + + @Bean + JwtAuthenticationConverter jwtAuthenticationConverter(CognitoProperties cognitoProperties) { + JwtGrantedAuthoritiesConverter localRolesConverter = new JwtGrantedAuthoritiesConverter(); + localRolesConverter.setAuthoritiesClaimName("roles"); + localRolesConverter.setAuthorityPrefix(""); JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter(); - authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter); + authenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> { + Collection authorities = new ArrayList<>(); + List groups = jwt.getClaimAsStringList("cognito:groups"); + CognitoRoleMapper.toRoles(groups, cognitoProperties).stream() + .map(SimpleGrantedAuthority::new) + .forEach(authorities::add); + authorities.addAll(localRolesConverter.convert(jwt)); + return authorities; + }); return authenticationConverter; } diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/LocalJwtAuthService.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/LocalJwtAuthService.java new file mode 100644 index 0000000..14c5ccb --- /dev/null +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/LocalJwtAuthService.java @@ -0,0 +1,76 @@ +package mx.gob.slp.baseadminweb.configuration.security; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.jose.jws.MacAlgorithm; +import org.springframework.security.oauth2.jwt.JwsHeader; +import org.springframework.security.oauth2.jwt.JwtClaimsSet; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.JwtEncoderParameters; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.List; + +@Service +@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "false") +public class LocalJwtAuthService { + + private static final String SCANNER_ROLE = "ROLE_SCANNER"; + + private final AuthenticationManager authenticationManager; + private final JwtEncoder jwtEncoder; + private final JwtProperties jwtProperties; + + public LocalJwtAuthService(AuthenticationManager authenticationManager, + JwtEncoder jwtEncoder, + JwtProperties jwtProperties) { + this.authenticationManager = authenticationManager; + this.jwtEncoder = jwtEncoder; + this.jwtProperties = jwtProperties; + } + + public ApiLoginResponse login(String username, String password) { + Authentication authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(username, password)); + + List roles = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .toList(); + + if (roles.stream().noneMatch(SCANNER_ROLE::equals)) { + throw new BadCredentialsException("El usuario no tiene permisos para escanear codigos."); + } + + Instant issuedAt = Instant.now(); + Instant expiresAt = issuedAt.plusSeconds(jwtProperties.getExpirationHours() * 3600L); + + JwtClaimsSet claims = JwtClaimsSet.builder() + .subject(authentication.getName()) + .issuedAt(issuedAt) + .expiresAt(expiresAt) + .claim("roles", roles) + .build(); + + JwsHeader header = JwsHeader.with(MacAlgorithm.HS256).build(); + String accessToken = jwtEncoder.encode(JwtEncoderParameters.from(header, claims)).getTokenValue(); + + return new ApiLoginResponse( + accessToken, + null, + null, + "Bearer", + jwtProperties.getExpirationHours() * 3600L, + authentication.getName(), + roles + ); + } + + public ApiLoginResponse refresh(String refreshToken) { + throw new BadCredentialsException("Refresh token no disponible sin Cognito."); + } +} \ No newline at end of file diff --git a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/SecurityConfig.java b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/SecurityConfig.java index 1a128fe..e861cbd 100644 --- a/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/SecurityConfig.java +++ b/base-admin-web-configuration/src/main/java/mx/gob/slp/baseadminweb/configuration/security/SecurityConfig.java @@ -1,10 +1,13 @@ package mx.gob.slp.baseadminweb.configuration.security; import mx.gob.slp.baseadminweb.application.service.AppUserDetailsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; @@ -22,12 +25,17 @@ public class SecurityConfig { private final AppUserDetailsService appUserDetailsService; + @Autowired(required = false) + @Qualifier("webAuthenticationManager") + private AuthenticationManager webAuthenticationManager; + public SecurityConfig(AppUserDetailsService appUserDetailsService) { this.appUserDetailsService = appUserDetailsService; } @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http, + PasswordEncoder passwordEncoder) throws Exception { http .securityMatcher(request -> !request.getRequestURI().startsWith("/api/")) .authorizeHttpRequests(authorize -> authorize @@ -49,9 +57,16 @@ public class SecurityConfig { .deleteCookies("JSESSIONID") ) .headers(headers -> headers.frameOptions(frame -> frame.sameOrigin())) - .userDetailsService(appUserDetailsService) .rememberMe(Customizer.withDefaults()); + if (webAuthenticationManager != null) { + http.authenticationManager(webAuthenticationManager); + } else { + DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(appUserDetailsService); + daoAuthenticationProvider.setPasswordEncoder(passwordEncoder); + http.authenticationProvider(daoAuthenticationProvider); + } + http.csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**")); return http.build(); } @@ -65,4 +80,4 @@ public class SecurityConfig { public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } -} +} \ No newline at end of file diff --git a/base-admin-web-jar/data/h2/baseadminweb.mv.db b/base-admin-web-jar/data/h2/baseadminweb.mv.db deleted file mode 100644 index 6b78434781cfc67dad22f2508055f1fd94b963b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40960 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 D3+BLX diff --git a/base-admin-web-jar/src/main/resources/application-test.yml b/base-admin-web-jar/src/main/resources/application-test.yml index 880aa51..5604b2c 100644 --- a/base-admin-web-jar/src/main/resources/application-test.yml +++ b/base-admin-web-jar/src/main/resources/application-test.yml @@ -1,3 +1,7 @@ +app: + cognito: + enabled: false + spring: datasource: url: jdbc:h2:mem:baseadminweb-test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE diff --git a/base-admin-web-jar/src/main/resources/application.yml b/base-admin-web-jar/src/main/resources/application.yml index 268eccd..c5fdf94 100644 --- a/base-admin-web-jar/src/main/resources/application.yml +++ b/base-admin-web-jar/src/main/resources/application.yml @@ -35,6 +35,17 @@ logging: app: title: ${APP_TITLE:Base Admin Web} + cognito: + enabled: ${APP_COGNITO_ENABLED:true} + region: ${COGNITO_REGION:us-east-1} + user-pool-id: ${COGNITO_USER_POOL_ID:us-east-1_BaseAdminWeb} + issuer-uri: ${COGNITO_ISSUER_URI:http://localhost:4566/us-east-1_BaseAdminWeb} + jwk-set-uri: ${COGNITO_JWK_SET_URI:http://localhost:4566/us-east-1_BaseAdminWeb/.well-known/jwks.json} + endpoint: ${AWS_ENDPOINT_URL:} + scanner-client-id: ${COGNITO_SCANNER_CLIENT_ID:4f878f50de4847b7adc7c0632a} + admin-client-id: ${COGNITO_ADMIN_CLIENT_ID:dde208d5b768465db6a2d8c6d2} + scanner-group: ${COGNITO_SCANNER_GROUP:scanners} + admin-group: ${COGNITO_ADMIN_GROUP:admins} jwt: secret: ${APP_JWT_SECRET:base-admin-web-dev-secret-change-me-32chars!!} expiration-hours: ${APP_JWT_EXPIRATION_HOURS:8} diff --git a/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/AuthApiController.java b/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/AuthApiController.java index 9900450..3074108 100644 --- a/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/AuthApiController.java +++ b/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/AuthApiController.java @@ -1,6 +1,7 @@ package mx.gob.slp.baseadminweb.web.controller; import mx.gob.slp.baseadminweb.configuration.security.ApiAuthService; +import mx.gob.slp.baseadminweb.configuration.security.ApiLoginResponse; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.BadCredentialsException; @@ -9,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.LinkedHashMap; import java.util.Map; @RestController @@ -24,23 +26,46 @@ public class AuthApiController { @PostMapping("/login") public ResponseEntity login(@RequestBody LoginRequest request) { try { - ApiAuthService.ApiLoginResponse response = apiAuthService.login(request.username(), request.password()); - return ResponseEntity.ok(Map.of( - "success", true, - "accessToken", response.accessToken(), - "tokenType", response.tokenType(), - "expiresIn", response.expiresIn(), - "username", response.username(), - "roles", response.roles() - )); + ApiLoginResponse response = apiAuthService.login(request.username(), request.password()); + return ResponseEntity.ok(toAuthBody(response)); } catch (BadCredentialsException ex) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of( - "success", false, - "message", ex.getMessage() - )); + return unauthorized(ex.getMessage()); } } + @PostMapping("/refresh") + public ResponseEntity refresh(@RequestBody RefreshRequest request) { + try { + ApiLoginResponse response = apiAuthService.refresh(request.refreshToken()); + return ResponseEntity.ok(toAuthBody(response)); + } catch (BadCredentialsException ex) { + return unauthorized(ex.getMessage()); + } + } + + private Map toAuthBody(ApiLoginResponse response) { + Map body = new LinkedHashMap<>(); + body.put("success", true); + body.put("accessToken", response.accessToken()); + body.put("refreshToken", response.refreshToken()); + body.put("idToken", response.idToken()); + body.put("tokenType", response.tokenType()); + body.put("expiresIn", response.expiresIn()); + body.put("username", response.username()); + body.put("roles", response.roles()); + return body; + } + + private ResponseEntity> unauthorized(String message) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of( + "success", false, + "message", message + )); + } + public record LoginRequest(String username, String password) { } + + public record RefreshRequest(String refreshToken) { + } } \ No newline at end of file diff --git a/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/DashboardController.java b/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/DashboardController.java index 14d1ae8..33b6eb9 100644 --- a/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/DashboardController.java +++ b/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/DashboardController.java @@ -1,7 +1,7 @@ package mx.gob.slp.baseadminweb.web.controller; -import mx.gob.slp.baseadminweb.domain.model.AppUser; -import org.springframework.security.core.annotation.AuthenticationPrincipal; +import mx.gob.slp.baseadminweb.web.support.WebUserView; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -15,8 +15,8 @@ public class DashboardController { } @GetMapping("/dashboard") - public String dashboard(@AuthenticationPrincipal AppUser user, Model model) { - model.addAttribute("user", user); + public String dashboard(Authentication authentication, Model model) { + model.addAttribute("user", WebUserView.from(authentication)); return "dashboard"; } @@ -24,4 +24,4 @@ public class DashboardController { public String adminHome() { return "redirect:/dashboard"; } -} +} \ No newline at end of file diff --git a/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/PassCodeController.java b/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/PassCodeController.java index b277e98..48f3106 100644 --- a/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/PassCodeController.java +++ b/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/controller/PassCodeController.java @@ -1,9 +1,9 @@ package mx.gob.slp.baseadminweb.web.controller; import mx.gob.slp.baseadminweb.application.service.PassCodeService; -import mx.gob.slp.baseadminweb.domain.model.AppUser; import mx.gob.slp.baseadminweb.web.dto.PassCodeListItem; -import org.springframework.security.core.annotation.AuthenticationPrincipal; +import mx.gob.slp.baseadminweb.web.support.WebUserView; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -26,8 +26,8 @@ public class PassCodeController { } @GetMapping - public String list(@AuthenticationPrincipal AppUser user, Model model) { - model.addAttribute("user", user); + public String list(Authentication authentication, Model model) { + model.addAttribute("user", WebUserView.from(authentication)); model.addAttribute("passCodes", passCodeService.findAll()); return "pass-codes"; } diff --git a/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/support/WebUserView.java b/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/support/WebUserView.java new file mode 100644 index 0000000..9fc8ac2 --- /dev/null +++ b/base-admin-web-web/src/main/java/mx/gob/slp/baseadminweb/web/support/WebUserView.java @@ -0,0 +1,25 @@ +package mx.gob.slp.baseadminweb.web.support; + +import mx.gob.slp.baseadminweb.configuration.security.AuthenticatedWebUser; +import mx.gob.slp.baseadminweb.domain.model.AppUser; +import org.springframework.security.core.Authentication; + +public record WebUserView(String username, String displayName) { + + public static WebUserView from(Authentication authentication) { + if (authentication == null) { + return new WebUserView("usuario", "Usuario"); + } + + Object principal = authentication.getPrincipal(); + if (principal instanceof AppUser appUser) { + return new WebUserView(appUser.getUsername(), appUser.getDisplayName()); + } + if (principal instanceof AuthenticatedWebUser webUser) { + return new WebUserView(webUser.username(), webUser.displayName()); + } + + String name = authentication.getName(); + return new WebUserView(name, name); + } +} \ No newline at end of file diff --git a/base-admin-web-web/src/main/resources/templates/login.html b/base-admin-web-web/src/main/resources/templates/login.html index e28d85a..23a2e8b 100644 --- a/base-admin-web-web/src/main/resources/templates/login.html +++ b/base-admin-web-web/src/main/resources/templates/login.html @@ -45,9 +45,12 @@
- Usuario demo: admin + Usuario: admin Password: Admin123*
+
+ Autenticacion via Cognito local (grupo admins) +
diff --git a/docker-compose-postgres.local.yml b/docker-compose-postgres.local.yml index 0649662..7436ce2 100644 --- a/docker-compose-postgres.local.yml +++ b/docker-compose-postgres.local.yml @@ -1,15 +1,3 @@ -services: - postgres: - image: postgres:16 - container_name: base-admin-web-postgres - environment: - POSTGRES_DB: base_admin_web - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ports: - - "5432:5432" - volumes: - - base_admin_web_postgres_data:/var/lib/postgresql/data - -volumes: - base_admin_web_postgres_data: +# Alias de compatibilidad. Usa docker-compose.local.yml como archivo principal. +include: + - path: docker-compose.local.yml \ No newline at end of file diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..fcecfba --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,45 @@ +services: + postgres: + image: postgres:16 + container_name: base-admin-web-postgres + environment: + POSTGRES_DB: base_admin_web + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - "5432:5432" + volumes: + - base_admin_web_postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d base_admin_web"] + interval: 5s + timeout: 5s + retries: 10 + + floci: + image: floci/floci:latest-compat + container_name: base-admin-web-floci + ports: + - "4566:4566" + volumes: + - base_admin_web_floci_data:/app/data + - ./docker/floci/init/ready.d:/etc/floci/init/ready.d:ro + - ./docker/floci/generated:/generated + environment: + FLOCI_HOSTNAME: floci + FLOCI_STORAGE_MODE: persistent + FLOCI_STORAGE_PERSISTENT_PATH: /app/data + FLOCI_DEFAULT_REGION: us-east-1 + FLOCI_DEFAULT_ACCOUNT_ID: "000000000000" + FLOCI_INIT_HOOKS_TIMEOUT_SECONDS: "120" + COGNITO_USER_POOL_ID: us-east-1_BaseAdminWeb + COGNITO_ADMIN_USERNAME: admin + COGNITO_ADMIN_PASSWORD: Admin123* + COGNITO_SCANNER_USERNAME: scanner + COGNITO_SCANNER_PASSWORD: Scanner123* + COGNITO_ENV_OUTPUT_DIR: /generated + restart: unless-stopped + +volumes: + base_admin_web_postgres_data: + base_admin_web_floci_data: \ No newline at end of file diff --git a/docker/floci/cognito.local.env.example b/docker/floci/cognito.local.env.example new file mode 100644 index 0000000..c692cc8 --- /dev/null +++ b/docker/floci/cognito.local.env.example @@ -0,0 +1,15 @@ +# Copia generada automaticamente en ./docker/floci/generated/cognito.local.env +# despues del primer arranque de Floci. + +COGNITO_USER_POOL_ID=us-east-1_BaseAdminWeb +COGNITO_REGION=us-east-1 +COGNITO_ISSUER_URI=http://localhost:4566/us-east-1_BaseAdminWeb +COGNITO_JWK_SET_URI=http://localhost:4566/us-east-1_BaseAdminWeb/.well-known/jwks.json +COGNITO_ADMIN_CLIENT_ID=REEMPLAZAR_CON_VALOR_GENERADO +COGNITO_SCANNER_CLIENT_ID=REEMPLAZAR_CON_VALOR_GENERADO +COGNITO_ADMIN_GROUP=admins +COGNITO_SCANNER_GROUP=scanners +AWS_ENDPOINT_URL=http://localhost:4566 +AWS_DEFAULT_REGION=us-east-1 +AWS_ACCESS_KEY_ID=test +AWS_SECRET_ACCESS_KEY=test \ No newline at end of file diff --git a/docker/floci/generated/.gitkeep b/docker/floci/generated/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker/floci/init/ready.d/10-cognito-bootstrap.sh b/docker/floci/init/ready.d/10-cognito-bootstrap.sh new file mode 100755 index 0000000..b78374c --- /dev/null +++ b/docker/floci/init/ready.d/10-cognito-bootstrap.sh @@ -0,0 +1,172 @@ +#!/bin/sh +set -eu + +POOL_ID="${COGNITO_USER_POOL_ID:-us-east-1_BaseAdminWeb}" +POOL_NAME="${COGNITO_USER_POOL_NAME:-base-admin-web}" +REGION="${AWS_DEFAULT_REGION:-us-east-1}" +DATA_DIR="${FLOCI_STORAGE_PERSISTENT_PATH:-/app/data}" +ENV_OUTPUT_DIR="${COGNITO_ENV_OUTPUT_DIR:-/generated}" +ENV_FILE="${ENV_OUTPUT_DIR}/cognito.local.env" + +ADMIN_USERNAME="${COGNITO_ADMIN_USERNAME:-admin}" +ADMIN_PASSWORD="${COGNITO_ADMIN_PASSWORD:-Admin123*}" +ADMIN_NAME="${COGNITO_ADMIN_DISPLAY_NAME:-Administrador Base}" + +SCANNER_USERNAME="${COGNITO_SCANNER_USERNAME:-scanner}" +SCANNER_PASSWORD="${COGNITO_SCANNER_PASSWORD:-Scanner123*}" +SCANNER_NAME="${COGNITO_SCANNER_DISPLAY_NAME:-Escaneador}" + +ADMIN_CLIENT_NAME="${COGNITO_ADMIN_CLIENT_NAME:-base-admin-web-admin}" +SCANNER_CLIENT_NAME="${COGNITO_SCANNER_CLIENT_NAME:-base-admin-web-scanner}" +ADMIN_GROUP="${COGNITO_ADMIN_GROUP:-admins}" +SCANNER_GROUP="${COGNITO_SCANNER_GROUP:-scanners}" + +mkdir -p "$DATA_DIR" "$ENV_OUTPUT_DIR" + +log() { + printf '[cognito-bootstrap] %s\n' "$1" >&2 +} + +pool_exists() { + aws cognito-idp describe-user-pool --user-pool-id "$POOL_ID" >/dev/null 2>&1 +} + +create_pool() { + log "Creando user pool $POOL_ID" + aws cognito-idp create-user-pool \ + --pool-name "$POOL_NAME" \ + --user-pool-tags "floci:override-id=${POOL_ID}" \ + --policies "PasswordPolicy={MinimumLength=8,RequireUppercase=true,RequireLowercase=true,RequireNumbers=true,RequireSymbols=true}" \ + --username-configuration "CaseSensitive=false" \ + --mfa-configuration OFF \ + --account-recovery-setting "RecoveryMechanisms=[{Priority=1,Name=verified_email}]" +} + +find_client_id() { + client_name="$1" + aws cognito-idp list-user-pool-clients \ + --user-pool-id "$POOL_ID" \ + --max-results 60 \ + --query "UserPoolClients[?ClientName=='${client_name}'].ClientId | [0]" \ + --output text +} + +create_client() { + client_name="$1" + existing_id="$(find_client_id "$client_name" || true)" + if [ -n "$existing_id" ] && [ "$existing_id" != "None" ]; then + log "App client $client_name ya existe ($existing_id)" + printf '%s' "$existing_id" + return 0 + fi + + log "Creando app client $client_name" + aws cognito-idp create-user-pool-client \ + --user-pool-id "$POOL_ID" \ + --client-name "$client_name" \ + --explicit-auth-flows \ + ALLOW_USER_PASSWORD_AUTH \ + ALLOW_REFRESH_TOKEN_AUTH \ + ALLOW_USER_SRP_AUTH \ + --prevent-user-existence-errors ENABLED \ + --supported-identity-providers COGNITO \ + --query "UserPoolClient.ClientId" \ + --output text +} + +create_group_if_needed() { + group_name="$1" + description="$2" + if aws cognito-idp get-group --user-pool-id "$POOL_ID" --group-name "$group_name" >/dev/null 2>&1; then + log "Grupo $group_name ya existe" + return 0 + fi + + log "Creando grupo $group_name" + aws cognito-idp create-group \ + --user-pool-id "$POOL_ID" \ + --group-name "$group_name" \ + --description "$description" +} + +user_exists() { + username="$1" + aws cognito-idp admin-get-user \ + --user-pool-id "$POOL_ID" \ + --username "$username" >/dev/null 2>&1 +} + +create_user_if_needed() { + username="$1" + password="$2" + display_name="$3" + group_name="$4" + + if ! user_exists "$username"; then + log "Creando usuario $username" + aws cognito-idp admin-create-user \ + --user-pool-id "$POOL_ID" \ + --username "$username" \ + --message-action SUPPRESS \ + --user-attributes "Name=name,Value=${display_name}" + else + log "Usuario $username ya existe" + fi + + aws cognito-idp admin-set-user-password \ + --user-pool-id "$POOL_ID" \ + --username "$username" \ + --password "$password" \ + --permanent + + aws cognito-idp admin-add-user-to-group \ + --user-pool-id "$POOL_ID" \ + --username "$username" \ + --group-name "$group_name" >/dev/null 2>&1 || true +} + +write_env_file() { + admin_client_id="$1" + scanner_client_id="$2" + + cat > "$ENV_FILE" < - - - diff --git a/flutter-app/qrscanner/android/app/src/main/AndroidManifest.xml b/flutter-app/qrscanner/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 2fbd078..0000000 --- a/flutter-app/qrscanner/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/flutter-app/qrscanner/android/app/src/main/kotlin/com/example/qrscanner/MainActivity.kt b/flutter-app/qrscanner/android/app/src/main/kotlin/com/example/qrscanner/MainActivity.kt deleted file mode 100644 index ca0660c..0000000 --- a/flutter-app/qrscanner/android/app/src/main/kotlin/com/example/qrscanner/MainActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.qrscanner - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity : FlutterActivity() diff --git a/flutter-app/qrscanner/android/app/src/main/res/drawable-v21/launch_background.xml b/flutter-app/qrscanner/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f..0000000 --- a/flutter-app/qrscanner/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/flutter-app/qrscanner/android/app/src/main/res/drawable/launch_background.xml b/flutter-app/qrscanner/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f..0000000 --- a/flutter-app/qrscanner/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/flutter-app/qrscanner/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/flutter-app/qrscanner/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/flutter-app/qrscanner/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/flutter-app/qrscanner/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/flutter-app/qrscanner/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/flutter-app/qrscanner/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/flutter-app/qrscanner/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/flutter-app/qrscanner/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/flutter-app/qrscanner/android/app/src/main/res/values-night/styles.xml b/flutter-app/qrscanner/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be..0000000 --- a/flutter-app/qrscanner/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/flutter-app/qrscanner/android/app/src/main/res/values/styles.xml b/flutter-app/qrscanner/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef88..0000000 --- a/flutter-app/qrscanner/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/flutter-app/qrscanner/android/app/src/profile/AndroidManifest.xml b/flutter-app/qrscanner/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f698..0000000 --- a/flutter-app/qrscanner/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/flutter-app/qrscanner/android/build.gradle.kts b/flutter-app/qrscanner/android/build.gradle.kts deleted file mode 100644 index dbee657..0000000 --- a/flutter-app/qrscanner/android/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -val newBuildDir: Directory = - rootProject.layout.buildDirectory - .dir("../../build") - .get() -rootProject.layout.buildDirectory.value(newBuildDir) - -subprojects { - val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) - project.layout.buildDirectory.value(newSubprojectBuildDir) -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean") { - delete(rootProject.layout.buildDirectory) -} diff --git a/flutter-app/qrscanner/android/gradle.properties b/flutter-app/qrscanner/android/gradle.properties deleted file mode 100644 index e96108c..0000000 --- a/flutter-app/qrscanner/android/gradle.properties +++ /dev/null @@ -1,6 +0,0 @@ -org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -# This newDsl flag was added by the Flutter template -android.newDsl=false -# This builtInKotlin flag was added by the Flutter template -android.builtInKotlin=false diff --git a/flutter-app/qrscanner/android/gradle/wrapper/gradle-wrapper.properties b/flutter-app/qrscanner/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 2d428bf..0000000 --- a/flutter-app/qrscanner/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/flutter-app/qrscanner/android/settings.gradle.kts b/flutter-app/qrscanner/android/settings.gradle.kts deleted file mode 100644 index c21f0c5..0000000 --- a/flutter-app/qrscanner/android/settings.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -pluginManagement { - val flutterSdkPath = - run { - val properties = java.util.Properties() - file("local.properties").inputStream().use { properties.load(it) } - val flutterSdkPath = properties.getProperty("flutter.sdk") - require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } - flutterSdkPath - } - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "9.0.1" apply false - id("org.jetbrains.kotlin.android") version "2.3.20" apply false -} - -include(":app") diff --git a/flutter-app/qrscanner/assets/sounds/scan.wav b/flutter-app/qrscanner/assets/sounds/scan.wav deleted file mode 100644 index 298f9899a645cd35ea05635ccab498eeaa54c240..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5336 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 diff --git a/flutter-app/qrscanner/assets/sounds/warning.wav b/flutter-app/qrscanner/assets/sounds/warning.wav deleted file mode 100644 index c4c35506325d70be251bc2c1338a01aa8781d1bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35324 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 - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - - diff --git a/flutter-app/qrscanner/ios/Flutter/Debug.xcconfig b/flutter-app/qrscanner/ios/Flutter/Debug.xcconfig deleted file mode 100644 index 592ceee..0000000 --- a/flutter-app/qrscanner/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/flutter-app/qrscanner/ios/Flutter/Release.xcconfig b/flutter-app/qrscanner/ios/Flutter/Release.xcconfig deleted file mode 100644 index 592ceee..0000000 --- a/flutter-app/qrscanner/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/flutter-app/qrscanner/ios/Runner.xcodeproj/project.pbxproj b/flutter-app/qrscanner/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index d04de4b..0000000 --- a/flutter-app/qrscanner/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,644 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 97C146ED1CF9000F007C117D; - remoteInfo = Runner; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 331C8082294A63A400263BE5 /* RunnerTests */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - 331C8081294A63A400263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - 331C807D294A63A400263BE5 /* Sources */, - 331C807F294A63A400263BE5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 331C8086294A63A400263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C8080294A63A400263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 97C146ED1CF9000F007C117D; - }; - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, - ); - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - 331C8080294A63A400263BE5 /* RunnerTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 331C8088294A63A400263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Debug; - }; - 331C8089294A63A400263BE5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Release; - }; - 331C808A294A63A400263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C8088294A63A400263BE5 /* Debug */, - 331C8089294A63A400263BE5 /* Release */, - 331C808A294A63A400263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; -/* End XCLocalSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { - isa = XCSwiftPackageProductDependency; - productName = FlutterGeneratedPluginSwiftPackage; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/flutter-app/qrscanner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/flutter-app/qrscanner/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter-app/qrscanner/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index c3fedb2..0000000 --- a/flutter-app/qrscanner/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/flutter-app/qrscanner/ios/Runner.xcworkspace/contents.xcworkspacedata b/flutter-app/qrscanner/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a1..0000000 --- a/flutter-app/qrscanner/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/flutter-app/qrscanner/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter-app/qrscanner/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/flutter-app/qrscanner/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/flutter-app/qrscanner/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/flutter-app/qrscanner/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/flutter-app/qrscanner/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/flutter-app/qrscanner/ios/Runner/AppDelegate.swift b/flutter-app/qrscanner/ios/Runner/AppDelegate.swift deleted file mode 100644 index c30b367..0000000 --- a/flutter-app/qrscanner/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Flutter -import UIKit - -@main -@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } - - func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { - GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) - } -} diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fa..0000000 --- a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 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 diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2..0000000 --- a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725..0000000 --- a/flutter-app/qrscanner/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/flutter-app/qrscanner/ios/Runner/Base.lproj/LaunchScreen.storyboard b/flutter-app/qrscanner/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c..0000000 --- a/flutter-app/qrscanner/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/flutter-app/qrscanner/ios/Runner/Base.lproj/Main.storyboard b/flutter-app/qrscanner/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c2851..0000000 --- a/flutter-app/qrscanner/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/flutter-app/qrscanner/ios/Runner/Info.plist b/flutter-app/qrscanner/ios/Runner/Info.plist deleted file mode 100644 index 1b77c6e..0000000 --- a/flutter-app/qrscanner/ios/Runner/Info.plist +++ /dev/null @@ -1,72 +0,0 @@ - - - - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Qrscanner - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - qrscanner - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - NSCameraUsageDescription - La app necesita la cámara para escanear códigos QR de pase. - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneClassName - UIWindowScene - UISceneConfigurationName - flutter - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - - - - - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/flutter-app/qrscanner/ios/Runner/Runner-Bridging-Header.h b/flutter-app/qrscanner/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a5..0000000 --- a/flutter-app/qrscanner/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/flutter-app/qrscanner/ios/Runner/SceneDelegate.swift b/flutter-app/qrscanner/ios/Runner/SceneDelegate.swift deleted file mode 100644 index b9ce8ea..0000000 --- a/flutter-app/qrscanner/ios/Runner/SceneDelegate.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Flutter -import UIKit - -class SceneDelegate: FlutterSceneDelegate { - -} diff --git a/flutter-app/qrscanner/ios/RunnerTests/RunnerTests.swift b/flutter-app/qrscanner/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index 86a7c3b..0000000 --- a/flutter-app/qrscanner/ios/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Flutter -import UIKit -import XCTest - -class RunnerTests: XCTestCase { - - func testExample() { - // If you add code to the Runner application, consider adding tests here. - // See https://developer.apple.com/documentation/xctest for more information about using XCTest. - } - -} diff --git a/flutter-app/qrscanner/lib/config/api_config.dart b/flutter-app/qrscanner/lib/config/api_config.dart deleted file mode 100644 index ddb0b71..0000000 --- a/flutter-app/qrscanner/lib/config/api_config.dart +++ /dev/null @@ -1,16 +0,0 @@ -/// URL base del API de base-admin-web. -/// -/// Configúrala al ejecutar o compilar si necesitas otra IP: -/// `flutter run --dart-define=API_BASE_URL=http://otra-ip:8080` -class ApiConfig { - ApiConfig._(); - - static const String baseUrl = String.fromEnvironment( - 'API_BASE_URL', - defaultValue: 'http://10.99.51.6:8080', - ); - - static String get loginUrl => '$baseUrl/api/auth/login'; - - static String get scanUrl => '$baseUrl/api/pass-codes/scan'; -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/main.dart b/flutter-app/qrscanner/lib/main.dart deleted file mode 100644 index 3261d47..0000000 --- a/flutter-app/qrscanner/lib/main.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:qrscanner/screens/login_screen.dart'; -import 'package:qrscanner/theme/app_theme.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'QR Scanner', - debugShowCheckedModeBanner: false, - theme: AppTheme.darkTheme, - home: const LoginScreen(), - ); - } -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/models/auth_session.dart b/flutter-app/qrscanner/lib/models/auth_session.dart deleted file mode 100644 index 1bddef2..0000000 --- a/flutter-app/qrscanner/lib/models/auth_session.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:qrscanner/models/login_response.dart'; - -class AuthSession { - const AuthSession({ - required this.username, - required this.accessToken, - required this.tokenType, - required this.expiresIn, - required this.roles, - }); - - factory AuthSession.fromLoginResponse(LoginResponse response) { - return AuthSession( - username: response.username ?? '', - accessToken: response.accessToken ?? '', - tokenType: response.tokenType ?? 'Bearer', - expiresIn: response.expiresIn ?? 0, - roles: response.roles, - ); - } - - factory AuthSession.fromJson(Map json) { - return AuthSession( - username: json['username'] as String? ?? '', - accessToken: json['accessToken'] as String? ?? '', - tokenType: json['tokenType'] as String? ?? 'Bearer', - expiresIn: (json['expiresIn'] as num?)?.toInt() ?? 0, - roles: (json['roles'] as List?) - ?.map((role) => role.toString()) - .toList() ?? - const [], - ); - } - - final String username; - final String accessToken; - final String tokenType; - final int expiresIn; - final List roles; - - String get authorizationHeader => '$tokenType $accessToken'; - - bool get isScanner => roles.contains('ROLE_SCANNER'); - - Map toJson() => { - 'username': username, - 'accessToken': accessToken, - 'tokenType': tokenType, - 'expiresIn': expiresIn, - 'roles': roles, - }; -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/models/login_response.dart b/flutter-app/qrscanner/lib/models/login_response.dart deleted file mode 100644 index 1b62970..0000000 --- a/flutter-app/qrscanner/lib/models/login_response.dart +++ /dev/null @@ -1,54 +0,0 @@ -class LoginResponse { - const LoginResponse({ - required this.success, - this.accessToken, - this.tokenType, - this.expiresIn, - this.username, - this.roles = const [], - this.message, - }); - - factory LoginResponse.fromJson(Map json) { - return LoginResponse( - success: json['success'] == true, - accessToken: json['accessToken'] as String?, - tokenType: json['tokenType'] as String?, - expiresIn: (json['expiresIn'] as num?)?.toInt(), - username: json['username'] as String?, - roles: (json['roles'] as List?) - ?.map((role) => role.toString()) - .toList() ?? - const [], - message: json['message'] as String?, - ); - } - - final bool success; - final String? accessToken; - final String? tokenType; - final int? expiresIn; - final String? username; - final List roles; - final String? message; - - LoginResponse copyWith({ - bool? success, - String? accessToken, - String? tokenType, - int? expiresIn, - String? username, - List? roles, - String? message, - }) { - return LoginResponse( - success: success ?? this.success, - accessToken: accessToken ?? this.accessToken, - tokenType: tokenType ?? this.tokenType, - expiresIn: expiresIn ?? this.expiresIn, - username: username ?? this.username, - roles: roles ?? this.roles, - message: message ?? this.message, - ); - } -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/models/pass_code.dart b/flutter-app/qrscanner/lib/models/pass_code.dart deleted file mode 100644 index 3a1f87b..0000000 --- a/flutter-app/qrscanner/lib/models/pass_code.dart +++ /dev/null @@ -1,31 +0,0 @@ -class PassCode { - const PassCode({ - required this.id, - required this.name, - required this.code, - required this.status, - required this.scanned, - required this.createdAt, - this.scannedAt, - }); - - factory PassCode.fromJson(Map json) { - return PassCode( - id: (json['id'] as num).toInt(), - name: json['name'] as String? ?? '', - code: json['code'] as String? ?? '', - status: json['status'] as String? ?? '', - scanned: json['scanned'] == true, - createdAt: json['createdAt'] as String? ?? '', - scannedAt: json['scannedAt'] as String?, - ); - } - - final int id; - final String name; - final String code; - final String status; - final bool scanned; - final String createdAt; - final String? scannedAt; -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/models/scan_response.dart b/flutter-app/qrscanner/lib/models/scan_response.dart deleted file mode 100644 index 392e34b..0000000 --- a/flutter-app/qrscanner/lib/models/scan_response.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:qrscanner/models/pass_code.dart'; - -class ScanResponse { - const ScanResponse({ - required this.success, - this.message, - this.passCode, - }); - - factory ScanResponse.fromJson(Map json) { - return ScanResponse( - success: json['success'] == true, - message: json['message'] as String?, - passCode: json['passCode'] is Map - ? PassCode.fromJson(json['passCode'] as Map) - : null, - ); - } - - final bool success; - final String? message; - final PassCode? passCode; -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/screens/home_screen.dart b/flutter-app/qrscanner/lib/screens/home_screen.dart deleted file mode 100644 index 6555771..0000000 --- a/flutter-app/qrscanner/lib/screens/home_screen.dart +++ /dev/null @@ -1,311 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:qrscanner/models/auth_session.dart'; -import 'package:qrscanner/screens/login_screen.dart'; -import 'package:qrscanner/screens/scanner_screen.dart'; -import 'package:qrscanner/services/auth_service.dart'; -import 'package:qrscanner/theme/app_theme.dart'; - -class HomeScreen extends StatelessWidget { - const HomeScreen({ - super.key, - required this.session, - }); - - final AuthSession session; - - Future _logout(BuildContext context) async { - await AuthService().clearSession(); - - if (!context.mounted) return; - - Navigator.of(context).pushReplacement( - PageRouteBuilder( - pageBuilder: (context, animation, secondaryAnimation) => - const LoginScreen(), - transitionsBuilder: (context, animation, secondaryAnimation, child) { - return FadeTransition(opacity: animation, child: child); - }, - transitionDuration: const Duration(milliseconds: 400), - ), - ); - } - - void _openScanner(BuildContext context) { - Navigator.of(context).push( - PageRouteBuilder( - pageBuilder: (context, animation, secondaryAnimation) => - ScannerScreen(session: session), - transitionsBuilder: (context, animation, secondaryAnimation, child) { - return FadeTransition( - opacity: animation, - child: SlideTransition( - position: Tween( - begin: const Offset(0, 0.06), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: animation, - curve: Curves.easeOutCubic, - )), - child: child, - ), - ); - }, - transitionDuration: const Duration(milliseconds: 380), - ), - ); - } - - @override - Widget build(BuildContext context) { - final expiresHours = (session.expiresIn / 3600).round(); - - return Scaffold( - body: Stack( - children: [ - Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - AppTheme.backgroundDark, - Color(0xFF0D1B2A), - Color(0xFF12182B), - ], - ), - ), - ), - Positioned( - top: -60, - right: -40, - child: Container( - width: 220, - height: 220, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: RadialGradient( - colors: [ - AppTheme.primary.withValues(alpha: 0.12), - Colors.transparent, - ], - ), - ), - ), - ), - SafeArea( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), - child: Row( - children: [ - const Spacer(), - IconButton( - icon: const Icon(Icons.logout_rounded), - tooltip: 'Cerrar sesión', - onPressed: () => _logout(context), - ), - ], - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 110, - height: 110, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(28), - gradient: const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [AppTheme.primary, AppTheme.primaryDark], - ), - boxShadow: [ - BoxShadow( - color: AppTheme.primary.withValues(alpha: 0.35), - blurRadius: 28, - offset: const Offset(0, 10), - ), - ], - ), - child: const Icon( - Icons.qr_code_scanner_rounded, - size: 52, - color: Colors.white, - ), - ), - const SizedBox(height: 28), - Text( - 'Hola, ${session.username}', - style: - Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - Text( - 'Listo para validar pases', - style: - Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Colors.white.withValues(alpha: 0.55), - ), - ), - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 14, - vertical: 8, - ), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.06), - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: Colors.white.withValues(alpha: 0.08), - ), - ), - child: Text( - 'Sesión activa · $expiresHours h', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.45), - fontSize: 13, - ), - ), - ), - const SizedBox(height: 48), - _ScanButton(onPressed: () => _openScanner(context)), - ], - ), - ), - ), - ], - ), - ), - ], - ), - ); - } -} - -class _ScanButton extends StatefulWidget { - const _ScanButton({required this.onPressed}); - - final VoidCallback onPressed; - - @override - State<_ScanButton> createState() => _ScanButtonState(); -} - -class _ScanButtonState extends State<_ScanButton> - with SingleTickerProviderStateMixin { - late final AnimationController _controller; - late final Animation _glow; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 1800), - )..repeat(reverse: true); - _glow = Tween(begin: 0.18, end: 0.38).animate( - CurvedAnimation(parent: _controller, curve: Curves.easeInOut), - ); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _glow, - builder: (context, child) { - return Container( - width: double.infinity, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: AppTheme.primary.withValues(alpha: _glow.value), - blurRadius: 28, - offset: const Offset(0, 10), - ), - ], - ), - child: child, - ); - }, - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: widget.onPressed, - borderRadius: BorderRadius.circular(20), - child: Ink( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - gradient: const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [AppTheme.primary, AppTheme.primaryDark], - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.18), - borderRadius: BorderRadius.circular(12), - ), - child: const Icon( - Icons.qr_code_scanner_rounded, - color: Colors.white, - size: 26, - ), - ), - const SizedBox(width: 16), - const Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Abrir escáner', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.w700, - ), - ), - Text( - 'Validar código de pase', - style: TextStyle( - color: Color(0xCCFFFFFF), - fontSize: 13, - ), - ), - ], - ), - const Spacer(), - const Icon( - Icons.arrow_forward_rounded, - color: Colors.white, - ), - ], - ), - ), - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/screens/login_screen.dart b/flutter-app/qrscanner/lib/screens/login_screen.dart deleted file mode 100644 index 57353b1..0000000 --- a/flutter-app/qrscanner/lib/screens/login_screen.dart +++ /dev/null @@ -1,440 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:qrscanner/config/api_config.dart'; -import 'package:qrscanner/models/auth_session.dart'; -import 'package:qrscanner/screens/home_screen.dart'; -import 'package:qrscanner/services/auth_service.dart'; -import 'package:qrscanner/theme/app_theme.dart'; - -class LoginScreen extends StatefulWidget { - const LoginScreen({super.key}); - - @override - State createState() => _LoginScreenState(); -} - -class _LoginScreenState extends State - with SingleTickerProviderStateMixin { - final _formKey = GlobalKey(); - final _usernameController = TextEditingController(); - final _passwordController = TextEditingController(); - final _authService = AuthService(); - - late final AnimationController _animationController; - late final Animation _fadeAnimation; - late final Animation _slideAnimation; - - bool _obscurePassword = true; - bool _isLoading = false; - bool _rememberMe = false; - String? _errorMessage; - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 900), - ); - _fadeAnimation = CurvedAnimation( - parent: _animationController, - curve: Curves.easeOut, - ); - _slideAnimation = Tween( - begin: const Offset(0, 0.08), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: _animationController, - curve: Curves.easeOutCubic, - )); - _animationController.forward(); - _checkStoredSession(); - } - - Future _checkStoredSession() async { - final session = await _authService.loadStoredSession(); - if (!mounted || session == null) return; - - _goToHome(session); - } - - @override - void dispose() { - _animationController.dispose(); - _usernameController.dispose(); - _passwordController.dispose(); - super.dispose(); - } - - Future _handleLogin() async { - if (!_formKey.currentState!.validate()) return; - - setState(() { - _isLoading = true; - _errorMessage = null; - }); - - try { - final response = await _authService.login( - username: _usernameController.text.trim(), - password: _passwordController.text, - rememberMe: _rememberMe, - ); - - if (!mounted) return; - - if (response.success && response.accessToken != null) { - _goToHome( - AuthSession.fromLoginResponse( - response.copyWith( - username: response.username ?? _usernameController.text.trim(), - ), - ), - ); - return; - } - - setState(() { - _errorMessage = response.message ?? 'Credenciales incorrectas'; - }); - } catch (_) { - if (!mounted) return; - setState(() { - _errorMessage = - 'No se pudo conectar al servidor. Revisa API_BASE_URL (${ApiConfig.baseUrl})'; - }); - } finally { - if (mounted) { - setState(() => _isLoading = false); - } - } - } - - void _goToHome(AuthSession session) { - Navigator.of(context).pushReplacement( - PageRouteBuilder( - pageBuilder: (context, animation, secondaryAnimation) => - HomeScreen(session: session), - transitionsBuilder: (context, animation, secondaryAnimation, child) { - return FadeTransition(opacity: animation, child: child); - }, - transitionDuration: const Duration(milliseconds: 400), - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Stack( - children: [ - _buildBackground(), - SafeArea( - child: FadeTransition( - opacity: _fadeAnimation, - child: SlideTransition( - position: _slideAnimation, - child: Center( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 28), - child: Column( - children: [ - _buildHeader(), - const SizedBox(height: 40), - _buildLoginCard(), - const SizedBox(height: 24), - _buildFooter(), - ], - ), - ), - ), - ), - ), - ), - ], - ), - ); - } - - Widget _buildBackground() { - return Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - AppTheme.backgroundDark, - Color(0xFF0D1B2A), - Color(0xFF1A1A3E), - ], - ), - ), - child: Stack( - children: [ - Positioned( - top: -80, - right: -60, - child: Container( - width: 260, - height: 260, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: RadialGradient( - colors: [ - AppTheme.primary.withValues(alpha: 0.15), - Colors.transparent, - ], - ), - ), - ), - ), - Positioned( - bottom: -100, - left: -80, - child: Container( - width: 300, - height: 300, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: RadialGradient( - colors: [ - AppTheme.accent.withValues(alpha: 0.12), - Colors.transparent, - ], - ), - ), - ), - ), - ], - ), - ); - } - - Widget _buildHeader() { - return Column( - children: [ - Container( - width: 88, - height: 88, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(22), - gradient: const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [AppTheme.primary, AppTheme.primaryDark], - ), - boxShadow: [ - BoxShadow( - color: AppTheme.primary.withValues(alpha: 0.35), - blurRadius: 24, - offset: const Offset(0, 8), - ), - ], - ), - child: const Icon( - Icons.qr_code_scanner_rounded, - size: 44, - color: Colors.white, - ), - ), - const SizedBox(height: 20), - Text( - 'QR Scanner', - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.bold, - letterSpacing: -0.5, - ), - ), - const SizedBox(height: 8), - Text( - 'Inicia sesión para continuar', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.white.withValues(alpha: 0.55), - ), - ), - ], - ); - } - - Widget _buildLoginCard() { - return Container( - width: double.infinity, - padding: const EdgeInsets.all(28), - decoration: BoxDecoration( - color: AppTheme.surfaceDark.withValues(alpha: 0.7), - borderRadius: BorderRadius.circular(24), - border: Border.all(color: Colors.white.withValues(alpha: 0.08)), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.3), - blurRadius: 32, - offset: const Offset(0, 16), - ), - ], - ), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (_errorMessage != null) ...[ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.red.withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.red.withValues(alpha: 0.3)), - ), - child: Text( - _errorMessage!, - style: const TextStyle(color: Color(0xFFFF8A80), fontSize: 13), - ), - ), - const SizedBox(height: 16), - ], - TextFormField( - controller: _usernameController, - keyboardType: TextInputType.text, - textInputAction: TextInputAction.next, - autocorrect: false, - style: const TextStyle(color: Colors.white), - decoration: InputDecoration( - hintText: 'Usuario', - prefixIcon: Icon( - Icons.person_outline_rounded, - color: Colors.white.withValues(alpha: 0.5), - ), - ), - validator: (value) { - if (value == null || value.trim().isEmpty) { - return 'Ingresa tu usuario'; - } - return null; - }, - ), - const SizedBox(height: 16), - TextFormField( - controller: _passwordController, - obscureText: _obscurePassword, - textInputAction: TextInputAction.done, - onFieldSubmitted: (_) => _handleLogin(), - style: const TextStyle(color: Colors.white), - decoration: InputDecoration( - hintText: 'Contraseña', - prefixIcon: Icon( - Icons.lock_outline_rounded, - color: Colors.white.withValues(alpha: 0.5), - ), - suffixIcon: IconButton( - icon: Icon( - _obscurePassword - ? Icons.visibility_off_outlined - : Icons.visibility_outlined, - color: Colors.white.withValues(alpha: 0.5), - ), - onPressed: () { - setState(() => _obscurePassword = !_obscurePassword); - }, - ), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Ingresa tu contraseña'; - } - return null; - }, - ), - const SizedBox(height: 12), - Row( - children: [ - SizedBox( - height: 36, - child: Checkbox( - value: _rememberMe, - activeColor: AppTheme.primary, - side: BorderSide( - color: Colors.white.withValues(alpha: 0.3), - ), - onChanged: (value) { - setState(() => _rememberMe = value ?? false); - }, - ), - ), - Text( - 'Recordarme', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.7), - fontSize: 14, - ), - ), - ], - ), - const SizedBox(height: 8), - _buildLoginButton(), - const SizedBox(height: 16), - Text( - 'API: ${ApiConfig.baseUrl}', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white.withValues(alpha: 0.35), - fontSize: 11, - ), - ), - ], - ), - ), - ); - } - - Widget _buildLoginButton() { - return DecoratedBox( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - gradient: const LinearGradient( - colors: [AppTheme.primary, AppTheme.primaryDark], - ), - boxShadow: [ - BoxShadow( - color: AppTheme.primary.withValues(alpha: 0.3), - blurRadius: 16, - offset: const Offset(0, 6), - ), - ], - ), - child: ElevatedButton( - onPressed: _isLoading ? null : _handleLogin, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - disabledBackgroundColor: Colors.transparent, - ), - child: _isLoading - ? const SizedBox( - height: 22, - width: 22, - child: CircularProgressIndicator( - strokeWidth: 2.5, - color: Colors.white, - ), - ) - : const Text( - 'Iniciar sesión', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ); - } - - Widget _buildFooter() { - return Text( - 'POST /api/auth/login', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.35), - fontSize: 12, - ), - ); - } -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/screens/scanner_screen.dart b/flutter-app/qrscanner/lib/screens/scanner_screen.dart deleted file mode 100644 index e7aa8db..0000000 --- a/flutter-app/qrscanner/lib/screens/scanner_screen.dart +++ /dev/null @@ -1,590 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; -import 'package:qrscanner/models/auth_session.dart'; -import 'package:qrscanner/models/pass_code.dart'; -import 'package:qrscanner/models/scan_response.dart'; -import 'package:qrscanner/services/feedback_service.dart'; -import 'package:qrscanner/services/pass_code_service.dart'; -import 'package:qrscanner/theme/app_theme.dart'; - -enum _ScanPhase { scanning, processing, success, warning } - -const double _scanWindowSize = 260; -const double _scanWindowRadius = 28; -const Alignment _scanWindowAlignment = Alignment(0, -0.12); - -class ScannerScreen extends StatefulWidget { - const ScannerScreen({ - super.key, - required this.session, - }); - - final AuthSession session; - - @override - State createState() => _ScannerScreenState(); -} - -class _ScannerScreenState extends State - with TickerProviderStateMixin { - final _scannerController = MobileScannerController( - detectionSpeed: DetectionSpeed.normal, - detectionTimeoutMs: 500, - facing: CameraFacing.back, - formats: const [BarcodeFormat.qrCode], - autoZoom: true, - ); - final _passCodeService = PassCodeService(); - final _feedbackService = FeedbackService(); - - _ScanPhase _phase = _ScanPhase.scanning; - bool _isHandlingDetection = false; - String? _lastScannedCode; - String? _message; - PassCode? _passCode; - - late final AnimationController _scanLineController; - late final AnimationController _resultController; - late final Animation _scanLineAnimation; - late final Animation _scaleAnimation; - late final Animation _arrowSlide; - - @override - void initState() { - super.initState(); - _scanLineController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 2400), - )..repeat(); - _scanLineAnimation = CurvedAnimation( - parent: _scanLineController, - curve: Curves.easeInOut, - ); - - _resultController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 700), - ); - _scaleAnimation = CurvedAnimation( - parent: _resultController, - curve: Curves.elasticOut, - ); - _arrowSlide = Tween( - begin: const Offset(0, -0.35), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: _resultController, - curve: Curves.easeOutBack, - )); - } - - Rect _scanWindowRect(Size size) { - final center = _scanWindowAlignment.alongSize(size); - return Rect.fromCenter( - center: center, - width: _scanWindowSize, - height: _scanWindowSize, - ); - } - - @override - void dispose() { - _scanLineController.dispose(); - _resultController.dispose(); - _scannerController.dispose(); - _feedbackService.dispose(); - super.dispose(); - } - - Future _onDetect(BarcodeCapture capture) async { - if (_phase != _ScanPhase.scanning || _isHandlingDetection) return; - - final rawValue = capture.barcodes.firstOrNull?.rawValue?.trim(); - if (rawValue == null || rawValue.isEmpty) return; - - final code = _extractCode(rawValue); - if (code.isEmpty || code == _lastScannedCode) return; - - _isHandlingDetection = true; - setState(() { - _phase = _ScanPhase.processing; - _lastScannedCode = code; - _message = null; - _passCode = null; - }); - - _scanLineController.stop(); - await _scannerController.stop(); - await _feedbackService.onCodeDetected(); - - try { - final response = await _passCodeService.scan( - session: widget.session, - code: code, - ); - - if (!mounted) return; - await _showResult(response); - } catch (_) { - if (!mounted) return; - await _showResult( - const ScanResponse( - success: false, - message: 'No se pudo validar el código. Revisa la conexión.', - ), - ); - } finally { - _isHandlingDetection = false; - } - } - - String _extractCode(String rawValue) { - final uri = Uri.tryParse(rawValue); - if (uri != null) { - final queryCode = uri.queryParameters['code']; - if (queryCode != null && queryCode.isNotEmpty) return queryCode; - - final segments = uri.pathSegments.where((s) => s.isNotEmpty).toList(); - if (segments.isNotEmpty) return segments.last; - } - return rawValue; - } - - Future _showResult(ScanResponse response) async { - _resultController.reset(); - - setState(() { - _message = response.message; - _passCode = response.passCode; - _phase = response.success ? _ScanPhase.success : _ScanPhase.warning; - }); - - if (response.success) { - await _feedbackService.onSuccess(); - } else { - await _feedbackService.onWarning(); - } - - await _resultController.forward(); - } - - Future _scanAgain() async { - _resultController.reset(); - _isHandlingDetection = false; - setState(() { - _phase = _ScanPhase.scanning; - _lastScannedCode = null; - _message = null; - _passCode = null; - }); - _scanLineController.repeat(); - await _scannerController.start(); - } - - @override - Widget build(BuildContext context) { - final isSuccess = _phase == _ScanPhase.success; - final isWarning = _phase == _ScanPhase.warning; - final overlayColor = isSuccess - ? AppTheme.success.withValues(alpha: 0.82) - : isWarning - ? AppTheme.warning.withValues(alpha: 0.82) - : Colors.transparent; - - return Scaffold( - backgroundColor: Colors.black, - extendBodyBehindAppBar: true, - appBar: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back_rounded), - onPressed: () => Navigator.of(context).pop(), - ), - title: const Text('Escanear pase'), - centerTitle: true, - ), - body: Stack( - fit: StackFit.expand, - children: [ - if (_phase == _ScanPhase.scanning || _phase == _ScanPhase.processing) - LayoutBuilder( - builder: (context, constraints) { - final scanRect = _scanWindowRect(constraints.biggest); - return MobileScanner( - controller: _scannerController, - onDetect: _onDetect, - scanWindow: scanRect, - overlayBuilder: (context, constraints) { - if (_phase != _ScanPhase.scanning) { - return const SizedBox.shrink(); - } - - return AnimatedBuilder( - animation: _scanLineAnimation, - builder: (context, child) { - return CustomPaint( - size: constraints.biggest, - painter: _ScannerOverlayPainter( - scanRect: scanRect, - scanLineProgress: _scanLineAnimation.value, - ), - ); - }, - ); - }, - ); - }, - ), - if (_phase != _ScanPhase.scanning && _phase != _ScanPhase.processing) - Container(color: AppTheme.backgroundDark), - AnimatedContainer( - duration: const Duration(milliseconds: 450), - curve: Curves.easeOutCubic, - color: overlayColor, - ), - if (_phase == _ScanPhase.processing) - SafeArea( - child: Center(child: _buildProcessingCard()), - ), - if (_phase == _ScanPhase.success || _phase == _ScanPhase.warning) - SafeArea( - child: Center( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 16), - child: _buildResultCard(), - ), - ), - ), - if (_phase == _ScanPhase.scanning) - SafeArea( - child: Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: const EdgeInsets.fromLTRB(32, 0, 32, 28), - child: _buildHint(), - ), - ), - ), - ], - ), - ); - } - - Widget _buildProcessingCard() { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 28), - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28), - decoration: BoxDecoration( - color: AppTheme.surfaceDark.withValues(alpha: 0.92), - borderRadius: BorderRadius.circular(24), - border: Border.all(color: Colors.white.withValues(alpha: 0.08)), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox( - width: 42, - height: 42, - child: CircularProgressIndicator( - strokeWidth: 3, - color: AppTheme.primary, - ), - ), - const SizedBox(height: 18), - Text( - 'Validando código...', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - if (_lastScannedCode != null) ...[ - const SizedBox(height: 8), - Text( - _lastScannedCode!, - style: TextStyle( - color: Colors.white.withValues(alpha: 0.45), - letterSpacing: 1.2, - ), - ), - ], - ], - ), - ); - } - - Widget _buildResultCard() { - final isSuccess = _phase == _ScanPhase.success; - final accent = isSuccess ? AppTheme.success : AppTheme.warning; - final accentDark = isSuccess ? AppTheme.successDark : AppTheme.warningDark; - - return FadeTransition( - opacity: _scaleAnimation, - child: ScaleTransition( - scale: _scaleAnimation, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 24), - padding: const EdgeInsets.fromLTRB(24, 32, 24, 24), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - accent.withValues(alpha: 0.95), - accentDark.withValues(alpha: 0.95), - ], - ), - borderRadius: BorderRadius.circular(28), - boxShadow: [ - BoxShadow( - color: accent.withValues(alpha: 0.45), - blurRadius: 32, - offset: const Offset(0, 12), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SlideTransition( - position: _arrowSlide, - child: Container( - width: 88, - height: 88, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.18), - shape: BoxShape.circle, - ), - child: Icon( - isSuccess - ? Icons.arrow_downward_rounded - : Icons.warning_amber_rounded, - size: isSuccess ? 52 : 46, - color: Colors.white, - ), - ), - ), - const SizedBox(height: 20), - Text( - isSuccess ? '¡Pase válido!' : 'No se pudo validar', - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - if (!isSuccess && (_message?.isNotEmpty ?? false)) ...[ - const SizedBox(height: 10), - Text( - _message!, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - height: 1.4, - color: Colors.white.withValues(alpha: 0.92), - ), - ), - ], - if (isSuccess && _passCode != null) ...[ - const SizedBox(height: 20), - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.18), - borderRadius: BorderRadius.circular(16), - ), - child: Column( - children: [ - _detailRow('Nombre', _passCode!.name), - const SizedBox(height: 8), - _detailRow('Código', _passCode!.code), - ], - ), - ), - ], - const SizedBox(height: 24), - SizedBox( - width: double.infinity, - child: FilledButton.icon( - onPressed: _scanAgain, - icon: const Icon(Icons.qr_code_scanner_rounded), - label: const Text('Escanear otro'), - style: FilledButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: accentDark, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - ), - ), - ], - ), - ), - ), - ); - } - - Widget _detailRow(String label, String value) { - return Row( - children: [ - Text( - '$label: ', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.7), - fontSize: 14, - ), - ), - Expanded( - child: Text( - value, - textAlign: TextAlign.end, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w600, - fontSize: 14, - ), - ), - ), - ], - ); - } - - Widget _buildHint() { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Text( - 'Apunta la cámara al código QR del pase', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white.withValues(alpha: 0.65), - fontSize: 14, - ), - ), - ); - } -} - -class _ScannerOverlayPainter extends CustomPainter { - _ScannerOverlayPainter({ - required this.scanRect, - required this.scanLineProgress, - }); - - final Rect scanRect; - final double scanLineProgress; - - static const double _cornerLength = 36; - static const double _cornerStroke = 4; - - @override - void paint(Canvas canvas, Size size) { - final cutout = RRect.fromRectAndRadius( - scanRect, - const Radius.circular(_scanWindowRadius), - ); - - final background = Path()..addRect(Offset.zero & size); - final hole = Path()..addRRect(cutout); - final overlay = Path.combine(PathOperation.difference, background, hole); - - canvas.drawPath( - overlay, - Paint()..color = Colors.black.withValues(alpha: 0.55), - ); - - final borderPaint = Paint() - ..color = AppTheme.primary.withValues(alpha: 0.35) - ..style = PaintingStyle.stroke - ..strokeWidth = 1.5; - canvas.drawRRect(cutout, borderPaint); - - final cornerPaint = Paint() - ..color = AppTheme.primary - ..style = PaintingStyle.stroke - ..strokeWidth = _cornerStroke - ..strokeCap = StrokeCap.round; - - _drawCorners(canvas, scanRect, cornerPaint); - - final lineY = scanRect.top + - _scanWindowRadius + - (scanRect.height - _scanWindowRadius * 2) * scanLineProgress; - final linePaint = Paint() - ..shader = LinearGradient( - colors: [ - AppTheme.primary.withValues(alpha: 0), - AppTheme.primary.withValues(alpha: 0.85), - AppTheme.primary.withValues(alpha: 0), - ], - stops: const [0, 0.5, 1], - ).createShader( - Rect.fromLTWH(scanRect.left + 16, lineY - 1, scanRect.width - 32, 2), - ) - ..strokeWidth = 2 - ..strokeCap = StrokeCap.round; - - canvas.drawLine( - Offset(scanRect.left + 16, lineY), - Offset(scanRect.right - 16, lineY), - linePaint, - ); - } - - void _drawCorners(Canvas canvas, Rect rect, Paint paint) { - const r = _scanWindowRadius; - const len = _cornerLength; - - canvas.drawLine( - Offset(rect.left + r, rect.top), - Offset(rect.left + r + len, rect.top), - paint, - ); - canvas.drawLine( - Offset(rect.left, rect.top + r), - Offset(rect.left, rect.top + r + len), - paint, - ); - - canvas.drawLine( - Offset(rect.right - r - len, rect.top), - Offset(rect.right - r, rect.top), - paint, - ); - canvas.drawLine( - Offset(rect.right, rect.top + r), - Offset(rect.right, rect.top + r + len), - paint, - ); - - canvas.drawLine( - Offset(rect.left + r, rect.bottom), - Offset(rect.left + r + len, rect.bottom), - paint, - ); - canvas.drawLine( - Offset(rect.left, rect.bottom - r - len), - Offset(rect.left, rect.bottom - r), - paint, - ); - - canvas.drawLine( - Offset(rect.right - r - len, rect.bottom), - Offset(rect.right - r, rect.bottom), - paint, - ); - canvas.drawLine( - Offset(rect.right, rect.bottom - r - len), - Offset(rect.right, rect.bottom - r), - paint, - ); - } - - @override - bool shouldRepaint(covariant _ScannerOverlayPainter oldDelegate) { - return oldDelegate.scanRect != scanRect || - oldDelegate.scanLineProgress != scanLineProgress; - } -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/services/auth_service.dart b/flutter-app/qrscanner/lib/services/auth_service.dart deleted file mode 100644 index c484f6b..0000000 --- a/flutter-app/qrscanner/lib/services/auth_service.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:convert'; - -import 'package:http/http.dart' as http; -import 'package:qrscanner/config/api_config.dart'; -import 'package:qrscanner/models/auth_session.dart'; -import 'package:qrscanner/models/login_response.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class AuthService { - static const _sessionKey = 'auth_session'; - - AuthSession? _currentSession; - - AuthSession? get currentSession => _currentSession; - - Future login({ - required String username, - required String password, - bool rememberMe = false, - }) async { - final response = await http.post( - Uri.parse(ApiConfig.loginUrl), - headers: const {'Content-Type': 'application/json'}, - body: jsonEncode({ - 'username': username, - 'password': password, - }), - ); - - final Map body = - jsonDecode(response.body) as Map; - final loginResponse = LoginResponse.fromJson(body); - - if (response.statusCode == 200 && - loginResponse.success && - loginResponse.accessToken != null) { - final session = AuthSession.fromLoginResponse( - loginResponse.copyWith(username: loginResponse.username ?? username), - ); - _currentSession = session; - - if (rememberMe) { - await _saveSession(session); - } else { - await clearSession(); - } - - return loginResponse; - } - - return LoginResponse( - success: false, - message: loginResponse.message ?? - 'No se pudo iniciar sesión (${response.statusCode})', - ); - } - - Future _saveSession(AuthSession session) async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_sessionKey, jsonEncode(session.toJson())); - } - - Future clearSession() async { - _currentSession = null; - final prefs = await SharedPreferences.getInstance(); - await prefs.remove(_sessionKey); - } - - Future loadStoredSession() async { - final prefs = await SharedPreferences.getInstance(); - final raw = prefs.getString(_sessionKey); - if (raw == null || raw.isEmpty) return null; - - final session = AuthSession.fromJson( - jsonDecode(raw) as Map, - ); - _currentSession = session; - return session; - } - - Future hasStoredSession() async { - final session = await loadStoredSession(); - return session != null && session.accessToken.isNotEmpty; - } -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/services/feedback_service.dart b/flutter-app/qrscanner/lib/services/feedback_service.dart deleted file mode 100644 index e53aa55..0000000 --- a/flutter-app/qrscanner/lib/services/feedback_service.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:audioplayers/audioplayers.dart'; -import 'package:flutter/services.dart'; -import 'package:vibration/vibration.dart'; - -class FeedbackService { - FeedbackService() : _player = AudioPlayer() { - _player.setReleaseMode(ReleaseMode.stop); - } - - final AudioPlayer _player; - - Future onCodeDetected() async { - await Future.wait([ - _play('sounds/scan.wav'), - _vibratePattern(const [0, 40]), - ]); - } - - Future onSuccess() async { - await Future.wait([ - _play('sounds/success.wav'), - _vibratePattern(const [0, 90, 70, 140]), - ]); - await HapticFeedback.heavyImpact(); - } - - Future onWarning() async { - await Future.wait([ - _play('sounds/warning.wav'), - _vibratePattern(const [0, 120, 80, 120, 80, 120]), - ]); - await HapticFeedback.mediumImpact(); - } - - Future _play(String asset) async { - try { - await _player.stop(); - await _player.play(AssetSource(asset)); - } catch (_) { - // Ignore audio errors on devices without speaker routing. - } - } - - Future _vibratePattern(List pattern) async { - try { - final hasVibrator = await Vibration.hasVibrator(); - if (hasVibrator != true) return; - - final hasAmplitude = await Vibration.hasAmplitudeControl(); - if (hasAmplitude == true) { - final intensities = pattern - .asMap() - .entries - .map((entry) => entry.key.isOdd ? 180 : 0) - .toList(); - await Vibration.vibrate( - pattern: pattern, - intensities: intensities, - ); - return; - } - - await Vibration.vibrate(pattern: pattern); - } catch (_) { - await HapticFeedback.selectionClick(); - } - } - - Future dispose() async { - await _player.dispose(); - } -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/services/pass_code_service.dart b/flutter-app/qrscanner/lib/services/pass_code_service.dart deleted file mode 100644 index 686b83b..0000000 --- a/flutter-app/qrscanner/lib/services/pass_code_service.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'dart:convert'; - -import 'package:http/http.dart' as http; -import 'package:qrscanner/config/api_config.dart'; -import 'package:qrscanner/models/auth_session.dart'; -import 'package:qrscanner/models/scan_response.dart'; - -class PassCodeService { - Future scan({ - required AuthSession session, - required String code, - }) async { - final response = await http.post( - Uri.parse(ApiConfig.scanUrl), - headers: { - 'Content-Type': 'application/json', - 'Authorization': session.authorizationHeader, - }, - body: jsonEncode({'code': code}), - ); - - final Map body = - jsonDecode(response.body) as Map; - - return ScanResponse.fromJson(body); - } -} \ No newline at end of file diff --git a/flutter-app/qrscanner/lib/theme/app_theme.dart b/flutter-app/qrscanner/lib/theme/app_theme.dart deleted file mode 100644 index 410974c..0000000 --- a/flutter-app/qrscanner/lib/theme/app_theme.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:flutter/material.dart'; - -class AppTheme { - static const Color primary = Color(0xFF00D4AA); - static const Color primaryDark = Color(0xFF00A88A); - static const Color backgroundDark = Color(0xFF0A0E1A); - static const Color surfaceDark = Color(0xFF141B2D); - static const Color accent = Color(0xFF6C63FF); - static const Color success = Color(0xFF22C55E); - static const Color successDark = Color(0xFF16A34A); - static const Color warning = Color(0xFFFBBF24); - static const Color warningDark = Color(0xFFF59E0B); - - static ThemeData get darkTheme { - return ThemeData( - useMaterial3: true, - brightness: Brightness.dark, - colorScheme: ColorScheme.dark( - primary: primary, - secondary: accent, - surface: surfaceDark, - onPrimary: Colors.white, - onSurface: Colors.white, - ), - scaffoldBackgroundColor: backgroundDark, - inputDecorationTheme: InputDecorationTheme( - filled: true, - fillColor: Colors.white.withValues(alpha: 0.06), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(14), - borderSide: BorderSide.none, - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(14), - borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.08)), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(14), - borderSide: const BorderSide(color: primary, width: 1.5), - ), - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(14), - borderSide: const BorderSide(color: Color(0xFFFF6B6B)), - ), - contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), - hintStyle: TextStyle(color: Colors.white.withValues(alpha: 0.4)), - ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - elevation: 0, - padding: const EdgeInsets.symmetric(vertical: 18), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/flutter-app/qrscanner/pubspec.lock b/flutter-app/qrscanner/pubspec.lock deleted file mode 100644 index b231bc5..0000000 --- a/flutter-app/qrscanner/pubspec.lock +++ /dev/null @@ -1,634 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" - source: hosted - version: "2.13.1" - audioplayers: - dependency: "direct main" - description: - name: audioplayers - sha256: f16640453cc47487b7de72a2b28d37c7df1ac97999849f4a46d92b1d2b0f093d - url: "https://pub.dev" - source: hosted - version: "6.7.1" - audioplayers_android: - dependency: transitive - description: - name: audioplayers_android - sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" - url: "https://pub.dev" - source: hosted - version: "5.2.1" - audioplayers_darwin: - dependency: transitive - description: - name: audioplayers_darwin - sha256: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91 - url: "https://pub.dev" - source: hosted - version: "6.4.0" - audioplayers_linux: - dependency: transitive - description: - name: audioplayers_linux - sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 - url: "https://pub.dev" - source: hosted - version: "4.2.1" - audioplayers_platform_interface: - dependency: transitive - description: - name: audioplayers_platform_interface - sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" - url: "https://pub.dev" - source: hosted - version: "7.1.1" - audioplayers_web: - dependency: transitive - description: - name: audioplayers_web - sha256: "24a6f258062bd7da8cb2157e83fccb9816a08dd306cbaaa24f9813d071470545" - url: "https://pub.dev" - source: hosted - version: "5.2.1" - audioplayers_windows: - dependency: transitive - description: - name: audioplayers_windows - sha256: "95f875a96c88c3dbbcb608d4f8288e300b0113d256a81d0b3197fcc18f0dc91a" - url: "https://pub.dev" - source: hosted - version: "4.3.1" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - characters: - dependency: transitive - description: - name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" - source: hosted - version: "1.4.1" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - code_assets: - dependency: transitive - description: - name: code_assets - sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - crypto: - dependency: transitive - description: - name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" - source: hosted - version: "3.0.7" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" - url: "https://pub.dev" - source: hosted - version: "1.0.9" - device_info_plus: - dependency: transitive - description: - name: device_info_plus - sha256: "6a642e1daa10190af89ba6cb6386c0df7d071a3592080bfe1e44faa63ae1df65" - url: "https://pub.dev" - source: hosted - version: "13.1.0" - device_info_plus_platform_interface: - dependency: transitive - description: - name: device_info_plus_platform_interface - sha256: "04b173a92e2d9161dfead145667037c8d834db725ce2e7b942bfe18fd2f45a46" - url: "https://pub.dev" - source: hosted - version: "8.1.0" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - ffi_leak_tracker: - dependency: transitive - description: - name: ffi_leak_tracker - sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97" - url: "https://pub.dev" - source: hosted - version: "0.1.2" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - hooks: - dependency: transitive - description: - name: hooks - sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - http: - dependency: "direct main" - description: - name: http - sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" - source: hosted - version: "1.6.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - jni: - dependency: transitive - description: - name: jni - sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f - url: "https://pub.dev" - source: hosted - version: "1.0.0" - jni_flutter: - dependency: transitive - description: - name: jni_flutter - sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" - source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" - source: hosted - version: "3.0.10" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - lints: - dependency: transitive - description: - name: lints - sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" - url: "https://pub.dev" - source: hosted - version: "6.1.0" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" - source: hosted - version: "0.12.19" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" - source: hosted - version: "0.13.0" - meta: - dependency: transitive - description: - name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" - url: "https://pub.dev" - source: hosted - version: "1.18.0" - mobile_scanner: - dependency: "direct main" - description: - name: mobile_scanner - sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce - url: "https://pub.dev" - source: hosted - version: "7.2.0" - objective_c: - dependency: transitive - description: - name: objective_c - sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" - url: "https://pub.dev" - source: hosted - version: "9.4.1" - package_config: - dependency: transitive - description: - name: package_config - sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" - source: hosted - version: "2.2.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - path_provider: - dependency: transitive - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.dev" - source: hosted - version: "2.6.0" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - record_use: - dependency: transitive - description: - name: record_use - sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" - url: "https://pub.dev" - source: hosted - version: "0.6.0" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf - url: "https://pub.dev" - source: hosted - version: "2.5.5" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: a2c49fc1fed7140cadd892d765bd47edbe4ac0b9c7e7e3c493dcb58126f99cf0 - url: "https://pub.dev" - source: hosted - version: "2.4.25" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" - url: "https://pub.dev" - source: hosted - version: "2.5.6" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" - source: hosted - version: "1.10.2" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - synchronized: - dependency: transitive - description: - name: synchronized - sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153" - url: "https://pub.dev" - source: hosted - version: "3.4.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" - url: "https://pub.dev" - source: hosted - version: "0.7.11" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - uuid: - dependency: transitive - description: - name: uuid - sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" - url: "https://pub.dev" - source: hosted - version: "4.5.3" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" - source: hosted - version: "2.2.0" - vibration: - dependency: "direct main" - description: - name: vibration - sha256: "9bb06614c69260f8bd11c80fe01ed7988905cf00e3417d656c2647e41f261d87" - url: "https://pub.dev" - source: hosted - version: "3.1.8" - vibration_platform_interface: - dependency: transitive - description: - name: vibration_platform_interface - sha256: "258c273268f8aa40c88d29741137c536874a738779b92ddb8aa32ed093721ec5" - url: "https://pub.dev" - source: hosted - version: "0.1.2" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" - source: hosted - version: "15.2.0" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - win32: - dependency: transitive - description: - name: win32 - sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738 - url: "https://pub.dev" - source: hosted - version: "6.3.0" - win32_registry: - dependency: transitive - description: - name: win32_registry - sha256: "73b1d78920a9d6e03f8b4e43e612b87bf3152a0e5c5e5150267762b7c4116904" - url: "https://pub.dev" - source: hosted - version: "3.0.3" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" -sdks: - dart: ">=3.12.1 <4.0.0" - flutter: ">=3.44.0" diff --git a/flutter-app/qrscanner/pubspec.yaml b/flutter-app/qrscanner/pubspec.yaml deleted file mode 100644 index e0bbd60..0000000 --- a/flutter-app/qrscanner/pubspec.yaml +++ /dev/null @@ -1,92 +0,0 @@ -name: qrscanner -description: "A new Flutter project." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 - -environment: - sdk: ^3.12.1 - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 - audioplayers: ^6.5.0 - http: ^1.2.2 - mobile_scanner: ^7.0.1 - shared_preferences: ^2.3.3 - vibration: ^3.1.3 - -dev_dependencies: - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^6.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - assets: - - assets/sounds/ - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package diff --git a/flutter-app/qrscanner/test/widget_test.dart b/flutter-app/qrscanner/test/widget_test.dart deleted file mode 100644 index f125623..0000000 --- a/flutter-app/qrscanner/test/widget_test.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; - -import 'package:qrscanner/main.dart'; - -void main() { - testWidgets('Login screen renders correctly', (WidgetTester tester) async { - await tester.pumpWidget(const MyApp()); - - expect(find.text('QR Scanner'), findsOneWidget); - expect(find.text('Iniciar sesión'), findsOneWidget); - expect(find.text('Usuario'), findsOneWidget); - }); -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index de4f4a1..608fff0 100644 --- a/pom.xml +++ b/pom.xml @@ -24,8 +24,21 @@ 25 25 UTF-8 + 2.31.78 + + + + software.amazon.awssdk + bom + ${aws.sdk.version} + pom + import + + + + base-admin-web-domain base-admin-web-application diff --git a/run-local.sh b/run-local.sh index b179385..9dd9823 100755 --- a/run-local.sh +++ b/run-local.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COGNITO_ENV_FILE="$SCRIPT_DIR/docker/floci/generated/cognito.local.env" + MIN_JAVA_VERSION=25 is_valid_jdk() { @@ -71,6 +74,39 @@ resolve_java_home() { return 1 } +wait_for_cognito_env() { + local elapsed=0 + local timeout=120 + + while [[ ! -f "$COGNITO_ENV_FILE" && $elapsed -lt $timeout ]]; do + sleep 2 + elapsed=$((elapsed + 2)) + if (( elapsed % 10 == 0 )); then + echo "Esperando bootstrap de Cognito en Floci..." >&2 + fi + done + + [[ -f "$COGNITO_ENV_FILE" ]] +} + +load_cognito_local_env() { + if [[ -n "${AWS_ENDPOINT_URL:-}" ]]; then + echo "AWS_ENDPOINT_URL ya definido; omitiendo carga de cognito.local.env" >&2 + return 0 + fi + + if [[ ! -f "$COGNITO_ENV_FILE" ]]; then + echo "No se encontro $COGNITO_ENV_FILE. Exporta las variables Cognito manualmente." >&2 + return 1 + fi + + set -a + # shellcheck disable=SC1090 + source "$COGNITO_ENV_FILE" + set +a + echo "Variables Cognito cargadas desde docker/floci/generated/cognito.local.env" >&2 +} + JAVA_HOME="$(resolve_java_home)" export JAVA_HOME export PATH="$JAVA_HOME/bin:$PATH" @@ -86,21 +122,34 @@ else echo "Base de datos: H2 en archivo local (./data/h2/). Para PostgreSQL usa: SPRING_PROFILES_ACTIVE=postgres ./run-local.sh" >&2 fi -if [[ "$PROFILE" == *"postgres"* ]]; then - if command -v docker >/dev/null 2>&1; then - if docker compose version >/dev/null 2>&1; then - docker compose -f docker-compose-postgres.local.yml up -d - else - echo "Docker Compose no esta disponible. Instala Docker Compose o levanta PostgreSQL manualmente." >&2 - exit 1 - fi +FLOCI_STARTED=false + +if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then + COMPOSE_FILE="$SCRIPT_DIR/docker-compose.local.yml" + mkdir -p "$SCRIPT_DIR/docker/floci/generated" + chmod 777 "$SCRIPT_DIR/docker/floci/generated" 2>/dev/null || true + + if [[ "$PROFILE" == *"postgres"* ]]; then + echo "Levantando PostgreSQL y Floci (Cognito local)..." >&2 + docker compose -f "$COMPOSE_FILE" up -d postgres floci else - echo "Docker no esta disponible. Instala Docker o usa el perfil dev con H2." >&2 - exit 1 + echo "Levantando Floci (Cognito local) en http://localhost:4566 ..." >&2 + docker compose -f "$COMPOSE_FILE" up -d floci + fi + FLOCI_STARTED=true +elif [[ "$PROFILE" == *"postgres"* ]]; then + echo "Docker Compose no esta disponible. Instala Docker o levanta PostgreSQL manualmente." >&2 + exit 1 +fi + +if [[ "$FLOCI_STARTED" == true ]]; then + if wait_for_cognito_env; then + load_cognito_local_env || true + else + echo "Advertencia: timeout esperando cognito.local.env. Revisa Floci o exporta las variables manualmente." >&2 fi fi -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" mvn -pl base-admin-web-jar -am -DskipTests install diff --git a/scripts/floci-up.sh b/scripts/floci-up.sh new file mode 100755 index 0000000..bcd4737 --- /dev/null +++ b/scripts/floci-up.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +cd "$ROOT_DIR" + +mkdir -p docker/floci/generated +chmod 777 docker/floci/generated + +if ! command -v docker >/dev/null 2>&1; then + echo "Docker no esta instalado." >&2 + exit 1 +fi + +if ! docker compose version >/dev/null 2>&1; then + echo "Docker Compose no esta disponible." >&2 + exit 1 +fi + +docker compose -f docker-compose.local.yml up -d floci + +cat <