feat: Implement complete APU (Análisis de Precios Unitarios) module

High priority features:
- APU CRUD with materials, labor, and equipment breakdown
- Labor catalog with FSR (Factor de Salario Real) calculation
- Equipment catalog with hourly cost calculation
- Link APU to budget line items (partidas)
- Explosion de insumos (consolidated materials list)

Additional features:
- Duplicate APU functionality
- Excel export for explosion de insumos
- Search and filters in APU list
- Price validation alerts for outdated prices
- PDF report export for APU

New components:
- APUForm, APUList, APUDetail
- ManoObraForm, EquipoForm
- ConfiguracionAPUForm
- VincularAPUDialog
- PartidasManager
- ExplosionInsumos
- APUPDF

New UI components:
- Alert component
- Tooltip component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mexus
2026-02-05 07:14:14 +00:00
parent e1847597d6
commit 56e39af3ff
47 changed files with 7779 additions and 18 deletions

View File

@@ -82,6 +82,38 @@ enum UnidadMedida {
PIEZA
ROLLO
CAJA
HORA
JORNADA
VIAJE
LOTE
GLOBAL
}
enum CategoriaManoObra {
PEON
AYUDANTE
OFICIAL_ALBANIL
OFICIAL_FIERRERO
OFICIAL_CARPINTERO
OFICIAL_PLOMERO
OFICIAL_ELECTRICISTA
CABO
MAESTRO_OBRA
OPERADOR_EQUIPO
}
enum TipoEquipo {
MAQUINARIA_PESADA
MAQUINARIA_LIGERA
HERRAMIENTA_ELECTRICA
TRANSPORTE
}
enum TipoInsumoAPU {
MATERIAL
MANO_OBRA
EQUIPO
HERRAMIENTA_MENOR
}
// ============== MODELS ==============
@@ -137,6 +169,11 @@ model Empresa {
empleados Empleado[]
subcontratistas Subcontratista[]
clientes Cliente[]
// APU Relations
categoriasTrabajo CategoriaTrabajoAPU[]
equiposMaquinaria EquipoMaquinaria[]
apus AnalisisPrecioUnitario[]
configuracionAPU ConfiguracionAPU?
}
model Cliente {
@@ -267,6 +304,8 @@ model TareaObra {
@@index([faseId])
@@index([estado])
@@index([asignadoId])
@@index([faseId, estado])
}
model RegistroAvance {
@@ -281,6 +320,8 @@ model RegistroAvance {
createdAt DateTime @default(now())
@@index([obraId])
@@index([registradoPorId])
@@index([obraId, createdAt])
}
model Presupuesto {
@@ -312,6 +353,8 @@ model PartidaPresupuesto {
categoria CategoriaGasto
presupuestoId String
presupuesto Presupuesto @relation(fields: [presupuestoId], references: [id], onDelete: Cascade)
apuId String?
apu AnalisisPrecioUnitario? @relation(fields: [apuId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -319,6 +362,7 @@ model PartidaPresupuesto {
gastos Gasto[]
@@index([presupuestoId])
@@index([apuId])
}
model Gasto {
@@ -395,10 +439,12 @@ model Material {
// Relations
movimientos MovimientoInventario[]
itemsOrden ItemOrdenCompra[]
insumosAPU InsumoAPU[]
@@unique([codigo, empresaId])
@@index([empresaId])
@@index([nombre])
@@index([activo, empresaId])
}
model MovimientoInventario {
@@ -470,6 +516,7 @@ model JornadaTrabajo {
@@index([empleadoId])
@@index([fecha])
@@index([empleadoId, fecha])
}
model Subcontratista {
@@ -773,6 +820,8 @@ model FotoAvance {
@@index([obraId])
@@index([faseId])
@@index([fechaCaptura])
@@index([subidoPorId])
@@index([obraId, fechaCaptura])
}
// ============== NOTIFICACIONES PUSH ==============
@@ -894,4 +943,134 @@ model ActividadLog {
@@index([empresaId])
@@index([tipo])
@@index([createdAt])
@@index([obraId, createdAt])
@@index([empresaId, createdAt])
}
// ============== ANÁLISIS DE PRECIOS UNITARIOS (APU) ==============
model CategoriaTrabajoAPU {
id String @id @default(cuid())
codigo String
nombre String
categoria CategoriaManoObra
salarioDiario Float
factorIMSS Float @default(0.2675)
factorINFONAVIT Float @default(0.05)
factorRetiro Float @default(0.02)
factorVacaciones Float @default(0.0411)
factorPrimaVac Float @default(0.0103)
factorAguinaldo Float @default(0.0411)
factorSalarioReal Float // Calculado: 1 + suma de factores
salarioReal Float // salarioDiario * FSR
activo Boolean @default(true)
empresaId String
empresa Empresa @relation(fields: [empresaId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
insumosAPU InsumoAPU[]
@@unique([codigo, empresaId])
@@index([empresaId])
@@index([categoria])
}
model EquipoMaquinaria {
id String @id @default(cuid())
codigo String
nombre String
tipo TipoEquipo
valorAdquisicion Float
vidaUtilHoras Float
valorRescate Float @default(0)
consumoCombustible Float? // Litros por hora
precioCombustible Float? // Precio por litro
factorMantenimiento Float @default(0.60)
costoOperador Float? // Costo por hora del operador
costoHorario Float // Calculado
activo Boolean @default(true)
empresaId String
empresa Empresa @relation(fields: [empresaId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
insumosAPU InsumoAPU[]
@@unique([codigo, empresaId])
@@index([empresaId])
@@index([tipo])
}
model AnalisisPrecioUnitario {
id String @id @default(cuid())
codigo String
descripcion String
unidad UnidadMedida
rendimientoDiario Float? // Cantidad de unidades por jornada
costoMateriales Float @default(0)
costoManoObra Float @default(0)
costoEquipo Float @default(0)
costoHerramienta Float @default(0)
costoDirecto Float @default(0)
porcentajeIndirectos Float @default(0)
costoIndirectos Float @default(0)
porcentajeUtilidad Float @default(0)
costoUtilidad Float @default(0)
precioUnitario Float @default(0)
activo Boolean @default(true)
empresaId String
empresa Empresa @relation(fields: [empresaId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
insumos InsumoAPU[]
partidas PartidaPresupuesto[]
@@unique([codigo, empresaId])
@@index([empresaId])
@@index([unidad])
}
model InsumoAPU {
id String @id @default(cuid())
tipo TipoInsumoAPU
descripcion String
unidad UnidadMedida
cantidad Float
desperdicio Float @default(0) // Porcentaje
cantidadConDesperdicio Float
rendimiento Float? // Para mano de obra: unidades por jornada
precioUnitario Float
importe Float
apuId String
apu AnalisisPrecioUnitario @relation(fields: [apuId], references: [id], onDelete: Cascade)
materialId String?
material Material? @relation(fields: [materialId], references: [id])
categoriaManoObraId String?
categoriaManoObra CategoriaTrabajoAPU? @relation(fields: [categoriaManoObraId], references: [id])
equipoId String?
equipo EquipoMaquinaria? @relation(fields: [equipoId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([apuId])
@@index([tipo])
@@index([materialId])
@@index([categoriaManoObraId])
@@index([equipoId])
}
model ConfiguracionAPU {
id String @id @default(cuid())
porcentajeHerramientaMenor Float @default(3)
porcentajeIndirectos Float @default(8)
porcentajeUtilidad Float @default(10)
empresaId String @unique
empresa Empresa @relation(fields: [empresaId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}