Análisis de Producción: Dashboard de Diagnóstico con Datos Reales

Cómo construir gráficos de producción que revelan el comportamiento de tu yacimiento

Ingeniería de Yacimientos
Producción
Vigilancia
Excel
Python

Aprende a construir gráficos de producción usando datos reales de 12 pozos. Incluye gráficos compuestos, WOR, GOR, curvas de declinación, Purvis plot y diagnóstico de mecanismo de empuje. Basado en Baker et al. (2015) “Practical Reservoir Engineering and Characterization”, Capítulo 4.

Author
Published

April 27, 2026

Introducción

El análisis de producción es una de las tareas más importantes (y más subestimadas) del ingeniero de yacimientos. Antes de correr un simulador numérico o ajustar un modelo de balance de materia, lo primero que debemos hacer es mirar los datos de producción con ojos de detective.

En este post trabajamos con datos reales del campo HOMOL obtenidos del portal de la antigua CNH: 12 pozos productores de aceite con producción mensual de aceite, gas y agua desde enero 2007 hasta abril 2021. El objetivo es construir paso a paso los gráficos diagnóstico que todo ingeniero debería tener antes de tomar cualquier decisión sobre un yacimiento.

Referencia técnica

Los tipos de gráficos que presentamos están basados en la Tabla 4.2.2 de Practical Reservoir Engineering and Characterization (Baker, Yarranton & Jensen, 2015), que es una referencia completa y práctica para análisis de datos de producción.

Los Datos

Trabajamos con un archivo CSV con la siguiente estructura:

Columna Descripción
Well Nombre del pozo (12 pozos: HOMOL-1 a HOMOL-63)
Fluid Tipo de fluido: Qo (aceite), Qg (gas), Qw (agua)
Date Fecha mensual (DD/MM/YYYY)
Rate Tasa de producción diaria

Resumen del campo:

  • Pozos activos: 12 productores
  • Período: Enero 2007 – Abril 2021 (172 meses)
  • Producción acumulada de aceite (Np): ~5.9 millones de barriles
  • Producción acumulada de agua (Wp): ~229 mil barriles
  • Producción acumulada de gas (Gp): ~6.9 millones de Mscf
  • Pico de producción del campo: ~60,329 bbl/d (septiembre 2014)
Ver código
import pandas as pd
import numpy as np

# Cargar datos
df = pd.read_csv('Production_Data.csv')
df['Date'] = pd.to_datetime(df['Date'], format='%d/%m/%Y')
df['Rate'] = pd.to_numeric(df['Rate'], errors='coerce').fillna(0)

# Pivotar a formato ancho
pivot = df.pivot_table(index=['Well','Date'], columns='Fluid', 
                       values='Rate', aggfunc='sum').reset_index()
pivot.columns.name = None
pivot = pivot.fillna(0)

# Totales del campo por mes
field = pivot.groupby('Date')[['Qo','Qg','Qw']].sum().reset_index()
field = field.sort_values('Date')

# Calcular acumuladas
field['Np'] = field['Qo'].cumsum()
field['Gp'] = field['Qg'].cumsum()
field['Wp'] = field['Qw'].cumsum()

# Calcular ratios (evitar división por cero)
field['GOR'] = np.where(field['Qo'] > 0, field['Qg'] / field['Qo'], 0)
field['WOR'] = np.where(field['Qo'] > 0, field['Qw'] / field['Qo'], 0)
field['WC']  = np.where((field['Qo'] + field['Qw']) > 0, 
                         field['Qw'] / (field['Qo'] + field['Qw']) * 100, 0)
field['Qf']  = field['Qo'] + field['Qw']  # Tasa de fluido total

# Conteo de pozos activos por mes
well_count = pivot[pivot['Qo'] > 0].groupby('Date')['Well'].nunique().reset_index()
well_count.columns = ['Date', 'WellCount']
field = field.merge(well_count, on='Date', how='left').fillna(0)

print(f"✅ Datos cargados: {len(field)} meses, {pivot['Well'].nunique()} pozos")
✅ Datos cargados: 172 meses, 12 pozos
Ver código
print(f"📊 Rango: {field['Date'].min().strftime('%b %Y')} - {field['Date'].max().strftime('%b %Y')}")
📊 Rango: ene. 2007 - abr. 2021

