from server_backsemot.models import TransaccionWeb, FacturaVenta, TicketFacturaVenta, InteraccionApiWeb, Transaccion, OrdenCompra, TicketPresencial, DetalleGuia, GuiaDespacho
from server_backsemot.api.errorlog import guardar_error
from django.db import transaction
from zeep.exceptions import Fault
from datetime import datetime
from base64 import b64encode
from environ import Env
from zeep import Client
from lxml import etree
import requests
import traceback
from decimal import Decimal, ROUND_HALF_UP
import sys

env = Env()
Env.read_env()


def enviar_datos(params, accion:int):
    try:
        wsdl = f"{env('FACTURACION_URL')}"
        client = Client(wsdl=wsdl)
        

        # Llamar al método Procesar
        if accion == 1:
           response = client.service.Procesar(**params)
        if accion == 2:
            response = client.service.ObtenerLink(**params)
        if accion == 3:
            response = client.service.AnularGuia(**params)

        return True, response

    except Fault as e:
        # Manejo de errores SOAP
        print(f"Error al llamar al servicio SOAP: {str(e)}")
        return {
            "estado": "error",
            "mensaje": str(e)
        }
    except:
        guardar_error(sys.exc_info())
        return False, "Error inesperado"


    
def generar_factura_o_boleta_ticket_web(transaccion_web_id):
    try:
        # Obtener la transacción web
        transaccion = TransaccionWeb.objects.select_related('cliente', 'estado_pago').get(pk=transaccion_web_id)
        
        if not transaccion or transaccion.activo == 0:
            raise ValueError("Transacción no válida o inactiva.")

        cliente = transaccion.cliente

        # Validar si el cliente tiene información necesaria para generar el documento
        if not cliente.rut or not cliente.direccion:
            raise ValueError(f"El cliente asociado no tiene información suficiente: {cliente.id}")

        # Determinar el tipo de documento (Factura o Boleta)
        tipo_documento = 33 if transaccion.dte.id == 2 else 39

        # Construir el XML
        root = etree.Element("DTE")
        documento = etree.SubElement(root, "Documento")
        encabezado = etree.SubElement(documento, "Encabezado")

        iddoc = etree.SubElement(encabezado, "IdDoc")
        etree.SubElement(iddoc, "TipoDTE").text = str(tipo_documento)
        etree.SubElement(iddoc, "Folio").text = str(0)  # 0 indica que el sistema de facturacion.cl debe generar el folio
        etree.SubElement(iddoc, "FchEmis").text = datetime.now().strftime("%Y-%m-%d")
        if cliente.dte.id == 1:
            etree.SubElement(iddoc, "IndServicio").text = str(3) # Indicado por Branko que consulto a Facturacion.cl

        emisor = etree.SubElement(encabezado, "Emisor")
        etree.SubElement(emisor, "RUTEmisor").text = env("SEMOT_RUT")  # Cambiar por valor dinámico si necesario
        etree.SubElement(emisor, "RznSoc").text = env("SEMOT_RAZON_SOCIAL")
        etree.SubElement(emisor, "GiroEmis").text = env("SEMOT_GIRO")
        etree.SubElement(emisor, "Telefono").text = env("SEMOT_TELEFONO")
        etree.SubElement(emisor, "DirOrigen").text = env("SEMOT_DIRECCION")
        etree.SubElement(emisor, "CmnaOrigen").text = env("SEMOT_COMUNA")
        etree.SubElement(emisor, "CiudadOrigen").text = env("SEMOT_CIUDAD")
        

        receptor = etree.SubElement(encabezado, "Receptor")
        etree.SubElement(receptor, "RUTRecep").text = cliente.rut
        etree.SubElement(receptor, "RznSocRecep").text = cliente.nombre
        etree.SubElement(receptor, "GiroRecep").text = cliente.giro or "Sin giro"
        etree.SubElement(receptor, "DirRecep").text = cliente.direccion
        etree.SubElement(receptor, "CmnaRecep").text = cliente.comuna.nombreComuna
        etree.SubElement(receptor, "CiudadRecep").text = "Santiago"
        etree.SubElement(receptor, "Contacto").text = cliente.telefono

        detalle = etree.SubElement(documento, "Detalle")
        item = etree.SubElement(detalle, "CdgItem")
        etree.SubElement(detalle, "NroLinDet").text = "1"
        etree.SubElement(item, "TpoCodigo").text = "INT1"
        etree.SubElement(item, "VlrCodigo").text = "W" + str(transaccion.qr.id)
        if transaccion.lomo == 0:
            etree.SubElement(detalle, "NmbItem").text = f"Ticket patente {transaccion.camion} - {str(transaccion.cantidad)} {str(transaccion.unidad)}"
        else:
            etree.SubElement(detalle, "NmbItem").text = f"Ticket patente {transaccion.camion} - {str(transaccion.cantidad)} {str(transaccion.unidad)}. Incluye recargo por 20%"
        etree.SubElement(detalle, "QtyItem").text = str(1)
        etree.SubElement(detalle, "PrcItem").text = str(int(transaccion.total / 1.19))
        etree.SubElement(detalle, "MontoItem").text = str(int(transaccion.total / 1.19))

        totales = etree.SubElement(encabezado, "Totales")
        monto_neto = int(transaccion.total / 1.19)
        iva = int(transaccion.total - monto_neto)
        if monto_neto + iva < transaccion.total:
            iva += 1
        etree.SubElement(totales, "MntNeto").text = str(monto_neto)
        etree.SubElement(totales, "TasaIVA").text = str(19)
        etree.SubElement(totales, "IVA").text = str(iva)
        etree.SubElement(totales, "MntTotal").text = str(transaccion.total)

        # Convertir el XML a cadena
        xml_data = etree.tostring(root, pretty_print=True, encoding="utf-8").decode("utf-8")
        #print(xml_data)
        reps, params = get_params()
        return True, xml_data, params

    except TransaccionWeb.DoesNotExist:
        return False, "Transacción no encontrada.", None
    except ValueError as e:
        return False, str(e), None
    except:
        guardar_error(sys.exc_info())
        return False, "Error inesperado", None



