import json import time from datetime import datetime from dotenv import load_dotenv import logger_utilities import sunat_manager import hana_manager import sapb1_manager from constants import ESTADOS_COMPROBANTE from hana_manager import actualizar_documento_sap # ============================================================================ def es_hora_pico(dt: datetime) -> bool: minutos = dt.hour * 60 + dt.minute return (360 <= minutos < 795) or (840 <= minutos < 1050) # ============================================================================ def cargar_empresas(): with open("empresas.json", "r", encoding="utf-8") as f: return json.load(f) # ============================================================================ def enviar_mensaje_sl(usercode, doc, resultado, schema): estado_cp = resultado.get("data", {}).get("estadoCp", "") estado_texto = ESTADOS_COMPROBANTE.get(estado_cp, "DESCONOCIDO") subject = f"❌ SUNAT - {doc['Indicator']}-{doc['FolioPref']}-{doc['FolioNum']}" observaciones = resultado.get("data", {}).get("observaciones", [""])[0] or "" text = ( f"El documento {doc['Indicator']}-{doc['FolioPref']}-{doc['FolioNum']} " f"de {schema} entrego el siguiente estado por SUNAT.\n" f"Estado: {estado_texto} (código: {estado_cp})\n" f"RUC Proveedor: {doc.get('LicTradNum', 'N/A')}\n" f"Razón Social: {doc.get('CardName', 'N/A')}\n" f"Mensaje SUNAT: {resultado.get('message', 'N/A')}\n" ) if observaciones: text += f"Observaciones SUNAT: {observaciones}\n" payload = { "MessageDataColumns": [ { "ColumnName": "Numero", "Link": "tNO", "MessageDataLines": [ { "Object":"", "ObjectKey":"", "Value":f"{doc['Indicator']}-{doc['FolioPref']}-{doc['FolioNum']} " } ] }, { "ColumnName": "Documento", "Link": "tYES", "MessageDataLines": [ { "Object": str(doc.get("ObjType", "18")), "ObjectKey": str(doc["DocEntry"]), "Value": str(doc["DocNum"]) } ] }, { "ColumnName": "Razón Social / Nombre", "Link": "tNO", "MessageDataLines": [ { "Object": "", "ObjectKey": "", "Value": f"{doc.get('CardName', 'N/A')}" } ] } ], "RecipientCollection": [ { "SendInternal": "tYES", "UserCode": usercode } ], "Subject": subject, "Text": text } print(f" ┌─ 📩 Enviando Messages a {usercode} ───────────") # for line in json.dumps(payload, indent=2).splitlines(): # print(f" │ {line}") sapb1_manager.send_message(payload) print(f" │ ✅ Mensaje enviado a {usercode}") print(f" └───────────────────────────────────────────────") # ============================================================================ def procesar_empresa(schema, datos_empresa, modo): print(f"\n{'='*60}") print(f"🏢 Procesando empresa: {schema}") print(f"{'='*60}") if modo == "rapido": documentos = hana_manager.obtener_documentos_creados_actualizados_hace_x_minutos(schema, 60) else: documentos = hana_manager.obtener_documentos_fecha_emision_ultimos_8_dias(schema) if not documentos: print(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)}") print(f"🔑 Solicitando token SUNAT...") token = sunat_manager.token(datos_empresa) if not token: print(f"❌ No se obtuvo token para {schema}") return 0, len(documentos) print(f"✅ Token obtenido correctamente") sapb1_manager.login() print(f"✅ Sesión SL establecida para {schema}") ruc_empresa = datos_empresa["Ruc"] procesados = 0 errores = 0 try: for idx, doc in enumerate(documentos, start=1): print(f"\n--- [{idx}/{len(documentos)}] {doc['FolioPref']}-{doc['FolioNum']} ---") try: payload = { "numRuc": doc["LicTradNum"], "codComp": "R1" if doc["Indicator"] == "02" else doc["Indicator"], "numeroSerie": doc["FolioPref"], "numero": doc["FolioNum"], "fechaEmision": doc["TaxDate"], } 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')}─────────────────────") for line in json.dumps(payload, indent=2).splitlines(): print(f" │ {line}") print(f" └────────────────────────────────────────────") resultado = sunat_manager.validar_comprobante(token, ruc_empresa, payload) print(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" └────────────────────────────────────────────") registro = { "objtype": str(doc["ObjType"]), "companydb": schema, "fieldvalue": doc["DocEntry"], } actualizar_documento_sap(schema, registro, resultado) if resultado.get("success"): estado_cp = resultado.get("data", {}).get("estadoCp", "") print(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')}") procesados += 1 if idx < len(documentos): time.sleep(2) except Exception as e: print(f"❌ Error procesando documento: {e}") errores += 1 finally: sapb1_manager.logout() print(f"🔒 Sesión SL cerrada para {schema}") return procesados, errores # ============================================================================ def main(): log_path = logger_utilities.setup_logger() logger_utilities.limpiar_logs_antiguos(dias=7) load_dotenv(dotenv_path=".env.dev") empresas = cargar_empresas() print(f"📋 Empresas cargadas: {len(empresas)}") print("=" * 60) print("🚀 Demonio iniciado") print(f"📄 Log guardado en: {log_path}") print(" Horario pico (cada 10 seg): 06:00 - 13:15 | 14:00 - 17:30") print(" Horario valle (cada 30 min): resto del día") print("=" * 60) while True: inicio_ciclo = datetime.now() logger_utilities.rotar_log() logger_utilities.limpiar_logs_antiguos(dias=7) modo = "rapido" if es_hora_pico(inicio_ciclo) else "lento" print(f"\n{'='*60}") print(f"🔄 Ciclo [{modo.upper()}] {inicio_ciclo.strftime('%Y-%m-%d %H:%M:%S')}") print(f"{'='*60}") total_procesados = 0 total_errores = 0 for empresa in empresas: schema = empresa["CompanyDB"] if empresa.get("Locked") == "Y": print(f"\n⏭️ Empresa {schema} está bloqueada (LOCKED=Y), saltando...") continue sapb1_manager._sap_credentials = { "company": empresa["CompanyDB"], "username": empresa["UserName"], "password": empresa["Password"], } try: p, e = procesar_empresa(schema, empresa, modo) total_procesados += p total_errores += e except Exception as ex: print(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") transcurrido = duracion_ciclo.total_seconds() modo_ahora = "rapido" if es_hora_pico(datetime.now()) else "lento" intervalo = 10 if modo_ahora == "rapido" else 1800 dormir = max(0, intervalo - transcurrido) print(f"💤 Próximo ciclo [{modo_ahora}] en {dormir:.0f}s...") time.sleep(dormir) # ============================================================================ if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n\n🛑 Demonio detenido por el usuario.")