Skip to main content
Logo
Resumen

Python en la construcción: Calculando la rentabilidad real de la mano de obra

30 de noviembre de 2025
5 min de lectura

En la industria de la construcción, el control de costos es la diferencia entre ganar dinero o financiar la obra de tu bolsillo. Uno de los costos más difíciles de asignar con precisión es la Mano de obra.

  • A los trabajadores se les paga mensualmente (o quincenalmente).
  • Los cobros al cliente (Estados de Pago) ocurren en fechas arbitrarias (ej: del 5 al 25).
  • La rotación es alta: gente entra y sale a mitad de periodo.

Si intentas cruzar esto en Excel, terminas con fórmulas kilométricas y errores manuales. Por lo tanto, decidí resolver esto programáticamente.

Paso 1: Estructurando los datos

En lugar de luchar con celdas combinadas de Excel, en Python trabajamos con DataFrames. Para este ejemplo, simulemos una cuadrilla pequeña y una serie de Estados de Pago (EP) con fechas irregulares.

import pandas as pd
import numpy as np
# 1. Nuestra "Base de Datos" de trabajadores
# En la vida real, esto vendría de tu ERP o CSV de RRHH
datos_trabajadores = {
'Nombre': ['Juan Pérez', 'Ana Silva', 'Carlos M.', 'Luis R.'],
'Cargo': ['Maestro', 'Jornal', 'Admin', 'Maestro'],
'Sueldo_Base': [800000, 500000, 1500000, 850000],
'Fecha_Contrato': ['2023-01-01', '2023-02-15', '2023-01-01', '2023-03-01'],
'Fecha_Salida': [None, None, None, '2023-04-15'] # None = Sigue trabajando
}
# 2. Los periodos que cobramos al cliente (Estados de Pago)
datos_estados_pago = {
'EP': ['EP01', 'EP02', 'EP03'],
'Inicio': ['2023-01-01', '2023-02-01', '2023-03-01'],
'Fin': ['2023-01-31', '2023-02-28', '2023-03-31'],
'Ingreso_Cobrado': [5000000, 8500000, 12000000]
}
# Convertimos a DataFrames de Pandas
df_workers = pd.DataFrame(datos_trabajadores)
df_ep = pd.DataFrame(datos_estados_pago)
# Vital: Convertir columnas de texto a objetos de Fecha (datetime)
cols_fecha = ['Fecha_Contrato', 'Fecha_Salida']
for col in cols_fecha:
df_workers[col] = pd.to_datetime(df_workers[col])
cols_ep = ['Inicio', 'Fin']
for col in cols_ep:
df_ep[col] = pd.to_datetime(df_ep[col])

Paso 2: La lógica de “días reales”

Aquí es donde Excel suele fallar y donde Python brilla. No podemos simplemente restar fechas, porque la realidad de la obra tiene múltiples “aristas”. Un trabajador puede estar en una de estas 4 situaciones durante un Estado de Pago (EP):

  1. Continuidad: Ya estaba contratado antes y sigue después (Trabaja el periodo completo).
  2. Ingreso: Fue contratado a mitad del periodo (Solo contamos desde su ingreso).
  3. Salida: Fue finiquitado a mitad del periodo (Solo contamos hasta su salida).
  4. Temporal: Entró y salió dentro del mismo periodo (Contamos solo esos días).

Para manejar esto con precisión, creamos una función que determina los límites efectivos de cada persona.

