modificado para obtener todas las facturas que tiene el casillero de validación vacío, no más de 30 días

This commit is contained in:
George committed 2026-08-07 13:40:04 -05:00
1 parent 22aec163d2
commit 9f2d96e64c
9 files changed
+213 -198

No files matched your search

+4 -4
View File
@@ -19,8 +19,8 @@
}, },
{ {
"CompanyDB": "SBO_CIRUGIA", "CompanyDB": "SBO_CIRUGIA",
"Client_ID": "21d41835-0289-4b3c-99c0-ff0591d2233d", "Client_ID": "258001f9-ed7b-4f93-84da-7b113a269401",
"Client_Secret": "hzKoLbgjRqLUmNhsLagb7w==", "Client_Secret": "Y2YfcAB/wWcS4Cln/Rth5w==",
"Ruc": "20521486857", "Ruc": "20521486857",
"Locked": "N", "Locked": "N",
"UserName": "manager", "UserName": "manager",
@@ -37,8 +37,8 @@
}, },
{ {
"CompanyDB": "SBO_CALICAY", "CompanyDB": "SBO_CALICAY",
"Client_ID": "d7ceb759-b6dc-4d97-a0c7-42bfc5226c53", "Client_ID": "66419990-c40b-4ed4-b423-c857039c955c",
"Client_Secret": "MIjVHUtvqEIeE06M35nOuQ==", "Client_Secret": "FWXTRHpUv+DeAif3hxxn+w==",
"Ruc": "20611487143", "Ruc": "20611487143",
"Locked": "N", "Locked": "N",
"UserName": "manager", "UserName": "manager",
+20 -21
View File
@@ -8,6 +8,9 @@ from hdbcli import dbapi
from dotenv import load_dotenv from dotenv import load_dotenv
from constants import TABLAS, ESTADOS_COMPROBANTE, ESTADOS_CONTRIBUYENTE, CONDICIONES_DOMICILIADO from constants import TABLAS, ESTADOS_COMPROBANTE, ESTADOS_CONTRIBUYENTE, CONDICIONES_DOMICILIADO
from logger_manager import get_logger
logger = get_logger()
# ============================================================================ # ============================================================================
# CONFIGURACIÓN # CONFIGURACIÓN
@@ -34,11 +37,11 @@ def get_connection():
) )
return conn return conn
except Exception as e: except Exception as e:
print(f" ❌ Error conectando a HANA: {e}") logger.error(f" ❌ Error conectando a HANA: {e}")
return None return None
# ============================================================================ # ============================================================================
def obtener_documentos_creados_actualizados_hace_60_minutos(schema, minutes=60): def obtener_documentos_aun_no_validados_ultimos_30_dias(schema):
""" """
Se conecta a la base de datos SAP HANA, realiza una consulta a la tabla OPCH, Se conecta a la base de datos SAP HANA, realiza una consulta a la tabla OPCH,
y formatea el resultado para que coincida con el esquema de documentos.json. y formatea el resultado para que coincida con el esquema de documentos.json.
@@ -60,11 +63,7 @@ def obtener_documentos_creados_actualizados_hace_60_minutos(schema, minutes=60):
FROM "{schema}".OPCH T0 FROM "{schema}".OPCH T0
INNER JOIN "{schema}".OCRD T3 ON T3."CardCode" = T0."CardCode" INNER JOIN "{schema}".OCRD T3 ON T3."CardCode" = T0."CardCode"
INNER JOIN "{schema}".OUSR T1 ON T1."USERID" = T0."UserSign2" INNER JOIN "{schema}".OUSR T1 ON T1."USERID" = T0."UserSign2"
WHERE TO_TIMESTAMP(TO_VARCHAR(T0."UpdateDate", 'YYYY-MM-DD') WHERE T0."UpdateDate" >= ADD_DAYS(CURRENT_DATE, -30)
|| ' ' || LPAD(TO_VARCHAR(T0."UpdateTS"), 6, '0'),
'YYYY-MM-DD HH24MISS')
>= ADD_SECONDS(CURRENT_TIMESTAMP, {-1 * minutes * 60})
AND T0."CANCELED" = 'N' AND T0."CANCELED" = 'N'
AND IFNULL(T0."Indicator",'') IN ('01','02','03','08') AND IFNULL(T0."Indicator",'') IN ('01','02','03','08')
AND LENGTH(IFNULL(T0."FolioPref",'')) > 0 AND LENGTH(IFNULL(T0."FolioPref",'')) > 0
@@ -73,7 +72,7 @@ def obtener_documentos_creados_actualizados_hace_60_minutos(schema, minutes=60):
AND (LENGTH(IFNULL(T0."U_PGX_STMENSAJE",'')) = 0 OR LOCATE('NO EXISTE', IFNULL(T0."U_PGX_STMENSAJE",'')) > 0) AND (LENGTH(IFNULL(T0."U_PGX_STMENSAJE",'')) = 0 OR LOCATE('NO EXISTE', IFNULL(T0."U_PGX_STMENSAJE",'')) > 0)
ORDER BY T0."CreateDate" DESC; ORDER BY T0."CreateDate" DESC;
""" """
print(f"🔌 Conectando al servidor SAP HANA ({HANA_HOST}:{HANA_PORT}) para schema {schema}...") logger.info(f"🔌 Conectando al servidor SAP HANA ({HANA_HOST}:{HANA_PORT}) para schema {schema}...")
connection = get_connection() connection = get_connection()
if not connection: if not connection:
raise ConnectionError("No se pudo establecer la conexión a SAP HANA.") raise ConnectionError("No se pudo establecer la conexión a SAP HANA.")
@@ -94,14 +93,14 @@ def obtener_documentos_creados_actualizados_hace_60_minutos(schema, minutes=60):
return results return results
except Exception as e: except Exception as e:
print(f" ❌ Error en consulta HANA: {e}") logger.error(f" ❌ Error en consulta HANA: {e}")
return [] return []
finally: finally:
if cursor: if cursor:
cursor.close() cursor.close()
if connection: if connection:
connection.close() connection.close()
print(f" 🔒 [{sys._getframe().f_code.co_name}] Conexión HANA cerrada.") logger.info(f" 🔒 [{sys._getframe().f_code.co_name}] Conexión HANA cerrada.")
# ============================================================================ # ============================================================================
def obtener_documentos_fecha_emision_ultimos_8_dias(schema): def obtener_documentos_fecha_emision_ultimos_8_dias(schema):
@@ -129,7 +128,7 @@ def obtener_documentos_fecha_emision_ultimos_8_dias(schema):
AND IFNULL(T3."U_EXX_TIPODOCU",'') IN ('6','4') AND IFNULL(T3."U_EXX_TIPODOCU",'') IN ('6','4')
ORDER BY T0."U_PGX_STMENSAJE", T0."CreateDate" DESC; ORDER BY T0."U_PGX_STMENSAJE", T0."CreateDate" DESC;
""" """
print(f"🔌 Conectando al servidor SAP HANA ({HANA_HOST}:{HANA_PORT}) para schema {schema}...") logger.info(f"🔌 Conectando al servidor SAP HANA ({HANA_HOST}:{HANA_PORT}) para schema {schema}...")
connection = get_connection() connection = get_connection()
if not connection: if not connection:
raise ConnectionError("No se pudo establecer la conexión a SAP HANA.") raise ConnectionError("No se pudo establecer la conexión a SAP HANA.")
@@ -148,14 +147,14 @@ def obtener_documentos_fecha_emision_ultimos_8_dias(schema):
return results return results
except Exception as e: except Exception as e:
print(f" ❌ Error en consulta HANA: {e}") logger.error(f" ❌ Error en consulta HANA: {e}")
return [] return []
finally: finally:
if cursor: if cursor:
cursor.close() cursor.close()
if connection: if connection:
connection.close() connection.close()
print(f" 🔒 [{sys._getframe().f_code.co_name}] Conexión HANA cerrada.") logger.info(f" 🔒 [{sys._getframe().f_code.co_name}] Conexión HANA cerrada.")
# ============================================================================ # ============================================================================
def obtener_documentos_creados_hace_x_dias(schema, dias=8): def obtener_documentos_creados_hace_x_dias(schema, dias=8):
@@ -184,7 +183,7 @@ def obtener_documentos_creados_hace_x_dias(schema, dias=8):
AND IFNULL(T3."U_EXX_TIPODOCU",'') IN ('6','4') AND IFNULL(T3."U_EXX_TIPODOCU",'') IN ('6','4')
ORDER BY T0."CreateDate" DESC; ORDER BY T0."CreateDate" DESC;
""" """
print(f"🔌 Conectando al servidor SAP HANA ({HANA_HOST}:{HANA_PORT}) para schema {schema}...") logger.info(f"🔌 Conectando al servidor SAP HANA ({HANA_HOST}:{HANA_PORT}) para schema {schema}...")
connection = get_connection() connection = get_connection()
if not connection: if not connection:
raise ConnectionError("No se pudo establecer la conexión a SAP HANA.") raise ConnectionError("No se pudo establecer la conexión a SAP HANA.")
@@ -203,14 +202,14 @@ def obtener_documentos_creados_hace_x_dias(schema, dias=8):
return results return results
except Exception as e: except Exception as e:
print(f" ❌ Error en consulta HANA: {e}") logger.error(f" ❌ Error en consulta HANA: {e}")
return [] return []
finally: finally:
if cursor: if cursor:
cursor.close() cursor.close()
if connection: if connection:
connection.close() connection.close()
print(f" 🔒 [{sys._getframe().f_code.co_name}] Conexión HANA cerrada.") logger.info(f" 🔒 [{sys._getframe().f_code.co_name}] Conexión HANA cerrada.")
# ============================================================================ # ============================================================================
def actualizar_documento_sap(schema, registro,resultado): def actualizar_documento_sap(schema, registro,resultado):
@@ -263,10 +262,10 @@ def actualizar_documento_sap(schema, registro,resultado):
"U_PGX_PETOTORHORA" = TO_VARCHAR(CURRENT_TIME,'HH24:MM:SS') "U_PGX_PETOTORHORA" = TO_VARCHAR(CURRENT_TIME,'HH24:MM:SS')
WHERE "DocEntry" = {registro["fieldvalue"]};""" WHERE "DocEntry" = {registro["fieldvalue"]};"""
print(f" ┌─ 🗄️ UPDATE HANA ──────────────────────") logger.info(f" ┌─ 🗄️ UPDATE HANA ──────────────────────")
for line in query.splitlines(): for line in query.splitlines():
print(f" │ {line}") logger.info(f" │ {line}")
print(f" └─────────────────────────────────────────") logger.info(f" └─────────────────────────────────────────")
try: try:
cursor.execute(query) cursor.execute(query)
@@ -274,13 +273,13 @@ def actualizar_documento_sap(schema, registro,resultado):
cursor.close() cursor.close()
return True return True
except Exception as e: except Exception as e:
print(f" ❌ Error actualizando registro en HANA: {e}") logger.error(f" ❌ Error actualizando registro en HANA: {e}")
return False return False
finally: finally:
if cursor: if cursor:
cursor.close() cursor.close()
if connection: if connection:
connection.close() connection.close()
print(f" 🔒 [{sys._getframe().f_code.co_name}] Conexión HANA cerrada.") logger.info(f" 🔒 [{sys._getframe().f_code.co_name}] Conexión HANA cerrada.")
# ============================================================================ # ============================================================================
+116
View File
@@ -0,0 +1,116 @@
import logging
import contextvars
import os
from contextlib import contextmanager
from logging.handlers import TimedRotatingFileHandler
from pathlib import Path
LOG_DIR = Path(__file__).parent / "logs"
LOG_FILE = LOG_DIR / "validador-documentos.log"
_LOG_CONFIGURED = False
_stack: contextvars.ContextVar[list] = contextvars.ContextVar("log_stack", default=[])
# ============================================================================
class TreeFormatter(logging.Formatter):
def format(self, record):
return f"{self.formatTime(record)} ─── {record.getMessage()}"
def formatTime(self, record, datefmt=None):
import datetime
return datetime.datetime.fromtimestamp(record.created).strftime("%Y-%m-%d %H:%M:%S")
# ============================================================================
def _build_prefix(stack, is_last):
if not stack:
return ""
parts = []
for sib_is_last in stack[:-1]:
parts.append(" " if sib_is_last else "│ ")
parts.append("└── " if is_last else "├── ")
return "".join(parts)
# ============================================================================
class _TaskContext:
def __init__(self, logger, msg, emoji, is_last):
self._logger = logger
self._msg = msg
self._emoji = emoji
self._is_last = is_last
def __enter__(self):
stack = _stack.get()
prefix = _build_prefix(stack, self._is_last)
emoji_str = f"{self._emoji} " if self._emoji else ""
self._logger._logger.info(f"{prefix}{emoji_str}{self._msg}")
_stack.set(stack + [self._is_last])
return TaskLogger(self._logger._logger)
def __exit__(self, exc_type, exc_val, exc_tb):
_stack.set(_stack.get()[:-1])
# ============================================================================
class TaskLogger:
def __init__(self, logger):
self._logger = logger
@contextmanager
def task(self, msg, emoji=""):
with _TaskContext(self, msg, emoji, is_last=False):
yield
@contextmanager
def last_task(self, msg, emoji=""):
with _TaskContext(self, msg, emoji, is_last=True):
yield
def info(self, msg, *args, _last=False, **kwargs):
self._log(logging.INFO, msg, args, kwargs, _last)
def warning(self, msg, *args, _last=False, **kwargs):
self._log(logging.WARNING, msg, args, kwargs, _last)
def error(self, msg, *args, _last=False, **kwargs):
self._log(logging.ERROR, msg, args, kwargs, _last)
def _log(self, level, msg, args, kwargs, is_last):
stack = _stack.get()
prefix = _build_prefix(stack, is_last)
self._logger.log(level, f"{prefix}{msg}", *args, **kwargs)
# ============================================================================
def setup_logging(level=None, console=True):
global _LOG_CONFIGURED
if _LOG_CONFIGURED:
return
if level is None:
level_name = os.getenv("LOG_LEVEL", "INFO").upper()
level = getattr(logging, level_name, logging.INFO)
LOG_DIR.mkdir(exist_ok=True)
formatter = TreeFormatter()
file_handler = TimedRotatingFileHandler(
LOG_FILE,
when="midnight",
interval=1,
backupCount=30,
encoding="utf-8",
)
file_handler.setFormatter(formatter)
handlers = [file_handler]
if console:
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
handlers.append(console_handler)
logging.basicConfig(level=level, handlers=handlers, force=True)
_LOG_CONFIGURED = True
# ============================================================================
def get_logger(name="validador-documentos"):
setup_logging()
return TaskLogger(logging.getLogger(name))
-93
View File
@@ -1,93 +0,0 @@
import sys
import os
from datetime import datetime, timedelta
_stdout_original = sys.stdout
_stderr_original = sys.stderr
_file_stream = None
_log_dir = "logs"
# ============================================================================
class MultiStream:
def __init__(self, *streams):
self.streams = list(streams)
def write(self, data):
for stream in self.streams:
stream.write(data)
stream.flush()
def flush(self):
for stream in self.streams:
stream.flush()
def reemplazar_file(self, old_fh, new_fh):
for i, s in enumerate(self.streams):
if s is old_fh:
self.streams[i] = new_fh
return
# ============================================================================
def _ruta_log(hoy=None):
if hoy is None:
hoy = datetime.now().strftime("%Y-%m-%d")
if not os.path.exists(_log_dir):
os.makedirs(_log_dir)
return os.path.join(_log_dir, f"{hoy}.log")
# ============================================================================
def setup_logger():
global _file_stream
log_path = _ruta_log()
_file_stream = open(log_path, "a", encoding="utf-8")
sys.stdout = MultiStream(_stdout_original, _file_stream)
sys.stderr = MultiStream(_stderr_original, _file_stream)
return log_path
# ============================================================================
def rotar_log():
global _file_stream
hoy = datetime.now().strftime("%Y-%m-%d")
log_path = _ruta_log(hoy)
if _file_stream and os.path.abspath(_file_stream.name) == os.path.abspath(log_path):
return
nuevo_fh = open(log_path, "a", encoding="utf-8")
if isinstance(sys.stdout, MultiStream):
sys.stdout.reemplazar_file(_file_stream, nuevo_fh)
if isinstance(sys.stderr, MultiStream):
sys.stderr.reemplazar_file(_file_stream, nuevo_fh)
try:
_file_stream.close()
except Exception:
pass
_file_stream = nuevo_fh
print(f"📄 Log rotado a: {log_path}")
# ============================================================================
def limpiar_logs_antiguos(dias=7):
"""
Elimina archivos de log con más de 'n' días de antigüedad.
"""
log_dir = "logs"
if not os.path.exists(log_dir):
return
limite = datetime.now() - timedelta(days=dias)
count = 0
for archivo in os.listdir(log_dir):
ruta_archivo = os.path.join(log_dir, archivo)
if os.path.isfile(ruta_archivo) and archivo.endswith(".log"):
fecha_archivo = datetime.fromtimestamp(os.path.getmtime(ruta_archivo))
if fecha_archivo < limite:
try:
os.remove(ruta_archivo)
count += 1
except Exception as e:
print(f"⚠️ Error al eliminar log {archivo}: {e}")
if count > 0:
print(f"🧹 Se eliminaron {count} archivos de log antiguos (más de {dias} días).")
# ============================================================================
+42 -52
View File
@@ -3,13 +3,15 @@ import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from dotenv import load_dotenv from dotenv import load_dotenv
import logger_utilities from logger_manager import get_logger
import sunat_manager import sunat_manager
import hana_manager import hana_manager
import sapb1_manager import sapb1_manager
from constants import ESTADOS_COMPROBANTE from constants import ESTADOS_COMPROBANTE
from hana_manager import actualizar_documento_sap from hana_manager import actualizar_documento_sap
logger = get_logger()
# ============================================================================ # ============================================================================
config = {} config = {}
@@ -147,41 +149,39 @@ def enviar_mensaje_sl(usercode, doc, resultado, schema):
print(f" ┌─ 📩 Enviando Messages a {usercode} ───────────") logger.info(f" ┌─ 📩 Enviando Messages a {usercode} ───────────")
# for line in json.dumps(payload, indent=2).splitlines():
# print(f" │ {line}")
sapb1_manager.send_message(payload) sapb1_manager.send_message(payload)
print(f" │ ✅ Mensaje enviado a {usercode}") logger.info(f" │ ✅ Mensaje enviado a {usercode}")
print(f" └───────────────────────────────────────────────") logger.info(f" └───────────────────────────────────────────────")
# ============================================================================ # ============================================================================
def procesar_empresa(schema, datos_empresa, modo): def procesar_empresa(schema, datos_empresa, modo):
print(f"\n{'='*60}") logger.info(f"\n{'='*60}")
print(f"🏢 Procesando empresa: {schema} [{modo}]") logger.info(f"🏢 Procesando empresa: {schema} [{modo}]")
print(f"{'='*60}") logger.info(f"{'='*60}")
if modo == "rapido": if modo == "rapido":
documentos = hana_manager.obtener_documentos_creados_actualizados_hace_60_minutos(schema, 60) documentos = hana_manager.obtener_documentos_aun_no_validados_ultimos_30_dias(schema)
else: else:
documentos = hana_manager.obtener_documentos_fecha_emision_ultimos_8_dias(schema) documentos = hana_manager.obtener_documentos_fecha_emision_ultimos_8_dias(schema)
if not documentos: if not documentos:
print(f"⚠️ No se encontraron documentos para {schema}") logger.warning(f"⚠️ No se encontraron documentos para {schema}")
return 0, 0 return 0, 0
## documentos = hana_manager.obtener_documentos_creados_hace_x_dias(schema, 8) ## documentos = hana_manager.obtener_documentos_creados_hace_x_dias(schema, 8)
print(f"📄 Documentos encontrados: {len(documentos)}") logger.info(f"📄 Documentos encontrados: {len(documentos)}")
print(f"🔑 Solicitando token SUNAT...") logger.info(f"🔑 Solicitando token SUNAT...")
token = sunat_manager.token(datos_empresa) token = sunat_manager.token(datos_empresa)
if not token: if not token:
print(f"❌ No se obtuvo token para {schema}") logger.error(f"❌ No se obtuvo token para {schema}")
return 0, len(documentos) return 0, len(documentos)
print(f"✅ Token obtenido correctamente") logger.info(f"✅ Token obtenido correctamente")
sapb1_manager.login() sapb1_manager.login()
print(f"✅ Sesión SL establecida para {schema}") logger.info(f"✅ Sesión SL establecida para {schema}")
ruc_empresa = datos_empresa["Ruc"] ruc_empresa = datos_empresa["Ruc"]
procesados = 0 procesados = 0
@@ -189,7 +189,7 @@ def procesar_empresa(schema, datos_empresa, modo):
try: try:
for idx, doc in enumerate(documentos, start=1): for idx, doc in enumerate(documentos, start=1):
print(f"\n--- [{idx}/{len(documentos)}] {doc['FolioPref']}-{doc['FolioNum']} ---") logger.info(f"\n--- [{idx}/{len(documentos)}] {doc['FolioPref']}-{doc['FolioNum']} ---")
try: try:
payload = { payload = {
@@ -203,15 +203,15 @@ def procesar_empresa(schema, datos_empresa, modo):
if doc["FolioPref"] and doc["FolioPref"][0].isalpha(): if doc["FolioPref"] and doc["FolioPref"][0].isalpha():
payload["monto"] = str(doc["DocTotal"]) payload["monto"] = str(doc["DocTotal"])
print(f" ┌─ 📤 Enviando a SUNAT {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}─────────────────────") logger.info(f" ┌─ 📤 Enviando a SUNAT {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}─────────────────────")
for line in json.dumps(payload, indent=2).splitlines(): for line in json.dumps(payload, indent=2).splitlines():
print(f" │ {line}") logger.info(f" │ {line}")
print(f" └────────────────────────────────────────────") logger.info(f" └────────────────────────────────────────────")
resultado = sunat_manager.validar_comprobante(token, ruc_empresa, payload) resultado = sunat_manager.validar_comprobante(token, ruc_empresa, payload)
print(f" ┌─ 📥 Respuesta de SUNAT {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}───────────────────") logger.info(f" ┌─ 📥 Respuesta de SUNAT {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}───────────────────")
for line in json.dumps(resultado, indent=2).splitlines(): for line in json.dumps(resultado, indent=2).splitlines():
print(f" │ {line}") logger.info(f" │ {line}")
print(f" └────────────────────────────────────────────") logger.info(f" └────────────────────────────────────────────")
registro = { registro = {
"objtype": str(doc["ObjType"]), "objtype": str(doc["ObjType"]),
@@ -223,13 +223,13 @@ def procesar_empresa(schema, datos_empresa, modo):
if resultado.get("success"): if resultado.get("success"):
estado_cp = resultado.get("data", {}).get("estadoCp", "") estado_cp = resultado.get("data", {}).get("estadoCp", "")
print(f"✅ OK - Estado CP: {estado_cp}") logger.info(f"✅ OK - Estado CP: {estado_cp}")
if estado_cp not in ["1","3"] and doc.get("UserCode"): if estado_cp not in ["1","3"] and doc.get("UserCode"):
enviar_mensaje_sl(doc["UserCode"], doc, resultado, schema) enviar_mensaje_sl(doc["UserCode"], doc, resultado, schema)
else: else:
print(f"⚠️ Falló - {resultado.get('message', 'Error desconocido')}") logger.warning(f"⚠️ Falló - {resultado.get('message', 'Error desconocido')}")
procesados += 1 procesados += 1
@@ -237,11 +237,11 @@ def procesar_empresa(schema, datos_empresa, modo):
time.sleep(2) time.sleep(2)
except Exception as e: except Exception as e:
print(f"❌ Error procesando documento: {e}") logger.error(f"❌ Error procesando documento: {e}")
errores += 1 errores += 1
finally: finally:
sapb1_manager.logout() sapb1_manager.logout()
print(f"🔒 Sesión SL cerrada para {schema}") logger.info(f"🔒 Sesión SL cerrada para {schema}")
return procesados, errores return procesados, errores
@@ -249,7 +249,7 @@ def procesar_empresa(schema, datos_empresa, modo):
def procesar_todas_empresas(inicio_ciclo, modo): def procesar_todas_empresas(inicio_ciclo, modo):
empresas = cargar_empresas() empresas = cargar_empresas()
print(f"📋 Empresas cargadas: {len(empresas)}") logger.info(f"📋 Empresas cargadas: {len(empresas)}")
total_procesados = 0 total_procesados = 0
total_errores = 0 total_errores = 0
@@ -258,7 +258,7 @@ def procesar_todas_empresas(inicio_ciclo, modo):
schema = empresa["CompanyDB"] schema = empresa["CompanyDB"]
if empresa.get("Locked") == "Y": if empresa.get("Locked") == "Y":
print(f"\n⏭️ Empresa {schema} está bloqueada (LOCKED=Y), saltando...") logger.warning(f"\n⏭️ Empresa {schema} está bloqueada (LOCKED=Y), saltando...")
continue continue
sapb1_manager._sap_credentials = { sapb1_manager._sap_credentials = {
@@ -272,29 +272,25 @@ def procesar_todas_empresas(inicio_ciclo, modo):
total_procesados += p total_procesados += p
total_errores += e total_errores += e
except Exception as ex: except Exception as ex:
print(f"❌ Error procesando empresa {schema}: {ex}") logger.error(f"❌ Error procesando empresa {schema}: {ex}")
total_errores += 1 total_errores += 1
duracion_ciclo = datetime.now() - inicio_ciclo duracion_ciclo = datetime.now() - inicio_ciclo
print(f"\n📊 CICLO COMPLETADO en {str(duracion_ciclo).split('.')[0]}") logger.info(f"\n📊 CICLO COMPLETADO en {str(duracion_ciclo).split('.')[0]}")
print(f" ✅ {total_procesados} procesados | ❌ {total_errores} errores") logger.info(f" ✅ {total_procesados} procesados | ❌ {total_errores} errores")
return total_procesados, total_errores return total_procesados, total_errores
# ============================================================================ # ============================================================================
def main(): def main():
log_path = logger_utilities.setup_logger()
_ultima_limpieza = None
load_dotenv(dotenv_path=".env") load_dotenv(dotenv_path=".env")
cargar_programacion() cargar_programacion()
print("=" * 60) logger.info("=" * 60)
print("🚀 Demonio iniciado") logger.info("🚀 Demonio iniciado")
print(f"📄 Log guardado en: {log_path}") logger.info("Servicio iniciado")
print("Servicio iniciado") logger.info("=" * 60)
print("=" * 60)
while True: while True:
@@ -302,15 +298,9 @@ def main():
cargar_programacion() cargar_programacion()
logger_utilities.rotar_log()
if _ultima_limpieza != inicio_ciclo.date():
logger_utilities.limpiar_logs_antiguos(dias=7)
_ultima_limpieza = inicio_ciclo.date()
if inicio_ciclo.weekday() not in obtener_dias_activos(): if inicio_ciclo.weekday() not in obtener_dias_activos():
dormir = segundos_hasta_proximo_dia_activo(inicio_ciclo) dormir = segundos_hasta_proximo_dia_activo(inicio_ciclo)
print( logger.info(
f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] " f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] "
f"Día no activo (weekday={inicio_ciclo.weekday()}).\n" f"Día no activo (weekday={inicio_ciclo.weekday()}).\n"
f"Próxima ejecución: " f"Próxima ejecución: "
@@ -325,7 +315,7 @@ def main():
intervalo, modo = resultado intervalo, modo = resultado
print( logger.info(
f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] " f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] "
f"Iniciando ciclo [{modo}] (cada {intervalo}s)" f"Iniciando ciclo [{modo}] (cada {intervalo}s)"
) )
@@ -335,14 +325,14 @@ def main():
duracion = (datetime.now() - inicio_ciclo).total_seconds() duracion = (datetime.now() - inicio_ciclo).total_seconds()
if duracion > intervalo: if duracion > intervalo:
print( logger.warning(
f"⚠️ El ciclo demoró {duracion:.1f}s " f"⚠️ El ciclo demoró {duracion:.1f}s "
f"(intervalo configurado {intervalo}s)" f"(intervalo configurado {intervalo}s)"
) )
dormir = max(0, intervalo - duracion) dormir = max(0, intervalo - duracion)
print(f"💤 Próximo ciclo [{modo}] en {dormir:.0f}s...") logger.info(f"💤 Próximo ciclo [{modo}] en {dormir:.0f}s...")
else: else:
@@ -350,7 +340,7 @@ def main():
despertar = inicio_ciclo + timedelta(seconds=dormir) despertar = inicio_ciclo + timedelta(seconds=dormir)
print( logger.info(
f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] " f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] "
f"Fuera del horario.\n" f"Fuera del horario.\n"
f"Próxima ejecución: {despertar:%Y-%m-%d %H:%M:%S}" f"Próxima ejecución: {despertar:%Y-%m-%d %H:%M:%S}"
@@ -363,4 +353,4 @@ if __name__ == "__main__":
try: try:
main() main()
except KeyboardInterrupt: except KeyboardInterrupt:
print("\n\n🛑 Demonio detenido por el usuario.") logger.info("\n\n🛑 Demonio detenido por el usuario.")
+7 -7
View File
@@ -1,7 +1,7 @@
certifi==2026.6.17 certifi
charset-normalizer==3.4.9 charset-normalizer
hdbcli==2.29.23 hdbcli
idna==3.18 idna
python-dotenv==1.2.2 python-dotenv
requests==2.34.2 requests
urllib3==2.7.0 urllib3
+14 -15
View File
@@ -7,10 +7,9 @@ import json
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from dotenv import load_dotenv from dotenv import load_dotenv
from logger_manager import get_logger
logger = get_logger()
# ============================================================================
# CONFIGURACIÓN
# ============================================================================ # ============================================================================
urllib3.disable_warnings() urllib3.disable_warnings()
@@ -37,7 +36,7 @@ def login() -> dict:
"UserName": _sap_credentials["username"], "UserName": _sap_credentials["username"],
"Password": _sap_credentials["password"], "Password": _sap_credentials["password"],
} }
print("🔐 Iniciando sesión en SAP B1 Service Layer...") logger.info("🔐 Iniciando sesión en SAP B1 Service Layer...")
resp = requests.post( resp = requests.post(
f"{SAPB1_URL}/Login", f"{SAPB1_URL}/Login",
json=payload, json=payload,
@@ -55,7 +54,7 @@ def login() -> dict:
headers = {"Cookie": f"B1SESSION={b1session}; ROUTEID={routeid}"} headers = {"Cookie": f"B1SESSION={b1session}; ROUTEID={routeid}"}
_sap_session.update({"headers": headers, "b1session": b1session, "routeid": routeid}) _sap_session.update({"headers": headers, "b1session": b1session, "routeid": routeid})
print(f"✅ Sesión SAP B1 establecida (B1SESSION: {b1session[:10]}...)") logger.info(f"✅ Sesión SAP B1 establecida (B1SESSION: {b1session[:10]}...)")
return headers return headers
@@ -64,9 +63,9 @@ def login() -> dict:
# ============================================================================ # ============================================================================
def logout(): def logout():
if not _sap_session["b1session"]: if not _sap_session["b1session"]:
print("⚠️ No hay sesión activa para cerrar.") logger.warning("⚠️ No hay sesión activa para cerrar.")
return return
print("🔓 Cerrando sesión SAP B1...") logger.info("🔓 Cerrando sesión SAP B1...")
try: try:
resp = requests.post( resp = requests.post(
f"{SAPB1_URL}/Logout", f"{SAPB1_URL}/Logout",
@@ -75,11 +74,11 @@ def logout():
verify=SAPB1_VERIFY_SSL, verify=SAPB1_VERIFY_SSL,
) )
if resp.status_code == 200: if resp.status_code == 200:
print("✅ Sesión SAP B1 cerrada correctamente.") logger.info("✅ Sesión SAP B1 cerrada correctamente.")
else: else:
print(f"⚠️ Logout respondió HTTP {resp.status_code}") logger.warning(f"⚠️ Logout respondió HTTP {resp.status_code}")
except Exception as exc: except Exception as exc:
print(f"❌ Error al cerrar sesión: {exc}") logger.error(f"❌ Error al cerrar sesión: {exc}")
finally: finally:
_sap_session.update({"headers": None, "b1session": None, "routeid": None}) _sap_session.update({"headers": None, "b1session": None, "routeid": None})
_sap_credentials.clear() _sap_credentials.clear()
@@ -105,9 +104,9 @@ def _log_response(method: str, endpoint: str, response: requests.Response, paylo
f" ({len(response.content)} bytes)" f" ({len(response.content)} bytes)"
) )
if response.status_code >= 400: if response.status_code >= 400:
print(f" │ ⚠️ {log_line} - {response.text[:300]}") logger.warning(f" │ ⚠️ {log_line} - {response.text[:300]}")
else: else:
print(log_line) logger.info(log_line)
# ============================================================================ # ============================================================================
@@ -132,7 +131,7 @@ def forward_to_sapb1(
**_sap_session["headers"], **_sap_session["headers"],
} }
print(f" │ 🔄 [{method}] {endpoint_path} | {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") logger.info(f" │ 🔄 [{method}] {endpoint_path} | {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
try: try:
resp = requests.request( resp = requests.request(
@@ -154,7 +153,7 @@ def forward_to_sapb1(
if resp.status_code == 401: if resp.status_code == 401:
if not _sap_credentials: if not _sap_credentials:
raise ConnectionError("Sesión expirada (401) y no hay credenciales para re-autenticar.") raise ConnectionError("Sesión expirada (401) y no hay credenciales para re-autenticar.")
print(" │ ⚠️ Sesión expirada (401). Reautenticando e intentando una vez...") logger.warning(" │ ⚠️ Sesión expirada (401). Reautenticando e intentando una vez...")
login() login()
req_headers["Cookie"] = _sap_session["headers"]["Cookie"] req_headers["Cookie"] = _sap_session["headers"]["Cookie"]
resp = requests.request( resp = requests.request(
@@ -181,5 +180,5 @@ def send_message(payload: dict) -> dict:
f" │ Error al enviar Mensaje: HTTP {resp.status_code} - {resp.text}" f" │ Error al enviar Mensaje: HTTP {resp.status_code} - {resp.text}"
) )
result = resp.json() result = resp.json()
print(f" │ ✅ SAP B1 Service Layer - {resp.status_code}") logger.info(f" │ ✅ SAP B1 Service Layer - {resp.status_code}")
return result return result
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"HORARIOS": [ "HORARIOS": [
[6, 30, 13, 15, 10, "rapido"], [6, 0, 13, 15, 10, "rapido"],
[13, 15, 14, 0, 1800, "full"], [13, 15, 14, 0, 1800, "full"],
[14, 0, 17, 30, 10, "rapido"], [14, 0, 17, 30, 10, "rapido"],
[17, 30, 21, 30, 1800, "full"] [17, 30, 21, 30, 1800, "full"]
+9 -5
View File
@@ -3,6 +3,10 @@ import json
import urllib3 import urllib3
import time import time
from logger_manager import get_logger
logger = get_logger()
# Desactivar advertencias de SSL no verificado # Desactivar advertencias de SSL no verificado
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@@ -28,8 +32,8 @@ def token(datos):
response = requests.request("POST", url, headers=headers, data=payload, verify=False, timeout=30) response = requests.request("POST", url, headers=headers, data=payload, verify=False, timeout=30)
print(f" 📤 Token request enviado") logger.info(f" 📤 Token request enviado")
print(f" 📥 Token response: {response.status_code}") logger.info(f" 📥 Token response: {response.status_code}")
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
@@ -66,13 +70,13 @@ def validar_comprobante(token,RUC,payload:dict):
for attempt in range(max_retries): for attempt in range(max_retries):
try: try:
response = requests.request("POST", url, headers=headers, json=payload, verify=False, timeout=30) response = requests.request("POST", url, headers=headers, json=payload, verify=False, timeout=30)
print(f" 📥 Status code: {response.status_code}") logger.info(f" 📥 Status code: {response.status_code}")
return response.json() return response.json()
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e: except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
print(f" ⚠️ Error de conexión (intento {attempt + 1}/{max_retries}): {e}") logger.warning(f" ⚠️ Error de conexión (intento {attempt + 1}/{max_retries}): {e}")
if attempt < max_retries - 1: if attempt < max_retries - 1:
time_to_wait = 2 ** (attempt + 1) time_to_wait = 2 ** (attempt + 1)
print(f" 🔄 Reintentando en {time_to_wait} segundos...") logger.info(f" 🔄 Reintentando en {time_to_wait} segundos...")
time.sleep(time_to_wait) time.sleep(time_to_wait)
else: else:
raise e raise e