[A04.P005] DASUTEN: Pipeline refactorizado + Documentación + Informes
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
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user