sábado, 24 de octubre de 2026

OFIMÁTICA

Competencia ofimática

Según Wikipedia, Ofimática (oficina + informática) designa al conjunto de técnicas, aplicaciones y herramientas informáticas que se utilizan en funciones de oficina para optimizar, automatizar, mejorar tareas y procedimientos relacionados. Comenzó a desarrollarse en la década de los 70 con la inclusión de equipos informáticos que fueron sustituyendo a los métodos y herramientas de oficina tradicionales.

Dada la importancia que tiene en determinados ámbitos profesionales y la facilidad que supone la informatización de un amplio abanico de tareas de oficina, podemos considerar hoy en día la compentencia ofimática debe considerarse parte necesaria del desarrollo competencial de esos ámbitos profesionales. El de los Servicios de Orientación es uno de ellos.

Es frecuente considerar, no obstante, que la competencia ofimática es una competencia genérica lo que lejos de incrementar su consideración, hace que su adquisición se presuponga como efecto natural del propio ejercicio de la profesión. Esto hace que no se desarrollen procesos de formación competencial específicos, o que éstos tengan escaso peso dentro de la formación permanente de los profesionales.

Si limitamos la competencia ofimática a un conjunto de capacidades de uso de determinadas herramientas que, con frecuencia, se denominan nivel usuario, incluso si exigimos una calificación específica de esa competencia como de usuario experto, casi se puede decir que es correcta la anterior valoración, si bien, en mi opinión, es dudoso que así sea a partir del nivel de usuario experto.

Donde claramente considero insostenible la presunción de suficiencia en cuanto a competencial ofimática "experiencial" es cuando esa competencia requiere el conocimiento y dominio práctico del uso de determinadas funcionalidades y, más aun, cuando concretamos el nivel competencial como capacidad (incluso de) desarrollar procedimientos de automatización de la composición documental.

Ni la experiencia personal ni los datos actuales sobre las practicas profesionales de los orientadores permite mantener una opinión opuesta a la anterior. Es más, diría que la IA y su irrupción como herramienta en esa práctica aleja aun más el logro de ese nivel competencial.

En la medida en que la IA sea capaz de ejecutar tareas que implican ese nivel de automatización se inhibirá el interés por alcanzar dicha competencia, haciendo verdad eso de que cuanto más potente sea la IA, menos competente necesita ser el usuario. Se puede decir de forma más clara. Cierto es que este planteamiento no es el único posible, puesto que existen otras perspectivas, otros planteamientos y otras formas de entender en qué consiste la tanto la compentencia profesional como la competencia digital, incluyendo en ella la compentencia ofimática. En este blog defendemos una de estas alternativas.

viernes, 2 de octubre de 2026

NÚMEROS

Correlación. Definición y tipología


Estadística descriptiva. Análisis bivariado correlacional

La correlación es una medida estadística que indica el grado y la dirección en que dos variables varían conjuntamente.

  • Dirección positiva (directa): cuando una variable aumenta, la otra tiende a aumentar (p. ej., estatura y peso).
  • Dirección negativa (inversa): cuando una variable aumenta, la otra tiende a disminuir (p. ej., velocidad de un vehículo y tiempo necesario para recorrer una distancia fija).
  • Dirección nula: no existe una relación lineal observable entre ambas variables (p. ej., número de calzado e inteligencia, si no existe asociación lineal en la población estudiada).

La correlación mide asociación, no causalidad. Que dos variables estén correlacionadas no implica que una sea la causa directa de la otra.

Un coeficiente de correlación es un índice numérico normalizado que cuantifica la fuerza y la dirección de la relación entre dos variables. Muchos de los coeficientes más utilizados están acotados en el intervalo de -1 a +1: +1 indica una asociación positiva perfecta, 0 ausencia de asociación según el tipo de relación que mida el coeficiente y -1 una asociación negativa perfecta.

El tipo de coeficiente adecuado depende principalmente de la escala de medida de las variables (cuantitativa, ordinal o nominal), del tipo de relación que se desea analizar y de determinados supuestos estadísticos. A continuación se resumen algunos de los coeficientes más utilizados según la combinación de variables.

Variables cuantitativas (intervalo o razón)

Pearson (r):

  • Uso: evalúa la intensidad y dirección de la relación lineal entre dos variables cuantitativas.
  • Supuestos: para la inferencia estadística convencional suelen considerarse la linealidad, la ausencia de valores atípicos influyentes y, dependiendo del procedimiento inferencial empleado, determinados supuestos de normalidad. La normalidad no es necesaria para calcular el coeficiente de Pearson.
  • Rango: de -1 a +1.

Spearman (rho o rs) / Kendall (tau):

  • Uso: permiten evaluar asociaciones monótonas y constituyen alternativas a Pearson cuando la relación no es lineal, existen valores ordinales o no resulta adecuado utilizar los supuestos del análisis paramétrico.
  • Spearman: se basa en los rangos de las observaciones y resulta especialmente útil para relaciones monótonas.
  • Kendall: se basa en la concordancia entre pares de observaciones y puede resultar especialmente apropiado con muestras pequeñas o cuando existen muchos empates.

Variables ordinales (escalas, rangos o ítems de tipo Likert)

Spearman (rho / rs):

  • Uso: evalúa la asociación monótona mediante los rangos. Es apropiado cuando al menos una de las variables es ordinal o cuando se desea analizar variables cuantitativas mediante sus rangos.

Kendall (tau):

  • Uso: mide la asociación ordinal a partir de la concordancia y discordancia entre pares de observaciones. Es especialmente útil con muestras pequeñas y situaciones con muchos empates.

Variables categóricas / nominales

V de Cramer (V):

  • Uso: mide el grado de asociación entre dos variables nominales en tablas de contingencia de cualquier dimensión (r × c). Se deriva del estadístico chi-cuadrado (χ2).
  • Rango: de 0 a 1, donde 0 indica ausencia de asociación y 1 asociación máxima.

Coeficiente Phi (φ):

  • Uso: se utiliza para medir la asociación entre dos variables dicotómicas, representadas mediante una tabla de contingencia 2 × 2. Está estrechamente relacionado con el coeficiente de correlación de Pearson para variables binarias.

Coeficiente de contingencia de Pearson (C):

  • Uso: mide la asociación entre dos variables nominales a partir del estadístico chi-cuadrado.
  • Rango: su límite superior depende del tamaño de la tabla de contingencia, por lo que no siempre puede alcanzar 1.

Combinación de tipos de variables (mixtas)

Biserial puntual (rpb):

  • Uso: mide la asociación entre una variable cuantitativa y una variable dicotómica naturalmente dicotómica (p. ej., respuesta correcta/incorrecta). Es un caso particular del coeficiente de Pearson.

Biserial (rb):

  • Uso: mide la asociación entre una variable cuantitativa y una variable que ha sido artificialmente dicotomizada a partir de una variable continua subyacente (p. ej., aprobado/suspenso a partir de una puntuación continua).

Eta (η):

  • Uso: mide la asociación entre una variable cuantitativa y una variable categórica que define diferentes grupos. Permite detectar relaciones que no tienen por qué ser lineales y está relacionado con el análisis de varianza (ANOVA).
  • Interpretación: η2 puede interpretarse como la proporción de variabilidad de la variable cuantitativa asociada a las diferencias entre grupos.

Rank-biserial (rrb):

  • Uso: mide la asociación entre una variable ordinal o basada en rangos y una variable dicotómica. Se utiliza, entre otros contextos, como medida del tamaño del efecto en comparaciones entre dos grupos mediante procedimientos no paramétricos.

lunes, 28 de septiembre de 2026

NÚMEROS. Python

Medidas de posición (II)


Estadística descriptiva

En esta entrada hablaremos de los cuantiles y estadísticos relacionados (rango intercuartílico) según son tratados en Pandas. Completamos así el tratamiento de estos estadísticos vistos previamente desde la óptica de OOo Basic/Calc y desde la biblioteca Stadistics de Python.

Como vimos en una entrada precedente, Pandas dispone del método df.describe(), que permite obtener los cuantiles 0.25, 0.50 y 0.75, además del mínimo y el máximo de una distribución.

Pero tambien contamos con el método .quantile(), que permite calcular el valor límite para una probabilidad dada o un conjunto de probabilidades, y las funciones pd.cut() y pd.qcut(), que sirven para dividir los datos en intervalos.



import pandas as pd

df = pd.DataFrame({
    'edad': [18, 22, 25, 30, 35, 40, 48, 55, 60, 70]
})

# Cuantil 0.50 (Mediana)
mediana = df['edad'].quantile(0.5)

print (mediana)

# Múltiples cuantiles (Cuartiles: 25%, 50%, 75%)
cuartiles = df['edad'].quantile([0.25, 0.50, 0.75])

print(cuartiles)


Sobre una columan numética de un DataFrame, aplicamos el método .quantile()...

  • pasando como parámetro el valor mediana = df['edad'].quantile(0.5)
  • o los valores cuartiles = df['edad'].quantile([0.25, 0.50, 0.75]) deseados

Observa que en estos casos especificamos sobre qué componente del DataFrame queremos trabajar, pero podemos omitirlo; no obstante es conveniente explicitarlo cuando existen varias columnas numéricas, ya que así concretamos sobre qué columna estamos realizando los cálculos.

Como dije antes, las funciones pd.cut() y pd.qcut() dividen los datos en intervalos, pero lo hacen de forma diferentes.

Supongamos que tenemos las notas de una serie de alumnos



import pandas as pd

notas = pd.Series([3, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10])


... y queremos clasificar las notas en tres categorías según el rango de valores.



pd.cut(notas, bins=3)


Para ello empleamos la función pd.cut() especificando la columna sobre la que trabajamos y el número de categorías. También podemos utilizar etiquetas para identificar las categorías pd.cut(notas, bins=3, labels=["Baja", "Media", "Alta"])

