DDL completo en db/000_schema.sql. Diagrama
entidad-relación:
erDiagram
COMPANIES ||--o{ USERS : "emplea"
COMPANIES ||--o{ SITES : "posee"
COMPANIES ||--o{ ATTENDANCE_EVENTS : "agrupa"
COMPANIES ||--o{ ALERTS : "recibe"
USERS ||--o{ EMPLOYEE_SITE_ASSIGNMENTS : "asignado"
SITES ||--o{ EMPLOYEE_SITE_ASSIGNMENTS : "recibe"
EMPLOYEE_SITE_ASSIGNMENTS ||--o{ SHIFTS : "horario"
USERS ||--o{ ATTENDANCE_EVENTS : "registra"
SITES ||--o{ ATTENDANCE_EVENTS : "geocerca"
ATTENDANCE_EVENTS ||--o{ ATTENDANCE_AUDIT_LOG : "corregido"
ATTENDANCE_EVENTS ||--o{ ODOO_SYNC : "sincronizado"
USERS ||--o{ CONSENTS : "acepta"
USERS ||--o{ DEVICES : "usa"
USERS ||--o{ PUSH_TOKENS : "recibe"
COMPANIES {
bigint id PK
text name
text timezone
jsonb legal_config
}
USERS {
bigint id PK
bigint company_id FK
text email
text password_hash
profile_type profile_type
user_role role
bigint odoo_employee_id
}
SITES {
bigint id PK
bigint company_id FK
geography geom
float radius_m
}
ATTENDANCE_EVENTS {
bigint id PK
bigint company_id FK
bigint employee_id FK
bigint site_id FK
event_type event_type
float lat
float lng
geofence_status geofence_status
timestamptz server_time
text idempotency_key
bigint chain_seq
text prev_hash
text hash
}
ODOO_SYNC {
bigint id PK
bigint event_id FK
odoo_sync_status status
}
| Tabla | Propósito |
|---|---|
companies |
Tenant + configuración legal LFT (legal_config). |
users |
Empleados y usuarios admin; profile_type (site/remote),
odoo_employee_id. |
sites |
Centro de trabajo con geography(Point) y
radius_m (geocerca). |
employee_site_assignments |
Muchos-a-muchos empleado↔︎sitio con vigencia y
is_primary. |
shifts |
Horario semanal por asignación + tipo de jornada (diurna/nocturna/mixta). |
attendance_events |
Núcleo. Eventos append-only con cadena de hash. |
attendance_audit_log |
Correcciones legales (referencia al evento original, sin editarlo). |
consents |
Consentimientos INAI por tipo (gps/biometric/face/data) y versión. |
devices |
Dispositivos registrados + estado root/jailbreak. |
push_tokens |
Tokens FCM/APNs/ntfy por empleado. |
alerts |
Alertas de fraude o incidencias. |
odoo_sync |
Outbox de sincronización con Odoo (idempotente). |
company_id en todas las
tablas de negocio; filtrado a nivel de aplicación.geography (distancias
reales en metros) + índice GIST; consulta con
ST_DWithin(geom, ST_MakePoint(:lng,:lat)::geography, :radius_m).UNIQUE (company_id, idempotency_key) evita duplicados en
reintentos.db/001_immutability.sql (trigger que bloquea UPDATE/DELETE
+ REVOKE al rol de aplicación + validación de cadena en
INSERT).hash = HMAC_SHA256(ATTENDANCE_HASH_SECRET, prev_hash || fila_canónica).
El prev_hash del evento N+1 referencia el hash
del evento N de la misma empresa; alterar cualquier fila invalida toda
la cadena posterior (tamper-evidence).