#!/usr/bin/env python3
"""
Generador del Notebook para la Actividad Semana 7 - Machine Learning Avanzado
NRC-8772 - Automatización de procesos para el flujo de trabajo en ML
Autor: Grupo 10
"""

import nbformat
from nbformat.v4 import new_notebook, new_code_cell, new_markdown_cell
import os

nb = new_notebook()
nb.metadata = {
    "kernelspec": {
        "display_name": "Python 3",
        "language": "python",
        "name": "python3"
    },
    "language_info": {
        "name": "python",
        "version": "3.10.0"
    },
    "colab": {
        "provenance": []
    }
}

cells = []

# =============================================================================
# CELDA 1: Encabezado Markdown
# =============================================================================
cells.append(new_markdown_cell("""# 🤖 Automatización de Procesos para el Flujo de Trabajo en Machine Learning
## Machine Learning Avanzado — NRC-8772 — Semana 7

[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1UFcyBiLMYu7WwMK4UlFYvfFq_wvpBDCB)

**Grupo:** Grupo 10  
**Integrantes:**  
- Alexander Oviedo Fadul (Ponente)  
- Maria Fernanda Ruiz Paipilla  
- Neheman Samir Jaller Cerchiaro  
- William David Obando Lopez  

**Enlace del Notebook en Google Colab:** [Abrir en Google Colab](https://colab.research.google.com/drive/1UFcyBiLMYu7WwMK4UlFYvfFq_wvpBDCB)

**Programa:** Especialización en Inteligencia Artificial, Corporación Universitaria Minuto de Dios (UNIMINUTO)  
**Curso:** NRC-8772 — Machine Learning Avanzado  
**Profesor:** Leonardo Valderrama García  
**Fecha:** 21 de junio de 2026

---

### Descripción de la actividad

En este proyecto se implementa un **flujo de trabajo automatizado (Pipeline)** de Machine Learning para predecir la supervivencia en el dataset del **Titanic**.
El objetivo principal es demostrar cómo la automatización de procesos mediante `scikit-learn` previene la fuga de datos (data leakage), optimiza hiperparámetros de manera integrada mediante `GridSearchCV` y permite una evaluación de modelos rigurosa a través de validación cruzada multimétrica.

### Estructura del notebook

| Sección | Contenido |
|---------|-----------|
| 1 | Respuestas a Preguntas Orientadoras |
| 2 | Importación de Librerías |
| 3 | Descarga e Ingesta Automática del Dataset |
| 4 | Análisis Exploratorio de Datos (EDA) |
| 5 | Ingeniería de Características Personalizada |
| 6 | Automatización del Preprocesamiento y Clasificadores (Pipelines) |
| 7 | Optimización de Hiperparámetros (GridSearchCV) |
| 8 | Evaluación Multimétrica con Validación Cruzada |
| 9 | Evaluación en Conjunto de Prueba y Matrices de Confusión |
| 10 | Dilemas Éticos y Sesgo Algorítmico |
| 11 | Conclusiones y Referencias |
"""))

