In [1]:
import plotly.offline as pyo
pyo.init_notebook_mode(connected=True)

Informe Ejecutivo: Estado y Proyección de Compensaciones Ambientales¶


Este panel de control consolida el estado operativo y financiero de las obligaciones de compensación. El objetivo de este informe es proporcionar a la gerencia una visión clara sobre:

  1. Panel de Control BI
  2. El nivel de cumplimiento físico (Siembra y Mantenimiento).
  3. Los requerimientos de flujo de caja proyectados hasta 2031.
  4. La eficiencia del gasto por individuo forestal y método de compensación.
  5. Distribución Geográfica de Proyectos
  6. Panel de Alertas y Riesgos de Incumplimiento
  7. Cronograma de Ejecución y Vencimiento Legal
  8. Análisis de Desviaciones Presupuestales (Outliers)
  9. Auditoría Automática de Mantenimientos (Text Mining)
  10. Segmentación Inteligente de Proyectos (Machine Learning)
  11. Modelo Predictivo de Presupuestación (Random Forest Regressor)
In [17]:
import pandas as pd
import plotly.express as px
import numpy as np

# 1. Carga de datos
df = pd.read_csv('compensaciones.csv', sep=';')

# 2. Estandarización de columnas
df.columns = [
    'Proyecto', 'Permiso', 'Expediente', 'Resolucion', 'Origen', 'Requerimiento', 
    'Arboles', 'Area_ha', 'Metodo', 'Fase_Analisis', 'Fase_Concertacion', 'Sitio', 
    'Avance_Siembra', 'Fecha_Siembra', 'Anos_Seguimiento', 'Avance_Seguimiento', 
    'Entrega_Autoridad', 'Cierre', 'Observaciones', '2026', '2027', '2028', '2029', '2030', '2031', 'Costo_Total'
]

# 3. Limpieza de formatos numéricos (monedas y porcentajes)
def clean_num(x):
    if pd.isna(x) or str(x).strip() in ['-', '', 'nan']: return 0.0
    s = str(x).replace('%', '').replace('$', '').replace(' ', '').replace('.', '').replace(',', '.')
    try: return float(s)
    except: return 0.0

cols_num = ['2026', '2027', '2028', '2029', '2030', '2031', 'Costo_Total', 'Arboles']
for col in cols_num: df[col] = df[col].apply(clean_num)

# Ajuste crítico para el tamaño de las burbujas (evitar errores de NaN o 0)
df['Anos_Seguimiento'] = df['Anos_Seguimiento'].fillna(0)
df['Size_Visual'] = df['Anos_Seguimiento'] + 1 

df['Avance_Siembra'] = df['Avance_Siembra'].apply(clean_num) / 100
df['Avance_Seguimiento'] = df['Avance_Seguimiento'].apply(clean_num) / 100

# 4. Lógica de Negocio: Semáforo de Estado
def determinar_salud(row):
    if row['Cierre'] == 1: return '✅ Finalizado y Cerrado'
    if row['Avance_Siembra'] == 1.0 and row['Avance_Seguimiento'] < 1.0: return '⚠️ En Mantenimiento'
    if row['Avance_Siembra'] > 0: return '🌱 En Ejecución'
    return '⏱️ Pendiente de Inicio'

df['Estado_Salud'] = df.apply(determinar_salud, axis=1)
print("✅ Motor de datos inicializado correctamente. Datos listos para análisis.")
✅ Motor de datos inicializado correctamente. Datos listos para análisis.

1. Panel de Control General (Vista BI)¶

Este tablero consolida las métricas de más alto nivel para revisión ejecutiva. Presenta un resumen de la inversión, el volumen operativo y la distribución de las obligaciones mediante:

  1. Tarjetas KPI: Indicadores macro de la operación.
  2. Distribución por Método: Peso de la rehabilitación vs rescate.
  3. Top Proyectos: Concentración del esfuerzo operativo.
In [33]:
import pandas as pd
from plotly.subplots import make_subplots
import plotly.graph_objects as go

# ==========================================
# 1. CARGA Y LIMPIEZA SEGURA 
# ==========================================
df = pd.read_csv('compensaciones.csv', sep=';')
df.columns = [
    'Proyecto', 'Permiso', 'Expediente', 'Resolucion', 'Origen', 'Requerimiento', 
    'Arboles', 'Area_ha', 'Metodo', 'Fase_Analisis', 'Fase_Concertacion', 'Sitio', 
    'Avance_Siembra', 'Fecha_Siembra', 'Anos_Seguimiento', 'Avance_Seguimiento', 
    'Entrega_Autoridad', 'Cierre', 'Observaciones', '2026', '2027', '2028', '2029', '2030', '2031', 'Costo_Total'
]

