erp-construccion/docs/02-definicion-modulos/MAI-007-rrhh-asistencias/historias-usuario/US-HR-006-reportes-asistencia.md

22 KiB
Raw Permalink Blame History

US-HR-006: Reportes de Asistencia

Epic: MAI-007 - RRHH, Asistencias y Nómina RF: No aplica (funcionalidad de reportes) ET: No aplica Tipo: Historia de Usuario Prioridad: Media Story Points: 5 Sprint: 11 Estado: 📋 Pendiente Última actualización: 2025-11-17


📖 Historia de Usuario

Como Gerente de RRHH, Residente de Obra o Director Quiero generar reportes detallados de asistencia con filtros y exportación Para analizar patrones de ausentismo, detectar problemas de puntualidad, tomar decisiones informadas sobre el personal y generar reportes para auditorías


🎯 Criterios de Aceptación

CA-1: Reporte Diario de Asistencia 📅

Dado que soy Residente de Obra o Gerente de RRHH Cuando accedo a "Reportes" > "Asistencia Diaria" Entonces puedo:

  1. Seleccionar Filtros:

    • Fecha: 2025-11-17 (default: hoy)
    • Obra: "Casa Modelo Norte" o "Todas las obras"
    • Cuadrilla: "Albañilería A" o "Todas las cuadrillas"
  2. Ver Resumen del Día:

    ┌─────────────────────────────────────┐
    │ Resumen de Asistencia - 17/11/2025  │
    ├─────────────────────────────────────┤
    │ ✅ Presentes: 45 (86%)              │
    │ ❌ Ausentes: 7 (14%)                │
    │ ⏰ Retardos: 5 (11% de presentes)   │
    │ 🏥 Incapacidades: 0                 │
    │ 📝 Permisos: 0                      │
    │ Total empleados: 52                 │
    └─────────────────────────────────────┘
    
  3. Ver Tabla Detallada:

    Empleado Cuadrilla Check-In Check-Out Horas Estado GPS
    Juan Pérez Albañilería A 07:15 17:30 10:15 Presente
    María López Albañilería A 07:45 17:35 09:50 Retardo
    Carlos Ruiz Electricidad - - 0:00 Ausente -
  4. Indicadores Visuales:

    • Verde : Presente y puntual (check-in antes de 7:30 AM)
    • Amarillo : Retardo (check-in después de 7:30 AM)
    • Rojo : Ausente (sin check-in)
    • Azul 🏥: Incapacidad médica
    • Morado 📝: Permiso autorizado
  5. Acciones:

    • Exportar a PDF: "Asistencia_17Nov2025.pdf"
    • Exportar a Excel: "Asistencia_17Nov2025.xlsx"
    • Enviar por email
    • Imprimir

Y el reporte debe actualizarse en tiempo real conforme se registran asistencias

CA-2: Reporte Semanal de Asistencia 📊

Dado que necesito analizar una semana completa Cuando selecciono "Reporte Semanal" Entonces veo:

  1. Seleccionar Semana:

    • Semana del: 13/11/2025 al 17/11/2025 (L-V)
    • Selector de semana con navegación ← →
  2. Vista de Tabla Resumen:

    Empleado Lun Mar Mié Jue Vie Total % Asist.
    Juan Pérez 5/5 100%
    María López 4/5 80%
    Carlos Ruiz 🏥 🏥 🏥 2/5 40%
  3. Gráfica de Asistencia Semanal:

    • Gráfica de barras por día:
      50 ┤     ██
      40 ┤     ██  ██
      30 ┤  ██ ██  ██  ██
      20 ┤  ██ ██  ██  ██  ██
      10 ┤  ██ ██  ██  ██  ██
       0 ┴──────────────────────
          Lun Mar Mié Jue Vie
      
    • Leyenda: Verde (presentes), Amarillo (retardos), Rojo (ausencias)
  4. Top 5 Ausencias:

    • Lista de empleados con más ausencias en la semana
    • Sugerencia de seguimiento

CA-3: Reporte Mensual de Asistencia 📆

