Refactorización del pipeline de backups DASUTEN: 1. Flujo simplificado (un solo destino): - Backup directo a X:\ (share srv-ns8) - Eliminado paso intermedio E:\BK_SQL\sysdasuten\ - Reducido de 3 ubicaciones a 2 (X: → /var/tmp/) 2. Módulo común DasuExecutor (lib/dasu_executor.rb): - Centraliza ejecución PowerShell en dasu-sql4 - Centraliza verificación de archivos en fenix - Elimina código repetido en 6 drones (-52% líneas) 3. Drones refactorizados: - dasuten_exportar_srvv-fenix.rb (FULL → X:) - dasuten_exportar_diferencial_srvv-fenix.rb (DIF → X:) - dasuten_transferir_srvv-fenix-srv-ns8.rb (simplificado) - dasuten_restaurar_dasu-sql4.rb (usa DasuExecutor) - dasuten_restaurar_diferencial_dasu-sql4.rb (usa DasuExecutor) - dasuten_download_drive-dasu-sql4.rb (usa DasuExecutor) - dasuten_verificar-integridad_dasu-sql4.rb (usa DasuExecutor) 4. Integración BKPs mantenida: - T8-T15: Tareas individuales - C7: dasuten_full (pipeline semanal) - C8: dasuten_diferencial (pipeline diario) 5. Documentación actualizada: - A04.P006_Backups-DASUTEN.md: flujo simplificado + módulo común - Lecciones aprendidas agregadas 6. Nuevos archivos: - Informes técnicos DASUTEN (docs/informes/2026-04_DASUTEN-migracion/) - A01.P011/P012: Documentación de drones y pipeline - proc_linux.rb, ops.rb: Utilitarios del ecosistema 7. Scripts legacy (para referencia): - orquestador_pipeline.rb, orquestador_pipeline_diferencial.rb - dasuten_upload_srv-ns8-drive.rb
173 lines
3.0 KiB
Python
173 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Genera PDF del informe ejecutivo desde Markdown usando Chrome headless."""
|
|
import markdown
|
|
import subprocess
|
|
import sys
|
|
import os
|
|
|
|
SRC = "01_informe_ejecutivo_DASUTEN.md"
|
|
OUT = "01_informe_ejecutivo_DASUTEN.pdf"
|
|
TMP = "/tmp/_informe_tmp.html"
|
|
|
|
# Leer markdown
|
|
with open(SRC, "r", encoding="utf-8") as f:
|
|
md_text = f.read()
|
|
|
|
# Convertir a HTML
|
|
html_body = markdown.markdown(md_text, extensions=["tables", "fenced_code"])
|
|
|
|
# Envolver con estilos profesionales
|
|
html = f"""<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
|
|
@page {{
|
|
size: A4;
|
|
margin: 25mm 20mm 25mm 20mm;
|
|
}}
|
|
|
|
body {{
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
font-size: 11pt;
|
|
line-height: 1.6;
|
|
color: #1a1a2e;
|
|
max-width: 100%;
|
|
}}
|
|
|
|
h1 {{
|
|
font-size: 20pt;
|
|
font-weight: 700;
|
|
color: #0f3460;
|
|
margin-top: 0;
|
|
border-bottom: 3px solid #0f3460;
|
|
padding-bottom: 8px;
|
|
}}
|
|
|
|
h2 {{
|
|
font-size: 15pt;
|
|
font-weight: 600;
|
|
color: #16213e;
|
|
margin-top: 28px;
|
|
border-bottom: 1.5px solid #e0e0e0;
|
|
padding-bottom: 5px;
|
|
page-break-after: avoid;
|
|
}}
|
|
|
|
h3 {{
|
|
font-size: 12pt;
|
|
font-weight: 600;
|
|
color: #1a1a2e;
|
|
margin-top: 20px;
|
|
page-break-after: avoid;
|
|
}}
|
|
|
|
p, li {{
|
|
text-align: justify;
|
|
orphans: 3;
|
|
widows: 3;
|
|
}}
|
|
|
|
blockquote {{
|
|
border-left: 4px solid #0f3460;
|
|
margin: 0 0 16px 0;
|
|
padding: 12px 16px;
|
|
background: #f8f9fa;
|
|
font-size: 10pt;
|
|
color: #333;
|
|
}}
|
|
|
|
blockquote p {{
|
|
margin: 4px 0;
|
|
text-align: left;
|
|
}}
|
|
|
|
table {{
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 16px 0;
|
|
font-size: 10pt;
|
|
page-break-inside: avoid;
|
|
}}
|
|
|
|
th {{
|
|
background: #0f3460;
|
|
color: white;
|
|
font-weight: 600;
|
|
padding: 8px 12px;
|
|
text-align: left;
|
|
}}
|
|
|
|
td {{
|
|
padding: 7px 12px;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}}
|
|
|
|
tr:nth-child(even) {{
|
|
background: #f8f9fa;
|
|
}}
|
|
|
|
hr {{
|
|
border: none;
|
|
border-top: 1px solid #ccc;
|
|
margin: 24px 0;
|
|
}}
|
|
|
|
ul {{
|
|
padding-left: 20px;
|
|
}}
|
|
|
|
li {{
|
|
margin-bottom: 4px;
|
|
}}
|
|
|
|
em {{
|
|
color: #555;
|
|
}}
|
|
|
|
strong {{
|
|
color: #0f3460;
|
|
}}
|
|
|
|
code {{
|
|
background: #f0f0f0;
|
|
padding: 2px 5px;
|
|
border-radius: 3px;
|
|
font-size: 9.5pt;
|
|
}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
{html_body}
|
|
</body>
|
|
</html>"""
|
|
|
|
# Guardar HTML temporal
|
|
with open(TMP, "w", encoding="utf-8") as f:
|
|
f.write(html)
|
|
|
|
# Generar PDF con Chrome headless
|
|
result = subprocess.run([
|
|
"google-chrome",
|
|
"--headless",
|
|
"--disable-gpu",
|
|
"--no-sandbox",
|
|
"--disable-software-rasterizer",
|
|
f"--print-to-pdf={OUT}",
|
|
"--print-to-pdf-no-header",
|
|
TMP
|
|
], capture_output=True, text=True)
|
|
|
|
if os.path.exists(OUT):
|
|
size = os.path.getsize(OUT) / 1024
|
|
print(f"✅ PDF generado: {OUT} ({size:.0f} KB)")
|
|
else:
|
|
print(f"❌ Error al generar PDF")
|
|
print(result.stderr)
|
|
sys.exit(1)
|
|
|
|
# Limpiar
|
|
os.remove(TMP)
|