def clean_num_safe(x):
    if pd.isna(x) or str(x).strip() in ['-', '', 'nan']: return 0.0
    if isinstance(x, (int, float)): return float(x)
    s = str(x).replace('%', '').replace('$', '').replace(' ', '').replace('.', '').replace(',', '.')
    try: return float(s)
    except: return 0.0

cols_num = ['Costo_Total', 'Arboles', 'Anos_Seguimiento', '2026', '2027', '2028', '2029', '2030', '2031']
for col in cols_num: df[col] = df[col].apply(clean_num_safe)

df['Avance_Siembra'] = df['Avance_Siembra'].apply(clean_num_safe) / 100
df['Avance_Seguimiento'] = df['Avance_Seguimiento'].apply(clean_num_safe) / 100

def determinar_salud(row):
    if row['Cierre'] == 1: return '✅ Cerrado'
    if row['Avance_Siembra'] >= 1.0 and row['Avance_Seguimiento'] < 1.0: return '⚠️ Mantenimiento'
    if row['Avance_Siembra'] > 0: return '🌱 Ejecución'
    return '⏱️ Pendiente'
df['Estado_Salud'] = df.apply(determinar_salud, axis=1)

# ==========================================
# 2. CALCULAR KPIs GLOBALES
# ==========================================
presupuesto_kpi = df['Costo_Total'].sum()
avance_global_kpi = df['Avance_Siembra'].mean() * 100

# ==========================================
# 3. DASHBOARD: KPIs ARRIBA, BARRAS FULL ANCHO, DONUTS ABAJO
# ==========================================
fig_bi = make_subplots(
    rows=4, cols=2,
    vertical_spacing=0.1,
    row_heights=[0.2, 0.25, 0.25, 0.3], # Distribuimos el alto de cada fila
    specs=[
        [{"type": "indicator"}, {"type": "indicator"}],       # Fila 1: KPIs
        [{"type": "xy", "colspan": 2}, None],                 # Fila 2: Barras (Ocupa 2 columnas)
        [{"type": "xy", "colspan": 2}, None],                 # Fila 3: Barras (Ocupa 2 columnas)
        [{"type": "domain"}, {"type": "domain"}]              # Fila 4: Donuts (1 por columna)
    ],
    subplot_titles=(
        "", "", # Sin títulos para los KPIs
        "Top 5: Mayor Presupuesto Asignado ($)", 
        "Top 5: Mayor Volumen de Siembra",
        "Distribución por Método", 
        "Salud Legal del Portafolio"
    )
)

# --- FILA 1: INDICADORES (KPIs) ---
fig_bi.add_trace(go.Indicator(
    mode="number", value=presupuesto_kpi,
    number={'prefix': "$", 'valueformat': ",.0f", 'font': {'size': 45, 'color': '#2c3e50'}},
    title={'text': "Inversión Total Requerida", 'font': {'size': 18}}
), row=1, col=1)

fig_bi.add_trace(go.Indicator(
    mode="number", value=avance_global_kpi,
    number={'suffix': "%", 'valueformat': ".1f", 'font': {'size': 45, 'color': '#18bc9c'}},
    title={'text': "Avance Promedio de Siembra", 'font': {'size': 18}}
), row=1, col=2)

# --- FILA 2 & 3: BARRAS HORIZONTALES (ANCHO COMPLETO) ---
top_costos = df.nlargest(5, 'Costo_Total').sort_values('Costo_Total', ascending=True)
fig_bi.add_trace(go.Bar(y=top_costos['Proyecto'], x=top_costos['Costo_Total'], orientation='h',
                        marker_color='#34495e', text=top_costos['Costo_Total'], texttemplate='$%{text:,.0f}'), row=2, col=1)

top_arboles = df.nlargest(5, 'Arboles').sort_values('Arboles', ascending=True)
fig_bi.add_trace(go.Bar(y=top_arboles['Proyecto'], x=top_arboles['Arboles'], orientation='h',
                        marker_color='#18bc9c', text=top_arboles['Arboles'], texttemplate='%{text:,.0f} und'), row=3, col=1)

# --- FILA 4: GRÁFICOS DE ANILLO (DONUTS) ---
resumen_metodo = df.groupby('Metodo')['Arboles'].sum().reset_index()
fig_bi.add_trace(go.Pie(labels=resumen_metodo['Metodo'], values=resumen_metodo['Arboles'], hole=0.45, 
                        marker_colors=['#2c3e50', '#18bc9c', '#3498db']), row=4, col=1)

resumen_salud = df['Estado_Salud'].value_counts().reset_index()
fig_bi.add_trace(go.Pie(labels=resumen_salud['Estado_Salud'], values=resumen_salud['count'], hole=0.45,
                        marker_colors=['#2ecc71', '#f1c40f', '#e74c3c', '#3498db']), row=4, col=2)