# =============================================================================
# CELDA 2: Preguntas Orientadoras
# =============================================================================
cells.append(new_markdown_cell("""## 1. Respuestas a las Preguntas Orientadoras

### Pregunta 1: ¿Cómo puede la automatización de procesos en los flujos de trabajo de Machine Learning aumentar la eficiencia y la precisión de los modelos?

La automatización de procesos (MLOps / Pipelines) incrementa la **eficiencia** y la **precisión** a través de varios mecanismos clave:

1. **Prevención de la Fuga de Datos (Data Leakage):** Al encapsular los pasos de preprocesamiento (como el escalado y la imputación de nulos) dentro de un objeto `Pipeline` de scikit-learn, garantizamos que las estadísticas (como la media o mediana) se calculen únicamente en los datos de entrenamiento de cada fold durante la validación cruzada, y no en los datos de prueba o validación. Esto evita estimaciones de rendimiento demasiado optimistas y mejora la precisión real en producción.
2. **Consistencia y Repetibilidad:** Se reduce el error humano. El mismo flujo exacto se aplica tanto a los conjuntos de entrenamiento, validación y prueba, como a los datos nuevos que llegan en tiempo real.
3. **Optimización Integrada:** Permite realizar búsquedas de hiperparámetros (`GridSearchCV` o `RandomizedSearchCV`) sobre todo el flujo simultáneamente, encontrando combinaciones óptimas de preprocesamiento (ej. número de componentes en PCA) y del clasificador que de otra manera requerirían ciclos manuales ineficientes.
4. **Agilidad en la Iteración:** Permite probar múltiples algoritmos y configuraciones cambiando pocas líneas de código, acelerando el ciclo de vida del desarrollo de software (SDLC) en proyectos de IA.

### Pregunta 2: ¿Cuáles son las herramientas y técnicas de automatización de procesos utilizadas en los flujos de trabajo de Machine Learning y cómo se aplican en diferentes casos de uso?

* **Pipelines de scikit-learn (`Pipeline` y `ColumnTransformer`):** Técnica para encadenar transformadores y estimadores. Ideal para preprocesar datos tubulares de forma diferenciada según el tipo de variable (numérica o categórica).
* **MLflow:** Herramienta para registrar parámetros de ejecución, métricas y artefactos de modelos. Se aplica en experimentación para comparar cientos de ejecuciones de modelos.
* **Vertex AI / Kubeflow Pipelines:** Plataformas de orquestación a gran escala que permiten ejecutar tareas de ML en contenedores aislados. Útil en entornos corporativos para reentrenar modelos periódicamente cuando se detecta deriva de datos (data drift).
* **GridSearchCV / Optuna:** Herramientas para la optimización automática de hiperparámetros. Se aplican en finetuning para exprimir el rendimiento de modelos complejos antes de su puesta en producción.
"""))

# =============================================================================
# CELDA 3: Importaciones
# =============================================================================
cells.append(new_markdown_cell("""## 2. Importación de librerías

Se importan las librerías necesarias para el análisis de datos, visualización y el diseño de los flujos de preprocesamiento, modelado y validación cruzada.
"""))

cells.append(new_code_cell("""# ===========================================================================
# 2. IMPORTACIÓN DE LIBRERÍAS
# ===========================================================================
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import os
import warnings
warnings.filterwarnings('ignore')

# Framework principal de Machine Learning (scikit-learn)
from sklearn.model_selection import (
    train_test_split, cross_validate, GridSearchCV
)
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, FunctionTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.metrics import (
    classification_report, confusion_matrix, accuracy_score,
    precision_score, recall_score, f1_score, roc_curve, auc
)

# Estilo visual de las gráficas
sns.set_theme(style="whitegrid")
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['font.size'] = 11

print("✅ Librerías cargadas exitosamente.")
"""))

# =============================================================================
# CELDA 4: Carga y descarga de datos
# =============================================================================
cells.append(new_markdown_cell("""## 3. Ingesta y descarga automática del dataset

Para asegurar la portabilidad y ejecución 100% web del notebook (por ejemplo en Google Colab), el dataset del **Titanic** se descarga directamente desde un repositorio público. Si se ejecuta en local, busca el archivo de respaldo `titanic.csv`.
"""))

cells.append(new_code_cell("""# ===========================================================================
# 3. CARGA DE DATOS DESDE LA WEB
# ===========================================================================
URL_TITANIC = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
RUTA_LOCAL = "./titanic.csv"

if os.path.exists(RUTA_LOCAL):
    print("📁 Cargando dataset desde la ruta local...")
    df = pd.read_csv(RUTA_LOCAL)
else:
    print("📥 Descargando dataset desde URL pública...")
    try:
        df = pd.read_csv(URL_TITANIC)
        df.to_csv(RUTA_LOCAL, index=False)
        print("💾 Dataset guardado localmente como 'titanic.csv'.")
    except Exception as e:
        print(f"❌ Error al descargar de la web: {e}")
        print("Intentando cargar desde la carpeta de la semana anterior...")
        df = pd.read_csv("../MA Semana 5/titanic.csv")

# Estandarizar nombres de columnas a minúsculas
df.columns = df.columns.str.lower()

# Resumen de dimensiones y columnas
print(f"Dimensiones del dataset: {df.shape[0]} filas, {df.shape[1]} columnas")
print("\\nPrimeras 5 filas:")
display(df.head())
"""))

