# QR Scanner Aplicación móvil Flutter para validar **pases de acceso** mediante códigos QR. Se conecta al backend **base-admin-web** para autenticar operadores con rol escáner y registrar cada lectura en el servidor. --- ## Tabla de contenidos - [Características](#características) - [Arquitectura](#arquitectura) - [Flujos principales](#flujos-principales) - [Estructura del proyecto](#estructura-del-proyecto) - [Dependencias](#dependencias) - [Requisitos](#requisitos) - [Configuración e instalación](#configuración-e-instalación) - [API del backend](#api-del-backend) - [Permisos](#permisos) - [Pruebas](#pruebas) - [Compilación](#compilación) --- ## Características - Login con usuario y contraseña contra el API de **base-admin-web** (Cognito / JWT). - Sesión persistente opcional con **Recordarme** (`shared_preferences`). - Escáner QR en tiempo real con ventana de enfoque y overlay visual. - Validación del pase en el servidor con `accessToken` (Bearer). - Retroalimentación auditiva y háptica al detectar, validar o rechazar un código. - UI oscura con animaciones en login, home y resultados de escaneo. --- ## Arquitectura ```mermaid flowchart TB subgraph App["App Flutter (qrscanner)"] UI["Screens\n(login · home · scanner)"] Services["Services\n(auth · pass_code · feedback)"] Models["Models\n(login_response · auth_session · pass_code)"] Config["ApiConfig"] end subgraph Device["Dispositivo"] Camera["Cámara"] Prefs["SharedPreferences"] Audio["Audio / Vibración"] end subgraph Backend["base-admin-web :8080"] AuthAPI["POST /api/auth/login"] ScanAPI["POST /api/pass-codes/scan"] Cognito["AWS Cognito / JWT"] end UI --> Services Services --> Models Services --> Config Services --> Prefs UI --> Camera Services --> Audio Config --> AuthAPI Config --> ScanAPI AuthAPI --> Cognito ScanAPI --> Cognito ``` ### Capas de la app ```mermaid graph LR A[Pantallas] --> B[Servicios] B --> C[Modelos] B --> D[HTTP / API] A --> E[Tema / UI] B --> F[Almacenamiento local] A --> G[mobile_scanner] ``` | Capa | Responsabilidad | |------|-----------------| | **Screens** | UI, navegación y estados visuales | | **Services** | Lógica de negocio, HTTP y feedback | | **Models** | Parseo JSON y sesión de autenticación | | **Config** | URL base del API configurable en compilación | --- ## Flujos principales ### Login ```mermaid sequenceDiagram actor Usuario participant Login as LoginScreen participant Auth as AuthService participant API as base-admin-web participant Home as HomeScreen Usuario->>Login: Usuario + contraseña Login->>Auth: login() Auth->>API: POST /api/auth/login API-->>Auth: accessToken, refreshToken, idToken, roles Auth->>Auth: Valida ROLE_SCANNER alt Credenciales válidas Auth-->>Login: LoginResponse success Login->>Home: Navegar con AuthSession else Error API-->>Login: success false / sin token Login-->>Usuario: Mensaje de error end ``` ### Escaneo y validación ```mermaid sequenceDiagram actor Operador participant Home as HomeScreen participant Scan as ScannerScreen participant MS as mobile_scanner participant Pass as PassCodeService participant API as base-admin-web participant FB as FeedbackService Operador->>Home: Abrir escáner Home->>Scan: AuthSession loop Escaneo continuo MS->>Scan: onDetect (QR) Scan->>FB: Sonido + vibración Scan->>Pass: scan(code) Pass->>API: POST /api/pass-codes/scan\nAuthorization: Bearer accessToken API-->>Pass: success + passCode Pass-->>Scan: ScanResponse Scan->>FB: success / warning Scan-->>Operador: Tarjeta verde (nombre + código)\no amarilla (error) end ``` ### Estados del escáner ```mermaid stateDiagram-v2 [*] --> scanning: Abrir cámara scanning --> processing: QR detectado processing --> success: API válida processing --> warning: API rechaza / error red success --> scanning: Escanear otro warning --> scanning: Escanear otro scanning --> [*]: Volver atrás ``` --- ## Estructura del proyecto ``` qrscanner/ ├── lib/ │ ├── main.dart # Punto de entrada │ ├── config/ │ │ └── api_config.dart # URL base y endpoints │ ├── models/ │ │ ├── auth_session.dart # Sesión y header Authorization │ │ ├── login_response.dart # Respuesta de login │ │ ├── pass_code.dart # Datos del pase │ │ └── scan_response.dart # Respuesta de escaneo │ ├── screens/ │ │ ├── login_screen.dart # Pantalla de inicio de sesión │ │ ├── home_screen.dart # Pantalla principal │ │ └── scanner_screen.dart # Cámara y validación QR │ ├── services/ │ │ ├── auth_service.dart # Login y sesión │ │ ├── pass_code_service.dart# Validación de pases │ │ └── feedback_service.dart # Audio y vibración │ └── theme/ │ └── app_theme.dart # Colores y tema oscuro ├── assets/ │ └── sounds/ │ ├── scan.wav │ ├── success.wav │ └── warning.wav ├── android/ # Configuración Android ├── ios/ # Configuración iOS ├── test/ # Pruebas unitarias y widget └── pubspec.yaml # Dependencias ``` --- ## Dependencias ### Directas (producción) | Paquete | Versión en `pubspec.yaml` | Versión instalada | Uso | |---------|---------------------------|-------------------|-----| | [flutter](https://flutter.dev) | SDK | — | Framework UI | | [cupertino_icons](https://pub.dev/packages/cupertino_icons) | `^1.0.8` | 1.0.9 | Iconos iOS | | [http](https://pub.dev/packages/http) | `^1.2.2` | 1.6.0 | Peticiones REST al backend | | [mobile_scanner](https://pub.dev/packages/mobile_scanner) | `^7.0.1` | 7.2.0 | Lectura de códigos QR con cámara | | [shared_preferences](https://pub.dev/packages/shared_preferences) | `^2.3.3` | 2.5.5 | Persistir sesión (Recordarme) | | [audioplayers](https://pub.dev/packages/audioplayers) | `^6.5.0` | 6.7.1 | Sonidos de feedback | | [vibration](https://pub.dev/packages/vibration) | `^3.1.3` | 3.1.8 | Vibración al escanear | ### Desarrollo | Paquete | Versión | Uso | |---------|---------|-----| | [flutter_test](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html) | SDK | Pruebas widget | | [flutter_lints](https://pub.dev/packages/flutter_lints) | `^6.0.0` (6.0.0) | Reglas de análisis estático | ### Dependencias transitivas relevantes Incluidas automáticamente por los paquetes anteriores: - `path_provider` — rutas de almacenamiento (audioplayers, shared_preferences) - `device_info_plus` — información del dispositivo (vibration) - `camera` / plugins nativos — soporte de cámara (mobile_scanner) Para verificar versiones actualizadas: ```bash flutter pub outdated ``` --- ## Requisitos - **Flutter** 3.12+ (Dart SDK `^3.12.1`) - **Backend** [base-admin-web](https://github.com) corriendo y accesible en red - Dispositivo físico o emulador con **cámara** (recomendado dispositivo real para QR) - Usuario con rol **`ROLE_SCANNER`** en Cognito / base-admin-web --- ## Configuración e instalación ### 1. Clonar e instalar dependencias ```bash cd flutter-app/qrscanner flutter pub get ``` ### 2. URL del API Por defecto la app apunta a: ``` http://10.99.51.6:8080 ``` Para usar otra IP o puerto: ```bash flutter run --dart-define=API_BASE_URL=http://192.168.1.100:8080 ``` Compilación release con URL personalizada: ```bash flutter build apk --dart-define=API_BASE_URL=http://10.99.51.6:8080 ``` ### 3. Ejecutar en dispositivo ```bash # Listar dispositivos flutter devices # Ejecutar flutter run # Ejecutar con URL custom flutter run --dart-define=API_BASE_URL=http://10.99.51.6:8080 ``` ### 4. Credenciales de prueba Según el entorno de desarrollo de **base-admin-web**: | Campo | Valor | |-------|-------| | Usuario | `scanner` | | Contraseña | `Scanner123*` | > El usuario debe pertenecer al grupo **scanners** / rol `ROLE_SCANNER`. --- ## API del backend Base URL configurable: `ApiConfig.baseUrl` ### Login ```http POST /api/auth/login Content-Type: application/json ``` **Request:** ```json { "username": "scanner", "password": "Scanner123*" } ``` **Response (200):** ```json { "success": true, "accessToken": "eyJ...", "refreshToken": "dXMt...", "idToken": "eyJ...", "tokenType": "Bearer", "expiresIn": 3600, "username": "scanner", "roles": ["ROLE_SCANNER"] } ``` La app usa **`accessToken`** en todas las peticiones autenticadas. ### Escanear pase ```http POST /api/pass-codes/scan Content-Type: application/json Authorization: Bearer ``` **Request:** ```json { "code": "ABC123XYZ" } ``` **Response exitosa (200):** ```json { "success": true, "message": "Codigo pase escaneado correctamente.", "passCode": { "id": 1, "name": "Pase visitante", "code": "ABC123XYZ", "status": "ACTIVE", "scanned": true, "createdAt": "2026-06-09T10:00:00", "scannedAt": "2026-06-09T15:30:00" } } ``` **Response error (400):** ```json { "success": false, "message": "Descripción del error" } ``` ### Formato del QR La app acepta: - Texto plano con el código del pase - URL con el código en query: `?code=ABC123` - URL con el código como último segmento del path --- ## Permisos ### Android (`AndroidManifest.xml`) | Permiso | Motivo | |---------|--------| | `INTERNET` | Comunicación con el API | | `CAMERA` | Escaneo QR | | `VIBRATE` | Feedback háptico | `android:usesCleartextTraffic="true"` habilitado para HTTP en red local (desarrollo). ### iOS Requiere descripción de uso de cámara en `Info.plist` al desplegar en producción. --- ## Pruebas ```bash # Todas las pruebas flutter test # Análisis estático flutter analyze ``` Pruebas incluidas: - `test/widget_test.dart` — renderizado de la pantalla de login - `test/login_response_test.dart` — parseo de respuesta Cognito y uso de `accessToken` --- ## Compilación ### Android APK ```bash flutter build apk --release \ --dart-define=API_BASE_URL=http://10.99.51.6:8080 ``` Salida: `build/app/outputs/flutter-apk/app-release.apk` ### Android App Bundle (Play Store) ```bash flutter build appbundle --release \ --dart-define=API_BASE_URL=https://tu-servidor.com ``` ### iOS ```bash flutter build ios --release \ --dart-define=API_BASE_URL=https://tu-servidor.com ``` --- ## Diagrama de integración con base-admin-web ```mermaid flowchart LR subgraph Mobile["📱 QR Scanner"] A[Login] B[Escáner] end subgraph Server["🖥️ base-admin-web"] C["/api/auth/login"] D["/api/pass-codes/scan"] E[(Base de datos)] end subgraph Auth["🔐 Cognito"] F[JWT / Tokens] end A -->|username + password| C C --> F F -->|accessToken| A B -->|Bearer accessToken + code| D D --> E D -->|passCode| B ``` --- ## Notas técnicas - **Token activo:** solo se envía `accessToken` en el header `Authorization`. - **Sesión:** si "Recordarme" está activo, se guardan `accessToken`, `refreshToken`, `idToken`, `username` y `roles` en `SharedPreferences`. - **Escáner:** modo `DetectionSpeed.normal`, solo formato `QR`, con `autoZoom` en Android. - **Resultado válido:** la tarjeta verde muestra únicamente **nombre** y **código** del pase.