diff --git a/scripts/deploy_admin_error_recovery_and_apk.py b/scripts/deploy_admin_error_recovery_and_apk.py index 1d06cd5..83b68c8 100644 --- a/scripts/deploy_admin_error_recovery_and_apk.py +++ b/scripts/deploy_admin_error_recovery_and_apk.py @@ -41,54 +41,61 @@ SYNC_FILES = [ ] def main(): - print("[1/5] Verificando APK local...") + print("[1/5] Verificando APK local...", flush=True) if not os.path.exists(LOCAL_APK): - print(f" [ERROR] {LOCAL_APK} no encontrado.") + print(f" [ERROR] {LOCAL_APK} no encontrado.", flush=True) sys.exit(1) size_mb = os.path.getsize(LOCAL_APK) / (1024 * 1024) - print(f" -> APK verificado: {LOCAL_APK} ({size_mb:.2f} MB)") + print(f" -> APK verificado: {LOCAL_APK} ({size_mb:.2f} MB)", flush=True) - print("[2/5] Git add, commit y push a origin main...") + print("[2/5] Git add, commit y push a origin main...", flush=True) try: subprocess.run(["git", "add", "."], cwd=r"c:\Users\Carlos\dev\almeira-web", check=False) msg = "fix(admin): resolve 'This page couldn't load' crash with auto chunk recovery, error boundaries, and null safety" commit_res = subprocess.run(["git", "commit", "-m", msg], cwd=r"c:\Users\Carlos\dev\almeira-web", capture_output=True, text=True) - print(" -> Git commit:", (commit_res.stdout or commit_res.stderr or "OK").strip()[:200]) + print(" -> Git commit:", (commit_res.stdout or commit_res.stderr or "OK").strip()[:150], flush=True) push_res = subprocess.run(["git", "push", "origin", "main"], cwd=r"c:\Users\Carlos\dev\almeira-web", capture_output=True, text=True) - print(" -> Git push:", (push_res.stdout or push_res.stderr or "OK").strip()[:200]) + print(" -> Git push:", (push_res.stdout or push_res.stderr or "OK").strip()[:150], flush=True) except Exception as e: - print(" [WARN Git]:", e) + print(" [WARN Git]:", e, flush=True) - print("[3/5] Subiendo APK y archivos modificados al VPS vía SFTP...") + print("[3/5] Subiendo APK y archivos modificados al VPS vía SFTP...", flush=True) ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect(hostname=HOST, username=USER, password=PASSWORD, timeout=15) - sftp = ssh.open_sftp() + ssh.connect(hostname=HOST, username=USER, password=PASSWORD, timeout=20) - print(f" -> Subiendo APK ({size_mb:.2f} MB)...") + # 1. Crear directorios necesarios antes de abrir SFTP + dirs_to_make = set() + for rel in SYNC_FILES: + rem = f"{REMOTE_BASE}/{rel}" + dirs_to_make.add(os.path.dirname(rem)) + for d in dirs_to_make: + ssh.exec_command(f"mkdir -p {d}") + time.sleep(0.5) + + # 2. Abrir SFTP para subida + sftp = ssh.open_sftp() + print(f" -> Subiendo APK ({size_mb:.2f} MB)...", flush=True) sftp.put(LOCAL_APK, REMOTE_APK) + print(" -> APK subido al VPS.", flush=True) for rel in SYNC_FILES: loc = os.path.join(LOCAL_APP, rel.replace("/", os.sep)) rem = f"{REMOTE_BASE}/{rel}" - rem_dir = os.path.dirname(rem) - try: - # Crear directorio remoto si no existe - stdin, stdout, stderr = ssh.exec_command(f"mkdir -p {rem_dir}") - stdout.channel.recv_exit_status() - if os.path.exists(loc): + if os.path.exists(loc): + try: sftp.put(loc, rem) - print(f" -> Sincronizado por SFTP: {rel}") - except Exception as e: - print(f" [WARN SFTP {rel}]:", e) + print(f" -> Sincronizado por SFTP: {rel}", flush=True) + except Exception as e: + print(f" [WARN SFTP {rel}]:", e, flush=True) sftp.close() - print("[4/5] Identificando contenedor activo de DripZone y actualizando...") + print("[4/5] Identificando contenedor activo de DripZone y actualizando...", flush=True) stdin, stdout, stderr = ssh.exec_command("docker ps --format '{{.Names}}'") names = [n.strip() for n in stdout.read().decode('utf-8').splitlines() if n.strip()] drip_containers = [n for n in names if "bfmno" in n or "drip" in n] - print(" Contenedores activos:", drip_containers) + print(" Contenedores activos:", drip_containers, flush=True) for c in drip_containers: ssh.exec_command(f"docker cp {REMOTE_APK} {c}:/app/public/DripZone.apk || true") @@ -98,7 +105,7 @@ def main(): rem_in_container_dir = os.path.dirname(f"/app/{rel}") ssh.exec_command(f"docker exec {c} mkdir -p {rem_in_container_dir} 2>/dev/null || true") ssh.exec_command(f"docker cp {loc} {c}:/app/{rel} 2>/dev/null || true") - print(f" -> Archivos copiados a: {c}") + print(f" -> Archivos copiados a: {c}", flush=True) ssh.close() @@ -107,11 +114,11 @@ def main(): req = urllib.request.Request(APK_URL, method='HEAD') with urllib.request.urlopen(req, context=ctx, timeout=15) as res: live_size = int(res.headers.get('Content-Length', 0)) / (1024 * 1024) - print(f" [OK] Verificado online: {APK_URL} (Status {res.status}, {live_size:.2f} MB)") + print(f" [OK] Verificado online: {APK_URL} (Status {res.status}, {live_size:.2f} MB)", flush=True) except Exception as e: - print(f" [WARN HEAD]: {e}") + print(f" [WARN HEAD]: {e}", flush=True) - print("[5/5] Enviando solución y APK al WhatsApp de Carlos (+57 316 745 5877)...") + print("[5/5] Enviando solución y APK al WhatsApp de Carlos (+57 316 745 5877)...", flush=True) message_text = ( "🚀 *DripZone - Corrección de Error 'This page couldn't load' en Admin Productos*\n\n" "¡Hola Carlos! Se diagnosticó y solucionó por completo la pantalla de error que experimentaste en `admin/productos`:\n\n" @@ -123,7 +130,7 @@ def main(): "• **Auto-Recuperación de Chunks (`ChunkLoadRecovery.tsx`)**: Si un usuario tiene una pestaña abierta durante un despliegue de Coolify, el sistema detecta la actualización de versión y recarga limpiamente la página en segundo plano sin mostrar ningún error.\n" "• **Error Boundaries Oficiales (`admin/error.tsx` & `global-error.tsx`)**: Interfaz personalizada DripZone con diagnóstico claro y botón de recarga con bypass de caché para administradores.\n" "• **Null Safety Reforzado**: Métodos de vigencia, ordenamiento y renderizado protegidos con validaciones defensivas.\n" - f"• **Catálogo Sincronizado**: 416 productos vigentes y 7 categorías activas y filtrables sin bloqueos.\n\n" + f"• **Catálogo Sincronizado**: 416 publicaciones vigentes y 7 categorías activas y filtrables sin bloqueos.\n\n" f"📦 *APK Android Actualizado:* {APK_URL}\n\n" "Te adjunto el APK limpio a continuación." ) @@ -135,9 +142,9 @@ def main(): ) try: with urllib.request.urlopen(req_text, context=ctx, timeout=30) as resp: - print(" [OK] Mensaje explicativo enviado a WhatsApp (Status:", resp.status, ")") + print(f" [OK] Mensaje explicativo enviado a WhatsApp (Status: {resp.status})", flush=True) except Exception as e: - print(" [Error texto WhatsApp]:", e) + print(" [Error texto WhatsApp]:", e, flush=True) req_media = urllib.request.Request( f"{EVOLUTION_API_URL}/message/sendMedia/{INSTANCE_NAME}", @@ -153,11 +160,11 @@ def main(): ) try: with urllib.request.urlopen(req_media, context=ctx, timeout=120) as resp: - print(" [OK] Documento APK enviado a WhatsApp (Status:", resp.status, ")") + print(f" [OK] Documento APK enviado a WhatsApp (Status: {resp.status})", flush=True) except Exception as e: - print(" [Error envío documento APK]:", e) + print(" [Error envío documento APK]:", e, flush=True) - print("\n✅ [DESPLIEGUE Y NOTIFICACIÓN COMPLETADOS EXITOSAMENTE]") + print("\n✅ [DESPLIEGUE Y NOTIFICACIÓN COMPLETADOS EXITOSAMENTE]", flush=True) if __name__ == "__main__": main()