Files
descarga_factura_transporte/main.py
T

208 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)