App móvil para validar pases QR contra base-admin-web con login Cognito, escáner en tiempo real, feedback auditivo/háptico y README completo.
12 KiB
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
- Arquitectura
- Flujos principales
- Estructura del proyecto
- Dependencias
- Requisitos
- Configuración e instalación
- API del backend
- Permisos
- Pruebas
- 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
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
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
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
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
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 | SDK | — | Framework UI |
| cupertino_icons | ^1.0.8 |
1.0.9 | Iconos iOS |
| http | ^1.2.2 |
1.6.0 | Peticiones REST al backend |
| mobile_scanner | ^7.0.1 |
7.2.0 | Lectura de códigos QR con cámara |
| shared_preferences | ^2.3.3 |
2.5.5 | Persistir sesión (Recordarme) |
| audioplayers | ^6.5.0 |
6.7.1 | Sonidos de feedback |
| vibration | ^3.1.3 |
3.1.8 | Vibración al escanear |
Desarrollo
| Paquete | Versión | Uso |
|---|---|---|
| flutter_test | SDK | Pruebas widget |
| 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:
flutter pub outdated
Requisitos
- Flutter 3.12+ (Dart SDK
^3.12.1) - Backend base-admin-web corriendo y accesible en red
- Dispositivo físico o emulador con cámara (recomendado dispositivo real para QR)
- Usuario con rol
ROLE_SCANNERen Cognito / base-admin-web
Configuración e instalación
1. Clonar e instalar dependencias
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:
flutter run --dart-define=API_BASE_URL=http://192.168.1.100:8080
Compilación release con URL personalizada:
flutter build apk --dart-define=API_BASE_URL=http://10.99.51.6:8080
3. Ejecutar en dispositivo
# 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
POST /api/auth/login
Content-Type: application/json
Request:
{
"username": "scanner",
"password": "Scanner123*"
}
Response (200):
{
"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
POST /api/pass-codes/scan
Content-Type: application/json
Authorization: Bearer <accessToken>
Request:
{
"code": "ABC123XYZ"
}
Response exitosa (200):
{
"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):
{
"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
# Todas las pruebas
flutter test
# Análisis estático
flutter analyze
Pruebas incluidas:
test/widget_test.dart— renderizado de la pantalla de logintest/login_response_test.dart— parseo de respuesta Cognito y uso deaccessToken
Compilación
Android APK
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)
flutter build appbundle --release \
--dart-define=API_BASE_URL=https://tu-servidor.com
iOS
flutter build ios --release \
--dart-define=API_BASE_URL=https://tu-servidor.com
Diagrama de integración con base-admin-web
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
accessTokenen el headerAuthorization. - Sesión: si "Recordarme" está activo, se guardan
accessToken,refreshToken,idToken,usernameyrolesenSharedPreferences. - Escáner: modo
DetectionSpeed.normal, solo formatoQR, conautoZoomen Android. - Resultado válido: la tarjeta verde muestra únicamente nombre y código del pase.