def generar_factura_o_boleta_ticket_presencial(transaccion_presencial_id):
    try:
        # Obtener la transacción web
        transaccion = TicketPresencial.objects.select_related('cliente', 'estado').get(pk=transaccion_presencial_id)
        
        if not transaccion or transaccion.activo == 0:
            raise ValueError("Transacción no válida o inactiva.")

        cliente = transaccion.cliente

        # Validar si el cliente tiene información necesaria para generar el documento
        if not cliente.rut or not cliente.direccion:
            raise ValueError(f"El cliente asociado no tiene información suficiente: {cliente.id}")

        # Determinar el tipo de documento (Factura o Boleta)
        tipo_documento = 33 if transaccion.dte.id == 2 else 39

        # Construir el XML
        root = etree.Element("DTE")
        documento = etree.SubElement(root, "Documento")
        encabezado = etree.SubElement(documento, "Encabezado")

        iddoc = etree.SubElement(encabezado, "IdDoc")
        etree.SubElement(iddoc, "TipoDTE").text = str(tipo_documento)
        etree.SubElement(iddoc, "Folio").text = str(0)  # 0 indica que el sistema de facturacion.cl debe generar el folio
        etree.SubElement(iddoc, "FchEmis").text = datetime.now().strftime("%Y-%m-%d")
        if transaccion.dte.id == 1:
            etree.SubElement(iddoc, "IndServicio").text = str(3) # Indicado por Branko que consulto a Facturacion.cl

        emisor = etree.SubElement(encabezado, "Emisor")
        etree.SubElement(emisor, "RUTEmisor").text = env("SEMOT_RUT")
        etree.SubElement(emisor, "RznSoc").text = env("SEMOT_RAZON_SOCIAL")
        etree.SubElement(emisor, "GiroEmis").text = env("SEMOT_GIRO")
        etree.SubElement(emisor, "Telefono").text = env("SEMOT_TELEFONO")
        etree.SubElement(emisor, "DirOrigen").text = env("SEMOT_DIRECCION")
        etree.SubElement(emisor, "CmnaOrigen").text = env("SEMOT_COMUNA")
        etree.SubElement(emisor, "CiudadOrigen").text = env("SEMOT_CIUDAD")

        receptor = etree.SubElement(encabezado, "Receptor")
        etree.SubElement(receptor, "RUTRecep").text = cliente.rut
        etree.SubElement(receptor, "RznSocRecep").text = cliente.nombre
        etree.SubElement(receptor, "GiroRecep").text = cliente.giro or "Sin giro"
        etree.SubElement(receptor, "DirRecep").text = cliente.direccion
        etree.SubElement(receptor, "CmnaRecep").text = cliente.comuna.nombreComuna
        etree.SubElement(receptor, "CiudadRecep").text = "Santiago"
        etree.SubElement(receptor, "Telefono").text = cliente.telefono
        etree.SubElement(receptor, "Patente").text = transaccion.camion.patente

        totales = etree.SubElement(encabezado, "Totales")
        monto_neto = int(transaccion.total / 1.19)
        iva = int(transaccion.total - monto_neto)
        if monto_neto + iva < transaccion.total:
            iva += 1
        etree.SubElement(totales, "MntNeto").text = str(monto_neto)
        etree.SubElement(totales, "TasaIVA").text = str(19)
        etree.SubElement(totales, "IVA").text = str(iva)
        etree.SubElement(totales, "MntTotal").text = str(transaccion.total)

        detalle = etree.SubElement(documento, "Detalle")
        item = etree.SubElement(detalle, "CdgItem")
        etree.SubElement(detalle, "NroLinDet").text = "1"
        etree.SubElement(item, "TpoCodigo").text = "INT1"
        etree.SubElement(item, "VlrCodigo").text = "P" + str(transaccion.qr.id)
        if transaccion.lomo == 0:
            etree.SubElement(detalle, "NmbItem").text = f"Ticket patente {transaccion.camion.patente} - {str(transaccion.cantidad)} {str(transaccion.unidad)}"
        else:
            etree.SubElement(detalle, "NmbItem").text = f"Ticket patente {transaccion.camion.patente} - {str(transaccion.cantidad)} {str(transaccion.unidad)}. Incluye recargo por 20%"
        etree.SubElement(detalle, "QtyItem").text = str(1)
        etree.SubElement(detalle, "PrcItem").text = str(int(transaccion.total / 1.19))
        etree.SubElement(detalle, "MontoItem").text = str(int(transaccion.total / 1.19))

        # === ADICIONALES ===
        if tipo_documento == 33:
            adicional = etree.SubElement(documento, "Adicional")
            nodosa = etree.SubElement(adicional, "NodosA")
            etree.SubElement(nodosa, "A5").text = transaccion.metodo_pago.nombre
            etree.SubElement(nodosa, "A7").text = transaccion.obra

        # Convertir el XML a cadena
        xml_data = etree.tostring(root, pretty_print=True, encoding="utf-8").decode("utf-8")
        #print(xml_data)
        reps, params = get_params()
        return True, xml_data, params

    except TicketPresencial.DoesNotExist:
        return False, "Transacción no encontrada.", None
    except ValueError as e:
        return False, str(e), None
    except:
        guardar_error(sys.exc_info())
        return False, "Error inesperado", None



