Mostrando entradas con la etiqueta IA. Mostrar todas las entradas
Mostrando entradas con la etiqueta IA. Mostrar todas las entradas

lunes, 15 de junio de 2026

DATOS. Tratamiento de datos

Datos no estructurados (VIII)

Reconocimiento de entidades (NER) (VII)

Continuamos ahora la entrada anterior. En ella planteamos que los SLM son adecuados para extraer las NER en local, permitiendo que el tratamiento dependa exclusivamente del blindaje de nuestro propio equipo y no de servicios en la nube. No obstante observamos que esos modelos pequeños de lenguaje presentan determinadas limitaciones en la gestión de tareas de cierta complejidad. En esta entrada abordaremos cómo superar esos techos mediante la segmentación de procesos y el desarrollo de un modelo mixto.

Los resultados del empleo directo del SLM demostraron que las limitaciones de los obtenidos mediante el script no se debían a una incapacidad para extraer datos (capacidad del modelo para leer el texto), sino a la falta de recursos para abordar simultáneamente dos tareas: esa lectura y la estructuración del formato JSON.

Es cierto que existe un margen de mejora mediante el ajuste del prompt haciendo uso de estrategias de prompt engineering, y es conveniente investigar este enfoque; pero cuando la causa radica en las restricciones estructurales, la mejora del prompt no es la solución: los datos de uso indican claramente que el cuello de botella no está en la claridad de las instrucciones, sino en las limitaciones de la memoria de trabajo del modelo.

Además pudimos comprobar en la práctica que modificaciones de la redacción de prompt, lejos de mejorar los resultados, solo trasladaban la causa del fallo de un punto a otro sin resolver el problema de fondo. Esto provocaba un comportamiento pendular de ineficiencia del que era imposible salir. Sólo el cambio de perspectiva consiguió una mejora significativa: la segmentación de procesos.

Este es el script unificado de ambas fases, resultante de la fusión en uno de lo que originalmente fueron dos script diferenciados aunque relacionados.



import json
import re
from typing import Literal
from ollama import chat
from pydantic import BaseModel, create_model

# ==========================================
# 0. CONFIGURACIÓN Y DATOS DE ENTRADA
# ==========================================

MODELO = 'llama3.2:3b'

texto_analizar = (
    "El alumno Alejandro Martínez Soler, escolarizado en 4º de Primaria del CEIP San Juan Bautista "
    "de Oviedo, presenta dificultades de adaptación significativas. La tutora, Doña Carmen Villalobos, "
    "informa de que tras la reunión mantenida el pasado martes 12 de mayo con la madre del menor, "
    "la Sra. Elena Soler, no se ha observado mejoría en el aula. El Equipo de Orientación Educativa "
    "(EOEP) ha sido notificado por el Director del centro, Don Roberto Pando, clasificando el expediente "
    "como intervención prioritaria. Se ha solicitado formalmente la colaboración del Servicio de Salud "
    "del Principado de Asturias (SESPA) para coordinar la valoración psiquiátrica del menor antes "
    "del viernes 19 de junio. Nota: Toda la documentación clínica de Alejandro ha sido archivada en "
    "el aplicativo institucional SAECE según el protocolo de la Consejería de Educación."
)

# ==========================================
# 1. FASE 1: EXTRACCIÓN DE BLOQUES SEMÁNTICOS
# ==========================================

PROMPT_SISTEMA_FASE1 = """
[OBJETIVO]
Localiza y extrae las frases, nombres, fechas e instituciones que representan entidades clave en el texto.
Devuelve el resultado como una lista de texto plano, un elemento por línea, precedido por '- '.

[REGLAS]
1. Extrae los bloques semánticos completos (ej: si hay un nombre con tratamiento de cortesía o cargo, extráelo completo).
2. NO incluyas introducciones, notas ni explicaciones.
3. Conserva las mayúsculas y siglas idénticas al original.

[EJEMPLO]
INPUT: El orientador del CEIP Marcelo Gago de Avilés remitió el informe técnico a la Consejería el pasado lunes 5 de octubre.
OUTPUT:
- El orientador
- CEIP Marcelo Gago
- Avilés
- la Consejería
- lunes 5 de octubre
"""

print("Ejecutando Fase 1 (Extracción de Bloques Semánticos)...")

response_fase1 = chat(
    model=MODELO,
    messages=[
        {'role': 'system', 'content': PROMPT_SISTEMA_FASE1},
        {'role': 'user', 'content': f"INPUT: {texto_analizar}"}
    ],
    options={'temperature': 0.0, 'top_p': 0.1}
)

salida_bruta = response_fase1.message.content
lines = [line.strip().lstrip('- ').strip() for line in salida_bruta.strip().split('\n') if line]

# --- CAPA DE LIMPIEZA DETERMINISTA (PYTHON) ---
STOP_WORDS_BLOQUES = {
    'alumno', 'menor', 'tutora', 'reunión', 'madre', 'director', 
    'aplicativo institucional', 'intervención prioritaria', 'informe'
}

lista_entidades_fase1 = []
for cadena in lines:
    # 1. Eliminar artículos y fórmulas de cortesía al inicio mediante regex
    procesada = re.sub(r'^(el|la|los|las|del|de|don|doña|sr|sra|sra\.)\s+', '', cadena, flags=re.IGNORECASE)
    
    # 2. Filtrar si coincide con una stop word o si quedó vacía
    if procesada.lower() not in STOP_WORDS_BLOQUES and len(procesada) > 2:
        lista_entidades_fase1.append(procesada)

print(f"-> Fase 1 Finalizada. Se detectaron {len(lista_entidades_fase1)} entidades potenciales.")


# ==========================================
# 2. FASE 2: CLASIFICACIÓN SEMÁNTICA (NER)
# ==========================================

# Definimos la taxonomía estricta mediante Literal
CategoriasNER = Literal[
    'ALUMNO', 'ROL_PROFESIONAL', 'CENTRO_EDUCATIVO', 
    'UBICACION', 'FAMILIAR', 'FECHA', 'ORGANISMO', 'APLICATIVO'
]

# Creamos el esquema Pydantic dinámico usando la salida real de la Fase 1
EsquemaClasificacion = create_model(
    'EsquemaClasificacion',
    __base__=BaseModel,
    **{entidad: (CategoriasNER, ...) for entidad in lista_entidades_fase1}
)

PROMPT_SISTEMA_FASE2 = """
[TASK]
Clasifica cada uno de los términos de la lista proporcionada asignándole exclusivamente una de las categorías permitidas.

[CATEGORÍAS PERMITIDAS]
- ALUMNO: Nombres de estudiantes.
- ROL_PROFESIONAL: Cargos, puestos, figuras docentes o equipos de orientación (individuales o colectivos).
- CENTRO_EDUCATIVO: Nombres de escuelas o colegios.
- UBICACION: Localidades, ciudades o provincias.
- FAMILIAR: Relaciones familiares o nombres de familiares del alumno.
- FECHA: Expresiones temporales.
- ORGANISMO: Servicios públicos, organismos de salud o consejerías.
- APLICATIVO: Software o plataformas informáticas.

[CRITICAL RULES]
1. Responde única y exclusivamente con el JSON estructurado según el esquema.
2. No agregues introducciones, notas ni texto fuera del JSON.

[EJEMPLO DE EJECUCIÓN]
INPUT LIST:
- CEIP Marcelo Gago
- Avilés
- lunes 5 de octubre
OUTPUT JSON:
{
    "CEIP Marcelo Gago": "CENTRO_EDUCATIVO",
    "Avilés": "UBICACION",
    "lunes 5 de octubre": "FECHA"
}

[FIN DEL EJEMPLO]
"""