Dado que necesito un análisis mensual Cuando selecciono "Reporte Mensual" Entonces veo:

  1. Seleccionar Mes:

    • Mes: Noviembre 2025
    • Días laborales: 21 (excluyendo sábados y domingos)
  2. Resumen Mensual:

    ┌──────────────────────────────────────┐
    │ Noviembre 2025 - Asistencia General  │
    ├──────────────────────────────────────┤
    │ Promedio de asistencia diaria: 89%   │
    │ Total días-hombre: 978               │
    │ Total ausencias: 124                 │
    │ Total retardos: 87                   │
    │ Día con más ausencias: 15/11 (12)    │
    │ Día con más retardos: 18/11 (9)      │
    └──────────────────────────────────────┘
    
  3. Tabla por Empleado:

    Empleado Días Lab. Presentes Ausencias Retardos % Asist. Estatus
    Juan Pérez 21 21 0 1 100% 🟢 Excelente
    María López 21 18 3 5 85.7% 🟡 Regular
    Carlos Ruiz 21 15 6 2 71.4% 🔴 Atención
  4. Clasificación de Estatus:

    • 🟢 Excelente: ≥ 95% asistencia
    • 🟡 Regular: 80-94% asistencia
    • 🔴 Atención requerida: < 80% asistencia
  5. Gráfica de Tendencia:

    • Línea de % de asistencia por día del mes
    • Identificar patrones (ej: lunes con más ausencias)

CA-4: Reporte de Ausentismo 📉

Dado que necesito analizar patrones de ausentismo Cuando accedo a "Reporte de Ausentismo" Entonces puedo:

  1. Ver Estadísticas Generales:

    • Tasa de ausentismo: 11.2%
    • Promedio de industria: 8-10% (benchmark)
    • Tendencia: ↑ +2.5% vs mes anterior
  2. Ausentismo por Tipo:

    Faltas injustificadas: 45% (56 días)
    Incapacidades médicas: 35% (44 días)
    Permisos autorizados: 15% (19 días)
    Faltas justificadas: 5% (6 días)
    
  3. Ausentismo por Día de la Semana:

    • Gráfica de pastel o barras:
      • Lunes: 35% (mayor ausentismo)
      • Viernes: 25%
      • Martes: 15%
      • Miércoles: 13%
      • Jueves: 12%
  4. Empleados con Mayor Ausentismo:

    Empleado Total Ausencias Último Mes Tendencia
    Carlos Ruiz 12 6 ↑ Incrementando
    Pedro Gómez 9 3 → Estable
    Luis Martínez 7 2 ↓ Mejorando
  5. Acciones Sugeridas:

    • 🔴 Alerta: 3 empleados con > 10 ausencias en 30 días
    • Botón: "Generar plan de seguimiento"
    • Botón: "Agendar reunión con empleado"

CA-5: Reporte de Puntualidad

Dado que necesito monitorear puntualidad Cuando acceso a "Reporte de Puntualidad" Entonces veo:

  1. Configuración de Horarios:

    • Hora de entrada esperada: 7:30 AM (configurable)
    • Tolerancia: 15 minutos (configurable)
    • Retardo > 15 min: Se marca como retardo
  2. Resumen de Puntualidad:

    ┌────────────────────────────────────┐
    │ Puntualidad - Noviembre 2025       │
    ├────────────────────────────────────┤
    │ ✅ Puntuales: 891 (91%)            │
    │ ⏰ Retardos: 87 (9%)               │
    │ Promedio de minutos de retardo: 22 │
    │ Mayor retardo: 1h 15min            │
    └────────────────────────────────────┘
    
  3. Tabla de Retardos:

    Fecha Empleado Esperado Real Minutos Retardo Motivo
    15/11 María López 07:30 07:45 15 Tráfico
    15/11 Juan García 07:30 08:45 75 -
  4. Empleados con Más Retardos:

    Empleado Total Retardos Promedio Min. Estatus
    María López 12 18 min 🟡 Advertencia
    Juan García 8 45 min 🔴 Crítico
  5. Gráfica de Distribución:

    • Histograma de minutos de retardo:
      40 ┤ ██
      30 ┤ ██  ██
      20 ┤ ██  ██  ██
      10 ┤ ██  ██  ██  ██
       0 ┴────────────────────
         0-15 15-30 30-60 >60
         min  min   min  min
      

CA-6: Reporte por Obra 🏗️