Gráfico 1 — Composite Plot vs Tiempo (Semilog)

El composite plot es el gráfico más importante en el análisis de producción. Combina en un solo panel el gasto de aceite, GOR, corte de agua y conteo de pozos vs tiempo.

¿Por qué semilog?

La escala semi-logarítmica en el eje Y en el gasto de aceite permite identificar fácilmente las tendencias de declinación exponencial (aparecen como líneas rectas) y detectar cambios de régimen que serían invisibles en escala lineal.

¿Qué buscar en este gráfico?

  • Tendencias de declinación en el gasto de aceite
  • Incrementos de GOR → posible liberación de gas en el yacimiento (por debajo de Pb)
  • Incremento de corte de agua → avance del frente de agua
  • Correlación con conteo de pozos → ¿el incremento de producción es por nuevos pozos o por mejora de los existentes?
Ver código
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

fig, axes = plt.subplots(4, 1, figsize=(12, 14), sharex=True, 
                          gridspec_kw={'height_ratios': [3, 1.5, 1.5, 1]})
fig.suptitle('CAMPO HOMOL — Composite Plot de Producción', 
             fontsize=16, fontweight='bold', y=0.98)

# Panel 1: Gasto de aceite (semilog) + gasto de fluido
ax1 = axes[0]
mask = field['Qo'] > 0
ax1.semilogy(field.loc[mask, 'Date'], field.loc[mask, 'Qo'], 
             '-', linewidth=1.5, label='Qo (aceite)', color='#2d8a4e')
mask_f = field['Qf'] > 0
ax1.semilogy(field.loc[mask_f, 'Date'], field.loc[mask_f, 'Qf'], 
             '--', linewidth=1, label='Qf (fluido total)', color='#1a5276', alpha=0.6)
ax1.set_ylabel('Tasa (bbl/d)', fontsize=11)
ax1.legend(loc='upper left', fontsize=9)
ax1.grid(True, which='both', alpha=0.3)
ax1.set_ylim(bottom=100)
(100, np.float64(80283.5515235902))
Ver código
# Eje secundario: conteo de pozos
ax1b = ax1.twinx()
ax1b.bar(field['Date'], field['WellCount'], width=25, alpha=0.15, color='gray', label='Pozos activos')
ax1b.set_ylabel('Pozos activos', fontsize=10, color='gray')
ax1b.tick_params(axis='y', labelcolor='gray')
ax1b.set_ylim(0, 20)
(0.0, 20.0)
Ver código
ax1b.legend(loc='upper right', fontsize=9)

# Panel 2: GOR
ax2 = axes[1]
ax2.plot(field.loc[mask, 'Date'], field.loc[mask, 'GOR'], 
         '-', linewidth=1.2, color='#c0392b')
ax2.set_ylabel('GOR', fontsize=11)
ax2.grid(True, alpha=0.3)
ax2.axhline(y=field.loc[mask, 'GOR'].median(), color='#c0392b', 
            linestyle='--', alpha=0.4, linewidth=0.8)

# Panel 3: Corte de agua
ax3 = axes[2]
ax3.plot(field.loc[mask, 'Date'], field.loc[mask, 'WC'], 
         '-', linewidth=1.2, color='#2980b9')
ax3.fill_between(field.loc[mask, 'Date'], 0, field.loc[mask, 'WC'], alpha=0.1, color='#2980b9')
ax3.set_ylabel('Corte de agua (%)', fontsize=11)
ax3.set_ylim(0, 100)
(0.0, 100.0)
Ver código
ax3.grid(True, alpha=0.3)

# Panel 4: WOR
ax4 = axes[3]
mask_wor = (field['WOR'] > 0) & (field['Qo'] > 0)
ax4.semilogy(field.loc[mask_wor, 'Date'], field.loc[mask_wor, 'WOR'], 
             '-', linewidth=1.2, color='#8e44ad')
ax4.set_ylabel('WOR', fontsize=11)
ax4.set_xlabel('Fecha', fontsize=11)
ax4.grid(True, which='both', alpha=0.3)
ax4.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
ax4.xaxis.set_major_locator(mdates.YearLocator(2))

plt.tight_layout()
plt.show()
Figure 1: Composite plot semilog del campo HOMOL. Panel superior: gasto de aceite y conteo de pozos. Panel medio: GOR. Panel inferior: corte de agua.