# =============================================================================
# CELDA 5: EDA Markdown
# =============================================================================
cells.append(new_markdown_cell("""## 4. Análisis Exploratorio de Datos (EDA)

Se realiza un análisis gráfico inicial para comprender la distribución de la supervivencia y la relación de variables críticas como la **clase del boleto (pclass)**, el **género (sex)**, la **tarifa (fare)** y la **edad (age)**.
"""))

# =============================================================================
# CELDA 6: EDA Código
# =============================================================================
cells.append(new_code_cell("""# ===========================================================================
# 4. ANÁLISIS EXPLORATORIO DE DATOS (EDA)
# ===========================================================================
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
fig.suptitle('Análisis Exploratorio de Datos (Supervivencia en el Titanic)', fontsize=16, fontweight='bold')

# 1. Distribución de supervivencia
sns.countplot(ax=axes[0, 0], x='survived', data=df, palette='Set2')
axes[0, 0].set_title('Distribución de Supervivencia (0 = No, 1 = Sí)')
axes[0, 0].set_xlabel('Supervivencia')
axes[0, 0].set_ylabel('Cantidad')

# 2. Supervivencia por género
sns.countplot(ax=axes[0, 1], x='sex', hue='survived', data=df, palette='Set1')
axes[0, 1].set_title('Supervivencia según el Género')
axes[0, 1].set_xlabel('Género')
axes[0, 1].set_ylabel('Cantidad')

# 3. Supervivencia por clase de boleto
sns.countplot(ax=axes[1, 0], x='pclass', hue='survived', data=df, palette='viridis')
axes[1, 0].set_title('Supervivencia por Clase de Boleto')
axes[1, 0].set_xlabel('Clase de Boleto')
axes[1, 0].set_ylabel('Cantidad')

# 4. Distribución de edad según supervivencia
sns.kdeplot(ax=axes[1, 1], x='age', hue='survived', data=df, fill=True, common_norm=False, palette='coolwarm')
axes[1, 1].set_title('Distribución de Edad por Supervivencia')
axes[1, 1].set_xlabel('Edad')
axes[1, 1].set_ylabel('Densidad')

plt.tight_layout()
plt.savefig('eda_supervivencia_titanic.png', dpi=150, bbox_inches='tight')
plt.show()
print("Gráfica guardada: eda_supervivencia_titanic.png")
"""))

# =============================================================================
# CELDA 7: Ingeniería de Características
# =============================================================================
cells.append(new_markdown_cell("""## 5. Ingeniería de Características Personalizada

Se implementa una función de ingeniería de características que genera nuevas variables lógicas a partir del texto y variables originales:
1. **family_size:** Suma de hermanos/cónyuges (`sibsp`) y padres/hijos (`parch`) más 1 (el propio pasajero).
2. **is_alone:** Variable binaria que indica si el pasajero viajaba solo (`family_size == 1`).
3. **title:** Título de cortesía extraído del nombre (Mr, Mrs, Miss, Master, etc.), útil para imputar edades y como indicador social.
"""))

cells.append(new_code_cell("""# ===========================================================================
# 5. INGENIERÍA DE CARACTERÍSTICAS
# ===========================================================================

def procesar_caracteristicas(data):
    # Crear una copia para evitar SettingWithCopyWarning
    df_new = data.copy()
    
    # 1. Tamaño de la familia
    df_new['family_size'] = df_new['sibsp'] + df_new['parch'] + 1
    
    # 2. ¿Viaja solo?
    df_new['is_alone'] = (df_new['family_size'] == 1).astype(int)
    
    # 3. Extraer títulos del nombre
    df_new['title'] = df_new['name'].str.extract(r' ([A-Za-z]+)\\.', expand=False)
    
    # Agrupar títulos poco comunes en categorias estándar
    titulos_raros = ['Lady', 'Countess','Capt', 'Col', 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona']
    df_new['title'] = df_new['title'].replace(titulos_raros, 'Rare')
    df_new['title'] = df_new['title'].replace('Mlle', 'Miss')
    df_new['title'] = df_new['title'].replace('Ms', 'Miss')
    df_new['title'] = df_new['title'].replace('Mme', 'Mrs')
    
    # Rellenar valores nulos de títulos
    df_new['title'] = df_new['title'].fillna('Mr')
    
    # Eliminar columnas que no usaremos en el modelado directo
    df_new = df_new.drop(['passengerid', 'name', 'ticket', 'cabin'], axis=1)
    
    return df_new

# Aplicar ingeniería de características al dataset completo
df_procesado = procesar_caracteristicas(df)

print("Columnas actuales del dataset procesado:")
print(list(df_procesado.columns))
print("\\nPrimeras 5 filas con nuevas características:")
display(df_procesado.head())
"""))

