Defensa en profundidad: local (móvil) + remota (servidor). Ninguna capa sola basta.
Location.isFromMockProvider() sobre la última
ubicación.Settings.Secure.ALLOW_MOCK_LOCATION (configuración
global de ubicaciones simuladas).Implementación:
mobile/android/.../MainActivity.kt—isMockLocationDetected().
fork() con salida negativa, sandbox bypass).CLLocation.horizontalAccuracy y consistencia del
fix.geolocator (ubicación), trust_location /
flutter_trust_location (mock),
device_info_plus.root_check (comprueba
su, Magisk, Superuser.apk, build
tags test-keys)./Applications/Cydia.app, /usr/sbin/sshd,
/bin/bash, /.installed_unc0ver), y
canOpenURL("cydia://").root_jailbreak_detected) y se registra en
devices.GET /api/v1/time devuelve el tiempo del servidor
firmado. El cliente calcula
offset = server_time - device_time y lo guarda en
flutter_secure_storage.|offset| > 120 s (umbral configurable).last_synced_offset + tiempo monotónico del arranque; al
sincronizar, el servidor valida que la deriva (offset previo + elapsed)
sea plausible.server_time (hora de recepción en el servidor) es el
timestamp legal del evento; recorded_at
(hora del dispositivo) se conserva solo como claim auditada.accuracy_m por encima
del umbral (p. ej. 100 m) → rechazo para perfil site.device_id
registrado y root_jailbreak_status previo.Si se detecta mock/root/jailbreak:
attendance_events.alerts
(type = mock_location / root_jailbreak) con
payload (coordenadas, app sospechosa, versión de OS).403 Forbidden con código de error
específico.