Dado que soy Residente de Obra Cuando genero reporte de mi obra específica Entonces veo:

  1. Selección:

    • Obra: Casa Modelo Residencial Norte
    • Periodo: Última semana / Último mes / Personalizado
  2. Dashboard de la Obra:

    ┌────────────────────────────────────┐
    │ Casa Modelo Residencial Norte      │
    ├────────────────────────────────────┤
    │ Empleados asignados: 24            │
    │ Promedio asistencia: 92%           │
    │ Cuadrillas activas: 3              │
    │ Total días-hombre (mes): 504       │
    └────────────────────────────────────┘
    
  3. Asistencia por Cuadrilla:

    Cuadrilla Empleados Presentes Hoy % Asist. Mes
    Albañilería A 10 9 95%
    Electricidad 8 8 88%
    Plomería 6 5 90%
  4. Comparación con Otras Obras:

    • Gráfica de barras comparando % de asistencia
    • Posición: 2° de 5 obras activas

CA-7: Reporte de Incidencias 📝

Dado que necesito rastrear incidencias Cuando accedo a "Reporte de Incidencias" Entonces veo:

  1. Tipos de Incidencias:

    • Incapacidades médicas: 44 (IMSS)
    • Permisos personales: 19
    • Faltas justificadas: 6
    • Accidentes de trabajo: 2
  2. Tabla de Incidencias:

    Fecha Empleado Tipo Días Documentado Aprobado
    10/11 Juan Pérez Incapacidad 3
    12/11 María López Permiso 1
    15/11 Carlos Ruiz Falta Just. 1
  3. Filtros:

    • Por tipo de incidencia
    • Por estado: Pendiente, Aprobado, Rechazado
    • Por fecha
  4. Documentos Adjuntos:

    • Ver/descargar documentos de respaldo (recetas médicas, etc.)

CA-8: Exportación de Reportes 📤

Dado que genero cualquier reporte Cuando hago clic en "Exportar" Entonces puedo:

  1. Seleccionar Formato:

    • 📄 PDF: Para impresión o firma
    • 📊 Excel (.xlsx): Para análisis adicional
    • 📋 CSV: Para importar a otros sistemas
  2. Exportación PDF:

    • Header con logo de la empresa
    • Título del reporte y periodo
    • Tablas con formato profesional
    • Footer con:
      • Fecha de generación
      • Usuario que generó
      • Página X de Y
    • Tamaño: Carta (8.5" × 11")
  3. Exportación Excel:

    • Hoja "Resumen" con métricas
    • Hoja "Detalle" con datos completos
    • Formato de celdas (moneda, porcentajes, fechas)
    • Fórmulas incluidas
    • Filtros automáticos en headers
  4. Envío por Email:

    • Modal: "Enviar Reporte por Email"
    • Destinatarios: rrhh@constructora.com (editable)
    • Asunto: "Reporte de Asistencia - Noviembre 2025"
    • Mensaje personalizable
    • Adjuntar archivo en formato seleccionado

CA-9: Dashboard Ejecutivo 📈

Dado que soy Director Cuando accedo a "Dashboard Ejecutivo de RRHH" Entonces veo:

  1. KPIs Principales:

    ┌───────────────┬───────────────┬───────────────┐
    │ Tasa Asist.   │ Ausentismo    │ Puntualidad   │
    │ 89%           │ 11%           │ 91%           │
    │ ↑ +2% vs ant. │ ↓ -1.5%       │ ↑ +3%         │
    └───────────────┴───────────────┴───────────────┘
    
  2. Gráficas de Tendencia:

    • Línea de asistencia últimos 6 meses
    • Comparación año actual vs año anterior
  3. Top Performers:

    • Obras con mejor asistencia
    • Cuadrillas con mejor puntualidad
    • Empleados destacados (100% asistencia)
  4. Alertas Críticas:

    • 3 empleados con riesgo de deserción (> 15 ausencias)
    • 2 obras con asistencia < 80%
    • 1 cuadrilla con 40% de retardos

CA-10: Permisos por Rol 🔐

Roles y Permisos:

Reporte Director Engineer Resident HR Finance
Reporte Diario
Reporte Semanal/Mensual (solo su obra)
Reporte de Ausentismo
Reporte de Puntualidad (solo su obra)
Dashboard Ejecutivo
Exportar a PDF/Excel

🔧 Detalles Técnicos

Servicio de Reportes

// attendance-reports.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Between } from 'typeorm';