# =============================================================================
# CELDA 8: Pipelines y ColumnTransformer
# =============================================================================
cells.append(new_markdown_cell("""## 6. Automatización del Preprocesamiento y Modelado (Pipelines)

Para evitar la fuga de información y automatizar el flujo, se construye un preprocesador usando `ColumnTransformer`:
- **Variables Numéricas (`age`, `fare`, `sibsp`, `parch`, `family_size`):** Imputación con la mediana y escalamiento estándar (`StandardScaler`).
- **Variables Categóricas (`sex`, `embarked`, `title`):** Imputación con el valor más frecuente y codificación One-Hot (`OneHotEncoder`).
- **Variables Binarias (`is_alone`):** Pasan directamente sin procesar.

A continuación, se definen los pipelines completos para dos algoritmos: **Random Forest** y **Support Vector Machine (SVM)**.
"""))

cells.append(new_code_cell("""# ===========================================================================
# 6. PIPELINES Y COLUMNTRANSFORMER
# ===========================================================================

# Separar características (X) y variable objetivo (y)
X = df_procesado.drop('survived', axis=1)
y = df_procesado['survived']

# Dividir en entrenamiento y prueba (80/20) con semilla aleatoria para replicabilidad
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# Definir las columnas numéricas y categóricas
num_cols = ['age', 'fare', 'sibsp', 'parch', 'family_size']
cat_cols = ['sex', 'embarked', 'title']
bin_cols = ['is_alone']

# 1. Pipeline numérico: Imputador Mediana + Scaler
num_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

# 2. Pipeline categórico: Imputador Moda + One-Hot Encoding
cat_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])

# Combinar preprocesamiento
preprocessor = ColumnTransformer(transformers=[
    ('num', num_transformer, num_cols),
    ('cat', cat_transformer, cat_cols),
    ('bin', 'passthrough', bin_cols)
])

# 3. Pipelines completos de extremo a extremo
pipeline_rf = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier(random_state=42, class_weight='balanced'))
])

pipeline_svm = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('classifier', SVC(probability=True, random_state=42, class_weight='balanced'))
])

print("✅ Pipelines de Random Forest y SVM creados exitosamente.")
"""))

# =============================================================================
# CELDA 9: Optimización de Hiperparámetros (GridSearchCV)
# =============================================================================
cells.append(new_markdown_cell("""## 7. Optimización de Hiperparámetros (GridSearchCV)

Se implementa una búsqueda de cuadrícula (`GridSearchCV`) integrada en los pipelines para automatizar la selección de los mejores hiperparámetros para ambos clasificadores, usando la métrica **F1-score** para optimizar el balance entre precisión y exhaustividad.
"""))