Lo que obtenemos son tres intervalos de igual amplitud, pero el número de observaciones por intervalo puede ser muy diferente, como es el caso

  • Bajo (1-4) -> 9 observaciones
  • Medio (4-7) -> 1 observación
  • Alto (7-10 -> 2 observaciones

Pero si utilizamos la función pd.qcut()...


pd.qcut(
    notas,
    q=3,
    labels=["Bajo", "Medio", "Alto"]
)

Obtenemos tres intervalos de diferente amplitud pero igual número de observaciones, por lo que se evidencia que se utiliza la lógica de los cuantiles para determinar dónde colocar los puntos de corte.

  • Bajo (1-3) -> 4 observaciones
  • Medio (3-4) -> 4 observaciones
  • Alto(4-10) -> 4 observaciones

Para finalizar, el cálculo de rango intercuartílico no tiene una función específica en Pandas, pero su cálculo es muy sencillo, a partir del método quantile(): ric = quantile(0.75) - quantile(0.25) Un ejemplo



import pandas as pd

datos = pd.Series([10, 15, 20, 25, 30, 35, 40, 45, 50])

q1 = datos.quantile(0.25)
q3 = datos.quantile(0.75)

ric = q3 - q1

print(ric)


sábado, 26 de septiembre de 2026

LENGUAJES

Programación y orientación educativa


Lenguajes de programación

Hoy nadie pone en duda que la denominada competencia digital forma parte de las competencias profesionales de los orientadores educativos. Sin embargo, predomina cierta reducción conceptual de la misma como conocimiento y manejo de recursos y aplicaciones, lo que supone adoptar una concepción fundamentalmente instrumental que deja en segundo plano la resolución de problemas, la creación de soluciones digitales y el pensamiento computacional. Frente a ello, los marcos europeos de competencia digital contemplan estas dimensiones e incluyen el pensamiento computacional y la programación como medios para analizar problemas, descomponerlos y formular secuencias de instrucciones que permitan resolverlos. Esta relación entre pensamiento computacional y programación ha sido también objeto de diversas revisiones sistemáticas de la investigación educativa reciente.

En este contexto, el conocimiento de los lenguajes de programación no constituye un fin profesional en sí mismo, sino una forma de materializar y poner en práctica ese pensamiento computacional mediante el desarrollo de soluciones propias. La inteligencia artificial generativa introduce aquí una doble posibilidad, potencialmente contradictoria: puede reforzar una relación meramente consumidora con la tecnología al permitir obtener soluciones cuyo proceso de generación permanece, en buena medida, opaco incluso para quien las utiliza y que, por tanto, pueden ser aceptadas sin disponer de los conocimientos necesarios para comprenderlas y evaluarlas; pero también facilita la transformación de una solución concebida computacionalmente en código, reduciendo parte del esfuerzo técnico necesario. La investigación reciente señala precisamente esta tensión entre la facilitación que ofrecen estas herramientas y el riesgo de que, especialmente entre quienes se inician, se externalice en ellas una parte del propio razonamiento. Una revisión sistemática de 40 estudios empíricos sobre IA generativa en la enseñanza de la programación llega a una conclusión relacionada: la integración de estas herramientas plantea oportunidades, pero también la necesidad de preservar las capacidades fundamentales de programación y de razonamiento de orden superior.

Esta doble posibilidad hace especialmente importante distinguir entre la capacidad para obtener una solución digital y la capacidad para comprenderla, evaluarla y modificarla. La IA generativa puede reducir el coste técnico de producir código, pero no elimina la necesidad de comprender el problema que se pretende resolver ni de ejercer criterio sobre la solución obtenida. En este sentido, el aprendizaje de la programación mantiene su valor precisamente porque proporciona los conocimientos necesarios para plantear correctamente un problema, valorar una solución y mantener el control sobre aquello que se ha construido, incluso cuando parte de su desarrollo se haya realizado con ayuda de herramientas de inteligencia artificial. Esta cuestión resulta especialmente relevante cuando se analiza la capacidad de los estudiantes para comprender y corregir código generado por modelos de lenguaje.

La cuestión, por tanto, no es convertir al orientador en un profesional de la programación, sino identificar qué puede aportar esta capacidad cuando se aplica a problemas propios de su práctica profesional. El valor de programar aparece precisamente cuando permite pasar de la utilización de herramientas ya disponibles a la posibilidad de intervenir sobre los propios procesos de trabajo, construyendo soluciones ajustadas a necesidades concretas, automatizando procedimientos cuando resulte conveniente y, sobre todo, manteniendo la capacidad de comprender, revisar y modificar aquello que se ha construido.

Los Servicios de Orientación trabajan constantemente con información: la recogen de distintas fuentes, la organizan, la relacionan, la transforman, la analizan y finalmente la utilizan para elaborar informes, tomar decisiones o comunicar resultados. El interés de la programación reside, sí, en automatizar algunas tareas, pero sobre todo y principalmente en proporcionar una forma de formalizar procedimientos y convertir determinadas necesidades en soluciones digitales que el propio profesional puede diseñar, adaptar y controlar. Esta capacidad de formalización es, precisamente, una de las formas en las que el pensamiento computacional adquiere una dimensión directamente vinculada con la práctica profesional.

Esta capacidad de analizar y transformar problemas en procedimientos tiene, además, una dimensión directamente profesional. Adquiere especial relevancia en un contexto en el que buena parte de las soluciones digitales utilizadas en los Servicios de Orientación son productos diseñados por terceros. Utilizarlos exige conocer sus posibilidades y limitaciones, pero no proporciona capacidad para modificarlos cuando no se ajustan a una necesidad concreta. Aprender a programar introduce, en cambio, un grado diferente de autonomía: permite pasar de ser únicamente usuario de herramientas a poder construir, modificar o integrar soluciones propias, aunque sean simples y específicas.

La inteligencia artificial generativa modifica además las condiciones en las que esta capacidad puede ejercerse. Puede reducir considerablemente el esfuerzo necesario para escribir código, facilitar el aprendizaje y hacer viable la creación de soluciones que antes requerían una mayor especialización técnica. Pero precisamente por eso adquiere importancia disponer de los conocimientos necesarios para plantear correctamente el problema, valorar la solución propuesta, detectar errores y decidir si el código generado responde realmente a las necesidades profesionales. La IA puede reducir el coste de producir código, pero no elimina la necesidad de comprender el problema que se pretende resolver ni de ejercer criterio sobre la solución obtenida. Los resultados de una revisión sistemática y metaanálisis sobre el efecto de las herramientas de IA en el aprendizaje de la programación permiten situar esta cuestión en un contexto más amplio de investigación.

De este modo, puede convertirse en un instrumento para ampliar la capacidad de programación, pero no en un sustituto del pensamiento computacional que debe orientar su utilización. Estudios recientes han explorado precisamente cómo una utilización de la IA acompañada de estrategias de apoyo y orientación puede mantener el desarrollo de la lógica de programación y de las capacidades de orden superior, reduciendo al mismo tiempo el riesgo de dependencia de la herramienta.

Si el valor de aprender a programar se sitúa en su capacidad para resolver problemas profesionales concretos, resulta especialmente interesante considerar aquellos lenguajes que pueden integrarse directamente en las herramientas que los orientadores ya utilizan o que permiten construir recursos específicos cuando las soluciones disponibles no responden a sus necesidades.

En este sentido, OOo Basic, por ejemplo, constituye una primera aproximación especialmente vinculada al trabajo cotidiano de los Servicios de Orientación. Al estar integrado en una suite ofimática como LibreOffice, permite programar sobre herramientas que ya forman parte del trabajo habitual. Esto hace posible automatizar operaciones repetitivas, transformar y organizar información, generar documentos a partir de datos, establecer procedimientos que se ejecuten de manera sistemática o adaptar el funcionamiento de las aplicaciones a necesidades concretas. La propia documentación de LibreOffice sobre macros y Basic permite ampliar información sobre estas posibilidades.

Python permite dar continuidad a este tipo de aplicaciones sobre datos, documentos y otros recursos ofimáticos, pero plantea al mismo tiempo un salto de escala. Su carácter de lenguaje de propósito general permite desarrollar soluciones que ya no están limitadas al funcionamiento de una aplicación ofimática concreta y que pueden combinar tratamiento de datos, generación y transformación de documentos, acceso a servicios externos, automatización de procesos, desarrollo de interfaces o conexión con diferentes fuentes de información. La documentación oficial de Python permite apreciar este carácter general, mientras que la biblioteca estándar muestra la amplitud de funcionalidades disponibles para tareas de programación y tratamiento de información.

Además debemos tener en cuenta la estrecha relación de Python con la inteligencia artificial. Buena parte de las herramientas, bibliotecas y servicios utilizados para trabajar con modelos de IA pueden integrarse mediante Python, lo que permite pasar de utilizar aplicaciones de inteligencia artificial ya construidas a incorporar determinadas capacidades de IA dentro de recursos propios.

Desde esta perspectiva, OOo Basic y Python constituyen recursos complementarios para ampliar la autonomía tecnológica del orientador. Ambos permiten identificar cuándo una necesidad profesional puede abordarse mediante una solución propia y, llegado el caso, construirla o adaptarla con ayuda de herramientas como la inteligencia artificial. Lo fundamental es que el profesional conserve la capacidad para comprender el problema, formalizarlo y valorar y controlar la solución adoptada.

Nota: Esta entrada ha sido elaborada en diálogo con ChatGPT, utilizado como herramienta de apoyo durante el proceso de redacción y revisión.

miércoles, 23 de septiembre de 2026

NÚMEROS. Python

Medidas de tendencia central (II)


Estadística descriptiva

Con Pandas podemos calcular las medidas de tendencia central (media, mediana y moda) sobre un DataFrame de forma rápida y directa. Estas funciones se integran de forma natural con la capacidad de la librería para manipular y filtrar conjuntos de datos.

Aunque en proyectos reales cargaremos los datos desde fuentes externas (como archivos CSV), para facilitar el aprendizaje aquí crearemos un DataFrame directamente a partir de un diccionario:



import pandas as pd

# Creamos un DataFrame de prueba
data = {
    'Nombre': ['Ana', 'Luis', 'Carlos', 'Marta', 'Sofía', 'Pedro', 'Elena'],
    'Edad': [25, 30, 25, 40, 30, 25, 35],
    'Nota': [2.5, 3.1, 2.5, 4.2, 3.1, 2.1, 3.8]
}

df = pd.DataFrame(data)


A partir de este conjunto de datos, podemos aplicar los principales estadísticos descriptivos tanto a columnas individuales como a todo el conjunto de datos numéricos de forma simultánea:



#---------------------------------------------------------------
#Medidas de tendencia central
#----------------------------------------------------------------

# Promedio -----------------------------

# Media de una sola columna
nota_media = df['Nota'].mean()
print(f"Nota media: {nota_media:.2f}")

# Media de todas las columnas numéricas del DataFrame
media_total = df.mean(numeric_only=True)
print("\nMedia de las columnas numéricas:\n", media_total)

# Mediana-----------------------------

# Mediana de una sola columna
mediana_nota = df['Nota'].median()
print(f"Mediana de la nota: {mediana_nota}")

# Mediana de todas las columnas numéricas
mediana_total = df.median(numeric_only=True)
print("\nMediana de todas las columnas numéricas:\n", mediana_total)

# Moda---------------------------------------

# Moda de una columna específica (devuelve una Series)
moda_nota = df['Nota'].mode()[0]  # Tomamos el primer elemento con [0]
print(f"Moda de la nota: {moda_nota}")

# Moda de todo el DataFrame
moda_total = df.mode()
print("\nModa de las columnas:\n", moda_total)


Calcular estos valores sobre todo el DataFrame a la vez refleja el verdadero potencial de Pandas para procesar colecciones de datos sin necesidad de iterar manualmente sobre ellas.

Además de los métodos individuales, disponemos del método describe(), que genera un resumen con los principales estadísticos descriptivos (incluyendo recuento, desviación estándar y percentiles):



resumen = df.describe()
print(resumen)


Si necesitamos un control más preciso sobre qué métricas aplicar a cada columna, podemos utilizar el método agg():



# Agregación personalizada con agg() sobre columnas existentes

def primera_moda(x):
    m = x.mode()
    return m.iloc[0] if not m.empty else None

estadisticos = df[['Edad', 'Nota']].agg(['mean', 'median', primera_moda])
estadisticos.rename(index={'primera_moda': 'mode'}, inplace=True)

print("\nAgregación personalizada (Edad y Nota):")
print(estadisticos)


En conclusión, Pandas ofrece diversas alternativas para obtener estadísticos descriptivos de manera eficiente, adaptándose tanto a análisis puntuales sobre una columna como a resúmenes globales sobre grandes volúmenes de datos.

martes, 22 de septiembre de 2026

NÚMEROS. Python

Pandas


Bibliotecas Python

Al contrario que NumPy, Pandas sí tienen un interés directo para la intervención de los SEO, tanto por sus capacidades para el trabajo con tablas de datos, ese conjunto de datos que tanto uso tiene en nuestra práctica profesional y que bajo Pandas pasa a identificarse como DataFrame, como por servirnos de base para la elaboración y la presentación de la información, en este caso complementado por bibliotecas orientadas a la representación gráfica.

Además de estos usos directos o como soporte de base, Pandas también tiene utilidad para que desde la Orientación educativa en proyectos dirigidos al desarrollo de recursos de utilidad en este campo, nos planteemos generar herramientas basadas en el Machine Learning. En concreto Pandas sirve para facilitar la limpieza y preparación de los dados con los que entrenar nuestros modelos y para la manipulación, transformación y creación de variables. Como podemos apreciar, estamos hablando del enfoque o modelo estadístico de la IA, fundamental en los planteamientos actuales de un ML orientado a la resolución de problemas prácticos también en nuestro ámbito profesional.

Pandas es una biblioteca de uso muy frecuente en el análisis de datos y en su tratamiento estadístico. Te recomiendo consultar su documentación oficial

Por lo que se refiere al uso práctico de Pandas, además de lo ya visto al hablar de los DataFrame en la sección LENGUAJES, en la presente nos centraremos en el uso de Pandas como herramienta para el análisis estadístico, por lo que me limito aquí a su mera presentación.

LENGUAJES. Python

DataFrame (Pandas) (II)

Colecciones de datos

En la entrada anterior creamos un DataFrame partiendo de un diccionario integrado en el propio script. Esta fórmula es adecuada desde el punto de vista didáctico, ya que ayuda a comprender la conexión entre las estructuras nativas de Python y las de Pandas, así como a experimentar con sus primeras manipulaciones. Sin embargo no refleja la dinámica de trabajo habitual.

En esta segunda entrada vamos a proceder del modo en que es más común trabajar, ya que en los proyectos reales, los datos residen en fuentes externas, como archivos CSV u hojas de cálculo. Mediante funciones de lectura se cargan los datos en memoria en forma de DataFrame, lo que permite separar el almacenamiento de los datos y su procesamiento. Esto mejora el flujo de trabajo y permite aprovechar al máximo la potencia analítica de Pandas.

Para el caso vamos a utilizar el archivo Avandono_escolar.csv, obtenido de la página web del MEFPD, sobre el cual realizaremos diferentes manipulaciones mediante Pandas, una vez convertido en un DataFrame de trabajo.

Empezaremos por acceder al archivo .cvs (1) para cargarlo en nuestro script, siguiendo lo que te propongo a continuación.


import pandas as pd

# 1. Definir la ruta del archivo CSV
ruta_archivo = r'directorio_de_archov.csv' #Sustituye por tu ruta

# 2. Leer el archivo CSV y cargarlo en un DataFrame
df = pd.read_csv(ruta_archivo, sep=';', decimal=',')


Lo primero que deberás hacer es sustituir directorio desde el que se cargará el archivo .csv. Después debes prestar atención a la instrucción mediante la que se carga ese archivo con DataFrame df = pd.read_csv(ruta_archivo, sep=';', decimal=','), ya que contiene dos especificaciones que son necesarias teniendo en cuenta las características de nuestro archivo de origen, dado que los campos están separados por ; y se utiliza la , como separador de la parte decimal del número. Normalmente separamos los campos por , y en los números empleamos . como separador de decimales, por ello la instrucción normal de carga es más simple df = pd.read_csv(ruta_archivo), pero es este caso fallaría (2)

Ahora podremos, por ejemplo, visualizar los primeros y los últimos registros de nuestro DataFrame, lo que nos sirve para confirmar que la carga ha resultado exitosa y ajustada a la estructura del DataFrame. Para ello añadiremos el siguiente código al script.



# 3. Mostrar las primeras cinco filas del DataFrame
print("Primeras 5 filas y últimas 5 filas del DataFrame:")
print(df)

print("\nTipos de datos asignados por Pandas:")
print(df.dtypes)


Si sólo deseamos ver las primeras filas usamos la instrucción print(df.head()) y si añadimos una cantidad dentro de la función print(df.head(10)) esteremos explicitando cuantas filas queremos ver (3)

Visualizar el DataFrame puede ser interesante para conocer su contenido y estructura, pero muy posiblemente ambos nos sean ya conocidos (4). Lo que realmente hace útil a Pandas son las posibilidades que nos ofrece de manipular el contenido del DataFrame, utilizando filtros para seleccionar parte del mismo, realizando cálculos sobre sus datos o generando nuevas columnas a partir de las existentes. Veamos en el código que sigue algunas de estas opciones.



#Ejemplos de trabajos sobre el DataFrame

# 1. Filtros. Aplicar un filtro con dos condiciones combinadas con '&'. Cada condición DEBE ir encerrada entre paréntesis (...)
sub_df = df[(df['Territorio'] == 'TOTAL') & (df['Sexo'] == 'Ambos')] #Establecemos los filtros
sub_df = sub_df.sort_values(by='periodo', ascending=True) # Reordenamos el DataFrame en orden ascendente
n = len(sub_df) # Obtenemos el número de registros (filas) del DataFrame para su posterior visualización

# 2. Cálculos. Calcular la diferencia interanual sobre la columna del valor ('Total %'). diff() resta a la fila actual el valor de la fila anterior
sub_df['dif'] = sub_df['Total %'].diff().round(2)

# 3. Hacer una copia explícita (Buena práctica recomendada en Pandas). Así evitamos avisos tipo 'SettingWithCopyWarning' si se modifica el sub_df
sub_df = sub_df.copy()

# 4. Visualizar el resultado
print(f"Filas resultantes en el sub-dataframe: {len(sub_df)}")
print(sub_df.head(n))


Finalmente, nos puede interesar guardar un archivo CSV con el trabajo realizado, creando un nuevo archivo (sin modificar el original) con los resultados de nuestro trabajo. Para ello añadiremos al script el código que sigue (4).



# Guardamos el sub-DataFrame como csv

# 1. Definir la ruta completa del nuevo archivo CSV (en la misma carpeta)
ruta_salida = r'C:\Mi_Ruta\Nuevo_Archivo.csv'

# 2. Guardar el sub-DataFrame
sub_df.to_csv(ruta_salida, sep=';', decimal=',', index=False, encoding='utf-8-sig')

print(f"El archivo se ha guardado correctamente en: {ruta_salida}")


Mediante Pandas es posible realizar manipulaciones de un DataFrame mucho más complejas que las explicadas en estos script, lo que permite realizar estudios diversos sobre los datos que contiene. También podemos construir nuevos DataFrames a partir de otros combinando sus datos, siempre que sus estructuras y contenidos lo permitan. Todo ello implica concretar un plan de trabajo, lo que es más propio de un proyecto de análisis que de una entrada como la actual, pensada para explicar la naturaleza de la colección de datos llamada DataFrame.

Otro campo de interés en el trabajo con estas colecciones de datos usando Pandas es la ejecutar funciones estadísticas sobre sus datos, pero esto lo dejaremos para tratarlo en otra sección. De momento esta la damos por cerrarda aquí.

NOTAS
1 Para lo que previamente habremos descargado dicho archivo desde el enlace que ofrezco en el párrafo anterior o, si se prefiere, desde el enlace a la web del MEFPD. En ambos casos deberemos haber guardado el archivo con .csv en un determinado directorio. Esa ubicación será la que sustituya a que empleamos en el script a modo de ejemplo.
2 Fallaría no en el momento de la carga, sino al trabajar con el DataFrame. Por eso doy toda esta explicación para evitarte tener que investigar sobre esta cuestión. En todo caso, es conveniente que conozcas de antemano cómo está construído tu archivo .csv para que ajuste la instrucción de carga.
3 En este caso veremos 10 filas o registros. Para ver sólo las últimas filas empleamos print(df.tail())
4 Ya sabes que puedes visualizar un archivo csv desde Bloc de notas o desde Calc, lo que te recomiendo como alternativa a visualizarlo en el CMD mediante Python.
4 Como supondrás, deberás modificar la instrucción ruta_salida = r'C:\Mi_Ruta\Nuevo_Archivo.csv' sustituyendo la ruta por la que corresponda en tu caso. Te sugiero que sea la misma que empleaste para guardar el archivo csv original, dando al nuevo un nombre que ilustre su contenido. En este, por ejemplo, caso algo así como Datos_Totales.csv.

sábado, 19 de septiembre de 2026

LENGUAJES. Python

DataFrame (Pandas) (I)

Colecciones de datos

Un DataFrame es una estructura de datos bidimensional (organizada en filas y columnas) similar a una tabla de base de datos, una hoja de cálculo o una matriz. Es el objeto fundamental para la manipulación y análisis de datos en programación, especialmente en bibliotecas como Pandas (Python).

Los datos de cada columna pueden ser de distinto tipo (pero los de la misma columna han de ser del mismo), lo que le otorga gran flexibilidad, y dispone de índices por filas y por columnas, lo que le proporciona un funcionamiento eficiente en cuanto al acceso, el filtrado de datos y su alineación. Además es posible realizar operaciones vectorizadas, lo que nos permite aplicar transformaciones, filtros y cálculos matemáticos a columnas completas de forma eficiente sin necesidad de recurrir a bucles explícitos. En todas estas características se hace notar la influencia de los array de NumPy, base sobre la que se diseñó la librería Pandas y el concepto de DataFrame en Python (1).

La creación de un DataFrame (2) puede partir de la transformación de colecciones de datos nativas de Python como, por ejemplo, a partir de un diccionario cuyos valores son listas de datos:



import pandas as pd

# Creacción del diccionario
datos = {
    'Nombre': ['Ana', 'Carlos', 'Elena', 'David'],
    'Edad': [4, 6, 8, 10],
    'Centro': ['CP San Tirso', 'Col. Donato Barato', 'CP Sevilla', 'CP Luna LLena'],
    'Madre': ['María', 'Rosana', 'Carlota', 'Venancia']
}

# Conversión de diccionario a DataFrame

df = pd.DataFrame(datos)


Primero importamos la biblioteca Pandas con el alias que es costumbre import pandas as pd y después de crear el diccionario datos lo convertimos en una DataFrame df = pd.DataFrame(datos). A partir de aquí podemos realizar diferentes operaciones con el DataFrame creado haciendo uso de instrucciones Python y de otras específicas de Pandas.

Por ejemplo, podemos mostar el contenido del DataFrame en el CMD mediante print(df), obteniendo...

También podemos incrementar el contenido en ambas estructuras (diccionario y DataFrame), comprobando de paso las diferencias en el manejo de ambas estructuras.



# ==================================================
# PROCEDIMIENTO A: Añadir "registro" al DICCIONARIO
# ===================================================

# Al tratarse de un diccionario de listas, hay que añadir cada elemento clave a clave en su lista correspondiente.

datos['Nombre'].append('Lucas')
datos['Edad'].append(5)
datos['Centro'].append('CP San Tirso')
datos['Madre'].append('Laura')

print("Diccionario actualizado:")
print(datos)
print("--------------------------")


# =================================================
# PROCEDIMIENTO B: Añadir "registro" al DATAFRAME
# =================================================

# Los DataFrames de Pandas son estructuras inmutables en tamaño sobre la memoria de NumPy.
# A partir de Pandas 2.0+, la forma de añadir una fila es crear un mini-DataFrame y concatenarlo mediante pd.concat().

nuevo_df = pd.DataFrame([{
    'Nombre': 'Lucas',
    'Edad': 5,
    'Centro': 'CP San Tirso',
    'Madre': 'Laura'
}])

df = pd.concat([df, nuevo_df], ignore_index=True)

print("DataFrame actualizado:")
print(df)
print("--------------------------")

También podemos ejecutar diferentes modificaciones en el mismo, como, por ejemplo...



# 1. Selección de columnas

nombres = df['Nombre']
centros_y_madres = df[['Centro', 'Madre']]

# 2. Filtrado por condición (operación vectorizada)

# Alumnos mayores de 6 años (Elena y David)
mayores_de_6 = df[df['Edad'] > 6]

# Alumnos del colegio "CP San Tirso"
san_tirso = df[df['Centro'] == 'CP San Tirso']

# 3. Creación y modificación de columnas (operación vectorizada)

# Calcular el año de nacimiento estimado
df['Año_Nacimiento'] = 2026 - df['Edad']

# Indicador booleano para etapa infantil (menores o iguales a 5 años)
df['Es_Infantil'] = df['Edad'] <= 5

# 4. Agregación y resumen de datos

edad_promedio = df['Edad'].mean()


... que realizan las modificaciones y cálculos explicados en los comentarios, pero que no se muestran en pantalla al faltar las instrucciones print() que dejo de tu mano. Una vez realizadas estas modificaciones, lo lógico es guardar el DataFrame modificado para poder utilizarlo más adelante. Para ello es suficiente con implementar al final del script el código siguiente (3)



# Guardar el DataFrame en la ruta especificada

df.to_csv("D:/datos/mi_df.csv", index=False, encoding='utf-8')

print("DataFrame guardado correctamente en D:/datos/mi_df.csv")
print("--------------------------")


NOTAS
1 Pandas es una librería diseñada en 2008 por Wes McKinney, quien construyó la arquitectura del DataFrame para que usara las ventajas de bajo nivel de NumPy.
2 Lo frecuente es trabajar con archivos ya creados (tablas en hojas de datos o archivos CSV), pero en esta entrada nos limitaremos a la transformación de colecciones de datos insertas en el script.
3 La ruta donde se guardar realmente queda de tu mano, por lo que deberás modificar la instrucción "D:/datos/mi_df.csv" de df.to_csv("D:/datos/mi_df.csv", index=False, encoding='utf-8'). Observa que el DataFrame se guarda como archivo csv. Finalmente, mediante index=False evitamos que se guarde la columna del índice numérico (0, 1, 2, 3)

jueves, 17 de septiembre de 2026

LENGUAJES. Python

Array (NumPy)

Colecciones de datos

Tras estudiar las colecciones de datos nativas de Python (listas, tuplas, conjuntos y diccionarios), nos centraremos a continuación en las colecciones no nativas pertenecientes a bibliotecas externas. La primera de ellas será el array, estructura de datos fundamental de la biblioteca NumPy.

En términos generales, un array es una estructura de datos homogénea que almacena elementos del mismo tipo en un bloque de memoria contiguo, lo que permite un acceso rápido a sus elementos mediante índices.

Aunque Python incluye el módulo nativo array, en la práctica se recurre al array de NumPy. Esta estructura replica el modelo del lenguaje C: datos homogéneos, contiguos en memoria y de tamaño fijo. Esta disposición optimizada permite ejecutar operaciones vectorizadas directamente a nivel de hardware sin necesidad de iterar con bucles, ofreciendo un rendimiento muy superior en el cómputo masivo de datos. Esto último se entiende mejor considerando que, a diferencia de lo que sucede al usar únicamente Python —donde los datos se procesan de uno en uno mediante instrucciones repetitivas (bucles)—, con NumPy los datos se organizan en la memoria de forma tan eficiente que el procesador puede realizar el cálculo sobre millones de elementos de un solo golpe.

Si quieres comparar la diferencia de rendimiento de Python respecto a NumPy, puedes ejecutar este script en tu consola:



import time
import numpy as np

# Datos: 1 millón de enteros
N = 1_000_000

# ==========================================
# 1. MODO NATIVO (Python puro)
# ==========================================
lista_python = list(range(N))

inicio_python = time.time()
# Procesamiento de uno en uno usando un bucle/comprensión de listas
resultado_python = [x ** 2 for x in lista_python]
tiempo_python = time.time() - inicio_python


# ==========================================
# 2. MODO NUMPY (Operación vectorizada)
# ==========================================
array_numpy = np.arange(N)

inicio_numpy = time.time()
# Procesamiento "de un solo golpe" (vectorizado)
resultado_numpy = array_numpy ** 2
tiempo_numpy = time.time() - inicio_numpy


# ==========================================
# RESULTADOS
# ==========================================
print(f"Tiempo Python nativo: {tiempo_python:.4f} segundos")
print(f"Tiempo NumPy:         {tiempo_numpy:.4f} segundos")

velocidad = tiempo_python / tiempo_numpy
print(f"\nNumPy fue aproximadamente {velocidad:.1f} veces más rápido.")


Lo que este script demuestra es que NumPy procesa los arrays de forma mucho más eficiente que Python nativo, pero la verdad es que esa diferencia es de escasa relevancia para el uso que, como orientadores y usuarios de a pie, podemos hacer de esta tecnología. De hecho, el uso directo de NumPy nos aporta muy poco, ya que ni vamos a desarrollar modelos de Inteligencia Artificial ni necesitamos procesar millones de datos en el día a día.

Sin embargo, comprender la existencia del array de NumPy es crucial por una razón de peso: es el motor invisible de todo lo que sí vamos a usar. Aunque raramente escribamos import numpy en nuestros propios scripts, esta biblioteca es la piedra angular sobre la que se construyen las herramientas que sí resultan verdaderamente útiles en nuestro trabajo:

  • Es la base de Pandas, la biblioteca por excelencia para manejar tablas, hojas de cálculo y datos de alumnos, que está construida internamente sobre arrays de NumPy. Gracias a esta arquitectura, Pandas nos permite cruzar listas de asistencia con notas, detectar patrones de riesgo de abandono escolar o baremar baterías de tests psicopedagógicos en un solo clic.
  • Permite la estadística automatizada: cuando calculamos puntuaciones típicas (z, T), percentiles o desviaciones en pruebas diagnósticas, las herramientas de cálculo estadístico que usamos por debajo emplean la precisión y rapidez de NumPy.
  • Facilita la visualización y generación de gráficos para nuestros informes, los cuales se construyen leyendo los datos directamente a través de esta estructura.

En definitiva, el array de NumPy es una pieza invisible para el usuario de a pie: no necesitamos programarlo directamente para sacar provecho de Python, pero saber que existe nos permite entender cómo y por qué nuestro ordenador es capaz de procesar toda la información del centro educativo con tanta solidez, orden y precisión. Por esta razón, en posteriores entradas haremos uso de NumPy como herramienta subyacente en otras tecnologías; concretamente, en Pandas.

martes, 15 de septiembre de 2026

LENGUAJES. Python

Diccionarios complejos (III)

Colecciones de datos

En entradas anteriores analizamos cómo estructurar los datos básicos de un expediente educativo en formato JSON (nombre, curso, nee) y cómo gestionar colecciones sencillas como la lista de medidas_apoyo.

Sin embargo también necesitamos almacenar información más compleja de tipo evolutivo, como la que supone el campo intervenciones, un elemento complejo formado por una lista de diccionarios. En esta entrada nos centraremos en cómo trabajar con este tipo de datos mediante Python.


import json
import os
from collections import Counter

# 1. Definimos la ruta del archivo JSON (puedes ajustarla según tu estructura de carpetas)
RUTA_ARCHIVO = "D:/Tu_Ubicacion/archivo_JSON/exped_orienta.JSON"

def cargar_datos_expediente(ruta):
    """Carga y devuelve el contenido completo del archivo JSON."""
    if not os.path.exists(ruta):
        print(f" Error: El archivo no existe en la ruta: '{ruta}'")
        return None

    try:
        with open(ruta, "r", encoding="utf-8") as f:
            return json.load(f)
    except json.JSONDecodeError:
        print(f" Error: El archivo '{ruta}' no tiene un formato JSON válido.")
        return None
    except Exception as e:
        print(f" Error inesperado al leer el archivo: {e}")
        return None


def mostrar_datos (datos_totales):
    """
    Itera sobre todos los registros, muestra las intervenciones,
    el total de actuaciones por alumno y un resumen estadístico global.
    """
    if not datos_totales:
        print("No hay datos para mostrar.")
        return

    total_registros = len(datos_totales)
    sumatorio_actuaciones = 0
    conteo_actuaciones_por_alumno = []

    print("=" * 65)
    print(f" REPORTES DE INTERVENCIONES - TOTAL REGISTROS: {total_registros}")
    print("=" * 65)

    # Iteramos sobre cada alumno en el diccionario principal
    for id_alumno, expediente in datos_totales.items():
        intervenciones = expediente.get("intervenciones", [])
        num_actuaciones = len(intervenciones)

        # Actualizamos métricas globales
        sumatorio_actuaciones += num_actuaciones
        conteo_actuaciones_por_alumno.append(num_actuaciones)

        print(f"\n>>> EXPEDIENTE: {id_alumno}")
        print("-" * 45)

        if not intervenciones:
            print("  (Sin intervenciones registradas)")
        else:
            for idx, sesion in enumerate(intervenciones, 1):
                fecha = sesion.get("fecha", "Sin fecha")
                motivo = sesion.get("motivo", "Sin especificar")
                acuerdos = sesion.get("acuerdos", "Sin acuerdos registrados")

                print(f"  [{idx}] Fecha:   {fecha}")
                print(f"      Motivo:  {motivo}")
                print(f"      Acuerdos: {acuerdos}\n")

        # Muestra del total al final de cada expediente
        print(f"  --> Total de actuaciones en {id_alumno}: {num_actuaciones}")

    # ---------------------------------------------------------
    # RESUMEN ESTADÍSTICO FINAL
    # ---------------------------------------------------------
    print("\n" + "=" * 65)
    print(" RESUMEN ESTADÍSTICO DE ACTUACIONES")
    print("=" * 65)
    print(f" • Total de alumnos/registros analizados: {total_registros}")
    print(f" • Sumatorio total de actuaciones:        {sumatorio_actuaciones}")
    print("\n Distribución de registros según número de actuaciones:")

    # Contamos la frecuencia de cada cantidad de actuaciones
    distribucion = Counter(conteo_actuaciones_por_alumno)

    # Ordenamos por número de actuaciones de menor a mayor
    for num_act, cant_alumnos in sorted(distribucion.items()):
        porcentaje = (cant_alumnos / total_registros) * 100
        print(
            f"   - Registros con {num_act} actuaciones: "
            f"{cant_alumnos} alumno(s) ({porcentaje:.1f}%)"
        )

    print("=" * 65)


# --- EJECUCIÓN DEL SCRIPT ---
if __name__ == "__main__":
    datos_json = cargar_datos_expediente(RUTA_ARCHIVO)
    mostrar_datos (datos_json)


Con este primer script cargamos un archivo JSON que contiene los datos desde una ubicación que será distintan en cada caso y que en este script de identifica como RUTA_ARCHIVO = "D:/Tu_Ubicacion/archivo_JSON/exped_orienta.JSON" siendo exped_orienta.JSON el nombre del archivo (datos inventados) que puedes descargar desde este enlace. Así sólo tienes que ajustar la ruta en el script.

La primera función del script def cargar_datos_expediente(ruta): se encarga de cargar el archivo, controlando los posibles errores que se pudieran cometer, por lo que se puede simplificar una vez que, como programadores de nuestro script y usuarios, ya tengamos asegurada la carga del documento JSON. En ese caso la función podría quedar simplificada como sigue:



def cargar_datos_expediente(ruta):
    """Carga y devuelve el contenido completo del archivo JSON."""
    with open(ruta, "r", encoding="utf-8") as f:
        return json.load(f)


El siguiente paso consiste en acceder a los datos y mostrarlos por pantalla. Para ello implementamos en el script la segunda función def mostrar_datos (datos_totales):, que además de mostrar el listado de registros con las actuaciones de cada uno de ellos, también realiza un recuento de cuantas actuaciones tiene cada registro y finaliza con una pequeña estadística que se muestra al final del listado. Por este motivo esta función se vuelve especialmente extensa y compleja, requiendo una explicación de cada una de sus partes.

Además de controlar la existencia de datos en el archivo JSON, en el inicio de la función se establecen unas variables que nos servirán para realizar los recuentos solicitados...



if not datos_totales:
        print("No hay datos para mostrar.")
        return

    total_registros = len(datos_totales)
    sumatorio_actuaciones = 0
    conteo_actuaciones_por_alumno = []


... y después se define el formato de la cabecera del informe que se imprimirá en su momento en el CMD



print("=" * 65)
    print(f" REPORTES DE INTERVENCIONES - TOTAL REGISTROS: {total_registros}")
    print("=" * 65)


Pero es a partir de este punto que empezamos a construir la estructura del contenido de ese informe, recorriendo el contenido del archivo...



# Iteramos sobre cada alumno en el diccionario principal
    for id_alumno, expediente in datos_totales.items():
        intervenciones = expediente.get("intervenciones", [])
        num_actuaciones = len(intervenciones)

        # Actualizamos métricas globales
        sumatorio_actuaciones += num_actuaciones
        conteo_actuaciones_por_alumno.append(num_actuaciones)


... y accediendo a los datos del diccionario anidado en el campo...



print(f"\n>>> EXPEDIENTE: {id_alumno}")
        print("-" * 45)

        if not intervenciones:
            print("  (Sin intervenciones registradas)")
        else:
            for idx, sesion in enumerate(intervenciones, 1):
                fecha = sesion.get("fecha", "Sin fecha")
                motivo = sesion.get("motivo", "Sin especificar")
                acuerdos = sesion.get("acuerdos", "Sin acuerdos registrados")

                print(f"  [{idx}] Fecha:   {fecha}")
                print(f"      Motivo:  {motivo}")
                print(f"      Acuerdos: {acuerdos}\n")

        # Muestra del total al final de cada expediente
        print(f"  --> Total de actuaciones en {id_alumno}: {num_actuaciones}")


El cuarto bloque de esta función está dedicada a la composición del conteo de datos con el que finaliza el informe que se muestra por pantalla, por lo que se puede omitir si no se desea esta información.



  # ---------------------------------------------------------
    # RESUMEN ESTADÍSTICO FINAL
    # ---------------------------------------------------------
    print("\n" + "=" * 65)
    print(" RESUMEN ESTADÍSTICO DE ACTUACIONES")
    print("=" * 65)
    print(f" • Total de alumnos/registros analizados: {total_registros}")
    print(f" • Sumatorio total de actuaciones:        {sumatorio_actuaciones}")
    print("\n Distribución de registros según número de actuaciones:")

    # Contamos la frecuencia de cada cantidad de actuaciones
    distribucion = Counter(conteo_actuaciones_por_alumno)

    # Ordenamos por número de actuaciones de menor a mayor
    for num_act, cant_alumnos in sorted(distribucion.items()):
        porcentaje = (cant_alumnos / total_registros) * 100
        print(
            f"   - Registros con {num_act} actuaciones: "
            f"{cant_alumnos} alumno(s) ({porcentaje:.1f}%)"
        )

    print("=" * 65)


Además de mostrar los registros (las actuaciones) por pantalla, también podemos realizar modificaciones sobre esos contenidos. Una de ellas (posiblemente la de mayor interés en la práctica) es la de añadir una nueva actuación dentro del registro de un determinado alumno. Para ello añadiremos este script al anterior.



# =============================================================================
# FASE 2: EDICIÓN E INCREMENTO DE INTERVENCIONES
# =============================================================================

def seleccionar_alumno(datos_totales):
    """
    Presenta los alumnos con un índice numérico para seleccionar uno.
    """
    print("\n" + "=" * 60)
    print(" SELECCIÓN DE ALUMNO / REGISTRO PARA AÑADIR INTERVENCIÓN")
    print("=" * 60)

    lista_ids = list(datos_totales.keys())

    for idx, id_alumno in enumerate(lista_ids, 1):
        nombre = datos_totales[id_alumno].get("nombre", "Sin nombre")
        num_intervenciones = len(datos_totales[id_alumno].get("intervenciones", []))
        print(f"  [{idx}] ID: {id_alumno} | Nombre: {nombre} ({num_intervenciones} intervenciones)")

    print("-" * 60)

    while True:
        opcion = input(f"Seleccione un alumno (1-{len(lista_ids)}): ").strip()
        if opcion.isdigit() and 1 <= int(opcion) <= len(lista_ids):
            return lista_ids[int(opcion) - 1]
        print(" Opción no válida. Introduzca un número de la lista.")


def capturar_nueva_intervencion():
    """
    Captura por consola los campos del nuevo diccionario anidado.
    """
    print("\n--- NUEVA ACTUACIÓN ---")
    fecha = input("• Fecha (AAAA-MM-DD): ").strip()
    motivo = input("• Motivo de la intervención: ").strip()
    acuerdos = input("• Acuerdos alcanzados: ").strip()

    return {
        "fecha": fecha if fecha else "Sin fecha",
        "motivo": motivo if motivo else "Sin especificar",
        "acuerdos": acuerdos if acuerdos else "Sin acuerdos registrados"
    }

def agregar_intervencion_interactiva(datos_totales):
    """
    Permite agregar de forma iterativa intervenciones en el mismo registro
    o cambiar a otros expedientes.
    """
    while True:
        id_alumno = seleccionar_alumno(datos_totales)
        expediente = datos_totales[id_alumno]

        if "intervenciones" not in expediente:
            expediente["intervenciones"] = []

        # Bucle para añadir varias intervenciones al mismo alumno
        while True:
            print(f"\nAñadiendo actuación para: {id_alumno} ({expediente.get('nombre', '')})")
            
            nueva_actuacion = capturar_nueva_intervencion()
            expediente["intervenciones"].append(nueva_actuacion)
            
            print(f"\n [OK] Actuación añadida. Total actual en {id_alumno}: {len(expediente['intervenciones'])}")

            mas_mismo = input("\n¿Desea añadir OTRA intervención a ESTE MISMO alumno? (s/n): ").strip().lower()
            if mas_mismo != "s":
                break

        mas_otros = input("\n¿Desea seleccionar OTRO alumno para añadir intervenciones? (s/n): ").strip().lower()
        if mas_otros != "s":
            print("\n Finalizada la sesión de altas.")
            break


Con él creamos un listado de alumnos para identificar aquel en el que queremos añadir una nueva actuación def seleccionar_alumno(datos_totales): y después añadimos los datos de esa actuación def agregar_intervencion_interactiva(datos_totales):, previa identificación de sus campos def capturar_nueva_intervencion():

Además podemos añadir más de una actuación respecto al mismo alumno, o añadir actuaciones realizadas con otro alumno def agregar_intervencion_interactiva(datos_totales):

Hasta el momento sólo hemos consultado los registros de actuaciones, identificado a un alumno en concreto e incrementado las actuaciones que se han realizado respecto a ese alumno, pero si dejamos así el script perderemos los cambios introducidos. Para que esto no suceda deberemos implementar otro segmento de código a nuestro script que se encargue de guardar en la dirección que corresponda un nuevo archivo JSON.



# =============================================================================
# FASE 3: PERSISTENCIA Y ACTUALIZACIÓN EN DISCO
# =============================================================================

def guardar_datos_actualizados(datos_totales, ruta=RUTA_ARCHIVO):
    """
    Sobrescribe el archivo JSON original con la estructura de datos
    actualizada en memoria, aplicando formato indentado.
    """
    print("\n" + "=" * 60)
    print(" GUARDANDO CAMBIOS EN EL ARCHIVO JSON")
    print("=" * 60)

    # Sobrescribimos el archivo con la información acumulada en memoria
    with open(ruta, "w", encoding="utf-8") as f:
        json.dump(datos_totales, f, ensure_ascii=False, indent=4)

    print(f" [OK] El archivo '{ruta}' se ha actualizado correctamente.")
    

En este segmento sólo se identifica una función def guardar_datos_actualizados(datos_totales, ruta=RUTA_ARCHIVO): cuyo nombre es suficientemente expresivo y que no requiere más explicación.

Lo que sí es necesario (ahora) es modificar el segmento inicial de la función if __name__ == "__main__": para incluir las nuevas funcinalidades. El resultado es el que sigue. Corre de tu cuenta actualizarla en el script final que construyas con los segmentos que te he ido proporcionando.



# =============================================================================
# BLOQUE PRINCIPAL DE EJECUCIÓN
# =============================================================================

if __name__ == "__main__":

    # 1. Cargar archivo JSON
    datos_json = cargar_datos_expediente(RUTA_ARCHIVO)

    # 2. Mostrar datos iniciales y resumen estadístico
    mostrar_datos(datos_json)

    # 3. Iniciar proceso interactivo de alta de intervenciones
    agregar_intervencion_interactiva(datos_json)

    # 4. Guardar los cambios acumulados en el archivo JSON
    guardar_datos_actualizados(datos_json, RUTA_ARCHIVO)


Podríamos implementar más utilidades dentro de este script, pero para el objetivo de la entrada del blog creo que tenemos más que suficiente. Puede que lo qua haga falta sea crear un procedimiento de trabajo que incluya la creación, actualización, manejo y archivo de un documentoJSON a modo de base de datos. En ese caso habría que identificar el objetivo y la estructura de contenidos. Un sistema de seguimiento de actuaciones como el que hemos simulado podría ser un buen tema de trabajo.

viernes, 14 de agosto de 2026

IA. Modelos

Introducción

IA. Modelos y paradigmas

Cuando hablamos de Inteligencia Artificial es común utilizar expresiones como IA basada en reglas, IA estadística, Machine Learning o Deep Learning como si fueran etapas delimitadas de una evolución tecnológica. Sin embargo, la relación entre estos conceptos es más compleja.

Desde una perspectiva histórica, la IA ha recorrido un camino que va desde sistemas cuyo comportamiento se especificaba mediante reglas hasta sistemas capaces de aprender modelos a partir de los datos. Y, posteriormente, hacia sistemas capaces de aprender también representaciones cada vez más complejas de esos datos.

La diferencia fundamental entre estos enfoques está en cómo se consigue que el sistema sepa qué debe hacer. En una IA basada en reglas, ese conocimiento se incorpora directamente al sistema mediante las reglas que el programador establece. En Machine Learning, en cambio, el sistema aprende un modelo a partir de los datos mediante un algoritmo de aprendizaje.

La historia de la IA puede contemplarse, en buena medida, como la historia de diferentes paradigmas desarrollados para abordar problemas similares mediante estrategias diferentes: desde la especificación explícita del conocimiento mediante reglas hasta la construcción de modelos a partir de los datos. Dentro de ese campo de la IA han coexistido y siguen coexistiendo diferentes paradigmas, entendiendo por paradigma una forma general de plantear cómo abordar un determinado tipo de problema y cómo construir el procedimiento necesario para resolverlo.

La diferencia entre estos paradigmas no reside necesariamente en los problemas que pretenden resolver, sino en la forma en que plantean su resolución y, especialmente, en cómo incorporan el conocimiento necesario para obtener un resultado.

Veamos a continuación cuáles son esos paradigmas y cuales sus características

El paradigma heurístico o cómo programar el conocimiento. Las primeras aproximaciones a la IA estuvieron fuertemente vinculadas a la IA simbólica y a los sistemas basados en reglas. La idea fundamental era relativamente directa: si somos capaces de representar explícitamente el conocimiento necesario y establecer las reglas de razonamiento correspondientes, podemos diseñar un algoritmo que aplique esas reglas para obtener una respuesta.

Un ejemplo elemental sería: SI A implica B ENTONCES C.. En este paradigma, el experto cuyo conocimiento se pretende representar, determina las reglas que utilizará el sistema. lo que prima son las reglas que se establecen a priori y los datos son aquello sobre lo que éstas se aplican. Por ello, el proceso se puede representar esquemáticamente así:

  • Reglas + datos → inferencia → resultado

Esta concepción dio lugar a los sistemas expertos, que tuvieron un importante desarrollo especialmente durante las décadas de 1970 y 1980. Su fortaleza era precisamente aquello que posteriormente se convertiría en una de sus limitaciones: el conocimiento podía representarse de forma explícita y, en determinadas condiciones, resultar interpretable. Sin embargo, el mundo real difícilmente puede reducirse a un conjunto completo de reglas.

¿Qué ocurre cuando existen miles de situaciones diferentes? ¿Qué sucede cuando las reglas son demasiado numerosas? ¿Y cuando ni siquiera sabemos formular explícitamente las reglas que utiliza una persona para reconocer una imagen, interpretar un texto o distinguir un patrón?

Aquí aparece el problema fundamental: podemos saber qué resultado queremos obtener sin saber formular explícitamente todas las reglas necesarias para obtenerlo. Una alternativa consiste entonces en analizar los datos para identificar en ellos regularidades, relaciones o patrones que puedan utilizarse para obtener el resultado buscado sin intentar especificar de antemano todas las reglas. Este planteamiento está en la base del enfoque estadístico de la IA.

La estadística ofrece una forma de abordar determinados problemas que es alternativa a la heurística. En lugar de intentar especificar todas las reglas, observamos los datos para buscar regularidades, relaciones y distribuciones que permitan describirlos o realizar inferencias. Por ello el esquema cambia: Datos → análisis estadístico → regularidades/inferencia → resultado

Esto introduce una transformación importante, ya que el conocimiento necesario para resolver el problema no tiene por qué estar completamente expresado mediante reglas diseñadas previamente por el programador. Puede inferirse matemáticamente a partir de los propios datos.

Dentro de este paradigma encontramos técnicas como la regresión, determinados métodos probabilísticos y otros procedimientos matemáticos que serán utilizados posteriormente dentro de sistemas de aprendizaje automático. Pero es necesario evitar identificar ambas, ya que estadística ≠ Machine Learning.. Un análisis estadístico puede limitarse a describir una población, estimar parámetros o estudiar relaciones entre variables. El Machine Learning añade una finalidad específica: construir mediante un procedimiento de aprendizaje un modelo capaz de realizar una tarea sobre datos, incluidos datos que no se utilizaron durante su entrenamiento. Podemos decir que la estadística constituye uno de los fundamentos de ML, pero no todo procedimiento estadístico es Machine Learning.

Con el Machine Learning (ML) se consolida una nueva forma de plantear muchos problemas de IA. Su característica diferencial no consiste en utilizar matemáticas o estadísticas; consiste en que el sistema aprende un modelo a partir de datos mediante un algoritmo de aprendizaje. La representación del proceso general sería: Datos → algoritmo de aprendizaje → modelo → nuevos datos → resultado

En el ML se distinguen tres elementos:

  • El algoritmo de aprendizaje establece cómo se aprende.
  • Los datos de entrenamiento proporcionan la información a partir de la cual se aprende.
  • Y el modelo es el resultado de ese proceso: una estructura matemática cuyos parámetros han sido ajustados para realizar una determinada tarea.

Lo que cambia respecto a la IA heurística es que el comportamiento concreto del modelo deja de estar completamente especificado de antemano mediante reglas explícitas. Mientras que en la IA basada en reglas, el conocimiento que determina el comportamiento del sistema se incorpora explícitamente a éste, en la IA Machine Learning este conocimiento se obtiene mediante un proceso de aprendizaje a partir de los datos.

Dentro del ML diferenciamos entre el llamado aprendizaje supervisado y el aprendizaje no supervisado.

  • En el aprendizaje supervisado disponemos de ejemplos de los que conocemos el resultado esperado (entrada → resultado conocido) y el algoritmo aprende la relación entre ambos.
  • En el aprendizane no supervisado desconocemos el resultado o la respuesta correcta de cada observación y el objetivo consiste en encontrar estructuras o agrupaciones presentes en los propios datos. Así funcionan, por ejemplo, algoritmos de clustering como K-means. Su esquema de funcionamiento sería el siguiente: datos → algoritmo → estructura descubierta

Finalmente, la IA Deep Learning (DL) es el Machine Learning llevado a otro nivel. No constituye un paradigma independiente situado fuera del Machine Learning. Es fundamentalmente una familia de métodos de ML basada en redes neuronales profundas.

La importancia histórica del Deep Learning procede, entre otras cosas, de su capacidad para aprender automáticamente representaciones de gran complejidad a partir de grandes cantidades de datos.

Mientras que en los sistemas tradicionales de ML era frecuente que una parte importante de la representación de los datos tuviera que ser diseñada previamente, en el Deep Learning es la propia red la que puede aprender progresivamente representaciones útiles para la tarea. Esto resulta especialmente importante en los siguientes campos:

  • reconocimiento de imágenes
  • reconocimiento de voz
  • procesamiento del lenguaje
  • traducción automática
  • generación de texto
  • generación de imágenes

La evolución conceptual puede expresarse entonces como una progresiva transferencia de decisiones desde el diseñador hacia el proceso de aprendizaje: Reglas explícitas → modelos aprendidos → representaciones aprendidas. Pero no debemos interpretar estas secuencia como una sucesión de paradigmas, ya que podemos constatar que los sistemas basados en reglas siguen siendo útiles cuando el conocimiento puede expresarse de manera explícita y necesitamos comportamientos deterministas o fácilmente verificables. Los métodos estadísticos continúan siendo fundamentales y el Machine Learning incorpora técnicas estadísticas, matemáticas y computacionales muy diversas. Por su parte, el Deep Learning convive con otros métodos de ML que no utilizan redes neuronales profundas.

Dicho de otro modo, no estamos ante una sucesión de tecnologías que se reemplazan unas a otras. Estamos ante diferentes formas de resolver problemas, algunas de las cuales se han mostrado especialmente adecuadas para determinados tipos de problemas y determinadas condiciones tecnológicas.

viernes, 7 de agosto de 2026

LENGUAJES. Python

Diccionarios complejos (II)

Colecciones de datos

En un diccionario complejo el value (par clave:valor) de un posible "campo" puede estar formado por una lista de datos ([]), de modo que el tratamiento de esos datos haga encesario implementar estrategias específicas. Sin pretender ser exhaustivos, ese tratamiento incluye operaciones como conteo, visualización y consulta (de toda la lista o de un elemento), añadido, supresión o modificación... En esta entrada estudiaremos cómo realizar estas operaciones.

Ya que por algún punto hay que empezar, nos situaremos en el siguiente contexto: hemos definido la estructura de diccionario...



 "ALU-cod-n": {
        "nombre": "al_nombre",
        "curso": "al_curso",
        "nee": true,
        "medidas_apoyo": [
            "mad_1"
        ],
        "intervenciones": [
            {
                "fecha": "int_aa_mm_dd",
                "motivo": "int_mot",
                "acuerdos": "int_acuerdos"
            }
        ]
    },


... ya sabemos cómo añadir elementos a los "campos" simples nombre - curso - nee...



import json
import os

def solicitar_booleano(mensaje):
    """
    Solicita una respuesta booleana al usuario garantizando que la entrada sea s/n.
    """
    while True:
        respuesta = input(f"{mensaje} (s/n): ").strip().lower()
        if respuesta in ['s', 'si', 'sí']:
            return True
        elif respuesta in ['n', 'no']:
            return False
        print(" [!] Entrada no válida. Por favor, introduzca 's' para Sí o 'n' para No.")

def capturar_ficha(codigo_num):
    cod_alumno = f"ALU-cod-{codigo_num}"
    print(f"\n==========================================")
    print(f"  NUEVA FICHA DE EXPEDIENTE: {cod_alumno}")
    print(f"==========================================")
    
    print("\n--- AVISO DE FORMATO DE ENTRADA ---")
    print("  * Nombre del alumno: Apellidos, Nombre (ejemplo: García López, María)")
    print("  * Curso: Etapa y grupo (ejemplo: 2º ESO B  o  1º Bachillerato A)")
    print("-------------------------------------\n")
    
    nombre = input("Nombre completo del alumno (ej: García López, María): ").strip()
    curso = input("Curso y grupo (ej: 2º ESO B): ").strip()
    nee = solicitar_booleano("¿Presenta Necesidades Educativas Especiales (NEE)?")
    

... pero todavía no sabemos cómo introducir datos en el "campo" "medidas_apoyo": [] cuyo value es una lista, como podemos identificar por el uso corchetes [] como delimitadores. El script que sigue cubre esta necesidad, aunque tiene una funcionalidad limitada.



import json
import os


def solicitar_booleano(mensaje):
    """Solicita una respuesta booleana al usuario garantizando que la entrada sea s/n."""
    while True:
        respuesta = input(f"{mensaje} (s/n): ").strip().lower()
        if respuesta in ["s", "si", "sí"]:
            return True
        elif respuesta in ["n", "no"]:
            return False
        print(
            " [!] Entrada no válida. Por favor, introduzca 's' para Sí o 'n' para No."
        )


# --- FUNCIÓN Cumplimentar datos de medidas_apoyo ---------------------------------------

def capturar_medidas_apoyo():
    """Captura de forma interactiva la lista de medidas de apoyo."""
    medidas = []
    quiere_anadir = solicitar_booleano(
        "¿Desea registrar alguna medida de apoyo?"
    )

    contador = 1
    while quiere_anadir:
        medida = input(f" -> Ingrese la medida de apoyo #{contador}: ").strip()
        if medida:
            medidas.append(medida)
            contador += 1
        quiere_anadir = solicitar_booleano("¿Desea añadir otra medida de apoyo?")

    return medidas


# --- FUNCIÓN PRINCIPAL de ejecución. -----------------------------------------------

def capturar_ficha(codigo_num):
    cod_alumno = f"ALU-cod-{codigo_num}"
    print(f"\n==========================================")
    print(f"  NUEVA FICHA DE EXPEDIENTE: {cod_alumno}")
    print(f"==========================================")

    print("\n--- AVISO DE FORMATO DE ENTRADA ---")
    print(
        "  * Nombre del alumno: Apellidos, Nombre (ejemplo: García López, María)"
    )
    print(
        "  * Curso: Etapa y grupo (ejemplo: 2º ESO B  o  1º Bachillerato A)"
    )
    print("-------------------------------------\n")

    nombre = input("Nombre completo del alumno (ej: García López, María): ").strip()
    curso = input("Curso y grupo (ej: 2º ESO B): ").strip()
    nee = solicitar_booleano(
        "¿Presenta Necesidades Educativas Especiales (NEE)?"
    )

    # 1. Llamamos a la nueva utilidad para generar la lista
    medidas_apoyo = capturar_medidas_apoyo()

    # 2. Retornamos el diccionario completo con el nuevo campo
    registro = {
        "codigo_alumno": cod_alumno,
        "nombre": nombre,
        "curso": curso,
        "nee": nee,
        "medidas_apoyo": medidas_apoyo,
    }

    return registro

# ==============================================================================
# BLOQUE DE EJECUCIÓN PRINCIPAL
# ==============================================================================
if __name__ == "__main__":
    # Solicitamos o asignamos el número identificador para el código de alumno
    num_expediente = 1

    # Llamada a la función principal para capturar los datos interactivos
    expediente_alumno = capturar_ficha(num_expediente)

    # Mostramos el resultado por pantalla con formato JSON legible
    print("\n==========================================")
    print("      REGISTRO GENERADO CON ÉXITO")
    print("==========================================")
    print(json.dumps(expediente_alumno, indent=4, ensure_ascii=False))


Este script complementa el de creación de registro añadiendo, a partir de def capturar_medidas_apoyo(): le procedimiento para obtener los primeros posibles datos de medidas_apoyo. Esta función es llamada desde la función principal def capturar_ficha(codigo_num): y se basa, en lo fundamental, en el bucle while quiere_anadir: que permite al usuario introducir cuantas medidas de apoyo desee.

Resuelto el procedimiento de creación de datos, nos situamos ahora en el segundo contexto: ya disponemos de un archivo JSON que contiene datos suficientes como para que sea pertinente realizar diferentes operaciones con la información de medidas_apoyo de los distintos registros-diccionarios.

En este contexto empezamos por acceder al archivo JSON para trabajar con él desde el script (en memoria) como diccionario complejo. Este procedimiento ya nos es conocido.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)
    

