erp-construccion/docs/02-definicion-modulos/MAI-007-rrhh-asistencias/requerimientos/RF-HR-002-asistencia-biometrica.md

555 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# RF-HR-002: Sistema de Asistencia con Biométrico
## 📋 Metadata
| Campo | Valor |
|-------|-------|
| **ID** | RF-HR-002 |
| **Módulo** | RRHH y Asistencias |
| **Prioridad** | P0 - Crítica |
| **Estado** | 🚧 Planificado |
| **Versión** | 1.0 |
| **Fecha creación** | 2025-11-17 |
| **Última actualización** | 2025-11-17 |
## 🔗 Referencias
### Especificación Técnica
📐 [ET-HR-002: Asistencia Biométrica Implementation](../especificaciones/ET-HR-002-asistencia-biometrica.md)
### Implementación
🗄️ **Database:**
- **Ubicación:** `apps/database/ddl/schemas/hr/tables/02-attendance_records.sql`
- **Tabla:** `hr.attendance_records`
- **ENUM:** `attendance_method` (biometric_fingerprint, biometric_facial, qr_code, manual, gps)
💻 **Backend:**
- **Módulo:** `apps/backend/src/modules/hr/attendance/`
- **Service:** `AttendanceService`, `BiometricService`
- **API:** `/api/hr/attendance`
📱 **App Móvil:**
- **Módulo:** `apps/mobile/src/features/attendance/`
- **Componentes:** Biometric scanner, QR scanner, GPS validator
### Reusado de GAMILIT
♻️ **Concepto similar:** Tracking de progreso de estudiantes
**Adaptación:** Alta - Funcionalidad completamente nueva con biométrico
---
## 📝 Descripción del Requerimiento
### Contexto
En proyectos de construcción, el control de asistencia del personal es **crítico** para:
- **Costeo real de mano de obra:** Saber exactamente qué días trabajó cada empleado
- **Cumplimiento legal:** IMSS requiere evidencia de días trabajados
- **Prevención de fraude:** Evitar "aviadores" (trabajadores que cobran sin trabajar)
- **Seguridad:** Saber quién está presente en obra (en caso de accidentes)
- **Productividad:** Relacionar avances con horas-hombre trabajadas
**Problemática actual (manual):**
- Listas en papel propensas a error y manipulación
- Imposible verificar ubicación del empleado
- Difícil auditar registros
- Tiempo perdido pasando lista cada mañana
- Riesgo de firmas falsas o doble registro
**Solución:**
Sistema de asistencia digital con **validación biométrica** (huella dactilar o facial) desde app móvil, con validaciones automáticas de GPS y horario.
---
## 🎯 Requerimientos Funcionales
### RF-HR-002.1: Métodos de Registro de Asistencia
El sistema **DEBE** soportar múltiples métodos de registro con la siguiente prioridad:
#### 1. Biométrico - Huella Dactilar (Método Preferido)
**Tecnología:** `react-native-biometrics`
**Proceso:**
1. Empleado coloca dedo en sensor del dispositivo móvil
2. App captura template biométrico
3. Compara con template almacenado en servidor (encriptado)
4. Si match >= 70%: Registro exitoso
5. Si match < 70%: Solicitar re-intento (max 3 intentos)
**Ventajas:**
- Rapidez: <3 segundos por registro
- Alta precisión (>99%)
- ✅ No requiere contacto adicional (device ya tiene sensor)
- ✅ Funciona offline (template en caché local)
**Desventajas:**
- ⚠️ No todos los dispositivos tienen sensor de huella
- ⚠️ Requiere enrollment previo (1ra vez)
---
#### 2. Biométrico - Reconocimiento Facial (Alternativa)
**Tecnología:** `expo-camera` + ML Kit / Vision API
**Proceso:**
1. Empleado mira a cámara frontal del dispositivo
2. App captura foto y extrae características faciales
3. Compara con foto almacenada en servidor
4. Si match >= 80%: Registro exitoso
5. Si match < 80%: Fallback a QR o manual
**Ventajas:**
- No requiere contacto físico
- Funciona en cualquier dispositivo con cámara frontal
- Buena precisión con buena iluminación
**Desventajas:**
- Requiere buena iluminación
- Puede fallar con cascos/lentes/cubrebocas
- Más lento que huella (~5-7 segundos)
---
#### 3. QR Code (Fallback Principal)
**Tecnología:** `react-native-qrcode-scanner`
**Proceso:**
1. Empleado muestra código QR personal (credencial o app propia)
2. Residente escanea con app
3. Sistema valida QR contra base de datos
4. Registro exitoso con foto opcional
**Ventajas:**
- Funciona en cualquier dispositivo con cámara
- Rápido (~2 segundos)
- No requiere enrollment previo
**Desventajas:**
- Requiere que empleado porte credencial
- QR puede ser falsificado (mitigado con foto adicional)
**Generación de QR:**
```
QR Content: {employeeId}|{constructoraId}|{hash}
Hash: HMAC-SHA256(employeeId + constructoraId + SECRET_KEY)
```
---
#### 4. Lista Manual (Fallback Secundario)
**Proceso:**
1. Residente busca empleado en lista
2. Selecciona nombre
3. Toma foto obligatoria del empleado
4. Confirma registro
**Ventajas:**
- Siempre funciona
- Útil para visitantes o personal temporal
**Desventajas:**
- Más lento (~10-15 segundos por persona)
- Requiere foto para validación
---
### RF-HR-002.2: Validaciones Automáticas
El sistema **DEBE** aplicar las siguientes validaciones en **tiempo real**:
#### 1. Validación de GPS (Geolocalización)
**Objetivo:** Verificar que el empleado esté físicamente en la obra
**Regla:**
```
IF distancia(ubicación_empleado, ubicación_obra) <= 100 metros
THEN validación_gps = PASS
ELSE validación_gps = WARNING (permitir override manual)
```
**Configuración:**
- Radio por defecto: 100 metros
- Radio configurable por obra (50m - 500m)
- Precisión mínima requerida del GPS: 20 metros
**Casos especiales:**
- **GPS desactivado:** Advertencia + Permitir registro con nota
- **GPS impreciso:** Advertencia + Permitir registro
- **Fuera de radio:** Advertencia + Requiere justificación del residente
**Implementación:**
```typescript
// Haversine formula para calcular distancia
function calculateDistance(
lat1: number, lon1: number,
lat2: number, lon2: number
): number {
const R = 6371e3; // Radio de la Tierra en metros
const φ1 = lat1 * Math.PI/180;
const φ2 = lat2 * Math.PI/180;
const Δφ = (lat2-lat1) * Math.PI/180;
const Δλ = (lon2-lon1) * Math.PI/180;
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c; // Distancia en metros
}
```
---
#### 2. Validación de Horario (Jornada Laboral)
**Objetivo:** Detectar registros fuera de horario normal
**Reglas:**
```
Horario normal: 6:00 AM - 8:00 PM (configurable)
IF hora_registro BETWEEN 6:00 AND 8:00
THEN validación_horario = PASS
ELSE validación_horario = WARNING (permitir registro con nota)
```
**Configuración por obra:**
- Horario de entrada permitido: 5:00 AM - 9:00 AM
- Horario de salida permitido: 3:00 PM - 9:00 PM
- Tolerancia de retraso: 15 minutos (sin penalización)
**Casos especiales:**
- **Hora extra:** Registro fuera de horario requiere autorización previa
- **Turno nocturno:** Validar contra horario de turno específico
- **Domingo/Festivo:** Advertencia + Confirmar con residente
---
#### 3. Validación de Estado del Empleado
**Objetivo:** Solo permitir registro de empleados activos y asignados
**Reglas:**
```sql
WHERE employee.status = 'active'
AND employee.id IN (
SELECT employee_id
FROM project_employee_assignments
WHERE project_id = {current_project_id}
AND active = true
)
```
**Estados de empleado:**
- `active`: Puede registrar asistencia
- `suspended`: No puede registrar (mensaje: "Empleado suspendido, contactar RRHH")
- `terminated`: No puede registrar (mensaje: "Empleado dado de baja")
- `inactive`: No puede registrar (mensaje: "Empleado inactivo")
---
#### 4. Validación de Duplicados
**Objetivo:** Prevenir doble check-in sin check-out previo
**Reglas:**
```
IF existe registro check-in del día SIN check-out
THEN bloquear nuevo check-in (mensaje: "Ya tienes un registro activo. Debes hacer check-out primero")
```
**Lógica:**
```sql
SELECT COUNT(*) FROM hr.attendance_records
WHERE employee_id = {employee_id}
AND project_id = {project_id}
AND date = CURRENT_DATE
AND check_out_time IS NULL
AND status = 'active'
```
---
### RF-HR-002.3: Tipos de Registro
#### 1. Check-in (Entrada)
**Cuándo:** Al iniciar jornada laboral
**Datos capturados:**
- Timestamp (fecha + hora)
- Método de registro (biometric_fingerprint, biometric_facial, qr_code, manual)
- GPS (latitud, longitud, precisión)
- Foto (opcional, obligatoria si es manual)
- Dispositivo usado (device_id)
- Residente que registró (user_id)
**Validaciones aplicadas:**
- GPS dentro de radio
- Horario de entrada permitido
- Empleado activo y asignado
- Sin check-in previo activo
---
#### 2. Check-out (Salida)
**Cuándo:** Al terminar jornada laboral
**Datos capturados:**
- Timestamp de salida
- GPS de salida
- Horas trabajadas (calculadas automáticamente)
**Validaciones aplicadas:**
- Existe check-in previo del día
- Tiempo mínimo trabajado: 1 hora (evitar registros erróneos)
- Horario de salida permitido
**Cálculo de horas trabajadas:**
```typescript
const horasTrabajadas = (checkOutTime - checkInTime) / (1000 * 60 * 60); // En horas
const horasRedondeadas = Math.round(horasTrabajadas * 4) / 4; // Redondear a cuartos de hora
```
---
### RF-HR-002.4: Modo Offline
**Requisito:** La app **DEBE** funcionar sin conexión a internet
**Capacidades offline:**
- Registrar hasta 500 asistencias en cola local
- Cache de lista de empleados de la obra (actualizado diariamente)
- Cache de templates biométricos (encriptados)
- Validaciones GPS y horario (usando hora local del dispositivo)
- Indicador visual de registros pendientes de sincronización
**Sincronización:**
```typescript
// Automática al detectar conexión
window.addEventListener('online', () => {
syncPendingAttendances();
});
// Manual por botón
function syncNow() {
if (navigator.onLine) {
syncPendingAttendances();
} else {
showToast('Sin conexión. Sincronizará automáticamente al reconectar.');
}
}
```
**Resolución de conflictos:**
- Si existe registro duplicado en servidor: Usar el de menor timestamp
- Si empleado fue dado de baja: Rechazar registro offline
- Log de conflictos para auditoría
---
### RF-HR-002.5: Seguridad y Privacidad
#### 1. Almacenamiento de Datos Biométricos
**Reglas:**
- **NUNCA** almacenar imagen de huella dactilar completa
- Almacenar solo template hash (irreversible)
- Templates encriptados en tránsito (HTTPS)
- Templates encriptados en reposo (AES-256)
**Formato de template:**
```typescript
interface BiometricTemplate {
employeeId: string;
templateHash: string; // SHA-256(template_raw)
algorithm: 'FingerprintJS' | 'FaceNet';
createdAt: Date;
lastUsed: Date;
encrypted: boolean;
}
```
---
#### 2. Consentimiento del Empleado
**Requisito:** Obtener consentimiento explícito antes de capturar biométricos
**Proceso:**
1. Al dar de alta empleado, mostrar aviso de privacidad
2. Empleado firma consentimiento digital
3. Consentimiento se almacena con timestamp y firma digital
4. Sin consentimiento: Solo métodos no biométricos (QR, manual)
**Texto de consentimiento (ejemplo):**
```
"Autorizo a [Constructora] a capturar y almacenar mis datos biométricos
(huella dactilar y/o reconocimiento facial) con el único propósito de
registrar mi asistencia laboral. Entiendo que estos datos están protegidos
conforme a la Ley Federal de Protección de Datos Personales en Posesión
de los Particulares."
```
---
#### 3. Retención de Datos
**Política:**
- Registros de asistencia: Retener 5 años (requerimiento fiscal)
- Templates biométricos: Eliminar a los 30 días de baja del empleado
- Fotos: Retener 1 año, luego eliminar automáticamente
---
### RF-HR-002.6: Reportes y Auditoría
#### 1. Reporte de Asistencias Diarias
**Contenido:**
- Lista de empleados con check-in/check-out del día
- Horas trabajadas
- Empleados faltantes (esperados pero sin registro)
- Anomalías (GPS fuera de radio, horario inusual)
**Formato:** PDF, Excel
---
#### 2. Reporte Mensual para Nómina
**Contenido:**
- Días trabajados por empleado
- Total de horas trabajadas
- Horas extra (fuera de horario normal)
- Faltas injustificadas
**Exportación:** Excel compatible con sistemas de nómina
---
#### 3. Log de Auditoría
**Registrar:**
- Todos los registros de asistencia
- Intentos fallidos de biométrico
- Overrides manuales de validaciones
- Cambios en configuración (radio GPS, horarios)
- Acceso a datos de asistencia
---
## ✅ Criterios de Aceptación
### AC-001: Métodos de Registro Funcionando
- [ ] Biométrico (huella) funciona en dispositivos compatibles
- [ ] Biométrico (facial) funciona como alternativa
- [ ] QR Code scanner funciona correctamente
- [ ] Registro manual con foto obligatoria funciona
### AC-002: Validaciones Automáticas Activas
- [ ] Validación GPS detecta empleado fuera de radio
- [ ] Validación de horario detecta registros fuera de jornada
- [ ] Validación de estado bloquea empleados inactivos
- [ ] Validación de duplicados previene doble check-in
### AC-003: Modo Offline Funcional
- [ ] App registra asistencias sin conexión
- [ ] Cola local almacena hasta 500 registros
- [ ] Sincronización automática al reconectar
- [ ] Indicador visual de registros pendientes
### AC-004: Seguridad y Privacidad
- [ ] Templates biométricos están encriptados
- [ ] Consentimiento de empleado se obtiene antes de enrollment
- [ ] Datos se eliminan según política de retención
- [ ] Auditoría completa de accesos
### AC-005: Reportes Disponibles
- [ ] Reporte diario de asistencias
- [ ] Reporte mensual para nómina
- [ ] Exportación Excel funcional
- [ ] Log de auditoría completo
---
## 🧪 Testing
### Test Case 1: Registro con huella dactilar exitoso
```typescript
test('Should register attendance with fingerprint', async () => {
const employee = await createEmployee();
await enrollFingerprint(employee.id);
const attendance = await registerAttendance({
employeeId: employee.id,
projectId: 'project-123',
method: 'biometric_fingerprint',
gps: { lat: 19.4326, lon: -99.1332 },
fingerprintTemplate: 'encrypted_template_hash'
});
expect(attendance.status).toBe('success');
expect(attendance.method).toBe('biometric_fingerprint');
expect(attendance.validations.gps).toBe('PASS');
});
```
### Test Case 2: Validación GPS falla (fuera de radio)
```typescript
test('Should warn when GPS is outside radius', async () => {
const employee = await createEmployee();
const project = await createProject({ gps: { lat: 19.4326, lon: -99.1332 }, radius: 100 });
const result = await registerAttendance({
employeeId: employee.id,
projectId: project.id,
gps: { lat: 19.5000, lon: -99.2000 } // ~10km away
});
expect(result.validations.gps).toBe('WARNING');
expect(result.warnings).toContain('GPS outside work radius');
});
```
### Test Case 3: Modo offline sincroniza correctamente
```typescript
test('Should sync offline records when back online', async () => {
await goOffline();
const attendance1 = await registerAttendanceOffline({ employeeId: 'emp-1' });
const attendance2 = await registerAttendanceOffline({ employeeId: 'emp-2' });
expect(await getPendingSyncCount()).toBe(2);
await goOnline();
await syncPendingAttendances();
expect(await getPendingSyncCount()).toBe(0);
expect(await getServerAttendanceCount()).toBe(2);
});
```
---
## 📚 Referencias Adicionales
### Documentos Relacionados
- 📄 [RF-HR-001: Empleados y Cuadrillas](./RF-HR-001-empleados-cuadrillas.md)
- 📄 [RF-HR-003: Costeo de Mano de Obra](./RF-HR-003-costeo-mano-obra.md)
- 📐 [ET-HR-002: Implementación Biométrica](../especificaciones/ET-HR-002-asistencia-biometrica.md)
- 📱 [US-HR-002: App Móvil de Asistencia](../historias-usuario/US-HR-002-asistencia-biometrica-app.md)
### Estándares y Regulaciones
- [LFPDPPP: Ley Federal de Protección de Datos Personales](https://www.diputados.gob.mx/LeyesBiblio/pdf/LFPDPPP.pdf)
- [ISO/IEC 24745: Biometric Information Protection](https://www.iso.org/standard/52946.html)
- [NIST: Biometric Standards](https://www.nist.gov/programs-projects/biometric-standards)
---
## 📅 Historial de Cambios
| Versión | Fecha | Autor | Cambios |
|---------|-------|-------|---------|
| 1.0 | 2025-11-17 | Tech Lead | Creación inicial - Nueva funcionalidad |
---
**Documento:** `docs/01-fase-alcance-inicial/MAI-007-rrhh-asistencias/requerimientos/RF-HR-002-asistencia-biometrica.md`
**Ruta relativa:** `MAI-007-rrhh-asistencias/requerimientos/RF-HR-002-asistencia-biometrica.md`
**Épica:** MAI-006 (RRHH, Asistencias y Nómina)
**Sprint:** Sprint 9-10 (Semanas 13.5-16)
**Prioridad:** P0 - Crítica