Reemplaza logging por consola/archivo, agrega historial de intentos y soporte para notas de crédito

- Nuevo logger_manager.py (reemplaza logger_utilities.py): logging con formato de árbol,
  rotación diaria a sunat.log, y consola por stdout (antes iba por stderr y se duplicaba
  en logs/error.log del launchd).
- Nuevo historial_manager.py: registra cada intento de consulta/descarga en historial.db
  para auditoría, con limpieza automática de registros de más de 180 días.
- consulta_cpe.py: corrige el id de notas de crédito/débito (usa F7/F8, no 07/08) y
  agrega reintentos ante errores 5xx.
- hana_manager.py: suma consulta a ORPC para notas de crédito (Indicator 07) y soporte
  de ObjType para actualizar la tabla correcta (OPCH/ORPC) al marcar PDF/XML descargado.
- gestor_fallas.py / main.py: propagan objtype end-to-end y dejan constancia de cada
  intento en historial_manager.
- Elimina login_api.py y reporte_fallas.py (código no usado / script manual sin uso reciente).
- Actualiza .gitignore (sunat-venv/, historial.db*) y docs/ para reflejar el estado real
  del proyecto.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5 committed 2026-08-31 12:31:48 -05:00
1 parent 99a587c4da
commit c2503745bf
18 files changed
+647 -862

No files matched your search

+14 -12
View File
@@ -4,9 +4,11 @@ import platform
import subprocess
from pathlib import Path
from dotenv import load_dotenv
from logger_manager import get_logger
# ============================================================================
load_dotenv()
logger = get_logger(__name__)
SMB_SERVER = os.getenv("SMB_SERVER", "").strip()
SMB_SHARE = os.getenv("SMB_SHARE", "").strip()
@@ -50,7 +52,7 @@ def esta_montado(mount_point):
def montar_smb():
mount_point = get_mount_point()
if not mount_point:
print(" ⚠️ No hay punto de montaje configurado para este SO.")
logger.warning("No hay punto de montaje configurado para este SO.")
return False
if esta_montado(mount_point):
@@ -82,20 +84,20 @@ def montar_smb():
]
subprocess.run(cmd, check=True, capture_output=True)
print(f" ✅ SMB montado en: {mount_point}")
logger.info(f"SMB montado en: {mount_point}")
return True
except Exception as e:
print(f" ⚠️ Error montando SMB: {e}")
logger.error(f"Error montando SMB: {e}")
return False
# ============================================================================
def sincronizar_a_smb(ruta_local):
if not os.path.isdir(ruta_local):
print(f" ⚠️ La carpeta local no existe: {ruta_local}")
logger.warning(f"La carpeta local no existe: {ruta_local}")
return False
if not montar_smb():
print(" ❌ No se pudo sincronizar: SMB no montado.")
logger.error("No se pudo sincronizar: SMB no montado.")
return False
mount_point = get_mount_point()
@@ -112,12 +114,12 @@ def sincronizar_a_smb(ruta_local):
if not os.path.exists(ruta_destino):
os.makedirs(ruta_destino)
print(f" 📁 Carpeta creada: {ruta_destino}")
logger.info(f"Carpeta creada: {ruta_destino}")
source = os.path.abspath(ruta_local)
destination = os.path.abspath(ruta_destino)
print(f" 🚀 Sincronizando a {current_os.upper()}: {destination}")
logger.info(f"Sincronizando a {current_os.upper()}: {destination}")
try:
if current_os in ("mac", "linux"):
@@ -126,12 +128,12 @@ def sincronizar_a_smb(ruta_local):
f"-exec rsync -av --remove-source-files {{}} {destination}/ \\;"
)
subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True)
print(f" ✅ Archivos copiados a NAS")
logger.info("Archivos copiados a NAS")
subprocess.run(
f"find {source} -type f \\( -name '*.json' -o -name '*.zip' \\) -delete",
shell=True, check=True, capture_output=True, text=True,
)
print(f" 🧹 Limpieza de temporales completada")
logger.info("Limpieza de temporales completada")
elif current_os == "windows":
import shutil
for archivo in os.listdir(source):
@@ -143,14 +145,14 @@ def sincronizar_a_smb(ruta_local):
if os.path.exists(dst_path):
os.remove(dst_path)
shutil.move(src_path, dst_path)
print(f" ✅ Archivos copiados a NAS")
logger.info("Archivos copiados a NAS")
for archivo in os.listdir(source):
if archivo.lower().endswith((".json", ".zip")):
os.remove(os.path.join(source, archivo))
print(f" 🧹 Limpieza de temporales completada")
logger.info("Limpieza de temporales completada")
return True
except Exception as e:
print(f" ❌ Error en sincronización: {e}")
logger.error(f"Error en sincronización: {e}")
return False
# ============================================================================