De las entradas anteriores sabemos crear el listado las claves principales, acceder al contenido de un archivo concreto o a todos ellos. También sabemos manipular el contenido de deteminados campos. Ahora nos detendremos en el acceso y las posibles modificaciones del contenido del campo cuyo value es una lista. Empezaremos por obtener el listado completo de registros mostrando cuántas y cuales son las medidas educativas en cada uno de ellos.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

try:
    # 1. Cargar el contenido del archivo JSON en memoria
    with open(ruta_archivo, "r", encoding="utf-8") as archivo:
        expedientes = json.load(archivo)

    print("\n==================================================")
    print("    LISTADO COMPLETO DE EXPEDIENTES Y MEDIDAS     ")
    print("==================================================\n")

    # Adaptación dinámica según si el JSON es una lista [...] o un diccionario {...}
    registros = (
        expedientes.items()
        if isinstance(expedientes, dict)
        else enumerate(expedientes, 1)
    )

    for id_registro, datos in registros:
        # Obtenemos la clave principal (clave del dict o campo 'codigo_alumno')
        if isinstance(datos, dict):
            clave_principal = datos.get("codigo_alumno", f"Registro #{id_registro}")
            medidas = datos.get("medidas_apoyo", [])
        else:
            clave_principal = str(id_registro)
            medidas = datos if isinstance(datos, list) else []

        # Contamos la cantidad total de elementos en la lista
        total_medidas = len(medidas)

        # Imprimimos la salida formateada en CMD
        print(f"📌 Clave Principal: {clave_principal}")
        print(f"   Total de medidas educativas aplicadas: {total_medidas}")

        if total_medidas > 0:
            print("   Listado de medidas:")
            for i, medida in enumerate(medidas, 1):
                print(f"      {i}. {medida}")
        else:
            print("   Listado de medidas: (Sin medidas registradas)")

        print("-" * 50)

