555 lines
16 KiB
Markdown
555 lines
16 KiB
Markdown
# 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
|