# --- AJUSTES ESTÉTICOS FINALES ---
fig_bi.update_layout(
    title_text="📊 Panel Integral de Inteligencia de Negocios (BI)",
    title_font=dict(size=26, color='#2c3e50'),
    height=1200, # Aumentamos la altura para que las barras respiren bien
    showlegend=False,
    plot_bgcolor='rgba(240, 244, 248, 1)',
    paper_bgcolor='white',
    margin=dict(l=40, r=40, t=100, b=40)
)

fig_bi.show()
fig_bi.write_html("Dashboard_Gerencial_Final.html", include_plotlyjs="cdn")

2. Estado Operativo y Alertas de Mantenimiento¶

El siguiente gráfico detalla la carga operativa actual. Es crítico observar los proyectos marcados en amarillo (⚠️ En Mantenimiento), ya que representan obligaciones donde la siembra se ejecutó al 100%, pero el monitoreo legal ante la autoridad ambiental sigue abierto y requiere visitas de campo activas.

In [32]:
fig_arboles = px.bar(df[df['Arboles'] > 0], x='Proyecto', y='Arboles', color='Estado_Salud',
                    title='Capacidad Operativa: Árboles por Proyecto y Estado Actual',
                    text_auto='.2s', color_discrete_map={
                        '✅ Finalizado y Cerrado': '#2ecc71',
                        '⚠️ En Mantenimiento': '#f1c40f',
                        '🌱 En Ejecución': '#3498db',
                        '⏱️ Pendiente de Inicio': '#e74c3c'
                    })
fig_arboles.update_layout(xaxis_title="Nombre del Proyecto", yaxis_title="Cantidad de Individuos Arbóreos")
fig_arboles.show()

3. Proyección Financiera (2026 - 2031)¶

Para garantizar el cumplimiento de las resoluciones, es necesario asegurar la liquidez en los años de mayor intensidad operativa. Esta curva muestra la consolidación presupuestal requerida año a año para cubrir siembras, replanteos y visitas de mantenimiento de todo el portafolio.

In [4]:
historico_costos = df[['2026', '2027', '2028', '2029', '2030', '2031']].sum()
fig_presupuesto = px.line(x=historico_costos.index, y=historico_costos.values, 
                         title='Cronograma Financiero: Necesidad de Recursos por Año',
                         markers=True, line_shape='spline')

fig_presupuesto.update_traces(fill='tozeroy', line_color='#2c3e50')
fig_presupuesto.update_layout(xaxis_title="Año Fiscal", yaxis_title="Presupuesto Requerido ($)")
fig_presupuesto.show()

4. Matriz de Eficiencia (Costo vs. Volumen)¶

Este análisis permite a la gerencia identificar sobrecostos.

  • El eje horizontal representa la escala del proyecto (cantidad de árboles).
  • El eje vertical representa el costo total acumulado.
  • El tamaño de la burbuja indica la cantidad de años que estamos obligados a hacerle seguimiento.
  • Nota: Los proyectos que se ubiquen muy arriba y a la izquierda representan un costo unitario desproporcionadamente alto y deben ser auditados.
In [5]:
df_plot = df[(df['Arboles'] > 0) | (df['Costo_Total'] > 0)].copy()

fig_bubble = px.scatter(df_plot, 
                        x='Arboles', 
                        y='Costo_Total', 
                        size='Size_Visual', 
                        color='Metodo', 
                        hover_name='Proyecto',
                        hover_data={'Anos_Seguimiento': True, 'Size_Visual': False}, 
                        title='Análisis de Costo-Eficiencia por Método de Compensación')

fig_bubble.update_layout(xaxis_title="N° de Árboles Sembrados", yaxis_title="Presupuesto Total del Proyecto ($)")
fig_bubble.show()
fig_bubble.write_html("Dashboard_Gerencial_Compensaciones3.html", include_plotlyjs="cdn")

5. Distribución Geográfica de Proyectos¶

El siguiente mapa interactivo georreferencia las obligaciones ambientales activas.

  • El color del marcador indica el estado actual del proyecto (verde para en ejecución, amarillo para mantenimiento).
  • El tamaño del marcador es proporcional a la cantidad de árboles comprometidos. Esta vista es fundamental para optimizar la logística de las cuadrillas de monitoreo y reducir costos de desplazamiento agrupando visitas a predios cercanos.
In [6]:
import pandas as pd
import plotly.express as px

# 1. Diccionario de Coordenadas Aproximadas (Lat, Lon) para Cundinamarca
coordenadas_base = {
    'tocancipá': (4.965, -73.914),
    'soacha': (4.577, -74.220),
    'ricaurte': (4.281, -74.772),
    'salgar': (5.463, -74.654),
    'tibacuy': (4.348, -74.453),
    'bogotá': (4.711, -74.141),
    'mámbita': (4.766, -73.323),
    'ubalá': (4.735, -73.535),
    'torca': (4.795, -74.041)
}