cells.append(new_code_cell("""# ===========================================================================
# 7. OPTIMIZACIÓN CON GRIDSEARCHCV
# ===========================================================================

# Cuadrículas de parámetros a probar
param_grid_rf = {
    'classifier__n_estimators': [50, 100, 200],
    'classifier__max_depth': [4, 6, 8, 10],
    'classifier__min_samples_split': [2, 5, 10]
}

param_grid_svm = {
    'classifier__C': [0.1, 1, 10],
    'classifier__gamma': ['scale', 'auto', 0.01, 0.1]
}

print("🔎 Iniciando búsqueda de hiperparámetros para Random Forest...")
grid_rf = GridSearchCV(pipeline_rf, param_grid_rf, cv=5, scoring='f1', n_jobs=-1)
grid_rf.fit(X_train, y_train)
best_rf_model = grid_rf.best_estimator_
print(f"🥇 Mejores parámetros RF: {grid_rf.best_params_}")
print(f"📈 Mejor F1-score RF: {grid_rf.best_score_:.4f}")

print("\\n🔎 Iniciando búsqueda de hiperparámetros para SVM...")
grid_svm = GridSearchCV(pipeline_svm, param_grid_svm, cv=5, scoring='f1', n_jobs=-1)
grid_svm.fit(X_train, y_train)
best_svm_model = grid_svm.best_estimator_
print(f"🥇 Mejores parámetros SVM: {grid_svm.best_params_}")
print(f"📈 Mejor F1-score SVM: {grid_svm.best_score_:.4f}")
"""))

# =============================================================================
# CELDA 10: Validación cruzada
# =============================================================================
cells.append(new_markdown_cell("""## 8. Evaluación Multimétrica con Validación Cruzada

Se realiza una validación cruzada con **5 particiones (5-Fold CV)** utilizando el conjunto de datos de entrenamiento para evaluar de forma robusta la capacidad de generalización de ambos modelos optimizados bajo múltiples métricas simultáneas.
"""))

cells.append(new_code_cell("""# ===========================================================================
# 8. EVALUACIÓN ROBUSTA CON VALIDACIÓN CRUZADA MULTIMÉTRICA
# ===========================================================================

metricas = {
    'accuracy': 'accuracy',
    'precision': 'precision',
    'recall': 'recall',
    'f1': 'f1'
}

# Validación cruzada para el modelo optimizado de Random Forest
cv_results_rf = cross_validate(
    best_rf_model, X_train, y_train, cv=5, scoring=metricas, return_train_score=True
)

# Validación cruzada para el modelo optimizado de SVM
cv_results_svm = cross_validate(
    best_svm_model, X_train, y_train, cv=5, scoring=metricas, return_train_score=True
)

# Mostrar resultados promedio
def mostrar_resumen_cv(resultados, nombre):
    print(f"=== Resultados de Validación Cruzada (Train vs Val) para {nombre} ===")
    print(f"{'Métrica':<15} | {'Entrenamiento (Promedio)':<25} | {'Validación (Promedio)':<20}")
    print("-" * 70)
    for m in metricas.keys():
        train_mean = resultados[f'train_{m}'].mean()
        val_mean = resultados[f'test_{m}'].mean()
        print(f"{m.capitalize():<15} | {train_mean:24.4f} | {val_mean:19.4f}")
    print("=" * 70 + "\\n")

mostrar_resumen_cv(cv_results_rf, "Random Forest")
mostrar_resumen_cv(cv_results_svm, "SVM")
"""))

# =============================================================================
# CELDA 11: Resultados finales
# =============================================================================
cells.append(new_markdown_cell("""## 9. Evaluación en el Conjunto de Prueba y Matrices de Confusión

Evaluamos finalmente los mejores modelos en el conjunto de prueba (`test set`) no visto durante el entrenamiento y visualizamos las **matrices de confusión** y las **curvas ROC** correspondientes.
"""))