Gráfico 2 — Composite Plot vs Np (Producción Acumulada)

Este gráfico es complementario al anterior, pero usa Np en el eje X en lugar del tiempo. Esto elimina los efectos de cierres temporales y permite ver directamente cuánto aceite se ha extraído.

¿Qué buscar?

  • La pendiente de la curva qo vs Np indica la tasa de declinación
  • El GOR vs Np muestra si hay liberación de gas proporcional a la extracción
  • El WOR vs Np permite extrapolar el factor de recuperación final
Ver código
fig, axes = plt.subplots(3, 1, figsize=(12, 11), sharex=True,
                          gridspec_kw={'height_ratios': [3, 1.5, 1.5]})
fig.suptitle('CAMPO HOMOL — Producción vs Np Acumulado', 
             fontsize=16, fontweight='bold', y=0.98)

Np_MM = field['Np'] / 1e6  # En millones

# Panel 1: qo vs Np
ax1 = axes[0]
ax1.semilogy(Np_MM[mask], field.loc[mask, 'Qo'], 
             '-', linewidth=1.5, color='#2d8a4e')
ax1.set_ylabel('Qo (bbl/d)', fontsize=11)
ax1.grid(True, which='both', alpha=0.3)
ax1.set_ylim(bottom=100)
(100, np.float64(80229.79027261313))
Ver código
# Panel 2: GOR vs Np
ax2 = axes[1]
ax2.plot(Np_MM[mask], field.loc[mask, 'GOR'], 
         '-', linewidth=1.2, color='#c0392b')
ax2.set_ylabel('GOR', fontsize=11)
ax2.grid(True, alpha=0.3)

# Panel 3: WC vs Np
ax3 = axes[2]
ax3.plot(Np_MM[mask], field.loc[mask, 'WC'], 
         '-', linewidth=1.2, color='#2980b9')
ax3.fill_between(Np_MM[mask], 0, field.loc[mask, 'WC'], alpha=0.1, color='#2980b9')
ax3.set_ylabel('Corte de agua (%)', fontsize=11)
ax3.set_xlabel('Np acumulado (MMbbl)', fontsize=11)
ax3.set_ylim(0, 100)
(0.0, 100.0)
Ver código
ax3.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
Figure 2: Composite plot vs producción acumulada de aceite (Np).

Gráfico 3 — Producción Acumulada vs Tiempo

El gráfico de acumuladas permite verificar la consistencia de los datos y relacionar la declinación de producción con los volúmenes totales extraídos.

Ver código
fig, ax1 = plt.subplots(figsize=(12, 6))
fig.suptitle('CAMPO HOMOL — Producción Acumulada', fontsize=16, fontweight='bold')

ax1.plot(field['Date'], field['Np']/1e6, '-', linewidth=2, color='#2d8a4e', label='Np (aceite)')
ax1.plot(field['Date'], field['Wp']/1e6, '-', linewidth=2, color='#2980b9', label='Wp (agua)')
ax1.set_ylabel('Producción acumulada (MMbbl)', fontsize=11)
ax1.legend(loc='upper left', fontsize=10)
ax1.grid(True, alpha=0.3)

ax2 = ax1.twinx()
ax2.plot(field['Date'], field['Gp']/1e6, '-', linewidth=2, color='#c0392b', label='Gp (gas)')
ax2.set_ylabel('Gas acumulado (MMscf)', fontsize=11, color='#c0392b')
ax2.tick_params(axis='y', labelcolor='#c0392b')
ax2.legend(loc='center left', fontsize=10)

ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tight_layout()
plt.show()
Figure 3: Producción acumulada de aceite, gas y agua del campo HOMOL.

Gráfico 4 — Purvis Plot (Diagnóstico de Water Drive)

El Purvis plot es una herramienta poderosa para yacimientos con empuje de agua o inyección de agua. Combina en un solo gráfico semi-log: la producción acumulada de fluido, el gasto diario de fluido, el gasto diario de aceite, y el WOR+1, todo vs Np acumulado.

Si el WOR+1 forma una línea recta en escala semilog vs Np, se puede extrapolar para pronosticar la producción futura asumiendo tasa de fluido constante:

\[q_o = \frac{q_f}{1 + WOR}\]