except FileNotFoundError:
    print(f" [!] Error: No se encontró el archivo en la ruta '{ruta_archivo}'.")
except json.JSONDecodeError:
    print(
        " [!] Error: El archivo JSON no tiene un formato válido o está corrupto."
    )


Podríamos dirigir nuestra atención al análisis del contenido de este "campo", pero dejaremos estos procedimientos para una tercera fase del estudio de los diccionarios y nos atendremos a explicar los procedimientos de manejo y modificación del value lista, si bien no es muy diferente del del que ya estudiamos en el tratamiento de estas colecciones de datos.

Al tratarse de un diccionario ya desarrollado en cuanto a su contenido, lo primero que deberemos hacer es identificar y acceder al registro de nuestro interés. Para ello debemos usar la clave principal /(vg. "ALU-2024-01") y, a partir de ahí a la clave "medidas_apoyo".

Disponemos de dos procedimientos para acceder a los datos de la lista. Bien directamente identificando al alumno por su clave:



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)

# 2. Identificamos directamente al alumno por su clave

id_alumno = "ALU-2024-01"

# 3. Verificar existencia y mostrar las medidas elemento por elemento
if id_alumno in expedientes:
    print(f"Medidas de apoyo para {id_alumno}:")
    for medida in expedientes[id_alumno]["medidas_apoyo"]:
        print(f"- {medida}")