# Convertimos la lista depurada en texto plano para el prompt
input_model_fase2 = "\n".join([f"- {item}" for item in lista_entidades_fase1])

print("\nEjecutando Fase 2 (Clasificación Semántica Estructurada)...")

response_fase2 = chat(
    model=MODELO,
    messages=[
        {'role': 'system', 'content': PROMPT_SISTEMA_FASE2},
        {'role': 'user', 'content': f"INPUT LIST:\n{input_model_fase2}"}
    ],
    format=EsquemaClasificacion.model_json_schema(),
    options={'temperature': 0.0}
)

# ==========================================
# 3. SALIDA FINAL CONSOLIDADA
# ==========================================

try:
    clasificacion_final = json.loads(response_fase2.message.content)
    print("\n--- Resultado Final Consolidado (NER Estructurado) ---")
    print(json.dumps(clasificacion_final, indent=4, ensure_ascii=False))
except Exception as e:
    print(f"\nError al estructurar el JSON final: {e}")
    print("Respuesta cruda del modelo:", response_fase2.message.content)


Este script presenta una arquitectura ajustada a los modelos de lenguaje pequeños y a sus limitaciones. En lugar de pedirle al modelo que extraiga y clasifique todo en un solo paso, el script implementa un patrón de diseño conocido como pipeline de dos fases con validación estricta y que obedece al siguiente esquema:

En la fase 1 se procede a la extracción de los datos mediante SLM y un prompt localizador de texto" que exige al modelo que extraiga bloques semánticos completos. Se definen hiperparámetros (temperature: 0.0 y top_p: 0.1) que fuerza al modelo a un comportamiento determinista y a ceñirse estrictamente al texto.

Además de la extracción de texto, en la fase 1 implementamos una capa híbrida de limpieza determinista mediante Python puro para que asuma funciones de limpieza de ruido en las que este modelo es mucho más eficiente que un modelo de lenguaje. Para esa limpieza empleamos estructuras RegEx (re.sub) y Stop Words (STOP_WORDS_BLOQUES

La fase 2 se inicia con la creación de un modelo dinámico basado en Pydantic. En este punto, el script genera en tiempo de ejecución una estructura de datos adaptada a las entidades encontradas en la fase anterior, definiendo un conjunto estricto de categorías permitidas.

Tras este proceso, de nuevo interviene el SLM para ejecutar el prompt de clasificación. Gracias a que el esquema de Pydantic se inyecta directamente en el motor de inferencia, el modelo queda restringido a devolver, única y exclusivamente, el JSON estructurado con el NER resultante, que es el siguiente:

{
"Alejandro Martínez Soler": "ALUMNO",
"CEIP San Juan Bautista": "CENTRO_EDUCATIVO",
"Oviedo": "UBICACION",
"Carmen Villalobos": "FAMILIAR",
"madre del menor": "FAMILIAR",
"Sra. Elena Soler": "FAMILIAR",
"pasado martes 12 de mayo": "FECHA",
"Roberto Pando": "ALUMNO",
"Equipo de Orientación Educativa (EOEP)": "ROL_PROFESIONAL",
"Director del centro": "ROL_PROFESIONAL",
"Servicio de Salud del Principado de Asturias (SESPA)": "ORGANISMO",
"viernes 19 de junio": "FECHA"
}

Como podemos observar en la salida del script, el formateo es impecable gracias al blindaje de Pydantic. Sin embargo, un modelo pequeño de lenguaje puede perder la relación de contexto y cometer errores importantes. Con todo, la mejora de resultados respecto al enfoque anterior es evidente, lo que demuestra que la segmentación del proceso y la combinación de Python + SLM presenta ventajas respecto a un modelo IA puro, ahorrando un 90% del trabajo de extracción.

Además, aun caben posibilidades de mejora de esta arquitectura modular... pero también plantear una tercera vía —la cual ya hemos esbozado en nuestro último script—: dividir el proceso de extracción de entidades combinando un modelo heurístico basado en SpaCy con otro basado en IA local, para que cada tecnología asuma las tareas en las que presenta mejor rendimiento.

Este será el comentido de la próxima entrada.

domingo, 14 de junio de 2026

DATOS. Tratamiento de datos

Datos no estructurados (VII)

Reconocimiento de entidades (NER) (VI)

Finalizaba la entrada anterior planteando la existencia de alternativas al modelo heurístico por pipeline híbrido, las cuales se concretan en varias opciones teóricas según vimos anteriormente). En realidad, esas opciones son eso, más teóricas que reales, ya que ni la basada en ML clásico ni la basada en DL discriminativo son realmente viables en función de las restricción reales de datos (confidencialidad, dificultad para conseguir los datos suficientes y coste del etiquetado) ni de hardware (limitaciones de procesamiento y de memoria) que puede afrontar un organismo con escasos recursos como es un SEO. La consecuencia es que sólo es viable la opción basada en IA generativa, pero únicamente la que se puede correr en local, lo que conlleva, además, una limitación en cuanto a los modelos de lenguaje a emplear, que no son otros que los modelos pequeños o SLM. Pero lo que esto no garantizada a priori es la viabilidad real y funcional del modelo IA local.

En consecuencia, la única alternativa viable es la IA generativa ejecutada en local. Esto conlleva, además, una restricción en los modelos de lenguaje a emplear, reduciéndolos a modelos pequeños o SLM. No obstante, esta viabilidad técnica no garantiza, a priori, la eficacia funcional del modelo de IA local para resolver un determinado problema. En el contexto de esta entrada y de las que la preceden, el problema a resolver es (sigue siendo) la extracción de las NER de un texto sintético que ya nos es conocido.

Como tendremos ocasión de comprobar en esta entrada, en la práctica pretender replicar la competencia de un LLM comercial online mediante el simple prompting de un SLM local es una vía infructuosa; las limitaciones en la capacidad de atención y la baja densidad de parámetros de estos modelos pequeños afectan radicalmente a su precisión en el desempeño de tareas como la extracción de datos a partir de un texto no estructurado. Por tanto, alcanzar una equivalencia funcional exige un cambio estructural en el procedimiento. Dado que el modelo en la nube queda descartado por motivos estrictos de confidencialidad, la solución real no radica en el aislamiento del modelo de lenguaje, sino en el diseño de una arquitectura híbrida: un pipeline robusto donde un modelo heurístico asuma la segmentación previa y la estructuración del texto, delegando en el SLM únicamente la extracción fina allí donde las reglas rígidas no alcanzan. El éxito, en consecuencia, pasa de depender de la potencia del modelo a depender de la inteligencia del proceso.

Dado que el tema requiere mucho más espacio del que podemos darle en esta entrada atendiendo a su propia finalidad, no me voy a detener en explicar qué es Ollama y cómo hemos llegado hasta el punto en que somos capaces de utilizar un modelo pequeño de lenguaje SLM como herramienta de trabajo; simplemente lo damos por ahora como un hecho , aunque nos tengamos que detener en algún detalle a la hora de explicar el funcionamiento del script.