@Injectable()
export class AttendanceReportsService {
  /**
   * Generar reporte diario de asistencia
   */
  async getDailyReport(
    constructoraId: string,
    date: Date,
    filters?: { workId?: string; crewId?: string }
  ) {
    const startOfDay = new Date(date.setHours(0, 0, 0, 0));
    const endOfDay = new Date(date.setHours(23, 59, 59, 999));

    // Query de asistencias del día
    const query = this.attendanceRepo
      .createQueryBuilder('att')
      .leftJoinAndSelect('att.employee', 'emp')
      .leftJoinAndSelect('emp.crewMemberships', 'crew')
      .where('att.workDate = :date', { date: startOfDay })
      .andWhere('emp.constructoraId = :constructoraId', { constructoraId });

    if (filters?.workId) {
      query.andWhere('att.workId = :workId', { workId: filters.workId });
    }

    if (filters?.crewId) {
      query.andWhere('crew.crewId = :crewId', { crewId: filters.crewId });
    }

    const attendances = await query.getMany();

    // Calcular estadísticas
    const total = attendances.length;
    const present = attendances.filter(a => a.type === 'check_in').length;
    const late = attendances.filter(a => {
      if (a.type !== 'check_in') return false;
      const checkInTime = new Date(a.timestamp).getHours() * 60 +
                         new Date(a.timestamp).getMinutes();
      const expectedTime = 7 * 60 + 30; // 7:30 AM
      return checkInTime > expectedTime + 15; // Más de 15 min tarde
    }).length;

    return {
      date: startOfDay,
      summary: {
        total,
        present,
        absent: total - present,
        late,
        presentPercentage: (present / total) * 100,
      },
      details: attendances.map(att => ({
        employeeId: att.employeeId,
        employeeName: att.employee.fullName,
        crewName: att.employee.crewMemberships[0]?.crew?.name,
        checkIn: att.type === 'check_in' ? att.timestamp : null,
        checkOut: att.type === 'check_out' ? att.timestamp : null,
        hoursWorked: this.calculateHours(att),
        status: this.getAttendanceStatus(att),
        gpsValidated: !att.locationWarning,
      })),
    };
  }

  /**
   * Generar reporte mensual
   */
  async getMonthlyReport(
    constructoraId: string,
    month: number,
    year: number
  ) {
    const startDate = new Date(year, month - 1, 1);
    const endDate = new Date(year, month, 0);

    // Query complejo con agregaciones
    const result = await this.db.query(`
      SELECT
        e.id,
        e.employee_code,
        CONCAT(e.first_name, ' ', e.last_name) as full_name,
        COUNT(DISTINCT att.work_date) FILTER (WHERE att.type = 'check_in') as days_present,
        $1 - COUNT(DISTINCT att.work_date) FILTER (WHERE att.type = 'check_in') as days_absent,
        COUNT(*) FILTER (
          WHERE att.type = 'check_in'
          AND EXTRACT(HOUR FROM att.timestamp) * 60 + EXTRACT(MINUTE FROM att.timestamp) > 450
        ) as late_count,
        ROUND(
          COUNT(DISTINCT att.work_date) FILTER (WHERE att.type = 'check_in')::numeric / $1 * 100,
          1
        ) as attendance_percentage
      FROM hr.employees e
      LEFT JOIN hr.attendance_records att ON att.employee_id = e.id
        AND att.work_date >= $2
        AND att.work_date <= $3
      WHERE e.constructora_id = $4
      GROUP BY e.id
      ORDER BY attendance_percentage DESC
    `, [this.getWorkDays(month, year), startDate, endDate, constructoraId]);

    return {
      month,
      year,
      workDays: this.getWorkDays(month, year),
      employees: result.rows,
      summary: {
        averageAttendance: result.rows.reduce((sum, emp) =>
          sum + parseFloat(emp.attendance_percentage), 0) / result.rows.length,
        totalAbsences: result.rows.reduce((sum, emp) =>
          sum + parseInt(emp.days_absent), 0),
        totalLate: result.rows.reduce((sum, emp) =>
          sum + parseInt(emp.late_count), 0),
      },
    };
  }

  private getAttendanceStatus(attendance: AttendanceRecord): string {
    if (!attendance) return 'absent';

    const checkInTime = new Date(attendance.timestamp).getHours() * 60 +
                       new Date(attendance.timestamp).getMinutes();
    const expectedTime = 7 * 60 + 30;

    if (checkInTime <= expectedTime + 15) return 'present';
    return 'late';
  }
}

Generación de PDF

// pdf-generator.service.ts
import { Injectable } from '@nestjs/common';
import * as PDFDocument from 'pdfkit';

