[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-/blog/tutorial/backup-automatizado-python-cron":3,"prev-/blog/tutorial/backup-automatizado-python-cron":4447,"next-/blog/tutorial/backup-automatizado-python-cron":4450,"related-/blog/tutorial/backup-automatizado-python-cron":4453},{"id":4,"title":5,"author":6,"authorUrl":7,"body":8,"category":4414,"cta":4415,"date":4418,"dateModified":4418,"description":4419,"draft":4420,"extension":4421,"faq":4422,"featured":4420,"image":4435,"imageAlt":4436,"meta":4437,"navigation":81,"path":4438,"readingTime":205,"seo":4439,"stem":4440,"tags":4441,"__hash__":4446},"blog/blog/tutorial/backup-automatizado-python-cron.md","Backup automatizado con Python y cron en servidores Linux","Syswork México","/nosotros",{"type":9,"value":10,"toc":4398},"minimark",[11,21,29,34,47,96,100,108,123,127,130,630,637,710,713,717,720,3825,3827,3831,3834,3854,3857,3873,3910,3914,3917,3938,3941,3958,3963,3969,3972,3986,3990,3993,3997,4000,4027,4031,4034,4271,4273,4277,4280,4287,4293,4336,4339,4345,4349,4352,4385,4394],[12,13,14,15,20],"p",{},"El backup más peligroso es el que crees que tienes pero nunca has probado. En nuestra experiencia dando ",[16,17,19],"a",{"href":18},"/servicios/soporte-tecnico","soporte técnico"," a empresas, el escenario más común no es que no tengan backups — es que los configuraron una vez, nunca los probaron y cuando los necesitaron, estaban corruptos, incompletos o eran de hace tres meses.",[12,22,23,24,28],{},"En esta guía vas a crear un sistema de backup automatizado con ",[16,25,27],{"href":26},"/tecnologias/python","Python"," que respalda bases de datos y archivos, comprime todo, sube a un destino remoto, limpia respaldos antiguos y te notifica si algo falla.",[30,31,33],"h2",{"id":32},"requisitos-previos","Requisitos previos",[12,35,36,37,41,42,46],{},"Necesitas un servidor Linux con Python 3.10+ y acceso a las bases de datos que quieres respaldar. Vamos a usar solo librerías estándar de Python más ",[38,39,40],"code",{},"pg_dump"," para ",[16,43,45],{"href":44},"/tecnologias/postgresql","PostgreSQL",".",[48,49,54],"pre",{"className":50,"code":51,"language":52,"meta":53,"style":53},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","# Verificar Python\npython3 --version\n\n# Verificar pg_dump (para backups de PostgreSQL)\npg_dump --version\n","bash","",[38,55,56,65,76,83,89],{"__ignoreMap":53},[57,58,61],"span",{"class":59,"line":60},"line",1,[57,62,64],{"class":63},"sutJx","# Verificar Python\n",[57,66,68,72],{"class":59,"line":67},2,[57,69,71],{"class":70},"sbgvK","python3",[57,73,75],{"class":74},"stzsN"," --version\n",[57,77,79],{"class":59,"line":78},3,[57,80,82],{"emptyLinePlaceholder":81},true,"\n",[57,84,86],{"class":59,"line":85},4,[57,87,88],{"class":63},"# Verificar pg_dump (para backups de PostgreSQL)\n",[57,90,92,94],{"class":59,"line":91},5,[57,93,40],{"class":70},[57,95,75],{"class":74},[30,97,99],{"id":98},"estructura-del-proyecto","Estructura del proyecto",[48,101,106],{"className":102,"code":104,"language":105},[103],"language-text","backup/\n├── backup.py          # Script principal\n├── config.py          # Configuración\n├── .env               # Credenciales (no se sube a git)\n└── logs/              # Logs de ejecución\n","text",[38,107,104],{"__ignoreMap":53},[109,110,113],"alert",{"title":111,"type":112},"¿Por qué Python y no bash?","info",[12,114,115,116,118,119,122],{},"Para un ",[38,117,40],{}," + ",[38,120,121],{},"tar"," simple, bash es suficiente. Usamos Python cuando necesitas manejo robusto de errores, notificaciones, rotación inteligente, subida a S3 y logs estructurados. Además, es más fácil de extender conforme crecen tus necesidades.",[30,124,126],{"id":125},"paso-1-configuración","Paso 1: Configuración",[12,128,129],{},"Primero, el archivo de configuración que define qué respaldar y dónde guardarlo:",[48,131,135],{"className":132,"code":133,"language":134,"meta":53,"style":53},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","# config.py\nimport os\nfrom pathlib import Path\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\n# Directorio de backups\nBACKUP_DIR = Path(os.getenv(\"BACKUP_DIR\", \"/var/backups/app\"))\nBACKUP_DIR.mkdir(parents=True, exist_ok=True)\n\n# Retención: cuántos días conservar los backups\nRETENTION_DAYS = int(os.getenv(\"RETENTION_DAYS\", \"30\"))\n\n# PostgreSQL\nPG_HOST = os.getenv(\"PG_HOST\", \"localhost\")\nPG_PORT = os.getenv(\"PG_PORT\", \"5432\")\nPG_USER = os.getenv(\"PG_USER\", \"postgres\")\nPG_PASSWORD = os.getenv(\"PG_PASSWORD\", \"\")\nPG_DATABASES = os.getenv(\"PG_DATABASES\", \"miapp\").split(\",\")\n\n# Directorios de archivos a respaldar\nFILE_DIRS = [\n    \"/var/www/app/uploads\",\n    \"/etc/nginx/sites-enabled\",\n    \"/etc/postgresql/16/main\",\n]\n\n# Notificaciones (Slack webhook, opcional)\nSLACK_WEBHOOK = os.getenv(\"SLACK_WEBHOOK\", \"\")\n","python",[38,136,137,142,152,165,177,181,192,197,203,253,288,293,299,338,343,349,383,416,449,478,525,530,536,547,561,573,585,591,596,602],{"__ignoreMap":53},[57,138,139],{"class":59,"line":60},[57,140,141],{"class":63},"# config.py\n",[57,143,144,148],{"class":59,"line":67},[57,145,147],{"class":146},"sVHd0","import",[57,149,151],{"class":150},"su5hD"," os\n",[57,153,154,157,160,162],{"class":59,"line":78},[57,155,156],{"class":146},"from",[57,158,159],{"class":150}," pathlib ",[57,161,147],{"class":146},[57,163,164],{"class":150}," Path\n",[57,166,167,169,172,174],{"class":59,"line":85},[57,168,156],{"class":146},[57,170,171],{"class":150}," dotenv ",[57,173,147],{"class":146},[57,175,176],{"class":150}," load_dotenv\n",[57,178,179],{"class":59,"line":91},[57,180,82],{"emptyLinePlaceholder":81},[57,182,184,188],{"class":59,"line":183},6,[57,185,187],{"class":186},"slqww","load_dotenv",[57,189,191],{"class":190},"sP7_E","()\n",[57,193,195],{"class":59,"line":194},7,[57,196,82],{"emptyLinePlaceholder":81},[57,198,200],{"class":59,"line":199},8,[57,201,202],{"class":63},"# Directorio de backups\n",[57,204,206,210,214,217,220,223,225,228,230,234,237,239,242,245,248,250],{"class":59,"line":205},9,[57,207,209],{"class":208},"s_hVV","BACKUP_DIR",[57,211,213],{"class":212},"smGrS"," =",[57,215,216],{"class":186}," Path",[57,218,219],{"class":190},"(",[57,221,222],{"class":186},"os",[57,224,46],{"class":190},[57,226,227],{"class":186},"getenv",[57,229,219],{"class":190},[57,231,233],{"class":232},"sjJ54","\"",[57,235,209],{"class":236},"s_sjI",[57,238,233],{"class":232},[57,240,241],{"class":190},",",[57,243,244],{"class":232}," \"",[57,246,247],{"class":236},"/var/backups/app",[57,249,233],{"class":232},[57,251,252],{"class":190},"))\n",[57,254,256,258,260,263,265,269,272,276,278,281,283,285],{"class":59,"line":255},10,[57,257,209],{"class":208},[57,259,46],{"class":190},[57,261,262],{"class":186},"mkdir",[57,264,219],{"class":190},[57,266,268],{"class":267},"s99_P","parents",[57,270,271],{"class":212},"=",[57,273,275],{"class":274},"s39Yj","True",[57,277,241],{"class":190},[57,279,280],{"class":267}," exist_ok",[57,282,271],{"class":212},[57,284,275],{"class":274},[57,286,287],{"class":190},")\n",[57,289,291],{"class":59,"line":290},11,[57,292,82],{"emptyLinePlaceholder":81},[57,294,296],{"class":59,"line":295},12,[57,297,298],{"class":63},"# Retención: cuántos días conservar los backups\n",[57,300,302,305,307,311,313,315,317,319,321,323,325,327,329,331,334,336],{"class":59,"line":301},13,[57,303,304],{"class":208},"RETENTION_DAYS",[57,306,213],{"class":212},[57,308,310],{"class":309},"sZMiF"," int",[57,312,219],{"class":190},[57,314,222],{"class":186},[57,316,46],{"class":190},[57,318,227],{"class":186},[57,320,219],{"class":190},[57,322,233],{"class":232},[57,324,304],{"class":236},[57,326,233],{"class":232},[57,328,241],{"class":190},[57,330,244],{"class":232},[57,332,333],{"class":236},"30",[57,335,233],{"class":232},[57,337,252],{"class":190},[57,339,341],{"class":59,"line":340},14,[57,342,82],{"emptyLinePlaceholder":81},[57,344,346],{"class":59,"line":345},15,[57,347,348],{"class":63},"# PostgreSQL\n",[57,350,352,355,357,360,362,364,366,368,370,372,374,376,379,381],{"class":59,"line":351},16,[57,353,354],{"class":208},"PG_HOST",[57,356,213],{"class":212},[57,358,359],{"class":150}," os",[57,361,46],{"class":190},[57,363,227],{"class":186},[57,365,219],{"class":190},[57,367,233],{"class":232},[57,369,354],{"class":236},[57,371,233],{"class":232},[57,373,241],{"class":190},[57,375,244],{"class":232},[57,377,378],{"class":236},"localhost",[57,380,233],{"class":232},[57,382,287],{"class":190},[57,384,386,389,391,393,395,397,399,401,403,405,407,409,412,414],{"class":59,"line":385},17,[57,387,388],{"class":208},"PG_PORT",[57,390,213],{"class":212},[57,392,359],{"class":150},[57,394,46],{"class":190},[57,396,227],{"class":186},[57,398,219],{"class":190},[57,400,233],{"class":232},[57,402,388],{"class":236},[57,404,233],{"class":232},[57,406,241],{"class":190},[57,408,244],{"class":232},[57,410,411],{"class":236},"5432",[57,413,233],{"class":232},[57,415,287],{"class":190},[57,417,419,422,424,426,428,430,432,434,436,438,440,442,445,447],{"class":59,"line":418},18,[57,420,421],{"class":208},"PG_USER",[57,423,213],{"class":212},[57,425,359],{"class":150},[57,427,46],{"class":190},[57,429,227],{"class":186},[57,431,219],{"class":190},[57,433,233],{"class":232},[57,435,421],{"class":236},[57,437,233],{"class":232},[57,439,241],{"class":190},[57,441,244],{"class":232},[57,443,444],{"class":236},"postgres",[57,446,233],{"class":232},[57,448,287],{"class":190},[57,450,452,455,457,459,461,463,465,467,469,471,473,476],{"class":59,"line":451},19,[57,453,454],{"class":208},"PG_PASSWORD",[57,456,213],{"class":212},[57,458,359],{"class":150},[57,460,46],{"class":190},[57,462,227],{"class":186},[57,464,219],{"class":190},[57,466,233],{"class":232},[57,468,454],{"class":236},[57,470,233],{"class":232},[57,472,241],{"class":190},[57,474,475],{"class":232}," \"\"",[57,477,287],{"class":190},[57,479,481,484,486,488,490,492,494,496,498,500,502,504,507,509,512,515,517,519,521,523],{"class":59,"line":480},20,[57,482,483],{"class":208},"PG_DATABASES",[57,485,213],{"class":212},[57,487,359],{"class":150},[57,489,46],{"class":190},[57,491,227],{"class":186},[57,493,219],{"class":190},[57,495,233],{"class":232},[57,497,483],{"class":236},[57,499,233],{"class":232},[57,501,241],{"class":190},[57,503,244],{"class":232},[57,505,506],{"class":236},"miapp",[57,508,233],{"class":232},[57,510,511],{"class":190},").",[57,513,514],{"class":186},"split",[57,516,219],{"class":190},[57,518,233],{"class":232},[57,520,241],{"class":236},[57,522,233],{"class":232},[57,524,287],{"class":190},[57,526,528],{"class":59,"line":527},21,[57,529,82],{"emptyLinePlaceholder":81},[57,531,533],{"class":59,"line":532},22,[57,534,535],{"class":63},"# Directorios de archivos a respaldar\n",[57,537,539,542,544],{"class":59,"line":538},23,[57,540,541],{"class":208},"FILE_DIRS",[57,543,213],{"class":212},[57,545,546],{"class":190}," [\n",[57,548,550,553,556,558],{"class":59,"line":549},24,[57,551,552],{"class":232},"    \"",[57,554,555],{"class":236},"/var/www/app/uploads",[57,557,233],{"class":232},[57,559,560],{"class":190},",\n",[57,562,564,566,569,571],{"class":59,"line":563},25,[57,565,552],{"class":232},[57,567,568],{"class":236},"/etc/nginx/sites-enabled",[57,570,233],{"class":232},[57,572,560],{"class":190},[57,574,576,578,581,583],{"class":59,"line":575},26,[57,577,552],{"class":232},[57,579,580],{"class":236},"/etc/postgresql/16/main",[57,582,233],{"class":232},[57,584,560],{"class":190},[57,586,588],{"class":59,"line":587},27,[57,589,590],{"class":190},"]\n",[57,592,594],{"class":59,"line":593},28,[57,595,82],{"emptyLinePlaceholder":81},[57,597,599],{"class":59,"line":598},29,[57,600,601],{"class":63},"# Notificaciones (Slack webhook, opcional)\n",[57,603,605,608,610,612,614,616,618,620,622,624,626,628],{"class":59,"line":604},30,[57,606,607],{"class":208},"SLACK_WEBHOOK",[57,609,213],{"class":212},[57,611,359],{"class":150},[57,613,46],{"class":190},[57,615,227],{"class":186},[57,617,219],{"class":190},[57,619,233],{"class":232},[57,621,607],{"class":236},[57,623,233],{"class":232},[57,625,241],{"class":190},[57,627,475],{"class":232},[57,629,287],{"class":190},[12,631,632,633,636],{},"El archivo ",[38,634,635],{},".env"," con las credenciales:",[48,638,640],{"className":50,"code":639,"language":52,"meta":53,"style":53},"# .env\nPG_HOST=localhost\nPG_USER=app\nPG_PASSWORD=tu_contraseña_segura\nPG_DATABASES=miapp,otrabase\nBACKUP_DIR=/var/backups/app\nRETENTION_DAYS=30\nSLACK_WEBHOOK=https://hooks.slack.com/services/xxx/yyy/zzz\n",[38,641,642,647,656,665,674,683,692,701],{"__ignoreMap":53},[57,643,644],{"class":59,"line":60},[57,645,646],{"class":63},"# .env\n",[57,648,649,651,653],{"class":59,"line":67},[57,650,354],{"class":150},[57,652,271],{"class":212},[57,654,655],{"class":236},"localhost\n",[57,657,658,660,662],{"class":59,"line":78},[57,659,421],{"class":150},[57,661,271],{"class":212},[57,663,664],{"class":236},"app\n",[57,666,667,669,671],{"class":59,"line":85},[57,668,454],{"class":150},[57,670,271],{"class":212},[57,672,673],{"class":236},"tu_contraseña_segura\n",[57,675,676,678,680],{"class":59,"line":91},[57,677,483],{"class":150},[57,679,271],{"class":212},[57,681,682],{"class":236},"miapp,otrabase\n",[57,684,685,687,689],{"class":59,"line":183},[57,686,209],{"class":150},[57,688,271],{"class":212},[57,690,691],{"class":236},"/var/backups/app\n",[57,693,694,696,698],{"class":59,"line":194},[57,695,304],{"class":150},[57,697,271],{"class":212},[57,699,700],{"class":236},"30\n",[57,702,703,705,707],{"class":59,"line":199},[57,704,607],{"class":150},[57,706,271],{"class":212},[57,708,709],{"class":236},"https://hooks.slack.com/services/xxx/yyy/zzz\n",[711,712],"ad-banner",{},[30,714,716],{"id":715},"paso-2-script-principal-de-backup","Paso 2: Script principal de backup",[12,718,719],{},"El script que ejecuta todo el proceso:",[48,721,723],{"className":132,"code":722,"language":134,"meta":53,"style":53},"#!/usr/bin/env python3\n\"\"\"\nbackup.py — Backup automatizado de bases de datos y archivos.\nUso: python3 backup.py\n\"\"\"\n\nimport subprocess\nimport tarfile\nimport logging\nimport json\nfrom datetime import datetime, timedelta\nfrom pathlib import Path\nfrom urllib.request import Request, urlopen\n\nimport config\n\n# --- Logging ---\nlog_file = config.BACKUP_DIR / \"logs\" / f\"backup-{datetime.now():%Y-%m-%d}.log\"\nlog_file.parent.mkdir(parents=True, exist_ok=True)\n\nlogging.basicConfig(\n    level=logging.INFO,\n    format=\"%(asctime)s [%(levelname)s] %(message)s\",\n    handlers=[\n        logging.FileHandler(log_file),\n        logging.StreamHandler(),\n    ],\n)\nlog = logging.getLogger(__name__)\n\n# --- Timestamp ---\nTIMESTAMP = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\nBACKUP_SUBDIR = config.BACKUP_DIR / TIMESTAMP\nBACKUP_SUBDIR.mkdir(parents=True, exist_ok=True)\n\n\ndef backup_postgresql():\n    \"\"\"Respalda cada base de datos PostgreSQL con pg_dump.\"\"\"\n    log.info(\"Iniciando backup de PostgreSQL...\")\n    dumps = []\n\n    for db in config.PG_DATABASES:\n        db = db.strip()\n        dump_file = BACKUP_SUBDIR / f\"{db}.sql.gz\"\n\n        cmd = (\n            f\"PGPASSWORD='{config.PG_PASSWORD}' \"\n            f\"pg_dump -h {config.PG_HOST} -p {config.PG_PORT} \"\n            f\"-U {config.PG_USER} -d {db} -Fc -Z6 -f {dump_file}\"\n        )\n\n        try:\n            subprocess.run(cmd, shell=True, check=True, capture_output=True)\n            size_mb = dump_file.stat().st_size / (1024 * 1024)\n            log.info(f\"  ✓ {db} → {dump_file.name} ({size_mb:.1f} MB)\")\n            dumps.append({\"db\": db, \"file\": str(dump_file), \"size_mb\": round(size_mb, 1)})\n        except subprocess.CalledProcessError as e:\n            log.error(f\"  ✗ Error en {db}: {e.stderr.decode()}\")\n            raise\n\n    return dumps\n\n\ndef backup_files():\n    \"\"\"Comprime directorios de archivos en un tar.gz.\"\"\"\n    log.info(\"Iniciando backup de archivos...\")\n    archive_file = BACKUP_SUBDIR / \"archivos.tar.gz\"\n\n    with tarfile.open(archive_file, \"w:gz\") as tar:\n        for dir_path in config.FILE_DIRS:\n            path = Path(dir_path)\n            if path.exists():\n                tar.add(str(path), arcname=path.name)\n                log.info(f\"  ✓ {dir_path}\")\n            else:\n                log.warning(f\"  ⚠ {dir_path} no existe, saltando...\")\n\n    size_mb = archive_file.stat().st_size / (1024 * 1024)\n    log.info(f\"  Archivo: {archive_file.name} ({size_mb:.1f} MB)\")\n    return {\"file\": str(archive_file), \"size_mb\": round(size_mb, 1)}\n\n\ndef cleanup_old_backups():\n    \"\"\"Elimina backups más antiguos que RETENTION_DAYS.\"\"\"\n    log.info(f\"Limpiando backups con más de {config.RETENTION_DAYS} días...\")\n    cutoff = datetime.now() - timedelta(days=config.RETENTION_DAYS)\n    removed = 0\n\n    for item in config.BACKUP_DIR.iterdir():\n        if item.is_dir() and item.name != \"logs\":\n            try:\n                dir_date = datetime.strptime(item.name[:8], \"%Y%m%d\")\n                if dir_date \u003C cutoff:\n                    import shutil\n                    shutil.rmtree(item)\n                    log.info(f\"  ✓ Eliminado: {item.name}\")\n                    removed += 1\n            except ValueError:\n                continue\n\n    log.info(f\"  {removed} backup(s) eliminado(s)\")\n    return removed\n\n\ndef notify_slack(message: str, success: bool = True):\n    \"\"\"Envía notificación a Slack.\"\"\"\n    if not config.SLACK_WEBHOOK:\n        return\n\n    color = \"#36a64f\" if success else \"#ff0000\"\n    emoji = \"✅\" if success else \"🚨\"\n\n    payload = {\n        \"attachments\": [{\n            \"color\": color,\n            \"title\": f\"{emoji} Backup {'completado' if success else 'FALLIDO'}\",\n            \"text\": message,\n            \"ts\": int(datetime.now().timestamp()),\n        }]\n    }\n\n    try:\n        req = Request(\n            config.SLACK_WEBHOOK,\n            data=json.dumps(payload).encode(),\n            headers={\"Content-Type\": \"application/json\"},\n        )\n        urlopen(req, timeout=10)\n    except Exception as e:\n        log.warning(f\"No se pudo notificar a Slack: {e}\")\n\n\ndef main():\n    \"\"\"Ejecuta el proceso completo de backup.\"\"\"\n    log.info(f\"{'='*50}\")\n    log.info(f\"BACKUP INICIADO — {TIMESTAMP}\")\n    log.info(f\"{'='*50}\")\n\n    start = datetime.now()\n    results = {\"databases\": [], \"files\": None, \"cleaned\": 0}\n\n    try:\n        # 1. Backup de bases de datos\n        results[\"databases\"] = backup_postgresql()\n\n        # 2. Backup de archivos\n        results[\"files\"] = backup_files()\n\n        # 3. Limpiar backups antiguos\n        results[\"cleaned\"] = cleanup_old_backups()\n\n        # Resumen\n        elapsed = (datetime.now() - start).total_seconds()\n        total_size = sum(d[\"size_mb\"] for d in results[\"databases\"])\n        if results[\"files\"]:\n            total_size += results[\"files\"][\"size_mb\"]\n\n        summary = (\n            f\"Servidor: {config.PG_HOST}\\n\"\n            f\"Bases de datos: {len(results['databases'])}\\n\"\n            f\"Tamaño total: {total_size:.1f} MB\\n\"\n            f\"Backups eliminados: {results['cleaned']}\\n\"\n            f\"Duración: {elapsed:.0f}s\"\n        )\n\n        log.info(f\"\\n{'='*50}\")\n        log.info(f\"BACKUP COMPLETADO en {elapsed:.0f}s — {total_size:.1f} MB total\")\n        log.info(f\"{'='*50}\")\n\n        notify_slack(summary, success=True)\n\n    except Exception as e:\n        log.error(f\"BACKUP FALLIDO: {e}\")\n        notify_slack(f\"Error: {e}\\nServidor: {config.PG_HOST}\", success=False)\n        raise\n\n\nif __name__ == \"__main__\":\n    main()\n",[38,724,725,730,736,742,747,751,755,762,769,776,783,800,810,832,836,843,847,852,924,957,961,974,990,1018,1028,1045,1057,1062,1066,1088,1092,1098,1135,1154,1181,1186,1191,1204,1215,1236,1247,1252,1273,1291,1318,1323,1334,1357,1391,1431,1437,1442,1450,1495,1532,1587,1657,1679,1729,1735,1740,1749,1754,1759,1769,1779,1799,1818,1823,1861,1880,1897,1913,1949,1975,1983,2011,2016,2047,2087,2132,2137,2142,2152,2162,2193,2230,2241,2246,2269,2305,2313,2356,2373,2382,2399,2430,2442,2453,2459,2464,2492,2500,2505,2510,2546,2556,2573,2579,2584,2615,2643,2648,2659,2675,2693,2749,2765,2795,2801,2807,2812,2820,2832,2844,2872,2901,2906,2929,2944,2971,2976,2981,2991,3001,3034,3060,3091,3096,3112,3163,3168,3175,3181,3205,3210,3216,3237,3242,3248,3269,3274,3280,3310,3357,3375,3404,3409,3419,3442,3477,3501,3529,3550,3555,3560,3593,3633,3664,3669,3690,3695,3708,3734,3780,3786,3791,3796,3817],{"__ignoreMap":53},[57,726,727],{"class":59,"line":60},[57,728,729],{"class":63},"#!/usr/bin/env python3\n",[57,731,732],{"class":59,"line":67},[57,733,735],{"class":734},"s2W-s","\"\"\"\n",[57,737,738],{"class":59,"line":78},[57,739,741],{"class":740},"sithA","backup.py — Backup automatizado de bases de datos y archivos.\n",[57,743,744],{"class":59,"line":85},[57,745,746],{"class":740},"Uso: python3 backup.py\n",[57,748,749],{"class":59,"line":91},[57,750,735],{"class":734},[57,752,753],{"class":59,"line":183},[57,754,82],{"emptyLinePlaceholder":81},[57,756,757,759],{"class":59,"line":194},[57,758,147],{"class":146},[57,760,761],{"class":150}," subprocess\n",[57,763,764,766],{"class":59,"line":199},[57,765,147],{"class":146},[57,767,768],{"class":150}," tarfile\n",[57,770,771,773],{"class":59,"line":205},[57,772,147],{"class":146},[57,774,775],{"class":150}," logging\n",[57,777,778,780],{"class":59,"line":255},[57,779,147],{"class":146},[57,781,782],{"class":150}," json\n",[57,784,785,787,790,792,795,797],{"class":59,"line":290},[57,786,156],{"class":146},[57,788,789],{"class":150}," datetime ",[57,791,147],{"class":146},[57,793,794],{"class":150}," datetime",[57,796,241],{"class":190},[57,798,799],{"class":150}," timedelta\n",[57,801,802,804,806,808],{"class":59,"line":295},[57,803,156],{"class":146},[57,805,159],{"class":150},[57,807,147],{"class":146},[57,809,164],{"class":150},[57,811,812,814,817,819,822,824,827,829],{"class":59,"line":301},[57,813,156],{"class":146},[57,815,816],{"class":150}," urllib",[57,818,46],{"class":190},[57,820,821],{"class":150},"request ",[57,823,147],{"class":146},[57,825,826],{"class":150}," Request",[57,828,241],{"class":190},[57,830,831],{"class":150}," urlopen\n",[57,833,834],{"class":59,"line":340},[57,835,82],{"emptyLinePlaceholder":81},[57,837,838,840],{"class":59,"line":345},[57,839,147],{"class":146},[57,841,842],{"class":150}," config\n",[57,844,845],{"class":59,"line":351},[57,846,82],{"emptyLinePlaceholder":81},[57,848,849],{"class":59,"line":385},[57,850,851],{"class":63},"# --- Logging ---\n",[57,853,854,857,859,862,864,867,870,872,875,877,879,883,886,890,893,895,898,901,904,907,910,913,915,918,921],{"class":59,"line":418},[57,855,856],{"class":150},"log_file ",[57,858,271],{"class":212},[57,860,861],{"class":150}," config",[57,863,46],{"class":190},[57,865,209],{"class":866},"swQdS",[57,868,869],{"class":212}," /",[57,871,244],{"class":232},[57,873,874],{"class":236},"logs",[57,876,233],{"class":232},[57,878,869],{"class":212},[57,880,882],{"class":881},"sbsja"," f",[57,884,885],{"class":236},"\"backup-",[57,887,889],{"class":888},"srdBf","{",[57,891,892],{"class":150},"datetime",[57,894,46],{"class":190},[57,896,897],{"class":186},"now",[57,899,900],{"class":190},"():",[57,902,903],{"class":212},"%",[57,905,906],{"class":150},"Y",[57,908,909],{"class":212},"-%",[57,911,912],{"class":150},"m",[57,914,909],{"class":212},[57,916,917],{"class":150},"d",[57,919,920],{"class":888},"}",[57,922,923],{"class":236},".log\"\n",[57,925,926,929,931,935,937,939,941,943,945,947,949,951,953,955],{"class":59,"line":451},[57,927,928],{"class":150},"log_file",[57,930,46],{"class":190},[57,932,934],{"class":933},"skxfh","parent",[57,936,46],{"class":190},[57,938,262],{"class":186},[57,940,219],{"class":190},[57,942,268],{"class":267},[57,944,271],{"class":212},[57,946,275],{"class":274},[57,948,241],{"class":190},[57,950,280],{"class":267},[57,952,271],{"class":212},[57,954,275],{"class":274},[57,956,287],{"class":190},[57,958,959],{"class":59,"line":480},[57,960,82],{"emptyLinePlaceholder":81},[57,962,963,966,968,971],{"class":59,"line":527},[57,964,965],{"class":150},"logging",[57,967,46],{"class":190},[57,969,970],{"class":186},"basicConfig",[57,972,973],{"class":190},"(\n",[57,975,976,979,981,983,985,988],{"class":59,"line":532},[57,977,978],{"class":267},"    level",[57,980,271],{"class":212},[57,982,965],{"class":186},[57,984,46],{"class":190},[57,986,987],{"class":866},"INFO",[57,989,560],{"class":190},[57,991,992,995,997,999,1002,1005,1008,1011,1014,1016],{"class":59,"line":538},[57,993,994],{"class":267},"    format",[57,996,271],{"class":212},[57,998,233],{"class":232},[57,1000,1001],{"class":888},"%(asctime)s",[57,1003,1004],{"class":236}," [",[57,1006,1007],{"class":888},"%(levelname)s",[57,1009,1010],{"class":236},"] ",[57,1012,1013],{"class":888},"%(message)s",[57,1015,233],{"class":232},[57,1017,560],{"class":190},[57,1019,1020,1023,1025],{"class":59,"line":549},[57,1021,1022],{"class":267},"    handlers",[57,1024,271],{"class":212},[57,1026,1027],{"class":190},"[\n",[57,1029,1030,1033,1035,1038,1040,1042],{"class":59,"line":563},[57,1031,1032],{"class":186},"        logging",[57,1034,46],{"class":190},[57,1036,1037],{"class":186},"FileHandler",[57,1039,219],{"class":190},[57,1041,928],{"class":186},[57,1043,1044],{"class":190},"),\n",[57,1046,1047,1049,1051,1054],{"class":59,"line":575},[57,1048,1032],{"class":186},[57,1050,46],{"class":190},[57,1052,1053],{"class":186},"StreamHandler",[57,1055,1056],{"class":190},"(),\n",[57,1058,1059],{"class":59,"line":587},[57,1060,1061],{"class":190},"    ],\n",[57,1063,1064],{"class":59,"line":593},[57,1065,287],{"class":190},[57,1067,1068,1071,1073,1076,1078,1081,1083,1086],{"class":59,"line":598},[57,1069,1070],{"class":150},"log ",[57,1072,271],{"class":212},[57,1074,1075],{"class":150}," logging",[57,1077,46],{"class":190},[57,1079,1080],{"class":186},"getLogger",[57,1082,219],{"class":190},[57,1084,1085],{"class":208},"__name__",[57,1087,287],{"class":190},[57,1089,1090],{"class":59,"line":604},[57,1091,82],{"emptyLinePlaceholder":81},[57,1093,1095],{"class":59,"line":1094},31,[57,1096,1097],{"class":63},"# --- Timestamp ---\n",[57,1099,1101,1104,1106,1108,1110,1112,1115,1118,1120,1122,1125,1128,1131,1133],{"class":59,"line":1100},32,[57,1102,1103],{"class":208},"TIMESTAMP",[57,1105,213],{"class":212},[57,1107,794],{"class":150},[57,1109,46],{"class":190},[57,1111,897],{"class":186},[57,1113,1114],{"class":190},"().",[57,1116,1117],{"class":186},"strftime",[57,1119,219],{"class":190},[57,1121,233],{"class":232},[57,1123,1124],{"class":236},"%Y%m",[57,1126,1127],{"class":888},"%d",[57,1129,1130],{"class":236},"_%H%M%S",[57,1132,233],{"class":232},[57,1134,287],{"class":190},[57,1136,1138,1141,1143,1145,1147,1149,1151],{"class":59,"line":1137},33,[57,1139,1140],{"class":208},"BACKUP_SUBDIR",[57,1142,213],{"class":212},[57,1144,861],{"class":150},[57,1146,46],{"class":190},[57,1148,209],{"class":866},[57,1150,869],{"class":212},[57,1152,1153],{"class":208}," TIMESTAMP\n",[57,1155,1157,1159,1161,1163,1165,1167,1169,1171,1173,1175,1177,1179],{"class":59,"line":1156},34,[57,1158,1140],{"class":208},[57,1160,46],{"class":190},[57,1162,262],{"class":186},[57,1164,219],{"class":190},[57,1166,268],{"class":267},[57,1168,271],{"class":212},[57,1170,275],{"class":274},[57,1172,241],{"class":190},[57,1174,280],{"class":267},[57,1176,271],{"class":212},[57,1178,275],{"class":274},[57,1180,287],{"class":190},[57,1182,1184],{"class":59,"line":1183},35,[57,1185,82],{"emptyLinePlaceholder":81},[57,1187,1189],{"class":59,"line":1188},36,[57,1190,82],{"emptyLinePlaceholder":81},[57,1192,1194,1197,1201],{"class":59,"line":1193},37,[57,1195,1196],{"class":881},"def",[57,1198,1200],{"class":1199},"sGLFI"," backup_postgresql",[57,1202,1203],{"class":190},"():\n",[57,1205,1207,1210,1213],{"class":59,"line":1206},38,[57,1208,1209],{"class":734},"    \"\"\"",[57,1211,1212],{"class":740},"Respalda cada base de datos PostgreSQL con pg_dump.",[57,1214,735],{"class":734},[57,1216,1218,1221,1223,1225,1227,1229,1232,1234],{"class":59,"line":1217},39,[57,1219,1220],{"class":150},"    log",[57,1222,46],{"class":190},[57,1224,112],{"class":186},[57,1226,219],{"class":190},[57,1228,233],{"class":232},[57,1230,1231],{"class":236},"Iniciando backup de PostgreSQL...",[57,1233,233],{"class":232},[57,1235,287],{"class":190},[57,1237,1239,1242,1244],{"class":59,"line":1238},40,[57,1240,1241],{"class":150},"    dumps ",[57,1243,271],{"class":212},[57,1245,1246],{"class":190}," []\n",[57,1248,1250],{"class":59,"line":1249},41,[57,1251,82],{"emptyLinePlaceholder":81},[57,1253,1255,1258,1261,1264,1266,1268,1270],{"class":59,"line":1254},42,[57,1256,1257],{"class":146},"    for",[57,1259,1260],{"class":150}," db ",[57,1262,1263],{"class":146},"in",[57,1265,861],{"class":150},[57,1267,46],{"class":190},[57,1269,483],{"class":866},[57,1271,1272],{"class":190},":\n",[57,1274,1276,1279,1281,1284,1286,1289],{"class":59,"line":1275},43,[57,1277,1278],{"class":150},"        db ",[57,1280,271],{"class":212},[57,1282,1283],{"class":150}," db",[57,1285,46],{"class":190},[57,1287,1288],{"class":186},"strip",[57,1290,191],{"class":190},[57,1292,1294,1297,1299,1302,1304,1306,1308,1310,1313,1315],{"class":59,"line":1293},44,[57,1295,1296],{"class":150},"        dump_file ",[57,1298,271],{"class":212},[57,1300,1301],{"class":208}," BACKUP_SUBDIR",[57,1303,869],{"class":212},[57,1305,882],{"class":881},[57,1307,233],{"class":236},[57,1309,889],{"class":888},[57,1311,1312],{"class":150},"db",[57,1314,920],{"class":888},[57,1316,1317],{"class":236},".sql.gz\"\n",[57,1319,1321],{"class":59,"line":1320},45,[57,1322,82],{"emptyLinePlaceholder":81},[57,1324,1326,1329,1331],{"class":59,"line":1325},46,[57,1327,1328],{"class":150},"        cmd ",[57,1330,271],{"class":212},[57,1332,1333],{"class":190}," (\n",[57,1335,1337,1340,1343,1345,1348,1350,1352,1354],{"class":59,"line":1336},47,[57,1338,1339],{"class":881},"            f",[57,1341,1342],{"class":236},"\"PGPASSWORD='",[57,1344,889],{"class":888},[57,1346,1347],{"class":150},"config",[57,1349,46],{"class":190},[57,1351,454],{"class":866},[57,1353,920],{"class":888},[57,1355,1356],{"class":236},"' \"\n",[57,1358,1360,1362,1365,1367,1369,1371,1373,1375,1378,1380,1382,1384,1386,1388],{"class":59,"line":1359},48,[57,1361,1339],{"class":881},[57,1363,1364],{"class":236},"\"pg_dump -h ",[57,1366,889],{"class":888},[57,1368,1347],{"class":150},[57,1370,46],{"class":190},[57,1372,354],{"class":866},[57,1374,920],{"class":888},[57,1376,1377],{"class":236}," -p ",[57,1379,889],{"class":888},[57,1381,1347],{"class":150},[57,1383,46],{"class":190},[57,1385,388],{"class":866},[57,1387,920],{"class":888},[57,1389,1390],{"class":236}," \"\n",[57,1392,1394,1396,1399,1401,1403,1405,1407,1409,1412,1414,1416,1418,1421,1423,1426,1428],{"class":59,"line":1393},49,[57,1395,1339],{"class":881},[57,1397,1398],{"class":236},"\"-U ",[57,1400,889],{"class":888},[57,1402,1347],{"class":150},[57,1404,46],{"class":190},[57,1406,421],{"class":866},[57,1408,920],{"class":888},[57,1410,1411],{"class":236}," -d ",[57,1413,889],{"class":888},[57,1415,1312],{"class":150},[57,1417,920],{"class":888},[57,1419,1420],{"class":236}," -Fc -Z6 -f ",[57,1422,889],{"class":888},[57,1424,1425],{"class":150},"dump_file",[57,1427,920],{"class":888},[57,1429,1430],{"class":236},"\"\n",[57,1432,1434],{"class":59,"line":1433},50,[57,1435,1436],{"class":190},"        )\n",[57,1438,1440],{"class":59,"line":1439},51,[57,1441,82],{"emptyLinePlaceholder":81},[57,1443,1445,1448],{"class":59,"line":1444},52,[57,1446,1447],{"class":146},"        try",[57,1449,1272],{"class":190},[57,1451,1453,1456,1458,1461,1463,1466,1468,1471,1473,1475,1477,1480,1482,1484,1486,1489,1491,1493],{"class":59,"line":1452},53,[57,1454,1455],{"class":150},"            subprocess",[57,1457,46],{"class":190},[57,1459,1460],{"class":186},"run",[57,1462,219],{"class":190},[57,1464,1465],{"class":186},"cmd",[57,1467,241],{"class":190},[57,1469,1470],{"class":267}," shell",[57,1472,271],{"class":212},[57,1474,275],{"class":274},[57,1476,241],{"class":190},[57,1478,1479],{"class":267}," check",[57,1481,271],{"class":212},[57,1483,275],{"class":274},[57,1485,241],{"class":190},[57,1487,1488],{"class":267}," capture_output",[57,1490,271],{"class":212},[57,1492,275],{"class":274},[57,1494,287],{"class":190},[57,1496,1498,1501,1503,1506,1508,1511,1513,1516,1518,1521,1524,1527,1530],{"class":59,"line":1497},54,[57,1499,1500],{"class":150},"            size_mb ",[57,1502,271],{"class":212},[57,1504,1505],{"class":150}," dump_file",[57,1507,46],{"class":190},[57,1509,1510],{"class":186},"stat",[57,1512,1114],{"class":190},[57,1514,1515],{"class":933},"st_size",[57,1517,869],{"class":212},[57,1519,1520],{"class":190}," (",[57,1522,1523],{"class":888},"1024",[57,1525,1526],{"class":212}," *",[57,1528,1529],{"class":888}," 1024",[57,1531,287],{"class":190},[57,1533,1535,1538,1540,1542,1544,1547,1550,1552,1554,1556,1559,1561,1563,1565,1568,1570,1572,1574,1577,1580,1582,1585],{"class":59,"line":1534},55,[57,1536,1537],{"class":150},"            log",[57,1539,46],{"class":190},[57,1541,112],{"class":186},[57,1543,219],{"class":190},[57,1545,1546],{"class":881},"f",[57,1548,1549],{"class":236},"\"  ✓ ",[57,1551,889],{"class":888},[57,1553,1312],{"class":186},[57,1555,920],{"class":888},[57,1557,1558],{"class":236}," → ",[57,1560,889],{"class":888},[57,1562,1425],{"class":186},[57,1564,46],{"class":190},[57,1566,1567],{"class":933},"name",[57,1569,920],{"class":888},[57,1571,1520],{"class":236},[57,1573,889],{"class":888},[57,1575,1576],{"class":186},"size_mb",[57,1578,1579],{"class":881},":.1f",[57,1581,920],{"class":888},[57,1583,1584],{"class":236}," MB)\"",[57,1586,287],{"class":190},[57,1588,1590,1593,1595,1598,1601,1603,1605,1607,1610,1612,1614,1616,1619,1621,1623,1626,1628,1630,1633,1635,1637,1639,1641,1645,1647,1649,1651,1654],{"class":59,"line":1589},56,[57,1591,1592],{"class":150},"            dumps",[57,1594,46],{"class":190},[57,1596,1597],{"class":186},"append",[57,1599,1600],{"class":190},"({",[57,1602,233],{"class":232},[57,1604,1312],{"class":236},[57,1606,233],{"class":232},[57,1608,1609],{"class":190},":",[57,1611,1283],{"class":186},[57,1613,241],{"class":190},[57,1615,244],{"class":232},[57,1617,1618],{"class":236},"file",[57,1620,233],{"class":232},[57,1622,1609],{"class":190},[57,1624,1625],{"class":309}," str",[57,1627,219],{"class":190},[57,1629,1425],{"class":186},[57,1631,1632],{"class":190},"),",[57,1634,244],{"class":232},[57,1636,1576],{"class":236},[57,1638,233],{"class":232},[57,1640,1609],{"class":190},[57,1642,1644],{"class":1643},"sptTA"," round",[57,1646,219],{"class":190},[57,1648,1576],{"class":186},[57,1650,241],{"class":190},[57,1652,1653],{"class":888}," 1",[57,1655,1656],{"class":190},")})\n",[57,1658,1660,1663,1666,1668,1671,1674,1677],{"class":59,"line":1659},57,[57,1661,1662],{"class":146},"        except",[57,1664,1665],{"class":150}," subprocess",[57,1667,46],{"class":190},[57,1669,1670],{"class":933},"CalledProcessError",[57,1672,1673],{"class":146}," as",[57,1675,1676],{"class":150}," e",[57,1678,1272],{"class":190},[57,1680,1682,1684,1686,1689,1691,1693,1696,1698,1700,1702,1705,1707,1710,1712,1715,1717,1720,1723,1725,1727],{"class":59,"line":1681},58,[57,1683,1537],{"class":150},[57,1685,46],{"class":190},[57,1687,1688],{"class":186},"error",[57,1690,219],{"class":190},[57,1692,1546],{"class":881},[57,1694,1695],{"class":236},"\"  ✗ Error en ",[57,1697,889],{"class":888},[57,1699,1312],{"class":186},[57,1701,920],{"class":888},[57,1703,1704],{"class":236},": ",[57,1706,889],{"class":888},[57,1708,1709],{"class":186},"e",[57,1711,46],{"class":190},[57,1713,1714],{"class":933},"stderr",[57,1716,46],{"class":190},[57,1718,1719],{"class":186},"decode",[57,1721,1722],{"class":190},"()",[57,1724,920],{"class":888},[57,1726,233],{"class":236},[57,1728,287],{"class":190},[57,1730,1732],{"class":59,"line":1731},59,[57,1733,1734],{"class":146},"            raise\n",[57,1736,1738],{"class":59,"line":1737},60,[57,1739,82],{"emptyLinePlaceholder":81},[57,1741,1743,1746],{"class":59,"line":1742},61,[57,1744,1745],{"class":146},"    return",[57,1747,1748],{"class":150}," dumps\n",[57,1750,1752],{"class":59,"line":1751},62,[57,1753,82],{"emptyLinePlaceholder":81},[57,1755,1757],{"class":59,"line":1756},63,[57,1758,82],{"emptyLinePlaceholder":81},[57,1760,1762,1764,1767],{"class":59,"line":1761},64,[57,1763,1196],{"class":881},[57,1765,1766],{"class":1199}," backup_files",[57,1768,1203],{"class":190},[57,1770,1772,1774,1777],{"class":59,"line":1771},65,[57,1773,1209],{"class":734},[57,1775,1776],{"class":740},"Comprime directorios de archivos en un tar.gz.",[57,1778,735],{"class":734},[57,1780,1782,1784,1786,1788,1790,1792,1795,1797],{"class":59,"line":1781},66,[57,1783,1220],{"class":150},[57,1785,46],{"class":190},[57,1787,112],{"class":186},[57,1789,219],{"class":190},[57,1791,233],{"class":232},[57,1793,1794],{"class":236},"Iniciando backup de archivos...",[57,1796,233],{"class":232},[57,1798,287],{"class":190},[57,1800,1802,1805,1807,1809,1811,1813,1816],{"class":59,"line":1801},67,[57,1803,1804],{"class":150},"    archive_file ",[57,1806,271],{"class":212},[57,1808,1301],{"class":208},[57,1810,869],{"class":212},[57,1812,244],{"class":232},[57,1814,1815],{"class":236},"archivos.tar.gz",[57,1817,1430],{"class":232},[57,1819,1821],{"class":59,"line":1820},68,[57,1822,82],{"emptyLinePlaceholder":81},[57,1824,1826,1829,1832,1834,1837,1839,1842,1844,1846,1849,1851,1854,1856,1859],{"class":59,"line":1825},69,[57,1827,1828],{"class":146},"    with",[57,1830,1831],{"class":150}," tarfile",[57,1833,46],{"class":190},[57,1835,1836],{"class":186},"open",[57,1838,219],{"class":190},[57,1840,1841],{"class":186},"archive_file",[57,1843,241],{"class":190},[57,1845,244],{"class":232},[57,1847,1848],{"class":236},"w:gz",[57,1850,233],{"class":232},[57,1852,1853],{"class":190},")",[57,1855,1673],{"class":146},[57,1857,1858],{"class":150}," tar",[57,1860,1272],{"class":190},[57,1862,1864,1867,1870,1872,1874,1876,1878],{"class":59,"line":1863},70,[57,1865,1866],{"class":146},"        for",[57,1868,1869],{"class":150}," dir_path ",[57,1871,1263],{"class":146},[57,1873,861],{"class":150},[57,1875,46],{"class":190},[57,1877,541],{"class":866},[57,1879,1272],{"class":190},[57,1881,1883,1886,1888,1890,1892,1895],{"class":59,"line":1882},71,[57,1884,1885],{"class":150},"            path ",[57,1887,271],{"class":212},[57,1889,216],{"class":186},[57,1891,219],{"class":190},[57,1893,1894],{"class":186},"dir_path",[57,1896,287],{"class":190},[57,1898,1900,1903,1906,1908,1911],{"class":59,"line":1899},72,[57,1901,1902],{"class":146},"            if",[57,1904,1905],{"class":150}," path",[57,1907,46],{"class":190},[57,1909,1910],{"class":186},"exists",[57,1912,1203],{"class":190},[57,1914,1916,1919,1921,1924,1926,1929,1931,1934,1936,1939,1941,1943,1945,1947],{"class":59,"line":1915},73,[57,1917,1918],{"class":150},"                tar",[57,1920,46],{"class":190},[57,1922,1923],{"class":186},"add",[57,1925,219],{"class":190},[57,1927,1928],{"class":309},"str",[57,1930,219],{"class":190},[57,1932,1933],{"class":186},"path",[57,1935,1632],{"class":190},[57,1937,1938],{"class":267}," arcname",[57,1940,271],{"class":212},[57,1942,1933],{"class":186},[57,1944,46],{"class":190},[57,1946,1567],{"class":933},[57,1948,287],{"class":190},[57,1950,1952,1955,1957,1959,1961,1963,1965,1967,1969,1971,1973],{"class":59,"line":1951},74,[57,1953,1954],{"class":150},"                log",[57,1956,46],{"class":190},[57,1958,112],{"class":186},[57,1960,219],{"class":190},[57,1962,1546],{"class":881},[57,1964,1549],{"class":236},[57,1966,889],{"class":888},[57,1968,1894],{"class":186},[57,1970,920],{"class":888},[57,1972,233],{"class":236},[57,1974,287],{"class":190},[57,1976,1978,1981],{"class":59,"line":1977},75,[57,1979,1980],{"class":146},"            else",[57,1982,1272],{"class":190},[57,1984,1986,1988,1990,1993,1995,1997,2000,2002,2004,2006,2009],{"class":59,"line":1985},76,[57,1987,1954],{"class":150},[57,1989,46],{"class":190},[57,1991,1992],{"class":186},"warning",[57,1994,219],{"class":190},[57,1996,1546],{"class":881},[57,1998,1999],{"class":236},"\"  ⚠ ",[57,2001,889],{"class":888},[57,2003,1894],{"class":186},[57,2005,920],{"class":888},[57,2007,2008],{"class":236}," no existe, saltando...\"",[57,2010,287],{"class":190},[57,2012,2014],{"class":59,"line":2013},77,[57,2015,82],{"emptyLinePlaceholder":81},[57,2017,2019,2022,2024,2027,2029,2031,2033,2035,2037,2039,2041,2043,2045],{"class":59,"line":2018},78,[57,2020,2021],{"class":150},"    size_mb ",[57,2023,271],{"class":212},[57,2025,2026],{"class":150}," archive_file",[57,2028,46],{"class":190},[57,2030,1510],{"class":186},[57,2032,1114],{"class":190},[57,2034,1515],{"class":933},[57,2036,869],{"class":212},[57,2038,1520],{"class":190},[57,2040,1523],{"class":888},[57,2042,1526],{"class":212},[57,2044,1529],{"class":888},[57,2046,287],{"class":190},[57,2048,2050,2052,2054,2056,2058,2060,2063,2065,2067,2069,2071,2073,2075,2077,2079,2081,2083,2085],{"class":59,"line":2049},79,[57,2051,1220],{"class":150},[57,2053,46],{"class":190},[57,2055,112],{"class":186},[57,2057,219],{"class":190},[57,2059,1546],{"class":881},[57,2061,2062],{"class":236},"\"  Archivo: ",[57,2064,889],{"class":888},[57,2066,1841],{"class":186},[57,2068,46],{"class":190},[57,2070,1567],{"class":933},[57,2072,920],{"class":888},[57,2074,1520],{"class":236},[57,2076,889],{"class":888},[57,2078,1576],{"class":186},[57,2080,1579],{"class":881},[57,2082,920],{"class":888},[57,2084,1584],{"class":236},[57,2086,287],{"class":190},[57,2088,2090,2092,2095,2097,2099,2101,2103,2105,2107,2109,2111,2113,2115,2117,2119,2121,2123,2125,2127,2129],{"class":59,"line":2089},80,[57,2091,1745],{"class":146},[57,2093,2094],{"class":190}," {",[57,2096,233],{"class":232},[57,2098,1618],{"class":236},[57,2100,233],{"class":232},[57,2102,1609],{"class":190},[57,2104,1625],{"class":309},[57,2106,219],{"class":190},[57,2108,1841],{"class":186},[57,2110,1632],{"class":190},[57,2112,244],{"class":232},[57,2114,1576],{"class":236},[57,2116,233],{"class":232},[57,2118,1609],{"class":190},[57,2120,1644],{"class":1643},[57,2122,219],{"class":190},[57,2124,1576],{"class":186},[57,2126,241],{"class":190},[57,2128,1653],{"class":888},[57,2130,2131],{"class":190},")}\n",[57,2133,2135],{"class":59,"line":2134},81,[57,2136,82],{"emptyLinePlaceholder":81},[57,2138,2140],{"class":59,"line":2139},82,[57,2141,82],{"emptyLinePlaceholder":81},[57,2143,2145,2147,2150],{"class":59,"line":2144},83,[57,2146,1196],{"class":881},[57,2148,2149],{"class":1199}," cleanup_old_backups",[57,2151,1203],{"class":190},[57,2153,2155,2157,2160],{"class":59,"line":2154},84,[57,2156,1209],{"class":734},[57,2158,2159],{"class":740},"Elimina backups más antiguos que RETENTION_DAYS.",[57,2161,735],{"class":734},[57,2163,2165,2167,2169,2171,2173,2175,2178,2180,2182,2184,2186,2188,2191],{"class":59,"line":2164},85,[57,2166,1220],{"class":150},[57,2168,46],{"class":190},[57,2170,112],{"class":186},[57,2172,219],{"class":190},[57,2174,1546],{"class":881},[57,2176,2177],{"class":236},"\"Limpiando backups con más de ",[57,2179,889],{"class":888},[57,2181,1347],{"class":186},[57,2183,46],{"class":190},[57,2185,304],{"class":866},[57,2187,920],{"class":888},[57,2189,2190],{"class":236}," días...\"",[57,2192,287],{"class":190},[57,2194,2196,2199,2201,2203,2205,2207,2209,2212,2215,2217,2220,2222,2224,2226,2228],{"class":59,"line":2195},86,[57,2197,2198],{"class":150},"    cutoff ",[57,2200,271],{"class":212},[57,2202,794],{"class":150},[57,2204,46],{"class":190},[57,2206,897],{"class":186},[57,2208,1722],{"class":190},[57,2210,2211],{"class":212}," -",[57,2213,2214],{"class":186}," timedelta",[57,2216,219],{"class":190},[57,2218,2219],{"class":267},"days",[57,2221,271],{"class":212},[57,2223,1347],{"class":186},[57,2225,46],{"class":190},[57,2227,304],{"class":866},[57,2229,287],{"class":190},[57,2231,2233,2236,2238],{"class":59,"line":2232},87,[57,2234,2235],{"class":150},"    removed ",[57,2237,271],{"class":212},[57,2239,2240],{"class":888}," 0\n",[57,2242,2244],{"class":59,"line":2243},88,[57,2245,82],{"emptyLinePlaceholder":81},[57,2247,2249,2251,2254,2256,2258,2260,2262,2264,2267],{"class":59,"line":2248},89,[57,2250,1257],{"class":146},[57,2252,2253],{"class":150}," item ",[57,2255,1263],{"class":146},[57,2257,861],{"class":150},[57,2259,46],{"class":190},[57,2261,209],{"class":866},[57,2263,46],{"class":190},[57,2265,2266],{"class":186},"iterdir",[57,2268,1203],{"class":190},[57,2270,2272,2275,2278,2280,2283,2285,2288,2290,2292,2294,2297,2299,2301,2303],{"class":59,"line":2271},90,[57,2273,2274],{"class":146},"        if",[57,2276,2277],{"class":150}," item",[57,2279,46],{"class":190},[57,2281,2282],{"class":186},"is_dir",[57,2284,1722],{"class":190},[57,2286,2287],{"class":212}," and",[57,2289,2277],{"class":150},[57,2291,46],{"class":190},[57,2293,1567],{"class":933},[57,2295,2296],{"class":212}," !=",[57,2298,244],{"class":232},[57,2300,874],{"class":236},[57,2302,233],{"class":232},[57,2304,1272],{"class":190},[57,2306,2308,2311],{"class":59,"line":2307},91,[57,2309,2310],{"class":146},"            try",[57,2312,1272],{"class":190},[57,2314,2316,2319,2321,2323,2325,2328,2330,2333,2335,2337,2340,2343,2346,2348,2350,2352,2354],{"class":59,"line":2315},92,[57,2317,2318],{"class":150},"                dir_date ",[57,2320,271],{"class":212},[57,2322,794],{"class":150},[57,2324,46],{"class":190},[57,2326,2327],{"class":186},"strptime",[57,2329,219],{"class":190},[57,2331,2332],{"class":186},"item",[57,2334,46],{"class":190},[57,2336,1567],{"class":933},[57,2338,2339],{"class":190},"[:",[57,2341,2342],{"class":888},"8",[57,2344,2345],{"class":190},"],",[57,2347,244],{"class":232},[57,2349,1124],{"class":236},[57,2351,1127],{"class":888},[57,2353,233],{"class":232},[57,2355,287],{"class":190},[57,2357,2359,2362,2365,2368,2371],{"class":59,"line":2358},93,[57,2360,2361],{"class":146},"                if",[57,2363,2364],{"class":150}," dir_date ",[57,2366,2367],{"class":212},"\u003C",[57,2369,2370],{"class":150}," cutoff",[57,2372,1272],{"class":190},[57,2374,2376,2379],{"class":59,"line":2375},94,[57,2377,2378],{"class":146},"                    import",[57,2380,2381],{"class":150}," shutil\n",[57,2383,2385,2388,2390,2393,2395,2397],{"class":59,"line":2384},95,[57,2386,2387],{"class":150},"                    shutil",[57,2389,46],{"class":190},[57,2391,2392],{"class":186},"rmtree",[57,2394,219],{"class":190},[57,2396,2332],{"class":186},[57,2398,287],{"class":190},[57,2400,2402,2405,2407,2409,2411,2413,2416,2418,2420,2422,2424,2426,2428],{"class":59,"line":2401},96,[57,2403,2404],{"class":150},"                    log",[57,2406,46],{"class":190},[57,2408,112],{"class":186},[57,2410,219],{"class":190},[57,2412,1546],{"class":881},[57,2414,2415],{"class":236},"\"  ✓ Eliminado: ",[57,2417,889],{"class":888},[57,2419,2332],{"class":186},[57,2421,46],{"class":190},[57,2423,1567],{"class":933},[57,2425,920],{"class":888},[57,2427,233],{"class":236},[57,2429,287],{"class":190},[57,2431,2433,2436,2439],{"class":59,"line":2432},97,[57,2434,2435],{"class":150},"                    removed ",[57,2437,2438],{"class":212},"+=",[57,2440,2441],{"class":888}," 1\n",[57,2443,2445,2448,2451],{"class":59,"line":2444},98,[57,2446,2447],{"class":146},"            except",[57,2449,2450],{"class":309}," ValueError",[57,2452,1272],{"class":190},[57,2454,2456],{"class":59,"line":2455},99,[57,2457,2458],{"class":146},"                continue\n",[57,2460,2462],{"class":59,"line":2461},100,[57,2463,82],{"emptyLinePlaceholder":81},[57,2465,2467,2469,2471,2473,2475,2477,2480,2482,2485,2487,2490],{"class":59,"line":2466},101,[57,2468,1220],{"class":150},[57,2470,46],{"class":190},[57,2472,112],{"class":186},[57,2474,219],{"class":190},[57,2476,1546],{"class":881},[57,2478,2479],{"class":236},"\"  ",[57,2481,889],{"class":888},[57,2483,2484],{"class":186},"removed",[57,2486,920],{"class":888},[57,2488,2489],{"class":236}," backup(s) eliminado(s)\"",[57,2491,287],{"class":190},[57,2493,2495,2497],{"class":59,"line":2494},102,[57,2496,1745],{"class":146},[57,2498,2499],{"class":150}," removed\n",[57,2501,2503],{"class":59,"line":2502},103,[57,2504,82],{"emptyLinePlaceholder":81},[57,2506,2508],{"class":59,"line":2507},104,[57,2509,82],{"emptyLinePlaceholder":81},[57,2511,2513,2515,2518,2520,2524,2526,2528,2530,2533,2535,2538,2540,2543],{"class":59,"line":2512},105,[57,2514,1196],{"class":881},[57,2516,2517],{"class":1199}," notify_slack",[57,2519,219],{"class":190},[57,2521,2523],{"class":2522},"sFwrP","message",[57,2525,1609],{"class":190},[57,2527,1625],{"class":309},[57,2529,241],{"class":190},[57,2531,2532],{"class":2522}," success",[57,2534,1609],{"class":190},[57,2536,2537],{"class":309}," bool",[57,2539,213],{"class":212},[57,2541,2542],{"class":274}," True",[57,2544,2545],{"class":190},"):\n",[57,2547,2549,2551,2554],{"class":59,"line":2548},106,[57,2550,1209],{"class":734},[57,2552,2553],{"class":740},"Envía notificación a Slack.",[57,2555,735],{"class":734},[57,2557,2559,2562,2565,2567,2569,2571],{"class":59,"line":2558},107,[57,2560,2561],{"class":146},"    if",[57,2563,2564],{"class":212}," not",[57,2566,861],{"class":150},[57,2568,46],{"class":190},[57,2570,607],{"class":866},[57,2572,1272],{"class":190},[57,2574,2576],{"class":59,"line":2575},108,[57,2577,2578],{"class":146},"        return\n",[57,2580,2582],{"class":59,"line":2581},109,[57,2583,82],{"emptyLinePlaceholder":81},[57,2585,2587,2590,2592,2594,2597,2599,2602,2605,2608,2610,2613],{"class":59,"line":2586},110,[57,2588,2589],{"class":150},"    color ",[57,2591,271],{"class":212},[57,2593,244],{"class":232},[57,2595,2596],{"class":236},"#36a64f",[57,2598,233],{"class":232},[57,2600,2601],{"class":146}," if",[57,2603,2604],{"class":150}," success ",[57,2606,2607],{"class":146},"else",[57,2609,244],{"class":232},[57,2611,2612],{"class":236},"#ff0000",[57,2614,1430],{"class":232},[57,2616,2618,2621,2623,2625,2628,2630,2632,2634,2636,2638,2641],{"class":59,"line":2617},111,[57,2619,2620],{"class":150},"    emoji ",[57,2622,271],{"class":212},[57,2624,244],{"class":232},[57,2626,2627],{"class":236},"✅",[57,2629,233],{"class":232},[57,2631,2601],{"class":146},[57,2633,2604],{"class":150},[57,2635,2607],{"class":146},[57,2637,244],{"class":232},[57,2639,2640],{"class":236},"🚨",[57,2642,1430],{"class":232},[57,2644,2646],{"class":59,"line":2645},112,[57,2647,82],{"emptyLinePlaceholder":81},[57,2649,2651,2654,2656],{"class":59,"line":2650},113,[57,2652,2653],{"class":150},"    payload ",[57,2655,271],{"class":212},[57,2657,2658],{"class":190}," {\n",[57,2660,2662,2665,2668,2670,2672],{"class":59,"line":2661},114,[57,2663,2664],{"class":232},"        \"",[57,2666,2667],{"class":236},"attachments",[57,2669,233],{"class":232},[57,2671,1609],{"class":190},[57,2673,2674],{"class":190}," [{\n",[57,2676,2678,2681,2684,2686,2688,2691],{"class":59,"line":2677},115,[57,2679,2680],{"class":232},"            \"",[57,2682,2683],{"class":236},"color",[57,2685,233],{"class":232},[57,2687,1609],{"class":190},[57,2689,2690],{"class":150}," color",[57,2692,560],{"class":190},[57,2694,2696,2698,2701,2703,2705,2707,2709,2711,2714,2716,2719,2721,2724,2727,2729,2731,2733,2735,2738,2741,2743,2745,2747],{"class":59,"line":2695},116,[57,2697,2680],{"class":232},[57,2699,2700],{"class":236},"title",[57,2702,233],{"class":232},[57,2704,1609],{"class":190},[57,2706,882],{"class":881},[57,2708,233],{"class":236},[57,2710,889],{"class":888},[57,2712,2713],{"class":150},"emoji",[57,2715,920],{"class":888},[57,2717,2718],{"class":236}," Backup ",[57,2720,889],{"class":888},[57,2722,2723],{"class":232},"'",[57,2725,2726],{"class":236},"completado",[57,2728,2723],{"class":232},[57,2730,2601],{"class":146},[57,2732,2604],{"class":150},[57,2734,2607],{"class":146},[57,2736,2737],{"class":232}," '",[57,2739,2740],{"class":236},"FALLIDO",[57,2742,2723],{"class":232},[57,2744,920],{"class":888},[57,2746,233],{"class":236},[57,2748,560],{"class":190},[57,2750,2752,2754,2756,2758,2760,2763],{"class":59,"line":2751},117,[57,2753,2680],{"class":232},[57,2755,105],{"class":236},[57,2757,233],{"class":232},[57,2759,1609],{"class":190},[57,2761,2762],{"class":150}," message",[57,2764,560],{"class":190},[57,2766,2768,2770,2773,2775,2777,2779,2781,2783,2785,2787,2789,2792],{"class":59,"line":2767},118,[57,2769,2680],{"class":232},[57,2771,2772],{"class":236},"ts",[57,2774,233],{"class":232},[57,2776,1609],{"class":190},[57,2778,310],{"class":309},[57,2780,219],{"class":190},[57,2782,892],{"class":186},[57,2784,46],{"class":190},[57,2786,897],{"class":186},[57,2788,1114],{"class":190},[57,2790,2791],{"class":186},"timestamp",[57,2793,2794],{"class":190},"()),\n",[57,2796,2798],{"class":59,"line":2797},119,[57,2799,2800],{"class":190},"        }]\n",[57,2802,2804],{"class":59,"line":2803},120,[57,2805,2806],{"class":190},"    }\n",[57,2808,2810],{"class":59,"line":2809},121,[57,2811,82],{"emptyLinePlaceholder":81},[57,2813,2815,2818],{"class":59,"line":2814},122,[57,2816,2817],{"class":146},"    try",[57,2819,1272],{"class":190},[57,2821,2823,2826,2828,2830],{"class":59,"line":2822},123,[57,2824,2825],{"class":150},"        req ",[57,2827,271],{"class":212},[57,2829,826],{"class":186},[57,2831,973],{"class":190},[57,2833,2835,2838,2840,2842],{"class":59,"line":2834},124,[57,2836,2837],{"class":186},"            config",[57,2839,46],{"class":190},[57,2841,607],{"class":866},[57,2843,560],{"class":190},[57,2845,2847,2850,2852,2855,2857,2860,2862,2865,2867,2870],{"class":59,"line":2846},125,[57,2848,2849],{"class":267},"            data",[57,2851,271],{"class":212},[57,2853,2854],{"class":186},"json",[57,2856,46],{"class":190},[57,2858,2859],{"class":186},"dumps",[57,2861,219],{"class":190},[57,2863,2864],{"class":186},"payload",[57,2866,511],{"class":190},[57,2868,2869],{"class":186},"encode",[57,2871,1056],{"class":190},[57,2873,2875,2878,2880,2882,2884,2887,2889,2891,2893,2896,2898],{"class":59,"line":2874},126,[57,2876,2877],{"class":267},"            headers",[57,2879,271],{"class":212},[57,2881,889],{"class":190},[57,2883,233],{"class":232},[57,2885,2886],{"class":236},"Content-Type",[57,2888,233],{"class":232},[57,2890,1609],{"class":190},[57,2892,244],{"class":232},[57,2894,2895],{"class":236},"application/json",[57,2897,233],{"class":232},[57,2899,2900],{"class":190},"},\n",[57,2902,2904],{"class":59,"line":2903},127,[57,2905,1436],{"class":190},[57,2907,2909,2912,2914,2917,2919,2922,2924,2927],{"class":59,"line":2908},128,[57,2910,2911],{"class":186},"        urlopen",[57,2913,219],{"class":190},[57,2915,2916],{"class":186},"req",[57,2918,241],{"class":190},[57,2920,2921],{"class":267}," timeout",[57,2923,271],{"class":212},[57,2925,2926],{"class":888},"10",[57,2928,287],{"class":190},[57,2930,2932,2935,2938,2940,2942],{"class":59,"line":2931},129,[57,2933,2934],{"class":146},"    except",[57,2936,2937],{"class":309}," Exception",[57,2939,1673],{"class":146},[57,2941,1676],{"class":150},[57,2943,1272],{"class":190},[57,2945,2947,2950,2952,2954,2956,2958,2961,2963,2965,2967,2969],{"class":59,"line":2946},130,[57,2948,2949],{"class":150},"        log",[57,2951,46],{"class":190},[57,2953,1992],{"class":186},[57,2955,219],{"class":190},[57,2957,1546],{"class":881},[57,2959,2960],{"class":236},"\"No se pudo notificar a Slack: ",[57,2962,889],{"class":888},[57,2964,1709],{"class":186},[57,2966,920],{"class":888},[57,2968,233],{"class":236},[57,2970,287],{"class":190},[57,2972,2974],{"class":59,"line":2973},131,[57,2975,82],{"emptyLinePlaceholder":81},[57,2977,2979],{"class":59,"line":2978},132,[57,2980,82],{"emptyLinePlaceholder":81},[57,2982,2984,2986,2989],{"class":59,"line":2983},133,[57,2985,1196],{"class":881},[57,2987,2988],{"class":1199}," main",[57,2990,1203],{"class":190},[57,2992,2994,2996,2999],{"class":59,"line":2993},134,[57,2995,1209],{"class":734},[57,2997,2998],{"class":740},"Ejecuta el proceso completo de backup.",[57,3000,735],{"class":734},[57,3002,3004,3006,3008,3010,3012,3014,3016,3018,3020,3022,3024,3027,3030,3032],{"class":59,"line":3003},135,[57,3005,1220],{"class":150},[57,3007,46],{"class":190},[57,3009,112],{"class":186},[57,3011,219],{"class":190},[57,3013,1546],{"class":881},[57,3015,233],{"class":236},[57,3017,889],{"class":888},[57,3019,2723],{"class":232},[57,3021,271],{"class":236},[57,3023,2723],{"class":232},[57,3025,3026],{"class":212},"*",[57,3028,3029],{"class":888},"50}",[57,3031,233],{"class":236},[57,3033,287],{"class":190},[57,3035,3037,3039,3041,3043,3045,3047,3050,3052,3054,3056,3058],{"class":59,"line":3036},136,[57,3038,1220],{"class":150},[57,3040,46],{"class":190},[57,3042,112],{"class":186},[57,3044,219],{"class":190},[57,3046,1546],{"class":881},[57,3048,3049],{"class":236},"\"BACKUP INICIADO — ",[57,3051,889],{"class":888},[57,3053,1103],{"class":1643},[57,3055,920],{"class":888},[57,3057,233],{"class":236},[57,3059,287],{"class":190},[57,3061,3063,3065,3067,3069,3071,3073,3075,3077,3079,3081,3083,3085,3087,3089],{"class":59,"line":3062},137,[57,3064,1220],{"class":150},[57,3066,46],{"class":190},[57,3068,112],{"class":186},[57,3070,219],{"class":190},[57,3072,1546],{"class":881},[57,3074,233],{"class":236},[57,3076,889],{"class":888},[57,3078,2723],{"class":232},[57,3080,271],{"class":236},[57,3082,2723],{"class":232},[57,3084,3026],{"class":212},[57,3086,3029],{"class":888},[57,3088,233],{"class":236},[57,3090,287],{"class":190},[57,3092,3094],{"class":59,"line":3093},138,[57,3095,82],{"emptyLinePlaceholder":81},[57,3097,3099,3102,3104,3106,3108,3110],{"class":59,"line":3098},139,[57,3100,3101],{"class":150},"    start ",[57,3103,271],{"class":212},[57,3105,794],{"class":150},[57,3107,46],{"class":190},[57,3109,897],{"class":186},[57,3111,191],{"class":190},[57,3113,3115,3118,3120,3122,3124,3127,3129,3131,3134,3136,3139,3141,3143,3146,3148,3150,3153,3155,3157,3160],{"class":59,"line":3114},140,[57,3116,3117],{"class":150},"    results ",[57,3119,271],{"class":212},[57,3121,2094],{"class":190},[57,3123,233],{"class":232},[57,3125,3126],{"class":236},"databases",[57,3128,233],{"class":232},[57,3130,1609],{"class":190},[57,3132,3133],{"class":190}," [],",[57,3135,244],{"class":232},[57,3137,3138],{"class":236},"files",[57,3140,233],{"class":232},[57,3142,1609],{"class":190},[57,3144,3145],{"class":274}," None",[57,3147,241],{"class":190},[57,3149,244],{"class":232},[57,3151,3152],{"class":236},"cleaned",[57,3154,233],{"class":232},[57,3156,1609],{"class":190},[57,3158,3159],{"class":888}," 0",[57,3161,3162],{"class":190},"}\n",[57,3164,3166],{"class":59,"line":3165},141,[57,3167,82],{"emptyLinePlaceholder":81},[57,3169,3171,3173],{"class":59,"line":3170},142,[57,3172,2817],{"class":146},[57,3174,1272],{"class":190},[57,3176,3178],{"class":59,"line":3177},143,[57,3179,3180],{"class":63},"        # 1. Backup de bases de datos\n",[57,3182,3184,3187,3190,3192,3194,3196,3199,3201,3203],{"class":59,"line":3183},144,[57,3185,3186],{"class":150},"        results",[57,3188,3189],{"class":190},"[",[57,3191,233],{"class":232},[57,3193,3126],{"class":236},[57,3195,233],{"class":232},[57,3197,3198],{"class":190},"]",[57,3200,213],{"class":212},[57,3202,1200],{"class":186},[57,3204,191],{"class":190},[57,3206,3208],{"class":59,"line":3207},145,[57,3209,82],{"emptyLinePlaceholder":81},[57,3211,3213],{"class":59,"line":3212},146,[57,3214,3215],{"class":63},"        # 2. Backup de archivos\n",[57,3217,3219,3221,3223,3225,3227,3229,3231,3233,3235],{"class":59,"line":3218},147,[57,3220,3186],{"class":150},[57,3222,3189],{"class":190},[57,3224,233],{"class":232},[57,3226,3138],{"class":236},[57,3228,233],{"class":232},[57,3230,3198],{"class":190},[57,3232,213],{"class":212},[57,3234,1766],{"class":186},[57,3236,191],{"class":190},[57,3238,3240],{"class":59,"line":3239},148,[57,3241,82],{"emptyLinePlaceholder":81},[57,3243,3245],{"class":59,"line":3244},149,[57,3246,3247],{"class":63},"        # 3. Limpiar backups antiguos\n",[57,3249,3251,3253,3255,3257,3259,3261,3263,3265,3267],{"class":59,"line":3250},150,[57,3252,3186],{"class":150},[57,3254,3189],{"class":190},[57,3256,233],{"class":232},[57,3258,3152],{"class":236},[57,3260,233],{"class":232},[57,3262,3198],{"class":190},[57,3264,213],{"class":212},[57,3266,2149],{"class":186},[57,3268,191],{"class":190},[57,3270,3272],{"class":59,"line":3271},151,[57,3273,82],{"emptyLinePlaceholder":81},[57,3275,3277],{"class":59,"line":3276},152,[57,3278,3279],{"class":63},"        # Resumen\n",[57,3281,3283,3286,3288,3290,3292,3294,3296,3298,3300,3303,3305,3308],{"class":59,"line":3282},153,[57,3284,3285],{"class":150},"        elapsed ",[57,3287,271],{"class":212},[57,3289,1520],{"class":190},[57,3291,892],{"class":150},[57,3293,46],{"class":190},[57,3295,897],{"class":186},[57,3297,1722],{"class":190},[57,3299,2211],{"class":212},[57,3301,3302],{"class":150}," start",[57,3304,511],{"class":190},[57,3306,3307],{"class":186},"total_seconds",[57,3309,191],{"class":190},[57,3311,3313,3316,3318,3321,3323,3325,3327,3329,3331,3333,3335,3338,3341,3343,3346,3348,3350,3352,3354],{"class":59,"line":3312},154,[57,3314,3315],{"class":150},"        total_size ",[57,3317,271],{"class":212},[57,3319,3320],{"class":1643}," sum",[57,3322,219],{"class":190},[57,3324,917],{"class":186},[57,3326,3189],{"class":190},[57,3328,233],{"class":232},[57,3330,1576],{"class":236},[57,3332,233],{"class":232},[57,3334,3198],{"class":190},[57,3336,3337],{"class":146}," for",[57,3339,3340],{"class":186}," d ",[57,3342,1263],{"class":146},[57,3344,3345],{"class":186}," results",[57,3347,3189],{"class":190},[57,3349,233],{"class":232},[57,3351,3126],{"class":236},[57,3353,233],{"class":232},[57,3355,3356],{"class":190},"])\n",[57,3358,3360,3362,3364,3366,3368,3370,3372],{"class":59,"line":3359},155,[57,3361,2274],{"class":146},[57,3363,3345],{"class":150},[57,3365,3189],{"class":190},[57,3367,233],{"class":232},[57,3369,3138],{"class":236},[57,3371,233],{"class":232},[57,3373,3374],{"class":190},"]:\n",[57,3376,3378,3381,3383,3385,3387,3389,3391,3393,3396,3398,3400,3402],{"class":59,"line":3377},156,[57,3379,3380],{"class":150},"            total_size ",[57,3382,2438],{"class":212},[57,3384,3345],{"class":150},[57,3386,3189],{"class":190},[57,3388,233],{"class":232},[57,3390,3138],{"class":236},[57,3392,233],{"class":232},[57,3394,3395],{"class":190},"][",[57,3397,233],{"class":232},[57,3399,1576],{"class":236},[57,3401,233],{"class":232},[57,3403,590],{"class":190},[57,3405,3407],{"class":59,"line":3406},157,[57,3408,82],{"emptyLinePlaceholder":81},[57,3410,3412,3415,3417],{"class":59,"line":3411},158,[57,3413,3414],{"class":150},"        summary ",[57,3416,271],{"class":212},[57,3418,1333],{"class":190},[57,3420,3422,3424,3427,3429,3431,3433,3435,3437,3440],{"class":59,"line":3421},159,[57,3423,1339],{"class":881},[57,3425,3426],{"class":236},"\"Servidor: ",[57,3428,889],{"class":888},[57,3430,1347],{"class":150},[57,3432,46],{"class":190},[57,3434,354],{"class":866},[57,3436,920],{"class":888},[57,3438,3439],{"class":208},"\\n",[57,3441,1430],{"class":236},[57,3443,3445,3447,3450,3452,3455,3457,3460,3462,3464,3466,3468,3471,3473,3475],{"class":59,"line":3444},160,[57,3446,1339],{"class":881},[57,3448,3449],{"class":236},"\"Bases de datos: ",[57,3451,889],{"class":888},[57,3453,3454],{"class":1643},"len",[57,3456,219],{"class":190},[57,3458,3459],{"class":186},"results",[57,3461,3189],{"class":190},[57,3463,2723],{"class":232},[57,3465,3126],{"class":236},[57,3467,2723],{"class":232},[57,3469,3470],{"class":190},"])",[57,3472,920],{"class":888},[57,3474,3439],{"class":208},[57,3476,1430],{"class":236},[57,3478,3480,3482,3485,3487,3490,3492,3494,3497,3499],{"class":59,"line":3479},161,[57,3481,1339],{"class":881},[57,3483,3484],{"class":236},"\"Tamaño total: ",[57,3486,889],{"class":888},[57,3488,3489],{"class":150},"total_size",[57,3491,1579],{"class":881},[57,3493,920],{"class":888},[57,3495,3496],{"class":236}," MB",[57,3498,3439],{"class":208},[57,3500,1430],{"class":236},[57,3502,3504,3506,3509,3511,3513,3515,3517,3519,3521,3523,3525,3527],{"class":59,"line":3503},162,[57,3505,1339],{"class":881},[57,3507,3508],{"class":236},"\"Backups eliminados: ",[57,3510,889],{"class":888},[57,3512,3459],{"class":150},[57,3514,3189],{"class":190},[57,3516,2723],{"class":232},[57,3518,3152],{"class":236},[57,3520,2723],{"class":232},[57,3522,3198],{"class":190},[57,3524,920],{"class":888},[57,3526,3439],{"class":208},[57,3528,1430],{"class":236},[57,3530,3532,3534,3537,3539,3542,3545,3547],{"class":59,"line":3531},163,[57,3533,1339],{"class":881},[57,3535,3536],{"class":236},"\"Duración: ",[57,3538,889],{"class":888},[57,3540,3541],{"class":150},"elapsed",[57,3543,3544],{"class":881},":.0f",[57,3546,920],{"class":888},[57,3548,3549],{"class":236},"s\"\n",[57,3551,3553],{"class":59,"line":3552},164,[57,3554,1436],{"class":190},[57,3556,3558],{"class":59,"line":3557},165,[57,3559,82],{"emptyLinePlaceholder":81},[57,3561,3563,3565,3567,3569,3571,3573,3575,3577,3579,3581,3583,3585,3587,3589,3591],{"class":59,"line":3562},166,[57,3564,2949],{"class":150},[57,3566,46],{"class":190},[57,3568,112],{"class":186},[57,3570,219],{"class":190},[57,3572,1546],{"class":881},[57,3574,233],{"class":236},[57,3576,3439],{"class":208},[57,3578,889],{"class":888},[57,3580,2723],{"class":232},[57,3582,271],{"class":236},[57,3584,2723],{"class":232},[57,3586,3026],{"class":212},[57,3588,3029],{"class":888},[57,3590,233],{"class":236},[57,3592,287],{"class":190},[57,3594,3596,3598,3600,3602,3604,3606,3609,3611,3613,3615,3617,3620,3622,3624,3626,3628,3631],{"class":59,"line":3595},167,[57,3597,2949],{"class":150},[57,3599,46],{"class":190},[57,3601,112],{"class":186},[57,3603,219],{"class":190},[57,3605,1546],{"class":881},[57,3607,3608],{"class":236},"\"BACKUP COMPLETADO en ",[57,3610,889],{"class":888},[57,3612,3541],{"class":186},[57,3614,3544],{"class":881},[57,3616,920],{"class":888},[57,3618,3619],{"class":236},"s — ",[57,3621,889],{"class":888},[57,3623,3489],{"class":186},[57,3625,1579],{"class":881},[57,3627,920],{"class":888},[57,3629,3630],{"class":236}," MB total\"",[57,3632,287],{"class":190},[57,3634,3636,3638,3640,3642,3644,3646,3648,3650,3652,3654,3656,3658,3660,3662],{"class":59,"line":3635},168,[57,3637,2949],{"class":150},[57,3639,46],{"class":190},[57,3641,112],{"class":186},[57,3643,219],{"class":190},[57,3645,1546],{"class":881},[57,3647,233],{"class":236},[57,3649,889],{"class":888},[57,3651,2723],{"class":232},[57,3653,271],{"class":236},[57,3655,2723],{"class":232},[57,3657,3026],{"class":212},[57,3659,3029],{"class":888},[57,3661,233],{"class":236},[57,3663,287],{"class":190},[57,3665,3667],{"class":59,"line":3666},169,[57,3668,82],{"emptyLinePlaceholder":81},[57,3670,3672,3675,3677,3680,3682,3684,3686,3688],{"class":59,"line":3671},170,[57,3673,3674],{"class":186},"        notify_slack",[57,3676,219],{"class":190},[57,3678,3679],{"class":186},"summary",[57,3681,241],{"class":190},[57,3683,2532],{"class":267},[57,3685,271],{"class":212},[57,3687,275],{"class":274},[57,3689,287],{"class":190},[57,3691,3693],{"class":59,"line":3692},171,[57,3694,82],{"emptyLinePlaceholder":81},[57,3696,3698,3700,3702,3704,3706],{"class":59,"line":3697},172,[57,3699,2934],{"class":146},[57,3701,2937],{"class":309},[57,3703,1673],{"class":146},[57,3705,1676],{"class":150},[57,3707,1272],{"class":190},[57,3709,3711,3713,3715,3717,3719,3721,3724,3726,3728,3730,3732],{"class":59,"line":3710},173,[57,3712,2949],{"class":150},[57,3714,46],{"class":190},[57,3716,1688],{"class":186},[57,3718,219],{"class":190},[57,3720,1546],{"class":881},[57,3722,3723],{"class":236},"\"BACKUP FALLIDO: ",[57,3725,889],{"class":888},[57,3727,1709],{"class":186},[57,3729,920],{"class":888},[57,3731,233],{"class":236},[57,3733,287],{"class":190},[57,3735,3737,3739,3741,3743,3746,3748,3750,3752,3754,3757,3759,3761,3763,3765,3767,3769,3771,3773,3775,3778],{"class":59,"line":3736},174,[57,3738,3674],{"class":186},[57,3740,219],{"class":190},[57,3742,1546],{"class":881},[57,3744,3745],{"class":236},"\"Error: ",[57,3747,889],{"class":888},[57,3749,1709],{"class":186},[57,3751,920],{"class":888},[57,3753,3439],{"class":208},[57,3755,3756],{"class":236},"Servidor: ",[57,3758,889],{"class":888},[57,3760,1347],{"class":186},[57,3762,46],{"class":190},[57,3764,354],{"class":866},[57,3766,920],{"class":888},[57,3768,233],{"class":236},[57,3770,241],{"class":190},[57,3772,2532],{"class":267},[57,3774,271],{"class":212},[57,3776,3777],{"class":274},"False",[57,3779,287],{"class":190},[57,3781,3783],{"class":59,"line":3782},175,[57,3784,3785],{"class":146},"        raise\n",[57,3787,3789],{"class":59,"line":3788},176,[57,3790,82],{"emptyLinePlaceholder":81},[57,3792,3794],{"class":59,"line":3793},177,[57,3795,82],{"emptyLinePlaceholder":81},[57,3797,3799,3802,3805,3808,3810,3813,3815],{"class":59,"line":3798},178,[57,3800,3801],{"class":146},"if",[57,3803,3804],{"class":208}," __name__",[57,3806,3807],{"class":212}," ==",[57,3809,244],{"class":232},[57,3811,3812],{"class":236},"__main__",[57,3814,233],{"class":232},[57,3816,1272],{"class":190},[57,3818,3820,3823],{"class":59,"line":3819},179,[57,3821,3822],{"class":186},"    main",[57,3824,191],{"class":190},[711,3826],{},[30,3828,3830],{"id":3829},"paso-3-probar-el-script-manualmente","Paso 3: Probar el script manualmente",[12,3832,3833],{},"Antes de automatizar, ejecuta el script a mano para verificar que funciona:",[48,3835,3837],{"className":50,"code":3836,"language":52,"meta":53,"style":53},"cd /opt/backup\npython3 backup.py\n",[38,3838,3839,3847],{"__ignoreMap":53},[57,3840,3841,3844],{"class":59,"line":60},[57,3842,3843],{"class":1643},"cd",[57,3845,3846],{"class":236}," /opt/backup\n",[57,3848,3849,3851],{"class":59,"line":67},[57,3850,71],{"class":70},[57,3852,3853],{"class":236}," backup.py\n",[12,3855,3856],{},"Deberías ver la salida con cada base de datos respaldada, los archivos comprimidos y el resumen final. Verifica que los archivos existen:",[48,3858,3860],{"className":50,"code":3859,"language":52,"meta":53,"style":53},"ls -lh /var/backups/app/\n",[38,3861,3862],{"__ignoreMap":53},[57,3863,3864,3867,3870],{"class":59,"line":60},[57,3865,3866],{"class":70},"ls",[57,3868,3869],{"class":74}," -lh",[57,3871,3872],{"class":236}," /var/backups/app/\n",[109,3874,3876,3879],{"title":3875,"type":1992},"Prueba la restauración",[12,3877,3878],{},"Un backup sin prueba de restauración no es un backup. Antes de confiar en este script, restaura al menos una base de datos para verificar que el dump es válido:",[48,3880,3882],{"className":50,"code":3881,"language":52,"meta":53,"style":53},"pg_restore -h localhost -U app -d miapp_test /var/backups/app/20260210_030000/miapp.sql.gz\n",[38,3883,3884],{"__ignoreMap":53},[57,3885,3886,3889,3892,3895,3898,3901,3904,3907],{"class":59,"line":60},[57,3887,3888],{"class":70},"pg_restore",[57,3890,3891],{"class":74}," -h",[57,3893,3894],{"class":236}," localhost",[57,3896,3897],{"class":74}," -U",[57,3899,3900],{"class":236}," app",[57,3902,3903],{"class":74}," -d",[57,3905,3906],{"class":236}," miapp_test",[57,3908,3909],{"class":236}," /var/backups/app/20260210_030000/miapp.sql.gz\n",[30,3911,3913],{"id":3912},"paso-4-automatizar-con-cron","Paso 4: Automatizar con cron",[12,3915,3916],{},"Ahora que el script funciona, prográmalo con cron para que se ejecute automáticamente:",[48,3918,3920],{"className":50,"code":3919,"language":52,"meta":53,"style":53},"# Editar crontab del root\nsudo crontab -e\n",[38,3921,3922,3927],{"__ignoreMap":53},[57,3923,3924],{"class":59,"line":60},[57,3925,3926],{"class":63},"# Editar crontab del root\n",[57,3928,3929,3932,3935],{"class":59,"line":67},[57,3930,3931],{"class":70},"sudo",[57,3933,3934],{"class":236}," crontab",[57,3936,3937],{"class":74}," -e\n",[12,3939,3940],{},"Agrega la línea:",[48,3942,3946],{"className":3943,"code":3944,"language":3945,"meta":53,"style":53},"language-cron shiki shiki-themes material-theme-lighter github-light github-dark","# Backup diario a las 3:00 AM\n0 3 * * * /usr/bin/python3 /opt/backup/backup.py >> /var/log/backup-cron.log 2>&1\n","cron",[38,3947,3948,3953],{"__ignoreMap":53},[57,3949,3950],{"class":59,"line":60},[57,3951,3952],{},"# Backup diario a las 3:00 AM\n",[57,3954,3955],{"class":59,"line":67},[57,3956,3957],{},"0 3 * * * /usr/bin/python3 /opt/backup/backup.py >> /var/log/backup-cron.log 2>&1\n",[3959,3960,3962],"h3",{"id":3961},"sintaxis-de-cron","Sintaxis de cron",[48,3964,3967],{"className":3965,"code":3966,"language":105},[103],"┌───────── minuto (0-59)\n│ ┌─────── hora (0-23)\n│ │ ┌───── día del mes (1-31)\n│ │ │ ┌─── mes (1-12)\n│ │ │ │ ┌─ día de la semana (0-7, 0 y 7 = domingo)\n│ │ │ │ │\n0 3 * * *   # Todos los días a las 3:00 AM\n0 3 * * 0   # Solo domingos a las 3:00 AM\n0 */6 * * * # Cada 6 horas\n",[38,3968,3966],{"__ignoreMap":53},[12,3970,3971],{},"Verifica que el cron se guardó:",[48,3973,3975],{"className":50,"code":3974,"language":52,"meta":53,"style":53},"sudo crontab -l\n",[38,3976,3977],{"__ignoreMap":53},[57,3978,3979,3981,3983],{"class":59,"line":60},[57,3980,3931],{"class":70},[57,3982,3934],{"class":236},[57,3984,3985],{"class":74}," -l\n",[30,3987,3989],{"id":3988},"paso-5-enviar-backup-a-destino-remoto","Paso 5: Enviar backup a destino remoto",[12,3991,3992],{},"El script guarda los backups localmente. Para cumplir con la regla 3-2-1, necesitas una copia fuera del servidor. Aquí dos opciones:",[3959,3994,3996],{"id":3995},"opción-a-rsync-a-otro-servidor","Opción A: rsync a otro servidor",[12,3998,3999],{},"Agrega esta función al script o ejecútala después con cron:",[48,4001,4003],{"className":50,"code":4002,"language":52,"meta":53,"style":53},"# Sincronizar backups a servidor remoto\nrsync -avz --delete /var/backups/app/ backup@10.0.1.20:/var/backups/app-remoto/\n",[38,4004,4005,4010],{"__ignoreMap":53},[57,4006,4007],{"class":59,"line":60},[57,4008,4009],{"class":63},"# Sincronizar backups a servidor remoto\n",[57,4011,4012,4015,4018,4021,4024],{"class":59,"line":67},[57,4013,4014],{"class":70},"rsync",[57,4016,4017],{"class":74}," -avz",[57,4019,4020],{"class":74}," --delete",[57,4022,4023],{"class":236}," /var/backups/app/",[57,4025,4026],{"class":236}," backup@10.0.1.20:/var/backups/app-remoto/\n",[3959,4028,4030],{"id":4029},"opción-b-subir-a-s3","Opción B: Subir a S3",[12,4032,4033],{},"Agrega al script:",[48,4035,4037],{"className":132,"code":4036,"language":134,"meta":53,"style":53},"import boto3\n\ndef upload_to_s3(backup_dir: Path):\n    \"\"\"Sube el backup del día a S3.\"\"\"\n    s3 = boto3.client(\"s3\")\n    bucket = os.getenv(\"S3_BUCKET\", \"mi-empresa-backups\")\n\n    for file in backup_dir.glob(\"*\"):\n        key = f\"backups/{backup_dir.name}/{file.name}\"\n        s3.upload_file(str(file), bucket, key)\n        log.info(f\"  ✓ S3: s3://{bucket}/{key}\")\n",[38,4038,4039,4046,4050,4068,4077,4103,4136,4140,4169,4206,4236],{"__ignoreMap":53},[57,4040,4041,4043],{"class":59,"line":60},[57,4042,147],{"class":146},[57,4044,4045],{"class":150}," boto3\n",[57,4047,4048],{"class":59,"line":67},[57,4049,82],{"emptyLinePlaceholder":81},[57,4051,4052,4054,4057,4059,4062,4064,4066],{"class":59,"line":78},[57,4053,1196],{"class":881},[57,4055,4056],{"class":1199}," upload_to_s3",[57,4058,219],{"class":190},[57,4060,4061],{"class":2522},"backup_dir",[57,4063,1609],{"class":190},[57,4065,216],{"class":150},[57,4067,2545],{"class":190},[57,4069,4070,4072,4075],{"class":59,"line":85},[57,4071,1209],{"class":734},[57,4073,4074],{"class":740},"Sube el backup del día a S3.",[57,4076,735],{"class":734},[57,4078,4079,4082,4084,4087,4089,4092,4094,4096,4099,4101],{"class":59,"line":91},[57,4080,4081],{"class":150},"    s3 ",[57,4083,271],{"class":212},[57,4085,4086],{"class":150}," boto3",[57,4088,46],{"class":190},[57,4090,4091],{"class":186},"client",[57,4093,219],{"class":190},[57,4095,233],{"class":232},[57,4097,4098],{"class":236},"s3",[57,4100,233],{"class":232},[57,4102,287],{"class":190},[57,4104,4105,4108,4110,4112,4114,4116,4118,4120,4123,4125,4127,4129,4132,4134],{"class":59,"line":183},[57,4106,4107],{"class":150},"    bucket ",[57,4109,271],{"class":212},[57,4111,359],{"class":150},[57,4113,46],{"class":190},[57,4115,227],{"class":186},[57,4117,219],{"class":190},[57,4119,233],{"class":232},[57,4121,4122],{"class":236},"S3_BUCKET",[57,4124,233],{"class":232},[57,4126,241],{"class":190},[57,4128,244],{"class":232},[57,4130,4131],{"class":236},"mi-empresa-backups",[57,4133,233],{"class":232},[57,4135,287],{"class":190},[57,4137,4138],{"class":59,"line":194},[57,4139,82],{"emptyLinePlaceholder":81},[57,4141,4142,4144,4148,4151,4154,4156,4159,4161,4163,4165,4167],{"class":59,"line":199},[57,4143,1257],{"class":146},[57,4145,4147],{"class":4146},"sMMDD"," file",[57,4149,4150],{"class":146}," in",[57,4152,4153],{"class":150}," backup_dir",[57,4155,46],{"class":190},[57,4157,4158],{"class":186},"glob",[57,4160,219],{"class":190},[57,4162,233],{"class":232},[57,4164,3026],{"class":236},[57,4166,233],{"class":232},[57,4168,2545],{"class":190},[57,4170,4171,4174,4176,4178,4181,4183,4185,4187,4189,4191,4194,4196,4198,4200,4202,4204],{"class":59,"line":205},[57,4172,4173],{"class":150},"        key ",[57,4175,271],{"class":212},[57,4177,882],{"class":881},[57,4179,4180],{"class":236},"\"backups/",[57,4182,889],{"class":888},[57,4184,4061],{"class":150},[57,4186,46],{"class":190},[57,4188,1567],{"class":933},[57,4190,920],{"class":888},[57,4192,4193],{"class":236},"/",[57,4195,889],{"class":888},[57,4197,1618],{"class":4146},[57,4199,46],{"class":190},[57,4201,1567],{"class":933},[57,4203,920],{"class":888},[57,4205,1430],{"class":236},[57,4207,4208,4211,4213,4216,4218,4220,4222,4224,4226,4229,4231,4234],{"class":59,"line":255},[57,4209,4210],{"class":150},"        s3",[57,4212,46],{"class":190},[57,4214,4215],{"class":186},"upload_file",[57,4217,219],{"class":190},[57,4219,1928],{"class":309},[57,4221,219],{"class":190},[57,4223,1618],{"class":4146},[57,4225,1632],{"class":190},[57,4227,4228],{"class":186}," bucket",[57,4230,241],{"class":190},[57,4232,4233],{"class":186}," key",[57,4235,287],{"class":190},[57,4237,4238,4240,4242,4244,4246,4248,4251,4253,4256,4258,4260,4262,4265,4267,4269],{"class":59,"line":290},[57,4239,2949],{"class":150},[57,4241,46],{"class":190},[57,4243,112],{"class":186},[57,4245,219],{"class":190},[57,4247,1546],{"class":881},[57,4249,4250],{"class":236},"\"  ✓ S3: s3://",[57,4252,889],{"class":888},[57,4254,4255],{"class":186},"bucket",[57,4257,920],{"class":888},[57,4259,4193],{"class":236},[57,4261,889],{"class":888},[57,4263,4264],{"class":186},"key",[57,4266,920],{"class":888},[57,4268,233],{"class":236},[57,4270,287],{"class":190},[711,4272],{},[30,4274,4276],{"id":4275},"monitoreo-de-backups","Monitoreo de backups",[12,4278,4279],{},"Un backup automatizado que falla silenciosamente es peor que no tener backup — porque crees que estás protegido. Implementa al menos una de estas capas de verificación:",[12,4281,4282,4286],{},[4283,4284,4285],"strong",{},"Notificaciones"," — El script ya incluye notificación a Slack. Si no recibes el mensaje diario de \"backup completado\", algo está mal.",[12,4288,4289,4292],{},[4283,4290,4291],{},"Monitoreo con Zabbix"," — Configura un item que verifique que el archivo de backup más reciente tiene menos de 25 horas de antigüedad:",[48,4294,4296],{"className":50,"code":4295,"language":52,"meta":53,"style":53},"# Script para Zabbix agent\nfind /var/backups/app -maxdepth 1 -type d -mmin -1500 | wc -l\n",[38,4297,4298,4303],{"__ignoreMap":53},[57,4299,4300],{"class":59,"line":60},[57,4301,4302],{"class":63},"# Script para Zabbix agent\n",[57,4304,4305,4308,4311,4314,4316,4319,4322,4325,4328,4331,4334],{"class":59,"line":67},[57,4306,4307],{"class":70},"find",[57,4309,4310],{"class":236}," /var/backups/app",[57,4312,4313],{"class":74}," -maxdepth",[57,4315,1653],{"class":888},[57,4317,4318],{"class":74}," -type",[57,4320,4321],{"class":236}," d",[57,4323,4324],{"class":74}," -mmin",[57,4326,4327],{"class":74}," -1500",[57,4329,4330],{"class":212}," |",[57,4332,4333],{"class":70}," wc",[57,4335,3985],{"class":74},[12,4337,4338],{},"Si el resultado es 0, no hubo backup en las últimas 25 horas — alerta.",[12,4340,4341,4344],{},[4283,4342,4343],{},"Pruebas de restauración automáticas"," — El nivel más alto de confianza. Un cron semanal que restaura el último backup en una base de datos de prueba y verifica que los datos son consistentes.",[30,4346,4348],{"id":4347},"siguientes-pasos","Siguientes pasos",[12,4350,4351],{},"Con el backup automatizado funcionando, puedes mejorar la estrategia:",[4353,4354,4355,4364,4370,4379],"ul",{},[4356,4357,4358,41,4361,4363],"li",{},[4283,4359,4360],{},"WAL archiving",[16,4362,45],{"href":44}," — point-in-time recovery que permite restaurar a cualquier segundo, no solo al momento del dump diario",[4356,4365,4366,4369],{},[4283,4367,4368],{},"Backups incrementales"," con BorgBackup o restic — solo respaldan lo que cambió, reduciendo tiempo y almacenamiento",[4356,4371,4372,4378],{},[4283,4373,4374],{},[16,4375,4377],{"href":4376},"/servicios/infraestructura","Infraestructura de backup"," profesional con Veeam o Proxmox Backup Server para ambientes virtualizados",[4356,4380,4381,4384],{},[4283,4382,4383],{},"Plan de recuperación ante desastres (DRP)"," — documento que define exactamente qué hacer cuando todo falla",[4386,4387],"call-to-action",{"description":4388,"eyebrow":4389,"icon":4390,"label":4391,"title":4392,"to":4393},"Diseñamos e implementamos tu plan de backup y recuperación ante desastres con pruebas de restauración periódicas documentadas.","Backups empresariales","i-lucide-database-backup","Solicitar evaluación","¿Tu estrategia de backup está probada?","/contacto",[4395,4396,4397],"style",{},"html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .slqww, html code.shiki .slqww{--shiki-light:#6182B8;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s2W-s, html code.shiki .s2W-s{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#032F62;--shiki-default-font-style:inherit;--shiki-dark:#9ECBFF;--shiki-dark-font-style:inherit}html pre.shiki code .sithA, html code.shiki .sithA{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#032F62;--shiki-default-font-style:inherit;--shiki-dark:#9ECBFF;--shiki-dark-font-style:inherit}html pre.shiki code .swQdS, html code.shiki .swQdS{--shiki-light:#E53935;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sFwrP, html code.shiki .sFwrP{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#24292E;--shiki-default-font-style:inherit;--shiki-dark:#E1E4E8;--shiki-dark-font-style:inherit}html pre.shiki code .sMMDD, html code.shiki .sMMDD{--shiki-light:#90A4AE;--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":53,"searchDepth":67,"depth":78,"links":4399},[4400,4401,4402,4403,4404,4405,4408,4412,4413],{"id":32,"depth":67,"text":33},{"id":98,"depth":67,"text":99},{"id":125,"depth":67,"text":126},{"id":715,"depth":67,"text":716},{"id":3829,"depth":67,"text":3830},{"id":3912,"depth":67,"text":3913,"children":4406},[4407],{"id":3961,"depth":78,"text":3962},{"id":3988,"depth":67,"text":3989,"children":4409},[4410,4411],{"id":3995,"depth":78,"text":3996},{"id":4029,"depth":78,"text":4030},{"id":4275,"depth":67,"text":4276},{"id":4347,"depth":67,"text":4348},"tutorial",{"title":4416,"description":4417,"label":4391,"to":4393,"icon":4390},"¿Necesitas una estrategia de backup empresarial?","Diseñamos e implementamos tu plan de backup y recuperación ante desastres con pruebas de restauración periódicas.","2026-02-10","Aprende a crear un script de backup en Python que respalda bases de datos y archivos, los comprime, los sube a un destino remoto y limpia respaldos antiguos — todo automatizado con cron.",false,"md",[4423,4426,4429,4432],{"question":4424,"answer":4425},"¿Por qué usar Python en lugar de un script bash para backups?","Bash funciona bien para backups simples. Python aporta ventajas cuando necesitas lógica más compleja — manejo robusto de errores, notificaciones por correo o Slack, logs estructurados, rotación inteligente de respaldos y subida a cloud (S3, Azure Blob). Además, es más fácil de mantener y extender con el tiempo.",{"question":4427,"answer":4428},"¿Con qué frecuencia debería ejecutar los backups?","Depende de qué tan críticos son tus datos y cuánto puedes permitirte perder. Para bases de datos de producción (ERP, CRM), recomendamos backup completo diario y WAL archiving continuo para point-in-time recovery. Para archivos de aplicación, un backup diario suele ser suficiente.",{"question":4430,"answer":4431},"¿Cómo sé si mis backups realmente funcionan?","Un backup que nunca se prueba no es un backup — es una ilusión de seguridad. Programa pruebas de restauración al menos una vez al mes. El script de esta guía incluye notificaciones de éxito y error para que sepas inmediatamente si algo falló.",{"question":4433,"answer":4434},"¿Es seguro guardar el backup en el mismo servidor?","No como única copia. Si el servidor se daña (disco roto, ransomware, error humano), pierdes los datos y el backup. Siempre sigue la regla 3-2-1: tres copias, en dos medios diferentes, con una copia fuera del sitio (otro servidor, S3, etc).","/images/blog/backup-python-cron.jpg","Terminal mostrando ejecución de script Python de backup automatizado con resumen de archivos respaldados",{},"/blog/tutorial/backup-automatizado-python-cron",{"title":5,"description":4419},"blog/tutorial/backup-automatizado-python-cron",[134,4442,4443,3945,4444,4445,4414],"backup","linux","postgresql","automatizacion","n5tPBMSNmttCIQQ6yO_hg_MALOic9_N9ag8JF9aeVPA",{"path":4448,"title":4449},"/blog/tutorial/automatizar-ansible","Automatizar tareas de infraestructura con Ansible",{"path":4451,"title":4452},"/blog/tutorial/cicd-gitlab-guia-practica","Desplegar aplicaciones con CI/CD en GitLab — guía práctica",[4454,4461,4468],{"path":4455,"title":4456,"description":4457,"date":4458,"category":4414,"image":4459,"imageAlt":4460,"readingTime":295},"/blog/tutorial/apis-rest-python-fastapi","Crear APIs REST con Python y FastAPI para integraciones empresariales","Guía paso a paso para construir una API REST profesional con Python y FastAPI que conecte tu ERP, CRM o cualquier sistema con validación, autenticación y documentación automática.","2026-03-04","/images/blog/fastapi-api-rest.jpg","Editor de código mostrando una API FastAPI con documentación Swagger generada automáticamente",{"path":4462,"title":4463,"description":4464,"date":4465,"category":4414,"image":4466,"imageAlt":4467,"readingTime":199},"/blog/tutorial/configurar-firewall-ufw-linux","Configurar firewall en Linux con UFW — reglas esenciales","Guía paso a paso para configurar UFW (Uncomplicated Firewall) en Ubuntu y Debian con las reglas esenciales para proteger servidores de producción.","2026-03-01","/images/blog/ufw-firewall-linux.jpg","Terminal de Linux mostrando reglas de firewall UFW activas protegiendo un servidor de producción",{"path":4469,"title":4470,"description":4471,"date":4472,"category":4414,"image":4473,"imageAlt":4474,"readingTime":255},"/blog/tutorial/traefik-reverse-proxy-docker","Configurar Traefik como reverse proxy para contenedores Docker","Guía paso a paso para instalar Traefik como reverse proxy con descubrimiento automático de contenedores Docker, SSL con Let's Encrypt y dashboard de monitoreo.","2026-02-28","/images/blog/traefik-docker.jpg","Dashboard de Traefik mostrando rutas automáticas hacia múltiples contenedores Docker con SSL activo"]