From 04e41e6445a1f05efc0c6551b6b6c7e2251d93c2 Mon Sep 17 00:00:00 2001 From: ernestohc21 Date: Thu, 6 Nov 2025 09:55:51 -0700 Subject: [PATCH] =?UTF-8?q?Implementar=20nueva=20l=C3=B3gica=20de=20estado?= =?UTF-8?q?s=20de=20sincronizaci=C3=B3n=20basada=20en=20ubicaci=C3=B3n=20d?= =?UTF-8?q?e=20archivos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Agregar nuevos estados: Recibido y Procesando - Implementar función determine_sync_status() que verifica: * D:\sftp\{nodo}.zip -> estado 'Recibido' * D:\BackupSFTP\{nodo}.zip -> estado 'Procesando' * Comparación de fechas para estado 'Actualizada' - Actualizar get_node_status() y get_all_nodes() para usar nueva lógica - Agregar script de pruebas test_sync_status.py --- sync_api/models/sync_models.py | 2 + sync_api/routers/nodes.py | 81 ++++++++++++++++----- test_sync_status.py | 126 +++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 18 deletions(-) create mode 100644 test_sync_status.py diff --git a/sync_api/models/sync_models.py b/sync_api/models/sync_models.py index bd33f3f..b341d6d 100644 --- a/sync_api/models/sync_models.py +++ b/sync_api/models/sync_models.py @@ -9,6 +9,8 @@ class EstadoSincronizacion(str, Enum): ACTUALIZADA = "Actualizada" ATENCION = "Atención" ERROR = "Error" + RECIBIDO = "Recibido" + PROCESANDO = "Procesando" class NodeStatus(BaseModel): diff --git a/sync_api/routers/nodes.py b/sync_api/routers/nodes.py index e9599d7..4101b30 100644 --- a/sync_api/routers/nodes.py +++ b/sync_api/routers/nodes.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, HTTPException, Path, Query from typing import List, Optional import logging +import os from datetime import datetime, timedelta from database.connection import db_connection from models.sync_models import ( @@ -42,6 +43,64 @@ def calculate_time_since_sync(last_sync: Optional[datetime]) -> Optional[str]: return f"{days} días" +def determine_sync_status(node_name: str, last_restore_date: Optional[datetime] = None) -> str: + """ + Determina el estado de sincronización basado en la ubicación de los archivos ZIP del nodo. + + Lógica: + - Si existe en D:\sftp -> "Recibido" + - Si existe en D:\BackupSFTP -> "Procesando" + - Si la fecha de última sincronización es mayor a la del archivo en D:\BackupSFTP -> "Actualizada" + - En otros casos sigue la lógica anterior basada en fechas + """ + try: + sftp_path = f"D:\\sftp\\{node_name}.zip" + backup_sftp_path = f"D:\\BackupSFTP\\{node_name}.zip" + + # Verificar si existe en D:\sftp (prioridad más alta) + if os.path.exists(sftp_path): + return EstadoSincronizacion.RECIBIDO.value + + # Verificar si existe en D:\BackupSFTP + if os.path.exists(backup_sftp_path): + # Si tenemos fecha de última restauración, comparar + if last_restore_date: + try: + # Obtener fecha de modificación del archivo + file_mod_time = datetime.fromtimestamp(os.path.getmtime(backup_sftp_path)) + + # Si la última sincronización es más reciente que el archivo + if last_restore_date > file_mod_time: + return EstadoSincronizacion.ACTUALIZADA.value + except OSError: + # Si hay error leyendo el archivo, asumir procesando + pass + + return EstadoSincronizacion.PROCESANDO.value + + # Si no existe en ninguna ruta SFTP, usar lógica basada en fechas de restauración + if last_restore_date is None: + return EstadoSincronizacion.ERROR.value + elif last_restore_date < datetime.now() - timedelta(days=7): + return EstadoSincronizacion.ERROR.value + elif last_restore_date < datetime.now() - timedelta(hours=24): + return EstadoSincronizacion.ATENCION.value + else: + return EstadoSincronizacion.ACTUALIZADA.value + + except Exception as e: + logger.error(f"Error determinando estado de sincronización para {node_name}: {str(e)}") + # En caso de error, usar lógica de fechas como fallback + if last_restore_date is None: + return EstadoSincronizacion.ERROR.value + elif last_restore_date < datetime.now() - timedelta(days=7): + return EstadoSincronizacion.ERROR.value + elif last_restore_date < datetime.now() - timedelta(hours=24): + return EstadoSincronizacion.ATENCION.value + else: + return EstadoSincronizacion.ACTUALIZADA.value + + @router.get("/{node_name}/status", response_model=NodeStatus, summary="Obtener estado específico de un nodo", @@ -108,15 +167,8 @@ async def get_node_status( last_restore_date = restore_info.get('last_restore_date') size_mb = restore_info.get('size_mb', 0) - # Determinar estado basado en fecha de restauración - if last_restore_date is None: - estado = 'Error' - elif last_restore_date < datetime.now() - timedelta(days=7): - estado = 'Error' - elif last_restore_date < datetime.now() - timedelta(hours=24): - estado = 'Atención' - else: - estado = 'Actualizada' + # Determinar estado basado en ubicación de archivos y fecha de restauración + estado = determine_sync_status(node_name, last_restore_date) # Calcular tiempo desde última sincronización time_since_sync = calculate_time_since_sync(last_restore_date) @@ -207,15 +259,8 @@ async def get_all_nodes( # Buscar por nombre de BD o nodo last_restore = restore_map.get(bd_name) or restore_map.get(nodo_name) - # Determinar estado basado en fecha de restauración - if last_restore is None: - estado_sync = 'Error' - elif last_restore < datetime.now() - timedelta(days=7): - estado_sync = 'Error' - elif last_restore < datetime.now() - timedelta(hours=24): - estado_sync = 'Atención' - else: - estado_sync = 'Actualizada' + # Determinar estado basado en ubicación de archivos y fecha de restauración + estado_sync = determine_sync_status(nodo_name, last_restore) # Aplicar filtros if estado and estado_sync != estado.value: diff --git a/test_sync_status.py b/test_sync_status.py new file mode 100644 index 0000000..25a60fa --- /dev/null +++ b/test_sync_status.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +Script de prueba para validar la nueva lógica de estados de sincronización +""" + +import os +import sys +from datetime import datetime, timedelta +from pathlib import Path + +# Agregar el directorio padre al path para importar los módulos +sys.path.insert(0, str(Path(__file__).parent / 'sync_api')) + +from routers.nodes import determine_sync_status +from models.sync_models import EstadoSincronizacion + + +def create_test_files(): + """Crear archivos de prueba para validar la lógica""" + + # Crear directorios de prueba si no existen + os.makedirs("D:\\sftp", exist_ok=True) + os.makedirs("D:\\BackupSFTP", exist_ok=True) + + # Crear archivo de prueba en D:\sftp + with open("D:\\sftp\\NODO_TEST1.zip", "w") as f: + f.write("archivo de prueba") + + # Crear archivo de prueba en D:\BackupSFTP + with open("D:\\BackupSFTP\\NODO_TEST2.zip", "w") as f: + f.write("archivo de prueba") + + print("Archivos de prueba creados:") + print("- D:\\sftp\\NODO_TEST1.zip") + print("- D:\\BackupSFTP\\NODO_TEST2.zip") + + +def test_sync_status_logic(): + """Probar la lógica de determinación de estados""" + + print("\n=== PRUEBAS DE LÓGICA DE ESTADOS ===\n") + + # Prueba 1: Nodo con archivo en D:\sftp (debe ser "Recibido") + print("1. Prueba: Archivo en D:\\sftp") + status = determine_sync_status("NODO_TEST1") + print(f" Estado: {status}") + print(f" Esperado: {EstadoSincronizacion.RECIBIDO.value}") + print(f" ✓ Correcto" if status == EstadoSincronizacion.RECIBIDO.value else " ✗ Incorrecto") + + # Prueba 2: Nodo con archivo en D:\BackupSFTP (debe ser "Procesando") + print("\n2. Prueba: Archivo en D:\\BackupSFTP") + status = determine_sync_status("NODO_TEST2") + print(f" Estado: {status}") + print(f" Esperado: {EstadoSincronizacion.PROCESANDO.value}") + print(f" ✓ Correcto" if status == EstadoSincronizacion.PROCESANDO.value else " ✗ Incorrecto") + + # Prueba 3: Nodo sin archivo, con fecha reciente (debe ser "Actualizada") + print("\n3. Prueba: Sin archivo, fecha reciente") + recent_date = datetime.now() - timedelta(hours=1) + status = determine_sync_status("NODO_TEST3", recent_date) + print(f" Estado: {status}") + print(f" Esperado: {EstadoSincronizacion.ACTUALIZADA.value}") + print(f" ✓ Correcto" if status == EstadoSincronizacion.ACTUALIZADA.value else " ✗ Incorrecto") + + # Prueba 4: Nodo sin archivo, fecha antigua (debe ser "Error") + print("\n4. Prueba: Sin archivo, fecha antigua") + old_date = datetime.now() - timedelta(days=10) + status = determine_sync_status("NODO_TEST4", old_date) + print(f" Estado: {status}") + print(f" Esperado: {EstadoSincronizacion.ERROR.value}") + print(f" ✓ Correcto" if status == EstadoSincronizacion.ERROR.value else " ✗ Incorrecto") + + # Prueba 5: Nodo sin archivo, sin fecha (debe ser "Error") + print("\n5. Prueba: Sin archivo, sin fecha") + status = determine_sync_status("NODO_TEST5", None) + print(f" Estado: {status}") + print(f" Esperado: {EstadoSincronizacion.ERROR.value}") + print(f" ✓ Correcto" if status == EstadoSincronizacion.ERROR.value else " ✗ Incorrecto") + + # Prueba 6: Archivo en BackupSFTP con fecha más reciente que el archivo (debe ser "Actualizada") + print("\n6. Prueba: Archivo en BackupSFTP, fecha más reciente") + future_date = datetime.now() + timedelta(hours=1) # Fecha futura para simular más reciente + status = determine_sync_status("NODO_TEST2", future_date) + print(f" Estado: {status}") + print(f" Esperado: {EstadoSincronizacion.ACTUALIZADA.value}") + print(f" ✓ Correcto" if status == EstadoSincronizacion.ACTUALIZADA.value else " ✗ Incorrecto") + + +def cleanup_test_files(): + """Limpiar archivos de prueba""" + test_files = [ + "D:\\sftp\\NODO_TEST1.zip", + "D:\\BackupSFTP\\NODO_TEST2.zip" + ] + + for file_path in test_files: + try: + if os.path.exists(file_path): + os.remove(file_path) + print(f"Archivo eliminado: {file_path}") + except Exception as e: + print(f"Error eliminando {file_path}: {e}") + + +if __name__ == "__main__": + print("=== SCRIPT DE PRUEBA PARA ESTADOS DE SINCRONIZACIÓN ===") + + try: + # Solo crear archivos de prueba si los directorios existen + if os.path.exists("D:\\") and os.access("D:\\", os.W_OK): + create_test_files() + test_sync_status_logic() + + # Preguntar si limpiar archivos de prueba + response = input("\n¿Deseas eliminar los archivos de prueba? (s/n): ") + if response.lower() in ['s', 'si', 'yes', 'y']: + cleanup_test_files() + else: + print("No se puede acceder al disco D: o no hay permisos de escritura") + print("Probando solo la lógica sin archivos reales...") + test_sync_status_logic() + + except Exception as e: + print(f"Error durante las pruebas: {e}") + import traceback + traceback.print_exc() \ No newline at end of file