27 KiB
US-HR-004: Integración con Nómina Externa
Epic: MAI-007 - RRHH, Asistencias y Nómina RF: No aplica (funcionalidad de integración) ET: No aplica Tipo: Historia de Usuario Prioridad: Media Story Points: 8 Sprint: 10-11 Estado: 📋 Pendiente Última actualización: 2025-11-17
📖 Historia de Usuario
Como Gerente de RRHH o Contador Quiero exportar automáticamente asistencias, incidencias y nómina a mi sistema de nómina externo (CONTPAQi, Aspel NOI, Excel) Para procesar la nómina sin captura manual, reducir errores y tener trazabilidad completa entre asistencia y pago
🎯 Criterios de Aceptación
CA-1: Configuración de Sistema de Nómina 🔧
Dado que soy Gerente de RRHH con permisos de configuración Cuando accedo a "Configuración" > "Integración de Nómina" Entonces puedo:
-
Seleccionar Tipo de Sistema:
- Opciones disponibles:
- ☐ CONTPAQi Nóminas
- ☐ Aspel NOI
- ☐ Tress Nomipaq
- ☐ Excel personalizado
- ☐ API REST personalizada
- ☐ CSV genérico
- Seleccionar uno
- Opciones disponibles:
-
Configurar Mapeo de Campos:
- Ver tabla de mapeo de campos:
Campo Interno Campo Externo Transformación employeeCode NUMERO_EMPLEADO Ninguna curp CURP Ninguna nss NSS Ninguna fullName NOMBRE_COMPLETO Concatenar currentSalary SALARIO_DIARIO Ninguna daysWorked DIAS_TRABAJADOS Suma mensual - Poder editar nombres de campos externos
- Poder agregar campos personalizados
-
Configurar Formato de Exportación:
- Si Excel:
- Seleccionar template (.xlsx) con formato deseado
- Definir hoja donde se escribirán datos
- Fila de inicio: 5 (ejemplo)
- Si CSV:
- Delimitador: coma, punto y coma, tab
- Encoding: UTF-8, Latin1
- Incluir headers: Sí/No
- Si API REST:
- URL del endpoint:
https://api.nomina.example.com/v1/attendance - Método: POST
- Headers personalizados (API Key, etc.)
- Formato del body: JSON, XML
- URL del endpoint:
- Si Excel:
-
Programar Exportación Automática:
- Frecuencia:
- ☐ Diaria (lunes a viernes a las 6 PM)
- ☐ Semanal (viernes a las 6 PM)
- ☐ Quincenal (días 15 y último del mes)
- ☐ Mensual (último día del mes)
- Email de notificación al completar
- Email de alerta si falla
- Frecuencia:
Y guardar configuración con validación completa
CA-2: Exportación Manual de Asistencias 📤
Dado que soy Gerente de RRHH Cuando accedo a "RRHH" > "Exportar a Nómina" Entonces puedo:
-
Seleccionar Periodo:
- Fecha inicio: 2025-11-01
- Fecha fin: 2025-11-15
- O seleccionar: "Última quincena", "Último mes"
- Mostrar preview: "Del 1 al 15 de noviembre (15 días)"
-
Seleccionar Empleados:
- Opción: "Todos los empleados activos" (default)
- O filtrar por:
- Obra específica
- Cuadrilla específica
- Lista personalizada (checkboxes)
- Mostrar contador: "52 empleados seleccionados"
-
Vista Previa de Datos:
- Ver tabla con datos que se exportarán:
Empleado NSS Días Trabajados Faltas Incap. Salario Total Juan Pérez 12345678901 13 2 0 $500 $6,500 María López 98765432109 15 0 0 $450 $6,750 ... ... ... ... ... ... ... - Paginación: 20 empleados por página
- Totales en footer:
- Total empleados: 52
- Total días trabajados: 742
- Total neto estimado: $371,000
-
Validaciones Previas:
- ✅ Todos los empleados tienen NSS válido
- ✅ No hay asistencias pendientes de aprobar
- ⚠️ 3 empleados con advertencias GPS
- ❌ 1 empleado sin CURP registrado
Si hay errores críticos (❌):
- No permitir exportación
- Mostrar lista de empleados con problemas
- Botón: "Ir a corregir datos"
-
Exportar:
- Botón: "Exportar a [CONTPAQi]"
- Spinner: "Generando archivo..."
- Descarga automática del archivo:
- Nombre:
Nomina_2025-11-01_2025-11-15_52empleados.xlsx - Tamaño: ~150 KB
- Nombre:
- Toast: "✓ Archivo exportado correctamente"
Y registrar log de exportación con usuario, fecha y periodo
CA-3: Formato de Exportación para CONTPAQi 📊
Dado que seleccioné CONTPAQi como sistema de nómina Cuando exporto asistencias Entonces el archivo Excel generado debe tener:
-
Estructura de Archivo:
Hoja: ASISTENCIAS Fila 1: [Logo] Fila 2: Reporte de Asistencias Fila 3: Periodo: 01/11/2025 - 15/11/2025 Fila 4: [Vacío] Fila 5: [Headers] Fila 6+: [Datos] -
Columnas Requeridas:
- A: NUMERO_EMPLEADO (texto)
- B: NOMBRE_COMPLETO (texto)
- C: NSS (texto, 11 dígitos)
- D: CURP (texto, 18 caracteres)
- E: RFC (texto, 13 caracteres)
- F: DIAS_TRABAJADOS (número, 2 decimales)
- G: FALTAS (número entero)
- H: INCAPACIDADES (número entero)
- I: SALARIO_DIARIO (moneda, 2 decimales)
- J: TOTAL_PERCIBIDO (fórmula: =F*I)
-
Formato de Celdas:
- Headers (fila 5): Negrita, fondo azul, texto blanco
- Datos: Arial 10, alineación izquierda para texto, derecha para números
- Columnas de moneda: formato $#,##0.00
-
Validaciones de Integridad:
- NSS: 11 dígitos exactos, sin guiones
- CURP: 18 caracteres, mayúsculas
- DIAS_TRABAJADOS: Max 15 en quincena, 31 en mes
- SALARIO_DIARIO: Min $248.93 (salario mínimo 2025)
CA-4: Formato CSV Genérico 📄
Dado que seleccioné CSV genérico Cuando exporto Entonces el archivo CSV debe:
-
Estructura:
EMPLEADO_CODIGO,NOMBRE,NSS,CURP,DIAS_TRAB,FALTAS,SALARIO,TOTAL EMP-00001,"Pérez García Juan",12345678901,BADD110313HCMLNS09,13,2,500.00,6500.00 EMP-00002,"López Martínez María",98765432109,LOMM900515MDFPRD08,15,0,450.00,6750.00 -
Configuración:
- Delimitador: Configurable (coma por defecto)
- Encoding: UTF-8 con BOM (para Excel)
- Línea de headers: Sí (configurable)
- Comillas para texto: Sí (para nombres con comas)
-
Manejo de Caracteres Especiales:
- Acentos: Preservados con UTF-8
- Ñ: Preservada
- Comas en nombres: Encerrar en comillas dobles
CA-5: Integración API REST 🔌
Dado que seleccioné API REST personalizada Cuando se ejecuta la exportación (manual o automática) Entonces el sistema debe:
-
Preparar Payload JSON:
{ "period": { "startDate": "2025-11-01", "endDate": "2025-11-15" }, "company": { "id": "constructora-uuid", "name": "Constructora ABC", "rfc": "CABC850101XYZ" }, "employees": [ { "employeeCode": "EMP-00001", "fullName": "Juan Pérez García", "nss": "12345678901", "curp": "BADD110313HCMLNS09", "rfc": "BADD110313AB1", "daysWorked": 13, "absences": 2, "incapacities": 0, "dailySalary": 500.00, "totalGross": 6500.00 } ], "summary": { "totalEmployees": 52, "totalDays": 742, "totalGross": 371000.00 } } -
Enviar Request HTTP:
const response = await axios.post( config.payrollApiUrl, payload, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.apiKey}`, 'X-Company-ID': constructoraId, }, timeout: 30000, // 30 segundos } ); -
Manejo de Respuestas:
- 200 OK:
- Mensaje: "✓ Datos enviados correctamente a sistema de nómina"
- Guardar
response.data.batchIdpara trazabilidad
- 400 Bad Request:
- Mostrar errores de validación del sistema externo
- Ejemplo: "Empleado NSS 12345678901 no existe en sistema de nómina"
- 401 Unauthorized:
- Error: "API Key inválida, contacta al administrador"
- 500 Server Error:
- Reintentar automáticamente 3 veces con delay (1s, 2s, 4s)
- Si falla: enviar email de alerta y mostrar error
- 200 OK:
-
Logs de Integración:
- Registrar cada llamada API:
- Timestamp
- URL
- Payload (resumen, no datos sensibles)
- Response status
- Tiempo de respuesta
- Retención: 90 días
- Registrar cada llamada API:
CA-6: Exportación Automática Programada ⏰
Dado que configuré exportación automática quincenal Cuando llega la fecha programada (día 15 a las 6 PM) Entonces el sistema debe:
-
Ejecutar Exportación Automáticamente:
- Detectar que es día 15 del mes
- A las 18:00 hrs ejecutar job
- Calcular periodo automáticamente:
- Si día 15: del 1 al 15
- Si último día del mes: del 16 al último día
-
Validar Datos:
- Verificar que no hay asistencias pendientes de aprobar
- Verificar que todos los empleados tienen datos completos
- Si hay problemas críticos:
- NO exportar
- Enviar email a RRHH con lista de problemas
- Programar reintento para mañana
-
Generar y Enviar:
- Generar archivo según configuración
- Si es archivo (Excel/CSV):
- Enviar por email a RRHH con archivo adjunto
- Si es API:
- Enviar request
- Guardar archivo en servidor en:
/storage/payroll-exports/2025/11/nomina_20251115.xlsx
-
Notificar Resultado:
-
Email a RRHH:
Asunto: ✓ Exportación automática de nómina completada Se exportaron 52 empleados del periodo 01/11/2025 - 15/11/2025 Resumen: - Total días trabajados: 742 - Total neto estimado: $371,000 - Archivo adjunto: nomina_20251115.xlsx Generado automáticamente el 15/11/2025 a las 18:05 hrs -
Si falla:
Asunto: ❌ Error en exportación automática de nómina No se pudo completar la exportación. Errores: - 3 empleados sin NSS registrado - 5 asistencias pendientes de aprobar Por favor, corrige estos problemas y ejecuta la exportación manualmente.
-
CA-7: Incluir Incidencias en Exportación 📝
Dado que hay incidencias registradas (faltas, incapacidades, permisos) Cuando exporto asistencias Entonces deben incluirse:
-
Tipos de Incidencias:
- Falta: Día laboral sin asistencia (injustificada)
- Falta Justificada: Con documento de respaldo
- Incapacidad: Por enfermedad (IMSS)
- Permiso: Autorizado por supervisor
- Vacaciones: Días de descanso programados
- Día Festivo: Día no laboral pagado
-
Cálculo de Incidencias:
Periodo: 15 días laborales - Asistencias: 13 días - Faltas: 2 días - Incapacidades: 0 días - Permisos: 0 días Total verificado: 13 + 2 = 15 ✓ -
Columnas en Exportación:
- DIAS_TRABAJADOS: 13
- FALTAS: 2
- INCAPACIDADES: 0
- PERMISOS: 0
- VACACIONES: 0
-
Validación de Consistencia:
- Suma de incidencias debe = días del periodo
- Si no coincide: marcar con warning
- Permitir ajuste manual antes de exportar
CA-8: Importación de Resultados de Nómina (Opcional) 📥
Dado que la nómina fue procesada en el sistema externo Cuando importo los resultados de vuelta Entonces puedo:
-
Subir Archivo de Resultados:
- Formato: Excel o CSV
- Columnas esperadas:
- EMPLEADO_CODIGO
- TOTAL_PERCEPCIONES
- TOTAL_DEDUCCIONES
- NETO_A_PAGAR
- FECHA_PAGO
-
Mapear Datos:
- Sistema valida que cada empleado existe
- Valida que periodo coincide
- Crea registros en tabla
payroll_results:{ "employeeId": "uuid", "period": "2025-11-01_2025-11-15", "totalPerceptions": 6500.00, "totalDeductions": 1235.00, "netPay": 5265.00, "paymentDate": "2025-11-16" }
-
Visualizar en Empleado:
- En detalle del empleado, nueva sección: "Historial de Nómina"
- Mostrar tabla con pagos pasados
- Solo lectura (no editable)
CA-9: Permisos por Rol 🔐
Roles y Permisos:
| Acción | Director | Engineer | Resident | HR | Finance |
|---|---|---|---|---|---|
| Configurar integración | ✅ | ❌ | ❌ | ✅ | ❌ |
| Exportar manualmente | ✅ | ❌ | ❌ | ✅ | ✅ |
| Ver exportaciones pasadas | ✅ | ❌ | ❌ | ✅ | ✅ |
| Descargar archivos | ✅ | ❌ | ❌ | ✅ | ✅ |
| Importar resultados | ✅ | ❌ | ❌ | ✅ | ✅ |
🔧 Detalles Técnicos
Servicio de Exportación
// payroll-export.service.ts
import { Injectable } from '@nestjs/common';
import * as ExcelJS from 'exceljs';
import { createObjectCsvStringifier } from 'csv-writer';
@Injectable()
export class PayrollExportService {
/**
* Exportar asistencias a Excel (CONTPAQi format)
*/
async exportToExcel(
period: { startDate: Date; endDate: Date },
employeeIds: string[]
): Promise<Buffer> {
// 1. Obtener datos de asistencias
const data = await this.getAttendanceData(period, employeeIds);
// 2. Crear workbook
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('ASISTENCIAS');
// 3. Header
worksheet.getCell('A1').value = 'Reporte de Asistencias';
worksheet.getCell('A1').font = { bold: true, size: 14 };
worksheet.getCell('A2').value = `Periodo: ${this.formatDate(period.startDate)} - ${this.formatDate(period.endDate)}`;
// 4. Column headers (row 5)
const headers = [
'NUMERO_EMPLEADO',
'NOMBRE_COMPLETO',
'NSS',
'CURP',
'RFC',
'DIAS_TRABAJADOS',
'FALTAS',
'INCAPACIDADES',
'SALARIO_DIARIO',
'TOTAL_PERCIBIDO',
];
worksheet.getRow(5).values = headers;
worksheet.getRow(5).font = { bold: true };
worksheet.getRow(5).fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: '4472C4' },
};
worksheet.getRow(5).font = { color: { argb: 'FFFFFF' }, bold: true };
// 5. Data rows
let rowIndex = 6;
for (const employee of data) {
worksheet.getRow(rowIndex).values = [
employee.employeeCode,
employee.fullName,
employee.nss,
employee.curp,
employee.rfc,
employee.daysWorked,
employee.absences,
employee.incapacities,
employee.dailySalary,
{ formula: `F${rowIndex}*I${rowIndex}` }, // Total
];
// Format salary columns
worksheet.getCell(`I${rowIndex}`).numFmt = '$#,##0.00';
worksheet.getCell(`J${rowIndex}`).numFmt = '$#,##0.00';
rowIndex++;
}
// 6. Auto-fit columns
worksheet.columns.forEach(column => {
column.width = 15;
});
// 7. Return buffer
const buffer = await workbook.xlsx.writeBuffer();
return Buffer.from(buffer);
}
/**
* Exportar a CSV
*/
async exportToCSV(
period: { startDate: Date; endDate: Date },
employeeIds: string[],
config: CSVConfig
): Promise<string> {
const data = await this.getAttendanceData(period, employeeIds);
const csvStringifier = createObjectCsvStringifier({
header: [
{ id: 'employeeCode', title: 'EMPLEADO_CODIGO' },
{ id: 'fullName', title: 'NOMBRE' },
{ id: 'nss', title: 'NSS' },
{ id: 'curp', title: 'CURP' },
{ id: 'daysWorked', title: 'DIAS_TRAB' },
{ id: 'absences', title: 'FALTAS' },
{ id: 'dailySalary', title: 'SALARIO' },
{ id: 'total', title: 'TOTAL' },
],
});
const records = data.map(emp => ({
...emp,
total: emp.daysWorked * emp.dailySalary,
}));
const csv = csvStringifier.getHeaderString() + csvStringifier.stringifyRecords(records);
return csv;
}
/**
* Enviar a API externa
*/
async sendToAPI(
period: { startDate: Date; endDate: Date },
employeeIds: string[],
apiConfig: APIConfig
): Promise<any> {
const data = await this.getAttendanceData(period, employeeIds);
const payload = {
period: {
startDate: period.startDate.toISOString().split('T')[0],
endDate: period.endDate.toISOString().split('T')[0],
},
employees: data.map(emp => ({
employeeCode: emp.employeeCode,
fullName: emp.fullName,
nss: emp.nss,
curp: emp.curp,
rfc: emp.rfc,
daysWorked: emp.daysWorked,
absences: emp.absences,
incapacities: emp.incapacities,
dailySalary: emp.dailySalary,
totalGross: emp.daysWorked * emp.dailySalary,
})),
};
const response = await axios.post(apiConfig.url, payload, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiConfig.apiKey}`,
},
timeout: 30000,
});
// Log the integration
await this.logIntegration({
type: 'api_export',
status: response.status,
responseTime: response.duration,
recordCount: data.length,
});
return response.data;
}
/**
* Obtener datos de asistencias con incidencias
*/
private async getAttendanceData(
period: { startDate: Date; endDate: Date },
employeeIds: string[]
) {
// Query complejo que une asistencias + incidencias
const result = await this.db.query(`
WITH attendance_summary AS (
SELECT
e.id as employee_id,
e.employee_code,
CONCAT(e.first_name, ' ', e.last_name) as full_name,
e.nss,
e.curp,
e.rfc,
e.current_salary as daily_salary,
COALESCE(SUM(lc.days_worked), 0) as days_worked
FROM hr.employees e
LEFT JOIN hr.labor_costs lc ON lc.employee_id = e.id
AND lc.work_date >= $1
AND lc.work_date <= $2
WHERE e.id = ANY($3)
GROUP BY e.id
),
incidences_summary AS (
SELECT
employee_id,
COUNT(*) FILTER (WHERE type = 'absence') as absences,
COUNT(*) FILTER (WHERE type = 'incapacity') as incapacities,
COUNT(*) FILTER (WHERE type = 'permission') as permissions
FROM hr.incidences
WHERE incidence_date >= $1 AND incidence_date <= $2
GROUP BY employee_id
)
SELECT
a.*,
COALESCE(i.absences, 0) as absences,
COALESCE(i.incapacities, 0) as incapacities,
COALESCE(i.permissions, 0) as permissions
FROM attendance_summary a
LEFT JOIN incidences_summary i ON i.employee_id = a.employee_id
ORDER BY a.full_name
`, [period.startDate, period.endDate, employeeIds]);
return result.rows;
}
}
Job Programado
// payroll-export.cron.ts
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { PayrollExportService } from './payroll-export.service';
@Injectable()
export class PayrollExportCron {
constructor(
private payrollExportService: PayrollExportService,
private emailService: EmailService,
) {}
/**
* Ejecutar exportación automática quincenalmente
* Día 15 y último día del mes a las 6 PM
*/
@Cron('0 18 15,L * *', { // L = último día del mes
name: 'payroll-export-automatic',
timeZone: 'America/Mexico_City',
})
async handleAutomaticExport() {
console.log('🕐 Iniciando exportación automática de nómina...');
const today = new Date();
const period = this.calculatePeriod(today);
try {
// 1. Validar que no hay problemas
const validation = await this.validateExportReady(period);
if (!validation.isValid) {
await this.emailService.send({
to: 'rrhh@constructora.com',
subject: '❌ Exportación automática de nómina: Errores detectados',
body: this.buildErrorEmail(validation.errors),
});
return;
}
// 2. Exportar
const buffer = await this.payrollExportService.exportToExcel(
period,
validation.employeeIds
);
// 3. Guardar archivo
const filename = `nomina_${this.formatDate(today)}.xlsx`;
await this.storageService.save(`payroll-exports/${filename}`, buffer);
// 4. Enviar email con archivo
await this.emailService.send({
to: 'rrhh@constructora.com',
subject: '✓ Exportación automática de nómina completada',
body: this.buildSuccessEmail(validation.summary),
attachments: [
{
filename,
content: buffer,
},
],
});
console.log('✓ Exportación automática completada');
} catch (error) {
console.error('Error en exportación automática:', error);
await this.emailService.send({
to: 'rrhh@constructora.com',
subject: '❌ Error en exportación automática de nómina',
body: `Error técnico: ${error.message}`,
});
}
}
private calculatePeriod(date: Date): { startDate: Date; endDate: Date } {
const day = date.getDate();
const year = date.getFullYear();
const month = date.getMonth();
if (day === 15) {
// Primera quincena: del 1 al 15
return {
startDate: new Date(year, month, 1),
endDate: new Date(year, month, 15),
};
} else {
// Segunda quincena: del 16 al último día
return {
startDate: new Date(year, month, 16),
endDate: new Date(year, month + 1, 0), // Último día del mes
};
}
}
}
🧪 Casos de Prueba
TC-PAY-001: Exportación Manual a Excel ✅
Precondiciones:
- 52 empleados activos
- Periodo: 01/11/2025 - 15/11/2025
- Configuración: CONTPAQi Excel
Pasos:
- Ir a "RRHH" > "Exportar a Nómina"
- Seleccionar periodo 01/11 - 15/11
- Seleccionar "Todos los empleados activos"
- Hacer clic en "Exportar"
Resultado esperado:
- Archivo descargado:
Nomina_2025-11-01_2025-11-15_52empleados.xlsx - Tamaño: ~150 KB
- Al abrir archivo:
- Hoja "ASISTENCIAS" existe
- Fila 5 tiene headers correcto
- Fila 6 tiene primer empleado
- 52 empleados en total (filas 6-57)
- Columnas formateadas correctamente
- Totales calculados con fórmulas
TC-PAY-002: Validación Antes de Exportar ❌
Precondiciones:
- 3 empleados sin NSS registrado
- 5 asistencias pendientes de aprobar
Pasos:
- Intentar exportar nómina
Resultado esperado:
- Validación falla
- Mensaje de error:
❌ No se puede exportar Problemas encontrados: - 3 empleados sin NSS: • Juan López (EMP-00045) • María García (EMP-00051) • Carlos Ruiz (EMP-00052) - 5 asistencias pendientes de aprobar Por favor, corrige estos problemas antes de exportar. - Botón "Ir a corregir" visible
- Botón "Exportar" deshabilitado
TC-PAY-003: Exportación a CSV ✅
Precondiciones:
- Configuración: CSV genérico
- Delimitador: coma
- Encoding: UTF-8
Pasos:
- Exportar nómina a CSV
Resultado esperado:
- Archivo:
Nomina_2025-11-01_2025-11-15.csv - Contenido:
EMPLEADO_CODIGO,NOMBRE,NSS,CURP,DIAS_TRAB,FALTAS,SALARIO,TOTAL EMP-00001,"Pérez García Juan",12345678901,BADD110313HCMLNS09,13,2,500.00,6500.00 EMP-00002,"López Martínez María",98765432109,LOMM900515MDFPRD08,15,0,450.00,6750.00 - Acentos preservados correctamente
- Comillas en nombres con comas
TC-PAY-004: Integración API REST ✅
Precondiciones:
- Configuración API:
- URL:
https://api.nomina.test.com/v1/attendance - API Key: válida
- URL:
- Mock server respondiendo 200 OK
Pasos:
- Exportar usando API
Resultado esperado:
- Request enviado a API con payload JSON correcto
- Response 200 OK recibida
batchIdguardado:BATCH-2025-11-15-001- Toast: "✓ Datos enviados correctamente"
- Log de integración registrado:
- Timestamp: 2025-11-15 18:05:32
- Status: 200
- Response time: 1.2s
- Records: 52
TC-PAY-005: Exportación Automática Quincenal ⏰
Precondiciones:
- Fecha: 15 de noviembre a las 6 PM
- Configuración automática activa
- 52 empleados con datos completos
Pasos:
- Cron job se ejecuta automáticamente
Resultado esperado:
- Exportación ejecutada
- Archivo generado y guardado en:
/storage/payroll-exports/2025/11/nomina_20251115.xlsx - Email enviado a RRHH:
- Asunto: "✓ Exportación automática completada"
- Archivo adjunto
- Resumen de 52 empleados, 742 días, $371,000
- Log registrado
TC-PAY-006: Fallo en Exportación Automática ❌
Precondiciones:
- Fecha: 15 de noviembre
- 3 empleados sin datos completos
Pasos:
- Cron job se ejecuta
Resultado esperado:
- Validación detecta problemas
- Exportación NO se ejecuta
- Email de alerta enviado:
- Asunto: "❌ Error en exportación automática"
- Lista de problemas
- Instrucciones para corrección manual
- Reintento programado para mañana
TC-PAY-007: Incluir Incidencias ✅
Precondiciones:
- Empleado trabajó 13 días
- 2 faltas registradas
- 0 incapacidades
Pasos:
- Exportar nómina
Resultado esperado:
- Fila del empleado muestra:
- DIAS_TRABAJADOS: 13
- FALTAS: 2
- INCAPACIDADES: 0
- Suma: 13 + 2 = 15 (total de días del periodo) ✓
📦 Dependencias
Dependencias de Otros US
- ✅ US-HR-001: Empleados con datos completos (NSS, CURP, RFC)
- ⏳ US-HR-002: Asistencias registradas
- ⏳ US-HR-003: Cálculo de días trabajados
Librerías Backend
{
"exceljs": "^4.4.0",
"csv-writer": "^1.6.0",
"@nestjs/schedule": "^4.0.0",
"axios": "^1.6.2"
}
⚠️ Riesgos
R-1: Cambios de Formato del Sistema Externo
Descripción: Sistema de nómina cambia estructura sin aviso Impacto: Alto Probabilidad: Media Mitigación:
- Versionado de templates
- Alertas automáticas si validación falla
- Documentar formato esperado
📊 Métricas de Éxito
Métricas de Negocio:
- ✅ 95% de exportaciones exitosas en primer intento
- ✅ Reducción de 80% en tiempo de captura manual
- ✅ 0 errores de captura vs método manual
Métricas Técnicas:
- ✅ Exportación de 100 empleados en < 10 segundos
- ✅ 100% de exportaciones automáticas ejecutadas a tiempo
- ✅ API response time < 5 segundos
📋 Checklist de Implementación
Backend
- Crear PayrollExportService con métodos de exportación
- Implementar generación de Excel con ExcelJS
- Implementar generación de CSV
- Implementar integración API REST
- Crear PayrollExportCron para ejecución automática
- Crear endpoints de configuración
- Implementar validaciones pre-exportación
- Implementar logs de integración
- Crear EmailService para notificaciones
Frontend
- Crear página PayrollExportConfig
- Crear componente ExportWizard
- Crear tabla de preview de datos
- Implementar validaciones visuales
- Crear historial de exportaciones
- Implementar descargas de archivos
Testing
- Tests de generación Excel
- Tests de generación CSV
- Tests de API integration
- Tests de cron job
- Tests de validaciones
Fecha de creación: 2025-11-17 Versión: 1.0