Sistema de Control de Asistencia Georeferenciado — v0.3.1 Última actualización: 2026-09-22
Este manual cubre el ciclo completo de operación del sistema: entornos, arquitectura de despliegue, build y publicación del APK, despliegue del backend, respaldo/restauración, rollback, monitoreo y resolución de incidentes conocidos.
| Entorno | Ubicación | Propósito |
|---|---|---|
| Producción | Linode 172.238.169.174 |
Servicio público
(https://www.intt2hub.com/ia/asistencia/) |
| Desarrollo (WSL2) | ~/DeepSeekHarness/projects/asistencia-geo/ |
Código fuente, pruebas, build del APK |
| Docker (PostGIS) | Linode, contenedor asistencia-postgis |
Base de datos PostgreSQL + PostGIS |
127.0.0.1:5433, base
asistencia.systemd (ia-asistencia.service), uvicorn en
127.0.0.1:8095./panel.Cliente (móvil Android / navegador)
│ HTTPS (TLS 1.3)
▼
Cloudflare CDN ────────────────────────── (edge cache; APK con no-store)
│
▼
nginx (Linode, intt2hub.conf)
├── /ia/asistencia/ → proxy_pass http://127.0.0.1:8095/ (uvicorn)
├── /ia/downloads/ → APK estáticos versionados
└── / → Odoo :8069
│
▼
systemd ia-asistencia.service
└── uvicorn app.main:app (venv /opt/asistencia/backend, .env con secretos)
│ asyncpg
▼
Docker asistencia-postgis (postgis/postgis:15-3.4 @ 127.0.0.1:5433)
│ XML-RPC (worker APScheduler)
▼
Odoo 18 Community (hr.attendance)
Véase también el diagrama PNG: docs/diagrams/04-despliegue.png.
/home/jpaez/flutter (Dart
>=3.3.0)/home/jpaez/android-toolchain/java/home/jpaez/android-toolchain/android-sdk~/.bashrc:
JAVA_HOME, ANDROID_HOME,
PATH.**/*.jks) +
android/key.properties.debug.keystore (es por-máquina y se puede perder).cd mobile
flutter pub get
flutter analyze # debe quedar limpio (0 errores)
flutter test # suite verde
# Universal (todos los ABIs)
flutter build apk --release
cp build/app/outputs/flutter-apk/app-release.apk \
../entregas/asistencia-geo/asistencia-geo-vX.Y.Z.apk
# Solo arm64 (APK más ligero, ~46 MB)
flutter build apk --release --target-platform android-arm64
cp build/app/outputs/flutter-apk/app-release.apk \
../entregas/asistencia-geo/asistencia-geo-vX.Y.Z-arm64.apkGOTCHA:
flutter build apk(universal) y--target-platform android-arm64escriben ambos aapp-release.apk; el segundo pisa al primero. Copiar cada uno a su nombre versionado inmediatamente después de cada build.
GOTCHA (versionCode): antes de publicar, verificar que
pubspec.yamltengaversion: X.Y.Z+NconNmayor al último publicado. No subir el versionCode provoca que la actualización falle en el dispositivo.
# En Linode
V=20260918_aprobaciones_v031
mkdir -p /opt/ia-intt/html/downloads/asistencia/$V
# scp de los APK + SHA256SUMS.txt + index.html al directorio versionado
cd /opt/ia-intt/html/downloads/asistencia
ln -sfn $V latest # reapuntar symlink 'latest'https://www.intt2hub.com/ia/downloads/asistencia/<fecha>_vNNN/asistencia-geo-vX.Y.Z.apk), no
depender de latest/app-release.apk (Cloudflare cachea
archivos grandes)..apk con
Content-Type: application/vnd.android.package-archive
sin Content-Disposition: attachment (rompe
la descarga en Android).# Respaldo previo (obligatorio)
ssh [email protected] 'tar czf /opt/asistencia/backup_pre_$(date +%Y%m%d_%H%M%S).tar.gz -C /opt/asistencia backend'
# Sincronizar (rsync --delete para el panel; scp para el código)
rsync -av --delete web/dist/ [email protected]:/opt/asistencia/backend/app/static/panel/
scp backend/app/**/*.py [email protected]:/opt/asistencia/backend/app/ssh [email protected] 'systemctl restart ia-asistencia.service && systemctl status ia-asistencia.service --no-pager'curl -s https://www.intt2hub.com/ia/asistencia/health
curl -s https://www.intt2hub.com/ia/asistencia/panel/ -o /dev/null -w '%{http_code}\n'El esquema se versiona en db/000_schema.sql,
db/001_immutability.sql, db/002_seed.sql y
db/003_approvals.sql. Las migraciones
nuevas se aplican de forma idempotente
(guards IF NOT EXISTS /
DO $$ ... duplicate_object).
# Aplicar migración idempotente
docker exec -i asistencia-postgis psql -U asistencia -d asistencia < db/003_approvals.sqlLa tabla
attendance_eventses inmutable (triggertrg_events_immutablebloquea UPDATE/DELETE). Cualquier corrección legal se hace insertando un evento nuevo + fila enattendance_audit_log, o una decisión enattendance_approvals.
docker exec asistencia-postgis pg_dump -U asistencia -d asistencia \
> /opt/asistencia/backup_db_$(date +%Y%m%d_%H%M%S).sqltar czf /opt/asistencia/backup_code_$(date +%Y%m%d_%H%M%S).tar.gz -C /opt/asistencia backenddocker exec -i asistencia-postgis psql -U asistencia -d asistencia < backup_db_*.sql
# + restaurar código + systemctl restart ia-asistenciabackup_code_*.tar.gz.backup_db_*.sql (si
hubo migración).systemctl restart ia-asistencia.service./health y /panel.# Estado del servicio
systemctl status ia-asistencia.service
# Logs del backend
journalctl -u ia-asistencia.service -f
# Estado del contenedor PostGIS
docker ps --filter name=asistencia-postgis
docker logs --tail 50 asistencia-postgis
# Health checks
curl -s https://www.intt2hub.com/ia/asistencia/health
curl -s https://www.intt2hub.com/ia/asistencia/time.env (nunca en código).
Destacados: JWT_SECRET, SERVER_TIME_SECRET,
ATTENDANCE_HASH_SECRET, PII_ENC_KEY (Fernet
AES-256-GCM), ODOO_URL/DB/USER/API_KEY.allow_origins=["*"]
(restringir en producción).ACCESS_TOKEN_EXPIRE_MINUTES hoy
480 (8h) en producción; refresh token 14 días.jwt_secret
default en código sigue siendo placeholder (el .env real lo
sobreescribe). Verificar que el valor sea fuerte.0.0.0.0;
evaluar cerrarlo al exterior.| # | Síntoma | Causa raíz | Fix |
|---|---|---|---|
| 1 | APK no se instala (conflicto de paquete) | Firma distinta / instalación residual | Desinstalar app vieja + keystore de release fijo |
| 2 | App cuelga al arrancar (spinner infinito) | flutter_secure_storage corrupto tras reinstalar
(BadPaddingException) |
try/catch + .timeout(8s) en lecturas/escrituras de
secure storage |
| 3 | Biometría no responde | MainActivity extendía FlutterActivity;
local_auth exige FlutterFragmentActivity |
Cambiar a FlutterFragmentActivity |
| 4 | “Token inválido” en panel ~1h | Access token 30 min sin auto-refresh | ACCESS_TOKEN_EXPIRE_MINUTES 480 + auto-refresh en
401 |
| 5 | APK descargado “colgado” al 100% | Content-Disposition: attachment + content-length |
Servir inline con MIME correcto, sin attachment |
| 6 | APK viejo servido pese a re-apuntar latest |
Cloudflare cachea latest/app-release.apk |
Publicar con nombre versionado +
Cache-Control: no-store |
| 7 | flutter build pisa el APK arm64 con el universal |
Ambos escriben a app-release.apk |
Copiar a nombre versionado tras cada build |
# Repositorio
cd ~/DeepSeekHarness/projects/asistencia-geo
# Backend local (dev)
cd backend && python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt && cp .env.example .env
uvicorn app.main:app --reload # http://127.0.0.1:8000/docs
# Base de datos local (dev)
docker compose up -d postgres
# Móvil (dev)
cd mobile && flutter pub get && flutter run
# Panel web (dev)
cd web && npm install && npm run devVéase también: - 01 — Arquitectura - 02 — Esquema de base de datos - 08 — Versiones y releases - 09 — Stack tecnológico - Diagramas PNG