... o bien hacerlo de forma interactiva introducciendo por input() el identificador de su clave, suponiendo que tenemos acceso a un listado de claves y nombres que nos facilitan la identificación del sujeto deseado.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)
    
# 2. Listado de claves-sujetos

listado = [f"{clave} - {datos['nombre']}" for clave, datos in expedientes.items()]
for al in listado:
    print(al)

# 3. Solicitar la clave directamente al usuario
clave = input("Introduce la clave del expediente: ")

# 4. Acceder al valor del campo 'medidas_apoyo' del registro indicado
medidas = expedientes[clave]["medidas_apoyo"]

# 5. Mostrar el contenido actual
print("Medidas de apoyo actuales:", medidas)


Una vez que accedemos al listado de medidas, podemos limitarnos a mostrarlo, pero también podemos actuar sobre él. Veamos algunas acciones posibles. Se escriben a continuación del script anterior).



print("\n--- ACCIONES SOBRE LAS MEDIDAS ---")

# (1) Conocer el número de medidas
print(f"\n4. Número total de medidas registradas: {len(medidas)}")

# (2) Añadir una nueva medida
nueva = input("1. Introduce la nueva medida a añadir: ").strip()
medidas.append(nueva)
print("   Lista actualizada:", medidas)

# (3) Borrar una medida
a_borrar = input("\n2. Introduce la medida a eliminar: ").strip()
medidas.remove(a_borrar)
print("   Lista tras borrar:", medidas)

