Files
Base-QR-FlutterApp/README.md
Alejandro Lara 50fe9a9d36 Initial commit: QR Scanner Flutter app
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.
2026-06-09 17:03:06 -06:00

481 lines
12 KiB
Markdown

# 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 <accessToken>
```
**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.