#!/usr/bin/env python3 """ Exportador de contratos a .docx con formato forense mexicano. Convierte los archivos _v2.md a documentos Word con: - Times New Roman 12 - Interlineado 1.5 - Sangría 1.27 cm en cuerpo - Título centrado y MAYÚSCULAS - Alineación justificada Uso: python3 exportar.py # Exporta todos los _v2.md python3 exportar.py contrato-cdmx-ejemplo_v2.md # Exporta uno específico """ import re import sys import os from pathlib import Path try: from docx import Document from docx.shared import Pt, Cm from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.oxml.ns import qn except ImportError: print("ERROR: Se requiere python-docx. Instalar con: pip install python-docx") sys.exit(1) def configure_font(run, size=12, bold=False, italic=False, name="Times New Roman"): run.font.name = name run.font.size = Pt(size) run.bold = bold run.italic = italic # Forzar fuente en XML para compatibilidad rpr = run._element.get_or_add_rPr() rFonts = rpr.find(qn('w:rFonts')) if rFonts is None: rFonts = run._element.makeelement(qn('w:rFonts'), {}) rpr.insert(0, rFonts) rFonts.set(qn('w:eastAsia'), name) rFonts.set(qn('w:cs'), name) def set_paragraph_spacing(paragraph, line_spacing=1.5): pf = paragraph.paragraph_format pf.line_spacing = line_spacing pf.space_before = Pt(0) pf.space_after = Pt(6) def add_formatted_paragraph(doc, text, alignment=WD_ALIGN_PARAGRAPH.JUSTIFY, bold=False, size=12, indent_cm=0, space_after=6): """Agrega un párrafo con formato estándar.""" p = doc.add_paragraph() p.alignment = alignment set_paragraph_spacing(p) pf = p.paragraph_format pf.space_after = Pt(space_after) if indent_cm > 0: pf.first_line_indent = Cm(indent_cm) run = p.add_run(text) configure_font(run, size=size, bold=bold) return p def add_separator(doc): """Agrega una línea separadora.""" p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER run = p.add_run("— — —") configure_font(run, size=12, bold=False) return p def add_signature_block(doc, lines): """Agrega un bloque de firmas.""" for line in lines: p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.LEFT set_paragraph_spacing(p) run = p.add_run(line) configure_font(run, size=12, bold=False) def markdown_to_docx(md_path, output_dir=None): """Convierte un archivo markdown de contrato a .docx.""" print(f"Procesando: {md_path}") with open(md_path, "r", encoding="utf-8") as f: content = f.read() doc = Document() # Configurar márgenes for section in doc.sections: section.top_margin = Cm(2.5) section.bottom_margin = Cm(2.5) section.left_margin = Cm(3) section.right_margin = Cm(2.5) lines = content.split("\n") i = 0 while i < len(lines): line = lines[i].strip() i += 1 if not line: continue # Separador de secciones if line == "- - -": add_separator(doc) continue # Título principal (primera línea en MAYÚSCULAS) if line == line.upper() and len(line) > 10 and not line.startswith("**") and i < 10: add_formatted_paragraph( doc, line, alignment=WD_ALIGN_PARAGRAPH.CENTER, bold=True, size=14, space_after=12 ) continue # Encabezados de secciones: D E C L A R A C I O N E S, C L Á U S U L A S if line.startswith("**") and line.endswith("**") and i < len(lines): clean = line.strip("*") add_formatted_paragraph( doc, clean, alignment=WD_ALIGN_PARAGRAPH.CENTER, bold=True, size=12, space_after=10 ) continue if line.startswith("D E C L A R A C I O N E S") or line.startswith("C L Á U S U L A S"): add_formatted_paragraph( doc, line, alignment=WD_ALIGN_PARAGRAPH.CENTER, bold=True, size=12, space_after=10 ) continue # Líneas en negritas con ** (títulos de cláusulas como PRIMERA. — OBJETO) if line.startswith("**") and line.endswith("**") and len(line) > 4: clean = line.strip("*") add_formatted_paragraph( doc, clean, alignment=WD_ALIGN_PARAGRAPH.LEFT, bold=True, size=12, space_after=6 ) continue # Negritas dentro de la línea (formato **texto**) if "**" in line: add_formatted_paragraph( doc, line.replace("**", ""), alignment=WD_ALIGN_PARAGRAPH.JUSTIFY, bold=False, size=12, indent_cm=1.27, space_after=6 ) continue # Bloque de ciudad y fecha if " a " in line and (" de " in line) and i < 5: add_formatted_paragraph( doc, line, alignment=WD_ALIGN_PARAGRAPH.LEFT, bold=False, size=12, space_after=6 ) continue # Texto normal — cuerpo del contrato add_formatted_paragraph( doc, line, alignment=WD_ALIGN_PARAGRAPH.JUSTIFY, bold=False, size=12, indent_cm=1.27, space_after=6 ) # Guardar archivo md_path = Path(md_path) if output_dir: output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) output_path = output_dir / f"{md_path.stem}.docx" else: output_path = md_path.with_suffix(".docx") doc.save(str(output_path)) print(f" ✓ Exportado: {output_path}") return output_path def main(): # Buscar archivos _v2.md script_dir = Path(__file__).parent md_files = list(script_dir.glob("*_v2.md")) if not md_files: print("No se encontraron archivos *_v2.md en el directorio.") return # Crear directorio de salida output_dir = script_dir / "docx_output" for md_file in md_files: try: markdown_to_docx(md_file, output_dir=output_dir) except Exception as e: print(f" ✗ Error en {md_file}: {e}") print(f"\nExportación completa. Archivos en: {output_dir}/") if __name__ == "__main__": main()