codigo0/scripts/generate-docs.py

328 lines
12 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
Script para convertir archivos MD de cada módulo en documentos Word (.docx)
Un documento Word por módulo, incluyendo todos sus bloques/temas
"""
import os
import re
from pathlib import Path
from datetime import datetime
from docx import Document
from docx.shared import Pt, Inches, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
# Mapeo de carpetas a nombres de módulos
MODULOS_MAP = {
'00_INDICE_Y_NAVEGACION': '00_INDICE_Y_NAVEGACION',
'01_FUNDAMENTOS_Y_CONCEPTOS': '01_FUNDAMENTOS_Y_CONCEPTOS',
'02_PROCEDIMIENTOS_BASICOS': '02_PROCEDIMIENTOS_BASICOS',
'03_SOPORTE_VITAL_BASICO': '03_SOPORTE_VITAL_BASICO',
'04_MATERIAL_E_INMOVILIZACION': '04_MATERIAL_E_INMOVILIZACION',
'05_OXIGENOTERAPIA_Y_MATERIAL_SANITARIO': '05_OXIGENOTERAPIA_Y_MATERIAL_SANITARIO',
'06_PROTOCOLOS_TRANSTELEFONICOS': '06_PROTOCOLOS_TRANSTELEFONICOS',
'07_FARMACOLOGIA_OPERATIVA': '07_FARMACOLOGIA_OPERATIVA',
'08_TRANSFERENCIA_Y_TRASLADO': '08_TRANSFERENCIA_Y_TRASLADO',
'09_TRIAGE_MULTIPLES_VICTIMAS': '09_TRIAGE_MULTIPLES_VICTIMAS',
'10_CONDUCCION_Y_SEGURIDAD_VIAL': '10_CONDUCCION_Y_SEGURIDAD_VIAL',
'11_SITUACIONES_ESPECIALES': '11_SITUACIONES_ESPECIALES',
'12_GESTION_OPERATIVA': '12_GESTION_OPERATIVA',
'HERRAMIENTAS_Y_CHECKLISTS': 'HERRAMIENTAS_Y_CHECKLISTS',
}
def limpiar_markdown(texto):
"""Limpia y prepara texto Markdown para Word"""
# Eliminar enlaces pero mantener texto
texto = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', texto)
# Eliminar código inline pero mantener texto
texto = re.sub(r'`([^`]+)`', r'\1', texto)
return texto
def procesar_markdown_a_docx(doc, contenido_md, archivo_origen):
"""Procesa contenido Markdown y lo agrega al documento Word"""
lineas = contenido_md.split('\n')
i = 0
while i < len(lineas):
linea = lineas[i].strip()
# Saltar líneas vacías
if not linea:
i += 1
continue
# Títulos (# ## ###)
if linea.startswith('# '):
doc.add_heading(linea[2:].strip(), level=1)
elif linea.startswith('## '):
doc.add_heading(linea[3:].strip(), level=2)
elif linea.startswith('### '):
doc.add_heading(linea[4:].strip(), level=3)
elif linea.startswith('#### '):
doc.add_heading(linea[5:].strip(), level=4)
# Listas no ordenadas
elif linea.startswith('- ') or linea.startswith('* '):
texto = limpiar_markdown(linea[2:].strip())
p = doc.add_paragraph(texto, style='List Bullet')
# Procesar líneas siguientes de la lista
i += 1
while i < len(lineas) and (lineas[i].strip().startswith('- ') or
lineas[i].strip().startswith('* ') or
(lineas[i].strip().startswith(' ') and lineas[i].strip())):
siguiente = lineas[i].strip()
if siguiente.startswith('- ') or siguiente.startswith('* '):
texto = limpiar_markdown(siguiente[2:].strip())
doc.add_paragraph(texto, style='List Bullet')
elif siguiente.startswith(' '):
texto = limpiar_markdown(siguiente.strip())
if texto:
doc.add_paragraph(texto, style='List Bullet 2')
i += 1
continue
# Listas ordenadas
elif re.match(r'^\d+\.\s', linea):
texto = limpiar_markdown(re.sub(r'^\d+\.\s', '', linea))
p = doc.add_paragraph(texto, style='List Number')
i += 1
while i < len(lineas) and (re.match(r'^\d+\.\s', lineas[i].strip()) or
(lineas[i].strip().startswith(' ') and lineas[i].strip())):
siguiente = lineas[i].strip()
if re.match(r'^\d+\.\s', siguiente):
texto = limpiar_markdown(re.sub(r'^\d+\.\s', '', siguiente))
doc.add_paragraph(texto, style='List Number')
elif siguiente.startswith(' '):
texto = limpiar_markdown(siguiente.strip())
if texto:
doc.add_paragraph(texto, style='List Number 2')
i += 1
continue
# Tablas (básico)
elif '|' in linea and linea.count('|') >= 2:
# Detectar inicio de tabla
filas_tabla = []
j = i
while j < len(lineas) and '|' in lineas[j]:
fila = [celda.strip() for celda in lineas[j].split('|') if celda.strip()]
if fila and not all(c in '-: ' for c in ''.join(fila)): # No es separador
filas_tabla.append(fila)
j += 1
if filas_tabla:
# Crear tabla en Word
tabla = doc.add_table(rows=len(filas_tabla), cols=len(filas_tabla[0]))
tabla.style = 'Light Grid Accent 1'
for row_idx, fila in enumerate(filas_tabla):
for col_idx, celda in enumerate(fila):
if col_idx < len(tabla.rows[row_idx].cells):
tabla.rows[row_idx].cells[col_idx].text = limpiar_markdown(celda)
i = j
continue
# Código bloque (ignorar por ahora o convertir a texto)
elif linea.startswith('```'):
# Saltar bloque de código
i += 1
while i < len(lineas) and not lineas[i].strip().startswith('```'):
i += 1
# Separadores
elif linea.startswith('---') or linea.startswith('***'):
doc.add_paragraph('' * 60)
# Párrafo normal
else:
texto = limpiar_markdown(linea)
if texto:
doc.add_paragraph(texto)
i += 1
def crear_documento_modulo(nombre_modulo, archivos_md, ruta_base, output_dir):
"""Crea un documento Word para un módulo específico"""
print(f"\n📖 Creando documento: {nombre_modulo}.docx")
# Crear nuevo documento Word
doc = Document()
# Configurar estilos
style = doc.styles['Normal']
style.font.name = 'Calibri'
style.font.size = Pt(11)
# Portada
titulo_portada = doc.add_heading('MANUAL TES DIGITAL', 0)
titulo_portada.alignment = WD_ALIGN_PARAGRAPH.CENTER
nombre_modulo_limpio = nombre_modulo.replace('_', ' ').title()
subtitulo = doc.add_heading(nombre_modulo_limpio, 1)
subtitulo.alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.add_paragraph()
doc.add_paragraph(f"Documento generado automáticamente desde archivos Markdown")
fecha = datetime.now().strftime("%Y-%m-%d")
doc.add_paragraph(f"Fecha: {fecha}")
doc.add_page_break()
# Índice
doc.add_heading('ÍNDICE DE CONTENIDOS', 1)
doc.add_paragraph()
for idx, archivo_md in enumerate(archivos_md, 1):
# Extraer nombre legible
nombre_tema = archivo_md.replace('BLOQUE_', '').replace('.md', '')
nombre_tema = re.sub(r'(\d+)_', r'\1. ', nombre_tema)
nombre_tema = nombre_tema.replace('_', ' ').title()
doc.add_paragraph(f"{idx}. {nombre_tema}", style='List Number')
doc.add_page_break()
# Procesar cada archivo MD del módulo
archivos_procesados = 0
archivos_no_encontrados = []
for archivo_md in archivos_md:
ruta_archivo = os.path.join(ruta_base, archivo_md)
if not os.path.exists(ruta_archivo):
print(f" ⚠️ Archivo no encontrado: {archivo_md}")
archivos_no_encontrados.append(archivo_md)
continue
print(f" Procesando: {archivo_md}")
# Leer contenido MD
try:
with open(ruta_archivo, 'r', encoding='utf-8') as f:
contenido_md = f.read()
except Exception as e:
print(f" ❌ Error leyendo {archivo_md}: {e}")
continue
# Extraer título principal (primera línea con #)
lineas = contenido_md.split('\n')
titulo_principal = None
for linea in lineas[:10]: # Buscar en primeras 10 líneas
if linea.startswith('# '):
titulo_principal = linea[2:].strip()
break
if not titulo_principal:
titulo_principal = archivo_md.replace('.md', '').replace('_', ' ').title()
# Agregar título al documento Word
doc.add_heading(titulo_principal, level=1)
# Procesar contenido
procesar_markdown_a_docx(doc, contenido_md, archivo_md)
# Separador entre bloques
doc.add_paragraph()
doc.add_paragraph('' * 60)
doc.add_paragraph()
archivos_procesados += 1
# Guardar documento
os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(output_dir, f"{nombre_modulo}.docx")
try:
doc.save(output_path)
tamaño_kb = os.path.getsize(output_path) / 1024
print(f" ✅ Documento guardado: {output_path} ({tamaño_kb:.1f} KB)")
print(f" 📄 Archivos procesados: {archivos_procesados}/{len(archivos_md)}")
if archivos_no_encontrados:
print(f" ⚠️ Archivos no encontrados: {len(archivos_no_encontrados)}")
return output_path, archivos_procesados, archivos_no_encontrados
except Exception as e:
print(f" ❌ Error guardando documento: {e}")
return None, 0, archivos_no_encontrados
def main():
"""Función principal"""
print("=" * 70)
print("GENERADOR DE DOCUMENTOS WORD - MANUAL TES DIGITAL")
print("=" * 70)
base_path = 'MANUAL_TES_DIGITAL'
output_dir = 'DOCUMENTOS_WORD'
if not os.path.exists(base_path):
print(f"❌ Error: La carpeta '{base_path}' no existe.")
return
# Escanear módulos
print(f"\n🔍 Explorando módulos en: {base_path}")
modulos_archivos = {}
for modulo_folder in MODULOS_MAP.keys():
modulo_path = os.path.join(base_path, modulo_folder)
if os.path.exists(modulo_path):
archivos = sorted([f for f in os.listdir(modulo_path) if f.endswith('.md')])
if archivos:
modulos_archivos[modulo_folder] = archivos
print(f"\n📚 Módulos encontrados con contenido: {len(modulos_archivos)}")
for modulo, archivos in sorted(modulos_archivos.items()):
print(f"{modulo}: {len(archivos)} archivos")
if not modulos_archivos:
print("❌ No se encontraron módulos con archivos .md")
return
# Generar todos los documentos
print(f"\n🚀 Generando {len(modulos_archivos)} documentos Word...")
print("=" * 70)
documentos_generados = []
total_archivos_procesados = 0
total_archivos_no_encontrados = []
for modulo_folder in sorted(modulos_archivos.keys()):
archivos = modulos_archivos[modulo_folder]
nombre_modulo = MODULOS_MAP[modulo_folder]
modulo_path = os.path.join(base_path, modulo_folder)
try:
doc_path, procesados, no_encontrados = crear_documento_modulo(
nombre_modulo, archivos, modulo_path, output_dir
)
if doc_path:
documentos_generados.append(doc_path)
total_archivos_procesados += procesados
total_archivos_no_encontrados.extend(no_encontrados)
except Exception as e:
print(f"❌ Error procesando {modulo_folder}: {e}")
# Resumen final
print("\n" + "=" * 70)
print("📊 RESUMEN DE GENERACIÓN")
print("=" * 70)
print(f"✅ Documentos generados: {len(documentos_generados)}")
print(f"📄 Archivos MD procesados: {total_archivos_procesados}")
if total_archivos_no_encontrados:
print(f"⚠️ Archivos no encontrados: {len(total_archivos_no_encontrados)}")
print(f"\n📁 Documentos guardados en: {output_dir}/")
print("\n📋 Documentos generados:")
for doc in sorted(documentos_generados):
tamaño = os.path.getsize(doc) / 1024 # KB
print(f"{os.path.basename(doc)} ({tamaño:.1f} KB)")
print("\n🎯 ¡Proceso completado!")
if __name__ == "__main__":
main()