diff --git a/empresas.json b/empresas.json index bd0aa64..121e2eb 100644 --- a/empresas.json +++ b/empresas.json @@ -19,8 +19,8 @@ }, { "CompanyDB": "SBO_CIRUGIA", - "Client_ID": "21d41835-0289-4b3c-99c0-ff0591d2233d", - "Client_Secret": "hzKoLbgjRqLUmNhsLagb7w==", + "Client_ID": "258001f9-ed7b-4f93-84da-7b113a269401", + "Client_Secret": "Y2YfcAB/wWcS4Cln/Rth5w==", "Ruc": "20521486857", "Locked": "N", "UserName": "manager", @@ -37,8 +37,8 @@ }, { "CompanyDB": "SBO_CALICAY", - "Client_ID": "d7ceb759-b6dc-4d97-a0c7-42bfc5226c53", - "Client_Secret": "MIjVHUtvqEIeE06M35nOuQ==", + "Client_ID": "66419990-c40b-4ed4-b423-c857039c955c", + "Client_Secret": "FWXTRHpUv+DeAif3hxxn+w==", "Ruc": "20611487143", "Locked": "N", "UserName": "manager", diff --git a/hana_manager.py b/hana_manager.py index 0818f7c..4e6f301 100644 --- a/hana_manager.py +++ b/hana_manager.py @@ -8,6 +8,9 @@ from hdbcli import dbapi from dotenv import load_dotenv from constants import TABLAS, ESTADOS_COMPROBANTE, ESTADOS_CONTRIBUYENTE, CONDICIONES_DOMICILIADO +from logger_manager import get_logger + +logger = get_logger() # ============================================================================ # CONFIGURACIΓ“N @@ -34,11 +37,11 @@ def get_connection(): ) return conn except Exception as e: - print(f" ❌ Error conectando a HANA: {e}") + logger.error(f" ❌ Error conectando a HANA: {e}") 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, 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 INNER JOIN "{schema}".OCRD T3 ON T3."CardCode" = T0."CardCode" INNER JOIN "{schema}".OUSR T1 ON T1."USERID" = T0."UserSign2" - WHERE TO_TIMESTAMP(TO_VARCHAR(T0."UpdateDate", 'YYYY-MM-DD') - || ' ' || LPAD(TO_VARCHAR(T0."UpdateTS"), 6, '0'), - 'YYYY-MM-DD HH24MISS') - >= ADD_SECONDS(CURRENT_TIMESTAMP, {-1 * minutes * 60}) - + WHERE T0."UpdateDate" >= ADD_DAYS(CURRENT_DATE, -30) AND T0."CANCELED" = 'N' AND IFNULL(T0."Indicator",'') IN ('01','02','03','08') 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) 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() if not connection: 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 except Exception as e: - print(f" ❌ Error en consulta HANA: {e}") + logger.error(f" ❌ Error en consulta HANA: {e}") return [] finally: if cursor: cursor.close() if connection: 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): @@ -129,7 +128,7 @@ def obtener_documentos_fecha_emision_ultimos_8_dias(schema): AND IFNULL(T3."U_EXX_TIPODOCU",'') IN ('6','4') 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() if not connection: 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 except Exception as e: - print(f" ❌ Error en consulta HANA: {e}") + logger.error(f" ❌ Error en consulta HANA: {e}") return [] finally: if cursor: cursor.close() if connection: 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): @@ -184,7 +183,7 @@ def obtener_documentos_creados_hace_x_dias(schema, dias=8): AND IFNULL(T3."U_EXX_TIPODOCU",'') IN ('6','4') 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() if not connection: 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 except Exception as e: - print(f" ❌ Error en consulta HANA: {e}") + logger.error(f" ❌ Error en consulta HANA: {e}") return [] finally: if cursor: cursor.close() if connection: 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): @@ -263,10 +262,10 @@ def actualizar_documento_sap(schema, registro,resultado): "U_PGX_PETOTORHORA" = TO_VARCHAR(CURRENT_TIME,'HH24:MM:SS') WHERE "DocEntry" = {registro["fieldvalue"]};""" - print(f" β”Œβ”€ πŸ—„οΈ UPDATE HANA ──────────────────────") + logger.info(f" β”Œβ”€ πŸ—„οΈ UPDATE HANA ──────────────────────") for line in query.splitlines(): - print(f" β”‚ {line}") - print(f" └─────────────────────────────────────────") + logger.info(f" β”‚ {line}") + logger.info(f" └─────────────────────────────────────────") try: cursor.execute(query) @@ -274,13 +273,13 @@ def actualizar_documento_sap(schema, registro,resultado): cursor.close() return True except Exception as e: - print(f" ❌ Error actualizando registro en HANA: {e}") + logger.error(f" ❌ Error actualizando registro en HANA: {e}") return False finally: if cursor: cursor.close() if connection: 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.") # ============================================================================ diff --git a/logger_manager.py b/logger_manager.py new file mode 100644 index 0000000..c2f27df --- /dev/null +++ b/logger_manager.py @@ -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)) \ No newline at end of file diff --git a/logger_utilities.py b/logger_utilities.py deleted file mode 100644 index 65f40cf..0000000 --- a/logger_utilities.py +++ /dev/null @@ -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).") -# ============================================================================ \ No newline at end of file diff --git a/main.py b/main.py index 3590441..59a7871 100644 --- a/main.py +++ b/main.py @@ -3,13 +3,15 @@ import time from datetime import datetime, timedelta from dotenv import load_dotenv -import logger_utilities +from logger_manager import get_logger import sunat_manager import hana_manager import sapb1_manager from constants import ESTADOS_COMPROBANTE from hana_manager import actualizar_documento_sap +logger = get_logger() + # ============================================================================ config = {} @@ -147,41 +149,39 @@ def enviar_mensaje_sl(usercode, doc, resultado, schema): - print(f" β”Œβ”€ πŸ“© Enviando Messages a {usercode} ───────────") - # for line in json.dumps(payload, indent=2).splitlines(): - # print(f" β”‚ {line}") + logger.info(f" β”Œβ”€ πŸ“© Enviando Messages a {usercode} ───────────") sapb1_manager.send_message(payload) - print(f" β”‚ βœ… Mensaje enviado a {usercode}") - print(f" └───────────────────────────────────────────────") + logger.info(f" β”‚ βœ… Mensaje enviado a {usercode}") + logger.info(f" └───────────────────────────────────────────────") # ============================================================================ def procesar_empresa(schema, datos_empresa, modo): - print(f"\n{'='*60}") - print(f"🏒 Procesando empresa: {schema} [{modo}]") - print(f"{'='*60}") + logger.info(f"\n{'='*60}") + logger.info(f"🏒 Procesando empresa: {schema} [{modo}]") + logger.info(f"{'='*60}") 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: documentos = hana_manager.obtener_documentos_fecha_emision_ultimos_8_dias(schema) if not documentos: - print(f"⚠️ No se encontraron documentos para {schema}") + logger.warning(f"⚠️ No se encontraron documentos para {schema}") return 0, 0 ## 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) if not token: - print(f"❌ No se obtuvo token para {schema}") + logger.error(f"❌ No se obtuvo token para {schema}") return 0, len(documentos) - print(f"βœ… Token obtenido correctamente") + logger.info(f"βœ… Token obtenido correctamente") 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"] procesados = 0 @@ -189,7 +189,7 @@ def procesar_empresa(schema, datos_empresa, modo): try: 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: payload = { @@ -203,15 +203,15 @@ def procesar_empresa(schema, datos_empresa, modo): if doc["FolioPref"] and doc["FolioPref"][0].isalpha(): 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(): - print(f" β”‚ {line}") - print(f" └────────────────────────────────────────────") + logger.info(f" β”‚ {line}") + logger.info(f" └────────────────────────────────────────────") 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(): - print(f" β”‚ {line}") - print(f" └────────────────────────────────────────────") + logger.info(f" β”‚ {line}") + logger.info(f" └────────────────────────────────────────────") registro = { "objtype": str(doc["ObjType"]), @@ -223,13 +223,13 @@ def procesar_empresa(schema, datos_empresa, modo): if resultado.get("success"): 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"): enviar_mensaje_sl(doc["UserCode"], doc, resultado, schema) else: - print(f"⚠️ FallΓ³ - {resultado.get('message', 'Error desconocido')}") + logger.warning(f"⚠️ FallΓ³ - {resultado.get('message', 'Error desconocido')}") procesados += 1 @@ -237,11 +237,11 @@ def procesar_empresa(schema, datos_empresa, modo): time.sleep(2) except Exception as e: - print(f"❌ Error procesando documento: {e}") + logger.error(f"❌ Error procesando documento: {e}") errores += 1 finally: sapb1_manager.logout() - print(f"πŸ”’ SesiΓ³n SL cerrada para {schema}") + logger.info(f"πŸ”’ SesiΓ³n SL cerrada para {schema}") return procesados, errores @@ -249,7 +249,7 @@ def procesar_empresa(schema, datos_empresa, modo): def procesar_todas_empresas(inicio_ciclo, modo): empresas = cargar_empresas() - print(f"πŸ“‹ Empresas cargadas: {len(empresas)}") + logger.info(f"πŸ“‹ Empresas cargadas: {len(empresas)}") total_procesados = 0 total_errores = 0 @@ -258,7 +258,7 @@ def procesar_todas_empresas(inicio_ciclo, modo): schema = empresa["CompanyDB"] 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 sapb1_manager._sap_credentials = { @@ -272,29 +272,25 @@ def procesar_todas_empresas(inicio_ciclo, modo): total_procesados += p total_errores += e except Exception as ex: - print(f"❌ Error procesando empresa {schema}: {ex}") + logger.error(f"❌ Error procesando empresa {schema}: {ex}") total_errores += 1 duracion_ciclo = datetime.now() - inicio_ciclo - print(f"\nπŸ“Š CICLO COMPLETADO en {str(duracion_ciclo).split('.')[0]}") - print(f" βœ… {total_procesados} procesados | ❌ {total_errores} errores") + logger.info(f"\nπŸ“Š CICLO COMPLETADO en {str(duracion_ciclo).split('.')[0]}") + logger.info(f" βœ… {total_procesados} procesados | ❌ {total_errores} errores") return total_procesados, total_errores # ============================================================================ def main(): - log_path = logger_utilities.setup_logger() - _ultima_limpieza = None - load_dotenv(dotenv_path=".env") cargar_programacion() - print("=" * 60) - print("πŸš€ Demonio iniciado") - print(f"πŸ“„ Log guardado en: {log_path}") - print("Servicio iniciado") - print("=" * 60) + logger.info("=" * 60) + logger.info("πŸš€ Demonio iniciado") + logger.info("Servicio iniciado") + logger.info("=" * 60) while True: @@ -302,15 +298,9 @@ def main(): 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(): dormir = segundos_hasta_proximo_dia_activo(inicio_ciclo) - print( + logger.info( f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] " f"DΓ­a no activo (weekday={inicio_ciclo.weekday()}).\n" f"PrΓ³xima ejecuciΓ³n: " @@ -325,7 +315,7 @@ def main(): intervalo, modo = resultado - print( + logger.info( f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] " f"Iniciando ciclo [{modo}] (cada {intervalo}s)" ) @@ -335,14 +325,14 @@ def main(): duracion = (datetime.now() - inicio_ciclo).total_seconds() if duracion > intervalo: - print( + logger.warning( f"⚠️ El ciclo demorΓ³ {duracion:.1f}s " f"(intervalo configurado {intervalo}s)" ) 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: @@ -350,7 +340,7 @@ def main(): despertar = inicio_ciclo + timedelta(seconds=dormir) - print( + logger.info( f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] " f"Fuera del horario.\n" f"PrΓ³xima ejecuciΓ³n: {despertar:%Y-%m-%d %H:%M:%S}" @@ -363,4 +353,4 @@ if __name__ == "__main__": try: main() except KeyboardInterrupt: - print("\n\nπŸ›‘ Demonio detenido por el usuario.") + logger.info("\n\nπŸ›‘ Demonio detenido por el usuario.") diff --git a/requirements.txt b/requirements.txt index 225b1bd..67cc2b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -certifi==2026.6.17 -charset-normalizer==3.4.9 -hdbcli==2.29.23 -idna==3.18 -python-dotenv==1.2.2 -requests==2.34.2 -urllib3==2.7.0 +certifi +charset-normalizer +hdbcli +idna +python-dotenv +requests +urllib3 diff --git a/sapb1_manager.py b/sapb1_manager.py index 0a5a3dd..d85cdb0 100644 --- a/sapb1_manager.py +++ b/sapb1_manager.py @@ -7,10 +7,9 @@ import json from typing import Optional, Dict, Any from dotenv import load_dotenv +from logger_manager import get_logger - -# ============================================================================ -# CONFIGURACIΓ“N +logger = get_logger() # ============================================================================ urllib3.disable_warnings() @@ -37,7 +36,7 @@ def login() -> dict: "UserName": _sap_credentials["username"], "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( f"{SAPB1_URL}/Login", json=payload, @@ -55,7 +54,7 @@ def login() -> dict: headers = {"Cookie": f"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 @@ -64,9 +63,9 @@ def login() -> dict: # ============================================================================ def logout(): if not _sap_session["b1session"]: - print("⚠️ No hay sesiΓ³n activa para cerrar.") + logger.warning("⚠️ No hay sesiΓ³n activa para cerrar.") return - print("πŸ”“ Cerrando sesiΓ³n SAP B1...") + logger.info("πŸ”“ Cerrando sesiΓ³n SAP B1...") try: resp = requests.post( f"{SAPB1_URL}/Logout", @@ -75,11 +74,11 @@ def logout(): verify=SAPB1_VERIFY_SSL, ) if resp.status_code == 200: - print("βœ… SesiΓ³n SAP B1 cerrada correctamente.") + logger.info("βœ… SesiΓ³n SAP B1 cerrada correctamente.") else: - print(f"⚠️ Logout respondiΓ³ HTTP {resp.status_code}") + logger.warning(f"⚠️ Logout respondiΓ³ HTTP {resp.status_code}") except Exception as exc: - print(f"❌ Error al cerrar sesiΓ³n: {exc}") + logger.error(f"❌ Error al cerrar sesiΓ³n: {exc}") finally: _sap_session.update({"headers": None, "b1session": None, "routeid": None}) _sap_credentials.clear() @@ -105,9 +104,9 @@ def _log_response(method: str, endpoint: str, response: requests.Response, paylo f" ({len(response.content)} bytes)" ) if response.status_code >= 400: - print(f" β”‚ ⚠️ {log_line} - {response.text[:300]}") + logger.warning(f" β”‚ ⚠️ {log_line} - {response.text[:300]}") else: - print(log_line) + logger.info(log_line) # ============================================================================ @@ -132,7 +131,7 @@ def forward_to_sapb1( **_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: resp = requests.request( @@ -154,7 +153,7 @@ def forward_to_sapb1( if resp.status_code == 401: if not _sap_credentials: 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() req_headers["Cookie"] = _sap_session["headers"]["Cookie"] resp = requests.request( @@ -181,5 +180,5 @@ def send_message(payload: dict) -> dict: f" β”‚ Error al enviar Mensaje: HTTP {resp.status_code} - {resp.text}" ) result = resp.json() - print(f" β”‚ βœ… SAP B1 Service Layer - {resp.status_code}") + logger.info(f" β”‚ βœ… SAP B1 Service Layer - {resp.status_code}") return result diff --git a/scheduler.json b/scheduler.json index cb93168..dd8e7e4 100644 --- a/scheduler.json +++ b/scheduler.json @@ -1,6 +1,6 @@ { "HORARIOS": [ - [6, 30, 13, 15, 10, "rapido"], + [6, 0, 13, 15, 10, "rapido"], [13, 15, 14, 0, 1800, "full"], [14, 0, 17, 30, 10, "rapido"], [17, 30, 21, 30, 1800, "full"] diff --git a/sunat_manager.py b/sunat_manager.py index 4c3838e..5bd9df0 100644 --- a/sunat_manager.py +++ b/sunat_manager.py @@ -3,6 +3,10 @@ import json import urllib3 import time +from logger_manager import get_logger + +logger = get_logger() + # Desactivar advertencias de SSL no verificado 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) - print(f" πŸ“€ Token request enviado") - print(f" πŸ“₯ Token response: {response.status_code}") + logger.info(f" πŸ“€ Token request enviado") + logger.info(f" πŸ“₯ Token response: {response.status_code}") if response.status_code == 200: result = response.json() @@ -66,13 +70,13 @@ def validar_comprobante(token,RUC,payload:dict): for attempt in range(max_retries): try: 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() 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: 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) else: raise e