# 0. Implementamos las bibliotecas necesarias: json, ollama, pydantic y typing

import json
from ollama import chat
from pydantic import BaseModel, Field
from typing import List

# 1. Definimos el esquema de salida esperado usando pydantic

class EntidadExtraida(BaseModel):
    texto: str = Field(description="El fragmento de texto exacto tal como aparece en el documento original.")
    etiqueta: str = Field(
        description=(
            "La categoría de la entidad. Debe ser una de las siguientes: "
            "'ALUMNO', 'ROL_PROFESIONAL', 'CENTRO_EDUCATIVO', 'UBICACION', "
            "'FAMILIAR', 'FECHA', 'ORGANISMO', 'APLICATIVO'."
        )
    )

class ResultadoNER(BaseModel):
    entidades: List[EntidadExtraida]

# 2. Entregamos el documento a procesar (obtener NER)

texto_analizar = (
    "El alumno Alejandro Martínez Soler, escolarizado en 4º de Primaria del CEIP San Juan Bautista "
    "de Oviedo, presenta dificultades de adaptación significativas. La tutora, Doña Carmen Villalobos, "
    "informa de que tras la reunión mantenida el pasado martes 12 de mayo con la madre del menor, "
    "La Sra. Elena Soler, no se ha observado mejoría en el aula. El Equipo de Orientación Educativa "
    "(EOEP) ha sido notificado por el Director del centro, Don Roberto Pando, clasificando el expediente "
    "como intervención prioritaria. Se ha solicitado formalmente la colaboración del Servicio de Salud "
    "del Principado de Asturias (SESPA) para coordinar la valoración psiquiátrica del menor antes "
    "del viernes 19 de junio. Nota: Toda la documentación clínica de Alejandro ha sido archivada en "
    "el aplicativo institucional SAECE según el protocolo de la Consejería de Educación."
)

print("Enviando petición a llama3.2:3b...")

# 3. Llamada a Ollama utilizando salida estructurada (en el paso 1). Incluye la llamada al SLM y el prompt

response = chat(
    model='llama3.2:3b',
    messages=[
        {
            'role': 'system',
            'content': (
                "Eres un asistente experto en procesamiento del lenguaje natural especializado en el ámbito educativo. "
                "Tu tarea es realizar Reconocimiento de Entidades Nombradas (NER) sobre el texto proporcionado. "
                "Extrae únicamente las entidades literales que correspondan a estas categorías estrictas:\n"
                "- ALUMNO: Nombre del estudiante.\n"
                "- ROL_PROFESIONAL: Cargos, puestos o figuras docentes/técnicas (ej. tutora, Director, Equipo de Orientación Educativa, EOEP).\n"
                "- CENTRO_EDUCATIVO: Nombres de colegios o institutos.\n"
                "- UBICACION: Ciudades, municipios o provincias.\n"
                "- FAMILIAR: Nombres de padres, madres o tutores legales.\n"
                "- FECHA: Referencias temporales explícitas.\n"
                "- ORGANISMO: Entidades públicas, servicios de salud o consejerías.\n"
                "- APLICATIVO: Software, plataformas web o bases de datos institucionales.\n"
                "Sé riguroso y extrae solo texto literal, sin omitir ni inventar nada."
            )
        },
        {
            'role': 'user',
            'content': f"Analiza el siguiente texto:\n\n{texto_analizar}"
        }
    ],
    format=ResultadoNER.model_json_schema(), # Forzamos la estructura JSON mediante el esquema de Pydantic
    options={'temperature': 0.0}  			 # Forzamos a literalidad tratsando de evitar alucinaciones
)

# 4. Procesar y mostrar el resultado

try:
    datos_extraidos = json.loads(response.message.content)
    print("\n--- Entidades Detectadas por llama3.2:3b ---")
    print(json.dumps(datos_extraidos, indent=4, ensure_ascii=False))
except Exception as e:
    print(f"Error al parsear la respuesta: {e}")
    print("Respuesta cruda del modelo:", response.message.content)


Dentro de este script podemos identificar varias partes, empezando por las bibliotecas necesarias para su funcionamiento: json, ollama, pydantic y typing. Cada una de ellas aporta componentes fundamentales para el logro del objetivo propuesto: ollama proporciona el canal de comunicación con el modelo SLM, que es el encargado de leer y "comprender" el documento, pero las demás contribuyen determinando el correcto funcionamiento de éste para que el análisis y la salidad se ajuste estrictamente al objetivo.

El esquema de funcionamiento del script queda descrito por el orden de acciones que se recogen en los comentarios:

  • Primero definimos qué queremos obtener y cómo mediante pydantic y typing
  • Despues porporcionamos el material (texto o conjunto de datos no estructurados) sobre el que trabajar
  • En tercer lugar implementamos el SLM y el instrumento que permite la comunicación entre con él (Ollama), precisamente en orden inverso. En este punto se acoplan las directrices de rol del system prompt con el esquema JSON derivado de Pydantic, fijando la temperatura en 0.0 para garantizar el determinismo absoluto de la prueba.
  • finalmente procesamos los resultados y los mostramos en la consola, controlando cualquier posible error mediante una estructura try...except.

Una breve nota aclaratoria sobre el uso de una herramienta de IA como es Ollama dentro de este script, sin más pretensiones. El uso de un servicio Chat de IA generativa, como Gemini o Chat-GPT, puede dar a entender que esa es, si no la única, sí la principal forma de utilizar la IA, pero lo cierto es que existen otras formas que pasan por integrarla dentro de un script de Python (por ejemplo). Y esto es precisamente lo que hemos hecho en el script anterior; algo que, como podemos observar, resulta muy fácil de realizar en Python al contar con bibliotecas específicas, en nuestro caso from ollama import chat .

En sentido estricto y sumamente restringido, import ollama y...