@Injectable()
export class PDFGeneratorService {
  async generateAttendanceReport(data: any): Promise<Buffer> {
    const doc = new PDFDocument({ size: 'LETTER', margin: 50 });

    const buffers = [];
    doc.on('data', buffers.push.bind(buffers));

    // Header
    doc
      .fontSize(20)
      .text('Reporte de Asistencia', { align: 'center' })
      .moveDown();

    doc
      .fontSize(12)
      .text(`Periodo: ${this.formatDate(data.startDate)} - ${this.formatDate(data.endDate)}`)
      .text(`Generado: ${new Date().toLocaleString('es-MX')}`)
      .moveDown();

    // Summary
    doc
      .fontSize(14)
      .text('Resumen', { underline: true })
      .moveDown(0.5);

    doc
      .fontSize(10)
      .text(`Total empleados: ${data.summary.total}`)
      .text(`Presentes: ${data.summary.present} (${data.summary.presentPercentage.toFixed(1)}%)`)
      .text(`Ausentes: ${data.summary.absent}`)
      .text(`Retardos: ${data.summary.late}`)
      .moveDown();

    // Table
    this.generateTable(doc, data.details);

    // Footer
    const pages = doc.bufferedPageRange();
    for (let i = 0; i < pages.count; i++) {
      doc.switchToPage(i);
      doc
        .fontSize(8)
        .text(
          `Página ${i + 1} de ${pages.count}`,
          50,
          doc.page.height - 50,
          { align: 'center' }
        );
    }

    doc.end();

    return new Promise((resolve) => {
      doc.on('end', () => resolve(Buffer.concat(buffers)));
    });
  }

  private generateTable(doc: any, data: any[]) {
    const tableTop = doc.y;
    const col1 = 50;
    const col2 = 150;
    const col3 = 250;
    const col4 = 350;
    const col5 = 450;

    // Headers
    doc
      .fontSize(9)
      .text('Empleado', col1, tableTop, { bold: true })
      .text('Check-In', col2, tableTop)
      .text('Check-Out', col3, tableTop)
      .text('Horas', col4, tableTop)
      .text('Estado', col5, tableTop);

    doc.moveDown();

    // Rows
    data.forEach((row) => {
      const y = doc.y;
      doc
        .fontSize(8)
        .text(row.employeeName, col1, y, { width: 90 })
        .text(row.checkIn ? this.formatTime(row.checkIn) : '-', col2, y)
        .text(row.checkOut ? this.formatTime(row.checkOut) : '-', col3, y)
        .text(row.hoursWorked || '0:00', col4, y)
        .text(this.translateStatus(row.status), col5, y);

      doc.moveDown(0.5);
    });
  }
}

🧪 Casos de Prueba

TC-REP-001: Reporte Diario

Precondiciones:

  • Fecha: 17/11/2025
  • 45 empleados presentes, 7 ausentes

Pasos:

  1. Ir a "Reportes" > "Asistencia Diaria"
  2. Seleccionar fecha 17/11/2025

Resultado esperado:

  • Resumen muestra:
    • Presentes: 45 (86%)
    • Ausentes: 7 (14%)
  • Tabla con 52 empleados
  • Exportar a PDF funcional

TC-REP-002: Reporte Mensual

Precondiciones:

  • Noviembre 2025, 21 días laborales

Pasos:

  1. Generar reporte mensual

Resultado esperado:

  • Promedio asistencia calculado correctamente
  • Tabla ordenada por % asistencia DESC
  • Clasificación de estatus visible

📦 Dependencias

  • US-HR-002: Asistencias registradas
  • US-HR-001: Empleados y cuadrillas

Librerías

{
  "pdfkit": "^0.14.0",
  "exceljs": "^4.4.0",
  "recharts": "^2.10.3"
}

⚠️ Riesgos

R-1: Rendimiento con Datos Históricos

Descripción: Reportes anuales pueden ser lentos Impacto: Medio Probabilidad: Media Mitigación:

  • Paginación en reportes
  • Índices en tablas
  • Cache de reportes frecuentes

📊 Métricas de Éxito

  • Reportes generan en < 5 segundos
  • 100% de exportaciones exitosas
  • 80% de gerentes usan reportes semanalmente

📋 Checklist de Implementación

Backend

  • Implementar AttendanceReportsService
  • Crear queries de agregación
  • Implementar PDFGeneratorService
  • Implementar ExcelGeneratorService
  • Crear endpoints de reportes

Frontend

  • Crear páginas de reportes
  • Implementar gráficas con Recharts
  • Crear componente de filtros
  • Implementar exportación

Fecha de creación: 2025-11-17 Versión: 1.0