Files
despacho/exportar.py
2026-07-18 11:57:58 -06:00

234 lines
6.6 KiB
Python

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