def generar_factura_tickets(factura_id):
    try:
        factura = FacturaVenta.objects.get(pk=factura_id)

        cliente = factura.cliente
        tickets = TicketFacturaVenta.objects.select_related("transaccion__qr", "transaccion__camion").filter(factura=factura).order_by("transaccion__qr__id")

        if not cliente.rut or not cliente.direccion or not cliente.giro:
            raise ValueError(f"El cliente asociado no tiene toda la información suficiente: {cliente.id}")

        tipo_documento = 33
        root = etree.Element("DTE")
        documento = etree.SubElement(root, "Documento")
        encabezado = etree.SubElement(documento, "Encabezado")

        iddoc = etree.SubElement(encabezado, "IdDoc")
        etree.SubElement(iddoc, "TipoDTE").text = str(tipo_documento)
        etree.SubElement(iddoc, "Folio").text = str(0)
        etree.SubElement(iddoc, "FchEmis").text = factura.fecha_factura.strftime("%Y-%m-%d")

        emisor = etree.SubElement(encabezado, "Emisor")
        etree.SubElement(emisor, "RUTEmisor").text = env("SEMOT_RUT")
        etree.SubElement(emisor, "RznSoc").text = env("SEMOT_RAZON_SOCIAL")
        etree.SubElement(emisor, "GiroEmis").text = env("SEMOT_GIRO")
        etree.SubElement(emisor, "Telefono").text = env("SEMOT_TELEFONO")
        etree.SubElement(emisor, "DirOrigen").text = env("SEMOT_DIRECCION")
        etree.SubElement(emisor, "CmnaOrigen").text = env("SEMOT_COMUNA")
        etree.SubElement(emisor, "CiudadOrigen").text = env("SEMOT_CIUDAD")

        receptor = etree.SubElement(encabezado, "Receptor")
        etree.SubElement(receptor, "RUTRecep").text = cliente.rut
        etree.SubElement(receptor, "RznSocRecep").text = cliente.nombre
        etree.SubElement(receptor, "GiroRecep").text = cliente.giro
        etree.SubElement(receptor, "DirRecep").text = cliente.direccion
        etree.SubElement(receptor, "CmnaRecep").text = cliente.comuna.nombreComuna
        etree.SubElement(receptor, "CiudadRecep").text = "Santiago"
        etree.SubElement(receptor, "Contacto").text = cliente.telefono

        # === CALCULAR MONTOS ===
        valores_brutos = [t.transaccion.total for t in tickets]
        total_bruto = sum(valores_brutos)

        # Neto global
        monto_neto_global = Decimal(total_bruto) / Decimal("1.19")
        monto_neto_global = monto_neto_global.quantize(Decimal("1"), rounding=ROUND_HALF_UP)
        iva_global = total_bruto - monto_neto_global

        # Netos individuales redondeados
        netos = []
        for bruto in valores_brutos:
            neto = Decimal(bruto) / Decimal("1.19")
            neto = neto.quantize(Decimal("1"), rounding=ROUND_HALF_UP)
            netos.append(int(neto))

        # Ajustar diferencia con el neto global
        diferencia = int(monto_neto_global) - sum(netos)
        if diferencia != 0:
            netos[-1] += diferencia
        
        # === TOTALES ===
        totales = etree.SubElement(encabezado, "Totales")
        etree.SubElement(totales, "MntNeto").text = str(int(monto_neto_global))
        etree.SubElement(totales, "TasaIVA").text = "19"
        etree.SubElement(totales, "IVA").text = str(int(iva_global))
        etree.SubElement(totales, "MntTotal").text = str(int(total_bruto))

        # === DETALLES ===
        for idx, (t, neto_unit) in enumerate(zip(tickets, netos), start=1):
            detalle = etree.SubElement(documento, "Detalle")
            item = etree.SubElement(detalle, "CdgItem")
            etree.SubElement(detalle, "NroLinDet").text = str(idx)
            etree.SubElement(item, "TpoCodigo").text = "INT1"
            etree.SubElement(item, "VlrCodigo").text = "N" + str(t.transaccion.qr.id)
            if t.transaccion.lomo == 0:
                etree.SubElement(detalle, "NmbItem").text = f"Ticket patente {t.transaccion.camion.patente} - {t.transaccion.cantidad} {t.transaccion.unidad}"
            else:
                etree.SubElement(detalle, "NmbItem").text = f"Ticket patente {t.transaccion.camion.patente} - {t.transaccion.cantidad} {t.transaccion.unidad}. Incluye recargo por 20%"
            etree.SubElement(detalle, "QtyItem").text = "1"
            etree.SubElement(detalle, "PrcItem").text = str(neto_unit)
            etree.SubElement(detalle, "MontoItem").text = str(neto_unit)

        # === REFERENCIAS OC ===
        contOC = 1
        obras_ids = tickets.values_list("transaccion__obra", flat=True).distinct()
        ordenes = OrdenCompra.objects.filter(obra_id__in=obras_ids).distinct()
        for o in ordenes:
            ocs = etree.SubElement(documento, "Referencia")
            etree.SubElement(ocs, "NroLinRef").text = str(contOC)
            etree.SubElement(ocs, "TpoDocRef").text = str(801)
            etree.SubElement(ocs, "FolioRef").text = str(o.numero)
            etree.SubElement(ocs, "FchRef").text = o.fecha_emision.strftime('%Y-%m-%d')
            etree.SubElement(ocs, "CodRef").text = "0"
            etree.SubElement(ocs, "RazonRef").text = f"{o.referencia}"
            contOC += 1
        
        # === ADICIONALES ===
        # if tipo_documento == 33:
        #     adicional = etree.SubElement(documento, "Adicional")
        #     nodosa = etree.SubElement(adicional, "NodosA")
        #     etree.SubElement(nodosa, "A5").text = "Cliente Crédito"
        #     etree.SubElement(nodosa, "A7").text = t.transaccion.obra.nombre

        xml_data = etree.tostring(root, pretty_print=True, encoding="utf-8").decode("utf-8")
        reps, params = get_params()
        return True, xml_data, params

    except FacturaVenta.DoesNotExist:
        return False, "Factura no encontrada.", None
    except ValueError as e:
        return False, str(e), None
    except:
        guardar_error(sys.exc_info())
        return False, "Error inesperado", None



