Sistema multi-tenant de control de asistencia con geocercas y anti-fraude. Componentes:
ST_DWithin), es la autoridad de tiempo (NTP), escribe
eventos inmutables y sincroniza con Odoo.hr.attendance).server_time) es el dato legal del registro; la
hora del dispositivo es solo una claim que se valida.REVOKE). Cada evento
encadena hash = HMAC_SHA256(secret, prev_hash || fila);
alterar la tabla rompe la cadena y es detectable en auditoría.idempotency_key para no
duplicar.odoo_sync) para que una caída de Odoo no pierda
asistencias.sequenceDiagram
participant App as App Flutter
participant API as FastAPI
participant DB as PostgreSQL+PostGIS
participant Odoo as Odoo
App->>App: biometría + detección mock/root/jailbreak + GPS
App->>API: POST /attendance/check-in (JWT, coords, flags, idempotency_key)
API->>API: valida JWT + offset NTP + flags anti-spoofing
API->>DB: ST_DWithin(site.geom, coords, radius_m)
DB-->>API: inside/outside
API->>DB: INSERT evento (cadena de hash, server_time)
API->>DB: INSERT odoo_sync (pending)
API-->>App: 200 { geofence_status, server_time, hash }
Note over Odoo: worker asíncrono
DB->>Odoo: XML-RPC hr.attendance create/write
asistencia-geo/
├── docs/ ← documentación técnica y legal
├── db/ ← DDL + triggers de inmutabilidad + seed
├── backend/ ← FastAPI (app/ por capas: core, models, schemas, api, services, tasks)
├── mobile/ ← Flutter (lib/ por feature + core)
└── web/ ← panel web React + Vite + MapLibre
app/core — configuración, seguridad (JWT/Argon2), NTP,
criptografía de PII.app/models — SQLAlchemy 2 (typed ORM) reflejo de
db/000_schema.sql.app/schemas — Pydantic v2 (validación de
entrada/salida).app/api — routers (auth, attendance, sites, employees,
reports, time, health).app/services — lógica de negocio (geofence, attendance,
payroll_rules, odoo_client, alerts, notifications).app/tasks — APScheduler (recordatorios de salida,
worker de Odoo).core — config, red, almacenamiento seguro, tiempo
NTP.features/attendance — modelo, repositorio y servicios
(GPS, mock, seguridad, biometría, facial, check-in).features/sync — cola offline + sincronización con
retry.features/maps — visualización de geocerca.app/core/crypto.py, clave en PII_ENC_KEY). La
cadena de hash usa un secreto independiente
(ATTENDANCE_HASH_SECRET).asyncpg manejan alta concurrencia de
check-ins.chain_state; a volúmenes extremos se particiona por
company_id o se usa un hash-linking asíncrono.ST_DWithin.