lunes, 9 de febrero de 2026

Documentos

Acceso a datos

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 permite 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. 

El objetivo de esta acción no es lo relevante, pero podemos 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 (por 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.


No hay comentarios:

Publicar un comentario

Comenta esta entrada