def generar_nota_credito_factura(factura, razon):
    try:
        # Obtener la transacción web
        factura = FacturaVenta.objects.select_related('cliente').get(pk=factura)
        
        if factura.facturado == 0:
            return False, "El documento no ha sido facturado", None

        cliente = factura.cliente

        # Validar si el cliente tiene información necesaria para generar el documento
        if not cliente.rut or not cliente.direccion:
            raise ValueError(f"El cliente asociado no tiene información suficiente: {cliente.id}")
        
        tickets = TicketFacturaVenta.objects.select_related("transaccion__qr", "transaccion__camion").filter(factura=factura).order_by("transaccion__qr__id")
        
        # Determinar el tipo de documento (Factura o Boleta)
        tipo_documento = 33

        # Construir el XML
        root = etree.Element("DTE")
        documento = etree.SubElement(root, "Documento")
        encabezado = etree.SubElement(documento, "Encabezado")

        iddoc = etree.SubElement(encabezado, "IdDoc")
        etree.SubElement(iddoc, "TipoDTE").text = "61"
        etree.SubElement(iddoc, "Folio").text = str(0) # 0 para que el folio de la NC se genera automático. El folio de la FV a eliminar va en referencias
        etree.SubElement(iddoc, "FchEmis").text = datetime.now().strftime("%Y-%m-%d")

        emisor = etree.SubElement(encabezado, "Emisor")
        etree.SubElement(emisor, "RUTEmisor").text = env("SEMOT_RUT")  # Cambiar por valor dinámico si necesario
        etree.SubElement(emisor, "RznSoc").text = env("SEMOT_RAZON_SOCIAL")
        etree.SubElement(emisor, "GiroEmis").text = env("SEMOT_GIRO")
        etree.SubElement(emisor, "Telefono").text = env("SEMOT_TELEFONO")
        etree.SubElement(emisor, "DirOrigen").text = env("SEMOT_DIRECCION")
        etree.SubElement(emisor, "CmnaOrigen").text = env("SEMOT_COMUNA")
        etree.SubElement(emisor, "CiudadOrigen").text = env("SEMOT_CIUDAD")
        

        receptor = etree.SubElement(encabezado, "Receptor")
        etree.SubElement(receptor, "RUTRecep").text = cliente.rut
        etree.SubElement(receptor, "RznSocRecep").text = cliente.nombre
        etree.SubElement(receptor, "GiroRecep").text = cliente.giro or "Sin giro"
        etree.SubElement(receptor, "DirRecep").text = cliente.direccion
        etree.SubElement(receptor, "CmnaRecep").text = cliente.comuna.nombreComuna

        cont = 1
        for t in tickets:
            detalle = etree.SubElement(documento, "Detalle")
            item = etree.SubElement(detalle, "CdgItem")
            etree.SubElement(detalle, "NroLinDet").text = str(cont)
            etree.SubElement(item, "TpoCodigo").text = "INT1"
            etree.SubElement(item, "VlrCodigo").text = "N" + str(t.transaccion.qr.id)
            if t.transaccion.lomo == 0:
                etree.SubElement(detalle, "NmbItem").text = f"Ticket patente {t.transaccion.camion.patente} - {str(t.transaccion.cantidad)} {str(t.transaccion.unidad)}"
            else:
                etree.SubElement(detalle, "NmbItem").text = f"Ticket patente {t.transaccion.camion.patente} - {str(t.transaccion.cantidad)} {str(t.transaccion.unidad)}. Incluye recargo por 20%"
            etree.SubElement(detalle, "QtyItem").text = str(1)
            etree.SubElement(detalle, "PrcItem").text = str(t.transaccion.total / 1.19)
            etree.SubElement(detalle, "MontoItem").text = str(t.transaccion.total / 1.19)
            cont += 1

        totales = etree.SubElement(encabezado, "Totales")
        monto_neto = int(factura.neto)
        iva = int(factura.iva)
        etree.SubElement(totales, "MntNeto").text = str(monto_neto)
        etree.SubElement(totales, "TasaIVA").text = str(19)
        etree.SubElement(totales, "IVA").text = str(iva)
        etree.SubElement(totales, "MntTotal").text = str(factura.total)

        referencia = etree.SubElement(documento, "Referencia")
        etree.SubElement(referencia, "NroLinRef").text = "1"
        etree.SubElement(referencia, "TpoDocRef").text = str(tipo_documento)
        etree.SubElement(referencia, "FolioRef").text = str(factura.folio)
        etree.SubElement(referencia, "FchRef").text = factura.fecha_factura.strftime("%Y-%m-%d")
        etree.SubElement(referencia, "CodRef").text = "1" # Eliminación del documento completo
        etree.SubElement(referencia, "RazonRef").text = str(razon) # 90 carácteres máximo (límite dado por facturación.cl)

        # Convertir el XML a cadena
        xml_data = etree.tostring(root, pretty_print=True, encoding="utf-8").decode("utf-8")
        #print(xml_data)
        reps, params = get_params()
        return True, xml_data, params

    except FacturaVenta.DoesNotExist:
        return False, "Factura no encontrada.", None
    except ValueError as e:
        return False, str(e), None
    except:
        traceback.print_exc()
        guardar_error(sys.exc_info())
        return False, "Error inesperado", None
    


