codigo0/scripts/limpiar_manual.py
planetazuzu 5808062d6b feat: Limpieza e integración completa del Manual TES
- Actualizadas 93 rutas en manual-index.ts para apuntar a /manual/
- Simplificado ManualViewer para usar rutas directas del índice
- Agregados scripts de limpieza y actualización de rutas
- Documentación completa de la limpieza e integración
- 93 archivos del manual organizados en public/manual/
- Backup excluido del repositorio (muy pesado)
2025-12-17 12:37:03 +01:00

288 lines
11 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Script de Limpieza e Integración del Manual TES Digital
OBJETIVO:
1. Identificar los 93 archivos .md válidos según manual-index.ts
2. Crear backup completo
3. Eliminar archivos obsoletos/duplicados
4. Asegurar que los 93 archivos estén en public/manual/ con estructura correcta
"""
import os
import re
import shutil
import json
from pathlib import Path
from typing import Set, List, Dict
from datetime import datetime
# Configuración
PROJECT_ROOT = Path(__file__).parent.parent
MANUAL_INDEX_PATH = PROJECT_ROOT / "src/data/manual-index.ts"
BACKUP_DIR = PROJECT_ROOT / "backup_manual_pre_limpieza"
MANUAL_SOURCE_DIR = PROJECT_ROOT / "manual-tes/TES_Manual_Digital"
MANUAL_PUBLIC_DIR = PROJECT_ROOT / "public/manual"
# Patrones para archivos obsoletos
PATRONES_OBSOLETOS = [
r".*\.(tmp|backup|old|bak)\.md$",
r".*prueba.*\.md$",
r".*test.*\.md$",
r".*ejemplo.*\.md$",
r".*temp.*\.md$",
]
def extraer_rutas_validas_del_indice() -> Set[str]:
"""Extrae las rutas de archivos válidos del manual-index.ts"""
rutas_validas = set()
if not MANUAL_INDEX_PATH.exists():
print(f"❌ ERROR: No se encuentra {MANUAL_INDEX_PATH}")
return rutas_validas
contenido = MANUAL_INDEX_PATH.read_text(encoding='utf-8')
# Buscar todas las rutas en el formato: rutaArchivo: "ruta/a/archivo.md"
patron = r'rutaArchivo:\s*"([^"]+)"'
matches = re.findall(patron, contenido)
for match in matches:
# Normalizar ruta relativa al proyecto
ruta_completa = PROJECT_ROOT / match
if ruta_completa.exists():
rutas_validas.add(str(ruta_completa))
else:
print(f"⚠️ ADVERTENCIA: Archivo del índice no encontrado: {match}")
print(f"✅ Encontradas {len(rutas_validas)} rutas válidas en el índice")
return rutas_validas
def encontrar_todos_los_md(excluir_node_modules: bool = True) -> List[Path]:
"""Encuentra todos los archivos .md en el proyecto"""
archivos_md = []
for root, dirs, files in os.walk(PROJECT_ROOT):
# Excluir node_modules y otros directorios
if excluir_node_modules and 'node_modules' in root:
continue
if '.git' in root:
continue
if 'backup' in root.lower():
continue
for file in files:
if file.endswith('.md'):
archivo_path = Path(root) / file
archivos_md.append(archivo_path)
return archivos_md
def es_archivo_obsoleto(archivo: Path, rutas_validas: Set[str]) -> bool:
"""Determina si un archivo es obsoleto"""
ruta_str = str(archivo)
# Si está en las rutas válidas, NO es obsoleto
if ruta_str in rutas_validas:
return False
# Verificar patrones obsoletos
nombre_archivo = archivo.name.lower()
for patron in PATRONES_OBSOLETOS:
if re.match(patron, nombre_archivo):
return True
# Archivos fuera de la estructura principal del manual
if 'manual-tes' not in ruta_str and 'public/manual' not in ruta_str:
# Pero mantener documentación del proyecto (README, etc.)
if archivo.name.upper() in ['README.MD', 'CHANGELOG.MD', 'LICENSE.MD', 'SECURITY.MD']:
return False
# Mantener archivos de documentación en la raíz o docs/
if archivo.parent.name in ['docs', ''] or archivo.parent == PROJECT_ROOT:
# Solo si son archivos de documentación del proyecto
if any(keyword in archivo.name.upper() for keyword in ['PLAN', 'GUIA', 'INSTRUCCION', 'PROBLEMA', 'CORRECCION']):
return False
# Archivos duplicados en diferentes ubicaciones (mantener solo los de TES_Manual_Digital)
if 'manual-tes' in ruta_str:
if 'TES_Manual_Digital' not in ruta_str:
# Archivos fuera de TES_Manual_Digital pero dentro de manual-tes
return True
return False
def crear_backup(archivos_a_backup: List[Path]) -> bool:
"""Crea un backup de todos los archivos .md antes de limpiar"""
print(f"\n📦 Creando backup en {BACKUP_DIR}...")
try:
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_log = BACKUP_DIR / f"backup_log_{timestamp}.txt"
with open(backup_log, 'w', encoding='utf-8') as log:
log.write(f"Backup creado: {timestamp}\n")
log.write(f"Total archivos: {len(archivos_a_backup)}\n\n")
for archivo in archivos_a_backup:
try:
# Mantener estructura relativa
ruta_relativa = archivo.relative_to(PROJECT_ROOT)
destino = BACKUP_DIR / ruta_relativa
destino.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(archivo, destino)
log.write(f"{ruta_relativa}\n")
except Exception as e:
log.write(f"❌ ERROR copiando {archivo}: {e}\n")
print(f"✅ Backup completado: {len(archivos_a_backup)} archivos")
print(f"📄 Log guardado en: {backup_log}")
return True
except Exception as e:
print(f"❌ ERROR creando backup: {e}")
return False
def copiar_archivos_validos_a_public(rutas_validas: Set[str]) -> Dict[str, bool]:
"""Copia los archivos válidos a public/manual/ con estructura correcta"""
resultados = {}
print(f"\n📋 Copiando archivos válidos a {MANUAL_PUBLIC_DIR}...")
MANUAL_PUBLIC_DIR.mkdir(parents=True, exist_ok=True)
for ruta_str in rutas_validas:
archivo_origen = Path(ruta_str)
if not archivo_origen.exists():
resultados[ruta_str] = False
print(f"⚠️ No encontrado: {archivo_origen}")
continue
# Extraer estructura de carpetas desde la ruta
# Ejemplo: manual-tes/TES_Manual_Digital/BLOQUE_0_FUNDAMENTOS/archivo.md
partes = archivo_origen.parts
# Buscar BLOQUE_X en la ruta
bloque_dir = None
for parte in partes:
if parte.startswith('BLOQUE_'):
bloque_dir = parte
break
if not bloque_dir:
print(f"⚠️ No se pudo determinar bloque para: {archivo_origen}")
resultados[ruta_str] = False
continue
# Crear estructura en public/manual/
destino_dir = MANUAL_PUBLIC_DIR / bloque_dir
destino_dir.mkdir(parents=True, exist_ok=True)
destino_archivo = destino_dir / archivo_origen.name
try:
shutil.copy2(archivo_origen, destino_archivo)
resultados[ruta_str] = True
print(f"✅ Copiado: {archivo_origen.name}{destino_dir.name}/")
except Exception as e:
resultados[ruta_str] = False
print(f"❌ ERROR copiando {archivo_origen.name}: {e}")
exitosos = sum(1 for v in resultados.values() if v)
print(f"\n✅ Copiados {exitosos}/{len(rutas_validas)} archivos a public/manual/")
return resultados
def generar_reporte_limpieza(archivos_obsoletos: List[Path], archivos_validos: List[Path]) -> Path:
"""Genera un reporte detallado de la limpieza"""
reporte_path = PROJECT_ROOT / "REPORTE_LIMPIEZA_MANUAL.md"
with open(reporte_path, 'w', encoding='utf-8') as f:
f.write("# 📋 Reporte de Limpieza del Manual TES Digital\n\n")
f.write(f"**Fecha:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write("## 📊 Resumen\n\n")
f.write(f"- **Archivos válidos encontrados:** {len(archivos_validos)}\n")
f.write(f"- **Archivos obsoletos identificados:** {len(archivos_obsoletos)}\n")
f.write(f"- **Total archivos .md en proyecto:** {len(archivos_validos) + len(archivos_obsoletos)}\n\n")
f.write("## ✅ Archivos Válidos (93 esperados)\n\n")
for archivo in sorted(archivos_validos):
f.write(f"- `{archivo.relative_to(PROJECT_ROOT)}`\n")
f.write("\n## 🗑️ Archivos Obsoletos Identificados\n\n")
f.write("**NOTA:** Estos archivos pueden ser eliminados de forma segura.\n\n")
for archivo in sorted(archivos_obsoletos):
f.write(f"- `{archivo.relative_to(PROJECT_ROOT)}`\n")
print(f"\n📄 Reporte generado: {reporte_path}")
return reporte_path
def main():
"""Función principal"""
print("=" * 70)
print("🧹 LIMPIEZA E INTEGRACIÓN DEL MANUAL TES DIGITAL")
print("=" * 70)
# Paso 1: Extraer rutas válidas del índice
print("\n[1/6] Extrayendo rutas válidas del índice...")
rutas_validas = extraer_rutas_validas_del_indice()
if len(rutas_validas) != 93:
print(f"⚠️ ADVERTENCIA: Se esperaban 93 archivos, se encontraron {len(rutas_validas)}")
# Paso 2: Encontrar todos los archivos .md
print("\n[2/6] Buscando todos los archivos .md...")
todos_los_md = encontrar_todos_los_md()
print(f"✅ Encontrados {len(todos_los_md)} archivos .md en total")
# Paso 3: Clasificar archivos
print("\n[3/6] Clasificando archivos (válidos vs obsoletos)...")
archivos_validos = []
archivos_obsoletos = []
for archivo in todos_los_md:
if str(archivo) in rutas_validas:
archivos_validos.append(archivo)
elif es_archivo_obsoleto(archivo, rutas_validas):
archivos_obsoletos.append(archivo)
# Los demás se mantienen (documentación del proyecto, etc.)
print(f"✅ Archivos válidos: {len(archivos_validos)}")
print(f"🗑️ Archivos obsoletos identificados: {len(archivos_obsoletos)}")
# Paso 4: Crear backup
print("\n[4/6] Creando backup...")
if not crear_backup(todos_los_md):
print("❌ ERROR: No se pudo crear el backup. Abortando.")
return
# Paso 5: Copiar archivos válidos a public/manual/
print("\n[5/6] Copiando archivos válidos a public/manual/...")
resultados_copia = copiar_archivos_validos_a_public(rutas_validas)
# Paso 6: Generar reporte
print("\n[6/6] Generando reporte...")
reporte = generar_reporte_limpieza(archivos_obsoletos, archivos_validos)
# Resumen final
print("\n" + "=" * 70)
print("✅ LIMPIEZA COMPLETADA")
print("=" * 70)
print(f"\n📊 Resumen:")
print(f" - Archivos válidos: {len(archivos_validos)}")
print(f" - Archivos obsoletos identificados: {len(archivos_obsoletos)}")
print(f" - Archivos copiados a public/manual/: {sum(1 for v in resultados_copia.values() if v)}")
print(f"\n📦 Backup guardado en: {BACKUP_DIR}")
print(f"📄 Reporte guardado en: {reporte}")
print(f"\n⚠️ PRÓXIMOS PASOS:")
print(f" 1. Revisar el reporte: {reporte}")
print(f" 2. Verificar que public/manual/ tenga los 93 archivos")
print(f" 3. Si todo está bien, ejecutar eliminación de obsoletos")
print(f" 4. Probar la aplicación")
if __name__ == "__main__":
main()