Implementar nueva lógica de estados de sincronización basada en ubicación de archivos

- 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
This commit is contained in:
2025-11-06 09:55:51 -07:00
parent 733ef04eeb
commit 04e41e6445
3 changed files with 191 additions and 18 deletions

View File

@@ -9,6 +9,8 @@ class EstadoSincronizacion(str, Enum):
ACTUALIZADA = "Actualizada" ACTUALIZADA = "Actualizada"
ATENCION = "Atención" ATENCION = "Atención"
ERROR = "Error" ERROR = "Error"
RECIBIDO = "Recibido"
PROCESANDO = "Procesando"
class NodeStatus(BaseModel): class NodeStatus(BaseModel):

View File

@@ -1,6 +1,7 @@
from fastapi import APIRouter, HTTPException, Path, Query from fastapi import APIRouter, HTTPException, Path, Query
from typing import List, Optional from typing import List, Optional
import logging import logging
import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from database.connection import db_connection from database.connection import db_connection
from models.sync_models import ( 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" 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", @router.get("/{node_name}/status",
response_model=NodeStatus, response_model=NodeStatus,
summary="Obtener estado específico de un nodo", 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') last_restore_date = restore_info.get('last_restore_date')
size_mb = restore_info.get('size_mb', 0) size_mb = restore_info.get('size_mb', 0)
# Determinar estado basado en fecha de restauración # Determinar estado basado en ubicación de archivos y fecha de restauración
if last_restore_date is None: estado = determine_sync_status(node_name, last_restore_date)
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'
# Calcular tiempo desde última sincronización # Calcular tiempo desde última sincronización
time_since_sync = calculate_time_since_sync(last_restore_date) 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 # Buscar por nombre de BD o nodo
last_restore = restore_map.get(bd_name) or restore_map.get(nodo_name) last_restore = restore_map.get(bd_name) or restore_map.get(nodo_name)
# Determinar estado basado en fecha de restauración # Determinar estado basado en ubicación de archivos y fecha de restauración
if last_restore is None: estado_sync = determine_sync_status(nodo_name, last_restore)
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'
# Aplicar filtros # Aplicar filtros
if estado and estado_sync != estado.value: if estado and estado_sync != estado.value:

126
test_sync_status.py Normal file
View File

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