def generar_nota_credito_factura_o_boleta_ticket_web(transaccion_web_id, razon):
    try:
        # Obtener la transacción web
        transaccion = TransaccionWeb.objects.select_related('cliente', 'estado_pago').get(pk=transaccion_web_id)
        
        if not transaccion or transaccion.activo == 0:
            raise ValueError("Transacción no válida o inactiva.")

        cliente = transaccion.cliente

        # Validar si el cliente tiene información necesaria para generar el documento
        if not cliente.rut or not cliente.direccion:
            raise ValueError(f"El cliente asociado no tiene información suficiente: {cliente.id}")
        
        # Determinar el tipo de documento (Factura o Boleta)
        tipo_documento = 33 if transaccion.dte.id == 2 else 39

        iapi = InteraccionApiWeb.objects.filter(transaccion=transaccion).first()
        if not iapi:
            return False, "No existe registro del documento a eliminar", None

        # Construir el XML
        root = etree.Element("DTE")
        documento = etree.SubElement(root, "Documento")
        encabezado = etree.SubElement(documento, "Encabezado")

        iddoc = etree.SubElement(encabezado, "IdDoc")
        etree.SubElement(iddoc, "TipoDTE").text = "61"
        etree.SubElement(iddoc, "Folio").text = str(0) # 0 para que el folio de la NC se genera automático. El folio de la FV a eliminar va en referencias
        etree.SubElement(iddoc, "FchEmis").text = datetime.now().strftime("%Y-%m-%d")

        emisor = etree.SubElement(encabezado, "Emisor")
        etree.SubElement(emisor, "RUTEmisor").text = env("SEMOT_RUT")  # Cambiar por valor dinámico si necesario
        etree.SubElement(emisor, "RznSoc").text = env("SEMOT_RAZON_SOCIAL")
        etree.SubElement(emisor, "GiroEmis").text = env("SEMOT_GIRO")
        etree.SubElement(emisor, "Telefono").text = env("SEMOT_TELEFONO")
        etree.SubElement(emisor, "DirOrigen").text = env("SEMOT_DIRECCION")
        etree.SubElement(emisor, "CmnaOrigen").text = env("SEMOT_COMUNA")
        etree.SubElement(emisor, "CiudadOrigen").text = env("SEMOT_CIUDAD")
        

        receptor = etree.SubElement(encabezado, "Receptor")
        etree.SubElement(receptor, "RUTRecep").text = cliente.rut
        etree.SubElement(receptor, "RznSocRecep").text = cliente.nombre
        etree.SubElement(receptor, "GiroRecep").text = cliente.giro or "Sin giro"
        etree.SubElement(receptor, "DirRecep").text = cliente.direccion
        etree.SubElement(receptor, "CmnaRecep").text = cliente.comuna.nombreComuna

        detalle = etree.SubElement(documento, "Detalle")
        item = etree.SubElement(detalle, "CdgItem")
        etree.SubElement(detalle, "NroLinDet").text = "1"
        etree.SubElement(item, "TpoCodigo").text = "INT1"
        etree.SubElement(item, "VlrCodigo").text = "W" + str(transaccion.id)
        if transaccion.lomo == 0:
            etree.SubElement(detalle, "NmbItem").text = f"Ticket patente {transaccion.camion} - {str(transaccion.cantidad)} {str(transaccion.unidad)}"
        else:
            etree.SubElement(detalle, "NmbItem").text = f"Ticket patente {transaccion.camion} - {str(transaccion.cantidad)} {str(transaccion.unidad)}. Incluye recargo por 20%"
        etree.SubElement(detalle, "QtyItem").text = str(1)
        etree.SubElement(detalle, "PrcItem").text = str(transaccion.total / 1.19)
        etree.SubElement(detalle, "MontoItem").text = str(transaccion.total / 1.19)

        totales = etree.SubElement(encabezado, "Totales")
        monto_neto = int(transaccion.total / 1.19)
        iva = int(transaccion.total - monto_neto)
        etree.SubElement(totales, "MntNeto").text = str(monto_neto)
        etree.SubElement(totales, "TasaIVA").text = str(19)
        etree.SubElement(totales, "IVA").text = str(iva)
        etree.SubElement(totales, "MntTotal").text = str(transaccion.total)

        referencia = etree.SubElement(documento, "Referencia")
        etree.SubElement(referencia, "NroLinRef").text = "1"
        etree.SubElement(referencia, "TpoDocRef").text = str(tipo_documento)
        etree.SubElement(referencia, "FolioRef").text = str(iapi.folio)
        etree.SubElement(referencia, "FchRef").text = transaccion.fecha.strftime("%Y-%m-%d")
        etree.SubElement(referencia, "CodRef").text = "1" # Eliminación del documento completo
        etree.SubElement(referencia, "RazonRef").text = str(razon) # 90 carácteres máximo (límite dado por facturación.cl)

        # Convertir el XML a cadena
        xml_data = etree.tostring(root, pretty_print=True, encoding="utf-8").decode("utf-8")
        #print(xml_data)
        reps, params = get_params()
        return True, xml_data, params

    except TransaccionWeb.DoesNotExist:
        return False, "Transacción no encontrada.", None
    except ValueError as e:
        return False, str(e), None
    except:
        guardar_error(sys.exc_info())
        return False, "Error inesperado", None