Ver código
fig, ax1 = plt.subplots(figsize=(12, 7))
fig.suptitle('CAMPO HOMOL — Purvis Plot', fontsize=16, fontweight='bold')

Np_MM = field['Np'] / 1e6
mask_prod = field['Qo'] > 0

# WOR + 1 en escala semilog
wor_plus1 = field.loc[mask_prod, 'WOR'] + 1
ax1.semilogy(Np_MM[mask_prod], wor_plus1, 'o-', markersize=2, 
             linewidth=1.2, color='#8e44ad', label='WOR + 1')

# Gasto de aceite
ax1.semilogy(Np_MM[mask_prod], field.loc[mask_prod, 'Qo'], 
             '-', markersize=1.5, linewidth=1, color='#2d8a4e', label='Qo (bbl/d)')

# Gasto de fluido
ax1.semilogy(Np_MM[mask_prod], field.loc[mask_prod, 'Qf'], 
             '-', markersize=1.5, linewidth=1, color='#2980b9', label='Qf (bbl/d)', alpha=0.7)

ax1.set_xlabel('Np acumulado (MMbbl)', fontsize=12)
ax1.set_ylabel('Escala logarítmica', fontsize=12)
ax1.legend(fontsize=10)
ax1.grid(True, which='both', alpha=0.3)

plt.tight_layout()
plt.show()
Figure 4: Purvis plot del campo HOMOL. La tendencia de WOR+1 vs Np permite pronosticar producción futura.

Gráfico 5 — Gp vs Np (Diagnóstico de Solution Gas Drive)

Para yacimientos con empuje por gas en solución, el gráfico de Gp vs Np en escala log-log muestra una tendencia lineal que permite pronosticar la recuperación final. La idea es que cuando se ha producido ~90% del gas en solución, la presión del yacimiento es tan baja que la producción se vuelve antieconómica.

Ver código
fig, ax = plt.subplots(figsize=(10, 7))
fig.suptitle('CAMPO HOMOL — Gp vs Np (Diagnóstico Solution Gas Drive)', 
             fontsize=15, fontweight='bold')

mask_both = (field['Np'] > 0) & (field['Gp'] > 0)
ax.loglog(field.loc[mask_both, 'Np']/1e6, field.loc[mask_both, 'Gp']/1e6, 
          '-', markersize=3, linewidth=1.2, color='#c0392b')

ax.set_xlabel('Np acumulado (MMbbl)', fontsize=12)
ax.set_ylabel('Gp acumulado (MMscf)', fontsize=12)
ax.grid(True, which='both', alpha=0.3)

plt.tight_layout()
plt.show()
Figure 5: Gp vs Np en escala log-log. Una tendencia lineal sugiere empuje por gas en solución.

Gráfico 6 — Producción Individual por Pozo (Stacked Area)

Para el análisis de producción es fundamental ver la contribución de cada pozo a la producción total del campo. El gráfico de área apilada permite identificar qué pozos son los principales contribuyentes y cuándo entran/salen de producción.

Ver código
# Preparar datos por pozo
well_monthly = pivot.pivot_table(index='Date', columns='Well', values='Qo', aggfunc='sum').fillna(0)
well_monthly = well_monthly.sort_index()

# Ordenar pozos por producción total (mayor a menor) para mejor visualización
well_order = well_monthly.sum().sort_values(ascending=False).index.tolist()
well_monthly = well_monthly[well_order]

fig, ax = plt.subplots(figsize=(14, 7))
fig.suptitle('CAMPO HOMOL — Producción de Aceite por Pozo (Área Apilada)', 
             fontsize=15, fontweight='bold')

colors = plt.cm.tab20(np.linspace(0, 1, len(well_order)))
ax.stackplot(well_monthly.index, well_monthly.T, labels=well_order, colors=colors, alpha=0.8)

ax.set_ylabel('Qo total (bbl/d)', fontsize=12)
ax.set_xlabel('Fecha', fontsize=12)
ax.legend(loc='upper right', fontsize=8, ncol=3, framealpha=0.9)
ax.grid(True, alpha=0.2)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))

plt.tight_layout()
plt.show()
Figure 6: Contribución de cada pozo a la producción total del campo.

Gráfico 7 — Panel Individual por Pozo (Ejemplo)