# (4) Ordenar alfabéticamente las medidas
medidas.sort()
print("\n3. Medidas ordenadas alfabéticamente:", medidas)

# (5) Saber si existe una determinada medida
buscar = input("\n5. Consulta si existe una medida concreta: ").strip()
print(f"   -> ¿Existe '{buscar}' en la lista?: {buscar in medidas}")


Lo suyo sería actualizar el archivo JSON, pero por ahora no es necesario ya que sólo estamos aprendiendo a trabajar con los componentes del diccionario.

martes, 4 de agosto de 2026

LENGUAJES. Python

Diccionarios complejos (I)

Colecciones de datos

En las dos entradas previas en las que hemos trabajado con diccionarios hemos empleado de forma intencional formulaciones simples de esta colección de datos. Se trataba de comprender en qué consistía y cómo se podía trabajar con ella, así que la simplicidad se imponía como necesidad. Pero la realidad es que un diccionario "normal" no suele ser tan sencillo ni su virtud o interés requiere de que los sea.

En esta entrada idearemos una estructura compleja (y más realista, incluso) de un diccionario y trabajaremos con ella. Se trata de comprobar la realidad y la utilidad real de un diccionario. Para ello vamos a plantearlo como colección de datos potencialmente aplicable a un proyecto real de trabajo (como SEO) aunque ajustado a las necesidades didácticas del momento.