def generar_guia_despacho(guia_despacho_id):
    try:
        guia = (
            GuiaDespacho.objects
            .select_related(
                'pesaje',
                'pesaje__camion',
                'pesaje__camion__cliente',
                'pesaje__camion__cliente__comuna',
            )
            .get(pk=guia_despacho_id)
        )

        if not guia:
            raise ValueError("Guía de despacho no válida.")

        camion = None
        cliente = None

        if guia.pesaje and guia.pesaje.camion:
            camion = guia.pesaje.camion
            cliente = camion.cliente

        detalles = list(
            DetalleGuia.objects
            .select_related('item', 'unidad', 'camion')
            .filter(guia=guia)
            .order_by('id')
        )

        if not detalles:
            raise ValueError(f"La guía {guia.id} no tiene detalles asociados.")

        if not camion:
            camion = detalles[0].camion if detalles[0].camion else None

        if not camion:
            raise ValueError(f"La guía {guia.id} no tiene camión asociado.")

        if camion.activo == 0:
            raise ValueError(f"El camión asociado a la guía {guia.id} está inactivo.")

        if not cliente:
            cliente = camion.cliente

        if not cliente:
            raise ValueError(f"La guía {guia.id} no tiene cliente asociado.")

        if cliente.activo == 0:
            raise ValueError(f"El cliente asociado a la guía {guia.id} está inactivo.")

        if not cliente.rut or not cliente.direccion or not cliente.nombre:
            raise ValueError(f"El cliente asociado no tiene información suficiente: {cliente.id}")

        if not cliente.comuna:
            raise ValueError(f"El cliente asociado no tiene comuna configurada: {cliente.id}")

        tipo_documento = 52

        root = etree.Element("DTE")
        documento = etree.SubElement(root, "Documento")
        encabezado = etree.SubElement(documento, "Encabezado")

        iddoc = etree.SubElement(encabezado, "IdDoc")
        etree.SubElement(iddoc, "TipoDTE").text = str(tipo_documento)
        etree.SubElement(iddoc, "Folio").text = "0"
        etree.SubElement(iddoc, "FchEmis").text = guia.fecha_guia.strftime("%Y-%m-%d")
        etree.SubElement(iddoc, "TipoDespacho").text = "1" # 1 = Traslado a cargo del receptor
        etree.SubElement(iddoc, "IndTraslado").text = str(guia.es_venta) # 1 = Es venta, >1 No es venta, 6 = El texto es -> No es venta

        emisor = etree.SubElement(encabezado, "Emisor")
        etree.SubElement(emisor, "RUTEmisor").text = env("SEMOT_RUT")
        etree.SubElement(emisor, "RznSoc").text = env("SEMOT_RAZON_SOCIAL")
        etree.SubElement(emisor, "GiroEmis").text = env("SEMOT_GIRO")
        etree.SubElement(emisor, "Telefono").text = env("SEMOT_TELEFONO")
        etree.SubElement(emisor, "DirOrigen").text = env("SEMOT_DIRECCION")
        etree.SubElement(emisor, "CmnaOrigen").text = env("SEMOT_COMUNA")
        etree.SubElement(emisor, "CiudadOrigen").text = env("SEMOT_CIUDAD")

        receptor = etree.SubElement(encabezado, "Receptor")
        etree.SubElement(receptor, "RUTRecep").text = cliente.rut
        etree.SubElement(receptor, "RznSocRecep").text = cliente.nombre
        etree.SubElement(receptor, "GiroRecep").text = cliente.giro or "Sin giro"
        etree.SubElement(receptor, "DirRecep").text = cliente.direccion
        etree.SubElement(receptor, "CmnaRecep").text = cliente.comuna.nombreComuna
        etree.SubElement(receptor, "CiudadRecep").text = "Santiago"
        if cliente.telefono:
            etree.SubElement(receptor, "Telefono").text = cliente.telefono

        total_neto_detalles = 0
        detalles_resueltos = []

        for detalle_obj in detalles:
            if not detalle_obj.item:
                raise ValueError(f"Detalle {detalle_obj.id} sin item asociado.")
            if not detalle_obj.unidad:
                raise ValueError(f"Detalle {detalle_obj.id} sin unidad asociada.")
            if detalle_obj.cantidad is None:
                raise ValueError(f"Detalle {detalle_obj.id} sin cantidad.")
            if detalle_obj.precio_unitario is None:
                raise ValueError(f"Detalle {detalle_obj.id} sin precio_unitario.")

            camion_detalle = detalle_obj.camion or camion
            if not camion_detalle:
                raise ValueError(f"Detalle {detalle_obj.id} sin camión asociado.")

            cantidad = Decimal(str(detalle_obj.cantidad))
            precio_unitario = Decimal(str(detalle_obj.precio_unitario))

            if cantidad <= 0:
                raise ValueError(f"Detalle {detalle_obj.id} con cantidad inválida.")

            if precio_unitario < 0:
                raise ValueError(f"Detalle {detalle_obj.id} con precio unitario inválido.")

            unidad_id = detalle_obj.unidad.id

            # 1 = Kilogramos
            # 2 = Toneladas
            # 3 = m3
            # 4 = Unidades
            
            if unidad_id in [1, 2, 3, 4]:
                cantidad_calculo = cantidad
                qty_item_xml = cantidad
            else:
                raise ValueError(
                    f"La unidad del detalle {detalle_obj.id} no está soportada: {detalle_obj.unidad.id}"
                )

            monto_item = int(
                (cantidad_calculo * precio_unitario).quantize(
                    Decimal("1"),
                    rounding=ROUND_HALF_UP
                )
            )

            total_neto_detalles += monto_item

            detalles_resueltos.append({
                "detalle": detalle_obj,
                "qty_item_xml": qty_item_xml,
                "precio_unitario": precio_unitario,
                "monto_item": monto_item,
            })

        iva = int(
            (Decimal(total_neto_detalles) * Decimal("0.19")).quantize(
                Decimal("1"),
                rounding=ROUND_HALF_UP
            )
        )
        total = total_neto_detalles + iva

        if int(guia.neto) != int(total_neto_detalles):
            raise ValueError(
                f"El neto de la guía ({guia.neto}) no coincide con la suma del detalle ({total_neto_detalles})."
            )

        if int(guia.iva) != int(iva):
            raise ValueError(
                f"El IVA de la guía ({guia.iva}) no coincide con el calculado ({iva})."
            )

        if int(guia.total) != int(total):
            raise ValueError(
                f"El total de la guía ({guia.total}) no coincide con el calculado ({total})."
            )

        totales = etree.SubElement(encabezado, "Totales")
        etree.SubElement(totales, "MntNeto").text = str(total_neto_detalles)
        etree.SubElement(totales, "TasaIVA").text = "19"
        etree.SubElement(totales, "IVA").text = str(iva)
        etree.SubElement(totales, "MntTotal").text = str(total)

        for index, item_resuelto in enumerate(detalles_resueltos, start=1):
            detalle_obj = item_resuelto["detalle"]
            qty_item_xml = item_resuelto["qty_item_xml"]
            precio_unitario = item_resuelto["precio_unitario"]
            monto_item = item_resuelto["monto_item"]

            detalle = etree.SubElement(documento, "Detalle")
            etree.SubElement(detalle, "NroLinDet").text = str(index)

            cdg_item = etree.SubElement(detalle, "CdgItem")
            etree.SubElement(cdg_item, "TpoCodigo").text = "INT1"
            etree.SubElement(cdg_item, "VlrCodigo").text = str(detalle_obj.item.codigo)

            etree.SubElement(detalle, "NmbItem").text = str(detalle_obj.item.descripcion)
            etree.SubElement(detalle, "QtyItem").text = (
                format(qty_item_xml, 'f').rstrip('0').rstrip('.')
                if '.' in format(qty_item_xml, 'f')
                else format(qty_item_xml, 'f')
            )
            etree.SubElement(detalle, "UnmdItem").text = str(detalle_obj.unidad.nombre)
            etree.SubElement(detalle, "PrcItem").text = str(
                int(precio_unitario.quantize(Decimal("1"), rounding=ROUND_HALF_UP))
            )
            etree.SubElement(detalle, "MontoItem").text = str(monto_item)

        xml_data = etree.tostring(
            root,
            pretty_print=True,
            encoding="ISO-8859-1",
            xml_declaration=True
        ).decode("ISO-8859-1")

        reps, params = get_params()

        return True, xml_data, params

    except GuiaDespacho.DoesNotExist:
        return False, "Guía de despacho no encontrada.", None
    except ValueError as e:
        return False, str(e), None
    except Exception:
        guardar_error(sys.exc_info())
        return False, "Error inesperado", None