cells.append(new_code_cell("""# ===========================================================================
# 9. EVALUACIÓN FINAL EN TEST SET Y MATRICES DE CONFUSIÓN
# ===========================================================================

# Predicciones
y_pred_rf = best_rf_model.predict(X_test)
y_pred_svm = best_svm_model.predict(X_test)

y_probs_rf = best_rf_model.predict_proba(X_test)[:, 1]
y_probs_svm = best_svm_model.predict_proba(X_test)[:, 1]

# Graficar matrices de confusión y curvas ROC
fig, axes = plt.subplots(2, 2, figsize=(16, 14))

# 1. Matriz de Confusión Random Forest
cm_rf = confusion_matrix(y_test, y_pred_rf)
sns.heatmap(cm_rf, annot=True, fmt='d', cmap='Blues', ax=axes[0, 0])
axes[0, 0].set_title('Matriz de Confusión - Random Forest')
axes[0, 0].set_xlabel('Predicción')
axes[0, 0].set_ylabel('Real')

# 2. Matriz de Confusión SVM
cm_svm = confusion_matrix(y_test, y_pred_svm)
sns.heatmap(cm_svm, annot=True, fmt='d', cmap='Oranges', ax=axes[0, 1])
axes[0, 1].set_title('Matriz de Confusión - SVM')
axes[0, 1].set_xlabel('Predicción')
axes[0, 1].set_ylabel('Real')

# 3. Curva ROC
fpr_rf, tpr_rf, _ = roc_curve(y_test, y_probs_rf)
fpr_svm, tpr_svm, _ = roc_curve(y_test, y_probs_svm)
auc_rf = auc(fpr_rf, tpr_rf)
auc_svm = auc(fpr_svm, tpr_svm)

axes[1, 0].plot(fpr_rf, tpr_rf, color='blue', lw=2, label=f'Random Forest (AUC = {auc_rf:.4f})')
axes[1, 0].plot(fpr_svm, tpr_svm, color='orange', lw=2, label=f'SVM (AUC = {auc_svm:.4f})')
axes[1, 0].plot([0, 1], [0, 1], color='navy', linestyle='--')
axes[1, 0].set_xlim([0.0, 1.0])
axes[1, 0].set_ylim([0.0, 1.05])
axes[1, 0].set_xlabel('Tasa de Falsos Positivos')
axes[1, 0].set_ylabel('Tasa de Verdaderos Positivos')
axes[1, 0].set_title('Curva ROC')
axes[1, 0].legend(loc="lower right")

# 4. Tabla comparativa de métricas en Test Set
metricas_test = {
    'Modelo': ['Random Forest', 'SVM'],
    'Accuracy': [accuracy_score(y_test, y_pred_rf), accuracy_score(y_test, y_pred_svm)],
    'Precision': [precision_score(y_test, y_pred_rf), precision_score(y_test, y_pred_svm)],
    'Recall': [recall_score(y_test, y_pred_rf), recall_score(y_test, y_pred_svm)],
    'F1-score': [f1_score(y_test, y_pred_rf), f1_score(y_test, y_pred_svm)]
}
df_metricas = pd.DataFrame(metricas_test)
# Mostrar tabla como heatmap de texto en la gráfica
axes[1, 1].axis('off')
axes[1, 1].set_title('Métricas Finales en Conjunto de Prueba', fontsize=14, fontweight='bold')
tabla = axes[1, 1].table(
    cellText=df_metricas.values,
    colLabels=df_metricas.columns,
    cellLoc='center',
    loc='center'
)
tabla.set_fontsize(12)
tabla.scale(1, 2)

plt.tight_layout()
plt.savefig('resultados_comparativos_modelos.png', dpi=150, bbox_inches='tight')
plt.show()
print("Gráfica guardada: resultados_comparativos_modelos.png")

# Imprimir reportes en texto
print("\\n" + "=" * 60)
print("REPORTE DE CLASIFICACIÓN: RANDOM FOREST")
print("=" * 60)
print(classification_report(y_test, y_pred_rf))

print("\\n" + "=" * 60)
print("REPORTE DE CLASIFICACIÓN: SVM")
print("=" * 60)
print(classification_report(y_test, y_pred_svm))
"""))