Imaginemos que sobre una estructura de diccionario queremos construir un recurso de seguimiento del alumnado. Se trata de una forma de expediente, muy parecido a lo que se podría dar soporte con una base de datos, pero también registrar y mantener como un documento JSON. Algo como esto:



 "ALU-cod-n": {
        "nombre": "al_nombre",
        "curso": "al_curso",
        "nee": true,
        "medidas_apoyo": [
            "mad_1"
        ],
        "intervenciones": [
            {
                "fecha": "int_aa_mm_dd",
                "motivo": "int_mot",
                "acuerdos": "int_acuerdos"
            }
        ]
    },


Muestro aquí la estructura de un elemento del documento CSV sin contenidos ni reales ni supuestos (eso viene después) para que podemos observar en qué consiste la complejidad de la que estamos hablando cuando lo trasladamos a diccionario (ya sabemos que estructura y contenido se mantienen): ahora cada elemento del diccionario - aquí "ALU-cod-n": - es la clave de un value que es a su vez un diccionario, de ahí que el conjunto se pueda considerar como un diccionario de diccionarios.

Ese value además de ser un diccionario (de ahí que todo él se encuentre delimitado por {}, es un diccionario complejo ya que sus diferentes elementos-campos lo son de diferente tipología:

  • Tenemos contenidos-campos clave:valor simples (y de distinto tipo de datos), como "nombre": "al_nombre"
  • Pero también un contenido-campo cuyo value es una lista "medidas_apoyo": ["mad_1"],
  • Y más aun, un contenido-campo cuyo value es una lista de diccionarios (de la que ahora sólo muestro su primer componente clave:valor por simplificar: "intervenciones": [{"fecha": "int_aa_mm_dd",}]

Sobra decir que debermos conocer cómo tratar a cada subestructura o tipo de componente, tanto como con el conjunto. Empezaremos con ello a continuación.



import json
import os

def solicitar_booleano(mensaje):
    """
    Solicita una respuesta booleana al usuario garantizando que la entrada sea s/n.
    """
    while True:
        respuesta = input(f"{mensaje} (s/n): ").strip().lower()
        if respuesta in ['s', 'si', 'sí']:
            return True
        elif respuesta in ['n', 'no']:
            return False
        print(" [!] Entrada no válida. Por favor, introduzca 's' para Sí o 'n' para No.")

def capturar_ficha(codigo_num):
    cod_alumno = f"ALU-cod-{codigo_num}"
    print(f"\n==========================================")
    print(f"  NUEVA FICHA DE EXPEDIENTE: {cod_alumno}")
    print(f"==========================================")
    
    print("\n--- AVISO DE FORMATO DE ENTRADA ---")
    print("  * Nombre del alumno: Apellidos, Nombre (ejemplo: García López, María)")
    print("  * Curso: Etapa y grupo (ejemplo: 2º ESO B  o  1º Bachillerato A)")
    print("-------------------------------------\n")
    
    nombre = input("Nombre completo del alumno (ej: García López, María): ").strip()
    curso = input("Curso y grupo (ej: 2º ESO B): ").strip()
    nee = solicitar_booleano("¿Presenta Necesidades Educativas Especiales (NEE)?")
    
    # Se mantienen las estructuras de listas para su posterior implementación
    return cod_alumno, {
        "nombre": nombre,
        "curso": curso,
        "nee": nee,
        "medidas_apoyo": [],
        "intervenciones": []
    }

def guardar_en_json(nuevos_expedientes, ruta_salida):
    # Limpieza de comillas iniciales o finales introducidas por el usuario
    ruta_salida = ruta_salida.strip('\"\'')
    ruta_salida = os.path.normpath(ruta_salida)
    
    # 1. Comprobar si el directorio existe y crearlo si no
    directorio = os.path.dirname(ruta_salida)
    if directorio and not os.path.exists(directorio):
        os.makedirs(directorio, exist_ok=True)
        print(f" [+] Directorio creado: {os.path.abspath(directorio)}")
        
    datos_acumulados = {}
    
    # 2. Comprobar si el archivo JSON existe para cargar su contenido previo
    if os.path.exists(ruta_salida):
        try:
            with open(ruta_salida, mode="r", encoding="utf-8") as archivo:
                contenido = archivo.read().strip()
                if contenido:
                    datos_acumulados = json.loads(contenido)
            print(f" [+] Archivo existente detectado. Se añadirán los nuevos registros a los {len(datos_acumulados)} existentes.")
        except Exception as e:
            print(f" [!] Advertencia: No se pudo leer el archivo existente ({e}). Se iniciará un JSON nuevo.")
            datos_acumulados = {}
    else:
        print(" [+] El archivo no existe. Se creará un nuevo archivo JSON.")

    # 3. Anexar/actualizar los registros nuevos sobre el objeto cargado
    datos_acumulados.update(nuevos_expedientes)
    
    # 4. Sobrescribir el archivo con el diccionario actualizado en formato JSON legible
    with open(ruta_salida, mode="w", encoding="utf-8") as archivo:
        json.dump(datos_acumulados, archivo, ensure_ascii=False, indent=4)
        
    return os.path.abspath(ruta_salida)

def main():
    expedientes_sesion = {}
    contador = 1
    
    print("----------------------------------------------------------------")
    print(" SISTEMA DE REGISTRO DE EXPEDIENTES ACADÉMICOS")
    print("----------------------------------------------------------------")
    
    # Bucle de captura continua de fichas
    while True:
        codigo, ficha = capturar_ficha(contador)
        expedientes_sesion[codigo] = ficha
        contador += 1
        
        if not solicitar_booleano("\n¿Desea incorporar la ficha de otro alumno?"):
            break
            
    # Guardado de datos en JSON
    if expedientes_sesion and solicitar_booleano("\n¿Desea guardar los expedientes recopilados en un archivo JSON local?"):
        print("\n=========================================================================")
        print(" INSTRUCCIONES PARA LA RUTA DEL ARCHIVO JSON:")
        print(" 1. NO use comillas al principio ni al final (ejemplo: NO use \"C:\\datos\\alumnos.json\")")
        print(" 2. Puede usar barras inclinadas normales ( / ) o barras invertidas ( \\ ).")
        print(" 3. Asegúrese de incluir la extensión .json al final del nombre.")
        print(" 4. Ejemplos de rutas válidas:")
        print("    - Windows (ruta absoluta): C:/MisDatos/expedientes.json  ó  C:\\MisDatos\\expedientes.json")
        print("    - Carpeta actual (ruta relativa): ./expedientes.json  ó  expedientes.json")
        print("=========================================================================\n")
        
        ruta_archivo = input("Introduzca la ruta y nombre del archivo JSON: ").strip()
        try:
            ruta_final = guardar_en_json(expedientes_sesion, ruta_archivo)
            print(f"\n[OK] Registros guardados con éxito en:\n     {ruta_final}")
        except Exception as e:
            print(f"\n[ERROR] No se pudo guardar el archivo JSON: {e}")
            
    print("\nCierre de sesión del sistema.")

if __name__ == "__main__":
    main()



Aunque el script resultante sea mejorable, ya resulta suficientemente complejo (además de funcional) como para que nos hagamos una idea de qué estamos planteando cuando implementamos un diccionario de este nivel de complejidad. Lo que hemos pretendido es ejemplificar lo que sería la primera y básica implementación de una utilidad de este tipo: obtener del usuario los datos básicos (y simples, se podría añadir) que componen la estructura del diccionario y archivar esa información partiendo desde cero y en formato JSON. Cualquier proyecto de este tipo debe contener estas funcionalidades y ajustarse a la estructura del JSON deseado.

Pero si el contexto primero es correcto (y realista), el que planteo a continuación también lo es. Dando por supuesto el exito del anterior, creamos ahora una utilidad de acceso a los datos. Partimos de que ya tenemos algunos creados y deseamos acceder a ellos, entendiendo que ya hemos recopilado cierto volumen de información, disponible como archivo JSON. Evidentemente los datos que incluímos son inventados (sintéticos, que se dice) y no nos vamos a detener en valorar su ajuste a la realidad ni su interés pedagógico real. Para que lo que aquí te presento te sea de ayuda, proporcionaré acceso al documento "inventado" al final de esta entrada.

Dicho lo anterior, lo que ahora necesitamos es un script que nos permita acceder al documento JSON para trabajar con él en memoria como diccionario.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)

# 2. Obtener la clave y los datos del primer registro
primer_id = list(expedientes.keys())[0]
primer_registro = expedientes[primer_id]

# 3. Mostrar el resultado por pantalla (CMD)
print(f"--- PRIMER REGISTRO: {primer_id} ---")
print(json.dumps(primer_registro, ensure_ascii=False, indent=4))


Dado que estamos trabajando con archivos JSON, lo primero que haremos será importar la biblioteca correspondiente import json para cargar en memoria el archivo expedientes = json.load(archivo) desde su ubicación local ruta_archivo y accedemos a él with open() as archivo. Después obtenemos las claves de todos los archivos en formato lista list(expedientes.keys()) y el contenido del primer registro primer_registro = expedientes[primer_id] y lo mostramos por CMD print(json.dumps(primer_registro, ensure_ascii=False, indent=4)).

Mediante este par de script abordamos las dos maniobras básicas que deberemos realizar con diccionarios, simples o complejos. Pero como hay tarea para rato, parece conveniente finalizar ahora esta entrada.

Archivo de datos (formato JSON). Acceso para descarga desde este enlace. Deberás ajustar la dirección ruta_archivo a la que tú elijas como directorio del archivo que has descargado.