def calcular_dias_reales(inicio_contrato, fin_contrato, inicio_ep, fin_ep):
"""
Calcula la intersección exacta de días trabajados dentro de un periodo específico,
manejando ingresos, finiquitos y personal activo.
"""
# 1. Definir el INICIO EFECTIVO
# El trabajador empieza a contar desde la fecha más tardía entre:
# A) El inicio del Estado de Pago (si ya estaba contratado)
# B) Su fecha de contrato (si entró durante el periodo)
inicio_efectivo = max(inicio_contrato, inicio_ep)
# 2. Definir el FIN EFECTIVO
# Primero, verificamos si el trabajador sigue activo (fin_contrato es NaT/None)
if pd.isna(fin_contrato):
# Si está activo, su tope es el fin del Estado de Pago
fin_efectivo = fin_ep
else:
# Si fue desvinculado, su tope es la fecha más temprana entre:
# A) Su fecha de salida
# B) El fin del Estado de Pago
fin_efectivo = min(fin_contrato, fin_ep)
# 3. Cálculo de Días (Inclusive)
# En nómina, si trabajas del día 1 al 1, es 1 día trabajado.
# La resta simple de fechas da 0, por eso sumamos +1.
dias = (fin_efectivo - inicio_efectivo).days + 1
# 4. Filtro de Seguridad
# Si 'dias' es negativo, significa que los periodos no se solapan
# (ej: entró después de que terminó el EP). Retornamos 0.
return max(0, dias)
Explicación (¿Por qué tanta lógica?)
Esta función es la única forma de garantizar que si un trabajador entró el día 15 y el periodo de cobro cerró el 20, el sistema asigne exactamente 6 días de costo, ni uno más ni uno menos. Esta precisión acumulada en cientos de trabajadores es lo que define el margen real.

Paso 3: Cruzando la información

Ahora, iteramos por cada Estado de Pago y aplicamos nuestra función a toda la plantilla de trabajadores en milisegundos.

resultados = []
for idx, row in df_ep.iterrows():
# Usamos una copia para no alterar los datos originales
temp_df = df_workers.copy()
# Aplicamos la función fila por fila
temp_df['Dias_Periodo'] = temp_df.apply(
lambda x: calcular_dias_reales(
x['Fecha_Contrato'], x['Fecha_Salida'], row['Inicio'], row['Fin']
), axis=1
)
# Calculamos el costo real prorrateado
# (Sueldo Base / 30) * Días trabajados
temp_df['Costo_Real'] = (temp_df['Sueldo_Base'] / 30) * temp_df['Dias_Periodo']
# Sumamos todo para obtener el Gasto Total del periodo
gasto_total = temp_df['Costo_Real'].sum()
utilidad = row['Ingreso_Cobrado'] - gasto_total
resultados.append({
'Periodo': row['EP'],
'Ingreso': row['Ingreso_Cobrado'],
'Gasto_MO_Real': round(gasto_total, 0),
'Utilidad': round(utilidad, 0),
'Margen_%': round((utilidad / row['Ingreso_Cobrado']) * 100, 1)
})
# Convertimos los resultados en una tabla final
df_final = pd.DataFrame(resultados)
print(df_final)

El resultado

Al ejecutar el script, obtenemos una visión financiera precisa, limpia de distorsiones por fechas de ingreso o salida.

Periodo Ingreso Gasto_MO_Real Utilidad Margen_%
EP01 5,000,000 2,450,000 2,550,000 51.0%
EP02 8,500,000 3,383,333 5,116,667 60.2%
EP03 12,000,000 4,650,000 7,350,000 61.2%
Consejo
Este script transformó una tarea que tomaba de 1 a 2 días para un analista administrativo en un proceso de 30 segundos, permitiéndonos enfocar el tiempo en analizar las desviaciones y no en calcularlas.

Conclusión: De operativo a estratégico

Excel sigue siendo una herramienta fantástica para análisis rápidos, pero cuando buscamos escalabilidad y precisión en procesos repetitivos, Python es el claro ganador. Lo mejor de este script es que funciona igual de rápido con una cuadrilla de 10 personas que con una obra de 500 trabajadores, eliminando los errores manuales que suelen ocurrir al manejar grandes volúmenes de datos.

Más allá del código, aquí radica el verdadero valor del Business Engineer: pasar de “llenar planillas” a generar inteligencia de negocios. Ya no perdemos tiempo discutiendo si el cálculo manual es correcto; confiamos en el algoritmo y usamos ese tiempo para lo que realmente importa: tomar decisiones estratégicas para proteger el margen de la obra.

Te invito a copiar el código, probarlo con tus propios datos y dar el salto. Si tienes dudas sobre cómo implementarlo o quieres discutir otras formas de automatizar el control de gestión, siéntete libre de contactarme.

La curva de aprendizaje vale cada minuto de eficiencia ganada.