Autor: Fernando

  • Ingeniería de Características y Transformación de Variables

    Introducción

    Hasta este punto del flujo de trabajo de Machine Learning, hemos cubierto la extracción de datos desde diversas fuentes, la limpieza profunda del dataset y el Análisis Exploratorio de Datos (EDA).

    Sin embargo, antes de proceder a entrenar cualquier algoritmo, los datos en bruto (raw data) rara vez están listos para ofrecer un rendimiento óptimo. La Ingeniería de Características (Feature Engineering) y la Transformación de Variables constituyen la etapa puente donde adaptamos y refinamos nuestra información para maximizar la precisión de los modelos.

    Objetivos de Aprendizaje de este Bloque

    • Transformación No Lineal: Entender cómo ciertas operaciones matemáticas modifican la distribución de los datos para mitigar el impacto de valores atípicos y estabilizar la varianza.
    • Codificación de Características (Feature Encoding): Dado que los modelos matemáticos de Machine Learning operan estrictamente con variables numéricas, aprenderemos a mapear y traducir variables categóricas (como textos o etiquetas) a representaciones numéricas inteligibles por el modelo.
    • Escalado de Características (Feature Scaling): En el mundo real, cada variable viene expresada en su propia escala (por ejemplo, la edad de 0 a 100 frente al salario anual de miles de dólares). Aprenderemos a homogeneizar estos rangos, una propiedad crítica para la convergencia de muchos algoritmos.

    Supuestos de los Modelos: El Caso de la Regresión Lineal

    La mayoría de los algoritmos de Machine Learning se fundamentan en presunciones teóricas sobre la naturaleza y distribución de los datos. El ejemplo arquetípico es la Regresión Lineal, la cual asume de manera estricta que existe una relación lineal entre las variables predictoras (características de entrada) y la variable objetivo o etiqueta (target).

    Estructura de un Modelo Lineal

    Para relacionar dos variables predictoras ($x_1$ y $x_2$) con una variable de salida ($y$), planteamos la siguiente ecuación matemática:

    $$f(x) = \beta_0 + \beta_1 x_1 + \beta_2 x_2$$

    Donde:

    • $\beta_0$ representa el intercepto (el valor de $y$ cuando todos los predictores son cero).
    • $\beta_1$ y $\beta_2$ son los coeficientes o pesos matemáticos asociados a cada variable.
    • El objetivo del algoritmo durante el entrenamiento es aprender los valores óptimos de estos parámetros ($\beta_0, \beta_1, \beta_2$) a partir de los datos históricos.

    Ejemplo Práctico: Predicción de Taquilla Financiera (Box Office)

    Para visualizar el comportamiento de un modelo lineal en el ámbito de los negocios, consideremos el escenario de predecir la recaudación de ingresos de una película en taquilla ($y$):

    • Variable Predictora $x_1$: Presupuesto asignado al reparto (Cast Budget).
    • Variable Predictora $x_2$: Presupuesto asignado a la campaña de mercadotecnia (Marketing Budget).

    En este diseño, los coeficientes $\beta_1$ y $\beta_2$ cuantificarán matemáticamente el impacto individual de cada presupuesto en la estimación de la recaudación final.

    En un sentido estrictamente matemático, la “linealidad” del modelo significa que la función avanza mediante la adición o suma de cada parámetro multiplicado por su respectivo coeficiente.

    Flexibilidad mediante la Transformación

    Un error común es pensar que si la relación original entre el presupuesto y la taquilla tiene una forma curva o exponencial, la regresión lineal deja de ser útil. La realidad es que podemos aplicar una transformación matemática a los datos de entrada en bruto (por ejemplo, calcular el logaritmo de $x_1$ o elevar $x_2$ al cuadrado).

    Al sustituir el valor en bruto por su equivalente transformado, el modelo sigue operando bajo una estructura lineal (una suma de coeficientes multiplicados por la variable), pero ahora es capaz de capturar relaciones complejas y no lineales del mundo real:

    $$f(x) = \beta_0 + \beta_1 \cdot \text{transformación}(x_1) + \beta_2 \cdot \text{transformación}(x_2)$$

    A partir de la próxima sesión, desglosaremos las técnicas específicas para codificar, transformar y escalar estas variables antes de alimentar nuestros algoritmos.

    El Supuesto de Normalidad en los Residuos

    En el marco teórico-matemático de la Regresión Lineal, existe un supuesto fundamental: los residuos (o errores) del modelo final deben seguir una distribución normal. Los residuos representan la diferencia entre los valores reales observados y las predicciones calculadas por el algoritmo.

    En el mundo real, los datos en bruto rara vez son perfectos. Con frecuencia, tanto las variables predictoras como la variable objetivo presentan sesgos significativos:

    • Sesgo Positivo (Right-Skewed): Los datos se concentran en valores bajos y la distribución presenta una “cola” larga hacia la derecha (por ejemplo, los salarios de una empresa o el presupuesto de las películas).
    • Sesgo Negativo (Left-Skewed): Los datos se concentran en valores altos y la cola se extiende hacia la izquierda.

    Si alimentamos un modelo de regresión lineal con datos crudos fuertemente sesgados, el algoritmo tendrá dificultades para optimizar sus parámetros de forma correcta, rompiendo el supuesto de normalidad en los errores. Las transformaciones de variables modifican la escala geométrica de los datos para estabilizar su varianza y aproximarlos a una distribución normal.

    Técnicas de Transformación No Lineal

    Para corregir el sesgo e inducir normalidad, la librería NumPy y el ecosistema científico de Python proveen varias funciones matemáticas clave:

    • Logaritmo Natural (np.log): Comprime los valores extremadamente altos y expande los valores bajos. Es la herramienta por excelencia para mitigar el sesgo positivo.
    • Logaritmo Más Uno (np.log1p): Dado que matemáticamente el logaritmo de cero no está definido ($\log(0) \to -\infty$), si tu dataset contiene ceros reales, utilizar np.log romperá el código. La función log1p calcula automáticamente $\log(x + 1)$, solucionando este problema de forma segura.
    • Transformación Box-Cox (scipy.stats.boxcox): Es una metodología más compleja y automatizada. A través de un parámetro lambda ($\lambda$), evalúa el dataset y encuentra de manera matemática la transformación de potencia exacta requerida para convertir una distribución sesgada en una normal.

    El Efecto Visual de la Transformación Logarítmica

    Al aplicar un logaritmo a un conjunto de datos con sesgo positivo, la distribución se redistribuye simétricamente:

    Modelado de Rendimientos Decrecientes y Polinomios

    Además de corregir la distribución, las transformaciones permiten que un algoritmo lineal capture dinámicas no lineales de la realidad económica y comercial.

    Rendimientos Decrecientes (Diminishing Returns)

    Consideremos la relación entre el presupuesto de una película ($x$) y la recaudación en taquilla ($y$). Un incremento de 5 millones de dólares en el presupuesto de una película independiente puede triplicar sus ganancias; sin embargo, ese mismo aumento en una superproducción multimillonaria apenas alterará su recaudación final.

    Esta estructura curva de saturación representa un rendimiento decreciente. Aunque la relación original no es una línea recta, es perfectamente lineal respecto al logaritmo de los datos:

    $$f(x) = \beta_0 + \beta_1 \cdot \log(x)$$

    El modelo resultante sigue considerándose una regresión lineal porque la ecuación mantiene una combinación lineal (sumas y multiplicaciones directas) de los coeficientes respecto a las características, aun cuando la característica en sí sea $\log(x)$ en lugar de $x$.

    Características Polinomiales (Polynomial Features)

    Si los datos describen curvas más complejas con puntos de inflexión (donde la tendencia cambia de dirección), podemos elevar el grado de nuestras variables mediante polinomios.

    • Grado 2 (Cuadrática): Al incorporar el término $x^2$, el modelo adquiere la flexibilidad de una parábola. Tiene un punto de inflexión (la curva sube y luego baja).$$f(x) = \beta_0 + \beta_1 x + \beta_2 x^2$$Ejemplo: En niveles extremos, un presupuesto exageradamente alto podría generar pérdidas netas debido a la imposibilidad de recuperar la inversión en taquilla.
    • Grado 3 o Superior (Cúbica): Al incorporar $x^3$, el modelo puede capturar dos puntos de inflexión (la curva sube, baja y vuelve a subir).

    Implementación de Transformaciones Polinomiales con Scikit-Learn

    Para automatizar la creación de términos cuadráticos o cúbicos sin necesidad de calcular las potencias columna por columna de forma manual, la librería Scikit-Learn ofrece el módulo PolynomialFeatures.

    En Scikit-Learn, estas herramientas operan bajo el patrón de diseño de objetos transformadores, los cuales ejecutan dos pasos fundamentales:

    1. .fit(): El objeto analiza la estructura del dataset de entrada para aprender las dimensiones y variables disponibles.
    2. .transform(): Utiliza las reglas aprendidas en el entrenamiento para generar un nuevo conjunto de datos con las columnas polinomiales calculadas.

    Código de Implementación en Python

    from sklearn.preprocessing import PolynomialFeatures
    import numpy as np
    
    # 1. Supongamos que tenemos una matriz de datos original (X_data)
    # Para este ejemplo, una columna con valores del 1 al 3
    X_data = np.array([[1], [2], [3]])
    
    # 2. Instanciar la clase especificando el grado deseado (ej. Grado 2)
    # Por defecto, incluye también el término de sesgo (columna de unos)
    polyFeat = PolynomialFeatures(degree=2, include_bias=False)
    
    # 3. Ajustar el transformador a los datos para que aprenda las dimensiones
    polyFeat.fit(X_data)
    
    # 4. Transformar la característica original 'x' en el nuevo espacio 'x, x^2'
    X_poly = polyFeat.transform(X_data)
    
    # Visualización del cambio estructural
    print("Datos Originales:\n", X_data)
    print("\nDatos Polinomiales (x, x^2):\n", X_poly)
    

    Análisis del Output

    Al imprimir la matriz generada X_poly, observamos cómo el transformador expandió automáticamente el espacio de características:

    Datos Originales:
     [[1]
      [2]
      [3]]
    
    Datos Polinomiales (x, x^2):
     [[1. 1.]
      [2. 4.]
      [3. 9.]]
    

    La primera columna conserva los valores originales ($x$) y la segunda columna almacena de manera automática sus valores elevados al cuadrado ($x^2$). A partir de este momento, la matriz X_poly está lista para ser introducida en un algoritmo de regresión lineal estándar, permitiéndole trazar curvas y capturar patrones de negocio sofisticados.

    Preparación de Variables: Codificación y Escalado

    La selección de variables consiste en elegir el conjunto óptimo de características (features) que formarán parte de nuestro modelo. Sin embargo, antes de poder alimentar cualquier algoritmo, la mayoría de estas características deben ser transformadas. Los modelos matemáticos de Machine Learning no entienden de texto ni de etiquetas cualitativas; operan bajo matrices numéricas estrictas.

    Más allá de las transformaciones logarítmicas y polinomiales, existen dos pilares fundamentales en la ingeniería de características:

    • Codificación (Encoding): El proceso de convertir características no numéricas (cualitativas, categóricas u ordinales) en valores numéricos.
    • Escalado (Scaling): La homogeneización del rango de las variables numéricas para que compartan una escala comparable y ningún predictor domine a otro por su magnitud matemática.

    El método idóneo para transformar nuestros datos dependerá enteramente de la naturaleza intrínseca de la característica, dividiéndose principalmente en dos grandes tipologías:

                          Tipos de Datos Categóricos
    
             ┌────────────────────────┴────────────────────────┐
             ▼                                                 ▼
      Datos Nominales                                   Datos Ordinales
    (Sin orden jerárquico)                            (Con orden intrínseco)
    Ej: Colores (Rojo, Azul)                          Ej: Rangos (Bajo, Medio, Alto)
    Ej: Estado (Casado, Soltero)                      Ej: Temperatura (Frío, Templado, Caliente)
    

    Estrategias de Codificación Categórica

    Codificación Binaria (Binary Encoding)

    Es la técnica de mapeo más simple y se aplica de forma exclusiva a variables dicotómicas, es decir, aquellas que solo pueden tomar dos valores posibles. Consiste en transformar la condición de la variable en un estado booleano de $0$ o $1$ (Falso o Verdadero).

    • Ejemplos: * ¿Está casado? $\to$ No = 0, Sí = 1
      • Género $\to$ Masculino = 0, Femenino = 1
      • Estado de transacción $\to$ Fallida = 0, Exitosa = 1

    Codificación One-Hot (One-Hot Encoding)

    Cuando nos enfrentamos a variables de tipo nominal con múltiples categorías sin orden implícito (como el color o el país de residencia), la codificación binaria simple se queda corta. Si asignáramos números correlativos ($1, 2, 3$) a una columna llamada Color (Rojo=1, Azul=2, Verde=3), el modelo interpretaría erróneamente que el Verde vale tres veces más que el Rojo, o que el Azul está “en medio” de ambos, distorsionando las operaciones matemáticas del algoritmo.

    Para solucionar esto, One-Hot Encoding expande la columna original en múltiples columnas binarias independientes (tantas como categorías existan). Cada nueva columna representa una categoría exclusiva: si el registro original pertenecía a esa categoría se le asigna un $1$, y al resto un $0$.

    Ejemplo de Transformación:

    Supongamos que tenemos un DataFrame con una sola columna llamada Color:

    IDColor
    1Rojo
    2Azul
    3Verde
    4Rojo

    Tras aplicar la transformación One-Hot Encoding, nuestro conjunto de datos se reestructura de la siguiente manera:

    IDColor_RojoColor_AzulColor_Verde
    1100
    2010
    3001
    4100

    Codificación Ordinal (Ordinal Encoding)

    A diferencia de los datos nominales, los datos ordinales sí poseen una jerarquía u orden integrado en su significado. En estos escenarios, convertir las etiquetas en números enteros secuenciales es completamente válido y deseable, ya que preserva la progresión de la variable.

    • Ejemplo: Niveles de riesgo o prioridad $\to$ Bajo = 1, Medio = 2, Alto = 3.

    El Dilema de la Distancia Geométrica: Al aplicar una codificación ordinal de $1, 2, 3$, le estamos indicando implícitamente al algoritmo que la distancia matemática entre Bajo y Medio ($2 – 1 = 1$) es exactamente la misma que entre Medio y Alto ($3 – 2 = 1$). En el mundo real, esta equidistancia métrica podría no existir.

    Criterios de Selección: ¿One-Hot u Ordinal?

    A medida que avances en tus proyectos de ciencia de datos, te enfrentarás al dilema de cuál técnica elegir para tus variables ordenadas. Tienes dos opciones principales:

    1. Mantener el orden (Ordinal Encoding): Utilizas una sola columna con enteros ($1, 2, 3$). La ventaja es que conservas la noción de progresión y jerarquía, y el dataset no crece en tamaño. La desventaja es que asumes distancias matemáticas artificiales entre categorías.
    2. Privilegiar la separación (One-Hot Encoding): Rompes la variable en múltiples columnas binarias. La ventaja es que eliminas el supuesto de las distancias fijas, permitiendo al modelo tratar cada estado de forma independiente. La desventaja es que pierdes por completo la información del orden secuencial y aumentas la dimensionalidad del dataset.

    No existe una respuesta única; experimentar con ambas estrategias en tus Jupyter Notebooks y evaluar el impacto directo en la precisión del modelo te dará el criterio necesario para tomar la mejor decisión en cada caso de negocio.

    Fundamentos del Escalado de Características (Feature Scaling)

    El escalado de características es una etapa crítica en el procesamiento de datos que consiste en ajustar los rangos de nuestras variables continuas para que compartan una magnitud matemática comparable.

    En los conjuntos de datos del mundo real, las variables vienen expresadas en unidades y magnitudes completamente dispares. Por ejemplo, si analizamos el éxito comercial de un artículo, podríamos tener:

    • Precio del producto: Con valores que oscilan entre $0$ y $10$ dólares.
    • Puntos de venta distribuidos: Con valores que oscilan entre $10,000$ y $50,000$ tiendas.

    Si alimentamos un modelo directamente con estas métricas en bruto, la disparidad de magnitudes distorsionará los cálculos de muchos algoritmos, haciendo que una variable eclipse por completo a la otra debido a su tamaño numérico y no a su verdadera importancia analítica.

    El Impacto de la Magnitud en los Algoritmos de Distancia

    Para entender por qué la diferencia de escalas representa un problema crítico, analicemos el comportamiento de los algoritmos basados en proximidad geométrica, como K-Nearest Neighbors (KNN) o los modelos de clasificación médica.

    Imaginemos un sistema diseñado para identificar pacientes en situación de riesgo. El algoritmo mapea a los individuos en un plano bidimensional y determina el diagnóstico de un nuevo paciente evaluando la distancia geométrica hacia sus “vecinos” más cercanos (es decir, si se sitúa cerca de pacientes de alto riesgo o cerca de pacientes sanos).

    Un Ejemplo Extremo: Edad en Segundos vs. Operaciones Quirúrgicas

    Para ilustrar la distorsión, supongamos que registramos dos variables para evaluar el riesgo clínico de un paciente:

    1. Número de cirugías previas: Un indicador crítico donde una variación de $1$ a $10$ cirugías denota un cambio drástico en la fragilidad del paciente. (Rango de variación: $9$ unidades).
    2. Edad del paciente: Supongamos que, por un error de diseño o de captura, la edad se mide en segundos en lugar de años.

    Dado que un solo año equivale aproximadamente a $31.5$ millones de segundos, la diferencia de edad entre dos personas de 20 y 21 años se traducirá matemáticamente en una distancia de $31,500,000$ unidades.

    La Consecuencia Matemática:

    Al calcular la distancia geométrica (como la distancia euclidiana), el algoritmo procesará estos valores numéricos fríos:

    • La brecha crítica de cirugías se pondera como un cambio insignificante de $9$ unidades.
    • La diferencia de un solo año de edad se pondera como un abismo de $31.5$ millones de unidades.

    Como resultado, el modelo agrupará a los pacientes basándose únicamente en que tengan exactamente la misma edad, ignorando por completo el número de cirugías. La escala ha sesgado la geometría del problema, anulando el poder predictivo del historial médico.

    Estrategias Comunes de Escalado

    Para solucionar este sesgo, aplicamos transformaciones matemáticas que homogeneizan las magnitudes de las características. A continuación, se detallan los tres enfoques más utilizados en Machine Learning:

    [Image comparing Standard Scaling, Min-Max Scaling, and Robust Scaling on a dataset with outliers]

    Escalado Estándar (Standard Scaling / Z-Score)

    Este método transforma las características para que tengan una media igual a $0$ y una desviación estándar igual a $1$, expresando cada valor en términos de cuántas desviaciones estándar se aleja de la media central.

    La fórmula matemática para estandarizar un valor $x$ es:

    $$z = \frac{x – \mu}{\sigma}$$

    Donde $\mu$ es la media de la columna y $\sigma$ es la desviación estándar.

    • Consideración: El Escalado Estándar es susceptible a la influencia de valores atípicos (outliers). Si un dataset contiene un registro con un valor extremadamente alto, este arrastrará la media hacia arriba y expandirá artificialmente la desviación estándar, afectando la compresión del resto de los datos normales.

    Escalado Min-Max (Min-Max Scaling / Normalization)

    Esta técnica transforma los datos para confinarlos estrictamente dentro de un rango acotado, por lo general entre $0$ y $1$.

    La fórmula para calcular la normalización Min-Max es:

    $$x_{\text{scaled}} = \frac{x – x_{\text{min}}}{x_{\text{max}} – x_{\text{min}}}$$

    Ejemplo Numérico de Prototipado:

    Supongamos una columna donde el valor mínimo ($x_{\text{min}}$) es $3$ y el valor máximo ($x_{\text{max}}$) es $100$.

    • Si evaluamos el valor mínimo $3$:$$\frac{3 – 3}{100 – 3} = \frac{0}{97} = 0$$
    • Si evaluamos un valor intermedio como $40$:$$\frac{40 – 3}{100 – 3} = \frac{37}{97} \approx 0.381$$
    • Si evaluamos el valor máximo $100$:$$\frac{100 – 3}{100 – 3} = \frac{97}{97} = 1$$
    • Consideración Crítica: El escalado Min-Max es altamente sensible a los valores atípicos. Si todos los registros de una columna se encuentran entre $0$ y $10$, pero existe un único registro atípico con el valor de $10,000$, ese registro se convertirá en el $x_{\text{max}}$. Al aplicar la fórmula, todos los datos reales del negocio (entre $0$ y $10$) se verán compactados y “aplastados” en una fracción microscópica cercana a $0$, perdiendo su variabilidad y detalle visual.

    Escalado Robusto (Robust Scaling)

    El escalado robusto se diseñó específicamente para contrarrestar la debilidad ante los valores atípicos. En lugar de utilizar la media y la desviación estándar (sensibles a extremos) o los valores máximos y mínimos absolutos, utiliza estadísticas de posición robustas: la mediana y el Rango Intercuartílico (IQR).

    La fórmula matemática es:

    $$x_{\text{scaled}} = \frac{x – \text{mediana}}{\text{IQR}}$$

    Donde el IQR representa la distancia entre el percentil 75 ($Q_3$) y el percentil 25 ($Q_1$), concentrando el $50\%$ central de los datos.

    • Ventaja: Al basarse en la masa central de la distribución, la presencia de valores atípicos extremos en los bordes no sesga ni deforma el escalado de los datos limpios.
    • Desventaja: A diferencia de Min-Max, el Escalado Robusto no garantiza que los límites finales queden confinados estrictamente entre $0$ y $1$.

    Matriz de Decisiones en Ingeniería de Características

    Para cerrar este bloque de preparación de datos, consolidaremos las técnicas estudiadas estructurándolas según la naturaleza de cada tipo de variable. En el desarrollo práctico con Python, la librería Scikit-Learn (sklearn.preprocessing) y la librería Pandas proveen todo el arsenal de funciones necesarias para manipular estas características de forma eficiente.

    A continuación, se presenta el mapa de ruta definitivo para transformar tus datos antes de la fase de modelado:

    Tipo de VariableObjetivo AnalíticoHerramienta en Python (sklearn / pandas)
    Numérica ContinuaAjustar magnitudes para algoritmos basados en distancias (KNN, SVM) o gradientes.StandardScaler, MinMaxScaler, RobustScaler
    Nominal / Categórica (Sin Orden)Convertir textos o etiquetas dicotómicas/multiclase en vectores numéricos independientes.OneHotEncoder, LabelBinarizer, pd.get_dummies()
    Ordinal (Con Orden)Mapear jerarquías manteniendo la relación de progresión secuencial.OrdinalEncoder, DictVectorizer

    Implementación de Herramientas según el Tipo de Dato

    Variables Numéricas Continuas (Escalado)

    Cuando tus datos presentan escalas dispares (como pesos, salarios o distancias), importamos los transformadores directos desde el módulo de preprocesamiento de Scikit-Learn:

    from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
    
    # Inicialización de los motores de escalado
    escalador_estandar = StandardScaler()  # Media 0, Dev. Std 1
    escalador_minmax = MinMaxScaler()      # Rango acotado [0, 1]
    escalador_robusto = RobustScaler()     # Basado en Mediana e IQR (antiatípicos)

    Variables Nominales o Categóricas (Codificación sin Orden)

    Para variables como el estado civil (Casado, Soltero) o colores (Rojo, Azul, Verde), requerimos representaciones binarias:

    • LabelBinarizer: Ideal para transformar variables de texto puramente binarias (dos categorías) en ceros y unos.
    • OneHotEncoder / pd.get_dummies(): Crean columnas separadas para cada categoría presente. pd.get_dummies() de Pandas es ampliamente utilizada por su simplicidad para prototipar directamente sobre DataFrames.
    import pandas as pd
    from sklearn.preprocessing import OneHotEncoder
    
    # Alternativa rápida en Pandas para One-Hot Encoding
    df_codificado = pd.get_dummies(df, columns=["columna_color"])
    

    Variables Ordinales (Codificación con Orden)

    Para registrar progresiones jerárquicas (como Bajo = 1, Medio = 2, Alto = 3), usamos OrdinalEncoder. Es fundamental pasar explícitamente el orden correcto de las categorías al transformador; de lo contrario, el algoritmo asignará los números enteros de forma alfabética o aleatoria, destruyendo la lógica del negocio.

    from sklearn.preprocessing import OrdinalEncoder
    
    # Definir explícitamente el orden jerárquico de los factores
    orden_categorias = [["Bajo", "Medio", "Alto"]]
    encoder_ordinal = OrdinalEncoder(categories=orden_categorias)
    

    Resumen del Módulo: Preparación de Datos

    Hemos cubierto los pilares fundamentales para transformar datos crudos en tensores matemáticos optimizados:

    1. Transformaciones No Lineales: Vimos cómo las funciones logarítmicas (np.log1p) suavizan el sesgo positivo y cómo las transformaciones polinomiales expanden la flexibilidad de nuestros modelos permitiéndoles trazar curvas mediante combinaciones lineales.
    2. Codificación Categórica: Aprendimos a aislar variables cualitativas sin orden mediante esquemas One-Hot para evitar falsas métricas de vecindad, y a estructurar variables jerárquicas mediante codificadores ordinales.
    3. Escalado Estadístico: Analizamos visualmente el riesgo de que variables con rangos numéricos masivos eclipsen por completo a métricas críticas en algoritmos basados en distancias (como KNN), y estudiamos cómo la estandarización y normalización mitigan este problema.
  • Exploratory Data Analysis (EDA)

    Introducción al Análisis Exploratorio de Datos (EDA)

    Una vez que hemos completado la fase de limpieza e higiene de nuestras tablas, avanzamos hacia el siguiente pilar fundamental del flujo de trabajo de Machine Learning: el Análisis Exploratorio de Datos (o EDA, por sus siglas en inglés: Exploratory Data Analysis).

    Podemos definir el EDA como la conversación inicial que un científico de datos mantiene con un conjunto de datos antes de tomar decisiones técnicas o algoritmos. Es la fase de reconocimiento donde se examina de forma exhaustiva la información para resumir sus características estructurales básicas, utilizando herramientas tanto estadísticas como de visualización gráfica.

    ¿Por qué es vital realizar un buen EDA?

    • Validación de Sanidad: Permite determinar de forma matemática si los datos recopilados realmente tienen sentido lógico o si, por el contrario, exigen una fase adicional de limpieza profunda, reestructuración o recolección de nuevas variables.
    • Descubrimiento de Patrones: Facilita la detección temprana de tendencias, correlaciones ocultas y anomalías espaciales en la muestra. En el entorno corporativo, los hallazgos puros derivados de un EDA riguroso suelen aportar tanto o más valor estratégico para el negocio que las propias predicciones matemáticas finales del modelo de Machine Learning.

    Herramientas y Técnicas de Análisis en el EDA

    El análisis exploratorio se divide operativamente en dos grandes familias de técnicas que se complementan entre sí:

    Técnicas Estadísticas (Resúmenes Numéricos)

    Consisten en extraer las métricas de tendencia central, dispersión y asociación matemática de las variables. Entre los estadísticos fundamentales destacan:

    • Media y Mediana: Para ubicar el centro de gravedad numérico de los datos.
    • Mínimos y Máximos: Para establecer los rangos operativos de las variables.
    • Correlaciones: Para medir el grado de dependencia lineal entre las distintas columnas del dataset.

    Técnicas Visuales (Gráficos)

    Los ojos humanos procesan los patrones geométricos con mayor rapidez que las tablas de números. El EDA se apoya en tres representaciones icónicas:

    • Histogramas: Para observar la distribución probabilística y la densidad de una variable numérica.
    • Gráficos de Dispersión (Scatter Plots): Ideales para proyectar la relación o correlación entre dos variables continuas e identificar agrupaciones naturales (clusters).
    • Diagramas de Caja (Box Plots): Para evaluar la dispersión cuartílica y geolocalizar valores atípicos (outliers).

    El Ecosistema Tecnológico en Python: Durante el proceso de manipulación de tablas y cálculo de resúmenes estadísticos, la librería estándar por excelencia es Pandas. Para la generación de la capa gráfica y visual, el ecosistema se apoya firmemente en Matplotlib y Seaborn.

    Caso Práctico: Análisis de Perfiles de Candidatos (Job Applicants)

    Para comprender cómo interactúan estas métricas en un entorno de negocio real, consideremos un dataset corporativo diseñado para evaluar las características de un grupo de aspirantes a puestos de trabajo:

    • Aplicación de la Media: Podemos calcular el promedio de las puntuaciones obtenidas en las entrevistas y desglosarlo por ciudad o función laboral. Esto permite contextualizar el rendimiento de un candidato específico frente a la media exacta de sus competidores directos en su misma región geográfica.
    • Aplicación de la Moda (Valor Máximo de Frecuencia): Útil para analizar campos de texto libre o materiales de solicitud. Al extraer la moda de los textos de los currículums, la empresa puede identificar cuáles son las palabras clave o habilidades técnicas que más se repiten entre los aspirantes.
    • Aplicación de Correlaciones: Mediante el cruce estadístico de la columna de “Años de Experiencia” frente a los resultados de las “Evaluaciones Técnicas”, la organización puede evaluar inmediatamente si existe una relación lineal ascendente (a mayor experiencia, mayor puntuación técnica) antes de proceder a la fase de modelado predictivo.

    Muestreo de Datos (Sampling) en DataFrames

    En muchas ocasiones, los científicos de datos se enfrentan a volúmenes masivos de información que saturan la memoria del sistema o ralentizan los tiempos de cómputo de los algoritmos. Para solucionar esto de manera eficiente, se recurre al muestreo aleatorio (random sampling).

    Razones Técnicas para Muestrear

    1. Optimización Computacional: Reducir el dataset original a una muestra representativa acelera los análisis exploratorios y prototipados rápidos sin degradar la calidad de las conclusiones.
    2. División de Datos (Train/Test Split): Es la base para entrenar un modelo en una porción de datos aleatoria y reservar otra muestra completamente independiente para evaluar su capacidad de generalización en fases de testeo.
    3. Preservación de Proporciones (Muestreo Estratificado): Supongamos que trabajamos con un dataset médico para detectar una enfermedad grave que solo padece el 1% de la población mundial. Si tomamos una muestra aleatoria simple muy pequeña, corremos el riesgo de generar un subconjunto donde no haya ningún individuo enfermo o, por el contrario, donde estén sobrerrepresentados. El muestreo estratificado (stratified sampling) asegura que la muestra mantenga exactamente la proporción del 1% del evento de interés.

    Implementación de muestreo con Pandas (.sample)

    La librería Pandas proporciona el método integrado .sample(), diseñado específicamente para extraer filas aleatorias de un objeto DataFrame de forma óptima.

    El siguiente bloque de código demuestra cómo extraer una muestra aleatoria fija de cinco filas, asegurando que no existan registros repetidos mediante el argumento replace=False (comportamiento por defecto), y seleccionando únicamente un segmento de columnas mediante indexación implícita con .iloc:

    import pandas as pd
    
    # Suponiendo que 'df' es nuestro DataFrame cargado (ej. el dataset Iris)
    # 1. Extracción de una muestra aleatoria de 5 registros sin reemplazo
    muestra_df = df.sample(n=5, replace=False)
    
    # 2. Segmentación: seleccionar todas las filas extraídas (:) 
    # y restringir a las últimas tres columnas (-3:)
    resultado_final = muestra_df.iloc[:, -3:]
    
    # 3. Visualización del resultado
    print(resultado_final)
    

    Análisis del Output Resultante

    Al ejecutar el código sobre una base de datos biológica como Iris, la consola devolverá una estructura limpia con índices aleatorios y únicamente las últimas tres columnas indexadas:

    Plaintext

         petal_length    petal_width        species
    34            1.5            0.2         setosa
    112           5.5            2.1      virginica
    78            4.5            1.5     versicolor
    14            1.2            0.2         setosa
    143           5.9            2.3      virginica
    
    • Explicación del Indexado: El operador -3: indica a Python que empiece a contar desde la tercera columna empezando por el final de la tabla (en este caso, petal_length) y avance hasta el término del DataFrame, logrando un aislamiento preciso de los datos que queremos inspeccionar visualmente.

    6. El Ecosistema de Librerías de Visualización en Python

    Para llevar a cabo un Análisis Exploratorio de Datos (EDA) efectivo, Python ofrece tres herramientas principales de visualización que interactúan entre sí de forma jerárquica:

                      Ecosistema de Visualización en Python
                      
                             ┌───────────────────┐
                             │      Seaborn      │  <- Estética avanzada y
                             │ (High-level API)  │     gráficos estadísticos
                             └─────────┬─────────┘
                                       │ (Construido sobre)
                             ┌─────────▼─────────┐
                             │     Matplotlib    │  <- Motor gráfico base
                             │    (Core Engine)  │     y control absoluto
                             └─────────▲─────────┘
                                       │ (Wrapper de)
                             ┌─────────┴─────────┐
                             │   Pandas .plot()  │  <- Prototipado rápido
                             │  (Quick Wrapper)  │     directo desde tablas
                             └───────────────────┘
    
    • Matplotlib (matplotlib.pyplot): Es la librería fundacional y el motor gráfico estándar de Python. Ofrece un control absoluto y microscópico sobre cada elemento del gráfico (ejes, etiquetas, colores, fuentes). Su gran flexibilidad la convierte en la base de la que dependen las demás librerías.
    • Pandas Plotting: Consiste en una interfaz integrada (wrapper) construida directamente sobre Matplotlib. Permite generar gráficos de manera rápida mediante sintaxis abreviada directamente desde un DataFrame (ej. df.plot()). Es ideal para inspecciones rápidas sobre la marcha, aunque carece de la flexibilidad fina de Matplotlib.
    • Seaborn: Una librería de alto nivel desarrollada sobre Matplotlib y diseñada específicamente para el análisis estadístico. Viene configurada con estilos visuales modernos por defecto y proporciona funciones simplificadas para trazar gráficos complejos (como mapas de calor, correlaciones cruzadas o modelos lineales) que requerirían decenas de líneas de código en Matplotlib puro.

    Efecto de Integración: Cuando importas Seaborn (import seaborn as sns) en tu entorno de trabajo, este reconfigura automáticamente las preferencias estéticas globales de Matplotlib. Así, cualquier gráfico posterior que generes usando código puro de Matplotlib adoptará el formato limpio y estilizado característico de Seaborn.

    Gráficos Fundamentales con Matplotlib

    Para inicializar el motor de gráficos de Matplotlib en entornos interactivos como Jupyter Notebooks, es una buena práctica incluir la directiva mágica %matplotlib inline. Esto asegura que los gráficos se rendericen e integren directamente debajo de las celdas de código.

    Gráfico de Dispersión Básico (Scatter Plot)

    Se utiliza para examinar visualmente la relación espacial entre dos variables numéricas continuas. Para configurarlo como un diagrama de puntos puro, debemos anular el trazado de líneas continuo mediante el parámetro ls="" (line style) y definir un marcador circular con marker="o".

    import matplotlib.pyplot as plt
    %matplotlib inline
    
    # Trazado de dispersión entre longitud y ancho del sépalo
    plt.plot(df["sepal_length"], df["sepal_width"], ls="", marker="o")
    plt.xlabel("Sepal Length")
    plt.ylabel("Sepal Width")
    plt.title("Relación Sépalo")
    plt.show()
    

    Gráficos Multicapa con Leyendas

    Matplotlib permite superponer múltiples series de datos sobre el mismo lienzo de forma acumulativa. Cada llamada consecutiva a plt.plot() dibuja una nueva capa de información antes de renderizar la figura final con plt.show().

    # Capa 1: Datos de Sépalo (Azul por defecto)
    plt.plot(df["sepal_length"], df["sepal_width"], ls="", marker="o", label="Sépalo")
    
    # Capa 2: Datos de Pétalo (Verde/Naranja automático)
    plt.plot(df["petal_length"], df["petal_width"], ls="", marker="o", label="Pétalo")
    
    # Activar la caja de leyendas utilizando las etiquetas 'label'
    plt.legend()
    plt.title("Comparativa de Sépalo vs Pétalo")
    plt.show()
    

    Histogramas de Distribución

    Para analizar la distribución probabilística de una sola columna numérica, utilizamos la función plt.hist(). El parámetro bins determina en cuántos intervalos o “barras” se fragmenta el rango total de los datos.

    # Histograma con 25 contenedores de frecuencia
    plt.hist(df["sepal_length"], bins=25)
    plt.xlabel("Longitud del Sépalo")
    plt.ylabel("Frecuencia")
    plt.show()
    

    Personalización Avanzada y Enfoque Orientado a Objetos

    Cuando se requiere construir cuadros de mando (dashboards) complejos o gráficos con modificaciones estructurales estrictas, la API orientada a objetos de Matplotlib mediante plt.subplots() es la opción recomendada. Esta función separa el lienzo general (fig) del recuadro o sistema de ejes de dibujo (ax).

    El siguiente script genera un gráfico de barras horizontales (ax.barh) para evaluar los primeros 10 registros de la muestra, aplicando desfases matemáticos en las etiquetas para centrarlas perfectamente en el medio de cada barra:

    import numpy as np
    
    # 1. Crear la estructura orientada a objetos (Lienzo e Hilera de ejes)
    fig, ax = plt.subplots(figsize=(6, 4))
    
    # 2. Generar índices de posición (0 a 9) y graficar barras horizontales
    posiciones = np.arange(10)
    ax.barh(posiciones, df["sepal_width"].iloc[:10], align="center")
    
    # 3. Ajustar los ticks para que queden centrados en cada barra
    # Desfasamos el rango para colocar el marcador visual en el medio geométrico
    ax.set_yticks(posiciones)
    ax.set_yticklabels(range(1, 11))
    
    # 4. Inyección de metadatos de texto mediante el objeto 'ax'
    ax.set(xlabel="Ancho del Sépalo (cm)", ylabel="Índice del Candidato", title="Análisis de Muestra Corta")
    plt.show()
    

    Visualización Rápida con Sintaxis de Pandas

    Si los datos ya están estructurados en un DataFrame de Pandas, podemos encadenar operaciones de agregación estadística (como .groupby()) directamente con el método de asistencia visual .plot(), reduciendo la cantidad de código necesario.

    Python

    # 1. Agrupar por especie y calcular el promedio de todas las métricas
    resumen_especies = df.groupby("species").mean()
    
    # 2. Trazar un gráfico de barras directamente desde el DataFrame agrupado
    resumen_especies.plot(
        kind="bar",
        color=["red", "blue", "black", "green"],
        fontsize=10,
        figsize=(4, 4)
    )
    
    plt.ylabel("Promedio (cm)")
    plt.title("Métricas Promedio por Especie")
    plt.show()
    

    Visualizaciones Avanzadas con Seaborn

    Gráfico de Pares (Pair Plot)

    Es una de las funciones más potentes de Seaborn para el análisis exploratorio. sns.pairplot() genera de forma automática una matriz de gráficos cruzados entre todas las variables numéricas del dataset.

    • Fuera de la diagonal: Muestra gráficos de dispersión (scatter plots) para evaluar correlaciones entre pares de variables.
    • En la diagonal: Al cruzar una variable consigo misma, el algoritmo la sustituye automáticamente por curvas de densidad o histogramas de distribución.
    • El parámetro hue: Segmenta y colorea los puntos según una variable categórica (como la especie), lo que permite identificar visualmente si las clases son linealmente separables.
    import seaborn as sns
    
    # Matriz de correlación visual segmentada por especies
    sns.pairplot(data=df, hue="species", size=2.5)
    plt.show()
    

    Gráfico de Densidad Hexagonal (Hexbin Joint Plot)

    Cuando trabajamos con miles de registros, un gráfico de dispersión tradicional sufre de saturación (overplotting), impidiendo ver dónde se concentran realmente los puntos. El gráfico sns.jointplot con el parámetro kind="hex" agrupa los puntos espaciales en celdas hexagonales: cuanto más oscuro es el hexágono, mayor es la densidad de registros en esa zona. Además, incluye las distribuciones marginales en los márgenes superior derecho.

    # Gráfico conjunto de densidad hexagonal con distribuciones marginales
    sns.jointplot(data=df, x="sepal_length", y="sepal_width", kind="hex")
    plt.show()
    

    Segmentación por Rejillas de Facetas (Facet Grid)

    El objeto FacetGrid permite subdividir el dataset en función de una o más variables categóricas, creando subgráficos independientes (facets) organizados en columnas o filas. Esto facilita la comparación directa de distribuciones entre diferentes categorías.

    # 1. Definir la cuadrícula estructural basada en la columna de especies
    grid = sns.FacetGrid(data=df, col="species", margin_titles=True)
    
    # 2. Mapear un histograma de la variable deseada en cada una de las facetas
    grid.map(plt.hist, "sepal_width", bins=10)
    plt.show()
    

    Visualización Avanzada: Integración de Pandas y Seaborn

    El Análisis Exploratorio de Datos (EDA) se potencia cuando combinamos la estructuración rápida de datos con capacidades de visualización avanzadas. A continuación, analizaremos cómo automatizar y sofisticar nuestros gráficos utilizando la sintaxis nativa de Pandas y el motor estadístico de Seaborn.

    Sintaxis de Trazado de Pandas con Agrupaciones (.groupby)

    Por defecto, al llamar al método .plot() en un DataFrame de Pandas, la librería intenta trazar un gráfico de líneas. Sin embargo, cuando se combina con funciones de agregación estadística como .groupby(), podemos mapear resúmenes multivariables rápidamente.

    Si quisiéramos analizar el promedio de las dimensiones del dataset Iris (longitud y ancho de pétalos y sépalos) para cada una de las especies, podemos definir colores personalizados y un tamaño de lienzo compacto de $4 \times 4$ pulgadas mediante el argumento figsize:

    Python

    # Agrupar por la columna objetivo y calcular la media de cada característica
    df_resumen = df.groupby("species").mean()
    
    # Generar gráfico de barras con parámetros personalizados
    df_resumen.plot(
        kind="bar", 
        color=["red", "blue", "black", "green"], 
        fontsize=10, 
        figsize=(4, 4)
    )

    Al ejecutar este bloque, el eje $X$ mostrará las categorías (setosa, versicolor, virginica) y las barras representarán las medias de cada característica, diferenciadas según la paleta de colores inyectada.

    Matrices de Correlación Visual: sns.pairplot

    Una de las utilidades más potentes de Seaborn para el análisis exploratorio es el Pair Plot (gráfico de pares). Mediante una sola línea de código, sns.pairplot() construye una matriz tridimensional que cruza todas las variables numéricas del DataFrame entre sí.

    • Fuera de la diagonal: Se generan gráficos de dispersión (scatter plots) automáticos que revelan la relación espacial y la correlación entre dos variables distintas.
    • En la diagonal: Dado que cruzar una variable consigo misma generaría una línea recta sin valor analítico, Seaborn la sustituye de forma inteligente por un histograma o una curva de densidad para mostrar la distribución univariable.
    • El argumento hue: Al igualar este parámetro a una columna categórica (como hue="species"), el algoritmo segmenta y colorea los datos por clases. Esto permite identificar de un vistazo si las poblaciones o perfiles de negocio son linealmente separables.
    import seaborn as sns
    import matplotlib.pyplot as plt
    
    # Importar Seaborn e implementar el gráfico de pares global
    sns.pairplot(data=df, hue="species", height=2.5)
    plt.show()
    

    💡 Caso de Uso en Negocios: Esta misma lógica se aplica al entorno corporativo. Si sustituyéramos las variables biológicas por métricas como Inversión en Publicidad (Ad Spend) y Retorno de Ingresos (Revenue), el pair plot nos mostraría instantáneamente la dispersión y efectividad de las campañas, además de la distribución de las ventas en el mismo lienzo.

    Gráficos de Densidad Hexagonal: sns.jointplot

    Cuando un conjunto de datos contiene miles de registros, los gráficos de dispersión tradicionales sufren de saturación de puntos (overplotting). Los marcadores se superponen unos sobre otros creando masas de color uniformes donde es imposible determinar dónde se concentra la mayoría de los datos.

    Para resolver este problema, Seaborn ofrece el gráfico de agrupación hexagonal (Hexbin plot), invocable mediante sns.jointplot() con el argumento kind="hex".

    Características del Gráfico Hexagonal:

    1. Contenedores Geométricos: El espacio bidimensional se divide en hexágonos regulares que actúan como “contenedores de frecuencia”.
    2. Escala de Densidad: Las celdas hexagonales se colorean con una escala de intensidad. Los hexágonos más oscuros denotan una densidad de datos muy alta (zonas de solapamiento frecuente), mientras que los más claros representan registros aislados.
    3. Márgenes de Distribución: En las secciones superior y derecha del gráfico, se renderizan de forma automática los histogramas univariables de los ejes $X$ e $Y$, permitiendo un doble diagnóstico en una sola mirada.

    Python

    # Trazado conjunto con densidad de hexágonos e histogramas marginales
    sns.jointplot(data=df, x="sepal_length", y="sepal_width", kind="hex")
    plt.show()
    

    Segmentación Estructural mediante sns.FacetGrid

    Cuando el objetivo del análisis es aislar por completo el comportamiento de una variable según subcategorías específicas (similar a realizar operaciones analíticas en paralelo), la herramienta ideal es FacetGrid.

    Una rejilla de facetas (FacetGrid) toma un criterio de segmentación (por ejemplo, las distintas especies) y genera un lienzo independiente para cada una, ordenándolas en columnas. Luego, mediante el método .map(), podemos “inyectar” el tipo de gráfico que deseamos replicar en cada contenedor.

    # 1. Inicializar la cuadrícula estructurando las columnas según la especie
    grid = sns.FacetGrid(data=df, col="species", margin_titles=True)
    
    # 2. Mapear un histograma del ancho del sépalo en cada una de las facetas creadas
    grid.map(plt.hist, "sepal_width", bins=10)
    plt.show()
    

    Si quisiéramos evaluar una característica diferente bajo la misma estructura, simplemente reconfiguramos el mapeo sobre el objeto:

    # 3. Reutilizar la cuadrícula para analizar la longitud del sépalo por separado
    grid_longitud = sns.FacetGrid(data=df, col="species", margin_titles=True)
    grid_longitud.map(plt.hist, "sepal_length", bins=10)
    plt.show()
    

    Conclusiones del Módulo EDA

    Con estas metodologías concluimos la base teórica del Análisis Exploratorio de Datos:

    • Métricas y Gráficos en Sintonía: El EDA combina la precisión numérica de los resúmenes estadísticos (medias, medianas, correlaciones) con el impacto visual de los histogramas y diagramas de dispersión.
    • Ecosistema Jerárquico: Recordamos que Matplotlib actúa como el motor flexible de bajo nivel, Pandas como la vía de prototipado veloz y Seaborn como la solución óptima para análisis estadísticos complejos y estéticos.
    • Muestreo Estratificado: Al enfrentarnos a datos masivos o desbalanceados, el submuestreo controlado protege la fidelidad de las proporciones poblacionales.
  • Data Cleaning

    La Importancia de la Limpieza de Datos (Data Cleaning)

    Una vez recuperada la información de nuestras fuentes, entramos en una de las fases más críticas y que más tiempo consume en el flujo de trabajo: la limpieza de datos.

    En el ámbito del Machine Learning rige una máxima fundamental: “Garbage-In, Garbage-Out” (Si entra basura, sale basura). Un modelo predictivo es un reflejo matemático directo de los datos con los que ha sido entrenado; si los datos de entrada son erróneos, ruidosos o están mal formateados, las predicciones resultantes serán completamente deficientes y carecerán de fiabilidad.

    Cuando alimentamos un algoritmo con observaciones (rows) deficientes, distorsionamos de forma inmediata la relación matemática real entre nuestras características (features) y la variable objetivo (target).

    El impacto de los errores en los componentes del modelo

    • Etiquetas (Labels): Si las variables de salida que intentamos predecir contienen errores humanos, el modelo aprenderá patrones falsos. Consideremos el histórico repositorio de imágenes ImageNet: si los encargados de etiquetar las fotos clasifican erróneamente un gato como un perro, el algoritmo asimilará características visuales equivocadas, frustrando la precisión final en producción.
    • Algoritmos: Los programas informáticos estiman sus parámetros asumiendo por defecto que los datos representan fielmente la realidad. No tienen la capacidad intuitiva de saber si un dato es un error de registro o un evento real.
    • Características (Features): Imagina un modelo de detección de fraude bancario donde los importes de las transacciones o las ubicaciones geográficas se registren de manera incorrecta. Un simple desfase en los dígitos puede hacer que una transacción legítima sea clasificada como fraude, o peor aún, que un patrón delictivo real pase desapercibido.

    Desafíos Corporativos en la Gestión de Datos

    A pesar de que los líderes empresariales priorizan el uso de la analítica avanzada, la gestión de la infraestructura de datos plantea tres grandes problemáticas operativas:

    1. Escasez de Datos (Lack of Data): Para que un modelo de Machine Learning generalice correctamente, necesita un volumen mínimo de datos relevantes. Si no existen, la organización debe reestructurar sus métodos de captura o adquirir bases de datos de terceros.
    2. Exceso de Datos e Infoxicación: Paradójicamente, poseer demasiada información fragmentada en decenas de servidores, nubes híbridas e infraestructuras on-premises satura la capacidad analítica. El problema deja de ser estadístico y se convierte en un reto de ingeniería de datos: recolectar, centralizar y unificar bajo un mismo criterio antes de programar el modelo.
    3. Calidad Deficiente de los Datos: Las encuestas del sector revelan que el 60% de los líderes empresariales se enfrentan a serias dificultades para gestionar la calidad de sus datos. La Inteligencia Artificial no puede desplegarse en una organización si sus cimientos informáticos carecen de un control de calidad riguroso.

    Anatomía de los Datos Corruptos o “Sucios”

    ¿A qué nos referimos exactamente cuando afirmamos que un conjunto de datos está “sucio” (messy data)? Principalmente a cinco escenarios:

    • Datos Duplicados: Introducen un peso artificial y sesgan el algoritmo. En el caso del fraude con tarjetas de crédito, si una misma transacción fraudulenta se duplica por error 200 veces en la tabla, el modelo hipertrofiará la importancia de esas características específicas, memorizando un patrón artificial.
    • Texto Inconsistente y Errores Tipográficos: Las computadoras son literales. Para un algoritmo, los registros "Madrid", "madrid", "Madrid " o "Madrdi", son cuatro categorías de ciudades completamente distintas, fragmentando la potencia predictiva de la característica.
    • Datos Faltantes (Missing Data): La ausencia de registros es inevitable en el mundo real. Sin embargo, una concentración excesiva de valores nulos en columnas clave anula su utilidad, obligándonos a descartar indicadores que podrían haber sido proyectores altamente determinantes.
    • Valores Atípicos (Outliers): Valores anómalos o extremos que se desvían drásticamente de la tendencia general de la muestra. Pueden sesgar las medias estadísticas y descolocar las líneas de regresión de los modelos, ocultando la verdadera estructura matemática subyacente.
    • Conflictos de Sincronización (Sourcing): Desajustes temporales o de formato que ocurren al fusionar datos extraídos de sistemas operativos dispares (por ejemplo, cruzar bases de datos transaccionales con registros de interacciones web).

    Estrategias para el Tratamiento de Datos Duplicados

    Frente a las observaciones repetidas, el científico de datos no debe aplicar una política de eliminación automatizada sin antes realizar un análisis de contexto. La naturaleza del problema dicta la regla de limpieza:

    ¿Los duplicados representan la realidad?
     ├── SÍ (Ej. Mediciones físicas idénticas en Iris con pocos decimales) ──> MANTENER observaciones.
     └── NO (Ej. Mismo archivo de imagen subido múltiples veces) ───────────> ELIMINAR mediante filtrado.
    
    • Cuándo mantener los duplicados: En conjuntos de datos científicos o de mediciones biológicas (como el dataset Iris), es perfectamente factible encontrar dos flores con las mismas dimensiones de sépalo y pétalo si el instrumento de medición solo registra dos decimales. Al ser una ocurrencia real de la naturaleza, ambas observaciones deben conservarse para no alterar la distribución de probabilidad real.
    • Cuándo eliminar los duplicados: En tareas de visión artificial o clasificación de imágenes, tener copias binarias exactamente iguales del mismo archivo de imagen no aporta información nueva al proceso de aprendizaje. Al contrario, satura el almacenamiento y sesga los pesos de la red neuronal, por lo que deben ser eliminados.

    Buenas Prácticas en el Laboratorio de Datos

    Antes de realizar cualquier purga o eliminación física de registros, es fundamental aplicar filtros exploratorios en Pandas para inspeccionar visualmente la estructura de los duplicados. Como regla de seguridad en ingeniería de datos, nunca destruyas o sobreescribas la base de datos original; mantén siempre una copia intacta de los datos crudos por si requieres auditar o recuperar información esencial en las fases avanzadas del proyecto.

    Gestión de Valores Faltantes (Missing Data)

    Los modelos de Machine Learning son ecuaciones matemáticas que requieren un valor numérico o categórico en cada celda para operar; no aceptan celdas en blanco o vacías porque carecen de información para realizar cálculos. Por lo tanto, el científico de datos debe aplicar políticas consistentes para asegurar que cada característica (feature) y etiqueta (label) contengan información válida.

    Existen tres estrategias principales para resolver la ausencia de datos:

                              Estrategias para Valores Faltantes
                                              
             ┌────────────────────────────────┼────────────────────────────────┐
                                                                             
    Eliminación Completa             Imputación de Datos               Enmascaramiento (Masking)
    (Descarte de filas/columnas)     (Sustitución estadística)        (Creación de nueva categoría)
    

    Eliminación Completa (Deletion)

    Consiste en descartar filas (observaciones) o columnas (características) enteras que contengan nulos. Si asumimos que todas las variables del dataset son útiles para el negocio, la práctica común se reduce a eliminar las filas afectadas.

    • Pros: Es una solución rápida y directa. Limpia el conjunto de datos de inmediato sin necesidad de inventar o “adivinar” un valor de reemplazo artificial.
    • Contras: Si la ausencia de datos se extiende por muchas filas, corremos el riesgo de perder un volumen crítico de información. Además, si los datos faltan por un sesgo sistemático (por ejemplo, un sensor que falla siempre a altas temperaturas), eliminar esos registros sesgará por completo el entrenamiento del modelo.

    B. Imputación de Datos (Imputation)

    Consiste en rellenar las celdas vacías utilizando métricas estadísticas calculadas a partir del resto de la muestra, como la media (promedio), la mediana o algoritmos predictivos más complejos (como K-Nearest Neighbors).

    • Pros: Evita la pérdida de observaciones valiosas, manteniendo la integridad del volumen del dataset para el entrenamiento del algoritmo.
    • Contras: Introduce un nuevo nivel de incertidumbre y ruido. El modelo entrenará asumiendo que esos valores estimados son certezas reales del mundo real, lo que puede debilitar su precisión en producción.

    C. Enmascaramiento (Masking)

    Esta técnica consiste en transformar los valores faltantes en una categoría explícita e independiente (por ejemplo, crear la categoría "Desconocido" o "No aportado"). Se aplica bajo la premisa de que la ausencia del dato en sí misma constituye una señal de información útil.

    • Ejemplo Práctico: Imaginemos una encuesta de satisfacción telefónica donde la última pregunta queda sistemáticamente en blanco. Esto puede indicar que los usuarios tienden a colgar el teléfono exhaustos antes de finalizar. Registrar esa celda vacía con la etiqueta "Colgó antes del fin" aporta un valor analítico real para el modelo.
    • Pros: Mantiene intactas las filas y columnas sin alterar artificialmente los valores numéricos mediante promedios.
    • Contras: Añade incertidumbre al asumir homogéneamente que todos los datos faltantes de esa columna ocurren por la misma razón exacta.

    Identificación y Tratamiento de Valores Atípicos (Outliers)

    Un valor atípico u outlier es una observación que se desvía de forma drástica y anormal respecto a la tendencia general del resto de los datos. Por lo general, representan anomalías o errores de captura que distorsionan el fenómeno real que el modelo intenta asimilar.

    El peligro de ignorar los outliers

    Supongamos que analizamos las ventas semanales de un comercio local. La inmensa mayoría de los registros oscila en un rango normal entre $10$ y $50$. Sin embargo, debido a un error tipográfico en el sistema, una semana registra un valor de $3000$.

    Si el modelo predictivo se basa en promedios estadísticos elementales para proyectar el futuro:

    • Sin el outlier, el promedio real rondaría los $30$.
    • Al introducir el valor de $3000$ en un conjunto de datos pequeño, la media se dispara artificialmente a un rango de $200$ o $300$.

    Como resultado, el modelo generará predicciones sobredimensionadas que no reflejan el comportamiento histórico real del negocio.

    Nota de Contexto Analítico: Aunque muchos outliers deben ser depurados para no sesgar el algoritmo, algunos valores atípicos son altamente informativos. Un pico de $3000$ en ventas podría no ser un error, sino el reflejo de una campaña de marketing viral masiva. Investigar la raíz de un outlier suele ofrecer conclusiones estratégicas más valiosas que analizar los datos estandarizados.

    Detección Visual de Outliers

    El análisis exploratorio gráfico es el primer mecanismo para capturar anomalías espaciales en nuestras variables. Las librerías de visualización en Python (como Seaborn) ofrecen herramientas directas para este fin:

    • Histogramas y Gráficos de Densidad (sns.displot): Permiten evaluar la distribución de la frecuencia de los datos. Un bloque de barras aislado y muy alejado de la gran “campana” central delata la presencia de outliers.
    • Diagramas de Caja o Boxplots (sns.boxplot): Es el estándar gráfico por excelencia para identificar atípicos. Representa visualmente la mediana, los cuartiles y dibuja los valores anómalos como puntos aislados fuera de unos límites calculados conocidos como “bigotes”.
    a box plot diagram showing median quartiles interquartile range and outliers, generada por IA

    Implementación en Python (Seaborn)

    import seaborn as sns
    import matplotlib.pyplot as plt
    
    # Generación de un gráfico de distribución (Histograma)
    sns.displot(data=df, x="unemployment", bins=30)
    plt.show()
    
    # Generación de un diagrama de caja para detectar puntos atípicos
    sns.boxplot(data=df, x="unemployment")
    plt.show()
    

    Detección Matemática: El Criterio del Rango Intercuartílico ($IQR$)

    Para identificar y aislar matemáticamente estos puntos sin depender exclusivamente de la interpretación visual, aplicamos el método estadístico del Rango Intercuartílico ($IQR$).

    El $IQR$ mide la dispersión del 50% central de los datos y se calcula restando el percentil 75 ($Q_3$) menos el percentil 25 ($Q_1$):

    $$IQR = Q_3 – Q_1$$

    A partir de ahí, establecemos un límite inferior y superior matemáticos (“los bigotes del boxplot”). Cualquier dato que se encuentre fuera de estas fronteras se define formalmente como un outlier:

    $$\text{Límite Mínimo} = Q_1 – 1.5 \times IQR$$

    $$\text{Límite Máximo} = Q_3 + 1.5 \times IQR$$

    Desarrollo del Algoritmo en Python con NumPy

    El siguiente bloque de código automatiza el cálculo de los límites matemáticos y utiliza técnicas de comprensión de listas (list comprehension) para extraer la lista exacta de valores que superan la frontera superior de desempleo:

    Python

    import numpy as np
    
    # 1. Extracción de los percentiles estructurales de la columna
    q25 = np.percentile(df["unemployment"], 25)  # Cuartil 1 (Q1)
    q75 = np.percentile(df["unemployment"], 75)  # Cuartil 3 (Q3)
    
    # 2. Cálculo del Rango Intercuartílico
    iqr = q75 - q25
    
    # 3. Definición matemática de las fronteras de tolerancia
    limite_min = q25 - 1.5 * iqr
    limite_max = q75 + 1.5 * iqr
    
    # 4. Filtrado y aislamiento de los outliers superiores
    outliers = [x for x in df["unemployment"] if x > limite_max]
    
    print(f"Valores atípicos detectados por encima del límite ({limite_max}): {outliers}")
    

    Además del método $IQR$, otra estrategia avanzada para detectar valores atípicos en modelos predictivos consiste en el análisis de residuos (evaluando residuos estandarizados, eliminados o studentizados), una técnica matemática que estudia la desviación entre las predicciones del modelo y los valores reales para capturar observaciones rebeldes. En la próxima sección profundizaremos en estas técnicas de diagnóstico avanzado.

    Detección Avanzada de Outliers: El Análisis de Residuos

    Además de los métodos gráficos y estadísticos tradicionales como el $IQR$, el Machine Learning nos ofrece una perspectiva alternativa para detectar anomalías mediante el uso de residuos.

    ¿Qué es un residuo?

    En el contexto de un modelo predictivo (como una regresión), un residuo es la diferencia matemática entre el valor real observado y el valor estimado o predicho por nuestro algoritmo:

    $$\text{Residuo} = \text{Valor Real} – \text{Valor Predicho}$$

    Los residuos cuantifican de forma directa el fallo del modelo. Si un punto del dataset arroja un residuo inusualmente grande, significa que las reglas generales que el modelo ha aprendido no sirven para explicar esa observación concreta. Por lo tanto, ese registro se convierte en un candidato inmediato a valor atípico.

    Enfoques Estadísticos Basados en Residuos

    Para evaluar adecuadamente estos desvíos sin importar la escala de la variable, recurrimos a tres tratamientos matemáticos:

    1. Residuos Estandarizados (Standardized Residuals): Consiste en tomar el residuo bruto y dividirlo por su error estándar. Esta normalización es vital para dar contexto. Por ejemplo, cometer un error de $4$ unidades es insignificante si nuestra variable objetivo se mueve en un rango de millones de dólares, pero es un fallo crítico si la variable oscila únicamente entre $0$ y $5$.
    2. Residuos Eliminados (Deleted Residuals): El procedimiento consiste en extraer físicamente una observación del conjunto de datos, reentrenar el modelo predictivo sin ella y evaluar qué proyecta el nuevo algoritmo para ese punto específico. Si al retirar la observación el comportamiento general del modelo experimenta un cambio drástico, se confirma que dicho punto ejercía una influencia desproporcionada (típica de un outlier).
    3. Residuos Studentizados (Studentized Residuals): También denominados residuos studentizados externamente. Es una técnica híbrida avanzada que toma los residuos eliminados explicados en el punto anterior y los estandariza dividiéndolos por su error estándar calculado. Al aislar por completo la influencia del punto sospechoso durante el cálculo de la varianza, es uno de los criterios matemáticos más robustos para diagnosticar observaciones rebeldes en producción.

    Estrategias Operativas ante la Detección de un Outlier

    Una vez identificado un valor atípico mediante criterios matemáticos o de residuos, el científico de datos dispone de cinco vías de actuación. La elección de una técnica sobre otra dependerá estrictamente del contexto de negocio y del diseño de los datos.

    EstrategiaProsContras
    Eliminar por completo (Deletion)Pone fin inmediato a las distorsiones que el outlier causa en el algoritmo.Pérdida de información complementaria útil al destruir la fila entera.
    Imputar un valor alternativoModera el impacto nocivo del pico anómalo sin necesidad de sacrificar el resto de la fila.Se destruye el valor real sustituyéndolo por una estimación artificial.
    Transformación matemática de la columnaOperaciones como la transformación logarítmica compactan las escalas, integrando los extremos de forma natural.Altera la escala de interpretación original de la característica.
    Modelado predictivo (Regresión)Reemplaza el dato utilizando modelos auxiliares basados en las características normales.Requiere un coste de ingeniería y computación significativamente mayor.
    Conservar el valor originalMantiene intacta la naturaleza real de los datos y respeta señales que podrían ser informativas.Obliga a usar algoritmos específicos que sean inmunes o robustos ante outliers.

    Resumen del Bloque de Limpieza de Datos

    Con esta sección cerramos el módulo estratégico de preparación y depuración de datos:

    • Misión Crítica: La limpieza de datos determina el techo de precisión de cualquier sistema de Inteligencia Artificial. Ignorar este paso conduce inevitablemente al efecto Garbage-In, Garbage-Out.
    • Tratamiento Criterioso: La gestión de duplicados, valores faltantes y puntos atípicos jamás debe automatizarse a ciegas. Cada decisión debe sopesar la pérdida de volumen informativo frente a la introducción de incertidumbre o sesgos artificiales.
    • Diagnóstico Avanzado: Herramientas como los diagramas de caja, el rango intercuartílico ($IQR$) y las métricas de residuos transforman la limpieza en un proceso matemático auditable y reproducible.

    Una vez que hemos garantizado la higiene analítica de nuestras tablas, estamos listos para avanzar hacia la siguiente etapa del flujo de trabajo: el Análisis Exploratorio de Datos (EDA), donde buscaremos correlaciones ocultas antes de entrenar nuestros primeros modelos de Machine Learning.

  • Recuperación y limpieza de datos

    El viaje de la ciencia de datos comienza siempre con la obtención de materia prima. Para construir cualquier modelo predictivo o extraer conclusiones analíticas, primero debemos dominar la ingeniería de adquisición de datos.

    En este artículo, exploraremos las estrategias esenciales para recuperar información desde múltiples orígenes —incluyendo bases de datos relacionales (SQL), no relacionales (NoSQL), interfaces de programación (APIs) y almacenes en la nube—, centrándonos en la ingesta práctica de los formatos de archivo más extendidos del sector: CSV y JSON.

    La Ingesta de Archivos CSV (Comma Separated Values)

    El formato CSV (Valores Separados por Comas) es uno de los estándares más antiguos y populares para el intercambio de datos estructurados. Consiste en archivos de texto plano organizados en filas, donde cada dato o valor se encuentra delimitado convencionalmente por una coma.

    A través de la librería Pandas en Python, la lectura e integración de estos archivos en estructuras tabulares de memoria conocidas como DataFrames se resuelve con apenas unas pocas líneas de código.

    Implementación básica en Python

    Para cargar un archivo CSV en nuestro entorno de desarrollo (como un Jupyter Notebook), se sigue el flujo de importación y definición de rutas estándar:

    import pandas as pd
    
    # Definición de la ruta del archivo estructurado
    file_path = "data/iris_data.csv"
    
    # Ingesta del archivo mediante la función nativa de Pandas
    data = pd.read_csv(file_path)
    
    # Visualización de las primeras 5 observaciones en el notebook
    print(data.iloc[0:5])

    Al ejecutar estas instrucciones utilizando un conjunto de datos como el histórico Iris, Pandas procesará la estructura de texto y nos devolverá una representación tabular limpia con sus respectivas columnas de características y objetivo:

    sepal_lengthsepal_widthpetal_lengthpetal_widthspecies
    05.13.51.40.2setosa
    14.93.01.40.2setosa
    24.73.21.30.2setosa
    34.63.11.50.2setosa
    45.03.61.40.2setosa

    Parámetros avanzados para el control de lectura

    En la práctica, los archivos CSV del mundo real rara vez vienen perfectamente formateados. La función pd.read_csv cuenta con una serie de argumentos cruciales para mitigar inconsistencias durante la ingesta:

    • Manejo de delimitadores alternativos (sep): No todos los archivos mal llamados “CSV” usan comas. Si nos enfrentamos a un archivo separado por tabuladores (TSV), podemos modificar el separador usando la sintaxis de escape de Python para tabulaciones (\t):
    df = pd.read_csv(file_path, sep='\t')
    • Manejo de espacios en blanco (delim_whitespace): Cuando los datos se separan mediante espacios en blanco variables o irregulares en lugar de un carácter fijo, habilitar este parámetro le indica a Pandas que trate cualquier secuencia de espacios como un único delimitador válido.
    • Gestión de cabeceras erróneas o ausentes (header): Muchos archivos del mundo real incluyen comentarios o filas vacías en la parte superior. Con el argumento header, podemos especificar explícitamente qué número de fila debe ser interpretado como el nombre de las columnas.
    • Asignación manual de nombres (names): Si el archivo carece por completo de una fila de títulos, podemos pasar una lista de cadenas con los nombres deseados a través del parámetro names. Es obligatorio asegurar que la longitud de esta lista coincida exactamente con el número de columnas físicas del archivo.
    • Tratamiento personalizado de valores nulos (na_values): En ocasiones, los errores de captura o la ausencia de datos se representan con cadenas específicas (como “NA”, “N/A”, o códigos numéricos anómalos como 99). Pasar estos patrones al parámetro na_values fuerza a Pandas a convertirlos automáticamente en valores nulos reales (NaN), facilitando su posterior limpieza estadística.

    Gestión de Archivos JSON (JavaScript Object Notation)

    El formato JSON (Notación de Objetos JavaScript) es el estándar por excelencia para la transferencia de información semiestructurada en la web. Es la estructura nativa en la que opera la inmensa mayoría de las APIs modernas y las bases de datos no relacionales (NoSQL).

    Su propósito es almacenar información de una manera organizada, jerárquica y fácil de leer tanto para humanos como para máquinas. Conceptualmente, la estructura de un archivo JSON es idéntica a la de los diccionarios de Python, basada en un sistema de pares clave-valor (key-value).

    Si analizamos una única fila de datos mapeada en formato JSON, la clave actúa como el nombre de la característica (columna) y el valor representa el dato de esa celda en particular:

    {
      "sepal_length": 5.1,
      "sepal_width": 3.5,
      "petal_length": 1.4,
      "petal_width": 0.2,
      "species": "setosa"
    }

    Lectura y Escritura de JSON en Pandas

    La manipulación de estas estructuras se realiza de manera simétrica a los archivos planos mediante funciones optimizadas de entrada y salida:

    • Lectura de datos (pd.read_json): Para cargar el archivo semiestructurado en memoria y transformarlo en un DataFrame plano, basta con invocar la función pasando la ruta correspondiente:
    df_json = pd.read_json("data/iris_data.json")
    • Escritura de datos (to_json): Una vez que hayamos concluido los procesos de transformación o limpieza sobre nuestras variables en memoria, podemos exportar el DataFrame de vuelta a un archivo JSON físico definiendo el nombre de salida:
    data.to_json("data/output_cleaned.json")

    Resolución de problemas de orientación espacial (orient)

    A diferencia de las tablas rígidas de un CSV, un archivo JSON puede estructurarse u orientarse de múltiples formas (por registros, por columnas, por índices o dividido en listas independientes).

    Si al importar un archivo JSON los datos se cargan de forma desalineada o el intérprete arroja un error de lectura, el primer paso obligado es revisar la documentación del parámetro orient. Probar diferentes configuraciones de este argumento (como split, records, index, columns o values) le explicará a Pandas la dirección exacta en la que se organizaron las claves dentro del fichero, asegurando una conversión exitosa a filas y columnas.

    Conexión e Ingesta desde Bases de Datos SQL

    Cuando trabajamos en entornos corporativos o con infraestructuras de datos maduras, la información estructurada suele almacenarse en sistemas de gestión de bases de datos relacionales o SQL (Structured Query Language). Estas bases de datos se caracterizan por tener un esquema fijo y altamente estructurado, organizando la información en tablas interconectadas mediante claves funcionales.

    Existen múltiples motores SQL en el mercado, cada uno con sutiles diferencias de sintaxis pero con una lógica operativa idéntica:

    • Sistemas Tradicionales / Locales: PostgreSQL, MySQL, Microsoft SQL Server, Oracle DB o IBM Db2.
    • Sistemas de Almacenamiento en la Nube (Data Warehouses): AWS Redshift o Google BigQuery.

    Para interactuar con ellos desde Python, necesitamos utilizar la librería conectora específica de cada motor junto con los métodos nativos de Pandas.

    Implementación Práctica con SQLite

    El paquete estándar sqlite3 viene integrado en la instalación nativa de Python y es ideal para entornos de pruebas o bases de datos ligeras en archivo. El flujo de trabajo consta de tres fases: inicializar la ruta física, abrir un canal de comunicación o conexión (con) y lanzar la consulta analítica (query).

    import sqlite3
    import pandas as pd
    
    # 1. Definición de la ruta donde reside la base de datos relacional
    db_path = "data/classic_rock.db"
    
    # 2. Establecimiento del canal de conexión física
    con = sqlite3.connect(db_path)
    
    # 3. Construcción de la consulta estructurada en formato string
    query = "SELECT * FROM rock_songs;"
    
    # 4. Ingesta directa del resultado SQL hacia un DataFrame de Pandas
    df_rock = pd.read_sql(query, con)
    

    Alternativas de Conexión Corporativa

    Si necesitas migrar este mismo bloque de código para extraer datos de los servidores de producción de tu empresa, solo debes sustituir el conector inicial importando el controlador adecuado:

    • SQLAlchemy: Una de las librerías de mapeo objeto-relacional más potentes, compatible con casi cualquier motor SQL del mercado.
    • Psycopg2: El driver optimizado específico para infraestructuras basadas en PostgreSQL.
    • ibm_db: El conector dedicado para la familia de bases de datos IBM Db2.

    Ingesta desde bases de datos NoSQL (No Relacionales)

    En el polo opuesto encontramos las bases de datos NoSQL, diseñadas para almacenar información no estructurada o semiestructurada. Al prescindir de esquemas rígidos de filas y columnas fijas, reducen drásticamente los costes de infraestructura tecnológica y permiten lecturas masivas a gran velocidad.

    La mayoría de los motores NoSQL orientados a documentos almacenan sus registros directamente en el formato JSON que analizamos previamente.

    Tipos principales de arquitecturas NoSQL

    1. Bases de Datos Documentales (ej. MongoDB): Cada observación equivale a un “documento” independiente (un archivo jerárquico JSON) con sus propias propiedades internas.
    2. Almacenes Clave-Valor (ej. Redis): Sistemas hiperrápidos donde se apunta a un identificador único (ID) para recuperar un bloque de datos asociado (nombre, dirección, etc.).
    3. Bases de Datos de Grafos (ej. Neo4j): Diseñadas de forma nativa para el análisis de redes complejas. Son el motor detrás de plataformas como LinkedIn o Facebook, estructuradas para calcular conexiones de primer, segundo o tercer nivel entre usuarios de forma inmediata.
    4. Familias de Columnas Anchas (ej. Cassandra): Agrupan columnas lógicamente relacionadas en “familias de columnas”. Por ejemplo, los datos de un usuario pueden segmentarse en una familia Personal (nombre, edad) y otra Profesional (experiencia, visa).

    Conexión y Extracción en MongoDB con pymongo

    Para interactuar con una base de datos NoSQL como MongoDB, importamos el método MongoClient para abrir la conexión corporativa (la cual puede requerir credenciales de seguridad si el servidor reside en la nube).

    from pymongo import MongoClient
    import pandas as pd
    
    # 1. Apertura del cliente de conexión (local o URL en la nube)
    con = MongoClient()
    
    # 2. Selección de la base de datos específica dentro del servidor
    db = con.database_name
    
    # 3. Consulta de la colección de documentos mediante llaves vacías {} (equivale a SELECT *)
    # Esto genera un objeto cursor que funciona como un generador de documentos JSON
    cursor = db.collection_name.find({})
    
    # 4. Conversión del generador en una lista nativa de diccionarios Python
    dict_list = list(cursor)
    
    # 5. Volcado de la lista estructurada dentro del DataFrame analítico
    df_nosql = pd.DataFrame(dict_list)
    

    Acceso a Datos en la Nube y APIs Públicas

    Muchos proveedores de datos corporativos no exigen conexiones directas a servidores de bases de datos; en su lugar, habilitan APIs (Application Programming Interfaces). Las APIs actúan como ventanillas de servicio técnico automatizadas: envías una solicitud con ciertos parámetros y la API te devuelve un archivo estructurado (normalmente JSON) de forma inmediata. Un ejemplo común es el uso de APIs para extraer tweets históricos o métricas publicitarias desde plataformas como Amazon o Google.

    Asimismo, la comunidad científica suele alojar bases de datos de libre acceso directamente en la nube. Pandas permite saltarse la descarga manual de estos archivos en tu ordenador; si conoces la dirección web (URL) del fichero, puedes inyectarlo directamente en memoria:

    import pandas as pd
    
    # Definición de la URL del set de datos en un repositorio en la nube (ej. UC Irvine ML Repository)
    data_url = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"
    
    # Descarga, procesamiento y conversión a DataFrame en un único paso
    df_cloud = pd.read_csv(data_url)
    
  • Introducción a Machine Learning

    ¿Cómo aprenden las máquinas? Una Guía Introductoria al Machine Learning y Deep Learning

    En la era digital, escuchamos los términos Inteligencia Artificial, Machine Learning y Deep Learning casi a diario. Sin embargo, ¿sabemos realmente qué significan y cómo se diferencian?

    A menudo se piensa en la informática como un conjunto de reglas estrictas escritas por humanos. Pero existe una rama tecnológica que rompe con este esquema: las máquinas que aprenden a partir de la experiencia. A continuación, desglosaremos los conceptos fundamentales para entender esta revolución tecnológica, que muchos ya consideran “la nueva electricidad”.

    ¿Qué es el Machine Learning (Aprendizaje Automático)?

    El Machine Learning (ML) es un subconjunto de la Inteligencia Artificial centrado en el estudio y la construcción de programas que no son programados explícitamente. En lugar de seguir instrucciones fijas, estos algoritmos descubren patrones ocultos a medida que se les expone a una mayor cantidad de datos a lo largo del tiempo.

    La regla de oro del ML: A mayor volumen de datos de calidad, mejor será la capacidad del algoritmo para entender los patrones subyacentes.

    Sin embargo, es importante destacar que el rendimiento de estos algoritmos tradicionales tiene un límite: llega un punto en el que el aprendizaje se estanca (efecto de rendimiento decreciente), independientemente de cuántos datos nuevos agreguemos.

    Los Componentes del Aprendizaje: Características frente a Objetivo

    Para entender cómo una máquina procesa la información, debemos desglosar un conjunto de datos en sus dos elementos fundamentales. Tomemos como ejemplo el famoso Iris Dataset, un conjunto de datos clásico en el mundo académico que registra las medidas de tres especies de flores (Virginica, Setosa y Versicolor):

    • Características (Features): Son las variables independientes, es decir, las propiedades observables que introducimos en el algoritmo. En el caso de las flores, las características son cuatro de sus medidas físicas: la longitud del sépalo, el ancho del sépalo, la longitud del pétalo y el ancho del pétalo.
    • Variable Objetivo (Target): Es la columna o etiqueta final que el programa intenta predecir de forma automática. En este ejemplo, el objetivo es determinar con precisión la especie de la flor.

    El flujo de producción: El algoritmo se entrena cruzando las features con sus respectivos targets. Una vez que el modelo ha aprendido la relación matemática entre ambos componentes, se puede poner en producción. A partir de ese momento, si le entregamos únicamente las cuatro características de una nueva flor, la máquina será capaz de adivinar la especie correcta sin necesidad de que un humano la etiquete.

    Los Dos Grandes Pilares: Aprendizaje Supervisado vs. No Supervisado

    Dependiendo de los datos que tengamos y del objetivo que busquemos, el Machine Learning se divide principalmente en dos metodologías:

    CriterioAprendizaje SupervisadoAprendizaje No Supervisado
    ¿Tiene etiquetas / Target?Sí. Los datos están preclasificados.No. Solo se dispone de los datos brutos.
    Meta principalPredecir el objetivo o etiqueta para nuevos datos.Encontrar una estructura o relación oculta en los datos.
    Ejemplo prácticoDetección de fraude: Analizar si una transacción con tarjeta es “fraude” o “no fraude” basándose en el historial.Segmentación de clientes: Agrupar usuarios de un e-commerce por comportamientos similares para campañas de marketing.
    EvaluaciónExiste una respuesta correcta o incorrecta bien definida.No hay una respuesta “correcta” única; se deben evaluar diferentes modelos.

    El Desafío de los Datos Complejos y el Nacimiento del Deep Learning

    El Machine Learning tradicional es extraordinario trabajando con datos estructurados (tablas, números, categorías). Por ejemplo, para predecir si un correo es spam, o si una transacción es fraudulenta, podemos definir características claras como la hora, el monto o la ubicación.

    Sin embargo, ¿qué pasa cuando intentamos que una máquina identifique si una imagen contiene un gato o un perro?

    Para una computadora, una imagen es una rejilla de miles de píxeles (una pequeña foto de 256 x 256 píxeles se traduce en más de 65.000 características). Si analizamos cada píxel de forma aislada, perdemos la relación espacial (cómo se conecta un píxel con el de al lado para formar un ojo o una nariz).

    Para solucionar esta limitación del ML tradicional, nació el Deep Learning (Aprendizaje Profundo).

    Deep Learning: Redes Neuronales y Autoaprendizaje

    El Deep Learning (DL) es, a su vez, un subconjunto del Machine Learning que utiliza modelos matemáticos altamente complejos llamados redes neuronales profundas.

    La diferencia radical entre ambos enfoques radica en el procesamiento de las características:

    • En el Machine Learning clásico: Un ingeniero o científico de datos humano debe identificar y extraer manualmente las características relevantes antes de entrenar al modelo.
    • En el Deep Learning: El proceso es directo. Se introducen los píxeles brutos de la imagen en la red neuronal, y es el propio modelo el que aprende a extraer y combinar las características en capas intermedias.

    Aunque los pasos intermedios de una red neuronal profunda no siempre son fáciles de interpretar por los humanos, el modelo es capaz de detectar primero bordes, luego combinar esos bordes para formar figuras (ojos, labios, formas) y finalmente realizar predicciones asombrosamente precisas (como reconocer el rostro de una persona específica).

    Las Dos Grandes Metodologías: Aprendizaje Supervisado y No Supervisado

    No todos los problemas de datos se estructuran de la misma manera ni persiguen los mismos objetivos. Por ello, el Machine Learning se divide principalmente en dos ramas bien definidas:

    Aprendizaje Supervisado (Supervised Learning)

    En esta modalidad, el algoritmo trabaja con un conjunto de datos completamente etiquetado. Esto significa que cada registro de entrenamiento incluye tanto las características como la respuesta correcta (el target).

    • El Objetivo: Aprender a mapear las entradas con las salidas para predecir la etiqueta de datos futuros y desconocidos.
    • Ejemplo 1 (Filtro de Spam): El sistema analiza miles de correos preclasificados por humanos como “Spam” o “No Spam”. Al procesar este histórico, el algoritmo detecta qué palabras o patrones se repiten en el correo basura y aprende a aislarlo en la bandeja de entrada real.
    • Ejemplo 2 (Detección de Fraude Financiero): Una entidad bancaria recopila un gran volumen de transacciones financieras etiquetadas previamente como “legítimas” o “fraudulentas”. El modelo estudia las variables asociadas a cada caso y automatiza las alertas en tiempo real.

    Aprendizaje No Supervisado (Unsupervised Learning)

    A diferencia del anterior, aquí nos enfrentamos a bases de datos que no contienen etiquetas ni una variable objetivo. La máquina se encuentra completamente sola ante los datos brutos.

    • El objetivo: encontrar una estructura interna, relaciones ocultas o agrupaciones naturales dentro del conjunto de información.
    • Ejemplo (Segmentación de Clientes): En una plataforma de comercio electrónico (e-commerce), se introducen los datos de comportamiento de compra de los usuarios. Sin decirle al programa qué buscar, el algoritmo encuentra patrones comunes y los divide en grupos con perfiles similares (por ejemplo: “compradores impulsivos de fin de semana” o “buscadores de ofertas tecnológicas”). Esto permite al equipo de marketing diseñar campañas personalizadas para cada segmento.

    Nota metodológica: En el Aprendizaje No Supervisado no existe una respuesta correcta o incorrecta predefinida por un humano. El científico de datos debe probar diferentes modelos matemáticos y evaluar qué agrupaciones tienen más coherencia y valor estratégico para el problema de negocio.

    Datos Estructurados: El Terreno Ideal del Machine Learning Tradicional

    Para cerrar esta introducción, es vital comprender qué tipo de problemas se resuelven mejor con estas técnicas clásicas. Pensemos nuevamente en el caso de la detección de fraudes con tarjetas de crédito.

    Si queremos identificar una transacción sospechosa, las características idóneas a combinar son muy claras y específicas:

    • La hora exacta de la transacción.
    • El monto económico cobrado.
    • La ubicación geográfica del comercio.
    • La categoría de la compra (tecnología, alimentación, viajes, etc.).

    Toda esta información se organiza de forma perfecta en tablas de filas y columnas. Este tipo de datos estructurados con características altamente intuitivas representa el escenario ideal donde los algoritmos tradicionales de Machine Learning ofrecen un rendimiento excepcional, rápido y sumamente preciso.

    Aquí tienes la continuación y el cierre de esta sección del artículo, totalmente integrada y redactada con un estilo educativo y profesional:

    El Límite de los Datos Estructurados: El Desafío de las Imágenes

    Como hemos visto, el Machine Learning tradicional brilla cuando trabaja con datos tabulares organizados. Sin embargo, el panorama cambia drásticamente cuando intentamos resolver problemas con datos no estructurados, como las imágenes.

    Imaginemos que queremos entrenar un modelo tradicional para determinar si una imagen muestra un gato o un perro. ¿Qué características (features) deberíamos extraer de forma manual?

    Para una computadora, una imagen no es más que una matriz de datos numéricos que representan el color de cada píxel individual. Si intentamos usar cada píxel como una característica aislada, nos topamos inmediatamente con dos grandes obstáculos teóricos y prácticos:

    1. La explosión de dimensiones: Incluso una imagen digital pequeña, de apenas $256 \times 256$ píxeles, se traduce en más de 65,000 píxeles. Esto significa obligar al algoritmo tradicional a trabajar con más de 65,000 características simultáneamente, un volumen masivo y poco eficiente para sus ecuaciones matemáticas.
    2. La pérdida de la relación espacial: Si analizamos cada píxel como una variable independiente, el sistema pierde por completo el contexto de lo que hay a su alrededor. La información de un píxel solo cobra verdadero sentido en relación con sus píxeles vecinos. Son las agrupaciones de píxeles contiguos las que forman de manera conjunta una estructura reconocible, como la nariz, los ojos o la silueta de un animal.

    Este callejón sin salida técnico marcó durante años las limitaciones del Machine Learning clásico, abriendo paso a una de las mayores revoluciones de la informática moderna: el Deep Learning.

    Deep Learning y Redes Neuronales Profundas

    El Deep Learning (Aprendizaje Profundo) es un subcampo especializado dentro del Machine Learning que aborda la complejidad mediante el uso de modelos matemáticos avanzados conocidos como redes neuronales profundas.

    Su gran ventaja competitiva radica en que los ingenieros ya no necesitan definir e identificar las características de forma manual. En su lugar, el propio modelo recibe los píxeles brutos de la imagen y posee la capacidad única de aprender, extraer y combinar esas características de forma completamente autónoma, estructurando internamente las relaciones espaciales de los datos.

    ¿Cómo se diferencian en la práctica? Un caso de estudio

    Para entender la diferencia radical en el flujo de trabajo, analicemos cómo identificarían ambos enfoques el rostro de una persona específica (por ejemplo, “Arjun”):

    • En el Machine Learning Clásico: El científico de datos debe definir a priori las características matemáticas que componen un rostro (la distancia entre los ojos, la forma de la nariz, el contorno de los labios). Si el profesional tiene éxito en su estimación manual, introduce estas propiedades seleccionadas en el algoritmo para que intente predecir la identidad. Diseñar buenas características mediante este método es una tarea titánica y sumamente compleja de realizar con precisión.
    • En el Deep Learning: El proceso se unifica en un único flujo de trabajo. La red neuronal recibe directamente los píxeles brutos de la fotografía. A través de sus múltiples capas intermedias (u ocultas), el modelo realiza una ingeniería de características automatizada. En las primeras capas suele identificar elementos simples como bordes y líneas; posteriormente, las capas más profundas combinan esos bordes para reconocer formas complejas (ojos, labios, orejas) hasta procesar el rostro completo y predecir de forma exacta que se trata de Arjun.

    Aunque el procesamiento matemático en estas capas intermedias a menudo resulta difícil de interpretar por los seres humanos (un fenómeno conocido como “caja negra”), su utilidad práctica para resolver tareas complejas de visión artificial y clasificación de imágenes ha superado con creces cualquier tecnología previa.

    Criterios de Elección: ¿Cuándo usar cada tecnología?

    A pesar del enorme impacto del Deep Learning en la investigación científica de vanguardia, es fundamental desmitificar la idea de que siempre es la mejor opción. La elección de la herramienta depende estrictamente de la naturaleza del problema y de los recursos disponibles:

    • El poder del Machine Learning tradicional: Sigue siendo el rey indiscutible cuando se trabaja con conjuntos de datos pequeños o cuando la información cambia y se actualiza de manera constante en el tiempo sin un patrón fijo. En estos escenarios, los algoritmos tradicionales ofrecen un rendimiento significativamente superior, son más rápidos de entrenar y requieren una fracción del coste computacional.
    • El momento del Deep Learning: Se vuelve indispensable cuando nos enfrentamos a grandes volúmenes de datos masivos (Big Data) y a formatos no estructurados (imágenes, audio, vídeo o texto libre), donde el rendimiento del Machine Learning tradicional tiende a estancarse.

    Comprender esta frontera técnica nos permite apreciar cómo la Inteligencia Artificial ha evolucionado desde sus fundamentos teóricos primitivos hasta convertirse en lo que muchos expertos ya catalogan como “la nueva electricidad” de nuestra sociedad.

    Aquí tienes la continuación del artículo, correspondiente a la sección final del módulo donde se introducen los fundamentos técnicos, las herramientas y el ciclo de trabajo que preparan al lector para la fase práctica de la ciencia de datos.

    Fundamentos del Aprendizaje Automático: Vocabulario, Herramientas y Ciclo de Trabajo

    Para cerrar esta introducción general y prepararnos con éxito de cara a las fases de Análisis Exploratorio de Datos (EDA), limpieza de información e inferencia estadística, es fundamental asentar las bases metodológicas. Todo profesional del Machine Learning debe dominar un conjunto de herramientas técnicas, un flujo de trabajo ordenado y un vocabulario preciso que evite interpretaciones erróneas de los modelos en producción.

    Requisitos Técnicos y de Criterio Científico

    Antes de pulsar la primera línea de código, el desarrollo de modelos requiere dos pilares conceptuales que no se pueden ignorar:

    1. Bases Matemáticas Fuertes: Es un error muy común saltarse los fundamentos de la estadística básica (probabilidad, cálculo de momentos, regla de Bayes) y del álgebra lineal. Sin esta base, se corre el riesgo de configurar mal los parámetros y malinterpretar por completo las métricas de rendimiento de los algoritmos.
    2. El Ecosistema Python: En la práctica, el entorno estándar de trabajo se compone de Jupyter Notebooks (o entornos iPython) respaldados por librerías maduras y especializadas:
    LibreríaPropósito Principal en el Flujo de Trabajo
    NumPyOperaciones matemáticas complejas y análisis numérico de matrices.
    PandasManipulación de datos mediante estructuras llamadas DataFrames (tablas).
    Matplotlib / SeabornCreación de gráficos y visualización de distribuciones de datos.
    Scikit-LearnImplementación de algoritmos tradicionales de Machine Learning.
    TensorFlow / KerasConstrucción y entrenamiento de Redes Neuronales Profundas (Deep Learning).

    El Flujo de Trabajo Típico en Machine Learning (Workflow)

    Construir un modelo predictivo no consiste simplemente en alimentar un algoritmo con datos aleatorios. Se trata de un proceso estructurado en 6 etapas secuenciales:

    1. Definición del Problema (Problem Statement): Determinar con exactitud qué meta se quiere alcanzar. Por ejemplo: “Queremos un sistema capaz de clasificar diferentes razas de perros a partir de fotografías”.
    2. Recolección de Datos (Data Collection): Reunir la información necesaria. Siguiendo el ejemplo anterior, no bastará con una sola imagen por raza; se requerirán miles de fotografías etiquetadas, tomadas desde diferentes ángulos y bajo distintas condiciones de luz.
    3. Exploración y Preprocesamiento (Exploratory Data Analysis & Preprocessing): Limpiar los datos para que el modelo pueda interpretarlos de forma óptima. Aquí se analizan mapas de calor de densidad de píxeles, se evalúa la distribución de las muestras y se transforman las variables en arrays multidimensionales normalizados.
    4. Modelado (Modeling): Entrenar los algoritmos. Normalmente se empieza construyendo un modelo base muy sencillo (baseline) para establecer un estándar mínimo de comparación antes de probar arquitecturas más complejas.
    5. Validación (Validation): Evaluar si el modelo realmente resuelve el problema. Para ello se utiliza un conjunto de datos de prueba retenido o apartado (holdout set). Se trata de datos e imágenes que el modelo nunca vio durante el entrenamiento, lo que permite verificar su porcentaje real de precisión en el mundo real.
    6. Toma de Decisiones y Despliegue (Deployment): Una vez que el modelo alcanza los rangos de precisión exigidos por el negocio, se comunican los resultados a las partes interesadas (stakeholders) y se traslada el código a un entorno de producción para que empiece a operar en tiempo real.

    Diccionario Esencial para Científicos de Datos

    Para comprender la estructura de cualquier conjunto de datos, resulta muy útil imaginar la información en el formato visual de una hoja de cálculo. Tomando de nuevo como referencia el histórico de las especies de flores Iris, definimos los cuatro términos clave:

    • Variable Objetivo (Target Variable): Es la columna o propiedad específica que deseamos predecir de cara al futuro. En nuestra hoja de cálculo, la columna objetivo es Species.
    • Características (Features / Explanatory Variables): Son el resto de las columnas de la tabla. Representan las variables independientes que el algoritmo analiza para buscar patrones y deducir el objetivo. En este conjunto de datos contamos con 4 características: sepal length, sepal width, petal length y petal width. Cuando el modelo trabaje en producción con datos nuevos, solo dispondrá de estas columnas.
    • Observación o Ejemplo (Observation / Example): Se refiere a una fila completa dentro de la base de datos. Cada fila representa un caso de estudio individual con todas sus características y su correspondiente resolución, y sirve para que el modelo ajuste matemáticamente sus parámetros de aprendizaje.
    • Etiqueta (Label): Es el valor concreto que toma la variable objetivo dentro de una observación específica. Mientras que la columna completa es el target, el dato puntual de una sola fila (por ejemplo, el valor escrito de versicolor o setosa) es la etiqueta.

  • AdventureWorks: Custom Full-Stack Data & Analytics Platform

    Este proyecto documenta el diseño e implementación de una plataforma de datos full-stack y de código abierto, desarrollada para reemplazar una infraestructura analítica comercial costosa y rígida. A través de la migración de la base de datos relacional de AdventureWorks desde SQL Server hacia PostgreSQL, el desarrollo de pipelines ETL modulares en Python y la integración de modelos predictivos de Machine Learning, se transformó un sistema de reportes tradicional en un motor independiente y escalable, centralizado en una aplicación web interactiva con Flask y Docker.

    El Desafío del Negocio (The Problem Statement)

    AdventureWorks Inc. gestionaba su inteligencia de negocio y reportería a través de una suite de analítica comercial propietaria y tradicional basada en licencias de software cerrado. Al escalar la organización, el equipo se enfrentó a tres bloqueos críticos:

    1. Escala Exponencial de Costos por Licenciamiento: El modelo de cobro por usuario o por núcleo se volvió financieramente insostenible ante la creciente demanda de acceso concurrente por parte de Ventas, RRHH, Producción y Compras.
    2. Limitaciones de Integración con Data Science y ML: El ecosistema cerrado dificultaba acoplar modelos avanzados de Machine Learning (como forecasting de series temporales con Prophet o clasificación con XGBoost) en los flujos de datos sin recurrir a costosos módulos de IA adicionales.
    3. Dependencia del Proveedor (Vendor Lock-in): La falta de control sobre el procesamiento impedía personalizar la interfaz a nivel corporativo y flexibilizar el despliegue en infraestructura propia.

    Nuestra Solución:

    Arquitectura Full-Stack Data de Código Abierto. Diseñamos y desarrollamos una plataforma web propietaria, independiente y end-to-end que migró la infraestructura y transformó los reportes rígidos del pasado en un motor analítico y predictivo centralizado:

    Pipeline del proyecto

    Fase 1: Ingesta, Auditoría y Exploración Profunda (OLTP)

    Objetivo: Comprender las reglas y la estructura exacta del negocio para mitigar sesgos en las etapas de transformación y modelado.

    • Aprovisionamiento: Extracción y restauración del backup relacional nativo (.bak) de AdventureWorks 2022 en SQL Server.
    • Data Quality & Perfilado Inicial: Auditoría técnica mediante scripts SQL de exploración individual para cada una de las 18 tablas relevantes, documentando rangos de fechas, valores únicos y patrones de columnas clave.
    • Garantía de Integridad: Identificación exhaustiva de claves primarias y foráneas reales, consolidando las dinámicas operacionales en un Diccionario de Datos maestro y un diagrama relacional completo.

    Fase 2: Diseño del Modelo Analítico Relacional y Requerimientos

    Objetivo: Traducir los objetivos corporativos en una arquitectura de datos técnica limpia y estructurada.

    • Definición de Granularidad: Establecimiento preciso del nivel de detalle de las entidades operativas (ej. Ventas por línea de pedido, Inventarios por snapshots de producto/almacén y Finanzas por periodo contable).
    • Mapeo de KPIs: Selección de indicadores estratégicos para una visión de 360° (Sales Amount, Total Product Cost, Profit, Margen %, Tendencias YoY/MoM, Ticket Promedio y Alertas de Stock).
    • Gobernanza Técnica: Generación de un Documento de Requerimientos Funcionales como base para validar formalmente las necesidades lógicas con cada área antes de iniciar el código.

    Fase 3: Extracción y Preparación mediante Vistas SQL

    Objetivo: Proveer una capa de datos purificada, segura y optimizada desde la base de origen.

    • Desacoplamiento Estructural: Creación de vistas en SQL Server diseñadas según los requerimientos funcionales para pre-estructurar el set de datos.
    • Seguridad y Anonimización: Selección rigurosa de atributos descriptivos y demográficos para segmentación, aplicando políticas de privacidad al anonimizar o remover datos sensibles de clientes.
    • Cómputo en Base de Datos: Implementación de transformaciones básicas directamente en el motor de origen (como cálculo dinámico de edad y unificación de jerarquías de productos) para aligerar las fases posteriores de procesamiento en Python.

    Fase 4: Migración Heterogénea de Bases de Datos

    Objetivo: Romper la dependencia con el proveedor tradicional migrando el núcleo de la compañía a un motor Open-Source (PostgreSQL) sin alterar su estructura relacional original.

    • Schema Mapping: Traducción de tipos de datos, restricciones e incompatibilidades entre SQL Server y PostgreSQL mediante el uso de Python.
    • Pipelines de Ingesta: Diseño de scripts de migración progresiva y por lotes para mover de manera controlada el modelo relacional puro, asegurando consistencia matemática absoluta entre ambos motores.
    • Stack Principal: SQL Server, PostgreSQL, SQLAlchemy, PyODBC, Psycopg2 y Pandas.

    Fase 5: Pipeline ETL y Automatización con Python

    Objetivo: Construir un pipeline reproducible con un solo comando que extraiga, limpie y guarde datos curados listos para consumo concurrente.

    • Pipeline Modular (Jupyter/Scripts): Desarrollo de notebooks de experimentación consolidados posteriormente en un script ejecutable (etl_pipeline.py) alimentado por un archivo central de configuración (config.py).
    • Feature Engineering & Limpieza: Tratamiento automatizado de valores nulos mediante reglas específicas según la columna, conversión estructurada de tipos/fechas y eliminación de duplicados mediante funciones reutilizables (CheckData).
    • Persistencia Eficiente (Formatos de Alto Rendimiento): Exportación de datasets limpios y validados a disco en formato Parquet (para optimizar espacio y velocidad de lectura).
    • Trazabilidad y Auditoría: Integración de logs detallados de seguimiento en consola para registrar el éxito/fallo del pipeline y mantenimiento de un registro maestro de control (datasets_control.xlsx) con métricas de filas y columnas procesadas.

    Fase 6: Optimización de la Capa Analítica en PostgreSQL

    Objetivo: Centralizar la lógica analítica pesada dentro de PostgreSQL para mitigar la carga computacional en el backend de la aplicación.

    • Modelado Lógico: Configuración de Vistas (Views) enriquecidas y almacenamiento indexado en PostgreSQL directamente sobre las tablas migradas.
    • Agregación Avanzada: Construcción de consultas complejas diseñadas para unificar la información dispersa de las áreas de Sales, HR, Product Performance, Supply Chain y Customer Analytics en estructuras consolidadas.

    Fase 7: Backend Analítico y Arquitectura de Software

    Objetivo: Construir la capa lógica del servidor encargada de procesar peticiones y unificar los datos analíticos de la empresa.

    • Backend Engineering: Desarrollo de una aplicación web robusta en Python utilizando Flask, estructurada de forma modular mediante Blueprints para segmentar los servicios de cada departamento de la compañía.
    • Persistencia Dinámica y APIs: Conexión interactiva a PostgreSQL para leer de forma nativa los archivos estructurados y las vistas optimizadas, garantizando tiempos mínimos de respuesta del servidor (latencia en milisegundos).

    Fase 8: Pipeline de Machine Learning y Analítica Predictiva

    Objetivo: Escalar la plataforma hacia la analítica predictiva, dotando al sistema de la capacidad de anticipar eventos críticos de negocio.

    • Modelado de Inteligencia Artificial: Diseño e implementación de modelos supervisados y de análisis temporal utilizando Scikit-Learn, XGBoost y Prophet.
    • Casos de Uso Operacionales:
      • Forecasting de Ventas: Modelos de series de tiempo para proyectar la demanda mensual e ingresos.
      • Clasificación de Churn: Modelos predictivos para calcular la probabilidad de abandono de clientes recurrentes.
      • Optimización de Cadena de Suministro: Predicción de quiebres de stock basándose en flujos históricos de inventario.
    • Inferencia en Producción: Creación de un script automatizado para la extracción de features, reentrenamiento de modelos y serialización de artefactos (.pkl / ONNX), consumidos directamente por la API de Flask para generar predicciones en tiempo real. [1]

    Fase 9: Visualización de Datos e Interfaces de Usuario (Frontend)

    Objetivo: Democratizar los insights corporativos permitiendo a la mesa directiva evaluar el rendimiento histórico y forecast predictivos en una sola pantalla unificada.

    • Frontend Interactivo: Creación de una interfaz web multipágina y responsive codificada con Plotly y Flask, reemplazando por completo los visualizadores de la suite comercial rígida.
    • Dashboards Ejecutivos Diseñados: Executive Overview, Sales Intelligence, HR Analytics, Product Performance y Geographic Insights.
    • Capa Semántica Unificada: Ocultamiento de columnas técnicas y exposición directa al usuario de medidas avanzadas de KPIs históricos calculados en base de datos junto a las proyecciones dinámicas estimadas por los modelos de Machine Learning.

    Fase 10: Despliegue en Producción y DevOps (MLOps)

    Objetivo: Publicar el sistema bajo una infraestructura escalable, segura y con costos de mantenimiento fijos, logrando la independencia total del negocio.

    • Infraestructura Cloud: Aprovisionamiento, hardening y configuración de políticas de seguridad en un Servidor Virtual Privado (VPS) con Ubuntu Server.
    • Contenerización Completa: Aislamiento de entornos y servicios (PostgreSQL, la aplicación Flask con sus pipelines de ML, las dependencias de Python y los servidores web) mediante Docker y Docker Compose.
    • Arquitectura de Producción: Implementación del servidor WSGI Gunicorn acoplado a Nginx como proxy inverso, garantizando la gestión eficiente de peticiones, compresión de assets, balanceo de carga básico y cifrado SSL.

    Ecosistema Tecnológico (Tech Stack)

    CategoríaTecnologías ClavePropósito en el Proyecto
    LenguajesPython (v3.11+), SQL (T-SQL / PL-pgSQL), HTML5/CSS3Procesamiento central, consultas optimizadas e interfaz nativa.
    Bases de DatosSQL Server , PostgreSQLMigración heterogénea desde el motor origen tradicional al destino Open-Source.
    Data Engineering & ETLPandas, SQLAlchemy, PyODBC, Psycopg2, ParquetScripting modular, mapeo de esquemas y persistencia de alto rendimiento.
    Data Science & MLXGBoost, Prophet (Meta), Scikit-Learn, ONNX / PickleModelos predictivos de series temporales, clasificación de churn e inferencia.
    Backend & APIsFlask, Flask Blueprints, GunicornArquitectura de software modular para servir datos y predicciones en tiempo real.
    Frontend & UXPlotly, Dash / Custom ComponentsVisualizaciones interactivas multipágina y dashboards corporativos 360°.
    DevOps & MLOpsDocker, Docker Compose, Nginx, Ubuntu Server (VPS), SSLContenerización multi-servicio, proxy inverso, seguridad y despliegue en la nube.

    Infografía de pipeline y arquitectura tecnológica

  • Dockerizar una app Flask para producción

    Construyendo aplicaciones analíticas portables con DashForge

    Las aplicaciones desarrolladas con Flask son extremadamente flexibles para crear dashboards, plataformas analíticas y sistemas de machine learning interactivos. Sin embargo, cuando el proyecto crece y empieza a desplegarse en servidores reales, surge un problema importante: el entorno.

    Distintas versiones de Python, conflictos entre librerías, dependencias del sistema operativo o diferencias entre equipos pueden hacer que una aplicación funcione correctamente en desarrollo pero falle en producción.

    Docker resuelve este problema encapsulando toda la aplicación dentro de un contenedor reproducible. El resultado es un entorno completamente aislado que puede ejecutarse exactamente igual en cualquier servidor compatible con Docker.

    En este tutorial vamos a dockerizar una aplicación Flask desarrollada con DashForge utilizando una arquitectura preparada para producción.

    Docker permite empaquetar una aplicación junto con todas sus dependencias. Todo queda integrado en una única imagen portable. Esto aporta ventajas muy importantes para aplicaciones analíticas:

    • despliegues reproducibles
    • aislamiento entre proyectos
    • facilidad para escalar aplicaciones
    • despliegues automáticos
    • ejecución bajo demanda
    • compatibilidad entre servidores
    • simplificación del entorno de producción

    En el caso de DashForge, Docker permite convertir cada dashboard o aplicación analítica en una unidad independiente que puede iniciarse o detenerse dinámicamente desde un portal central.

    Instalar docker

    Linux

    Para instalar en sistemas Debian sigue las siguientes instrucciones :

    # Descarga la lista actualizada de los paquetes disponibles en los servidores de Linux
    sudo apt update
    
    # Intalar las dependencias
    sudo apt install -y ca-certificates curl gnupg
    
    # Añadir clave oficial de Docker
    sudo install -m 0755 -d /etc/apt/keyrings
    
    # Descargar y registrar de forma segura la clave pública oficial de Docker en tu sistema
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
    sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    
    # Dar permisos de lectura a todos los usuarios sobre la clave de seguridad de Docker
    sudo chmod a+r /etc/apt/keyrings/docker.gpg

    Añadir el repositorio Docker

    echo \
      "deb [arch=$(dpkg --print-architecture) \
      signed-by=/etc/apt/keyrings/docker.gpg] \
      https://download.docker.com/linux/ubuntu \
      $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
      sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

    Actualizar los índices e instalar Docker

    sudo apt update
    
    sudo apt install -y \
        docker-ce \
        docker-ce-cli \
        containerd.io \
        docker-buildx-plugin \
        docker-compose-plugin

    Permitir utilizar Docker sin sudo

    sudo usermod -aG docker $USER
    
    # reinicia seccion ssh
    exit

    Verificar la instalación

    docker --version
    
    # Probar Docker
    docker ps
    
    # verificar servicio
    sudo systemctl status docker

    Windows

    Lo primero, instalar Docker en tu sistema: https://www.docker.com/products/docker-desktop/

    Una vez instalado, hay que reiniciar el sistema. Docker instala servicios, WSL, variables y el daemon. Sigue las instrucciones de la aplicación; quizás sea necesario actualizar WSL, Docker Desktop normalmente guía todo automáticamente.

    Verifica la instalación de Docker y su daemon, PowerShell ejecuta:

    # Verifica la instalacion de docker
    docker --version
    
    # Verifica que el daemon este funcionando
    docker ps
    

    Preparar el proyecto Flask para Docker

    Antes de dockerizar la aplicación, es recomendable mantener una estructura organizada. Una aplicación Flask típica preparada para Docker podría tener esta estructura:

    DashForge/
    
    ├── app/
    ├── static/
    ├── templates/
    ├── requirements.txt
    ├── run.py
    ├── Dockerfile
    ├── .dockerignore
    └── gunicorn.conf.py

    Preparando las dependencias del proyecto

    Docker necesita conocer todas las librerías utilizadas por la aplicación para poder instalarlas dentro del contenedor. La forma estándar de hacerlo es mediante un archivo requirements.txt. Este archivo incluirá todas las librerías necesarias para ejecutar la aplicación.

    Al usar este comando, ten en cuenta que exporta todo, incluyendo paquetes del sistema Windows que pueden impedir crear la imagen Docker al tratar de instalar paquetes de Windows en WLS (Subsistema de Linux para Windows). Una vez ejecutado el código revisa el fichero y elimina archivos como pywin32, pywinpty o
    winshell.

    pip freeze > requirements.txt

    Excluyendo archivos innecesarios con .dockerignore

    Cuando Docker construye una imagen, copia el contenido del proyecto al contenedor. Sin embargo, muchos archivos no deben incluirse:

    • entornos virtuales
    • cachés
    • configuraciones del editor
    • repositorios Git
    • logs

    Para evitarlo se utiliza un archivo /.dockerignore.

    __pycache__/
    *.pyc
    
    .venv/
    venv/
    
    .git/
    .vscode/
    .idea/
    
    .env
    *.log
    
    .ipynb_checkpoints/

    Instalar Gunicorn

    Instalamos Gunicorn dentro del entorno virtual y lo agregamos a las dependencias:

    pip install gunicorn
    pip show gunicorn

    Configurar Gunicorn creando el archivo: /gunicorn.conf.py. Esta configuración es suficiente para la mayoría de los dashboards analíticos pequeños y medianos.

    bind = "0.0.0.0:5000"
    
    workers = 2
    
    threads = 2
    
    timeout = 120
    
    worker_class = "gthread"

    Creando el Dockerfile

    El archivo más importante del proceso es el /Dockerfile. Aquí se define cómo construir el contenedor.

    FROM python:3.13-slim
    
    ENV PYTHONDONTWRITEBYTECODE=1
    ENV PYTHONUNBUFFERED=1
    
    WORKDIR /app
    
    COPY requirements.txt .
    
    RUN pip install --no-cache-dir -r requirements.txt
    
    RUN apt-get update && apt-get install -y curl
    
    COPY . .
    
    EXPOSE 5000
    
    HEALTHCHECK CMD curl --fail http://localhost:5000 || exit 1
    
    CMD ["gunicorn", "-c", "gunicorn.conf.py", "run:app"]
    

    Entendiendo cada sección del Dockerfile

    • FROM python:3.13-slim: La aplicación se construirá sobre una imagen ligera de Python. La versión slim reduce considerablemente el tamaño final del contenedor.
    • ENV PYTHONDONTWRITEBYTECODE=1: Evitan generar archivos .pyc, mejoran el comportamiento del contenedor en producción.
    • ENV PYTHONUNBUFFERED=1: fuerzan salida inmediata de logs.
    • WORKDIR /app: Define el directorio interno donde vivirá la aplicación.
    • RUN pip install : instala las librerías listadas en requirements.txt.
    • RUN apt-get ... : instalar curl para healthcheck.
    • COPY ... : Copia el resto del proyecto al contenedor.
    • EXPOSE 5000: Indica que Flask/Gunicorn utilizará el puerto 5000.
    • HEALTHCHECK CMD curl: añadir comprobaciones automáticas de salud de los contenedores.
    • CMD : inicia la aplicación utilizando Gunicorn.

    Construyendo la imagen Docker

    Una vez preparados todos los archivos, ya podemos construir la imagen. En Powershell y desde la raíz del proyecto ejecutamos:

    docker build -t dashforge-spacex .

    Docker empezará a:

    1. descargar la imagen base
    2. instalar dependencias
    3. copiar el proyecto
    4. construir la imagen final
    • docker build: Es el comando principal que le ordena a Docker empaquetar tu aplicación, sus dependencias (como Python, Pandas, Scikit-Learn) y el sistema operativo base en una sola imagen aislada.
    • -t dashforge-spacex (Tag): Asigna un nombre y una etiqueta personalizada a la imagen que estás creando. El nombre que elijas aquí (dashforge-spacex) es la referencia exacta que usarás después para arrancar el contenedor con el comando docker run.
    • . (Punto final): Indica el contexto de construcción, diciéndole a Docker que busque el archivo llamado Dockerfile en el directorio actual.

    Ejecutar el contenedor

    Cuando la imagen termina de construirse, podemos iniciar la aplicación:

    docker run -d -p 5010:5000 --name dashforge-spacex dashforge-spacex
    • docker run -d : Esto crea un contenedor en segundo plano -d (Detached).
    • -p 5000:5000 (Publish / Ports): Conecta un puerto de tu computadora real (Anfitrión) con un puerto dentro del contenedor (Contenedor) siguiendo la estructura -p puerto_externo:puerto_interno.
    • Primer 5010 (Externo): El puerto de tu máquina real. Podrás abrir tu navegador web e ingresar a http://localhost:5000 para ver tu app.
    • Segundo 5000 (Interno): El puerto donde tu servidor (como Flask, Dash o FastAPI) está escuchando dentro del entorno cerrado del contenedor.
    • --name dashforge-spacex: Asigna un nombre personalizado e identificable a este contenedor específico.
    • dashforge-spacex (Al final): Es el nombre de la imagen de Docker de origen que vas a utilizar como plantilla para construir este contenedor. Debe coincidir exactamente con el nombre de la imagen que creaste previamente con el comando docker build.

    Gestionando contenedores Docker

    Docker incluye comandos para administrar los contenedores.

    # Ver dontenedores activos
    docker ps
    
    # Detener un contendor
    docker stop dashforge-spacex
    
    # Eliminar un contenedor
    docker rm dashforge-spacex
    
    # Eliminar una imagen de docker
    docker rmi dashforge-spacex
    
    

    Preparando DashForge para despliegues dinámicos

    Una de las ventajas más potentes de Docker es que cada dashboard puede ejecutarse como un contenedor independiente. Esto permite construir una arquitectura bajo demanda:

    • El portal principal permanece activo
    • Las aplicaciones solo se inician cuando un usuario las solicita
    • Los contenedores pueden apagarse automáticamente tras un periodo de inactividad

    Cada proyecto puede ejecutarse aislado, con sus propias dependencias, sus modelos ML y su configuración independiente. Esta arquitectura escala muchísimo mejor que mantener decenas de aplicaciones Flask activas permanentemente.

  • Flask

    Iniciar la aplicación

    Crea una carpeta llamada app, que alojará la aplicación.

    (venv) $ mkdir app

    Dentro crea un fichero con nombre __init__.py con el siguiente código:

    from flask import Flask
    
    app = Flask(__name__)
    
    from app import routes
    • Se crea el objeto de la aplicación como una instancia de una clase Flask importada del paquete flask.
    • La variable __name__ que se pasa a la clase Flask es una variable predefinida de Python, cuyo nombre corresponde al del módulo en el que se utiliza.
    • En la práctica, pasar esta variable __name__ casi siempre configurará Flask correctamente. A continuación, la aplicación importa el módulo routes, que aún no existe.

    Un aspecto que puede resultar confuso al principio es que existen dos entidades con el mismo nombre app.

    • El paquete app que se define mediante el directorio de la aplicación y el script __init__.py , y se hace referencia a él en la declaración from app import routes.
    • La variable app que se define como una instancia de la clase Flask en el script __init__.py , lo que la convierte en miembro del paquete app.

    Otra particularidad es que el módulo routes se importa al final del script y no al principio, como se hace habitualmente. La importación al final es una solución conocida que evita las importaciones circulares , un problema común en las aplicaciones Flask. Verás que el módulo routes necesita importar la variable app definida en este script, por lo que colocar una de las importaciones recíprocas al final evita el error que resulta de las referencias mutuas entre estos dos archivos.

    Modulo routes

    Las rutas gestionan las diferentes URL que admite la aplicación. En Flask, los manejadores de las rutas de la aplicación se escriben como funciones de Python, llamadas view funtions. Estas funciones se asocian a una o más URL de ruta para que Flask sepa qué lógica ejecutar cuando un cliente solicita una URL determinada.

    Aquí está la primera función de vista para esta aplicación, que debes escribir en un nuevo módulo llamado routes.py

    from app import app
    
    @app.route('/')
    @app.route('index')
    
    def index():
      return "Hello, World"

    Las dos líneas @app.route son decoradores , una característica única del lenguaje Python. Un decorador modifica la función que le sigue. Un patrón común con los decoradores es usarlos para registrar funciones como devoluciones de llamada para ciertos eventos.

    En este caso, el decorador @app.route crea una asociación entre la URL proporcionada como argumento y la función. Esto significa que cuando un navegador web solicita cualquiera de estas dos URL, Flask invocará esta función y devolverá su valor de retorno al navegador como respuesta.

    Para completar la aplicación, necesitas tener un script de Python en el nivel superior que defina la instancia de la aplicación Flask. Llamemos a este script microblog.py y definámoslo como una sola línea que importe la instancia de la aplicación:

    from app import app

    ¿Recuerdas las dos entidades app? Aquí puedes verlas juntas en la misma oración.

    • La instancia de la aplicación Flask se llama app y pertenece al paquete app.
    • La instrucción from app import app importa la variable app que pertenece al paquete app. Si te resulta confuso, puedes cambiar el nombre del paquete o de la variable.

    Para asegurarnos de que todo se está haciendo correctamente, a continuación se muestra un diagrama de la estructura del proyecto hasta el momento:

    microblog/
      venv/
      app/
        __init__.py
        routes.py
      microblog.py

    La aplicación está lista, solo hace falta llamarla:

    flask --app microblog.py --debug run

    Templates

    Las plantillas ayudan a lograr la separación entre la presentación y la lógica de negocio. En Flask, las plantillas se escriben como archivos separados, almacenados en una carpeta llamada templates dentro del paquete de la aplicación.

    archivo index.html

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8"/>
            <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
            <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
            <title>{{title}}</title>
        </head>
        <body>
            <h1>Hello, {{user.username}}!</h1>
        </body>
    </html>

    Los marcadores de posición {{ … }} representan las partes de la página que son variables y se cargan en el momento de la ejecución. Ahora que usamos las plantillas, podemos eliminar el texto de la función index() y agregar la plantilla.

    from flask import render_template
    from app import app
    
    @app.route('/')
    @app.route('/index')
    def index():
        user = {'username': 'Miguel'}
        return render_template('index.html', title='Home', user=user)

    La operación que convierte una plantilla en una página HTML completa se llama renderizado . Para renderizar la plantilla, importa una función Flask llamada  render_template(). Esta función realiza esta acción:

    1. Busca el archivo templates/index.html
    2. Sustituye las variables Jinja2
    3. Devuelve el HTML generado al navegador

    La función render_template() invoca el motor de plantillas Jinja que viene incluido con el framework Flask. Jinja sustituye  los bloques {{ ... }} con los valores correspondientes, proporcionados por los argumentos en la llamada  render_template().

    Declaraciones condicionales

    Ya has visto cómo Jinja reemplaza los marcadores de posición con valores reales durante la renderización, pero esta es solo una de las muchas operaciones potentes que Jinja admite en los archivos de plantilla. Por ejemplo, las plantillas también admiten instrucciones de control, que se encuentran dentro {% ... %}de bloques. La siguiente versión de la plantilla index.html añade una instrucción condicional:

    <!doctype html>
    <html>
        <head>
            {% if title %}
            <title>{{ title }} - Microblog</title>
            {% else %}
            <title>Welcome to Microblog!</title>
            {% endif %}
        </head>
        <body>
            <h1>Hello, {{ user.username }}!</h1>
        </body>
    </html>

    Bucles

    Hasta este punto, la plantilla index.html mostraba únicamente información estática o variables individuales, como el nombre del usuario conectado. Sin embargo, en una aplicación real es habitual trabajar con colecciones de datos, por ejemplo:

    • Publicaciones de un blog.
    • Comentarios.
    • Productos.
    • Resultados de consultas.
    • Registros de una base de datos.

    Para representar este tipo de estructuras, Jinja2 incorpora instrucciones de control similares a las de Python, entre ellas el bucle for.

    En app/routes.py, debajo del usuario autenticado, crea una lista llamada posts. Cada elemento de la lista es un diccionario que representa una publicación con autor:body . A la función añade posts=posts

    posts = [
        {
            'author': {'username': 'John'},
            'body': 'Beautiful day in Portland!'
        },
        {
            'author': {'username': 'Susan'},
            'body': 'The Avengers movie was so cool!'
        }
    ]

    Uso del bucle for en Jinja2

    En templates/index.html, la colección se recorre mediante la instrucción:

    {% for post in posts %}
        ...
    {% endfor %}

    Durante cada iteración, post representa un elemento individual de la lista al que se puede acceder a sus atributos o claves.

    Acceso a estructuras anidadas

    Jinja2 permite navegar por diccionarios anidados usando la notación de punto, lo que hace el código más legible.

    {{ post.author.username }}
    {{ post.body }}

    Herencia de plantillas en Jinja2

    La idea consiste en crear una plantilla base que contenga toda la estructura común del sitio y dejar definidos ciertos espacios vacíos, llamados bloques, donde cada página insertará su contenido específico.

    La plantilla base

    La plantilla base.html define la estructura general de todas las páginas.

    El bloque content

    La instrucción define una zona reemplazable. La plantilla hija podrá proporcionar el contenido que se insertará exactamente en ese punto.

    {% block content %}{% endblock %}

    Plantilla hija: index.html

    {% extends "base.html" %}
    
    {% block content %}
        <h1>Hi, {{ user.username }}!</h1>
    
        {% for post in posts %}
            <div>
                <p>
                    {{ post.author.username }} says:
                    <b>{{ post.body }}</b>
                </p>
            </div>
        {% endfor %}
    {% endblock %}

    La instrucción extends Indica que index.html no es un documento HTML completo, sino una plantilla que hereda de base.html.

    {% extends "base.html" %}

    Cuando Flask ejecuta: render_template("index.html"), Jinja2 realiza internamente estos pasos:

    1. Carga index.html.
    2. Detecta {% extends "base.html" %}.
    3. Carga base.html.
    4. Sustituye el bloque content de base.html por el bloque content definido en index.html.
    5. Genera el HTML final.
  • Proyecto de aplicación web Flask: Projects Portal.

    En este proyecto desarrollaremos una aplicación web con Flask que funcionará como el portal principal de nuestro VPS. Su propósito es servir como punto central de acceso a todos los proyectos, aplicaciones y servicios que vayamos desplegando a lo largo del tiempo.

    La página estará disponible en el subdominio projects.fernandorioseco.es y actuará como un auténtico escaparate técnico y profesional. Desde ella será posible entrar a las aplicaciones de proyectos realizados. También cada proyecto contará con su propio subdominio dentro del VPS.

    En ese subdominio se desplegará la aplicación funcional del proyecto, es decir, la herramienta o servicio que el usuario podrá utilizar directamente, ya sea una aplicación desarrollada con Flask, Streamlit, FastAPI u otra tecnología.

    La explicación detallada del proyecto —objetivos, arquitectura, tecnologías empleadas, proceso de desarrollo y aprendizajes obtenidos— no se mostrará dentro de la aplicación, sino en el sitio principal del portfolio, donde cada proyecto tendrá su propia ficha o artículo descriptivo.

    De esta forma se separan claramente dos elementos:

    1. La documentación del proyecto, alojada en el sitio principal del portfolio.
    2. La aplicación desplegada, accesible mediante su subdominio correspondiente.

    Este enfoque permite mantener el portfolio organizado y profesional, ofreciendo por un lado la explicación técnica del trabajo realizado y, por otro, el acceso directo a la aplicación en funcionamiento.

    Estructura general del proyecto

    El propósito del proyecto es construir una aplicación web en Flask desplegada en projects.fernandorioseco.es que actuará como portal central del VPS.

    Objetivos

    • Centralizar el acceso a proyectos.
    • Mostrar información sobre el VPS.
    • Presentar el stack tecnológico.
    • Servir como portfolio profesional.

    Para desarrollar el Projects Portal seguiremos una metodología organizada que abarcará desde el diseño de la aplicación en local hasta su despliegue definitivo en el VPS. El proyecto no solo consistirá en construir una aplicación web con Flask, sino también en integrarla dentro de la infraestructura del servidor y conectarla con el resto de aplicaciones del portfolio.

    La estructura general del proyecto se dividirá en las siguientes fases:

    1 – Planificación y Diseño: En primer lugar definiremos el objetivo del portal, las secciones que incluirá y la experiencia de usuario que queremos ofrecer. En esta fase estableceremos la arquitectura de la aplicación, el diseño de la interfaz y la información que se mostrará en cada sección.

    2Setup de la Aplicación y Control de Versiones con Git y GitHub: Preparar el setup del proyecto con una app mínima, inicializar un repositorio Git y publicar el código en GitHub para mantener un historial de cambios y facilitar el despliegue en el servidor.

    3 – Desarrollo Local: Crearemos el proyecto Flask en nuestro entorno local, organizaremos la estructura de carpetas, diseñaremos las plantillas HTML y desarrollaremos los estilos CSS y la lógica necesaria para mostrar los proyectos de forma dinámica.

    4 – Configuración del Dominio: Crearemos el subdominio projects.fernandorioseco.es y configuraremos el registro DNS para que apunte a la dirección IP pública del VPS.

    5 – Preparación del VPS: Crearemos el directorio del proyecto en el servidor, clonaremos el repositorio desde GitHub, configuraremos el entorno virtual de Python e instalaremos todas las dependencias necesarias.

    6 – Configuración del Servicio con Gunicorn y systemd: Configuraremos Gunicorn como servidor WSGI y crearemos un servicio de systemd para que la aplicación se ejecute automáticamente y permanezca activa tras reinicios del sistema.

    7 – Configuración de Nginx y HTTPS: Configuraremos Nginx como proxy inverso para publicar la aplicación en Internet y habilitaremos HTTPS mediante certificados SSL de Let’s Encrypt.

    8 – Actualización y Mantenimiento: Definiremos el procedimiento para desplegar nuevas versiones del portal y añadir proyectos al catálogo mediante archivos JSON individuales.

    9 – Mejoras y Evolución: Una vez desplegado el portal, podremos incorporar nuevas funcionalidades, como filtros por tecnología, estadísticas del VPS en tiempo real o un panel de administración para gestionar los proyectos desde una interfaz web.

    1. Planificación y Diseño

    Arquitectura de la Aplicación

    La aplicación Projects Portal se desarrollará con Flask siguiendo una arquitectura sencilla, modular y escalable. El objetivo es separar claramente la lógica de negocio, la presentación y los datos para facilitar el mantenimiento y la incorporación de nuevos proyectos.

    La arquitectura se basará en los siguientes componentes:

    1. Cliente web (navegador).
    2. Servidor web Nginx.
    3. Servidor de aplicaciones Gunicorn.
    4. Aplicación Flask.
    5. Plantillas HTML con Jinja2.
    6. Archivos estáticos (CSS, JavaScript e imágenes).
    7. Archivo de configuración con los datos de los proyectos.

    Flujo de Funcionamiento

    Usuario
       
    Navegador Web
       
    Nginx (Reverse Proxy)
       
    Gunicorn (WSGI Server)
       
    Flask Application
       
    Routes (views.py)
       
    Data Source (projects.json o Python dictionary)
       
    Jinja2 Templates
       
    HTML Renderizado
       
    Respuesta al Navegador
    

    Componentes de la Arquitectura

    • Navegador del Usuario: Es el cliente que realiza la petición HTTP y renderiza el HTML, CSS y JavaScript enviados por la aplicación.
    • Nginx: Actúa como proxy inverso.
      • Recibe las peticiones HTTPS.
      • Gestiona los certificados SSL/TLS.
      • Redirige el tráfico hacia Gunicorn.
      • Sirve archivos estáticos si se desea.
    • Gunicorn: Servidor WSGI encargado de ejecutar la aplicación Flask.
      • Inicia la aplicación.
      • Gestiona procesos workers.
      • Atiende múltiples peticiones concurrentes.
    • Aplicación Flask: Contiene la lógica principal del portal.
      • Fuente de Datos: Los proyectos pueden almacenarse en:
        • Un archivo projects.json.
      • Plantillas Jinja2: Se encargan de generar el HTML dinámico.
        • base.html
        • index.html
        • Componentes parciales:
          • navbar.html
          • hero.html
          • projects.html
          • footer.html
      • Archivos Estáticos: Recursos utilizados por la interfaz:
        • CSS.
        • JavaScript.
        • Imágenes.
        • Iconos.

    Estructura de carpetas y ficheros

    projects-portal/
    ├── app/
       ├── __init__.py
       ├── routes.py
       ├── data/
          └── projects.json
       ├── templates/
          ├── base.html
          ├── index.html
          └── components/
              └── navbar.html
              └── hero.html
              └── vps_info.html
              └── tech_stack.html
              └── projects.html
              └── resources.html
              └── footer.html
       └── static/
           ├── css/
           ├── js/
           └── img/
    ├── config.py
    ├── requirements.txt
    ├── run.py
    └── wsgi.py
    

    Descripción de los ficheros

    • app/__init__.py: Crea y configura la aplicación Flask, registra las rutas y devuelve la instancia principal de la aplicación.
    • app/routes.py: Define las rutas de la aplicación y establece que la página principal renderice la plantilla index.html.
    • run.py: Ejecuta la aplicación en modo desarrollo para realizar pruebas en el entorno local.
    • wsgi.py: Expone la aplicación para que pueda ser ejecutada por un servidor WSGI como Gunicorn en producción.
    • app/templates/index.html: Crea la estructura HTML básica de la página principal con el nombre del portal y una breve descripción del laboratorio.
    • app/data/projects.json: Almacenará la información de los proyectos que se mostrarán dinámicamente en el portal.
    • requirements.txt: Guarda el listado de dependencias Python necesarias para ejecutar el proyecto.
    • config.py: Centraliza la configuración general de la aplicación.
    • app/static/css/: Contendrá las hojas de estilo CSS de la aplicación.
    • app/static/js/: Contendrá los archivos JavaScript utilizados en la interfaz.
    • app/static/img/: Almacenará imágenes, iconos y recursos gráficos.
    • app/templates/base.html: Servirá como plantilla base común para todas las páginas del sitio.
    • app/templates/components/: Almacenará componentes reutilizables de la interfaz, como la barra de navegación o el footer.

    Patrón Arquitectónico

    El portal Projects Portal seguirá una versión simplificada del patrón arquitectónico MVC (Model-View-Controller), uno de los modelos de diseño más utilizados en el desarrollo de aplicaciones web. El objetivo es separar claramente los datos, la lógica de la aplicación, y la presentación visual.

    Componente MVCImplementación
    Modelapp/data/projects.json
    Viewapp/templates/*.html
    Controllerapp/routes.py

    Escalabilidad

    La arquitectura permite evolucionar fácilmente hacia:

    • Base de datos relacional.
    • Panel de administración.
    • API REST.
    • Autenticación.
    • Caché con Redis.
    • Contenerización con Docker.

    Estructura de la Interfaz de Usuario (UI)

    La aplicación Projects Portal estará compuesta por una única página principal diseñada para presentar el laboratorio técnico y ofrecer acceso directo a todos los proyectos desplegados en el VPS. La interfaz se organizará en varias secciones claramente diferenciadas, cada una con una función específica dentro de la experiencia de usuario.

    Boceto de referencia aproximado generado por IA:

    • Barra de Navegación (Navbar): La página comenzará con una barra de navegación fija en la parte superior. Elementos de la navbar:
      • Logotipo o icono del portal.
      • Nombre del sitio: Projects Portal.
      • Enlaces externos:
      • Botón opcional para modo oscuro/claro.
    • Hero Section: Es la sección principal visible al cargar la página. Contenido:
      • Etiqueta superior: Laboratorio Personal en la Nube.
      • Título principal: Projects Portal.
      • Subtítulo con la propuesta de valor.
      • Descripción breve del portal.
      • Botones de acción:
        • Ver Proyectos.
        • GitHub.
      • Ilustración isométrica de un servidor VPS conectado a la nube.
    • Información del VPS: Sección formada por tarjetas con las características principales del servidor.
      • Sistema operativo.
      • CPU.
      • Memoria RAM.
      • Almacenamiento.
      • IP pública.
      • Dominio principal.
    • Stack Tecnológico: Grid con iconos y nombres de las tecnologías utilizadas. Tecnologías representadas:
      • Linux.
      • Docker.
      • Flask.
      • Nginx.
      • PostgreSQL.
      • Redis.
      • Python.
      • Power BI.
      • LangChain.
      • Machine Learning.
    • Proyectos Desplegados: Sección principal del portal, el contenido se distribuye en tarjetas
      • Icono o imagen representativa.
      • Nombre del proyecto.
      • Descripción breve.
      • Tecnologías utilizadas.
      • Botones de acción:
        • Open App.
        • Documentation.
        • Source Code.
        • API Docs (solo si existe API).
    • Recursos y Enlaces: Bloque con accesos a recursos externos.
    • Footer: Pie de página visible al final del sitio.
      • Nombre del autor.
      • Cargo o marca personal.
      • Correo electrónico.
      • Ubicación.
      • Iconos a redes profesionales.
      • Copyright.

    Flujo de interacción del usuario

    1. El visitante accede a projects.fernandorioseco.es.
    2. Visualiza la presentación del laboratorio.
    3. Consulta la infraestructura y tecnologías utilizadas.
    4. Explora las tarjetas de proyectos.
    5. Accede a:
      • La aplicación en producción.
      • La documentación del proyecto.
      • El código fuente.
      • La documentación de la API, si existe.
    6. Navega a GitHub, blog o LinkedIn.

    Objetivo de la interfaz

    La UI debe transmitir una imagen:

    • Profesional.
    • Moderna.
    • Tecnológica.
    • Clara y fácil de navegar.

    Al mismo tiempo, debe funcionar como un punto central desde el que cualquier visitante pueda comprender tu infraestructura y acceder rápidamente a todos los proyectos de tu portfolio.

    2. Setup de la Aplicación y Control de Versiones

    Setup del Proyecto

    En esta fase realizaremos la configuración inicial del proyecto en nuestro entorno local. El objetivo es preparar la estructura base de la aplicación, crear un entorno virtual, instalar las dependencias necesarias y verificar que Flask funciona correctamente antes de comenzar con el desarrollo de la interfaz. En esta fase desarrollamos:

    • El directorio raíz del proyecto.
    • Un entorno virtual de Python.
    • Librerías necesarias
    • El archivo requirements.txt.
    • La estructura de carpetas del proyecto.
    • Los archivos base de la aplicación.
    • Implementar una aplicación Flask mínima.
    • Crear el repositorio en GitHub.

    Implementar una aplicación Flask mínima

    #app/__init__.py
    from flask import Flask
    
    def create_app():
        app = Flask(__name__)
    
        from app.routes import main
        app.register_blueprint(main)
    
        return app
        
        
    # app/routes.py
    from flask import Blueprint, render_template
    
    main = Blueprint("main", __name__)
    
    @main.route("/")
    def home():
        return render_template("index.html")
    
    
    
    # run.py
    from app import create_app
    
    app = create_app()
    
    if __name__ == "__main__":
        app.run(debug=True)
    
    
    #wsgi.py
    from app import create_app
    
    app = create_app()
    
    
    #app/templates/index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Projects Portal</title>
    </head>
    <body>
        <h1>Projects Portal</h1>
        <p>Personal VPS Lab for DevOps, Data Science, MLOps and AI Engineering.</p>
    </body>
    </html>

    Ejecutar la aplicación en local

    python run.py

    Abrimos el navegador en:

    http://127.0.0.1:5000

    Si todo ha ido correctamente, veremos la primera versión funcional del portal.

    Crear un archivo projects.json de ejemplo

    Aunque todavía no lo utilizaremos, podemos dejar preparado el archivo que almacenará la información de los proyectos. en app/data/projects.json

    [
      {
        "name": "CO2 Emissions Dashboard",
        "description": "Análisis interactivo de emisiones de vehículos.",
        "technologies": ["Python", "Pandas", "Power BI", "Streamlit"],
        "app_url": "https://co2.projects.fernandorioseco.es",
        "article_url": "https://fernandorioseco.es/co2-emissions-dashboard",
        "github_url": "https://github.com/fernandorioseco/co2-emissions-dashboard",
        "api_docs_url": null
      }
    ]
    

    Control de versiones con Git y Github

    1. Crear el archivo .gitignore.
    2. Inicializar el repositorio Git.
    3. Realizar el primer commit.
    4. Crear el repositorio remoto en GitHub.
    5. Subir el código al repositorio.
    6. Establecer la rama principal main.

    3. Desarrollo local

    Arquitectura de Plantillas

    La interfaz del Projects Portal se construye siguiendo una arquitectura de plantillas modular basada en el motor de plantillas Jinja, integrado de forma nativa en Flask. Este enfoque permite dividir la interfaz en componentes independientes y reutilizables, facilitando el mantenimiento y la escalabilidad de la aplicación.

    La finalidad de esta arquitectura es separar la estructura visual del sitio en piezas con responsabilidades bien definidas:

    • Una plantilla base común.
    • Una página principal.
    • Componentes reutilizables.

    Gracias a esta organización, cada sección de la interfaz puede desarrollarse y modificarse de forma aislada. Las plantillas HTML se almacenan en el directorio: app/templates/. La estructura adoptada es la siguiente:

    app/templates/
    ├── base.html
    ├── index.html
    └── components/
        ├── navbar.html
        ├── hero.html
        ├── vps_info.html
        ├── tech_stack.html
        ├── projects.html
        ├── resources.html
        └── footer.html
    ElementoDescripción
    base.htmlPlantilla base de la aplicación. Define la estructura HTML común del sitio, incluyendo la cabecera del documento, la carga de estilos, la barra de navegación, el bloque de contenido principal y el footer.
    index.htmlPágina principal del portal. Ensambla secuencialmente los distintos componentes visuales mediante instrucciones {% include %}. Contiene muy poca lógica y actúa como orquestador de la interfaz.
    components/Directorio que contiene las secciones independientes que conforman la página principal. Cada archivo representa un bloque funcional concreto.
    navbar.htmlBarra de navegación superior.
    hero.htmlSección principal de presentación del portal.
    vps_info.htmlInformación técnica del servidor VPS.
    tech_stack.htmlTecnologías y herramientas utilizadas en el laboratorio.
    projects.htmlListado dinámico de proyectos desplegados.
    resources.htmlEnlaces externos a documentación, GitHub, blog y LinkedIn.
    footer.htmlPie de página del sitio.

    Integración con Flask

    La ruta principal devuelve la plantilla:

    return render_template("index.html", projects=projects)

    Jinja2 se encarga automáticamente de resolver la herencia e inclusiones.

    base.html – plantilla base de la aplicación

    El archivo base.html constituye la plantilla base del Projects Portal. Su función es definir la estructura HTML común que compartirán todas las páginas del sitio. Este enfoque permite centralizar los elementos globales de la interfaz y evitar la duplicación de código. Ubicación del archivo: app/templates/base.html

    Propósito

    La plantilla base se encarga de definir la estructura general del documento HTML e incluir aquellos elementos que deben estar presentes en todas las páginas de la aplicación. En concreto, base.html incorpora:

    • Definir la estructura HTML general del documento.
    • Configurar las etiquetas meta necesarias.
    • Establecer un título por defecto.
    • Cargar la hoja de estilos.
    • Incluir la barra de navegación.
    • Definir un bloque de contenido que heredarán las páginas hijas.
    • Incluir el footer.

    Estructura conceptual

    [ DOCUMENTO HTML ] (Template Maestro)
    
     ├── [ HEAD ] (Configuración Invisible)
     │    ├── Metadatos (Viewport, Charset)
     │    ├── Título Dinámico {% block title %}
     │    └── Estilos Propios (styles.css)
    
     └── [ BODY ] (Estructura Visible)
    
          ├── [ HEADER ] ─── {% include "navbar.html" %}  ── (Componente Reutilizable)
    
          ├── [ MAIN ] ───── {% block content %} ──────── (Espacio Inyectable/Variable)
          │                                                *Aquí se carga el contenido*
          │                                                *específico de cada ruta*
    
          └── [ FOOTER ] ─── {% include "footer.html" %}  ── (Componente Reutilizable)

    Elementos de Jinja2 utilizados

    • title: este permite definir un título por defecto y sobrescribirlo desde plantillas hijas.
    • content: reserva el espacio donde se insertará el contenido específico de cada página.
    • include: inserta componentes reutilizables como la barra de navegación y el footer.
    • url_for(): genera automáticamente la URL correcta para los archivos estáticos.

    Relación con el resto de plantillas

    base.html es utilizada por index.html mediante la instrucción: {% extends "base.html" %}. A partir de ese momento, index.html solo necesita definir el contenido correspondiente al bloque content.

    Flujo de renderizado

    index.html  
    └── extends base.html           
            ├── navbar.html           
            ├── content           
            └── footer.html

    navbar.html – barra de navegación

    La barra de navegación es el componente situado en la parte superior de la página y proporciona acceso a recursos externos relevantes. Al tratarse de un elemento común a todas las páginas del sitio, se implementa como un componente independiente que es incluido desde base.html. Ubicación del archivo: app/templates/components/navbar.html

    Propósito del componente

    • Identificar visualmente el sitio mediante el nombre del portal.
    • Proporcionar acceso a recursos externos como GitHub, el blog y LinkedIn.

    Elementos:

    • Identidad del sitio: Projects Portal
    • Enlaces externos: GitHub, Blog, LinkedIn

    Estructura conceptual

    [ NAV: .navbar ] (Marco Principal: Fondo oscuro + Sombra)
     └── [ .container ] (Centrado del contenido)
          ├── [ .navbar-brand ] ─ "Projects Portal" (Logo/Título)
    
          ├── [ .navbar-toggler ] ─ [ Icono Hamburguesa ] (Solo visible en móviles)
    
          └── [ .collapse .navbar-collapse ] (Contenedor colapsable)
               └── [ .navbar-nav ] (Lista de enlaces: Alineada a la derecha)
                    ├── [ .nav-item ] ── [ .nav-link ] ─ "GitHub" (Externo ↗)
                    ├── [ .nav-item ] ── [ .nav-link ] ─ "Blog"   (Externo ↗)
                    └── [ .nav-item ] ── [ .nav-link ] ─ "LinkedIn" (Externo ↗)

    Enlaces externos

    Los enlaces externos se abren en una nueva pestaña mediante target="_blank".

    Relación con base.html

    La plantilla base incorpora este componente mediante: {% include "components/navbar.html" %}. De esta forma, cualquier modificación realizada en navbar.html se reflejará automáticamente en todas las páginas del sitio.

    Diseño visual

    La navbar se diseñará con las siguientes características:

    • Posición fija o sticky en la parte superior.
    • Fondo blanco o semitransparente.
    • Sombra ligera.
    • Distribución horizontal.
    • Adaptación responsive para dispositivos móviles.

    hero.html – sección principal de presentación

    Define la sección superior de la página principal y constituye el primer elemento visual que verá el visitante al acceder al portal. Su objetivo es comunicar de forma inmediata qué es Projects Portal, cuál es su propósito y qué tipo de proyectos alberga. Ubicación del archivo: app/templates/components/hero.html

    Elementos: La sección estará compuesta por dos columnas.

    • Columna izquierda
      • Badge superior: Personal VPS Lab.
      • Título principal: Projects Portal.
      • Subtítulo descriptivo.
      • Breve descripción del portal.
      • Botones de acción:
        • View Projects.
        • GitHub.
    • Columna derecha
      • Ilustración isométrica del VPS.

    Estructura conceptual

    [ SECTION: .hero ] (Contenedor de alto impacto / Fondo)
     └── [ .container .hero-content ] (Alineación Flex/Grid)
    
          ├── [ .hero-text ] (Bloque de Información - Izquierda/Centro)
          │    ├── [ .hero-badge ] ─ "Personal VPS Lab" (Categorización)
          │    ├── [ .hero-title ] ─ "Projects Portal" (H1 - Mensaje Principal)
          │    ├── [ .hero-subtitle ] ─ (Propuesta técnica: DevOps, IA, etc.)
          │    ├── [ .hero-description ] ─ (Detalle del ecosistema: APIs, Dashboards)
          │    └── [ .hero-actions ] (Llamada a la acción / CTA)
          │         ├── [ .btn-primary ] ─ "View Projects"
          │         └── [ .btn-secondary ] ─ "GitHub"
    
          └── [ .hero-image ] (Bloque Visual - Derecha)
               └── [ img ] ─ "vps-illustration.png" (Ilustración técnica)

    Mensaje principal

    La sección presentará el portal como: Un laboratorio personal desplegado en un VPS, orientado a DevOps, Ciencia de Datos, MLOps e Ingeniería de Inteligencia Artificial.

    Diseño visual

    El Hero Section tendrá las siguientes características:

    • Fondo azul oscuro.
    • Texto en color blanco.
    • Diseño en dos columnas.
    • Botones de llamada a la acción.
    • Imagen ilustrativa a la derecha.
    • Amplio espaciado vertical.

    Relación con index.html

    La página principal incluirá este componente mediante: {% include "components/hero.html" %}

    vps_info.html – infraestructura del VPS

    El componente vps_info.html muestra un resumen de las principales características técnicas del servidor donde se alojan el Projects Portal y el resto de aplicaciones del portfolio. Esta sección aporta contexto técnico y refuerza la credibilidad del portal al evidenciar que los proyectos se ejecutan sobre una infraestructura real administrada por el propio autor. Ubicación del archivo: app/templates/components/vps_info.html

    Propósito

    La sección tiene como objetivo presentar, de forma visual y estructurada, la configuración básica del VPS utilizado como entorno de despliegue. Permite al visitante conocer:

    • El sistema operativo del servidor.
    • Los recursos de CPU y memoria.
    • La capacidad de almacenamiento.
    • La IP pública.
    • El dominio asociado al portal.

    Información mostrada

    • Sistema Operativo.
    • CPU.
    • RAM.
    • Disco.
    • IP pública.
    • Dominio.

    Diseño visual

    • Un título
    • Una cuadrícula de tarjetas.
    • Un icono SVG por cada métrica.
    • El nombre del atributo.
    • Su valor correspondiente.

    Estructura conceptual

    [ SECTION: #vps-info ] (Ancla de navegación + Fondo de sección)
     └── [ .container ] (Alineación central)
          ├── [ .section-header ]
          │    └── [ h2 ] ── "Infraestructura del VPS"
    
          └── [ .vps-grid ] (Contenedor Layout: Probablemente Grid o Flexbox)
               ├── [ .vps-card ] ─ (OS: Ubuntu 24.04)
               ├── [ .vps-card ] ─ (CPU: 4 vCPU)
               ├── [ .vps-card ] ─ (RAM: 8 GB)
               ├── [ .vps-card ] ─ (Disco: 160 GB SSD)
               ├── [ .vps-card ] ─ (IP: 186.xxx...)
               └── [ .vps-card ] ─ (Dominio: projects.fernandorioseco.es)

    Recursos gráficos

    Los iconos utilizados en esta sección se almacenarán en: app/static/img/icons/

    Relación con index.html

    La página principal incluirá esta sección mediante: {% include "components/vps_info.html" %}

    projects.html – catálogo dinámico de proyectos

    El componente projects.html es la sección más importante del portal, ya que muestra las aplicaciones desplegadas en el VPS y proporciona acceso directo a cada una de ellas.

    A diferencia del resto de secciones, su contenido se genera dinámicamente a partir de los datos almacenados en projects.json. El archivo se ubica en: app/templates/components/projects.html.

    La sección incorpora un buscador dinámico que permite filtrar proyectos por cualquier término relevante, como tecnologías, categorías, nombre o palabras incluidas en la descripción. El buscador permite al usuario encontrar proyectos escribiendo uno o varios términos de búsqueda.

    Cada tarjeta de proyecto almacena en el atributo data-search el contenido que será evaluado por JavaScript. Este atributo incluye: nombre del proyecto, descripción y tecnologías utilizadas.

    Lógica de filtrado

    El archivo main.js:

    1. Captura el contenido del input.
    2. Divide el texto por comas.
    3. Normaliza los términos.
    4. Recorre todas las tarjetas.
    5. Muestra solo las que contienen todos los términos.

    Propósito

    Esta sección tiene como objetivo presentar cada proyecto del portfolio mediante una tarjeta con información resumida y enlaces relevantes. Cada tarjeta permitirá al visitante acceder a:

    • La aplicación desplegada.
    • El artículo de documentación del proyecto.
    • El repositorio de código fuente en GitHub.
    • La documentación de la API, cuando exista.

    Fuente de datos

    Los datos de los proyectos se almacenarán en el archivo: app/data/projects.json. Cada registro del archivo contendrá toda la información necesaria para construir una tarjeta.

    Información mostrada en cada tarjeta

    • Nombre del proyecto.
    • Descripción breve.
    • Tecnologías utilizadas.
    • Enlaces de acción.

    Estructura conceptual de una tarjeta

    [ DATOS: Lista de Proyectos ] 
    
          ▼ (Ciclo for project in projects)
    [ ARTICLE: .project-card ]  <──────────────────┐
     │                                             │
     ├── [ .project-title ] ─── {{ project.name }} │
     │                                             │
     ├── [ .project-description ] ─ {{ desc }}     │ (Repite por cada
     │                                             │  proyecto en la lista)
     ├── [ .project-tech ] ────────────────────────┤
     │    └── [ .tech-badge ] ─ {{ tech }} (Bucle) │
     │                                             │
     └── [ .project-links ] ───────────────────────┘
          ├── App URL
          ├── Documentation
          ├── GitHub
          └── [ IF: API URL ] ── (Carga condicional)

    Generación dinámica con Jinja2

    La plantilla recorrerá la lista projects enviada desde Flask mediante un bucle for. Por cada elemento del JSON se generará automáticamente una nueva tarjeta.

    Relación con Flask

    La ruta principal cargará los datos y los enviará a la plantilla: return render_template("index.html", projects=projects)

    Archivos involucrados

    La página principal incluirá esta sección mediante: {% include "components/projects.html" %}, en la acción de búsqueda se involucran :

    ArchivoFunción
    projects.htmlCampo de búsqueda y atributo data-search.
    main.jsLógica de filtrado.
    styles.cssEstilos del buscador.
    base.htmlCarga del archivo JavaScript.

    resources.html – recursos y enlaces externos

    El componente resources.html agrupa los enlaces externos más relevantes relacionados con el Projects Portal y con el perfil profesional del autor. Su objetivo es ofrecer al visitante un acceso rápido a la documentación técnica, al código fuente y a los perfiles profesionales asociados al proyecto. Ubicación del archivo: app/templates/components/resources.html

    Propósito

    Esta sección actúa como un bloque complementario al catálogo de proyectos. Mientras que cada tarjeta de proyecto contiene enlaces específicos, resources.html reúne los recursos generales del portal y del autor.

    Recursos que se mostrarán

    Información mostrada en cada tarjeta

    • Nombre del recurso.
    • Descripción breve.
    • Botón o enlace de acceso.

    Diseño visual

    La sección se mostrará como una cuadrícula de tarjetas o como una fila de enlaces destacados.

    Elementos visuales

    • Título de la sección.
    • Breve texto descriptivo.
    • Tarjetas con icono SVG.
    • Enlaces externos.

    Estructura conceptual

    [ SECTION: #resources ] (Contenedor de cierre / Footer-Prep)
     └── [ .container ] (Alineación y consistencia visual)
          ├── [ .section-header ]
          │    ├── [ h2 ] ── "Resources"
          │    └── [ p ] ─── (Resumen de contenido externo)
    
          └── [ .resources-grid ] (Layout de rejilla para acceso rápido)
               ├── [ .resource-card ] ─ (Documentation ➜ Blog)
               ├── [ .resource-card ] ─ (GitHub Repository ➜ Source Code)
               ├── [ .resource-card ] ─ (LinkedIn ➜ Professional Profile)
               └── [ .resource-card ] ─ (Website ➜ Portfolio)

    Relación con index.html

    La página principal incluirá esta sección mediante: {% include "components/resources.html" %}

    footer.html – pie de página

    El componente footer.html define la sección final del sitio y contiene información general sobre el portal y enlaces complementarios. Aunque es un elemento sencillo, cumple una función importante al cerrar visualmente la página y ofrecer referencias adicionales al visitante. Ubicación del archivo: app/templates/components/footer.html

    Propósito

    • Mostrar información de copyright.
    • Identificar al autor del portal.
    • Incluir enlaces a recursos relevantes.
    • Cerrar visualmente la página de forma consistente.

    Información que incluirá

    • Nombre del portal.
    • Año actual.
    • Nombre del autor.
    • Enlaces a GitHub, Blog y LinkedIn.

    Estructura conceptual

    [ FOOTER: .footer ] (Bloque de cierre / Estilo de fondo)
     └── [ .container .footer-content ] (Alineación y distribución espacial)
    
          ├── [ .footer-text ] ─── "© 2026 Projects Portal · Fernando Rioseco"
          │                        (Propiedad Intelectual / Marca Personal)
    
          └── [ .footer-links ] ── (Navegación Secundaria / Social)
               ├── [ Enlace ] ─ GitHub
               ├── [ Enlace ] ─ Blog
               └── [ Enlace ] ─ LinkedIn

    Diseño visual

    • Fondo oscuro o gris claro.
    • Texto de tamaño reducido.
    • Enlaces discretos.
    • Distribución horizontal en escritorio.
    • Apilado vertical en dispositivos móviles.

    Relación con base.html

    La plantilla base lo incluye mediante: {% include "components/footer.html" %}

    index.html – ensamblado de la página principal

    El archivo index.html es la plantilla principal del Projects Portal. Su función no es definir el contenido detallado de cada sección, sino ensamblar todos los componentes que conforman la página de inicio. Actúa como un punto de integración donde se combinan la plantilla base y los distintos componentes HTML desarrollados previamente. Ubicación del archivo: app/templates/index.html

    Propósito

    index.html tiene tres responsabilidades principales:

    1. Heredar la estructura general definida en base.html.
    2. Definir el contenido del bloque principal.
    3. Incluir, en el orden correcto, todos los componentes de la interfaz.

    Componentes incluidos

    La página principal incorpora las siguientes secciones:

    1. hero.html
    2. vps_info.html
    3. tech_stack.html
    4. projects.html
    5. resources.html

    La barra de navegación y el footer no se incluyen directamente aquí, ya que son incorporados automáticamente por base.html.

    Relación con base.html

    La plantilla comienza con: {% extends "base.html" %}. Esto indica que utilizará la estructura general definida en la plantilla base.

    Definición del contenido principal

    Todo el contenido de la página se inserta dentro del bloque:

    {% block content %}
    ...
    {% endblock %}

    Estructura conceptual

    [ ARCHIVO: base.html ] (El Cascarón)
    
     ├── [ BLOQUE: content ] <─── (Inyección desde este archivo)
     │    │
     │    ├── [ Hero.html ] ───────── (Impacto Visual / Intro)
     │    ├── [ vps_info.html ] ───── (Hardware / Especificaciones)
     │    ├── [ tech_stack.html ] ─── (Herramientas / Lenguajes)
     │    ├── [ projects.html ] ───── (Portafolio / Apps)
     │    └── [ resources.html ] ──── (Enlaces / Documentación)
    
     └── [ Footer ] (Desde base.html)

    Flujo de renderizado

    Cuando Flask renderiza index.html:

    1. Carga base.html.
    2. Sustituye el bloque content.
    3. Inserta los componentes incluidos.
    4. Genera el HTML final enviado al navegador.

    Arquitectura CSS

    Una vez definida la estructura HTML de la aplicación, el siguiente paso consiste en desarrollar la capa de presentación mediante hojas de estilo CSS. En el Projects Portal se ha optado por utilizar CSS puro, sin frameworks externos como Bootstrap, con el objetivo de mantener un control total sobre el diseño y comprender en detalle el comportamiento visual de cada componente. Todos los estilos del proyecto se almacenan inicialmente en: app/static/css/styles.css

    La arquitectura CSS tiene como finalidad:

    • Centralizar todos los estilos de la aplicación.
    • Mantener una organización clara y escalable.
    • Reutilizar reglas comunes.
    • Facilitar el mantenimiento.
    • Garantizar consistencia visual.

    El archivo styles.css se estructurará en bloques claramente diferenciados.

    styles.css
    ├── Variables CSS
    ├── Reset básico
    ├── Estilos globales
    ├── Componentes reutilizables
    ├── Layout
    ├── Estilos por sección
    └── Media queries

    La arquitectura CSS del Projects Portal se basa en un único archivo styles.css, organizado en bloques lógicos que incluyen variables, estilos globales, componentes reutilizables y reglas específicas para cada sección de la interfaz.

    El Projects Portal ha sido diseñado para adaptarse correctamente a distintos tamaños de pantalla, garantizando una experiencia de usuario adecuada tanto en ordenadores de escritorio como en tablets y teléfonos móviles.

    El objetivo del diseño responsive es que todos los elementos de la interfaz se ajusten automáticamente al espacio disponible sin necesidad de crear versiones separadas de la página.

    Breakpoints

    La adaptación a diferentes resoluciones se realiza mediante media queries. Los puntos de ruptura utilizados se corresponden con los tamaños habituales de dispositivos:

    • Móviles pequeños.
    • Tablets.
    • Portátiles.
    • Monitores de escritorio.

    Técnicas utilizadas

    Para lograr el comportamiento responsive se emplean:

    • CSS Grid.
    • Flexbox.
    • Media queries.
    • Unidades relativas (rem, %, fr).
    • Imágenes fluidas (max-width: 100%).

    Resultado preliminar de la estructura + css

    Haz clic en la imagen para ver.

    Incorporación de Recursos Visuales y Ajustes Finales de UI

    El propósito es transformar una interfaz funcional en una interfaz visualmente pulida.

    • Descarga de iconos
    • Optimización de imágenes
    • Integración de logos tecnológicos
    • Ilustración del Hero Section
    • Ajustes de CSS
    • Refinamiento de tipografía y colores
    • Corrección de detalles responsive
    • Revisión visual final

    Resultado final de la aplicación web

    4. Configuración del dominio

    Una vez completado el desarrollo local de la aplicación, el siguiente paso consiste en asociarla a un dominio público para que pueda ser accesible desde Internet. En este proyecto, la aplicación Flask se publicará en el subdominio: projects.fernandorioseco.es

    La configuración del dominio permite:

    • Asociar la aplicación a una URL pública.
    • Dirigir el tráfico al VPS.
    • Servir la aplicación mediante Nginx.
    • Habilitar HTTPS con certificados SSL.

    Arquitectura general

    Usuario
    
    projects.fernandorioseco.es
    
    DNS
    
    IP pública del VPS
    
    Nginx
    
    Gunicorn
    
    Flask Application
    

    Crear el registro DNS

    En el proveedor donde gestionas tu dominio, debes crear un registro de tipo A.

    CampoValor
    TipoA
    Nombreprojects
    ValorIP pública del VPS
    TTLAuto

    Verificar la propagación DNS

    Una vez creado el registro, el subdominio debe resolver hacia la IP del servidor.

    nslookup projects.fernandorioseco.es

    5. Preparación del VPS

    Una vez que la aplicación ha sido desarrollada localmente y el subdominio ya apunta al servidor, el siguiente paso consiste en preparar el VPS para alojar el proyecto.

    En esta fase se crea la estructura de directorios en el servidor, se descarga el código fuente desde GitHub, se configura un entorno virtual de Python y se instalan las dependencias necesarias para ejecutar la aplicación.

    La preparación del VPS tiene como finalidad:

    • Crear el directorio donde residirá el proyecto: /var/www.
    • Descargar el código fuente desde el repositorio remoto.
    • Configurar un entorno virtual aislado.
    • Instalar las librerías definidas en requirements.txt.
    • Verificar que la aplicación puede ejecutarse en el servidor.
    # Crear la carpeta del proyecto
    sudo mkdir -p /var/www/projects-portal
    
    # Cambiar propietario y grupo de los archivos del proyecto
    sudo chown -R $USER:$USER /var/www/projects-portal
    
    # clonar repositorio desde github
    git clone https://github.com/fer78/projects-portal.git /var/www/projects-portal

    Crear entorno virtual

    # en sistemas Debian/Ubuntu no viene instalado por defecto
    sudo apt install python3.12-venv
    
    # Accede al directorio
    cd /var/www/projects-portal
    
    # Crea el entorno virtual y activalo
    python3 -m venv venv
    source venv/bin/activate
    
    # Instalar dependencias
    pip install --upgrade pip
    pip install -r requirements.txt
    
    # comprobar que las dependencias se instalaron correctamente
    pip list

    Probar la aplicación Flask.

    python run.py

    Si todo es correcto, Flask iniciará el servidor de desarrollo.

    Probar Gunicorn

    gunicorn --bind 127.0.0.1:8000 wsgi:app

    Este comando confirma que la aplicación está lista para ejecutarse en producción.

    Archivos implicados

    ArchivoFunción
    requirements.txtLista de dependencias Python.
    wsgi.pyPunto de entrada para Gunicorn.
    venv/Entorno virtual del proyecto.
    run.pyScript de desarrollo local.

    6. Configuración del servicio con Gunicorn y systemd

    Una vez que la aplicación Flask funciona correctamente en el VPS, el siguiente paso consiste en ejecutarla como un servicio del sistema. Para ello utilizaremos:

    • Gunicorn como servidor WSGI.
    • systemd como gestor de servicios de Linux.

    Esta configuración permite que la aplicación se inicie automáticamente al arrancar el servidor y se reinicie en caso de fallo.

    La configuración del servicio persigue los siguientes objetivos:

    • Ejecutar la aplicación Flask en segundo plano.
    • Iniciar el servicio automáticamente en cada reinicio.
    • Reiniciar el proceso si se detiene inesperadamente.
    • Centralizar los logs en journalctl.

    Arquitectura del servicio

    systemd
    
    Gunicorn
    
    wsgi.py
    
    Flask Application

    Crear el archivo de servicio

    sudo nano /etc/systemd/system/projects-portal.service
    

    Definir la unidad de servicio

    [Unit]
    Description=Gunicorn service for Projects Portal
    After=network.target
    
    [Service]
    User=tu usuario
    Group=www-data
    WorkingDirectory=/var/www/projects-portal
    Environment="PATH=/var/www/projects-portal/venv/bin"
    
    ExecStart=/var/www/projects-portal/venv/bin/gunicorn \
        --workers 3 \
        --bind 127.0.0.1:8000 \
        wsgi:app
    
    Restart=always
    
    [Install]
    WantedBy=multi-user.target
    

    Poner a punto el servicio

    # reiniciar systemd
    sudo systemctl daemon-reload
    
    # habilitar el servicio
    sudo systemctl start projects-portal
    
    # verificar el estado
    sudo systemctl status projects-portal

    Comandos de administración

    # Consultar logs
    sudo journalctl -u projects-portal -f
    
    # Reiniciar
    sudo systemctl restart projects-portal
    
    # Detener
    sudo systemctl stop projects-portal
    
    # Deshabilitar
    sudo systemctl disable projects-portal

    Resultado esperado

    Al finalizar esta fase:

    • Gunicorn se ejecutará como servicio del sistema.
    • La aplicación arrancará automáticamente tras cada reinicio.
    • Los logs estarán disponibles mediante journalctl.
    • El proceso se reiniciará automáticamente ante fallos.

    7. Configuración de Nginx y HTTPS

    Una vez que la aplicación Flask se ejecuta correctamente mediante Gunicorn y systemd, el siguiente paso consiste en publicarla en Internet a través de un servidor web. Para ello utilizaremos:

    • Nginx como proxy inverso.
    • Certbot para obtener certificados SSL.
    • Let’s Encrypt como autoridad certificadora.

    Esta etapa permite:

    • Asociar el subdominio projects.fernandorioseco.es a la aplicación.
    • Redirigir las peticiones HTTP hacia Gunicorn.
    • Servir archivos estáticos directamente.
    • Habilitar HTTPS.
    • Redirigir automáticamente de HTTP a HTTPS.

    Arquitectura final

    Usuario
       
    https://projects.fernandorioseco.es
       
    Nginx (puertos 80 y 443)
       
    Gunicorn (127.0.0.1:8000)
       
    Flask Application

    Instalar y Configurar Nginx

    El siguiente paso es crear un archivo de configuración de Nginx para la aplicación Flask. Ese archivo define cómo Nginx debe responder cuando alguien acceda a projects.fernandorioseco.es.

    sudo apt install nginx
    sudo nano /etc/nginx/sites-available/projects-portal
    server {
        listen 80;
        server_name projects.fernandorioseco.es;
    
        location /static/ {
            alias /var/www/projects-portal/app/static/;
        }
    
        location / {
            proxy_pass http://127.0.0.1:8000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }

    Activar el sitio

    sudo ln -s /etc/nginx/sites-available/projects-portal \
               /etc/nginx/sites-enabled/
               
    # Validar la configuración
    sudo nginx -t
    
    # Recargar Nginx
    sudo systemctl reload nginx
    

    Configurar HTTPS con Let’s Encrypt

    Generar el certificado SSL con Let’s Encrypt y Certbot.

    sudo certbot --nginx -d projects.fernandorioseco.es

    Verificar acceso seguro

    La aplicación deberá quedar disponible en:

    https://projects.fernandorioseco.es

    Flujo de resolución del dominio

    1. El usuario accede al subdominio.
    2. DNS devuelve la IP del VPS.
    3. Nginx recibe la petición.
    4. Nginx reenvía la petición a Gunicorn.
    5. Gunicorn ejecuta Flask.
    6. Flask devuelve la página renderizada.

    8. Actualización y Mantenimiento

    Una vez desplegado el portal, es importante definir un procedimiento claro para actualizar el código, incorporar nuevos proyectos y mantener la aplicación operativa en el tiempo. La arquitectura adoptada, basada en archivos JSON individuales y control de versiones con Git, simplifica enormemente estas tareas de establecer un flujo de trabajo para:

    • Desplegar nuevas versiones del código.
    • Añadir proyectos al catálogo.
    • Corregir errores.
    • Reiniciar servicios.
    • Supervisar el funcionamiento del portal.

    Flujo de actualización del código

    El proceso habitual consiste en:

    1. Realizar cambios en el entorno local (añadir nuevos proyectos o funcionalidades)
    2. Confirmar los cambios en Git.
    3. Enviar los cambios al repositorio remoto.
    4. Actualizar el VPS mediante git pull.
    5. Reiniciar el servicio.
    # ve a la carpeta del proyecto
    cd /var/www/projects-portal
    
    # Actualiza los ficheros desde github
    git pull origin main
    
    # reinicia el servicio 
    sudo systemctl restart projects-portal
    
    # verifica el funcionamiento
    sudo systemctl status projects-portal

    El mantenimiento del portal se reduce a actualizar el repositorio Git, añadir nuevos archivos JSON y reiniciar el servicio Gunicorn cuando sea necesario.

    Script de Actualización del Portal

    En vez de estar haciendo secuencias de comandos en cada actualización, creamos un script en Bash que agilice el proceso. Este script descargará los últimos cambios desde GitHub, reiniciará el servicio de Gunicorn y mostrará el estado final de la aplicación.

    Crear el Script

    nano /var/www/projects-portal/update.sh
    #!/bin/bash
    
    set -e
    
    echo "=========================================="
    echo " Updating Projects Portal"
    echo "=========================================="
    
    cd /var/www/projects-portal
    
    echo "Pulling latest changes from GitHub..."
    git pull origin main
    
    echo "Restarting Gunicorn service..."
    sudo systemctl restart projects-portal
    
    echo "Checking service status..."
    sudo systemctl status projects-portal --no-pager
    
    echo "=========================================="
    echo " Update completed successfully"
    echo "=========================================="
    

    Permisos de ejecución y añadir al PATH.

    # permisos de ejecucion
    chmod +x /var/www/projects-portal/update.sh
    
    # añadir al path para ejecutar desde cualquier ubicacion
    sudo ln -s /var/www/projects-portal/update.sh /usr/local/bin/update-portal

    Ahora el script de actualización es un comando que puede ejecutarse desde cualquier ubicación

    update-portal

    9. Mejoras y Evolución

    El Projects Portal se ha diseñado con una arquitectura modular y escalable que facilita la incorporación de nuevas funcionalidades. A medida que el catálogo crezca, el portal puede evolucionar desde una página estática hacia una plataforma más avanzada de gestión y visualización de proyectos.

    Posibles mejoras

    • Buscador avanzado: Filtros por tecnología, por tipo de proyecto u ordenación.
    • Métricas del VPS: CPU, memoria, uso de disco, uptime, etc.
    • Panel de administración: Crear y editar proyectos desde una interfaz web, subir imágenes y recursos.
    • Base de datos: migración desde archivos JSON a PostgreSQL.
    • API REST: Exponer los proyectos mediante una API desarrollada con FastAPI.
    • Integración con GitHub: mostrar automáticamente estrellas, forks y fecha de última actualización.
    • Dashboards embebidos: integrar aplicaciones desarrolladas con Streamlit, Dash o Gradio.

    Conclusiones

    El desarrollo de Projects Portal ha permitido construir una aplicación web completa utilizando una arquitectura moderna basada en Python y Flask, con despliegue profesional sobre un VPS Linux.

    Aunque funcionalmente se trata de un portal sencillo, el proyecto integra todos los componentes fundamentales que intervienen en el ciclo de vida de una aplicación web en producción: desarrollo local, control de versiones, despliegue, automatización de servicios, configuración del servidor web y publicación segura mediante HTTPS.

    Conocimientos aplicados

    A lo largo del proyecto se han puesto en práctica competencias técnicas de distintas áreas.

    Desarrollo Backend

    • Programación en Python.
    • Desarrollo web con Flask.
    • Uso de plantillas con Jinja2.
    • Lectura dinámica de archivos JSON.
    • Diseño modular de componentes.

    Frontend

    • HTML5 semántico.
    • CSS3 con diseño responsive.
    • JavaScript para búsqueda y filtrado.
    • Integración de recursos gráficos e iconografía.

    DevOps y Linux

    • Administración de Ubuntu.
    • Gestión de permisos y estructura de directorios.
    • Uso de entornos virtuales.
    • Automatización con systemd.
    • Despliegue con Gunicorn.
    • Configuración de Nginx.

    Redes e Infraestructura

    • Configuración de DNS.
    • Gestión de subdominios.
    • Resolución de nombres.
    • Configuración de HTTPS.
    • Certificados SSL con Let’s Encrypt.

    Control de versiones

    • Uso de Git.
    • Publicación en GitHub.
    • Flujo de despliegue basado en git pull.

    Diseño arquitectónico

    El proyecto ha sido diseñado siguiendo principios de modularidad y mantenibilidad:

    • Plantillas HTML desacopladas.
    • Archivos JSON individuales por proyecto.
    • Separación entre contenido, lógica y presentación.
    • Servicio persistente gestionado por systemd.
    • Proxy inverso con Nginx.

    Valor del proyecto

    Projects Portal cumple una doble función.

    • Portfolio técnico: Actúa como punto central para acceder a todos los proyectos desplegados.
    • Laboratorio práctico: Sirve como entorno real para aplicar conocimientos.

    Reflexión final

    Projects Portal demuestra la capacidad de diseñar, desarrollar y desplegar una aplicación web completa siguiendo prácticas profesionales de ingeniería de software e infraestructura.

    El proyecto integra desarrollo backend, frontend, administración de sistemas, redes y automatización, constituyendo una evidencia sólida de competencias en programación, análisis de datos, DevOps y tecnologías modernas de Inteligencia Artificial.

  • Configurar un VPS para prácticas

    Tutorial paso a paso para dejar un servidor virtual listo para trabajar con Python, Docker, GitHub, Jupyter, FastAPI y herramientas de MLOps. Un VPS (Virtual Private Server) es un servidor virtual que alquilas en la nube y al que accedes como si fuera una máquina Linux real. Disponer de un VPS propio es una de las mejores formas de practicar:

    • Administración Linux
    • DevOps
    • Docker y Kubernetes
    • CI/CD
    • APIs con FastAPI
    • Aplicaciones de IA agéntica
    • MLOps
    • Despliegue de modelos de Machine Learning

    En esta guía vamos a configurar un VPS desde cero para convertirlo en un entorno profesional de laboratorio.

    Características Recomendadas para un VPS de prácticas

    Antes de contratar un VPS, conviene elegir una configuración equilibrada que permita trabajar con herramientas de desarrollo, contenedores y cargas moderadas de machine learning.

    Configuración mínima recomendada

    Para un laboratorio personal de DevOps, Data Science y MLOps, estas especificaciones son más que suficientes:

    RecursoRecomendación mínimaRecomendación ideal
    CPU2 vCPU4 vCPU
    Memoria RAM4 GB8 GB o más
    Almacenamiento40 GB SSD/NVMe80 GB NVMe
    Transferencia1 TB/mes2 TB/mes o más
    Sistema operativoUbuntu 24.04 LTSUbuntu 24.04 LTS
    Acceso root
    SnapshotsDeseableMuy recomendable
    Backup automáticoOpcionalRecomendable

    Configuración recomendada según el uso

    CaracteristicaLaboratorio básicoLaboratorio profesionalLaboratorio Avanzado
    CPU2 48
    RAM4 GB8 GB16 GB
    NVMe40 GB80 GB160 GBNVMe
    Adecuado para: Linux, SSH, Python, GitHub Actions, Docker BásicoDocker Compose, FastAPI, MLflow, PostgreSQL, DVC, Entrenamiento de modelos moderados.Múltiples contenedores, Airflow, Kubeflow ligero, procesamiento intensivo.

    Proveedores recomendados

    • Hetzner Cloud
    • Contabo
    • DigitalOcean
    • Vultr
    • OVHcloud
    • AWS EC2
    • Google Cloud
    • Microsoft Azure

    Características del VPS elegido

    Cloud VPS 20 de Contabo

    CaracterísticaEspecificación
    vCPU Cores6 núcleos (AMD EPYC)
    RAM12 GB
    Almacenamiento200 GB SSD (o 100 GB NVMe)
    Velocidad de Puerto300 Mbit/s
    TráficoIlimitado (inbound + outbound)
    Snapshots2 snapshots incluidos
    VirtualizaciónKVM
    Sistema OperativoUbuntu Server 24.04 LTS
    IP1 IPv4 dedicada
    DDoS ProtectionSiempre activada (gratis)
    Precio Base€7.00 / mes + IVA, €5.60/año + IVA

    Una vez completada la suscripción del servicio, nos llegan al correo las credenciales y dirección IP en aproximadamente 30 min. En la consola ya podemos realizar la primera conexión con ssh:

    ssh root@IP_DEL_SERVIDOR
    • ssh: El programa que establece la conexión segura y cifrada.
    • root: El nombre de usuario con el que quieres entrar. En este caso, es el superusuario (el que tiene control total sobre el servidor).
    • @203.0.113.10: La dirección IP del servidor remoto al que te quieres conectar.

    Primeras acciones después de activar tu VPS

    Actualizar el sistema

    sudo apt update && sudo apt upgrade -y
    • sudo: te da permisos de administrador para poder realizar cambios en el sistema.
    • apt update: descarga la información más reciente sobre qué programas tienen versiones nuevas disponibles. No instala nada, solo actualiza la lista.
    • &&: Es un conector lógico. Le dice a la terminal: “si el primer comando termina con éxito, ejecuta el siguiente inmediatamente”.
    • apt upgrade: Compara tus programas instalados con la lista nueva y descarga e instala las actualizaciones.
    • -y: Significa “Yes” (Sí). Responde automáticamente que sí a la pregunta de confirmación, así no tienes que presionar ninguna tecla para que la instalación continúe.

    Crear un usuario administrativo

    adduser usuario
    usermod -aG sudo usuario
    • adduser usuario: Crea una cuenta de usuario nueva llamada “usuario”.
      • Te pedirá que establezcas una contraseña.
      • Creará automáticamente su carpeta personal (/home/usuario) y configurará los archivos básicos del entorno.
    • usermod -aG sudo usuario: Le otorga permisos de administrador.
      • -aG sudo: Añade (-a de append) al usuario al grupo (-G) llamado sudo.
      • Esto permite que el nuevo usuario pueda ejecutar comandos con sudo (como el de actualización que vimos antes) usando su propia contraseña.
    • Después de ejecutar estos comandos, el nuevo usuario debe cerrar sesión y volver a entrar para que los permisos de administrador se activen.

    Configurar la autenticación basada en llaves (en lugar de usar contraseñas).

    En tu equipo local:

    ssh-keygen -t ed25519 -C "[email protected]"

    Este comando se usa para crear un nuevo par de llaves SSH, que es una forma mucho más segura (y cómoda) de entrar a tu servidor que usar una contraseña tradicional.

    • ssh-keygen: La herramienta para generar las llaves.
    • -t ed25519: Especifica el tipo de algoritmo. Ed25519 es el estándar moderno más recomendado porque es increíblemente seguro, rápido y genera llaves cortas.
    • -C "[email protected]": Añade una etiqueta o comentario (normalmente tu email) al final de la llave pública para que sepas a quién pertenece cuando la veas en el servidor.

    Cuando se ejecute el comando, te pregunta si guarda la clave en la ubicación por defecto: ~/.ssh/id_ed25519 (presiona Enter para aceptar). Luego te pedirá una “frase de paso” (opcional). Es una contraseña extra para proteger tu llave privada si alguien robara tu ordenador. Como resultado se crearán dos archivos en tu carpeta .ssh:

    • id_ed25519.pub (Llave Pública): Es como el candado. Esta es la que debes copiar al servidor para poder entrar sin contraseña.
    • id_ed25519 (Llave Privada): Es como tu llave física. Nunca la compartas ni la subas a ningún sitio.

    Instalación de la llave pública en el servidor (o SSH Key Deployment).

    Si usas Linux: 
    ssh-copy-id user@IP_DEL_SERVIDOR
    
    Si usas Windows:
    type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh user@IP_DEL_SERVIDOR "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
    • Este código se conecta al servidor remoto usando tu contraseña por última vez.
    • Copia el contenido de tu llave pública (~/.ssh/id_ed25519.pub) al servidor.
    • Lo agrega automáticamente en un archivo especial llamado ~/.ssh/authorized_keys dentro de la carpeta del usuario remoto.
    • Configura los permisos correctos de las carpetas para que el servidor acepte la llave.

    A partir de este momento, cuando escribas ssh user@IP_DEL_SERVIDOR, el servidor reconocerá tu “candado” y te dejará entrar sin pedirte contraseña, o si configuras una palabra de paso (passphrase) te la pedirá al loguearte.

    Copiar tu llave privada a un pendrive

    Puedes copiar tu llave privada a un pendrive y luego usarla en otro ordenador desde la consola de Windows (PowerShell). Supongamos que tu pendrive es la unidad D: Ejecuta esto en PowerShell:

    # Crear una carpeta en el pendrive para la llave
    mkdir D:\mykeys
    
    # Copiar la llave privada
    copy $env:USERPROFILE\.ssh\id_ed25519 D:\mykeys\

    Cómo usarla desde otro ordenador

    Cuando estés en el otro ordenador y quieras conectarte a tu servidor usando la llave del pendrive puedes apuntar a ella directamente al conectarte:

    # Windows
    ssh -i D:\mykeys\id_ed25519 user@IP_DEL_SERVIDOR
    
    # Linux
    ssh -i /media/tu_usuario/NOMBRE_USB/mykeys/id_ed25519 user@IP_DEL_SERVIDOR
    

    Fortalecer SSH (SSH Hardening).

    El SSH Hardening es un conjunto de configuraciones de seguridad aplicadas al servicio de acceso remoto para evitar ataques. Por defecto, SSH es como una puerta con una cerradura estándar; el hardening es como poner una puerta blindada, cambiar la cerradura por un lector de huellas y ocultar la ubicación de la casa.

    El objetivo principal es pasar de una autenticación basada en “algo que sabes” (contraseñas, que pueden ser adivinadas o robadas) a “algo que tienes” (una llave criptográfica única).

    Los 3 Pilares del Fortalecimiento

    1. Eliminación de contraseñas: Se desactiva la posibilidad de loguearse con clave, obligando al uso de llaves SSH (Ed25519).
    2. Restricción de privilegios: Se prohíbe el acceso directo al usuario root. Debes entrar con un usuario normal y escalar privilegios solo cuando sea necesario.
    3. Reducción de exposición: Se configuran parámetros para que el servidor ignore intentos de conexión malintencionados.

    Riesgos y Cómo evitarlos:

    RiesgoExplicaciónCómo evitarlo
    Bloqueo Total (Lockout)Si tu ordenador se rompe o pierdes tu llave privada, no podrás entrar al servidor porque las contraseñas están desactivadas.Guarda una copia de tu llave privada en un gestor de contraseñas o en un USB cifrado.
    Robo de Llave PrivadaSi alguien copia tu archivo id_ed25519, puede entrar a tu servidor sin esfuerzo.Ponle una passphrase (contraseña) a tu llave al generarla. Así, aunque la roben, no podrán usarla sin la clave.
    Cambio de IP del ServidorSi el servidor cambia de IP y no tienes acceso físico, la llave SSH no servirá de nada si no sabes dónde conectar.Usa una IP estática o un nombre de dominio (DNS) vinculado a tu servidor.

    Visto lo anterior, si necesitas tener una buena seguridad en tu VPS, realiza las siguientes tareas:

    Ejecuta el siguiente comando en una terminal para editar el fichero sshd_config. (sustituye ? por i).

    sudo nano /etc/ssh/sshd_conf?g

    Si deseas eliminar la entrada por contraseñas, realiza los siguientes cambios:

    PermitRootLogin no
    PasswordAuthentication no
    PubkeyAuthentication yes
    • PermitRootLogin no: Prohíbe que alguien intente entrar directamente como root. Ahora es obligatorio entrar con tu usuario normal (user) y luego usar sudo. Esto frena miles de ataques automatizados que prueban contraseñas contra el usuario root.
    • PasswordAuthentication no: Esta es la medida más importante. Desactiva las contraseñas por completo para SSH. A partir de ahora, si alguien no tiene tu archivo de llave privada, no podrá entrar aunque adivine tu contraseña.
    • PubkeyAuthentication yes: Asegura que el sistema permita el acceso mediante el par de llaves (pública/privada) que configuraste antes.
    • sudo systemctl restart ssh: Aplica los cambios inmediatamente.

    ¡Aviso muy importante! No toques esa ventana de la terminal después de reiniciar el servidor ssh. Úsala como “salvavidas”. Si algo falla, desde ahí puedes volver a abrir el archivo de configuración y arreglarlo.

    Reinicia el servidor ssh.

    sudo systemctl restart ssh

    Abre una ventana de PowerShell (en Windows) o una terminal nueva (en Linux/Mac) totalmente independiente de la primera. Intenta conectar desde la nueva ventana:

    ssh user@IP_DEL_SERVIDOR<br>

    Analiza el resultado:

    • Si entras directamente (o te pide la contraseña de tu llave): ¡Éxito! El hardening funciona. Ahora sí puedes cerrar todas las sesiones.
    • Si te pide la contraseña del usuario “fernan” (la de texto): algo falló. El servidor sigue aceptando contraseñas. Revisa que pusiste PasswordAuthentication no.
    • Si te dice “Permission denied (publickey)”: ¡Cuidado! El servidor ha bloqueado las contraseñas, pero no reconoce tu llave.

    ¿Qué hacer si falla?: Como tienes la primera ventana todavía abierta, vuelve a ella y:

    1. Revisa el archivo: sudo nano /etc/ssh/sshd_con*
    2. Asegúrate de que tu llave pública esté en ~/.ssh/authorized_keys.
    3. Reinicia de nuevo: sudo systemctl restart ssh.
    4. Vuelve a probar en la segunda ventana.

    Configurar Firewall UFW

    Configurar y activar el firewall (cortafuegos) de tu servidor, asegurándote de que no te bloquee el acceso SSH que acabas de configurar.

    sudo ufw allow OpenSSH
    sudo ufw allow 22/tcp
    sudo ufw enable
    sudo ufw status
    • sudo ufw allow OpenSSH: Crea una regla que permite el tráfico para el perfil “OpenSSH”. Es la forma más recomendada porque el sistema ya sabe qué puertos usa SSH por defecto.
    • sudo ufw allow 22/tcp: Hace casi lo mismo que el anterior, pero de forma manual especificando el puerto 22 y el protocolo TCP. Es redundante si ya hiciste el anterior, pero sirve como refuerzo.
    • sudo ufw enable: Activa el firewall.
      • Al ejecutar el comando, te lanzará un aviso: “Command may disrupt existing ssh connections. Proceed with y/n?”. Escribe y y presiona Enter. Como ya permitiste el puerto 22 en los pasos anteriores, no perderás la conexión.
    • sudo ufw status: Te muestra la lista de reglas activas para confirmar que el puerto 22 (SSH) está en modo ALLOW (Permitido).

    Si en el futuro decides cambiar el puerto de SSH (por ejemplo al 2222) para evitar ataques automáticos, primero deberás hacer un sudo ufw allow 2222/tcp antes de cambiar la configuración del servidor, o te quedarás fuera.

    Instalar Fail2Ban

    Fail2Ban es un Framework de Prevención de Intrusiones (IPS) ligero y modular, escrito en Python. Su función técnica no es solo vigilar, sino actuar como una capa de automatización de políticas de red. En lugar de depender de la intervención manual de un administrador para bloquear ataques, Fail2Ban establece un puente entre el análisis de logs (aplicación) y la gestión de paquetes (red).

    Características Técnicas

    • Persistencia: Permite mantener bases de datos de atacantes recurrentes, lo que ayuda a identificar patrones de ataque persistentes y a establecer prohibiciones a largo plazo para IPs con comportamiento malicioso sistemático.
    • Arquitectura Basada en Jails (Jaulas): Permite configurar entornos aislados para cada servicio (SSH, Nginx, MySQL). Cada “jail” combina un filtro (la expresión regular que busca el error) con una acción (la respuesta del firewall).
    • Gestión Dinámica de Reglas Netfilter: A diferencia de las reglas estáticas de un firewall tradicional, Fail2Ban inyecta y remueve reglas en las tablas de iptables o nftables dinámicamente, minimizando el impacto en el rendimiento del sistema.
    • Multiservicio: Aunque su uso más común es SSH, es estándar en la industria para mitigar ataques de denegación de servicio (DDoS) a nivel de aplicación en servidores web, intentos de inyección en bases de datos y ataques de fuerza bruta en servidores de correo.

    Instala el paquete desde los repositorios oficiales:

    sudo apt update
    sudo apt install fail2ban -y

    Configuración (Crear tu “Jail”):

    Para esto lo correcto es crear una copia local llamada jail.local. Nunca edites el archivo jail.conf directamente, ya que se sobrescribe al actualizar.

    sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
    sudo nano /etc/fail2ban/jail.local

    Busca la sección [sshd] y déjala configurada así (puedes borrar lo que haya o simplemente añadir estas líneas):

    [sshd]
    enabled = true
    port = ssh
    filter = sshd
    logpath = /var/log/auth.log
    maxretry = 3
    findtime = 600
    bantime = 3600

    ¿Qué significan estos valores?

    • maxretry = 3: El bot tiene solo 3 oportunidades antes del bloqueo.
    • findtime = 600: Los 3 fallos deben ocurrir en una ventana de 10 minutos.
    • bantime = 3600: Si falla, se le prohíbe la entrada por 1 hora (3600 segundos).

    Activar y Verificar

    Guarda el archivo y reinicia el servicio para aplicar los cambios:

    sudo systemctl restart fail2ban
    sudo systemctl enable fail2ban

    Verifica que esté funcionando:

    sudo fail2ban-client status sshd

    Importante: No te bloquees a ti mismo

    Si te equivocas de contraseña 3 veces, tú también serás bloqueado. Para evitarlo, busca la línea ignoreip en tu archivo jail.local y añade tu IP local.

    ignoreip = 127.0.0.1/8 ::1 TU_IP_AQUÍ

    Si te quedas fuera por error o bloqueas a un compañero, puedes liberar la IP inmediatamente con este comando:

    sudo fail2ban-client set sshd unbanip TU_IP_AQUÍ

    Si no estás seguro de qué IPs están baneadas en este momento, primero listalas con:

    sudo fail2ban-client status sshd

    Configurar zona horaria

    Comprobar la zona horaria del servidor

    timedatectl

    Establecer tu zona horaria, en mi caso Islas Canarias

     sudo timedatectl set-timezone Atlantic/Canary

    Herramientas útiles para administrar un VPS

    Estas utilidades son ampliamente utilizadas en administración de sistemas, DevOps y operaciones.

    • tmux — Permite mantener sesiones de terminal persistentes y ejecutar procesos largos aunque se cierre la conexión SSH.
    • htop — Monitor interactivo de procesos para visualizar CPU, memoria y procesos en tiempo real.
    • btop — Monitor avanzado del sistema con métricas de CPU, RAM, disco y red.
    • tree — Muestra la estructura de directorios en formato de árbol.
    • jq — Procesa y filtra datos JSON desde la línea de comandos.
    • ncdu — Analiza el uso del disco y muestra qué directorios ocupan más espacio.
    • rsync — Sincroniza archivos y directorios de forma eficiente, ideal para backups y despliegues.
    • lsof — Muestra qué procesos tienen abiertos archivos, sockets y puertos.
    • dnsutils — Incluye herramientas como dig y nslookup para diagnosticar DNS.
    • mtr — Analiza conectividad de red combinando ping y traceroute.
    • sysstat — Proporciona herramientas como iostat, sar y mpstat para análisis de rendimiento.
    • ufw — Firewall simplificado para gestionar reglas de red.
    • fail2ban — Bloquea automáticamente IPs con intentos repetidos de acceso fallido.
    • unattended-upgrades — Instala automáticamente actualizaciones de seguridad del sistema.
    • tcpdump — Captura y analiza tráfico de red a bajo nivel.
    • iotop — Muestra qué procesos están generando actividad de lectura/escritura en disco.
    • bash-completion — Añade autocompletado avanzado para comandos y opciones en Bash.
    • git — Sistema de control de versiones para clonar y gestionar repositorios.
    • curl — Herramienta para realizar peticiones HTTP y descargar contenido.
    • wget — Utilidad para descargar archivos desde Internet.

    Portal de proyectos para el VPS

    Agrega un sitio web alojado en el vps donde tengas los proyectos que vayas realizando. Puedes ver este proyecto aquí.

    https://fernandorioseco.es/proyecto-aplicacion-web-flask-projects-portal/