# =============================================================================
# CELDA 12: Ética del Algoritmo
# =============================================================================
cells.append(new_markdown_cell("""## 10. Dilemas Éticos y Sesgo Algorítmico en la IA

El dataset del Titanic, si bien se utiliza comúnmente con fines educativos, expone con claridad varios de los problemas analizados en las lecturas de **Carsten (2020)** y **Béranger (2018)** sobre la ética de los algoritmos y la revolución digital:

1. **Sesgo en los Datos Históricos:** 
   El análisis exploratorio demuestra que variables como `sex` (género) y `pclass` (clase del billete) son predictores dominantes de la supervivencia. Esto refleja políticas y sesgos de la sociedad eduardiana de 1912 ("mujeres y niños primero", y prioridad absoluta a pasajeros de primera clase). Un modelo de IA entrenado con estos datos históricos simplemente "memoriza" y perpetúa esta discriminación estructurada.
2. **Transferencia del Sesgo a Modelos de Decisiones:**
   Si utilizáramos un algoritmo entrenado bajo este paradigma para tomar decisiones sobre quién debe ser rescatado en un siniestro marítimo actual, el algoritmo priorizaría sistemáticamente a mujeres ricas frente a hombres de clase trabajadora. Esto ejemplifica la **"falsa neutralidad"** de los algoritmos de IA: los modelos no son inherentemente éticos ni objetivos; solo codifican y automatizan las asimetrías presentes en sus conjuntos de datos de entrenamiento.
3. **El Rol de la Auditoría y Gobernanza Algorítmica:**
   Béranger (2018) propone marcos éticos de control y transparencia para que los ingenieros de Machine Learning auditen periódicamente las variables de decisión. En nuestro flujo automatizado, esto implica analizar la **importancia de características** y controlar que variables sensibles (como el género o el estatus socioeconómico) no introduzcan sesgos discriminatorios inaceptables en sistemas de toma de decisiones humanas críticas.
"""))

# =============================================================================
# CELDA 13: Conclusiones
# =============================================================================
cells.append(new_markdown_cell("""## 11. Conclusiones y Referencias

### Conclusiones

1. **La importancia de los Pipelines:** El uso de `Pipeline` y `ColumnTransformer` facilitó la separación limpia del flujo de procesamiento para variables numéricas y categóricas, asegurando un entrenamiento libre de fugas de datos y garantizando que el modelo sea almacenable y exportable directamente a producción.
2. **Validación Cruzada Multimétrica:** La validación cruzada integrada con `cross_validate` permitió comparar el desempeño global de Random Forest y SVM no solo en base al Accuracy, sino también sobre el F1-Score, que resultó más robusto al balancear la precisión y el recall.
3. **Optimización con GridSearchCV:** La búsqueda de hiperparámetros en cuadricula permitió automatizar la sintonización de parámetros complejos (como la profundidad de árboles o los coeficientes C y gamma en SVM), reduciendo significativamente el tiempo de desarrollo manual.
4. **La Ética en la IA:** Automatizar el modelado nos recuerda que los algoritmos son espejos de los datos. La supervisión humana y la gobernanza ética son esenciales para detectar y neutralizar sesgos discriminatorios heredados del pasado histórico.

### Referencias Bibliográficas

* Béranger, J. (2018). *The Framework for Algorithmic Processing*. En The algorithmic code of ethics: Ethics at the bedside of the digital revolution (pp. 122-164). John Wiley & Sons, Incorporated.
* Campesato, O. (2020). *Introduction to AI* / *Introduction to Machine Learning* / *Classifiers in Machine Learning*. En Artificial intelligence, machine learning, and deep learning (pp. 1-66). Mercury Learning and Information.
* Carsten, B. (2020). *Ethical Issues of AI* / *Addressing Ethical Issues in AI*. En Artificial Intelligence for a Better Future: An Ecosystem Perspective on the Ethics of AI and Emerging Digital Technologies (pp. 35-64). Springer.
* Valderrama García, L. (2026, 20 de junio). *Clase 7: Automatización de procesos para el flujo de trabajo en Machine Learning* [Clase magistral]. NRC-8772 Machine Learning Avanzado, Corporación Universitaria Minuto de Dios.
"""))

# Guardar el notebook
nb.cells = cells

output_path = "Grupo10_MLA_Automatizacion_S7.ipynb"
with open(output_path, 'w', encoding='utf-8') as f:
    nbformat.write(nb, f)

print(f"✅ Notebook de Machine Learning Avanzado generado exitosamente: {output_path}")
print(f"   Celdas totales: {len(cells)}")
print(f"   Celdas de código: {sum(1 for c in cells if c.cell_type == 'code')}")
print(f"   Celdas markdown: {sum(1 for c in cells if c.cell_type == 'markdown')}")