# 2. Función para asignar coordenadas basadas en el texto de la columna 'Sitio'
def asignar_coordenadas(sitio, eje):
    sitio_texto = str(sitio).lower()
    for clave, (lat, lon) in coordenadas_base.items():
        if clave in sitio_texto:
            return lat if eje == 'lat' else lon
    # Si no encuentra el lugar, lo pone cerca a Bogotá por defecto
    return 4.609 if eje == 'lat' else -74.081 

# Aplicamos la función a nuestro DataFrame (asumiendo que ya corriste la limpieza anterior)
df['Latitud'] = df['Sitio'].apply(lambda x: asignar_coordenadas(x, 'lat'))
df['Longitud'] = df['Sitio'].apply(lambda x: asignar_coordenadas(x, 'lon'))

# Filtramos solo proyectos que tengan árboles para no ensuciar el mapa
df_mapa = df[df['Arboles'] > 0].copy()

# 3. CREACIÓN DEL MAPA INTERACTIVO
fig_mapa = px.scatter_mapbox(
    df_mapa, 
    lat="Latitud", 
    lon="Longitud", 
    hover_name="Proyecto", 
    hover_data={"Sitio": True, "Arboles": True, "Estado_Salud": True, "Latitud": False, "Longitud": False},
    color="Estado_Salud",
    size="Arboles",
    color_discrete_map={
        '✅ Finalizado y Cerrado': '#2ecc71',
        '⚠️ En Mantenimiento': '#f1c40f',
        '🌱 En Ejecución': '#3498db',
        '⏱️ Pendiente de Inicio': '#e74c3c'
    },
    zoom=7, 
    height=600,
    title="Georreferenciación de Compensaciones Forestales"
)

# Usamos un estilo de mapa gratuito y limpio
fig_mapa.update_layout(mapbox_style="carto-positron")
fig_mapa.update_layout(margin={"r":0,"t":40,"l":0,"b":0})

