erp-construccion/docs/02-definicion-modulos/MAI-007-rrhh-asistencias/historias-usuario/US-HR-003-costeo-mano-obra.md

871 lines
26 KiB
Markdown
Raw 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.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# US-HR-003: Costeo de Mano de Obra por Obra
**Epic:** MAI-007 - RRHH, Asistencias y Nómina
**RF:** RF-HR-003
**ET:** ET-HR-003
**Tipo:** Historia de Usuario
**Prioridad:** Alta
**Story Points:** 10
**Sprint:** 10
**Estado:** 📋 Pendiente
**Última actualización:** 2025-11-17
---
## 📖 Historia de Usuario
**Como** Director de Constructora o Ingeniero Residente
**Quiero** un sistema automático que calcule y registre el costo real de mano de obra por obra usando el FSR
**Para** comparar presupuesto vs real, detectar desviaciones temprano y proyectar el costo final al 100% de avance
---
## 🎯 Criterios de Aceptación
### CA-1: Configuración del FSR (Factor de Salario Real) ⚙️
**Dado que** soy Director de Constructora
**Cuando** accedo a "Configuración" > "RRHH" > "Factor de Salario Real"
**Entonces** puedo:
1. **Ver FSR Actual:**
- Ver tarjeta con FSR total: **1.58**
- Ver desglose de componentes:
- IMSS: 23.00%
- INFONAVIT: 5.00%
- Aguinaldo: 4.17%
- Vacaciones: 1.67%
- Prima Vacacional: 0.42%
- Domingos: 14.28%
- Días Festivos: 2.19%
- Ausentismo: 5.00%
- Otros: 3.00%
- **Total: 58%** → FSR = 1 + 0.58 = **1.58**
2. **Editar Componentes:**
- Poder modificar cada porcentaje individualmente
- Ver actualización automática del FSR total
- Ejemplo: Si cambio IMSS a 25%, FSR se recalcula a 1.60
- Validación: Cada componente debe ser ≥ 0% y ≤ 50%
- Validación: FSR total debe ser entre 1.0 y 3.0
3. **Guardar Configuración:**
- Al guardar, se registra fecha de cambio
- FSR aplica a **nuevos** cálculos (no retroactivo)
- Mensaje: "FSR actualizado a 1.60. Aplicará a registros posteriores al 2025-11-17"
- Log de auditoría: quién cambió, cuándo y valores anteriores vs nuevos
**Y** solo el rol Director puede modificar el FSR
### CA-2: Cálculo Automático de Costo al Aprobar Asistencia 🤖
**Dado que** se aprueba un registro de asistencia
**Cuando** el evento `attendance.approved` se dispara
**Entonces** el sistema debe automáticamente:
1. **Obtener Datos del Empleado:**
- Empleado: Juan Pérez García
- Salario diario base: $450.00
- Obra asignada: Casa Modelo Norte
- Salario específico de obra (si existe): $500.00
- **Usar:** $500.00 (prioridad a salario específico)
2. **Obtener FSR de la Constructora:**
- Buscar FSR configurado: 1.58
- Fecha efectiva: 2025-11-15
3. **Calcular Días Trabajados:**
- Basado en check-in y check-out:
- Check-in: 07:15 AM
- Check-out: 05:30 PM
- Horas trabajadas: 10h 15min
- **Si ≥ 8 horas:** 1.0 día
- **Si 4-8 horas:** 0.5 días
- **Si < 4 horas:** 0.25 días
- En este caso: **1.0 día**
4. **Calcular Costo Real:**
```
Costo Real = Salario Diario × Días Trabajados × FSR
Costo Real = $500.00 × 1.0 × 1.58 = $790.00
```
5. **Determinar Partida Presupuestal:**
- Buscar si el empleado pertenece a una cuadrilla
- Buscar si la cuadrilla está asignada a una partida presupuestal en esa obra
- Cuadrilla: Albañilería A
- Partida asignada: "03.02 - Muro de Block"
- Si no hay asignación: marcar como "Indirecto"
6. **Guardar Registro de Costo:**
```json
{
"attendanceId": "uuid-attendance",
"employeeId": "uuid-employee",
"workId": "uuid-work",
"budgetItemId": "uuid-budget-item", // o null
"workDate": "2025-11-17",
"daysWorked": 1.0,
"dailySalary": 500.00,
"fsr": 1.58,
"realCost": 790.00 // Calculado automáticamente por BD
}
```
**Y** este proceso debe ocurrir en segundo plano sin intervención del usuario
### CA-3: Dashboard de Costeo por Obra 📊
**Dado que** soy Director, Ingeniero o Residente
**Cuando** accedo a una obra > "Costeo de Mano de Obra"
**Entonces** veo un dashboard con:
1. **Resumen en Tarjetas:**
```
┌─────────────────────┐ ┌─────────────────────┐
│ Presupuesto MO │ │ Real Gastado │
│ $1,250,000 │ │ $875,342 │
└─────────────────────┘ └─────────────────────┘
┌─────────────────────┐ ┌─────────────────────┐
│ Proyección 100% │ │ Desviación │
│ $1,312,500 │ │ +5.0% [AMARILLO] │
└─────────────────────┘ └─────────────────────┘
```
2. **Indicadores de Color:**
- **Verde:** Desviación < 10% (dentro de presupuesto)
- **Amarillo:** Desviación 10-20% (advertencia)
- **Rojo:** Desviación > 20% (crítico)
3. **Avance Físico:**
- Porcentaje de avance de la obra: 66.7%
- Integrado desde módulo de Control de Obra
- Proyección calculada como: `Real Gastado / Avance Físico × 100`
- Ejemplo: `$875,342 / 0.667 × 100 = $1,312,500`
4. **Fórmula de Desviación:**
```
Desviación = (Proyección - Presupuesto) / Presupuesto × 100
Desviación = ($1,312,500 - $1,250,000) / $1,250,000 × 100 = +5.0%
```
### CA-4: Detalle por Partida Presupuestal 📋
**Dado que** veo el dashboard de costeo
**Cuando** bajo a la sección "Costo por Partida"
**Entonces** veo una tabla con:
| Partida | Presupuestado | Real Gastado | Días-Hombre | Desviación | Estado |
|---------|---------------|--------------|-------------|------------|--------|
| 02.01 - Excavación | $85,000 | $82,500 | 165 | -2.9% | 🟢 |
| 03.02 - Muro de Block | $320,000 | $285,000 | 570 | -10.9% | 🟢 |
| 04.01 - Castillos | $180,000 | $195,000 | 390 | +8.3% | 🟡 |
| 05.03 - Losa | $425,000 | $312,842 | 625 | (proyección) | 🟢 |
| **Indirecto** | $240,000 | $0 | 0 | - | - |
| **TOTAL** | **$1,250,000** | **$875,342** | **1,750** | **+5.0%** | **🟡** |
**Características de la tabla:**
- Ordenable por cualquier columna
- Filtrable por estado (verde, amarillo, rojo)
- Exportable a Excel
- Clic en partida: ver detalle de empleados
**Y** la fila "Indirecto" agrupa costos sin partida asignada (supervisión, logística, etc.)
### CA-5: Detalle de Empleados por Partida 👷
**Dado que** hago clic en una partida (ej: "03.02 - Muro de Block")
**Cuando** se abre el modal de detalle
**Entonces** veo:
1. **Header:**
- Partida: 03.02 - Muro de Block
- Presupuestado: $320,000
- Real gastado: $285,000
- Días-hombre: 570
2. **Lista de Empleados:**
| Empleado | Cuadrilla | Días Trabajados | Costo Total |
|----------|-----------|-----------------|-------------|
| Juan Pérez | Albañilería A | 45 | $35,550 |
| María López | Albañilería A | 43 | $34,002 |
| Carlos Ruiz | Albañilería B | 38 | $30,020 |
| ... | ... | ... | ... |
3. **Gráfica de Tendencia:**
- Gráfica de línea mostrando costo acumulado por semana
- Comparación con curva de presupuesto
- Detectar si hay aceleración o desaceleración de gasto
### CA-6: Asignación de Cuadrillas a Partidas 🔧
**Dado que** soy Ingeniero o Residente
**Cuando** accedo a "Cuadrillas" en una obra
**Entonces** puedo:
1. **Ver Cuadrillas de la Obra:**
- Lista de cuadrillas activas
- Por cada cuadrilla: nombre, tipo, supervisor, # miembros
2. **Asignar a Partida:**
- Hacer clic en "Asignar a Partida"
- Seleccionar partida del presupuesto (dropdown)
- Seleccionar fecha de inicio: 2025-11-10
- Fecha de fin: opcional (abierta si es indefinido)
- Guardar asignación
3. **Registro de Asignación:**
```json
{
"crewId": "uuid-crew",
"workId": "uuid-work",
"budgetItemId": "uuid-budget-item",
"startDate": "2025-11-10",
"endDate": null, // Abierta
"isActive": true
}
```
4. **Validación:**
- Una cuadrilla puede estar asignada a múltiples partidas en diferentes periodos
- No puede estar en dos partidas **simultáneamente** (fechas traslapadas)
- Si se intenta: error "La cuadrilla ya está asignada a '04.01 - Castillos' desde el 2025-11-08"
5. **Historial de Asignaciones:**
- Ver tabla con todas las asignaciones pasadas y presentes
- Filtrar por cuadrilla, partida, fecha
**Y** a partir de la fecha de asignación, todos los costos de esa cuadrilla se imputan a la partida correspondiente
### CA-7: Alertas de Desviación 🚨
**Dado que** el sistema calcula costos diariamente
**Cuando** detecta una desviación significativa
**Entonces** debe:
1. **Generar Alerta Automática:**
- **Condición:** Desviación de una partida > 15%
- Crear notificación para:
- Ingeniero Residente de la obra
- Director de Constructora
- Contenido de notificación:
```
⚠️ Alerta de Desviación de Costo
Obra: Casa Modelo Norte
Partida: 04.01 - Castillos
Desviación: +18.5%
Real: $195,000 vs Presupuesto: $164,620 (a la fecha)
Acción recomendada: Revisar rendimientos y asignación de personal
```
2. **Dashboard de Alertas:**
- Sección en home del usuario
- Lista de alertas activas
- Filtros: por obra, por criticidad (amarillo, rojo)
- Acción: "Marcar como revisado"
3. **Email Semanal:**
- Cada lunes a las 8 AM
- Resumen de alertas de la semana anterior
- Top 3 partidas con mayor desviación
- Solo si hay alertas activas
### CA-8: Comparación Histórica de Obras 📈
**Dado que** soy Director
**Cuando** accedo a "Reportes" > "Análisis Comparativo de Obras"
**Entonces** puedo:
1. **Seleccionar Obras:**
- Seleccionar hasta 5 obras para comparar
- Filtrar por: estado (activa, terminada), año, tipo de obra
2. **Ver Tabla Comparativa:**
| Obra | Presup. MO | Real MO | Desv. | m² | Costo/m² | Eficiencia |
|------|------------|---------|-------|-----|----------|------------|
| Casa Norte | $1.25M | $1.31M | +5% | 250 | $5,240 | 95% |
| Casa Sur | $980K | $920K | -6% | 200 | $4,600 | 106% |
| Edificio A | $3.5M | $3.8M | +8% | 800 | $4,750 | 92% |
3. **Fórmulas:**
- Costo/m² = Real MO / Metros cuadrados
- Eficiencia = (Presupuesto / Real) × 100
- Benchmark: Identificar obra con mejor costo/m²
4. **Gráfica de Dispersión:**
- Eje X: Metros cuadrados
- Eje Y: Costo/m²
- Cada punto: una obra
- Detectar outliers
**Y** poder exportar el análisis a PDF para presentaciones
### CA-9: Proyección y Escenarios 🔮
**Dado que** veo el dashboard de una obra en progreso
**Cuando** accedo a "Proyecciones"
**Entonces** puedo:
1. **Ver Proyección Base:**
- Basada en % de avance físico actual
- Costo final proyectado: $1,312,500
- Desviación proyectada: +5.0%
2. **Simular Escenarios:**
- **Escenario Optimista:** Si mejoramos rendimiento 10%
- Costo proyectado: $1,181,250 (-5.5%)
- **Escenario Pesimista:** Si rendimiento empeora 10%
- Costo proyectado: $1,443,750 (+15.5%)
- **Escenario Realista:** Con tendencia actual
- Mantiene proyección base
3. **Ajuste de Variables:**
- Slider de % de mejora/empeoramiento: -20% a +20%
- Cambio de FSR futuro (si se espera cambio legal)
- Cambio de salario promedio (aumentos programados)
- Ver impacto en tiempo real
4. **Exportar Escenario:**
- Guardar escenario con nombre
- Ejemplo: "Escenario Post-Aumento Salarial Diciembre"
- Compartir con equipo vía link
### CA-10: Permisos por Rol 🔐
**Roles y Permisos:**
| Acción | Director | Engineer | Resident | HR | Finance |
|--------|----------|----------|----------|-----|---------|
| Ver dashboard costeo | ✅ | ✅ | ✅ | ❌ | ✅ |
| Configurar FSR | ✅ | ❌ | ❌ | ❌ | ❌ |
| Asignar cuadrillas a partidas | ✅ | ✅ | ✅ | ❌ | ❌ |
| Ver detalle de empleados | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ver salarios individuales | ✅ | ❌ | ❌ | ✅ | ✅ |
| Exportar reportes | ✅ | ✅ | ✅ | ❌ | ✅ |
| Crear proyecciones | ✅ | ✅ | ❌ | ❌ | ❌ |
---
## 🔧 Detalles Técnicos
### Arquitectura Event-Driven
```typescript
// labor-costs.service.ts
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class LaborCostsService {
/**
* Event listener que se ejecuta cuando se aprueba asistencia
*/
@OnEvent('attendance.approved')
async handleAttendanceApproved(attendance: AttendanceRecord) {
// 1. Obtener empleado y salario
const employee = await this.getEmployeeWithSalary(attendance.employeeId);
// 2. Obtener FSR de la constructora
const fsrConfig = await this.getFSRConfig(employee.constructoraId);
// 3. Calcular días trabajados
const daysWorked = await this.calculateDaysWorked(attendance);
// 4. Determinar partida presupuestal
const budgetItemId = await this.determineBudgetItem(
attendance.employeeId,
attendance.workId,
attendance.workDate
);
// 5. Crear registro de costo
const laborCost = this.laborCostRepo.create({
attendanceId: attendance.id,
employeeId: attendance.employeeId,
workId: attendance.workId,
budgetItemId,
workDate: attendance.workDate,
daysWorked,
dailySalary: employee.workSpecificSalary || employee.currentSalary,
fsr: fsrConfig.totalFsr,
// realCost se calcula automáticamente en BD con GENERATED column
});
await this.laborCostRepo.save(laborCost);
// 6. Verificar desviaciones y emitir alertas si es necesario
await this.checkDeviations(attendance.workId);
}
}
```
### Columna Calculada en PostgreSQL
```sql
-- labor_costs table
CREATE TABLE hr.labor_costs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
attendance_id UUID UNIQUE NOT NULL REFERENCES hr.attendance_records(id),
employee_id UUID NOT NULL,
work_id UUID NOT NULL,
budget_item_id UUID REFERENCES budgets.budget_items(id),
work_date DATE NOT NULL,
days_worked DECIMAL(3,2) NOT NULL CHECK(days_worked > 0 AND days_worked <= 1),
daily_salary DECIMAL(10,2) NOT NULL,
fsr DECIMAL(4,2) NOT NULL,
-- Columna generada automáticamente
real_cost DECIMAL(10,2) GENERATED ALWAYS AS (daily_salary * days_worked * fsr) STORED,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_labor_costs_work ON hr.labor_costs(work_id, work_date);
CREATE INDEX idx_labor_costs_budget_item ON hr.labor_costs(budget_item_id);
```
### Query de Dashboard
```typescript
// Obtener resumen de costeo por obra
async getCostSummary(workId: string) {
// 1. Total presupuestado de MO
const budgetedLabor = await this.db.query(`
SELECT SUM(labor_cost) as total
FROM budgets.budget_items
WHERE work_id = $1
`, [workId]);
// 2. Total real gastado
const realLabor = await this.db.query(`
SELECT
SUM(real_cost) as total,
COUNT(*) as total_records,
SUM(days_worked) as total_days
FROM hr.labor_costs
WHERE work_id = $1
`, [workId]);
// 3. Avance físico (de control de obra)
const physicalProgress = await this.getPhysicalProgress(workId);
// 4. Proyección
const projected = physicalProgress > 10
? (realLabor.total / physicalProgress) * 100
: null;
// 5. Desviación
const deviation = projected
? ((projected - budgetedLabor.total) / budgetedLabor.total) * 100
: null;
return {
budgeted: budgetedLabor.total,
real: realLabor.total,
totalDays: realLabor.total_days,
physicalProgress,
projected,
deviation,
status: this.getDeviationStatus(deviation),
};
}
```
### Componente React del Dashboard
```typescript
// CostDashboard.tsx
import { useQuery } from '@tanstack/react-query';
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
export function CostDashboard({ workId }: { workId: string }) {
const { data, isLoading } = useQuery({
queryKey: ['labor-costs', 'summary', workId],
queryFn: () => apiService.get(`/hr/labor-costs/summary/${workId}`),
refetchInterval: 60000, // Actualizar cada minuto
});
if (isLoading) return <Skeleton />;
const statusColors = {
green: 'bg-green-500',
yellow: 'bg-yellow-500',
red: 'bg-red-500',
};
return (
<div className="space-y-6">
{/* Tarjetas de resumen */}
<div className="grid grid-cols-4 gap-4">
<Card className="p-6">
<p className="text-sm text-muted-foreground">Presupuesto MO</p>
<p className="text-3xl font-bold">
${data.budgeted.toLocaleString('es-MX')}
</p>
</Card>
<Card className="p-6">
<p className="text-sm text-muted-foreground">Real Gastado</p>
<p className="text-3xl font-bold">
${data.real.toLocaleString('es-MX')}
</p>
<p className="text-xs text-muted-foreground mt-1">
{data.totalDays} días-hombre
</p>
</Card>
<Card className="p-6">
<p className="text-sm text-muted-foreground">Proyección 100%</p>
<p className="text-3xl font-bold">
${data.projected?.toLocaleString('es-MX') || 'N/A'}
</p>
<p className="text-xs text-muted-foreground mt-1">
Avance: {data.physicalProgress}%
</p>
</Card>
<Card className="p-6">
<p className="text-sm text-muted-foreground">Desviación</p>
<div className="flex items-center gap-3">
<p className="text-3xl font-bold">
{data.deviation > 0 ? '+' : ''}
{data.deviation?.toFixed(1)}%
</p>
<Badge className={statusColors[data.status]}>
{data.status.toUpperCase()}
</Badge>
</div>
</Card>
</div>
{/* Tabla de partidas */}
<BudgetItemsTable workId={workId} />
{/* Gráfica de tendencia */}
<CostTrendChart workId={workId} />
</div>
);
}
```
---
## 🧪 Casos de Prueba
### TC-COST-001: Cálculo Automático de Costo ✅
**Precondiciones:**
- Empleado: Juan Pérez, salario $500/día
- FSR configurado: 1.58
- Asistencia: 1.0 día trabajado
**Pasos:**
1. Aprobar asistencia del empleado
2. Event `attendance.approved` se dispara
3. Sistema calcula costo automáticamente
**Resultado esperado:**
- Registro en `hr.labor_costs` creado:
- `dailySalary`: 500.00
- `daysWorked`: 1.0
- `fsr`: 1.58
- `realCost`: 790.00 (calculado automáticamente)
- Cálculo: 500 × 1.0 × 1.58 = 790.00
- Proceso completado en < 500ms
### TC-COST-002: Asignación a Partida Presupuestal ✅
**Precondiciones:**
- Empleado pertenece a Cuadrilla "Albañilería A"
- Cuadrilla asignada a partida "03.02 - Muro de Block" desde 2025-11-10
- Fecha de trabajo: 2025-11-17
**Pasos:**
1. Registrar asistencia del empleado
2. Sistema determina partida automáticamente
**Resultado esperado:**
- `budgetItemId` = UUID de "03.02 - Muro de Block"
- Costo se imputa a esa partida
- Visible en dashboard bajo "Muro de Block"
### TC-COST-003: Empleado Sin Partida Asignada (Indirecto) ✅
**Precondiciones:**
- Empleado NO pertenece a ninguna cuadrilla
- O cuadrilla sin asignación a partida
**Pasos:**
1. Registrar asistencia
**Resultado esperado:**
- `budgetItemId` = null
- Costo clasificado como "Indirecto"
- Visible en fila "Indirecto" en dashboard
### TC-COST-004: Configuración de FSR ⚙️
**Precondiciones:**
- Usuario con rol Director
- FSR actual: 1.58
**Pasos:**
1. Ir a "Configuración" > "FSR"
2. Cambiar IMSS de 23% a 25%
3. Ver actualización automática: FSR = 1.60
4. Guardar cambios
**Resultado esperado:**
- FSR guardado como 1.60
- Fecha efectiva: 2025-11-17
- Log de auditoría registrado:
- Usuario: Director
- Cambio: IMSS 23% → 25%, FSR 1.58 → 1.60
- Nuevos cálculos usan 1.60
- Cálculos anteriores mantienen 1.58
### TC-COST-005: Dashboard de Costeo ✅
**Precondiciones:**
- Obra con presupuesto MO: $1,250,000
- Real gastado: $875,342
- Avance físico: 66.7%
**Pasos:**
1. Ir a obra > "Costeo de Mano de Obra"
2. Ver dashboard
**Resultado esperado:**
- Presupuesto: $1,250,000
- Real: $875,342
- Proyección: $1,312,500 (calculado: 875342 / 0.667)
- Desviación: +5.0% (calculado: (1312500-1250000)/1250000×100)
- Badge amarillo (10% < desv. < 20%)
### TC-COST-006: Alerta de Desviación 🚨
**Precondiciones:**
- Partida "04.01 - Castillos"
- Presupuesto: $180,000
- Real gastado: $195,000
- Desviación: +8.3% cambia a +16.5%
**Pasos:**
1. Registrar más asistencias que aumentan el costo
2. Desviación supera 15%
3. Sistema detecta automáticamente
**Resultado esperado:**
- Notificación creada para Ingeniero Residente y Director
- Contenido:
```
Alerta de Desviación
Partida: 04.01 - Castillos
Desviación: +16.5%
Real: $209,700 vs Presupuesto: $180,000
```
- Email enviado si está configurado
- Dashboard de alertas muestra 1 nueva alerta
### TC-COST-007: Asignación de Cuadrilla a Partida ✅
**Precondiciones:**
- Cuadrilla "Albañilería A" creada
- Obra con presupuesto que incluye "03.02 - Muro de Block"
**Pasos:**
1. Ir a Cuadrillas de la obra
2. Seleccionar "Albañilería A"
3. Clic en "Asignar a Partida"
4. Seleccionar "03.02 - Muro de Block"
5. Fecha inicio: 2025-11-10
6. Guardar
**Resultado esperado:**
- Registro creado en `crew_budget_assignments`
- `startDate`: 2025-11-10
- `endDate`: null (abierta)
- `isActive`: true
- A partir del 2025-11-10, todos los costos de esa cuadrilla se imputan a esa partida
### TC-COST-008: Evitar Traslape de Asignaciones ❌
**Precondiciones:**
- Cuadrilla ya asignada a "03.02 - Muro" desde 2025-11-10 (sin fecha fin)
**Pasos:**
1. Intentar asignar la misma cuadrilla a "04.01 - Castillos"
2. Fecha inicio: 2025-11-15
**Resultado esperado:**
- Error: "La cuadrilla ya está asignada a '03.02 - Muro de Block' desde 2025-11-10"
- Sugerencia: "Cierra la asignación anterior o usa otra cuadrilla"
- No se permite guardar
### TC-COST-009: Proyección de Escenarios 🔮
**Precondiciones:**
- Obra con costo real $875,342
- Proyección base: $1,312,500
**Pasos:**
1. Ir a "Proyecciones"
2. Mover slider a "Mejora del 10%"
**Resultado esperado:**
- Proyección actualizada en tiempo real
- Nuevo valor: $1,181,250 (1312500 × 0.9)
- Desviación: -5.5%
- Badge verde
- Gráfica se actualiza mostrando nueva línea de proyección
### TC-COST-010: Comparación Histórica de Obras 📈
**Precondiciones:**
- 3 obras completadas con datos de costeo
**Pasos:**
1. Ir a "Reportes" > "Análisis Comparativo"
2. Seleccionar 3 obras
3. Ver tabla y gráfica
**Resultado esperado:**
- Tabla muestra:
- Casa Norte: $5,240/m², Eficiencia 95%
- Casa Sur: $4,600/m², Eficiencia 106% ⭐ Mejor
- Edificio A: $4,750/m², Eficiencia 92%
- Gráfica de dispersión muestra 3 puntos
- Casa Sur identificada como benchmark
- Opción de exportar a PDF visible
---
## 📦 Dependencias
### Dependencias de Otros US
-**US-FUND-004:** Infraestructura (event emitters, TypeORM)
-**US-HR-001:** Empleados y cuadrillas
-**US-HR-002:** Asistencias (genera evento attendance.approved)
-**US-BUD-001:** Presupuestos (partidas presupuestales)
-**US-PROJ-003:** Control de Obra (% de avance físico)
### Librerías Backend
```json
{
"@nestjs/event-emitter": "^2.0.3",
"decimal.js": "^10.4.3"
}
```
### Librerías Frontend
```json
{
"recharts": "^2.10.3",
"date-fns": "^3.0.1",
"jspdf": "^2.5.1"
}
```
---
## ⚠️ Riesgos
### R-1: Cambios Retroactivos de FSR
**Descripción:** Si se cambia el FSR, usuarios pueden esperar recálculo retroactivo
**Impacto:** Medio
**Probabilidad:** Media
**Mitigación:**
- FSR NO es retroactivo por diseño
- Mensaje claro al guardar: "Aplicará solo a registros futuros"
- Opción de "Recálculo Masivo" solo para Director (con auditoría)
### R-2: Precisión de Proyección con Bajo Avance
**Descripción:** Proyección al 100% es imprecisa si avance < 10%
**Impacto:** Medio
**Probabilidad:** Alta
**Mitigación:**
- No mostrar proyección si avance < 10%
- Mensaje: "Proyección disponible cuando avance > 10%"
- Usar promedio de obras similares como referencia inicial
### R-3: Desvinculación de Empleado de Cuadrilla
**Descripción:** Si empleado sale de cuadrilla, costos pasados pueden quedar "huérfanos"
**Impacto:** Bajo
**Probabilidad:** Media
**Mitigación:**
- Los costos históricos NO se modifican
- Solo costos **futuros** usan la nueva asignación
- Historial de asignaciones mantiene trazabilidad
---
## 📊 Métricas de Éxito
**Métricas de Negocio:**
- ✅ 90% de obras con desviación < 15%
- Detección de desviaciones 2 semanas antes vs método manual
- Reducción de 50% en sobrecostos por mejor control
**Métricas Técnicas:**
- Cálculo de costo < 500ms después de aprobar asistencia
- Dashboard carga en < 2 segundos
- 100% de costos con partida asignada o clasificados como indirecto
**Métricas de Usuario:**
- 95% de Ingenieros revisan dashboard semanalmente
- 0 quejas de cálculos incorrectos en primer mes
- Satisfacción > 4.5/5
---
## 📋 Checklist de Implementación
### Backend
- [ ] Crear tabla `hr.labor_costs` con columna generada
- [ ] Crear tabla `hr.fsr_configuration`
- [ ] Crear tabla `hr.crew_budget_assignments`
- [ ] Implementar `LaborCostsService` con event listener
- [ ] Implementar `FSRConfigurationService`
- [ ] Implementar cálculo de días trabajados
- [ ] Implementar determinación de partida presupuestal
- [ ] Crear endpoints de dashboard
- [ ] Crear endpoint de configuración FSR
- [ ] Implementar sistema de alertas
- [ ] Crear queries de comparación histórica
- [ ] Crear seeds de FSR por defecto
### Frontend
- [ ] Crear página `CostDashboard`
- [ ] Crear componente `FSRConfiguration`
- [ ] Crear componente `BudgetItemsTable`
- [ ] Crear componente `CostTrendChart` con Recharts
- [ ] Crear componente `CrewBudgetAssignment`
- [ ] Crear componente `DeviationAlerts`
- [ ] Crear página `HistoricalComparison`
- [ ] Crear componente `ProjectionScenarios`
- [ ] Implementar exportación a PDF
- [ ] Implementar permisos por rol
### Testing
- [ ] Tests de cálculo de costo real
- [ ] Tests de event listener
- [ ] Tests de validación de FSR (1.0 - 3.0)
- [ ] Tests de detección de desviaciones
- [ ] Tests de asignación de cuadrillas
- [ ] Tests de traslape de fechas
- [ ] Tests de proyecciones
- [ ] Tests E2E de flujo completo
---
**Fecha de creación:** 2025-11-17
**Versión:** 1.0
**Autor:** Equipo de Desarrollo
**Revisado por:** Product Owner