#!/usr/bin/env python3 """ Script para verificar la estructura completa del Manual TES Digital Compara el índice esperado con los archivos reales existentes """ import os import re from pathlib import Path from collections import defaultdict from typing import Dict, List, Tuple, Set # Ruta base del proyecto BASE_DIR = Path("/home/planetazuzu/protocolo-r-pido") MANUAL_DIR = BASE_DIR / "manual-tes" / "TES_Manual_Digital" # Mapeo del índice esperado según INDICE_COMPLETO_MANUAL_TES.md INDICE_ESPERADO = { # PARTE I: FUNDAMENTOS Y EVALUACIÓN INICIAL "1.1.1": { "nombre": "Fundamentos de Emergencias", "bloque": 0, "parte": 1, "archivo_esperado": "BLOQUE_00_0_FUNDAMENTOS_EMERGENCIAS.md" }, "1.2.1": { "nombre": "Constantes Vitales", "bloque": 1, "parte": 1, "archivo_esperado": "BLOQUE_01_1_CONSTANTES_VITALES.md" }, "1.2.2": { "nombre": "ABCDE Operativo", "bloque": 1, "parte": 1, "archivo_esperado": "BLOQUE_01_2_ABCDE_OPERATIVO.md" }, "1.2.3": { "nombre": "Glasgow Operativo", "bloque": 1, "parte": 1, "archivo_esperado": "BLOQUE_01_3_GLASGOW_OPERATIVO.md" }, "1.2.4": { "nombre": "Triage START", "bloque": 1, "parte": 1, "archivo_esperado": "BLOQUE_01_4_TRIAGE_START.md" }, # PARTE II: SOPORTE VITAL Y PROCEDIMIENTOS CRÍTICOS "2.1.1": { "nombre": "Acceso Vascular Básico", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_0_ACCESO_VASCULAR_BASICO.md" }, "2.1.2": { "nombre": "Reconocimiento PCR", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_0B_RECONOCIMIENTO_PCR.md" }, "2.1.3": { "nombre": "RCP Adultos", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_1_RCP_ADULTOS.md" }, "2.1.4": { "nombre": "RCP Pediatría", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_2_RCP_PEDIATRIA.md" }, "2.1.5": { "nombre": "RCP Lactantes", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_3_RCP_LACTANTES.md" }, "2.1.6": { "nombre": "Uso DESA", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_4_USO_DESA.md" }, "2.1.7": { "nombre": "RCP Dos Intervinientes", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_5_RCP_DOS_INTERVINIENTES.md" }, "2.1.8": { "nombre": "OVACE Adultos", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_6_OVACE_ADULTOS.md" }, "2.1.9": { "nombre": "OVACE Pediatría", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_7_OVACE_PEDIATRIA.md" }, "2.1.10": { "nombre": "OVACE Lactantes", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_8_OVACE_LACTANTES.md" }, "2.1.11": { "nombre": "Posición Lateral de Seguridad", "bloque": 4, "parte": 2, "archivo_esperado": "BLOQUE_04_9_POSICION_LATERAL_SEGURIDAD.md" }, "2.2.1": { "nombre": "Medicina de Emergencias Aplicada", "bloque": 9, "parte": 2, "archivo_esperado": "BLOQUE_09_0_MEDICINA_EMERGENCIAS_APLICADA.md" }, # PARTE III: MATERIAL Y EQUIPAMIENTO "3.1.1": { "nombre": "Anatomía Operativa", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_0_ANATOMIA_OPERATIVA.md" }, "3.1.2": { "nombre": "Inmovilización Manual", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_2_INMOVILIZACION_MANUAL.md" }, "3.1.3": { "nombre": "Collarín Cervical", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_3_COLLARIN_CERVICAL.md" }, "3.1.4": { "nombre": "Camilla Cuchara", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_4_CAMILLA_CUCHARA.md" }, "3.1.5": { "nombre": "Tablero Espinal", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_5_TABLERO_ESPINAL.md" }, "3.1.6": { "nombre": "Colchón Vacío", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_6_COLCHON_VACIO.md" }, "3.1.7": { "nombre": "Extricación y Movimientos en Bloque", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_7_EXTRICACION_MOVIMIENTOS_BLOQUE.md" }, "3.1.8": { "nombre": "Transferencias y Movilización", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_8_TRANSFERENCIAS_MOVILIZACION.md" }, "3.1.9": { "nombre": "Errores Críticos", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_9_ERRORES_CRITICOS.md" }, "3.1.10": { "nombre": "Férulas", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_10_FERULAS.md" }, "3.1.11": { "nombre": "Cinturón Pélvico", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_11_CINTURON_PELVICO.md" }, "3.1.12": { "nombre": "Férula de Tracción", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_12_FERULA_TRACCION.md" }, "3.1.13": { "nombre": "Camillas y Sillas de Evacuación", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_13_CAMILLAS_SILLAS_EVACUACION.md" }, "3.1.14": { "nombre": "Inventario de Material", "bloque": 2, "parte": 3, "archivo_esperado": "BLOQUE_02_X_INVENTARIO_MATERIAL.md" }, "3.2.1": { "nombre": "Oxigenoterapia Básica", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_0A_OXIGENOTERAPIA_BASICA.md" }, "3.2.2": { "nombre": "Oxigenoterapia - Fundamentos", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_0B_OXIGENOTERAPIA_FUNDAMENTOS.md" }, "3.2.3": { "nombre": "Dispositivos de Oxigenoterapia", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_1_DISPOSITIVOS_OXIGENOTERAPIA.md" }, "3.2.4": { "nombre": "Ventilación con Bolsa-Mascarilla", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_1B_VENTILACION_BOLSA_MASCARILLA.md" }, "3.2.5": { "nombre": "Aspiración", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_2_ASPIRACION.md" }, "3.2.6": { "nombre": "Cánula Orofaringea", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_2B_CANULA_OROFARINGEA.md" }, "3.2.7": { "nombre": "BVM (Bolsa Válvula Mascarilla)", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_3_BVM.md" }, "3.2.8": { "nombre": "Cánulas", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_4_CANULAS.md" }, "3.2.9": { "nombre": "Organización del Maletín", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_5_ORGANIZACION_MALETIN.md" }, "3.2.10": { "nombre": "Control de Hemorragias", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_6_CONTROL_HEMORRAGIAS.md" }, "3.2.11": { "nombre": "Quemaduras", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_7_QUEMADURAS.md" }, "3.2.12": { "nombre": "Heridas y Vendajes", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_8_HERIDAS_VENDAJES.md" }, "3.2.13": { "nombre": "Exposición y Aislamiento Térmico", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_9_EXPOSICION_AISLAMIENTO_TERMICO.md" }, "3.2.14": { "nombre": "Monitorización Básica", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_10_MONITORIZACION_BASICA.md" }, "3.2.15": { "nombre": "Glucometro", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_11_GLUCOMETRO.md" }, "3.2.16": { "nombre": "Termometría", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_12_TERMOMETRIA.md" }, "3.2.17": { "nombre": "Confort y Dolor", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_13_CONFORT_DOLOR.md" }, "3.2.18": { "nombre": "Bioseguridad y Descontaminación", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_14_BIOSEGURIDAD_DESCONTAMINACION.md" }, "3.2.19": { "nombre": "Gestión de Material en Escena", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_15_GESTION_MATERIAL_ESCENA.md" }, "3.2.20": { "nombre": "Comunicación Operativa", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_16_COMUNICACION_OPERATIVA.md" }, "3.2.21": { "nombre": "Señalización e Iluminación", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_17_SENALIZACION_ILUMINACION.md" }, "3.2.22": { "nombre": "Documentación Operativa", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_18_DOCUMENTACION_OPERATIVA.md" }, "3.2.23": { "nombre": "Cierre Bloque 3", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_99_CIERRE_BLOQUE_3.md" }, "3.2.24": { "nombre": "Inventario Material Sanitario", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_X_INVENTARIO_MATERIAL_SANITARIO.md" }, "3.2.25": { "nombre": "Maletín de Curas", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_X2_MALETIN_CURAS.md" }, "3.2.26": { "nombre": "Bolsa de Monitorización", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_X3_BOLSA_MONITORIZACION.md" }, "3.2.27": { "nombre": "Inventario Global", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_X4_INVENTARIO_GLOBAL.md" }, "3.2.28": { "nombre": "Checklist Maestro", "bloque": 3, "parte": 3, "archivo_esperado": "BLOQUE_03_X5_CHECKLIST_MAESTRO.md" }, # PARTE IV: FARMACOLOGÍA Y MEDICAMENTOS "4.1.1": { "nombre": "Principios de Administración de Fármacos", "bloque": 6, "parte": 4, "archivo_esperado": "BLOQUE_06_0_PRINCIPIOS_ADMINISTRACION_FARMACOS.md" }, "4.1.2": { "nombre": "Vademécum Operativo", "bloque": 6, "parte": 4, "archivo_esperado": "BLOQUE_06_1_VADEMECUM_OPERATIVO.md" }, "4.1.3": { "nombre": "Oxígeno - Administración y Seguridad", "bloque": 6, "parte": 4, "archivo_esperado": "BLOQUE_06_2_OXIGENO_ADMINISTRACION_Y_SEGURIDAD.md" }, "4.1.4": { "nombre": "Adrenalina - Uso en Anafilaxia y RCP", "bloque": 6, "parte": 4, "archivo_esperado": "BLOQUE_06_3_ADRENALINA_USO_ANAFILAXIA_Y_RCP.md" }, "4.1.5": { "nombre": "Aspirina - Uso en SCA", "bloque": 6, "parte": 4, "archivo_esperado": "BLOQUE_06_4_ASPIRINA_USO_SCA.md" }, "4.1.6": { "nombre": "Glucagón - Uso en Hipoglucemia", "bloque": 6, "parte": 4, "archivo_esperado": "BLOQUE_06_5_GLUCAGON_USO_HIPOGLUCEMIA.md" }, "4.1.7": { "nombre": "Salbutamol - Uso en Crisis Asmática", "bloque": 6, "parte": 4, "archivo_esperado": "BLOQUE_06_6_SALBUTAMOL_USO_CRISIS_ASMATICA.md" }, "4.1.8": { "nombre": "Abreviaturas y Terminología Farmacológica", "bloque": 6, "parte": 4, "archivo_esperado": "BLOQUE_06_7_ABREVIATURAS_TERMINOLOGIA_FARMACOLOGICA.md" }, # PARTE V: PROTOCOLOS Y GESTIÓN OPERATIVA "5.1.1": { "nombre": "Introducción a Protocolos Transtelefónicos", "bloque": 5, "parte": 5, "archivo_esperado": "BLOQUE_05_0_INTRODUCCION_PROTOCOLOS_TRANSTELEFONICOS.md" }, "5.1.2": { "nombre": "Protocolos de Emergencias Específicas", "bloque": 5, "parte": 5, "archivo_esperado": "BLOQUE_05_0B_PROTOCOLOS_EMERGENCIAS_ESPECIFICAS.md" }, "5.1.3": { "nombre": "PCR Transtelefónica", "bloque": 5, "parte": 5, "archivo_esperado": "BLOQUE_05_1_PCR_TRANSTELEFONICA.md" }, "5.1.4": { "nombre": "OVACE Transtelefónica", "bloque": 5, "parte": 5, "archivo_esperado": "BLOQUE_05_2_OVACE_TRANSTELEFONICA.md" }, "5.1.5": { "nombre": "SCA Transtelefónico", "bloque": 5, "parte": 5, "archivo_esperado": "BLOQUE_05_3_SCA_TRANSTELEFONICO.md" }, "5.1.6": { "nombre": "ICTUS Transtelefónico", "bloque": 5, "parte": 5, "archivo_esperado": "BLOQUE_05_4_ICTUS_TRANSTELEFONICO.md" }, "5.1.7": { "nombre": "Anafilaxia Transtelefónica", "bloque": 5, "parte": 5, "archivo_esperado": "BLOQUE_05_5_ANAFILAXIA_TRANSTELEFONICA.md" }, "5.1.8": { "nombre": "Crisis Asmática Transtelefónica", "bloque": 5, "parte": 5, "archivo_esperado": "BLOQUE_05_6_CRISIS_ASMATICA_TRANSTELEFONICA.md" }, "5.1.9": { "nombre": "Hipoglucemia Transtelefónica", "bloque": 5, "parte": 5, "archivo_esperado": "BLOQUE_05_7_HIPOGLUCEMIA_TRANSTELEFONICA.md" }, "5.1.10": { "nombre": "Comunicación con Coordinador", "bloque": 5, "parte": 5, "archivo_esperado": "BLOQUE_05_8_COMUNICACION_COORDINADOR.md" }, "5.2.1": { "nombre": "Introducción a Gestión Operativa", "bloque": 8, "parte": 5, "archivo_esperado": "BLOQUE_08_0_INTRODUCCION_GESTION_OPERATIVA.md" }, "5.2.2": { "nombre": "Documentación Clínica Prehospitalaria", "bloque": 8, "parte": 5, "archivo_esperado": "BLOQUE_08_1_DOCUMENTACION_CLINICA_PREHOSPITALARIA.md" }, "5.2.3": { "nombre": "Coordinación y Comunicación Operativa", "bloque": 8, "parte": 5, "archivo_esperado": "BLOQUE_08_2_COORDINACION_Y_COMUNICACION_OPERATIVA.md" }, "5.2.4": { "nombre": "Gestión de Recursos y Material", "bloque": 8, "parte": 5, "archivo_esperado": "BLOQUE_08_3_GESTION_RECURSOS_Y_MATERIAL.md" }, "5.2.5": { "nombre": "Calidad y Mejora Continua", "bloque": 8, "parte": 5, "archivo_esperado": "BLOQUE_08_4_CALIDAD_Y_MEJORA_CONTINUA.md" }, # PARTE VI: CONDUCCIÓN Y SEGURIDAD VIAL "6.1.1": { "nombre": "Fundamentos de Conducción en Urgencias", "bloque": 7, "parte": 6, "archivo_esperado": "BLOQUE_07_0_FUNDAMENTOS_CONDUCCION_URGENCIAS.md" }, "6.1.2": { "nombre": "Uso de Luces y Sirena", "bloque": 7, "parte": 6, "archivo_esperado": "BLOQUE_07_1_USO_LUCES_Y_SIRENA.md" }, "6.1.3": { "nombre": "Técnicas de Conducción en Emergencias", "bloque": 7, "parte": 6, "archivo_esperado": "BLOQUE_07_2_TECNICAS_CONDUCCION_EMERGENCIAS.md" }, "6.1.4": { "nombre": "Seguridad Vial y Prevención de Accidentes", "bloque": 7, "parte": 6, "archivo_esperado": "BLOQUE_07_3_SEGURIDAD_VIAL_Y_PREVENCION_ACCIDENTES.md" }, "6.1.5": { "nombre": "Gestión de Rutas y Navegación", "bloque": 7, "parte": 6, "archivo_esperado": "BLOQUE_07_4_GESTION_RUTAS_Y_NAVEGACION.md" }, "6.1.6": { "nombre": "Protocolos de Seguridad en Escena", "bloque": 7, "parte": 6, "archivo_esperado": "BLOQUE_07_5_PROTOCOLOS_SEGURIDAD_EN_ESCENA.md" }, # PARTE VII: SITUACIONES ESPECIALES Y TRAUMA "7.1.1": { "nombre": "Situaciones Especiales", "bloque": 10, "parte": 7, "archivo_esperado": "BLOQUE_10_0_SITUACIONES_ESPECIALES.md" }, "7.2.1": { "nombre": "Protocolos de Trauma", "bloque": 11, "parte": 7, "archivo_esperado": "BLOQUE_11_0_PROTOCOLOS_TRAUMA.md" }, # PARTE VIII: HABILIDADES PROFESIONALES "8.1.1": { "nombre": "Marco Legal, Ético y Profesional del TES", "bloque": 12, "parte": 8, "archivo_esperado": "BLOQUE_12_0_MARCO_LEGAL_ETICO_PROFESIONAL.md" }, "8.2.1": { "nombre": "Comunicación y Relación con el Paciente", "bloque": 13, "parte": 8, "archivo_esperado": "BLOQUE_13_0_COMUNICACION_RELACION_PACIENTE.md" }, "8.3.1": { "nombre": "Seguridad Personal y Salud del TES", "bloque": 14, "parte": 8, "archivo_esperado": "BLOQUE_14_0_SEGURIDAD_PERSONAL_SALUD_TES.md" }, } # Mapeo de bloques a carpetas BLOQUE_TO_FOLDER = { 0: "BLOQUE_0_FUNDAMENTOS", 1: "BLOQUE_1_PROCEDIMIENTOS_BASICOS", 2: "BLOQUE_2_MATERIAL_E_INMOVILIZACION", 3: "BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA", 4: "BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP", 5: "BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS", 6: "BLOQUE_6_FARMACOLOGIA", 7: "BLOQUE_7_CONDUCCION_Y_SEGURIDAD_VIAL", 8: "BLOQUE_8_GESTION_OPERATIVA_Y_DOCUMENTACION", 9: "BLOQUE_9_MEDICINA_EMERGENCIAS_APLICADA", 10: "BLOQUE_10_SITUACIONES_ESPECIALES", 11: "BLOQUE_11_PROTOCOLOS_TRAUMA", 12: "BLOQUE_12_MARCO_LEGAL_ETICO_PROFESIONAL", 13: "BLOQUE_13_COMUNICACION_RELACION_PACIENTE", 14: "BLOQUE_14_SEGURIDAD_PERSONAL_SALUD_TES", } def obtener_archivos_existentes() -> Dict[str, List[str]]: """Obtiene todos los archivos .md existentes organizados por bloque""" archivos_por_bloque = defaultdict(list) for bloque_num, folder_name in BLOQUE_TO_FOLDER.items(): bloque_dir = MANUAL_DIR / folder_name if bloque_dir.exists(): for archivo in bloque_dir.glob("*.md"): archivos_por_bloque[bloque_num].append(archivo.name) return archivos_por_bloque def verificar_estructura(): """Verifica la estructura completa del proyecto""" archivos_existentes = obtener_archivos_existentes() reporte = { "capitulos_encontrados": [], "capitulos_faltantes": [], "archivos_huérfanos": [], "inconsistencias_nombres": [], "errores_estructura": [], "estadisticas": { "total_esperado": len(INDICE_ESPERADO), "total_encontrado": 0, "total_faltante": 0, "total_huérfanos": 0, } } # Verificar cada capítulo esperado archivos_verificados = set() for codigo, info in INDICE_ESPERADO.items(): bloque_num = info["bloque"] archivo_esperado = info["archivo_esperado"] folder_name = BLOQUE_TO_FOLDER[bloque_num] ruta_completa = MANUAL_DIR / folder_name / archivo_esperado if ruta_completa.exists(): reporte["capitulos_encontrados"].append({ "codigo": codigo, "nombre": info["nombre"], "archivo": archivo_esperado, "ruta": str(ruta_completa.relative_to(BASE_DIR)) }) archivos_verificados.add((bloque_num, archivo_esperado)) reporte["estadisticas"]["total_encontrado"] += 1 else: reporte["capitulos_faltantes"].append({ "codigo": codigo, "nombre": info["nombre"], "archivo_esperado": archivo_esperado, "bloque": bloque_num, "carpeta": folder_name }) reporte["estadisticas"]["total_faltante"] += 1 # Buscar archivos huérfanos (existen pero no están en el índice) for bloque_num, archivos in archivos_existentes.items(): folder_name = BLOQUE_TO_FOLDER[bloque_num] for archivo in archivos: if (bloque_num, archivo) not in archivos_verificados: # Verificar si es documentación interna if not archivo.startswith("_") and "DOCUMENTACION" not in str(MANUAL_DIR / folder_name): reporte["archivos_huérfanos"].append({ "archivo": archivo, "bloque": bloque_num, "carpeta": folder_name, "ruta": str((MANUAL_DIR / folder_name / archivo).relative_to(BASE_DIR)) }) reporte["estadisticas"]["total_huérfanos"] += 1 # Verificar estructura de carpetas for bloque_num, folder_name in BLOQUE_TO_FOLDER.items(): bloque_dir = MANUAL_DIR / folder_name if not bloque_dir.exists(): reporte["errores_estructura"].append({ "tipo": "carpeta_faltante", "bloque": bloque_num, "carpeta_esperada": folder_name }) return reporte def generar_reporte_markdown(reporte: dict) -> str: """Genera un reporte detallado en formato Markdown""" md = [] md.append("# REPORTE DE VERIFICACIÓN DE ESTRUCTURA - MANUAL TES DIGITAL\n") md.append(f"**Fecha de verificación:** {__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") md.append("---\n") # Estadísticas generales md.append("## 📊 ESTADÍSTICAS GENERALES\n") stats = reporte["estadisticas"] md.append(f"- **Total de capítulos esperados:** {stats['total_esperado']}") md.append(f"- **Total de capítulos encontrados:** {stats['total_encontrado']}") md.append(f"- **Total de capítulos faltantes:** {stats['total_faltante']}") md.append(f"- **Total de archivos huérfanos:** {stats['total_huérfanos']}") md.append(f"- **Porcentaje de completitud:** {(stats['total_encontrado']/stats['total_esperado']*100):.1f}%\n") md.append("---\n") # Capítulos encontrados (resumen por parte) md.append("## ✅ CAPÍTULOS ENCONTRADOS\n") md.append(f"**Total:** {len(reporte['capitulos_encontrados'])} capítulos\n") # Agrupar por parte por_parte = defaultdict(list) for cap in reporte["capitulos_encontrados"]: codigo = cap["codigo"] parte_num = codigo.split(".")[0] por_parte[parte_num].append(cap) for parte_num in sorted(por_parte.keys(), key=int): md.append(f"\n### Parte {parte_num}") md.append(f"**Capítulos encontrados:** {len(por_parte[parte_num])}") for cap in sorted(por_parte[parte_num], key=lambda x: x["codigo"]): md.append(f"- `{cap['codigo']}` - {cap['nombre']}") md.append("\n---\n") # Capítulos faltantes if reporte["capitulos_faltantes"]: md.append("## ❌ CAPÍTULOS FALTANTES\n") md.append(f"**Total:** {len(reporte['capitulos_faltantes'])} capítulos\n") por_parte_faltante = defaultdict(list) for cap in reporte["capitulos_faltantes"]: codigo = cap["codigo"] parte_num = codigo.split(".")[0] por_parte_faltante[parte_num].append(cap) for parte_num in sorted(por_parte_faltante.keys(), key=int): md.append(f"\n### Parte {parte_num}") for cap in sorted(por_parte_faltante[parte_num], key=lambda x: x["codigo"]): md.append(f"- `{cap['codigo']}` - **{cap['nombre']}**") md.append(f" - Archivo esperado: `{cap['archivo_esperado']}`") md.append(f" - Ubicación esperada: `{cap['carpeta']}/`") else: md.append("## ✅ TODOS LOS CAPÍTULOS ESTÁN PRESENTES\n") md.append("\n---\n") # Archivos huérfanos if reporte["archivos_huérfanos"]: md.append("## ⚠️ ARCHIVOS HUÉRFANOS (no listados en el índice)\n") md.append(f"**Total:** {len(reporte['archivos_huérfanos'])} archivos\n") por_bloque_huérfano = defaultdict(list) for archivo in reporte["archivos_huérfanos"]: por_bloque_huérfano[archivo["bloque"]].append(archivo) for bloque_num in sorted(por_bloque_huérfano.keys()): md.append(f"\n### Bloque {bloque_num} - {por_bloque_huérfano[bloque_num][0]['carpeta']}") for archivo in por_bloque_huérfano[bloque_num]: md.append(f"- `{archivo['archivo']}`") md.append(f" - Ruta: `{archivo['ruta']}`") else: md.append("## ✅ NO HAY ARCHIVOS HUÉRFANOS\n") md.append("\n---\n") # Errores de estructura if reporte["errores_estructura"]: md.append("## 🔴 ERRORES DE ESTRUCTURA\n") for error in reporte["errores_estructura"]: if error["tipo"] == "carpeta_faltante": md.append(f"- ❌ Carpeta faltante: `{error['carpeta_esperada']}` (Bloque {error['bloque']})") else: md.append("## ✅ ESTRUCTURA DE CARPETAS CORRECTA\n") md.append("\n---\n") # Resumen por bloques md.append("## 📁 RESUMEN POR BLOQUES\n") archivos_existentes = obtener_archivos_existentes() for bloque_num in sorted(BLOQUE_TO_FOLDER.keys()): folder_name = BLOQUE_TO_FOLDER[bloque_num] bloque_dir = MANUAL_DIR / folder_name md.append(f"\n### Bloque {bloque_num}: {folder_name}") if bloque_dir.exists(): archivos_bloque = archivos_existentes.get(bloque_num, []) md.append(f"- ✅ Carpeta existe") md.append(f"- Archivos .md encontrados: {len(archivos_bloque)}") # Contar capítulos esperados en este bloque esperados_bloque = [c for c in INDICE_ESPERADO.values() if c["bloque"] == bloque_num] md.append(f"- Capítulos esperados según índice: {len(esperados_bloque)}") else: md.append(f"- ❌ Carpeta NO existe") md.append("\n---\n") md.append("## 📝 NOTAS\n") md.append("- Este reporte compara el índice en `INDICE_COMPLETO_MANUAL_TES.md` con los archivos reales en `manual-tes/TES_Manual_Digital/`") md.append("- Los archivos huérfanos son archivos que existen pero no están listados en el índice") md.append("- Se recomienda revisar los archivos huérfanos para determinar si deben agregarse al índice o eliminarse") return "\n".join(md) if __name__ == "__main__": print("Verificando estructura del Manual TES Digital...") reporte = verificar_estructura() reporte_md = generar_reporte_markdown(reporte) # Guardar reporte reporte_path = BASE_DIR / "REPORTE_VERIFICACION_ESTRUCTURA.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" - Capítulos encontrados: {reporte['estadisticas']['total_encontrado']}/{reporte['estadisticas']['total_esperado']}") print(f" - Capítulos faltantes: {reporte['estadisticas']['total_faltante']}") print(f" - Archivos huérfanos: {reporte['estadisticas']['total_huérfanos']}")