fig_mapa.show()
fig_mapa.write_html("Dashboard_Gerencial_Compensaciones2.html", include_plotlyjs="cdn")
C:\Users\aleja\AppData\Local\Temp\ipykernel_36632\512677222.py:34: DeprecationWarning: *scatter_mapbox* is deprecated! Use *scatter_map* instead. Learn more at: https://plotly.com/python/mapbox-to-maplibre/
  fig_mapa = px.scatter_mapbox(

6. Panel de Alertas y Riesgos de Incumplimiento¶

Esta sección filtra y destaca únicamente las obligaciones que requieren acción inmediata para evitar vencimientos de términos legales:

  • Fondo Naranja (En Mantenimiento): Árboles sembrados pero requieren visitas de seguimiento obligatorias para no perder la supervivencia exigida.
  • Fondo Rojo (Pendiente de Inicio): Proyectos con atraso operativo. Tienen un presupuesto asignado pero no reportan avance de siembra.
In [7]:
import plotly.graph_objects as go

# 1. Filtrar solo los proyectos que son un riesgo o requieren atención
estados_alerta = ['⚠️ En Mantenimiento', '⏱️ Pendiente de Inicio']
df_alertas = df[df['Estado_Salud'].isin(estados_alerta)].copy()

# 2. Ordenar para que los más urgentes salgan arriba
# Ordenamos por estado y luego por los que tienen más árboles comprometidos
df_alertas = df_alertas.sort_values(by=['Estado_Salud', 'Arboles'], ascending=[False, False])

# 3. Asignar colores de fondo según el tipo de alerta
colores_fondo = []
for estado in df_alertas['Estado_Salud']:
    if 'Mantenimiento' in estado:
        colores_fondo.append('#ffeeba') # Naranja claro/Amarillo (Alerta Media)
    else:
        colores_fondo.append('#f5c6cb') # Rojo claro (Alerta Alta)

# 4. Crear la Tabla Visual Interactiva
fig_alertas = go.Figure(data=[go.Table(
    header=dict(
        values=['<b>Proyecto (Sitio)</b>', '<b>Nivel de Alerta</b>', '<b>Árboles</b>', '<b>Años Seguimiento</b>', '<b>Presupuesto Total</b>'],
        fill_color='#2c3e50', # Azul oscuro corporativo
        font=dict(color='white', size=14),
        align='left'
    ),
    cells=dict(
        values=[
            df_alertas['Proyecto'] + " (" + df_alertas['Sitio'].fillna('N/A') + ")", 
            df_alertas['Estado_Salud'], 
            df_alertas['Arboles'].apply(lambda x: f"{x:,.0f}"), 
            df_alertas['Anos_Seguimiento'].apply(lambda x: f"{x:.0f} años"), 
            df_alertas['Costo_Total'].apply(lambda x: f"${x:,.0f}")
        ],
        fill_color=[colores_fondo],
        font=dict(color='#333333', size=13),
        align='left',
        height=30
    ))
])

# Ajustar el diseño
fig_alertas.update_layout(
    title='🚨 Panel de Alertas Tempranas: Proyectos que requieren acción',
    margin=dict(l=20, r=20, t=50, b=20),
    height=400
)

fig_alertas.show()
fig_alertas.write_html("Dashboard_Gerencial_Compensaciones1.html", include_plotlyjs="cdn")

7. Cronograma de Ejecución y Vencimiento Legal¶

Para el control regulatorio, es vital conocer la "ventana de tiempo" de cada obligación. Este diagrama de líneas de tiempo (Gantt) calcula automáticamente la fecha estimada de finalización del proyecto sumando los años de seguimiento obligatorios a la fecha de ejecución de la siembra.

  • Nota: Los proyectos más extendidos hacia la derecha son los compromisos a más largo plazo.
In [9]:
import plotly.express as px
import pandas as pd

# 1. Preparar las fechas (Ingeniería de Datos)
# Convertimos el texto '01/09/2020' a un formato de Fecha real de Python
df['Fecha_Inicio'] = pd.to_datetime(df['Fecha_Siembra'], format='%d/%m/%Y', errors='coerce')

# Calculamos la fecha de fin sumando los años de seguimiento (1 año = ~365.25 días)
df['Fecha_Fin'] = df['Fecha_Inicio'] + pd.to_timedelta(df['Anos_Seguimiento'] * 365.25, unit='D')

# Filtramos los que sí tienen fechas válidas para no dañar el gráfico
df_gantt = df.dropna(subset=['Fecha_Inicio', 'Fecha_Fin']).copy()

# Ordenamos por fecha de inicio para que se vea escalonado
df_gantt = df_gantt.sort_values('Fecha_Inicio')

# 2. Crear el Gráfico de Gantt
fig_gantt = px.timeline(
    df_gantt, 
    x_start="Fecha_Inicio", 
    x_end="Fecha_Fin", 
    y="Proyecto", 
    color="Estado_Salud",
    hover_name="Proyecto",
    hover_data={'Anos_Seguimiento': True, 'Metodo': True},
    title="Línea de Tiempo de Obligaciones Ambientales",
    color_discrete_map={
        '✅ Cerrado': '#2ecc71', '⚠️ Mantenimiento': '#f1c40f',
        '🌱 Ejecución': '#3498db', '⏱️ Pendiente': '#e74c3c'
    }
)

# Ajustes estéticos para que se vea ordenado
fig_gantt.update_yaxes(autorange="reversed") # El más antiguo arriba
fig_gantt.update_layout(height=600, xaxis_title="Línea de Tiempo", yaxis_title="Proyectos")

# Exportamos este gráfico individualmente si quieres (Opcional)
fig_gantt.write_html("Cronograma_Legal.html", include_plotlyjs="cdn")
fig_gantt.show()

8. Análisis de Desviaciones Presupuestales (Outliers)¶

Este gráfico estadístico (Diagrama de Cajas) permite a la gerencia identificar proyectos con "sobrecostos" o presupuestos inusualmente altos en comparación con otros del mismo método.

  • Los puntos individuales representan proyectos.
  • Si un punto se sale mucho de la caja hacia arriba, es una anomalía financiera que requiere revisión detallada.
In [10]:
# Filtramos proyectos sin costo para no sesgar el análisis
df_costos = df[df['Costo_Total'] > 0].copy()

# Crear un Boxplot (Diagrama de cajas y bigotes) con los puntos superpuestos
fig_box = px.box(
    df_costos, 
    x="Metodo", 
    y="Costo_Total", 
    color="Metodo",
    points="all", # Muestra todos los puntos encima de la caja
    hover_name="Proyecto",
    hover_data={"Costo_Total": True, "Arboles": True},
    title="Distribución Financiera y Detección de Valores Atípicos por Método"
)

fig_box.update_layout(
    xaxis_title="Método de Compensación", 
    yaxis_title="Inversión Total ($)",
    showlegend=False
)

# Exportamos (Opcional)
fig_box.show()

9. Auditoría Automática de Mantenimientos (Text Mining)¶

Este módulo extrae inteligencia directamente de las bitácoras de texto (columna de Observaciones). Utilizando Expresiones Regulares (Regex), el algoritmo lee el texto, cuenta cuántas visitas efectivas se han registrado y las compara contra el mínimo legal exigido (calculado como un estándar de 2 visitas anuales por cada año de seguimiento obligatorio).

  • Barras Azules: Visitas de campo efectivamente realizadas.
  • Línea Roja: Umbral mínimo de visitas esperadas.
In [11]:
import pandas as pd
import plotly.graph_objects as go
import re

# 1. EXTRACCIÓN DE DATOS CON REGEX (Expresiones Regulares)
def contar_visitas(texto):
    if pd.isna(texto): 
        return 0
    # Buscamos un patrón: 3 letras + barra + 2 números (ej. "Oct/20", "feb/25")
    # Ignoramos mayúsculas/minúsculas con re.IGNORECASE
    patron = r'[a-z]{3}/\d{2}'
    visitas_encontradas = re.findall(patron, str(texto), re.IGNORECASE)
    return len(visitas_encontradas)

# Aplicamos el "lector" a toda la columna de observaciones
df['Visitas_Realizadas'] = df['Observaciones'].apply(contar_visitas)

# 2. LÓGICA DE NEGOCIO (Auditoría)
# Supuesto de control: La autoridad exige mínimo 2 mantenimientos al año
visitas_minimas_por_ano = 2 
df['Visitas_Esperadas'] = df['Anos_Seguimiento'] * visitas_minimas_por_ano

# Calculamos si hay un déficit
df['Deficit_Visitas'] = df['Visitas_Esperadas'] - df['Visitas_Realizadas']
# Si el déficit es negativo (hicieron más de las esperadas), lo dejamos en 0
df['Deficit_Visitas'] = df['Deficit_Visitas'].apply(lambda x: x if x > 0 else 0)

# 3. FILTRADO PARA EL GRÁFICO
# Solo analizamos proyectos que ya iniciaron siembra y requieren seguimiento
df_auditoria = df[(df['Avance_Siembra'] > 0) & (df['Anos_Seguimiento'] > 0)].copy()
df_auditoria = df_auditoria.sort_values('Visitas_Realizadas', ascending=False)

# 4. CREACIÓN DEL GRÁFICO COMPARATIVO
fig_auditoria = go.Figure()

# Barras de visitas reales
fig_auditoria.add_trace(go.Bar(
    x=df_auditoria['Proyecto'], 
    y=df_auditoria['Visitas_Realizadas'], 
    name='Visitas Realizadas (Bitácora)',
    marker_color='#3498db',
    text=df_auditoria['Visitas_Realizadas'],
    textposition='auto'
))

# Línea de umbral exigido
fig_auditoria.add_trace(go.Scatter(
    x=df_auditoria['Proyecto'], 
    y=df_auditoria['Visitas_Esperadas'], 
    name='Mínimo Legal Esperado',
    mode='lines+markers',
    line=dict(color='#e74c3c', width=3, dash='dot'),
    marker=dict(size=8)
))

fig_auditoria.update_layout(
    title='Auditoría de Mantenimiento: Visitas Ejecutadas vs. Requerimiento Legal',
    xaxis_title='Proyecto',
    yaxis_title='Número de Visitas',
    barmode='group',
    height=500
)

# Exportar de forma estática si es necesario
fig_auditoria.show()

# --- REPORTE DE ALERTAS CRÍTICAS ---
# Imprimimos los proyectos que están en rojo (tienen menos visitas de las requeridas)
proyectos_criticos = df_auditoria[df_auditoria['Deficit_Visitas'] > 0][['Proyecto', 'Sitio', 'Visitas_Realizadas', 'Visitas_Esperadas', 'Deficit_Visitas']]

print("\n" + "⚠️"*20)
print("REPORTE DE RIESGO: PROYECTOS CON DÉFICIT DE MANTENIMIENTO")
print("⚠️"*20)
if len(proyectos_criticos) > 0:
    print(proyectos_criticos.to_string(index=False))
else:
    print("✅ Todos los proyectos activos cumplen con el mínimo de visitas esperadas.")
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
REPORTE DE RIESGO: PROYECTOS CON DÉFICIT DE MANTENIMIENTO
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
                           Proyecto                             Sitio  Visitas_Realizadas  Visitas_Esperadas  Deficit_Visitas
                         SE Mámbita           Central guavio, Mámbita                   4                6.0              2.0
MT- Portugal redes Cortijo calle 80     Calle 80 PTAR Salitre, Bogotá                   3                6.0              3.0
        LT Zipaquirá - Ubaté T8-T26            Los Canales, sutatausa                   3                6.0              3.0
       LT Nueva Esperanza - Indumil Lote 4, vda alto de cabra, Soacha                   3                6.0              3.0
            LT Muña- Sauces T27-T52 Lote 4, vda alto de cabra, Soacha                   3                6.0              3.0
      LT Muña- Sauces T1-T4 T11-T26 Lote 4, vda alto de cabra, Soacha                   3                6.0              3.0
       LT Nueva Esperanza - Indumil Lote 4, vda alto de cabra, Soacha                   3                6.0              3.0
            LT Muña- Sauces T53-T82                        Fusagasugá                   2                6.0              4.0
                       SE Boqueron           Escuela albania, Tibacuy                   2                6.0              4.0
                         SE Mámbita    Mámbita centro, caño mochilero                   0                6.0              6.0
                         SE Mámbita    Mámbita centro, caño mochilero                   0                6.0              6.0
                         SE Mámbita    Nacedero caño poblano, Mámbita                   0                6.0              6.0
                         SE Mámbita    Mámbita centro, caño mochilero                   0                6.0              6.0
                         SE Mámbita    Nacedero caño poblano, Mámbita                   0                6.0              6.0
                         SE Mámbita    Mámbita centro, caño mochilero                   0                6.0              6.0
       LT Zipaquirá - Ubaté T27-P69                               NaN                   0                6.0              6.0
      SE Tren de occidente y líneas       Predio La Alondra, Mosquera                   0                6.0              6.0
      SE Tren de occidente y líneas       Predio La Alondra, Mosquera                   0                6.0              6.0
      SE Tren de occidente y líneas       Predio La Alondra, Mosquera                   0                6.0              6.0
        MT- Tren de Occidente redes       Predio La Alondra, Mosquera                   0                6.0              6.0
                        SE Mosquera       Predio La Alondra, Mosquera                   0                6.0              6.0
                   LT Guaca-Colegio      Agroparque Sabio Mutis, Tena                   0                6.0              6.0
                   LT Guaca-Colegio      Agroparque Sabio Mutis, Tena                   0                6.0              6.0
                   LT Guaca-Colegio      Agroparque Sabio Mutis, Tena                   0                6.0              6.0
                   LT Guaca-Colegio      Agroparque Sabio Mutis, Tena                   0                6.0              6.0

10. Segmentación Inteligente de Proyectos (Machine Learning)¶

Utilizando Inteligencia Artificial (algoritmo K-Means Clustering), el sistema ha analizado las características operativas y financieras de todos los proyectos para descubrir patrones ocultos. La IA ha clasificado automáticamente el portafolio en 3 perfiles distintos:

  1. Proyectos de Alta Intensidad: Mayor costo y volumen. Requieren supervisión gerencial.
  2. Proyectos Estándar: El grueso de la operación.
  3. Proyectos Ligeros: Bajo costo y volumen. Se pueden gestionar en bloque.
In [13]:
import pandas as pd
import plotly.express as px
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

# 1. PREPARACIÓN DE DATOS PARA LA IA
# Filtramos proyectos viables para el análisis
df_ml = df[(df['Arboles'] > 0) & (df['Costo_Total'] > 0)].copy()

# Seleccionamos las variables (features) que la IA va a estudiar
X = df_ml[['Arboles', 'Costo_Total', 'Anos_Seguimiento']]

# Estandarización: La IA necesita que todo esté en la misma escala (manzanas con manzanas)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 2. ENTRENAMIENTO DEL MODELO DE MACHINE LEARNING
# Le pedimos al algoritmo K-Means que encuentre 3 agrupaciones naturales
modelo_kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
df_ml['Cluster_ID'] = modelo_kmeans.fit_predict(X_scaled)

# 3. INTERPRETACIÓN DE LOS RESULTADOS (Lógica de Negocio)
# Calculamos el promedio de árboles de cada cluster para ponerles un nombre entendible
promedios = df_ml.groupby('Cluster_ID')['Arboles'].mean().sort_values()

# Mapeamos los IDs de la IA a nombres corporativos
nombres_clusters = {
    promedios.index[0]: '🟢 Tier 3: Proyectos Ligeros',
    promedios.index[1]: '🟡 Tier 2: Proyectos Estándar',
    promedios.index[2]: '🔴 Tier 1: Proyectos de Alta Intensidad'
}
df_ml['Perfil_IA'] = df_ml['Cluster_ID'].map(nombres_clusters)

# 4. VISUALIZACIÓN DEL MODELO
fig_ml = px.scatter(
    df_ml, 
    x="Arboles", 
    y="Costo_Total", 
    color="Perfil_IA",
    size="Anos_Seguimiento",
    hover_name="Proyecto",
    title="Análisis de Clústeres (K-Means): Segmentación Automática del Portafolio",
    labels={'Arboles': 'Volumen (N° Árboles)', 'Costo_Total': 'Inversión Financiera ($)'},
    color_discrete_map={
        '🔴 Tier 1: Proyectos de Alta Intensidad': '#e74c3c',
        '🟡 Tier 2: Proyectos Estándar': '#f39c12',
        '🟢 Tier 3: Proyectos Ligeros': '#2ecc71'
    }
)

# Añadimos un fondo corporativo y un estilo limpio
fig_ml.update_layout(plot_bgcolor='rgba(245, 246, 250, 1)', height=600)

# Exportar HTML
fig_ml.write_html("Segmentacion_IA.html", include_plotlyjs="cdn")
fig_ml.show()

# --- RESUMEN DE LA IA ---
print("\n" + "="*50)
print("🧠 CONCLUSIONES DEL MODELO DE MACHINE LEARNING")
print("="*50)
resumen_ia = df_ml.groupby('Perfil_IA').agg(
    Cantidad_Proyectos=('Proyecto', 'count'),
    Costo_Promedio=('Costo_Total', 'mean'),
    Arboles_Promedio=('Arboles', 'mean')
).reset_index()

# Formatear para que se vea bonito en consola
for index, row in resumen_ia.iterrows():
    print(f"{row['Perfil_IA']}:")
    print(f"  - Proyectos en este grupo: {row['Cantidad_Proyectos']}")
    print(f"  - Costo Promedio: ${row['Costo_Promedio']:,.0f}")
    print(f"  - Árboles Promedio: {row['Arboles_Promedio']:,.0f} individuos\n")
==================================================
🧠 CONCLUSIONES DEL MODELO DE MACHINE LEARNING
==================================================
🔴 Tier 1: Proyectos de Alta Intensidad:
  - Proyectos en este grupo: 3
  - Costo Promedio: $190,940,606
  - Árboles Promedio: 3,028 individuos

🟡 Tier 2: Proyectos Estándar:
  - Proyectos en este grupo: 12
  - Costo Promedio: $18,465,724
  - Árboles Promedio: 340 individuos

🟢 Tier 3: Proyectos Ligeros:
  - Proyectos en este grupo: 1
  - Costo Promedio: $360,435,990
  - Árboles Promedio: 242 individuos

11. Modelo Predictivo de Presupuestación (Random Forest Regressor)¶

Este módulo utiliza Inteligencia Artificial para estimar el presupuesto requerido para nuevos proyectos. El algoritmo analiza el histórico de costos, métodos y volumen operativo para generar una predicción financiera con base matemática, eliminando la estimación manual.

In [14]:
from sklearn.ensemble import RandomForestRegressor
import numpy as np

# 1. Preparar los datos (Features y Target)
df_pred = df[(df['Arboles'] > 0) & (df['Costo_Total'] > 0)].copy()

# Convertimos la columna de texto 'Metodo' en variables numéricas (One-Hot Encoding)
df_features = pd.get_dummies(df_pred[['Arboles', 'Anos_Seguimiento', 'Metodo']], drop_first=True)
y_target = df_pred['Costo_Total']

# 2. Entrenar el Modelo Predictivo
modelo_rf = RandomForestRegressor(n_estimators=100, random_state=42)
modelo_rf.fit(df_features, y_target)

# 3. CREAR LA "CALCULADORA" (Función de Predicción)
def predecir_presupuesto(arboles, anos, metodo_nombre):
    # Crear un registro vacío con las mismas columnas que aprendió el modelo
    nuevo_dato = pd.DataFrame(columns=df_features.columns)
    nuevo_dato.loc[0] = 0 # Llenar de ceros
    
    # Asignar los valores del usuario
    nuevo_dato['Arboles'] = arboles
    nuevo_dato['Anos_Seguimiento'] = anos
    
    # Activar la columna del método correspondiente
    columna_metodo = f'Metodo_{metodo_nombre}'
    if columna_metodo in nuevo_dato.columns:
        nuevo_dato[columna_metodo] = 1
        
    # Hacer la predicción
    prediccion = modelo_rf.predict(nuevo_dato)[0]
    return prediccion

# --- PRUEBA DEL MODELO ---
print("🤖 ASISTENTE FINANCIERO DE IA INICIADO")
print("-" * 50)
# Simulamos que un gerente pregunta por un proyecto nuevo:
sim_arboles = 1500
sim_anos = 3
sim_metodo = "Rehabilitación/Recuperación"

presupuesto_estimado = predecir_presupuesto(sim_arboles, sim_anos, sim_metodo)

print(f"📋 Parámetros del nuevo proyecto: {sim_arboles} árboles, {sim_anos} años, {sim_metodo}")
print(f"💰 Presupuesto estimado por la IA: ${presupuesto_estimado:,.2f}")
🤖 ASISTENTE FINANCIERO DE IA INICIADO
--------------------------------------------------
📋 Parámetros del nuevo proyecto: 1500 árboles, 3 años, Rehabilitación/Recuperación
💰 Presupuesto estimado por la IA: $111,300,061.63
In [ ]: