fix(admin): resolve 'This page couldn't load' crash with auto chunk recovery, error boundaries, and null safety

This commit is contained in:
AI Bot 2026-09-20 13:48:15 -05:00
parent 93e915c341
commit 1bba620689

View File

@ -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()