codigo0/scripts/generate-docs.py

328 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()