response = chat(
    model='llama3.2:3b'
    

... son las instrucciones que hacen posible que usemos Ollama response = chat( y el SLM seleccionado model='llama3.2:3b', al que sigue el prompt, que es fundamental para su implementación. Este prompt debe ajustarse a una redacción concreta, ya que de ella depende el funcionamiento efectivo del SLM. Esto es válido también para los LLM, pero aun más para un SLM, dadas sus características y limitaciones respecto a sus hermanos mayores. El prompt que implementamos en este script se puede considerar ajustado a esos condicionantes.



response = chat(
    model='llama3.2:3b',
    messages=[
        {
            'role': 'system',
            'content': (
                "Eres un asistente experto en procesamiento del lenguaje natural especializado en el ámbito educativo. "
                "Tu tarea es realizar Reconocimiento de Entidades Nombradas (NER) sobre el texto proporcionado. "
                "Extrae únicamente las entidades literales que correspondan a estas categorías estrictas:\n"
                "- ALUMNO: Nombre del estudiante.\n"
                "- ROL_PROFESIONAL: Cargos, puestos o figuras docentes/técnicas (ej. tutora, Director, Equipo de Orientación Educativa, EOEP).\n"
                "- CENTRO_EDUCATIVO: Nombres de colegios o institutos.\n"
                "- UBICACION: Ciudades, municipios o provincias.\n"
                "- FAMILIAR: Nombres de padres, madres o tutores legales.\n"
                "- FECHA: Referencias temporales explícitas.\n"
                "- ORGANISMO: Entidades públicas, servicios de salud o consejerías.\n"
                "- APLICATIVO: Software, plataformas web o bases de datos institucionales.\n"
                "Sé riguroso y extrae solo texto literal, sin omitir ni inventar nada."
            )
        },
        {
            'role': 'user',
            'content': f"Analiza el siguiente texto:\n\n{texto_analizar}"
        }
    ],
    

A priori, este diseño satisface los niveles de exigencia del prompting para trabajar con modelos de lenguaje:

  • Asignación de rol que ayuda al modelo a, probabilísticamente, "acotar" el vocabulario y entender el contexto institucional.
  • Instrucciones de restricción rigurosas mediante el uso de palabras ("únicamente", "categorías estrictas" y "solo texto literal, sin omitir ni inventar nada") que actuan como barreras efectivas contra las alucinaciones.
  • Taxonomía con ejemplos In-line, como en ROL_PROFESIONAL, donde se incluyen ejemplos del dominio (tutora, Director, EOEP), lo que permite acotar entidades (v.g. identificar si "Equipo de Orientación" es un rol o un organismo).

Hagamos ahora un sencillo ejercicio de comparación entre lo que nos revuelve el uso de este prompt con Gemini y su LLM...

ALUMNO: Alejandro Martínez Soler
ROL_PROFESIONAL: tutora, Carmen Villalobos, Equipo de Orientación Educativa, EOEP, Director, Roberto Pando
CENTRO_EDUCATIVO: CEIP San Juan Bautista
UBICACION: Oviedo
FAMILIAR: Elena Soler
FECHA: martes 12 de mayo, viernes 19 de junio
ORGANISMO: Servicio de Salud del Principado de Asturias, SESPA, Consejería de Educación
APLICATIVO: SAECE

... y el resultado de su empleo con Ollama + llama3.2:3b

- ALUMNO: Alejandro Martínez Soler
- ROL_PROFESIONAL:
* Tutora: Doña Carmen Villalobos
* Director: Don Roberto Pando
* Equipo de Orientación Educativa (EOEP)
- CENTRO_EDUCATIVO: CEIP San Juan Bautista de Oviedo
- UBICACION: Oviedo
- FAMILIAR:
* Elena Soler (madre del alumno)
* FECHA:
* pasado martes 12 de mayo
* viernes 19 de junio
- ORGANISMO: Servicio de Salud del Principado de Asturias (SESPA)
- APLICATIVO: SAECE

Resulta muy interesante comprobar que el modelo LLM no consigue mejores resultados que el SLM en la tarea propuesta (NER) y sobre un texto plano; esto refuerza la idea de que hoy en día es posible obtener buenos resultados en según que tareas trabajando con modelos pequeños en local, lo que hace posible desarrollar proyectos con estas tecnologías con confianza en los resultados que podemos obtener, facilitando así dar respuesta a condicionantes como son los derivados de la privacidad. no obstante estos modelos no son superiores, ni siquiera iguales, a los LLM en cuanto la tarea se complica o entramos en contextos más extensos o interconectados. Pero cada cosa a su tiempo. De momento nos podemos quedar con lo que hasta ahora nos resulta útil: para un NER, un SLM corriendo en local puede que no tenga mucho que envidiar a un LLM... incluso es posible que se inviertan los papeles...

Pero veamos qué obtenemos al aplicar nuestro script:

{
"entidades": [
{
"texto": "Alejandro Martínez Soler",
"etiqueta": "ALUMNO"
},
{
"texto": "Doña Carmen Villalobos",
"etiqueta": "ROL_PROFESIONAL"
},
{
"texto": "La Sra. Elena Soler",
"etiqueta": "FAMILIAR"
},
{
"texto": "Don Roberto Pando",
"etiqueta": "ROL_PROFESIONAL"
},
{
"texto": "CEIP San Juan Bautista de Oviedo",
"etiqueta": "CENTRO_EDUCATIVO"
},
{
"texto": "Oviedo",
"etiqueta": "UBICACION"
},
{
"texto": "SEPA",
"etiqueta": "ORGANISMO"
},
{
"texto": "SAECE",
"etiqueta": "APLICATIVO"
}
]
}

Es de destacar el cambio formal que se ha producido, ya que pasamos de un listado estructurado de elementos a la expresión formal de una estructura JSON. Pero además también podemos comprobar cómo el mismo SLM que antes extraía los datos solicitados, ahora comete muchos más errores y omisiones. La explicación hay que buscarla en la naturaleza del modelo (y sus limitaciones) enfrentada a las exigencias de la tarea: al ser texto y prompt los mismos, la diferencia estriba en la exigencia de devolver una estructura que obliga a prestar atención a muchos detalles de forma como es un JSON. Ante todo ello, un modelo pequeño de lenguaje es incapaz de ofrecer una respuesta de calidad, la misma que sí puede ofrecer si no se le exige más allá de sus posibilidades. Esto abre una segunda vía de trabajo o, si se prefiere, un diseño en el que el código Python se complemente eficientemente con la IA.

Hasta aquí esta entrada, que evidentemente queda inconclusa. Lo que resta, que no es poco, para la próxima.

NOTAS
1 Puede que sea necesario dedicar una entrada al tema, pero diré que, más por cacharrear que por otra cosa, lo cierto es que tengo instalado Ollama en mi ordenador y con este servicio una serie de modelos. La instalación de Ollama no supone ninguna dificultad y la instalación de los modelos tampoco, así que me voy a ahorrar en estos momentos.
2 Según Gemini, en sentido estricto, Ollama es un entorno de ejecución (runtime) y un gestor de modelos ligero y de código abierto, diseñado para empaquetar, desplegar y ejecutar Modelos de Lenguaje (LLMs y SLMs) de forma local. No es un modelo de inteligencia artificial en sí mismo, sino la infraestructura que permite interactuar con ellos sin dependencia de servicios en la nube.

miércoles, 25 de febrero de 2026

Orient-IA

IA Modelo experto (II)

Estrategias de razonamiento: encadenamiento hacia adelante y encadenamiento hacia atras

Al hablar del modelo experto, dijimos que constaba de tres componentes, la base de conocimiento, el motor de inferencias y el interfaz de usuario. Mientras que el primero y tercer componente no parecen ofrecer dificultades para su comprensión e identificación en una aplicación real, el segundo requiere una explicación más detallada. Este será el objetivo de esta segunda entrada sobre los modelos expertos de IA.

Aunque sin duda no es el único ejemplo, sí me parece que los informes automarizados de pruebas que ofrecen los sistemas de corrección on-line se acercan mucho a la representación del paradigma modelo experto IA. Con esto no quiero decir que funcionen limitándose a este modelo, pero sí que podrían hacerlo perfectamente. Es por ello que considero a estas aplicaciones o servicios una buena ejemplifición de los componentes de que están dotados estos modelos. Al menos en su parte visible e "intuible" así podrían considerarse.

Empezando por el final, el interfaz se muestra, generalmente, de modo generalmente tosco (tambiém hay que decirlo) como sistema de entrada de datos basado en un formulario simple. La base de conocimientos que sustenta el sistema está oculta, especialmente en lo que hace a los datos sensibles, aquellos sobre los que opera el sistema de reglas del motor de inferencias, aunque algunas de sus partes resultan evidentes en los informes resultantes (1).

Y aquí es cuando llegamos a la cuestión que llama ahora nuestra atención: ¿en que´consiste en la práctica eso que llaman motor de inferencias?, ¿qué es el encadenamiento hacia adelante y el encadenamiento hacia atrás?. Conocer estas cuestiones nos ayuda a implementar este tipo de soluciones en nuestras propuestas; especialmente ahí donde la automatización se puede beneficiar de una aplicación fundamentada del modelo experto IA (2)

Entendemos por encadenamiento hacia adelante (Forward Chaining) es un mecanismo de inferencia dirigido por los datos (data-driven), conceptos que vienen a ser sinónimos. El sistema comienza con los hechos conocidos y aplica las reglas si-entonces para generar nuevos hechos. Este proceso se repite hasta que no se pueden extraer más conclusiones. Se basa en la regla lógica Modus Ponens: Sabemos que P implica Q; si tenemos P, entonces se producirá Q.

Un ejemplo muy simple: sabemos que la regla "Si llueve, el suelo se moja" es cierta (P→Q); contatamos que "Está lloviendo" (P), así que el sistema deduce automáticamente que "El suelo está mojado" (Q).

En el encadenamiento hacia atrás (backward chaining) el motor de inferencias empieza con una meta o hipótesis y trabaja a la inversa para ver si los hechos la sustentan.

En este caso el modelo se guía por objetivos ((goal-driven)) e intenta demostrar una conclusión examinando las cláusulas ENTONCES (consecuentes) de las reglas. Si encuentra una regla cuya conclusión coincide con el objetivo, se mueve al SI (antecedente) y convierte esa condición en un nuevo objetivo a demostrar. Esto es, se basa en la búsqueda de evidencia para la implicación. Para demostrar que Q es cierto, el sistema busca si P lo es en la base de hechos o si existe otra regla que tenga a P como conclusión.

Para ejmplificar ambos procedimientos vamos a exponerlos en un contexto de evaluación clínica. Este contexto, además de no ser extraño para el SEO (al que con mucha frecuencia se le acusa de reproducir indebidamente el modelo clínico), resulta pertinente por ser uno de los contextos en que se han desarrollado soportes IA basados en el modelo experto.

Pondremos como ejemplo la atención médica a un paciente que llega a urgencias con dolor torácico. En este contexto, el procesamiento hacia adelante actúa como el triaje inicial qué sugieren los síntomas y el procesamiento hacia atrás actúa como el especialista que busca confirmar una sospecha específica (en este caso reponder a la pregunta: "¿es un infarto?"). Ambos procedimientos, más que antagónicos, resultan complementarios.

Concretando, llamemos escenario A a la aplicación del procesamiento hacia adelante (triaje) e identifiquemos sus pasos:

  • Hecho 1: Dolor torácico opresivo.
  • Hecho 2: Sudoración profusa.
  • Regla: SI "Dolor opresivo" Y "Sudoración" (datos) ENTONCES (inferir) "Posible Isquemia".
  • Resultado: El sistema alerta al médico para que priorice al paciente porque los datos indican posible riesgo coronario.

El escenario B se plantea como procedimiento hacia Atrás (cardiólogo)

  • Meta: Confirmar Infarto (IAM).
  • Regla necesaria: Para confirmar IAM necesito "Elevación del segmento ST en ECG" O "Troponinas altas en sangre".
  • Acción del sistema: El sistema pregunta: "¿Tiene el resultado del ECG?".
  • Hecho: El usuario sube el ECG con elevación ST.
  • Resultado: La meta "Infarto" se marca como Verdadera.

RESUMEN COMPARATIVO

Característica Encadenamiento Hacia Adelante (Data-driven) Encadenamiento Hacia Atrás (Goal-driven)
Punto de Partida Los Síntomas: El paciente dice que tiene dolor opresivo y sudoración fría. La Hipótesis: El cardiólogo sospecha de un "Infarto Agudo de Miocardio" (IAM)
Pregunta Guía ¿A qué diagnóstico nos llevan estos síntomas? ¿Qué pruebas necesito para confirmar que es un infarto?
Flujo de Trabajo Se introducen los datos ---> Se activan reglas ---> Se llega a una conclusión Se fija el objetivo ---> Se buscan las condiciones necesarias ---> Se solicitan los datos.
Resultado intermedio Genera todas las conclusiones posibles Solo genera las necesarias para probar la meta
Comportamiento Exploratorio: Puede concluir varias cosas a la vez: ENTONCES ---> angina | ansiedad | reflujo Confirmatorio: Se enfoca exclusivamente en evidencias que apoyen o descarten el IAM
Interacción Sistema-Sanitario El sistema procesa la información ya disponible en la ficha médica El sistema solicita activamente: "Realice un ECG" o "¿El dolor se irradia al brazo izquierdo?"
Control de búsqueda Puede generar inferencias no solicitadas Búsqueda dirigida y selectiva
Costo computacional Alto si la base de reglas es grande Más eficiente si la meta está bien definida
Naturaleza Exploratoria Confirmatoria
Eficiencia Alta cuando hay muchos síntomas y no se sabe qué pasa Alta cuando se precisa diagnóstico urgente y hay que descartar riesgos vitales
Ejemplo histórico Sistemas tipo CLIPS Sistemas tipo MYCIN

Consultas. Texto elaborado a partir de consultas a IA Gemini - IA ChatGPT (ambas versión gratuita, los mismos prompt)

Notas
1Sobre estas cuestiones trataremos en su momento dentro de la sección Evaluación.
2Como dije, la automatización de informes de pruebas parece ser un campo de trabajo especialmente apropiado. Lo que no implica que no presente limitaciones ni que otros modelos no sean pertinentes.

martes, 24 de febrero de 2026

Orient-IA

IA Modelo experto

El modelo experto (si se prefiere, el paradigma de sistema experto o "expert system paradigm") fue el paradigma de desarrollo de la IA en los años 70-80 (para algunos, hasta inicios 90). Este modelo se basa en la emulación explícita del conocimiento humano, concretamente de los procedimientos con los que los expertos resuelven problemas propios de su área de conocimiento, aplicando reglas de decisión. En esto se diferencia radicalmente de la IA generativa (modelo actualmente predominante) que se basa en el (auto)aprendizaje de patrones (machine learning).

El modelo experto es un enfoque de IA simbólica que tuvo su auge en los años 70-80 con sistemas como MYCIN (para el diagnóstico de infecciones bacterianas) o DENDRAL (para el análisis de estructuras químicas). Se basa en la premisa de que si podemos capturar el conocimiento declarativo y procedimental de un experto, podremos representarlo formalmente, por lo que una máquina puede reproducir su capacidad de decisión en ese dominio.

Para que un modelo experto funcione se requieren tres componentes:

  • A. Una base de conocimientos o repositorio de hechos y reglas de tipo "Si... entonces..." (IF-THEN). No se trata de datos brutos, sino conocimiento estructurado. Esta base de conocimientos contiene: hechos (o datos sobre el dominio), reglas de producción (de tipo *SI condición → ENTONCES conclusión) y, en algunos casos, marcos (frames) u ontologías. Ejemplo simplificado: SI fiebre = alta Y infección = probable -> ENTONCES prescribir antibiótico tipo X
  • B. Un motor de inferencias, o mecanismo de razonamiento que aplica las reglas lógicas a los hecho conocidos para deducir nuevas conclusiones. Opera mediante el encadenamiento hacia adelante (data-driven), el encadenamiento hacia atrás (goal-driven) y la lógica proposicional, lógica de predicados o esquemas probabilísticos (ej., factores de certeza en MYCIN).
  • C. Una interfaz de usuario, que es el medio mediante el cual el sistema hace preguntas al usuario y entrega una solución o explicación.

En el proceso de creación intervienen dos figuras principales: el experto, que provee de los conocimientos temáticos, y el ingeniero informático, que traduce ese conocimiento a código lógico que la máquina pueda entender.

Dada la naturaleza de la lógica subyacente, estos sistemas son capaces de justificar sus conclusiones: “Recomendé X porque se cumplen las reglas A, B y C”. Esto constituye una ventaja respecto a muchos de los sistemas actuales de aprendizaje profundo (deep learning), que no son capaces de informar de su proceso de toma de decisiones.

El modelo experto no pretende simular conciencia o comprensión profunda, sólo la capacidad decisional especializada mediante:

  • Inteligencia simbólica
  • Razonamiento explícito
  • Conocimiento formalizado y estructurado
  • Competencia estrecha (narrow AI)

El modelo experto presenta como puntos fuertes:

  • Una alta precisión en dominios acotados
  • Capacidad de explicar sus decisiones (trazabilidad del razonamiento), lo que constituye una ventaja crítica en entornos basados en regulaciones y en entornos clínicos.
  • Posibilidad de control lógico del sistema
  • Economía y estabilidad, ya que no exige ni depende de grandes volúmenes de datos.

Por el contrario, presenta también una serie de debilidades o limitaciones críticas que explican el predominio actual de los modelos IA de tipo Machine Learning.

1 - Limitaciones derivadas del cuello de botella que representa el conocimiento humano (knowledge acquisition bottleneck), dado que extraer conocimiento de expertos humanos es lento, costoso y parcial, dado que mucho conocimiento experto es intuitivo, tácito y no explícito.

2 - Rigidez y falta de capacidad de generalización. Esto modelos son deterministas y rígidos, funcionan bien sólo en el dominio para el que fueron diseñados, pero ni transfieren conocimiento ni aprenden por experiencia, salvo que se reprogramen reglas.

3 - Escalabilidad limitada. Al aumentar el número de reglas crece exponencialmente su complejidad, aparecen conflictos entre unas reglas y otras y el mantenimiento se vuelve problemático. Además, cada actualización requiere de la intervención humana.

4 - Incapacidad para manejar el contexto y la ambigüedad compleja. Dado que el mundo real es incierto, borroso y no completamente formalizable, los sistemas expertos clásicos presentan dificultades para enfrentarse a esta realidad, ya que operan mejor en entornos con variables discretas, ontologías claras y criterios normativos bien definidos.

5 - No capturan aprendizaje estadístico implícito. A diferencia del enfoque IA actual (IA generativa), basado en redes neuronales, el modelo experto no aprende de grandes corpus de datos, no infiere patrones latentes ni construye representaciones distribuidas. Su conocimiento es explícito, no emergente.

RESUMEN COMPARATIVO

Característica Modelo Experto (Simbólico) IA Moderna (Redes Neuronales)
Origen del conocimiento Basado en reglas. Ingenieros programando reglas Basado en datos. Datos y entrenamiento autónomo
Tipo de conocimiento Conocimiento explícito Representaciones implícitas
Aprendizaje No aprende solo Aprende por entrenamiento
Explicabilidad Alta (puedes ver la regla que aplicó) Baja ("caja negra")
Flexibilidad De dominio estrecho. Muy baja, es rígido. Generalización estadísticas. Muy alta, se adapta a nuevos datos
Uso ideal Tareas con reglas fijas y claras Areas complejas (visión, lenguaje)

Consultas

Además de IA Gemini - IA ChatGPT (ambas versión gratuita), consultar...

martes, 17 de febrero de 2026

DATOS. Tratamiento de datos

Limpieza de datos

¿En qué consiste?

La limpieza de datos es un conjunto de procedimientos que permiten identificar y corregir los errores e incoherencias que pueden presentar los datos para mejorar su calidad, a fin de garantizar que sean precisos, completos, coherentes y utilizables (1).

Se puede decir que la limpieza de datos es una fase crítica en el desarrollo de cualquier solución basada en código, especialmente necesaria en el campo de la inteligencia artificial (IA), dado que en ella importa tanto la cantidad como la calidad. Si la cantidad no es suficiente, los modelos no alcanzan el nivel crítico de rendimiento para ser funcionales, pero si los datos son de mala calidad, el resultado es necesariamente también de mala calidad.

Fruto del interés que tiene la limpieza de datos es la sistematización de la que ha sido objeto, derivando de esta la identificación de seis fases en su desarrollo:

1. Inspección: Antes de nada, observa si hay columnas con nombres extraños, fechas que parecen texto, números imposibles...
2. Gestión de los valores faltantes: Los famosos códigos NaN ante los que caben tres opciones: eliminarlos (si son pocos), imputarlos (usando la media o la mediana) o marcarlos como "Desconocido".
3. Tratamiento de valores atípicos: Identificar outliers: ¿ese dato (900 archivos en un expediente) es un error de escritura o un valor extraordinario?. Según la respuesta y el objetivo que se persiga con el análisis previsto se decide mantenerlo o eliminarlo.
4. Estandarización: Unificar formatos en expresiones como "madrid", "MADRID" y "Madrid ".
5. Deduplicación: Los registros repetidos (duplicados) son ruido que se debe elimínar para evitar sesgos.
6. Validación final: Si tras la limpieza, en un registro aparece una edad de -5 años es que algo no se hizo bien.

Para desarrollar todo este conjunto de actuaciones disponemos de diferentes estrategias, herramienta y enfoques.

POdemos recurrir a procedimientos manuales, basados en la inspección visual, las referencias cruzadas o las tablas dinámicas basadas en Excel o Calc.

En el extremo opuesto a lo "manual" se sitúan las alternativas basadas en la IA, con los que se prioriza la automatización del proceso por entero y que contempla diferentes opciones:

  • Para el análisis de los datos originales, las herramientas de limpieza de datos con IA pueden identificar automáticamente patrones, anomalías e incoherencias y sugerir correcciones.
  • Para la estandarización de los datos, las técnicas de procesamiento del lenguaje natural (PLN) pueden estandarizar texto no estructurado (el formato de direcciones, por ejemplo); los modelos de machine learning (ML) pueden identificar formatos y recomendar los que se adecúan a determinados datos, como fechas o unidades monetarias; y los generadores de expresiones regulares basados en IA permite automatizar la detección y normalización de formatos incoherentes con la naturaleza de los datos.
  • Para la consolidación de duplicados, los modelos de IA basados en reglas o en estrategias aprendizaje-máquina pueden decidir la mejor opción ante la posibilidad de eliminar duplicados, atendiendo a criterios de precisión, actualización y fiabilidad.
  • Para la aplicación de reglas, los modelos de IA pueden automatizar la creación y aplicación de reglas de limpieza de datos resultanres del aprendizaje (historial de correcciones pasadas) y aplicar estas reglas a nuevos conjuntos de datos. También pueden generar reglas personalizadas para aplicar en sectores o dominios específicos.

A pesar de su potencia, estos sistemas no son infalibles, no siempre están disponibles y no son necesariamente la mejor opción; de hecho presentan problemas de coste, de tratamiento confidencial de datos y de limitaciones para correr en nuestros sistemas en local.

Además simplemente podemos optar por mantener el control sobre el proceso de limpieza sin delegarlo totalmente en la IA y/o preferir alternativas basadas en lenguajes como Python o R. Si optamos por Python tenemos a nuestra disposición herramientas (bibliotecas) como las siguientes:
1. Pandas: Herramienta básica que permite cargar datos, filtrarlos y manejar valores nulos.
2. NumPy: Potencia matemática ideal para transformaciones numéricas complejas y para el manejo eficiente de grandes matrices.
3. Scikit-learn: Herramienta pensada para machine learning (ML) cuyo módulo preprocessing permite normalizar escalas y codificar variables categóricas con gran precisión y fiabilidad.
4. Missingno: Visualización de datos vacíos que permite vilualizar dónde están los "huecos" en tu conjunto de datos (dataset)

Nota (1)

Fuentes de información utilizadas: artículo www.ibm.com e información resultante de la consulta a IA Gemini (versión gratuíta básica)

lunes, 9 de febrero de 2026

Expedientes

Acceso a archivos

Este se puede considera el proceso inverso a lo que en su momento constituyó la generación automatizada de un documento de acreditación; también se puede entender como fase inicial del análisis de datos. Sea lo uno o lo otro, lo que nos importa es la consecuencia: se trata de generar un procedimiento que nos permita acceder al contenido de determinados documentos.

Pero como de algún modo hay que plantearlo para darle contexto, aprovecho la ocasión y el trabajo ya desarrollado para invertir su lógica y cerrar el círculo: si entonces se trataba de generar masivamente documentos partiendo de una tabla de datos, ahora se pretender procesar automáticamente y en cascada ciertos documentos para acceder a determinados datos y crear una fuente de datos.

Aunque el objetivo no es lo relevante, no está de más decir que se enmarca dentro del seguimiento o análisis de la intervención, que es una forma de análisis de datos adaptado a la acción de los SEO.

Dado que los datos son inventados (escandalosamente inventados), publicar el contenido de los documentos carece de importancia (cualquier parecido es mera coincidencia), incluyendo su uso (a fines comparativos) en Gemini o NotebookLM.

No puedo asegurar que sea así en todos los casos ya que mi experiencia es muy limitada, pero utilizar estas herramientas IA (y también ChatGPT) en el proceso descrito en la entrada antes citada ("Combinar correspondencia"), aunque es posible, sólo relativamente, con limitaciones cuantitativas y nunca exento de posibilidades de error. Esto es cierto al menos en determinadas condiciones, como son en las que yo me encuentro: Gemini y ChatGPT en sus cuentas gratuitas.

Pero si simulo el acceso al contenido de una colección de documentos para obtener determinados datos en NotebookLM los resultados son claramente positivos: es sencillo hacer la consulta y se obtienen resultados fiables y precisos. Cierto que si necesitamos procesar muchos documentos (y muchos no son tantos) deberás hacerlo en varios cuadernos porque no se puede sobrepasar un límite muy moderado (50 archivos en versión usuario, 300 en versión Google Workspace (empresa o educación).

Esta limitación de por sí es un hándicap nada despreciable, pero se puede asumir; lo que no se puede asumir (sí aquí por las características de la "base de datos" que uso) es la vulneración de la confidencialidad de datos que supone subir este tipo de archivos a la red, especialmente a un sistema IA. Esta limitación es radical y nos obliga a desarrollar otro tipo de alternativas si queremos combinar el análisis de nuestros datos con su automatización, lo que con frecuencia equivale a decir simplemente si realmente queremos realizar análisis de datos basados en la documentación disponible en el SEO.

La solución (una de ellas, porque ya sabemos que tampoco en informática hay una única solución) pasa, por ejemplo, por desarrollar un script Python que nos de respuesta al objetivo que nos podamos plantear. El problema: ni es sencillo ni es posible garantizar que lo que sirve para una cuestión sirva para otra, aunque parezca similar.

Es por ello que la propuesta que presento en esta entrada sirve para lo que se propone (y para otros casos) pero no está garantizado que se pueda generalizar. Deberemos plantear otras situaciones y otros objetivos para ir generando un almacén de herramientas de acceso y análisis de documentos. Ahora sólo estamos en los meros inicios.

Rebobino y concreto: tomo los archivos generados automáticamente mediante el script presentado en esta entrada y los convierto en documentación objeto de análisis. En realidad, más que de un análisis se trata de obtener de ellos datos funcionales para, por ejemplo, generar una "agenda de teléfonos" para establecer comunicación con los progenitores. Se requiere, por tanto, acceso al nombre del progenitor y al teléfono de contacto. Ambos son campos disponibles en nuestra base de datos de referencia, pero vamos a situarnos en ausencia de ese documento.

Primero buscaremos el nombre de los familiares. Para ello podemos aplicar este script...



import os
import re
from docx import Document

folder_path = 'Creados'
def extraer_familiar_regex(file_path):
	doc = Document(file_path)
    texto_total = []
    for tabla in doc.tables:
    	for fila in tabla.rows:
        	for celda in fila.cells:
            	t = celda.text.strip()
                if t and (not texto_total or t != texto_total[-1]):
                	texto_total.append(t)
	etiqueta_objetivo = "Nombre y apellidos:"
    contador = 0
	for i, texto in enumerate(texto_total):
    	if re.search(rf"^{etiqueta_objetivo}", texto, re.IGNORECASE):
        	contador += 1
            if contador == 2:
            	if i + 1 < len(texto_total):
                	return texto_total[i + 1]
               	
	return None

# --- Ejecución y muestra por consola ---

print(f"👤 Buscando nombres de familiares en '{folder_path}'...\n")

for filename in os.listdir(folder_path):
	if filename.endswith('.docx') and not filename.startswith('~$'):
    	ruta = os.path.join(folder_path, filename)
        try:
        	nombre_familiar = extraer_familiar_regex(ruta)
            if nombre_familiar:
            	print(f"✅ {filename}: {nombre_familiar}")
            else:
            	print(f"⚠️ {filename}: No se encontró el nombre del familiar.")
		except Exception as e:
        	print(f"❌ Error en {filename}: {e}")
print("\nBúsqueda finalizada. 🎯")


... que nos devuelve el listado de familiares por consola (te animo a que lo pruebes ubicándolo en la raíz del directorio que contiene la carpeta (subdirectorio) Creados, que contiene los documentos que sirve de base para este proyecto y que te dejo para descarga en Documentos.

De modo similar y con resultados parecidos, podemos obtener el dato Teléfonos:



import os
import re
from docx import Document

folder_path = 'Creados'

def extraer_telefonos_flexibles(file_path):
    doc = Document(file_path)
    telefonos_limpios = []
    
    # EXPLICACIÓN DEL PATRÓN (Marca):
    # \b[679]        -> Empieza por 6, 7 o 9 (límite de palabra)
    # (?:[\s.-]?\d)  -> Un número precedido opcionalmente por un espacio, punto o guion
    # {8}            -> Esto se repite 8 veces para completar los 9 dígitos
    # \b             -> Límite de palabra al final
    patron_flexible = r'\b[679](?:[\s.-]?\d){8}\b'
    
    for tabla in doc.tables:
        for fila in tabla.rows:
            for celda in fila.cells:
                texto = celda.text.strip()
                coincidencias = re.findall(patron_flexible, texto)
                
                for tel in coincidencias:
                    # Limpiamos el teléfono para que en el Excel/Consola quede uniforme (sin espacios)
                    # Ejemplo: "600 12 34 56" -> "600123456"
                    tel_limpio = re.sub(r'[\s.-]', '', tel)
                    
                    if tel_limpio not in telefonos_limpios:
                        telefonos_limpios.append(tel_limpio)
                        
    return telefonos_limpios

# --- Ejecución ---
print(f"🧐 Buscando teléfonos con formatos variables en '{folder_path}'...\n")

for filename in os.listdir(folder_path):
    if filename.endswith('.docx') and not filename.startswith('~$'):
        try:
            tels = extraer_telefonos_flexibles(os.path.join(folder_path, filename))
            if tels:
                print(f"✅ {filename}: {tels}")
            else:
                print(f"⚠️ {filename}: No se encontraron teléfonos.")
        except Exception as e:
            print(f"❌ Error en {filename}: {e}")


Aunque el código de ambos es muy interesante (lo que lo trabajaremos en entradas posteriores), no deja de ser una solución poco funcional, ya que lo deseable es que ambas búsquedas se den en el mismo script y que queden recogidas en un soporte fácil de consultar, como puede ser un archivo xlsx, por ejemplo.

Para responder a estas demandas vamos a presentar un script que unifique ambos procedimientos y devuelva ese archivo Excel.



import os
import re
import pandas as pd
from docx import Document

# Configuración
folder_path = 'Creados'
output_file = 'lista_datos2.xlsx'

def procesar_documentos():
    datos_finales = []
    
    # Patrones Regex
    patron_tel = r'\b[679](?:[\s.-]?\d){8}\b'
    etiqueta_nombre = "Nombre y apellidos:"

    print(f"🚀 Iniciando procesamiento de archivos en '{folder_path}'...")

    for filename in os.listdir(folder_path):
        if filename.endswith('.docx') and not filename.startswith('~$'):
            ruta = os.path.join(folder_path, filename)
            try:
                doc = Document(ruta)
                texto_total = []
                
                # 1. Aplanamiento lineal
                for tabla in doc.tables:
                    for fila in tabla.rows:
                        for celda in fila.cells:
                            t = celda.text.strip()
                            if t and (not texto_total or t != texto_total[-1]):
                                texto_total.append(t)
                
                # 2. Extracción de Familiar (2ª ocurrencia)
                nombre_familiar = "No encontrado"
                contador_nombres = 0
                for i, texto in enumerate(texto_total):
                    if re.search(rf"^{etiqueta_nombre}", texto, re.IGNORECASE):
                        contador_nombres += 1
                        if contador_nombres == 2:
                            if i + 1 < len(texto_total):
                                nombre_familiar = texto_total[i+1]
                            break

                # 3. Extracción de Teléfono (el primero que encuentre con Regex)
                # Unimos todo el texto para que Regex busque en todo el documento
                todo_el_texto = " ".join(texto_total)
                coincidencias_tel = re.findall(patron_tel, todo_el_texto)
                
                # Limpiamos el teléfono si existe
                tel_final = "No encontrado"
                if coincidencias_tel:
                    # Tomamos el primero y le quitamos espacios/puntos
                    tel_final = re.sub(r'[\s.-]', '', coincidencias_tel[0])

                # 4. Guardamos en la lista para Pandas
                datos_finales.append({
                    "Archivo": filename,
                    "Familiar": nombre_familiar,
                    "Teléfono": tel_final
                })
                
                print(f"✅ Procesado: {nombre_familiar} -> {tel_final}")

            except Exception as e:
                print(f"❌ Error en {filename}: {e}")

    # 5. Creación del DataFrame y Excel con Pandas
    if datos_finales:
        df = pd.DataFrame(datos_finales)
        df.to_excel(output_file, index=False)
        print(f"\n✨ Proceso completado. Se ha generado '{output_file}' con {len(df)} registros.")
    else:
        print("\n⚠️ No se encontraron datos para guardar.")

if __name__ == "__main__":
    procesar_documentos()
Mostrando lista_fam_tlf.py.


Este script, además de presentar por pantalla el listado de familiares y teléfonos, genera un archivo Excel (.xlsx) que contiene una tabla con esos mismos datos.

Aunque sólo sea por curiosidad, si quieres saber cómo lee Python los archivos docx, este script te muestra el resultado de aplicar las técnicas de simplificación que permiten a Python acceder a contenidos concretos. No se trata de la única opción existente, sólo una de las más sencillas pero potentes, como puedes ver por los resultados.



from docx import Document

file_path = 'Creados/Acredita_1.docx' 

def ver_lista_lineal(ruta):
    try:
        doc = Document(ruta)
        texto_total = []
        
        # 1. Proceso de "aplanamiento" (igual que en tu script original)
        for tabla in doc.tables:
            for fila in tabla.rows:
                for celda in fila.cells:
                    t = celda.text.strip()
                    # Filtro para evitar duplicados por celdas combinadas
                    if t and (not texto_total or t != texto_total[-1]):
                        texto_total.append(t)
        
        # 2. Mostrar el resultado como una lista pura
        print(f"--- LISTA LINEAL DE: {ruta} ---")
        for i, elemento in enumerate(texto_total):
            # Imprimimos el índice para que veas la "posición" de cada dato
            print(f"Posición {i}: {elemento}")
            
    except Exception as e:
        print(f"❌ Error: {e}")

ver_lista_lineal(file_path)


Puedes compara la sucesión de "etiquetas" y "campos" y la diferencia con la apariencia visual del documento .docx. Y es que uno de los problemas con los que nos encontramos a la hora de trabajar en este tipo de proyectos es la complejidad de los documentos objeto de análisis. Esta complejidad se incrementa cuanto tratamos con documentos prescriptivos, formados por una complicada estructura de tablas las cuales han sido modificadas a lo largo del tiempo generando problemas de acceso importantes que explicar las dificultades con las que nos podemos encontrar al tratar de automatizar procedimientos. Algo de esto ya sabemos.

Documentos.

Para finalizar te dejo acceso a los script Python y al listado de documentos sobre los que trabajan. En esta ocasión no aporto un cuaderno Colab porque no me detengo a analizar el código de los script. Son muchos y suficientemente complejos como para hacerlo ahora. Tiempo habrá y en ello colaborará Gemini, auxiliar también en la creación de estas soluciones.

RECUERDA Debes descargar estos documentos en una misma carpeta.