- Integración de 93 capítulos del manual completo - Componente MarkdownViewer para renderizar archivos .md - Navegación jerárquica completa (ManualIndex) - Sistema de búsqueda mejorado - Página ManualViewer con navegación anterior/siguiente - Scripts de verificación del manual - Puerto configurado en 8096 - Configuración de despliegue (Vercel, Netlify, GitHub Pages) - Todos los problemas detectados corregidos
465 lines
22 KiB
Python
Executable file
465 lines
22 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Script para generar documento Word del Manual TES Digital
|
|
FASE 3: Generación de documento Word con hipervínculos internos
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
from docx import Document
|
|
from docx.shared import Pt, Inches, RGBColor
|
|
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
|
|
from docx.oxml.ns import qn
|
|
from docx.oxml import OxmlElement
|
|
from docx.shared import Length
|
|
|
|
# Configuración
|
|
BASE_DIR = Path("/home/planetazuzu/protocolo-r-pido/manual-tes/TES_Manual_Digital")
|
|
OUTPUT_FILE = Path("/home/planetazuzu/protocolo-r-pido/MANUAL_TES_DIGITAL_COMPLETO.docx")
|
|
|
|
# Mapeo de archivos según estructura jerárquica (rutas relativas desde BASE_DIR)
|
|
ESTRUCTURA_ARCHIVOS = {
|
|
"PARTE I: FUNDAMENTOS Y EVALUACIÓN INICIAL": {
|
|
"BLOQUE 0 - Fundamentos de Emergencias Prehospitalarias": [
|
|
"./BLOQUE_0_FUNDAMENTOS/BLOQUE_00_0_FUNDAMENTOS_EMERGENCIAS.md"
|
|
],
|
|
"BLOQUE 1 - Procedimientos Básicos": [
|
|
"./BLOQUE_1_PROCEDIMIENTOS_BASICOS/BLOQUE_01_1_CONSTANTES_VITALES.md",
|
|
"./BLOQUE_1_PROCEDIMIENTOS_BASICOS/BLOQUE_01_2_ABCDE_OPERATIVO.md",
|
|
"./BLOQUE_1_PROCEDIMIENTOS_BASICOS/BLOQUE_01_3_GLASGOW_OPERATIVO.md",
|
|
"./BLOQUE_1_PROCEDIMIENTOS_BASICOS/BLOQUE_01_4_TRIAGE_START.md"
|
|
]
|
|
},
|
|
"PARTE II: SOPORTE VITAL Y PROCEDIMIENTOS CRÍTICOS": {
|
|
"BLOQUE 4 - Soporte Vital Básico y RCP": [
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_0_ACCESO_VASCULAR_BASICO.md",
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_0B_RECONOCIMIENTO_PCR.md",
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_1_RCP_ADULTOS.md",
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_2_RCP_PEDIATRIA.md",
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_3_RCP_LACTANTES.md",
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_4_USO_DESA.md",
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_5_RCP_DOS_INTERVINIENTES.md",
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_6_OVACE_ADULTOS.md",
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_7_OVACE_PEDIATRIA.md",
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_8_OVACE_LACTANTES.md",
|
|
"./BLOQUE_4_SOPORTE_VITAL_BASICO_Y_RCP/BLOQUE_04_9_POSICION_LATERAL_SEGURIDAD.md"
|
|
],
|
|
"BLOQUE 9 - Medicina de Emergencias Aplicada": [
|
|
"./BLOQUE_9_MEDICINA_EMERGENCIAS_APLICADA/BLOQUE_09_0_MEDICINA_EMERGENCIAS_APLICADA.md"
|
|
]
|
|
},
|
|
"PARTE III: MATERIAL Y EQUIPAMIENTO": {
|
|
"BLOQUE 2 - Material e Inmovilización": [
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_0_ANATOMIA_OPERATIVA.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_2_INMOVILIZACION_MANUAL.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_3_COLLARIN_CERVICAL.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_4_CAMILLA_CUCHARA.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_5_TABLERO_ESPINAL.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_6_COLCHON_VACIO.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_7_EXTRICACION_MOVIMIENTOS_BLOQUE.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_8_TRANSFERENCIAS_MOVILIZACION.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_9_ERRORES_CRITICOS.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_10_FERULAS.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_11_CINTURON_PELVICO.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_12_FERULA_TRACCION.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_13_CAMILLAS_SILLAS_EVACUACION.md",
|
|
"./BLOQUE_2_MATERIAL_E_INMOVILIZACION/BLOQUE_02_X_INVENTARIO_MATERIAL.md"
|
|
],
|
|
"BLOQUE 3 - Material Sanitario y Oxigenoterapia": [
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_0_OXIGENOTERAPIA_BASICA.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_0_OXIGENOTERAPIA_FUNDAMENTOS.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_1_DISPOSITIVOS_OXIGENOTERAPIA.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_1_VENTILACION_BOLSA_MASCARILLA.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_2_ASPIRACION.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_2_CANULA_OROFARINGEA.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_3_BVM.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_4_CANULAS.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_5_ORGANIZACION_MALETIN.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_6_CONTROL_HEMORRAGIAS.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_7_QUEMADURAS.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_8_HERIDAS_VENDAJES.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_9_EXPOSICION_AISLAMIENTO_TERMICO.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_10_MONITORIZACION_BASICA.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_11_GLUCOMETRO.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_12_TERMOMETRIA.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_13_CONFORT_DOLOR.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_14_BIOSEGURIDAD_DESCONTAMINACION.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_15_GESTION_MATERIAL_ESCENA.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_16_COMUNICACION_OPERATIVA.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_17_SENALIZACION_ILUMINACION.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_18_DOCUMENTACION_OPERATIVA.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_99_CIERRE_BLOQUE_3.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_X_INVENTARIO_MATERIAL_SANITARIO.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_X2_MALETIN_CURAS.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_X3_BOLSA_MONITORIZACION.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_X4_INVENTARIO_GLOBAL.md",
|
|
"./BLOQUE_3_MATERIAL_SANITARIO_Y_OXIGENOTERAPIA/BLOQUE_03_X5_CHECKLIST_MAESTRO.md"
|
|
]
|
|
},
|
|
"PARTE IV: FARMACOLOGÍA Y MEDICAMENTOS": {
|
|
"BLOQUE 6 - Farmacología y Vademécum Operativo": [
|
|
"./BLOQUE_6_FARMACOLOGIA/BLOQUE_06_0_PRINCIPIOS_ADMINISTRACION_FARMACOS.md",
|
|
"./BLOQUE_6_FARMACOLOGIA/BLOQUE_06_1_VADEMECUM_OPERATIVO.md",
|
|
"./BLOQUE_6_FARMACOLOGIA/BLOQUE_06_2_OXIGENO_ADMINISTRACION_Y_SEGURIDAD.md",
|
|
"./BLOQUE_6_FARMACOLOGIA/BLOQUE_06_3_ADRENALINA_USO_ANAFILAXIA_Y_RCP.md",
|
|
"./BLOQUE_6_FARMACOLOGIA/BLOQUE_06_4_ASPIRINA_USO_SCA.md",
|
|
"./BLOQUE_6_FARMACOLOGIA/BLOQUE_06_5_GLUCAGON_USO_HIPOGLUCEMIA.md",
|
|
"./BLOQUE_6_FARMACOLOGIA/BLOQUE_06_6_SALBUTAMOL_USO_CRISIS_ASMATICA.md",
|
|
"./BLOQUE_6_FARMACOLOGIA/BLOQUE_06_7_ABREVIATURAS_TERMINOLOGIA_FARMACOLOGICA.md"
|
|
]
|
|
},
|
|
"PARTE V: PROTOCOLOS Y GESTIÓN OPERATIVA": {
|
|
"BLOQUE 5 - Protocolos Transtelefónicos": [
|
|
"./BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS/BLOQUE_05_0_INTRODUCCION_PROTOCOLOS_TRANSTELEFONICOS.md",
|
|
"./BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS/BLOQUE_05_0_PROTOCOLOS_EMERGENCIAS_ESPECIFICAS.md",
|
|
"./BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS/BLOQUE_05_1_PCR_TRANSTELEFONICA.md",
|
|
"./BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS/BLOQUE_05_2_OVACE_TRANSTELEFONICA.md",
|
|
"./BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS/BLOQUE_05_3_SCA_TRANSTELEFONICO.md",
|
|
"./BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS/BLOQUE_05_4_ICTUS_TRANSTELEFONICO.md",
|
|
"./BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS/BLOQUE_05_5_ANAFILAXIA_TRANSTELEFONICA.md",
|
|
"./BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS/BLOQUE_05_6_CRISIS_ASMATICA_TRANSTELEFONICA.md",
|
|
"./BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS/BLOQUE_05_7_HIPOGLUCEMIA_TRANSTELEFONICA.md",
|
|
"./BLOQUE_5_PROTOCOLOS_TRANSTELEFONICOS/BLOQUE_05_8_COMUNICACION_COORDINADOR.md"
|
|
],
|
|
"BLOQUE 8 - Gestión Operativa y Documentación": [
|
|
"./BLOQUE_8_GESTION_OPERATIVA_Y_DOCUMENTACION/BLOQUE_08_0_INTRODUCCION_GESTION_OPERATIVA.md",
|
|
"./BLOQUE_8_GESTION_OPERATIVA_Y_DOCUMENTACION/BLOQUE_08_1_DOCUMENTACION_CLINICA_PREHOSPITALARIA.md",
|
|
"./BLOQUE_8_GESTION_OPERATIVA_Y_DOCUMENTACION/BLOQUE_08_2_COORDINACION_Y_COMUNICACION_OPERATIVA.md",
|
|
"./BLOQUE_8_GESTION_OPERATIVA_Y_DOCUMENTACION/BLOQUE_08_3_GESTION_RECURSOS_Y_MATERIAL.md",
|
|
"./BLOQUE_8_GESTION_OPERATIVA_Y_DOCUMENTACION/BLOQUE_08_4_CALIDAD_Y_MEJORA_CONTINUA.md"
|
|
]
|
|
},
|
|
"PARTE VI: CONDUCCIÓN Y SEGURIDAD VIAL": {
|
|
"BLOQUE 7 - Conducción y Seguridad Vial": [
|
|
"./BLOQUE_7_CONDUCCION_Y_SEGURIDAD_VIAL/BLOQUE_07_0_FUNDAMENTOS_CONDUCCION_URGENCIAS.md",
|
|
"./BLOQUE_7_CONDUCCION_Y_SEGURIDAD_VIAL/BLOQUE_07_1_USO_LUCES_Y_SIRENA.md",
|
|
"./BLOQUE_7_CONDUCCION_Y_SEGURIDAD_VIAL/BLOQUE_07_2_TECNICAS_CONDUCCION_EMERGENCIAS.md",
|
|
"./BLOQUE_7_CONDUCCION_Y_SEGURIDAD_VIAL/BLOQUE_07_3_SEGURIDAD_VIAL_Y_PREVENCION_ACCIDENTES.md",
|
|
"./BLOQUE_7_CONDUCCION_Y_SEGURIDAD_VIAL/BLOQUE_07_4_GESTION_RUTAS_Y_NAVEGACION.md",
|
|
"./BLOQUE_7_CONDUCCION_Y_SEGURIDAD_VIAL/BLOQUE_07_5_PROTOCOLOS_SEGURIDAD_EN_ESCENA.md"
|
|
]
|
|
},
|
|
"PARTE VII: SITUACIONES ESPECIALES Y TRAUMA": {
|
|
"BLOQUE 10 - Situaciones Especiales y Protocolos Avanzados": [
|
|
"./BLOQUE_10_SITUACIONES_ESPECIALES/BLOQUE_10_0_SITUACIONES_ESPECIALES.md"
|
|
],
|
|
"BLOQUE 11 - Protocolos de Trauma y Escenarios de Riesgo": [
|
|
"./BLOQUE_11_PROTOCOLOS_TRAUMA/BLOQUE_11_0_PROTOCOLOS_TRAUMA.md"
|
|
]
|
|
},
|
|
"PARTE VIII: HABILIDADES PROFESIONALES": {
|
|
"BLOQUE 12 - Marco Legal, Ético y Profesional del TES": [
|
|
"./BLOQUE_12_MARCO_LEGAL_ETICO_PROFESIONAL/BLOQUE_12_0_MARCO_LEGAL_ETICO_PROFESIONAL.md"
|
|
],
|
|
"BLOQUE 13 - Comunicación y Relación con el Paciente": [
|
|
"./BLOQUE_13_COMUNICACION_RELACION_PACIENTE/BLOQUE_13_0_COMUNICACION_RELACION_PACIENTE.md"
|
|
],
|
|
"BLOQUE 14 - Seguridad Personal y Salud del TES": [
|
|
"./BLOQUE_14_SEGURIDAD_PERSONAL_SALUD_TES/BLOQUE_14_0_SEGURIDAD_PERSONAL_SALUD_TES.md"
|
|
]
|
|
}
|
|
}
|
|
|
|
def sanitize_bookmark(text):
|
|
"""Sanitiza texto para usar como bookmark"""
|
|
# Elimina caracteres especiales y espacios
|
|
text = re.sub(r'[^\w\s-]', '', text)
|
|
text = re.sub(r'[-\s]+', '_', text)
|
|
return text[:50] # Limita longitud
|
|
|
|
def process_markdown_content(doc, content, file_name):
|
|
"""Procesa contenido Markdown y lo añade al documento Word con formato compacto"""
|
|
lines = content.split('\n')
|
|
in_code_block = False
|
|
last_was_empty = False
|
|
|
|
for line in lines:
|
|
# Detectar bloques de código
|
|
if line.strip().startswith('```'):
|
|
in_code_block = not in_code_block
|
|
continue
|
|
|
|
if in_code_block:
|
|
p = doc.add_paragraph(line, style='Normal')
|
|
p.style.font.name = 'Courier New'
|
|
p.style.font.size = Pt(10)
|
|
p.paragraph_format.space_before = Pt(0)
|
|
p.paragraph_format.space_after = Pt(3)
|
|
p.paragraph_format.line_spacing = 1.15
|
|
last_was_empty = False
|
|
continue
|
|
|
|
# Saltos de página explícitos - eliminar completamente
|
|
if line.strip() == '---' and len(line.strip()) == 3:
|
|
continue
|
|
|
|
# Títulos
|
|
if line.startswith('#'):
|
|
level = len(line) - len(line.lstrip('#'))
|
|
title_text = line.lstrip('#').strip()
|
|
|
|
# Limpiar formato markdown del título
|
|
title_text = re.sub(r'\*\*(.+?)\*\*', r'\1', title_text)
|
|
title_text = re.sub(r'\*(.+?)\*', r'\1', title_text)
|
|
|
|
if level == 1:
|
|
p = doc.add_heading(title_text, level=1)
|
|
p.paragraph_format.space_before = Pt(6)
|
|
p.paragraph_format.space_after = Pt(3)
|
|
elif level == 2:
|
|
p = doc.add_heading(title_text, level=2)
|
|
p.paragraph_format.space_before = Pt(6)
|
|
p.paragraph_format.space_after = Pt(3)
|
|
elif level == 3:
|
|
p = doc.add_heading(title_text, level=3)
|
|
p.paragraph_format.space_before = Pt(4)
|
|
p.paragraph_format.space_after = Pt(2)
|
|
elif level == 4:
|
|
p = doc.add_heading(title_text, level=4)
|
|
p.paragraph_format.space_before = Pt(3)
|
|
p.paragraph_format.space_after = Pt(2)
|
|
elif level == 5:
|
|
p = doc.add_heading(title_text, level=5)
|
|
p.paragraph_format.space_before = Pt(2)
|
|
p.paragraph_format.space_after = Pt(1)
|
|
else:
|
|
p = doc.add_heading(title_text, level=6)
|
|
p.paragraph_format.space_before = Pt(2)
|
|
p.paragraph_format.space_after = Pt(1)
|
|
|
|
last_was_empty = False
|
|
continue
|
|
|
|
# Listas con guiones
|
|
if line.strip().startswith('-') or line.strip().startswith('*'):
|
|
list_text = line.strip().lstrip('-*').strip()
|
|
|
|
# Procesar formato dentro de la lista
|
|
p = doc.add_paragraph(style='List Bullet')
|
|
p.paragraph_format.space_before = Pt(0)
|
|
p.paragraph_format.space_after = Pt(0)
|
|
p.paragraph_format.line_spacing = 1.15
|
|
process_text_formatting(p, list_text)
|
|
last_was_empty = False
|
|
continue
|
|
|
|
# Listas numeradas
|
|
match = re.match(r'^(\d+)\.\s*(.+)', line.strip())
|
|
if match:
|
|
list_text = match.group(2)
|
|
p = doc.add_paragraph(style='List Number')
|
|
p.paragraph_format.space_before = Pt(0)
|
|
p.paragraph_format.space_after = Pt(0)
|
|
p.paragraph_format.line_spacing = 1.15
|
|
process_text_formatting(p, list_text)
|
|
last_was_empty = False
|
|
continue
|
|
|
|
# Líneas vacías - máximo una seguida
|
|
if not line.strip():
|
|
if not last_was_empty:
|
|
p = doc.add_paragraph()
|
|
p.paragraph_format.space_before = Pt(0)
|
|
p.paragraph_format.space_after = Pt(0)
|
|
last_was_empty = True
|
|
continue
|
|
|
|
# Texto normal
|
|
p = doc.add_paragraph()
|
|
p.paragraph_format.space_before = Pt(0)
|
|
p.paragraph_format.space_after = Pt(3)
|
|
p.paragraph_format.line_spacing = 1.15
|
|
process_text_formatting(p, line.strip())
|
|
last_was_empty = False
|
|
|
|
def process_text_formatting(paragraph, text):
|
|
"""Procesa formato Markdown en texto (negritas, cursivas, etc.)"""
|
|
# Patrón para negritas **texto**
|
|
parts = re.split(r'(\*\*[^*]+\*\*)', text)
|
|
|
|
for part in parts:
|
|
if part.startswith('**') and part.endswith('**'):
|
|
# Negrita
|
|
run = paragraph.add_run(part[2:-2])
|
|
run.bold = True
|
|
elif part.startswith('*') and part.endswith('*') and len(part) > 2:
|
|
# Cursiva (solo si no es negrita)
|
|
run = paragraph.add_run(part[1:-1])
|
|
run.italic = True
|
|
elif part.strip():
|
|
# Texto normal
|
|
paragraph.add_run(part)
|
|
|
|
def process_markdown_file(file_path):
|
|
"""Lee un archivo Markdown"""
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
return f.read()
|
|
except Exception as e:
|
|
print(f" ⚠️ Error leyendo {file_path}: {e}")
|
|
return None
|
|
|
|
def main():
|
|
"""Función principal"""
|
|
print("=" * 70)
|
|
print("GENERACIÓN DE DOCUMENTO WORD - MANUAL TES DIGITAL")
|
|
print("FASE 3: Documento Word con hipervínculos internos")
|
|
print("=" * 70)
|
|
|
|
# Crear documento
|
|
doc = Document()
|
|
|
|
# Configurar estilos base
|
|
style = doc.styles['Normal']
|
|
font = style.font
|
|
font.name = 'Calibri'
|
|
font.size = Pt(11)
|
|
style.paragraph_format.line_spacing = 1.15
|
|
style.paragraph_format.space_before = Pt(0)
|
|
style.paragraph_format.space_after = Pt(3)
|
|
|
|
# Configurar estilos de títulos
|
|
for i in range(1, 7):
|
|
heading_style = doc.styles[f'Heading {i}']
|
|
heading_style.font.name = 'Calibri'
|
|
heading_style.font.size = Pt(max(16 - i, 12)) # Título 1: 16pt, Título 2: 15pt, etc.
|
|
heading_style.paragraph_format.line_spacing = 1.15
|
|
if i == 1:
|
|
heading_style.paragraph_format.space_before = Pt(6)
|
|
heading_style.paragraph_format.space_after = Pt(3)
|
|
elif i == 2:
|
|
heading_style.paragraph_format.space_before = Pt(6)
|
|
heading_style.paragraph_format.space_after = Pt(3)
|
|
else:
|
|
heading_style.paragraph_format.space_before = Pt(4)
|
|
heading_style.paragraph_format.space_after = Pt(2)
|
|
|
|
# Configurar estilo de listas
|
|
for list_style_name in ['List Bullet', 'List Number']:
|
|
if list_style_name in doc.styles:
|
|
list_style = doc.styles[list_style_name]
|
|
list_style.font.size = Pt(11)
|
|
list_style.paragraph_format.line_spacing = 1.15
|
|
list_style.paragraph_format.space_before = Pt(0)
|
|
list_style.paragraph_format.space_after = Pt(0)
|
|
|
|
# Configurar márgenes (compactos: 2cm = ~0.79 pulgadas)
|
|
sections = doc.sections
|
|
for section in sections:
|
|
section.top_margin = Inches(0.79)
|
|
section.bottom_margin = Inches(0.79)
|
|
section.left_margin = Inches(0.79)
|
|
section.right_margin = Inches(0.79)
|
|
|
|
# PORTADA (compacta)
|
|
print("\n📄 Creando portada...")
|
|
title = doc.add_heading('MANUAL TES DIGITAL', level=1)
|
|
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
title.paragraph_format.space_after = Pt(6)
|
|
|
|
p = doc.add_paragraph('Versión 1.0')
|
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
p.paragraph_format.space_after = Pt(3)
|
|
|
|
p = doc.add_paragraph('Fecha: 2024-12-15')
|
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
p.paragraph_format.space_after = Pt(3)
|
|
|
|
p = doc.add_paragraph('Tipo: Manual Operativo Completo')
|
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
p.paragraph_format.space_after = Pt(6)
|
|
|
|
doc.add_page_break()
|
|
|
|
# ÍNDICE GENERAL (compacto)
|
|
print("📑 Creando índice general...")
|
|
doc.add_heading('ÍNDICE GENERAL', level=1)
|
|
|
|
for parte, bloques in ESTRUCTURA_ARCHIVOS.items():
|
|
p = doc.add_paragraph(parte, style='Normal')
|
|
p.style.font.bold = True
|
|
p.style.font.size = Pt(12)
|
|
p.paragraph_format.space_before = Pt(3)
|
|
p.paragraph_format.space_after = Pt(1)
|
|
|
|
for bloque, archivos in bloques.items():
|
|
p = doc.add_paragraph(f" {bloque}", style='Normal')
|
|
p.style.font.size = Pt(11)
|
|
p.style.font.italic = True
|
|
p.paragraph_format.space_before = Pt(0)
|
|
p.paragraph_format.space_after = Pt(1)
|
|
|
|
doc.add_page_break()
|
|
|
|
# PROCESAR CADA PARTE
|
|
total_archivos = sum(len(archivos) for bloques in ESTRUCTURA_ARCHIVOS.values() for archivos in bloques.values())
|
|
archivo_actual = 0
|
|
|
|
partes_list = list(ESTRUCTURA_ARCHIVOS.items())
|
|
for idx, (parte, bloques) in enumerate(partes_list):
|
|
print(f"\n📚 Procesando {parte}...")
|
|
|
|
# Título de parte
|
|
parte_heading = doc.add_heading(parte, level=1)
|
|
parte_heading.paragraph_format.space_before = Pt(6)
|
|
parte_heading.paragraph_format.space_after = Pt(3)
|
|
|
|
for bloque, archivos in bloques.items():
|
|
print(f" 📖 Procesando {bloque}...")
|
|
|
|
# Título de bloque
|
|
bloque_heading = doc.add_heading(bloque, level=2)
|
|
bloque_heading.paragraph_format.space_before = Pt(6)
|
|
bloque_heading.paragraph_format.space_after = Pt(3)
|
|
|
|
# Procesar cada archivo del bloque
|
|
for archivo_relativo in archivos:
|
|
archivo_actual += 1
|
|
archivo_path = BASE_DIR / archivo_relativo.lstrip('./')
|
|
|
|
if not archivo_path.exists():
|
|
print(f" ⚠️ Archivo no encontrado: {archivo_relativo}")
|
|
p = doc.add_paragraph(f"[ARCHIVO NO ENCONTRADO: {archivo_relativo}]")
|
|
p.style.font.color.rgb = RGBColor(255, 0, 0)
|
|
p.paragraph_format.space_before = Pt(0)
|
|
p.paragraph_format.space_after = Pt(3)
|
|
continue
|
|
|
|
print(f" 📄 [{archivo_actual}/{total_archivos}] {archivo_path.name}")
|
|
|
|
# Título del capítulo (nombre del archivo sin extensión y formato)
|
|
chapter_title = archivo_path.stem.replace('BLOQUE_', '').replace('_', ' ')
|
|
heading = doc.add_heading(chapter_title, level=3)
|
|
heading.paragraph_format.space_before = Pt(4)
|
|
heading.paragraph_format.space_after = Pt(2)
|
|
|
|
# Procesar contenido
|
|
content = process_markdown_file(archivo_path)
|
|
if content:
|
|
process_markdown_content(doc, content, archivo_path.name)
|
|
else:
|
|
p = doc.add_paragraph("[Error al leer el contenido del archivo]")
|
|
p.style.font.color.rgb = RGBColor(255, 0, 0)
|
|
p.paragraph_format.space_before = Pt(0)
|
|
p.paragraph_format.space_after = Pt(3)
|
|
|
|
# Salto de página solo entre partes principales (no después de la última)
|
|
if idx < len(partes_list) - 1:
|
|
doc.add_page_break()
|
|
|
|
# Guardar documento
|
|
print(f"\n💾 Guardando documento en {OUTPUT_FILE}...")
|
|
doc.save(str(OUTPUT_FILE))
|
|
|
|
file_size_mb = OUTPUT_FILE.stat().st_size / 1024 / 1024
|
|
print(f"\n✅ Documento generado exitosamente!")
|
|
print(f" 📁 Archivo: {OUTPUT_FILE}")
|
|
print(f" 📊 Tamaño: {file_size_mb:.2f} MB")
|
|
print(f" 📄 Archivos procesados: {archivo_actual}/{total_archivos}")
|
|
print("\n" + "=" * 70)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|