#!/usr/bin/env python3 """ Análisis profundo del contenido del Manual TES Digital Verifica: referencias cruzadas, links rotos, formato, imágenes, etc. """ import os import re from pathlib import Path from collections import defaultdict from typing import Dict, List, Set, Tuple BASE_DIR = Path("/home/planetazuzu/protocolo-r-pido") MANUAL_DIR = BASE_DIR / "manual-tes" / "TES_Manual_Digital" def obtener_todos_los_archivos_md() -> List[Path]: """Obtiene todos los archivos .md del manual""" archivos = [] for bloque_dir in MANUAL_DIR.iterdir(): if bloque_dir.is_dir() and bloque_dir.name.startswith("BLOQUE_"): for archivo in bloque_dir.glob("*.md"): archivos.append(archivo) return sorted(archivos) def extraer_referencias_entre_corchetes(contenido: str) -> List[str]: """Extrae referencias del tipo [texto](ruta) o [texto]""" patrones = [ r'\[([^\]]+)\]\(([^\)]+)\)', # [texto](ruta) r'\[([^\]]+)\]', # [texto] sin ruta ] referencias = [] for patron in patrones: matches = re.findall(patron, contenido) for match in matches: if isinstance(match, tuple): referencias.append(match[1] if match[1] else match[0]) else: referencias.append(match) return referencias def extraer_referencias_cruzadas(contenido: str) -> List[str]: """Extrae referencias a otros capítulos/bloques""" # Patrones comunes de referencias cruzadas patrones = [ r'(?:ver|Ver|VER|consultar|Consultar|CONSULTAR)\s+(?:el\s+)?(?:capítulo|Capítulo|CAPÍTULO|bloque|Bloque|BLOQUE)?\s*([0-9]+\.[0-9]+(?:\.[0-9]+)?)', r'(?:ver|Ver|VER|consultar|Consultar|CONSULTAR)\s+(?:el\s+)?(?:capítulo|Capítulo|CAPÍTULO|bloque|Bloque|BLOQUE)?\s*([0-9]+\.[0-9]+)', r'\(ver\s+([0-9]+\.[0-9]+(?:\.[0-9]+)?)\)', r'\(Ver\s+([0-9]+\.[0-9]+(?:\.[0-9]+)?)\)', r'\[([0-9]+\.[0-9]+(?:\.[0-9]+)?)\]', ] referencias = [] for patron in patrones: matches = re.findall(patron, contenido) referencias.extend(matches) return referencias def extraer_imagenes(contenido: str) -> List[str]: """Extrae referencias a imágenes""" patron = r'!\[([^\]]*)\]\(([^\)]+)\)' matches = re.findall(patron, contenido) return [match[1] for match in matches] def extraer_tablas(contenido: str) -> int: """Cuenta tablas en formato markdown""" # Buscar líneas que contengan | (indicador de tabla) lineas = contenido.split('\n') tablas = 0 en_tabla = False for linea in lineas: if '|' in linea and linea.strip().startswith('|'): if not en_tabla: tablas += 1 en_tabla = True elif en_tabla and not linea.strip(): en_tabla = False elif en_tabla and '|' not in linea: en_tabla = False return tablas def analizar_estructura_headers(contenido: str) -> Dict: """Analiza la estructura de headers del documento""" lineas = contenido.split('\n') headers = [] for linea in lineas: if linea.startswith('#'): nivel = len(linea) - len(linea.lstrip('#')) texto = linea.lstrip('#').strip() headers.append({'nivel': nivel, 'texto': texto}) return { 'total': len(headers), 'headers': headers, 'tiene_titulo_principal': len(headers) > 0 and headers[0]['nivel'] == 1, 'estructura_valida': len(headers) > 0 } def verificar_metadatos(contenido: str) -> Dict: """Verifica metadatos comunes en los archivos""" metadatos = { 'tiene_version': False, 'tiene_fecha': False, 'tiene_tipo': False, 'version': None, 'fecha': None, 'tipo': None } # Buscar versión match_version = re.search(r'\*\*Versión:\*\*\s*([^\n]+)', contenido) if match_version: metadatos['tiene_version'] = True metadatos['version'] = match_version.group(1).strip() # Buscar fecha match_fecha = re.search(r'\*\*Fecha:\*\*\s*([^\n]+)', contenido) if match_fecha: metadatos['tiene_fecha'] = True metadatos['fecha'] = match_fecha.group(1).strip() # Buscar tipo match_tipo = re.search(r'\*\*Tipo:\*\*\s*([^\n]+)', contenido) if match_tipo: metadatos['tiene_tipo'] = True metadatos['tipo'] = match_tipo.group(1).strip() return metadatos def analizar_contenido_completitud(contenido: str) -> Dict: """Analiza la completitud del contenido""" lineas = contenido.split('\n') lineas_no_vacias = [l for l in lineas if l.strip()] return { 'total_lineas': len(lineas), 'lineas_no_vacias': len(lineas_no_vacias), 'total_caracteres': len(contenido), 'palabras': len(contenido.split()), 'tiene_contenido_sustancial': len(lineas_no_vacias) > 50, # Más de 50 líneas no vacías 'ratio_contenido': len(lineas_no_vacias) / len(lineas) if lineas else 0 } def analizar_archivo(archivo: Path) -> Dict: """Analiza un archivo completo""" try: with open(archivo, 'r', encoding='utf-8') as f: contenido = f.read() except Exception as e: return {'error': str(e)} # Extraer información básica nombre_archivo = archivo.name ruta_relativa = str(archivo.relative_to(BASE_DIR)) # Análisis referencias = extraer_referencias_entre_corchetes(contenido) referencias_cruzadas = extraer_referencias_cruzadas(contenido) imagenes = extraer_imagenes(contenido) num_tablas = extraer_tablas(contenido) estructura_headers = analizar_estructura_headers(contenido) metadatos = verificar_metadatos(contenido) completitud = analizar_contenido_completitud(contenido) return { 'archivo': nombre_archivo, 'ruta': ruta_relativa, 'referencias': referencias, 'referencias_cruzadas': referencias_cruzadas, 'imagenes': imagenes, 'num_tablas': num_tablas, 'estructura_headers': estructura_headers, 'metadatos': metadatos, 'completitud': completitud } def verificar_links_rotos(analisis_archivos: List[Dict]) -> List[Dict]: """Verifica links rotos entre archivos""" # Crear mapa de archivos existentes archivos_existentes = set() for analisis in analisis_archivos: if 'error' not in analisis: archivos_existentes.add(analisis['archivo']) # También agregar variaciones del nombre nombre_sin_ext = Path(analisis['archivo']).stem archivos_existentes.add(nombre_sin_ext) links_rotos = [] for analisis in analisis_archivos: if 'error' in analisis: continue archivo_actual = analisis['archivo'] for ref in analisis['referencias']: # Verificar si es una ruta relativa if ref.startswith('../') or ref.startswith('./'): # Intentar resolver la ruta archivo_ref_dir = Path(analisis['ruta']).parent ruta_completa = (BASE_DIR / archivo_ref_dir / ref).resolve() if not ruta_completa.exists(): links_rotos.append({ 'archivo': archivo_actual, 'referencia': ref, 'tipo': 'ruta_relativa' }) elif ref.endswith('.md'): # Verificar si el archivo existe if ref not in archivos_existentes: # Buscar en todos los bloques encontrado = False for bloque_dir in MANUAL_DIR.iterdir(): if bloque_dir.is_dir(): if (bloque_dir / ref).exists(): encontrado = True break if not encontrado: links_rotos.append({ 'archivo': archivo_actual, 'referencia': ref, 'tipo': 'archivo_md' }) return links_rotos def generar_reporte_profundo(): """Genera un reporte profundo del análisis""" print("Analizando contenido de archivos...") archivos = obtener_todos_los_archivos_md() analisis_completo = [] for archivo in archivos: analisis = analizar_archivo(archivo) analisis_completo.append(analisis) if 'error' in analisis: print(f"⚠️ Error analizando {archivo.name}: {analisis['error']}") print(f"✅ Analizados {len(analisis_completo)} archivos") # Verificar links rotos print("Verificando links rotos...") links_rotos = verificar_links_rotos(analisis_completo) # Generar estadísticas stats = { 'total_archivos': len(analisis_completo), 'archivos_con_errores': len([a for a in analisis_completo if 'error' in a]), 'archivos_con_metadatos_completos': len([a for a in analisis_completo if 'metadatos' in a and a['metadatos']['tiene_version'] and a['metadatos']['tiene_fecha']]), 'total_referencias_cruzadas': sum(len(a.get('referencias_cruzadas', [])) for a in analisis_completo), 'total_imagenes': sum(len(a.get('imagenes', [])) for a in analisis_completo), 'total_tablas': sum(a.get('num_tablas', 0) for a in analisis_completo), 'links_rotos': len(links_rotos), 'archivos_sin_contenido_sustancial': len([a for a in analisis_completo if 'completitud' in a and not a['completitud'].get('tiene_contenido_sustancial', False)]) } # Generar reporte markdown reporte_md = [] reporte_md.append("# REPORTE DE ANÁLISIS PROFUNDO - MANUAL TES DIGITAL\n") reporte_md.append(f"**Fecha:** {__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") reporte_md.append("---\n") # Estadísticas generales reporte_md.append("## 📊 ESTADÍSTICAS GENERALES\n") reporte_md.append(f"- **Total de archivos analizados:** {stats['total_archivos']}") reporte_md.append(f"- **Archivos con errores de lectura:** {stats['archivos_con_errores']}") reporte_md.append(f"- **Archivos con metadatos completos:** {stats['archivos_con_metadatos_completos']}") reporte_md.append(f"- **Total referencias cruzadas:** {stats['total_referencias_cruzadas']}") reporte_md.append(f"- **Total imágenes referenciadas:** {stats['total_imagenes']}") reporte_md.append(f"- **Total tablas:** {stats['total_tablas']}") reporte_md.append(f"- **Links rotos encontrados:** {stats['links_rotos']}") reporte_md.append(f"- **Archivos sin contenido sustancial:** {stats['archivos_sin_contenido_sustancial']}\n") reporte_md.append("---\n") # Links rotos if links_rotos: reporte_md.append("## 🔴 LINKS ROTOS ENCONTRADOS\n") reporte_md.append(f"**Total:** {len(links_rotos)}\n") for link_roto in links_rotos[:20]: # Limitar a 20 para no hacer el reporte muy largo reporte_md.append(f"- `{link_roto['archivo']}` → `{link_roto['referencia']}` ({link_roto['tipo']})") if len(links_rotos) > 20: reporte_md.append(f"\n*... y {len(links_rotos) - 20} más*") else: reporte_md.append("## ✅ NO SE ENCONTRARON LINKS ROTOS\n") reporte_md.append("\n---\n") # Análisis de metadatos reporte_md.append("## 📋 ANÁLISIS DE METADATOS\n") archivos_sin_version = [a for a in analisis_completo if 'metadatos' in a and not a['metadatos']['tiene_version']] archivos_sin_fecha = [a for a in analisis_completo if 'metadatos' in a and not a['metadatos']['tiene_fecha']] archivos_sin_tipo = [a for a in analisis_completo if 'metadatos' in a and not a['metadatos']['tiene_tipo']] if archivos_sin_version: reporte_md.append(f"\n### Archivos sin versión ({len(archivos_sin_version)}):") for archivo in archivos_sin_version[:10]: reporte_md.append(f"- `{archivo['archivo']}`") if len(archivos_sin_version) > 10: reporte_md.append(f"*... y {len(archivos_sin_version) - 10} más*") if archivos_sin_fecha: reporte_md.append(f"\n### Archivos sin fecha ({len(archivos_sin_fecha)}):") for archivo in archivos_sin_fecha[:10]: reporte_md.append(f"- `{archivo['archivo']}`") if len(archivos_sin_fecha) > 10: reporte_md.append(f"*... y {len(archivos_sin_fecha) - 10} más*") if archivos_sin_tipo: reporte_md.append(f"\n### Archivos sin tipo ({len(archivos_sin_tipo)}):") for archivo in archivos_sin_tipo[:10]: reporte_md.append(f"- `{archivo['archivo']}`") if len(archivos_sin_tipo) > 10: reporte_md.append(f"*... y {len(archivos_sin_tipo) - 10} más*") if not archivos_sin_version and not archivos_sin_fecha and not archivos_sin_tipo: reporte_md.append("✅ Todos los archivos tienen metadatos completos\n") reporte_md.append("\n---\n") # Análisis de completitud reporte_md.append("## 📄 ANÁLISIS DE COMPLETITUD\n") archivos_cortos = [a for a in analisis_completo if 'completitud' in a and a['completitud']['lineas_no_vacias'] < 50] if archivos_cortos: reporte_md.append(f"\n### Archivos con menos de 50 líneas de contenido ({len(archivos_cortos)}):") for archivo in archivos_cortos[:10]: lineas = archivo['completitud']['lineas_no_vacias'] reporte_md.append(f"- `{archivo['archivo']}` ({lineas} líneas)") if len(archivos_cortos) > 10: reporte_md.append(f"*... y {len(archivos_cortos) - 10} más*") else: reporte_md.append("✅ Todos los archivos tienen contenido sustancial\n") reporte_md.append("\n---\n") # Resumen de referencias cruzadas reporte_md.append("## 🔗 REFERENCIAS CRUZADAS\n") reporte_md.append(f"Se encontraron {stats['total_referencias_cruzadas']} referencias cruzadas entre capítulos.\n") reporte_md.append("Esto indica buena integración entre los diferentes capítulos del manual.\n") reporte_md.append("\n---\n") # Recomendaciones reporte_md.append("## 💡 RECOMENDACIONES\n") recomendaciones = [] if stats['links_rotos'] > 0: recomendaciones.append("Revisar y corregir los links rotos identificados") if stats['archivos_con_metadatos_completos'] < stats['total_archivos']: recomendaciones.append("Completar metadatos (versión, fecha, tipo) en todos los archivos") if stats['archivos_sin_contenido_sustancial'] > 0: recomendaciones.append("Revisar archivos con poco contenido para asegurar completitud") if not recomendaciones: recomendaciones.append("✅ El proyecto está en excelente estado") for i, rec in enumerate(recomendaciones, 1): reporte_md.append(f"{i}. {rec}") reporte_md.append("\n---\n") return "\n".join(reporte_md), stats if __name__ == "__main__": reporte_md, stats = generar_reporte_profundo() reporte_path = BASE_DIR / "REPORTE_ANALISIS_PROFUNDO.md" with open(reporte_path, "w", encoding="utf-8") as f: f.write(reporte_md) print(f"\n✅ Reporte generado: {reporte_path}") print(f"\n📊 Resumen:") print(f" - Archivos analizados: {stats['total_archivos']}") print(f" - Links rotos: {stats['links_rotos']}") print(f" - Referencias cruzadas: {stats['total_referencias_cruzadas']}") print(f" - Imágenes: {stats['total_imagenes']}") print(f" - Tablas: {stats['total_tablas']}")