def generar_anulacion_guia_despacho(guia_despacho_id):
    try:
        guia = GuiaDespacho.objects.get(pk=guia_despacho_id)

        if not guia:
            raise ValueError("Guía de despacho no válida.")

        if int(guia.facturado) == -1:
            raise ValueError("La guía ya fue anulada previamente.")

        if int(guia.facturado) == 0:
            raise ValueError("La guía no ha sido enviada a facturacion.cl.")

        if not guia.folio:
            raise ValueError("La guía no tiene folio asociado.")

        resp, params = get_params()
        if not resp:
            return False, params, None

        params["tpomov"] = b64encode("V".encode("utf-8")).decode("utf-8")
        params["folio"] = b64encode(str(guia.folio).encode("utf-8")).decode("utf-8")
        params["tipo"] = b64encode("52".encode("utf-8")).decode("utf-8")

        return True, params, guia

    except GuiaDespacho.DoesNotExist:
        return False, "Guía de despacho no encontrada.", None
    except ValueError as e:
        return False, str(e), None
    except:
        guardar_error(sys.exc_info())
        return False, "Error inesperado", None



def generar_params_obtener_link_web(folio, td):
    try:
        resp, params = get_params()
        if td == 33:
            params["tpomov"] = b64encode("V".encode("utf-8")).decode("utf-8")
        if td == 39:
            params["tpomov"] = b64encode("B".encode("utf-8")).decode("utf-8")
        params["folio"] = b64encode(str(folio).encode("utf-8")).decode("utf-8")
        params["tipo"] = b64encode(str(td).encode("utf-8")).decode("utf-8")
        params["cedible"] = b64encode("True".encode("utf-8")).decode("utf-8")
        return resp, params
    except:
        guardar_error(sys.exc_info())
        return False, "Error inesperado"



def get_params():
    try:
        # Codificar los datos de autenticación y archivo en base64
        usuario = b64encode(env("FACTURACION_USUARIO").encode("utf-8")).decode("utf-8")
        rut = b64encode(env("FACTURACION_RUT").encode("utf-8")).decode("utf-8")
        clave = b64encode(env("FACTURACION_CLAVE").encode("utf-8")).decode("utf-8")
        puerto = b64encode(env("FACTURACION_PUERTO").encode("utf-8")).decode("utf-8")
        params = {
            'login': {
                'Usuario': usuario,
                'Rut': rut,
                'Clave': clave,
                'Puerto': puerto,
                'IncluyeLink': "1"
            },
        }

        return True, params
    except:
        guardar_error(sys.exc_info())
        return False, "Error inesperado"