Para un análisis detallado, cada pozo necesita su propio mini-dashboard con gasto de aceite, GOR y corte de agua. Aquí mostramos el pozo HOMOL-1 como ejemplo.

Ver código
well_name = 'HOMOL-1'
wdf = pivot[pivot['Well'] == well_name].sort_values('Date').copy()
wdf['GOR'] = np.where(wdf['Qo'] > 0, wdf['Qg'] / wdf['Qo'], 0)
wdf['WC'] = np.where((wdf['Qo'] + wdf['Qw']) > 0, 
                      wdf['Qw'] / (wdf['Qo'] + wdf['Qw']) * 100, 0)

fig, axes = plt.subplots(3, 1, figsize=(12, 10), sharex=True)
fig.suptitle(f'Pozo {well_name} — Panel de Producción', fontsize=15, fontweight='bold')

mask_w = wdf['Qo'] > 0

axes[0].semilogy(wdf.loc[mask_w, 'Date'], wdf.loc[mask_w, 'Qo'], 
                 'g-', linewidth=1.5, color='#2d8a4e')
axes[0].set_ylabel('Qo (bbl/d)')
axes[0].grid(True, which='both', alpha=0.3)

axes[1].plot(wdf.loc[mask_w, 'Date'], wdf.loc[mask_w, 'GOR'], 
             '-', linewidth=1.2, color='#c0392b')
axes[1].set_ylabel('GOR')
axes[1].grid(True, alpha=0.3)

axes[2].plot(wdf.loc[mask_w, 'Date'], wdf.loc[mask_w, 'WC'], 
             '-', linewidth=1.2, color='#2980b9')
axes[2].fill_between(wdf.loc[mask_w, 'Date'], 0, wdf.loc[mask_w, 'WC'], 
                     alpha=0.1, color='#2980b9')
axes[2].set_ylabel('Corte de agua (%)')
axes[2].set_ylim(0, 100)
(0.0, 100.0)
Ver código
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
Figure 7: Panel de producción del pozo HOMOL-1.

Resumen de la Tabla 4.2.2 — Tipos de Gráficos de Producción

La siguiente tabla resume los tipos de gráficos de producción recomendados por Baker et al. (2015), que son los que hemos implementado en este post:

Tipos de gráficos de producción para yacimientos de aceite. Adaptado de Tabla 4.2.2, Baker et al. (2015).
Gráfico Eje Y Eje X Propósito
Composite qo, qf, GOR, WC, pozos Tiempo o Np Diagnosticar mecanismo de empuje, detectar breakthrough, identificar candidatos a workover
Acumulado Np, Gp, Wp, Wi, Gi Tiempo o Np Relacionar declinación con volúmenes extraídos
Gp vs Np Gp (log) Np (log) Pronóstico en yacimientos de gas en solución
Purvis WOR+1, Qf, Qo, Np_fluido (log) Np Pronóstico en water drive / waterfloods
Ershaghi 1/fw − ln(1−fw)⁻¹ Np Pronóstico alternativo en waterfloods

Diagnóstico Preliminar del Campo HOMOL

A partir de los gráficos generados, podemos hacer las siguientes observaciones iniciales:

  1. Fase de desarrollo activa (2007-2015): Se observa un crecimiento sostenido de producción conforme se van incorporando pozos (de 1 a 12 pozos)
  2. Pico de producción en 2014: ~60,000 bbl/d con 10+ pozos activos
  3. GOR relativamente estable: Sugiere que el yacimiento se mantiene por encima o cerca de la presión de burbuja, o que el gas producido es principalmente gas en solución
  4. Corte de agua bajo pero creciente: La producción de agua es relativamente baja (< 5% a nivel de campo) pero algunos pozos individuales muestran mayor producción de agua (HOMOL-3, HOMOL-47, HOMOL-62)
  5. Declinación post-2015: Se observa una declinación clara después del pico, posiblemente por agotamiento natural sin inyección de agua aparente
Siguiente paso

Con estos gráficos como base, se puede continuar con el análisis de curvas de declinación para estimar reservas remanentes.

Referencias

  • Baker, R.O., Yarranton, H.W., & Jensen, J.L. (2015). Practical Reservoir Engineering and Characterization. Gulf Professional Publishing. Capítulo 4: Pool History — Tabla 4.2.2.