# Base Admin Web 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 | 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) | --- ## Arquitectura del sistema ### Capas y dependencias ```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"] jar --> web jar --> config jar --> app jar --> domain web --> config web --> app config --> app app --> domain ``` | 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) | ### Dos cadenas de seguridad Spring Security define **dos filter chains** con distinto comportamiento: ```mermaid flowchart LR subgraph order1["@Order(1) — ApiSecurityConfig"] A1["/api/**"] A2["Stateless"] A3["JWT Bearer"] A4["ROLE_SCANNER en /scan"] end subgraph order2["@Order(2) — SecurityConfig"] B1["Todo excepto /api/**"] B2["Sesión HTTP"] B3["Form login /login"] B4["ROLE_ADMIN en /admin/**"] end A1 --> A2 --> A3 --> A4 B1 --> B2 --> B3 --> B4 ``` | 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 | --- ## Seguridad y autenticación ### Usuarios iniciales #### Administrador (panel web) - 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 #### 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 Content-Type: application/json { "username": "scanner", "password": "Scanner123*" } ``` Respuesta (Cognito): ```json { "success": true, "accessToken": "eyJhbG...", "refreshToken": "dXMtZWFzdC0x...", "idToken": "eyJhbG...", "tokenType": "Bearer", "expiresIn": 3600, "username": "scanner", "roles": ["ROLE_SCANNER"] } ``` ### Renovar token ```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 Authorization: Bearer eyJhbG... Content-Type: application/json { "code": "K7P2M9XQ4R" } ``` ### Listado JSON (panel admin, requiere sesión) ```http GET /admin/pass-codes/list ``` --- ## 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 ### Opción recomendada ```bash ./run-local.sh ``` El script: - Detecta Java 25 (Temurin) - Levanta Floci (y PostgreSQL si aplica) - Espera y carga `cognito.local.env` - Compila e inicia `base-admin-web-jar` Si ya tienes `AWS_ENDPOINT_URL` exportado, no sobrescribe tus variables (útil para probar contra AWS). ### Maven manual (terminal) ```bash # 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 ``` Si Floci ya estaba levantado de antes, basta con el `source` y el `spring-boot:run`. ### PostgreSQL local ```bash SPRING_PROFILES_ACTIVE=postgres ./run-local.sh ``` ### PostgreSQL remoto ```bash SPRING_PROFILES_ACTIVE=postgres \ SPRING_DATASOURCE_URL=jdbc:postgresql://TU_SERVIDOR:5432/base_admin_web \ SPRING_DATASOURCE_USERNAME=tu_usuario \ SPRING_DATASOURCE_PASSWORD=tu_password \ ./run-local.sh ``` --- ## Arranque manual e IntelliJ IDEA 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 | 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 local (solo `APP_COGNITO_ENABLED=false`) | Variable | Descripción | |----------|-------------| | `APP_JWT_SECRET` | Secreto HS256 (mín. 32 caracteres) | | `APP_JWT_EXPIRATION_HOURS` | Expiración del token | ### Usuarios semilla (modo local sin Cognito) | 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 mínimo para arrancar nuevos proyectos internos: - 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 se mantiene deliberadamente sencillo como punto de partida para futuras integraciones.