import os import sys import json import email_utilities import outlook_utilities import database_utilities import logger_utilities import sunat_schema from datetime import datetime, timedelta from config_loader import CONFIG ######################################################## def obtener_token(): try: print("Intentando obtener token de acceso...") outlook_token = outlook_utilities.get_access_token() print("✅ Token obtenido exitosamente.") print(f"Token prefix: {outlook_token[:25]}...") return outlook_token except Exception as e: print(f"❌ Error: {e}") sys.exit(1) ######################################################## def descargar_correos(outlook_token, fecha_inicio, fecha_fin): try: print("\n" + "="*60) print(f"[MAIL] {CONFIG.client.email}") emails = outlook_utilities.get_emails(CONFIG.client.email, outlook_token, fecha_inicio, fecha_fin) print(f"Se encontraron {len(emails)} correos.") return emails except Exception as e: print(f"❌ Error: {e}") sys.exit(1) return [] ######################################################## def descargar_adjuntos_de_correos(outlook_token, emails, ruta_descarga): try: emails_con_adjuntos = [] for email in emails: # Guardar lista original para el log adjuntos = outlook_utilities.download_attachments( CONFIG.client.email, email['id'], outlook_token, ruta_descarga) if adjuntos: email['adjuntos'] = adjuntos emails_con_adjuntos.append(email) return emails_con_adjuntos except Exception as ex: print(f" ❌ Error procesando {email['id'][:10]}: {ex}") return [] ######################################################## def dejar_mail_si_tiene_adjunto_pdf_o_xml(emails): """ Recorre los adjuntos de cada email y elimina los que no tengan extensión pdf o xml actualiza la lista de adjuntos del correo si el mail no tiene pdf ni xml también se elimina """ emails_con_pdf_o_xml = [] for email in emails: adjuntos_validos = [adjunto for adjunto in email['adjuntos'] if adjunto.lower().endswith(('.pdf', '.xml'))] if adjuntos_validos: email['adjuntos'] = adjuntos_validos emails_con_pdf_o_xml.append(email) return emails_con_pdf_o_xml ######################################################## def leer_xml_obtener_datos_validos(emails): json_final = [] for e in emails: print(f"\n📬 [{e['fecha']}] {e['subject'][:80]}...") #adjuntos originales print(f" ├── 📂 Adjuntos originales:") for idx, a in enumerate(e['adjuntos']): prefijo = " │ └── " if idx == len(e['adjuntos'])-1 else " │ ├── " print(f"{prefijo}{a}") if a.lower().endswith(".xml"): # procesar archivo xml de SUNAT carpeta_adjuntos = os.path.join(os.path.dirname(__file__), CONFIG.client.ruta_descarga) archivo_xml = os.path.join(carpeta_adjuntos, a) print(f" │ 📄 Procesando XML: {a}") data = sunat_schema.extraer_datos_xml(archivo_xml) if data: print(f" │ ✅ Factura extraída: {data.get('serie')}-{data.get('numero')}") json_final.append(data) else: print(" │ ℹ️ XML sin datos (no es factura tipo 01)") # opcional: guardar JSON junto al XML archivo_json = f"ejecucion_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.json" with open(archivo_json, "w", encoding="utf-8") as f: json.dump(json_final, f, ensure_ascii=False, indent=4) return json_final ######################################################## def anidar_guias_en_facturas(json_final): # obtener solo facturas facturas = [item for item in json_final if item.get("tipo") == "01"] # obtener solo guias guias = [item for item in json_final if item.get("tipo") == "31"] # recorrer cada factura y buscar guias relacionadas de campo "documento_relacionado" que tenga el mismo numero de serie y numero for factura in facturas: # obtener la guia de tipo ='31' de los documemtos_relacionados guias_transporte = [item for item in factura.get("documentos_referencia", []) if item.get("tipo") == "31"] # buscar en la lista de guias si existe una guia que tenga el mismo numero de serie y numero que la guia de transporte for guia in guias_transporte: guia_encontrada = next((g for g in guias if g.get("serie")== guia.get("serie") and g.get("numero")== guia.get("numero")), None) if guia_encontrada: # si se encuentra, anidar la guia dentro de la factura en un nuevo campo "guia_trasporte" factura["guia_transporte"] = guia_encontrada print(f" ├── 📦 Guía {guia_encontrada.get('serie')}-{guia_encontrada.get('numero')} anidada en factura {factura.get('serie')}-{factura.get('numero')}") else: print(f" ├── ⚠️ Guía {guia.get('serie')}-{guia.get('numero')} no encontrada para factura {factura.get('serie')}-{factura.get('numero')}") # opcional: guardar JSON junto al XML archivo_json = f"anidado_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.json" with open(archivo_json, "w", encoding="utf-8") as f: json.dump(facturas, f, ensure_ascii=False, indent=4) return facturas ######################################################## def main(): # Inicializar Base de Datos database_utilities.init_db() # Calcular fechas hoy = datetime.now() ayer = hoy - timedelta(days=30) fecha_inicio = ayer.strftime("%Y-%m-%d") fecha_fin = hoy.strftime("%Y-%m-%d") print(f"Rango de búsqueda: {fecha_inicio} al {fecha_fin}") outlook_token = obtener_token() # se descarga todos los correos en el rango de fecha ingresado # tengan o no adjuntos emails = descargar_correos(outlook_token, fecha_inicio, fecha_fin) # se verifica si estos correos ya estan en la base de datos # si ya esta registrado, se quita del listado # si no esta se inserta en la base de datos emails = email_utilities.quitar_correos_ya_registrados_en_base_de_datos(emails) # descargar adjuntos de cada correo y agregar emails = descargar_adjuntos_de_correos(outlook_token, emails, CONFIG.client.ruta_descarga) # with open("correos_con_adjuntos.json", "w", encoding="utf-8") as f: # json.dump(emails, f, ensure_ascii=False, indent=4) # si el correo tiene archivos zip, se descomprimen y se agregan a # la lista de archivos adjuntos emails = email_utilities.descomprimir_adjuntos(emails, CONFIG.client.ruta_descarga) # se eliminan mails que no tengan adjunto pdf o xml # también se eliminar los adjuntos no que sean xml y pdf emails = dejar_mail_si_tiene_adjunto_pdf_o_xml(emails) # obtener datos de los archivos xml y generar JSON final json_final = leer_xml_obtener_datos_validos(emails) anidar_guias_en_facturas(json_final) ######################################################## if __name__ == "__main__": # Configurar Logger (Redirige stdout a archivo y consola) log_path = logger_utilities.setup_logger() logger_utilities.limpiar_logs_antiguos(dias=30) inicio_ejecucion = datetime.now() print("="*60) print(f"🚀 INICIO DE EJECUCIÓN: {inicio_ejecucion.strftime('%Y-%m-%d %H:%M:%S')}") print(f"📄 Log guardado en: {log_path}") print("="*60) main() fin_ejecucion = datetime.now() duracion = fin_ejecucion - inicio_ejecucion print("\n" + "="*60) print(f"🏁 FIN DE EJECUCIÓN: {fin_ejecucion.strftime('%Y-%m-%d %H:%M:%S')}") print(f"⏱️ Duración total: {str(duracion).split('.')[0]}") # Sin microsegundos print("="*60)