#!/usr/bin/env python3

# ~ Empresa Libre
# ~ Copyright (C) 2016-2018  Mauricio Baeza Servin (web@correolibre.net)
# ~
# ~ This program is free software: you can redistribute it and/or modify
# ~ it under the terms of the GNU General Public License as published by
# ~ the Free Software Foundation, either version 3 of the License, or
# ~ (at your option) any later version.
# ~
# ~ This program is distributed in the hope that it will be useful,
# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# ~ GNU General Public License for more details.
# ~
# ~ You should have received a copy of the GNU General Public License
# ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.

import datetime
from xml.etree import ElementTree as ET
from xml.dom.minidom import parseString

from dateutil import parser
from logbook import Logger


log = Logger('XML')
CFDI_ACTUAL = 'cfdi40'
NOMINA_ACTUAL = 'nomina12'
PUBLIC = 'PUBLICO EN GENERAL'
CFDI_EGRESO = 'E'

DEFAULT = {
    'exportacion': '01',
}


SAT = {
    'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
    'cfdi40': {
        'version': '4.0',
        'prefix': 'cfdi',
        'xmlns': 'http://www.sat.gob.mx/cfd/4',
        'schema': 'http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd',
    },
    'cfdi33': {
        'version': '3.3',
        'prefix': 'cfdi',
        'xmlns': 'http://www.sat.gob.mx/cfd/3',
        'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd',
    },
    'cfdi32': {
        'version': '3.2',
        'prefix': 'cfdi',
        'xmlns': 'http://www.sat.gob.mx/cfd/3',
        'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd',
    },
    'nomina11': {
        'version': '1.1',
        'prefix': 'nomina',
        'xmlns': 'http://www.sat.gob.mx/nomina',
        'schema': ' http://www.sat.gob.mx/nomina http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina11.xsd',
    },
    'nomina': {
        'version': '1.2',
        'prefix': 'nomina12',
        'xmlns': 'http://www.sat.gob.mx/nomina12',
        'schema': ' http://www.sat.gob.mx/nomina12 http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina12.xsd',
    },
    'locales': {
        'version': '1.0',
        'prefix': 'implocal',
        'xmlns': 'http://www.sat.gob.mx/implocal',
        'schema': ' http://www.sat.gob.mx/implocal http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd',
    },
    'donativo': {
        'version': '1.1',
        'prefix': 'donat',
        'xmlns': 'http://www.sat.gob.mx/donat',
        'schema': ' http://www.sat.gob.mx/donat http://www.sat.gob.mx/sitio_internet/cfd/donat/donat11.xsd',
        'leyenda': 'Este comprobante ampara un donativo, el cual será destinado por la donataria a los fines propios de su objeto social. En el caso de que los bienes donados hayan sido deducidos previamente para los efectos del impuesto sobre la renta, este donativo no es deducible. La reproducción no autorizada de este comprobante constituye un delito en los términos de las disposiciones fiscales.',
    },
    'ine': {
        'version': '1.1',
        'prefix': 'ine',
        'xmlns': 'http://www.sat.gob.mx/ine',
        'schema': ' http://www.sat.gob.mx/ine http://www.sat.gob.mx/sitio_internet/cfd/ine/ine11.xsd',
    },
    'edu': {
        'version': '1.0',
        'prefix': 'iedu',
        'xmlns': 'http://www.sat.gob.mx/iedu',
        'schema': ' http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd',
    },
    'pagos': {
        'version': '2.0',
        'prefix': 'pago20',
        'xmlns': 'http://www.sat.gob.mx/Pagos20',
        'schema': ' http://www.sat.gob.mx/Pagos20 http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd',
    },
    'divisas': {
        'version': '1.0',
        'prefix': 'divisas',
        'xmlns': 'http://www.sat.gob.mx/divisas',
        'schema': ' http://www.sat.gob.mx/divisas http://www.sat.gob.mx/sitio_internet/cfd/divisas/divisas.xsd',
    },
    'leyendas': {
        'version': '1.0',
        'prefix': 'leyendasFisc',
        'xmlns': 'http://www.sat.gob.mx/leyendasFiscales',
        'schema': ' http://www.sat.gob.mx/leyendasFiscales http://www.sat.gob.mx/sitio_internet/cfd/leyendasFiscales/leyendasFisc.xsd',
    },
    'cartaporte': {
        'version': '3.0',
        'prefix': 'cartaporte30',
        'xmlns': 'http://www.sat.gob.mx/CartaPorte30',
        'schema': ' http://www.sat.gob.mx/CartaPorte30 http://www.sat.gob.mx/sitio_internet/cfd/CartaPorte/CartaPorte30.xsd',
    },
    'comercioe': {
        'version': '2.0',
        'prefix': 'cce20',
        'xmlns': 'http://www.sat.gob.mx/ComercioExterior20',
        'schema': ' http://www.sat.gob.mx/ComercioExterior20 http://www.sat.gob.mx/sitio_internet/cfd/ComercioExterior20/ComercioExterior20.xsd',
    }
}


class CFDI(object):

    def __init__(self, version=CFDI_ACTUAL):
        self._sat_cfdi = SAT[version]
        self._xsi = SAT['xsi']
        self._pre = self._sat_cfdi['prefix']
        self._cfdi = None
        self._complemento = None
        self._impuestos_locales = False
        self._donativo = False
        self._ine = False
        self._edu = False
        self._pagos = False
        self._is_nomina = False
        self._leyendas = False
        self._carta_porte = False
        self._comercio_exterior = False
        self._divisas = ''
        self._tipo_de_comprobante = ''
        self._exportacion = DEFAULT['exportacion']
        self.error = ''

    def _now(self):
        return datetime.datetime.now().isoformat()[:19]

    def get_xml(self, datos):
        if not self._validate(datos):
            return ''

        self._comprobante(datos['comprobante'])
        if 'global' in datos:
            self._informacion_global(datos['global'])
        self._relacionados(datos['relacionados'])
        self._emisor(datos['emisor'])
        self._receptor(datos['receptor'])
        self._conceptos(datos['conceptos'])
        self._impuestos(datos['impuestos'])
        self._locales(datos['impuestos'])
        self._donatarias(datos['donativo'])
        self._complementos(datos['complementos'])

        if 'nomina' in datos:
            self._nomina(datos['nomina'])

        xml = self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))

        return xml

    def add_sello(self, sello, cert_txt):
        self._cfdi.attrib['Sello'] = sello
        self._cfdi.attrib['Certificado'] = cert_txt
        return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))

    def _to_pretty_xml(self, source):
        tree = parseString(source)
        xml = tree.toprettyxml(encoding='utf-8').decode('utf-8')
        return xml

    def _validate(self, datos):
        if datos['impuestos']:
            if datos['impuestos']['total_locales_trasladados'] or \
                datos['impuestos']['total_locales_retenciones']:
                self._impuestos_locales = True

        if datos['donativo']:
            self._donativo = True

        self._edu = datos.get('edu', False)

        if datos['complementos']:
            if 'ine' in datos['complementos']:
                self._ine = True
            self._pagos = bool(datos['complementos'].get('pagos', False))
            self._leyendas = bool(datos['complementos'].get('leyendas', False))
            self._carta_porte = bool(datos['complementos'].get('cartaporte', False))
            self._comercio_exterior = bool(datos['complementos'].get('comercioe', False))
            if self._comercio_exterior:
                self._exportacion = datos['complementos']['comercioe'].pop('Exportacion')

        self._divisas = datos['comprobante'].pop('divisas', '')

        if 'nomina' in datos:
            self._is_nomina = True
            return self._validate_nomina(datos)

        return True

    def _validate_nomina(self, datos):
        return True

    def _comprobante(self, datos):
        attributes = {}
        attributes['xmlns:{}'.format(self._pre)] = self._sat_cfdi['xmlns']
        attributes['xmlns:xsi'] = self._xsi

        schema_locales = ''
        if self._impuestos_locales:
            name = 'xmlns:{}'.format(SAT['locales']['prefix'])
            attributes[name] = SAT['locales']['xmlns']
            schema_locales = SAT['locales']['schema']

        schema_donativo = ''
        if self._donativo:
            name = 'xmlns:{}'.format(SAT['donativo']['prefix'])
            attributes[name] = SAT['donativo']['xmlns']
            schema_donativo = SAT['donativo']['schema']

        schema_ine = ''
        if self._ine:
            name = 'xmlns:{}'.format(SAT['ine']['prefix'])
            attributes[name] = SAT['ine']['xmlns']
            schema_ine = SAT['ine']['schema']

        schema_edu = ''
        if self._edu:
            name = 'xmlns:{}'.format(SAT['edu']['prefix'])
            attributes[name] = SAT['edu']['xmlns']
            schema_edu = SAT['edu']['schema']

        schema_divisas = ''
        if self._divisas:
            name = 'xmlns:{}'.format(SAT['divisas']['prefix'])
            attributes[name] = SAT['divisas']['xmlns']
            schema_divisas = SAT['divisas']['schema']

        schema_nomina = ''
        if self._is_nomina:
            name = 'xmlns:{}'.format(SAT['nomina']['prefix'])
            attributes[name] = SAT['nomina']['xmlns']
            schema_nomina = SAT['nomina']['schema']

        schema_pagos = ''
        if self._pagos:
            name = 'xmlns:{}'.format(SAT['pagos']['prefix'])
            attributes[name] = SAT['pagos']['xmlns']
            schema_pagos = SAT['pagos']['schema']

        schema_leyendas = ''
        if self._leyendas:
            name = 'xmlns:{}'.format(SAT['leyendas']['prefix'])
            attributes[name] = SAT['leyendas']['xmlns']
            schema_leyendas = SAT['leyendas']['schema']

        schema_carta_porte = ''
        if self._carta_porte:
            name = 'xmlns:{}'.format(SAT['cartaporte']['prefix'])
            attributes[name] = SAT['cartaporte']['xmlns']
            schema_carta_porte = SAT['cartaporte']['schema']

        schema_comercioe = ''
        if self._comercio_exterior:
            name = 'xmlns:{}'.format(SAT['comercioe']['prefix'])
            attributes[name] = SAT['comercioe']['xmlns']
            schema_carta_porte = SAT['comercioe']['schema']

        attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \
            schema_locales + schema_donativo + schema_ine + schema_edu + \
            schema_divisas + schema_nomina + schema_pagos + schema_leyendas + \
            schema_carta_porte + schema_comercioe
        attributes.update(datos)

        if not 'Version' in attributes:
            attributes['Version'] = self._sat_cfdi['version']
        if not 'Fecha' in attributes:
            attributes['Fecha'] = self._now()

        # ~ cfdi4
        if not 'Exportacion' in attributes:
            attributes['Exportacion'] = self._exportacion

        self._tipo_de_comprobante = attributes['TipoDeComprobante']

        self._cfdi = ET.Element('{}:Comprobante'.format(self._pre), attributes)
        return

    def _informacion_global(self, datos):
        if not datos:
            return

        node_name = '{}:InformacionGlobal'.format(self._pre)
        node = ET.SubElement(self._cfdi, node_name, datos)
        return

    def _relacionados(self, datos):
        if not datos or not datos['tipo'] or not datos['cfdis']:
            return

        node_name = '{}:CfdiRelacionados'.format(self._pre)
        value = {'TipoRelacion': datos['tipo']}
        node = ET.SubElement(self._cfdi, node_name, value)
        for uuid in datos['cfdis']:
            node_name = '{}:CfdiRelacionado'.format(self._pre)
            value = {'UUID': uuid}
            ET.SubElement(node, node_name, value)
        return

    def _emisor(self, datos):
        node_name = '{}:Emisor'.format(self._pre)
        emisor = ET.SubElement(self._cfdi, node_name, datos)
        return

    def _receptor(self, datos):
        receptor_name = datos['Nombre'].upper()
        if receptor_name == PUBLIC and self._tipo_de_comprobante == CFDI_EGRESO:
            receptor_name = datos['Nombre']
        datos['Nombre'] = receptor_name
        node_name = '{}:Receptor'.format(self._pre)
        emisor = ET.SubElement(self._cfdi, node_name, datos)
        return

    def _conceptos(self, datos):
        from xml.sax.saxutils import escape, unescape

        conceptos = ET.SubElement(self._cfdi, '{}:Conceptos'.format(self._pre))
        # ~ for row in reversed(datos):
        for row in datos:
            complemento = {}
            if 'complemento' in row:
                complemento = row.pop('complemento')
            cuenta_predial = row.pop('CuentaPredial', '')
            pedimento = row.pop('Pedimento', '')
            student = row.pop('student', '')

            taxes = {}
            if 'impuestos' in row:
                taxes = row.pop('impuestos')
            node_name = '{}:Concepto'.format(self._pre)
            concepto = ET.SubElement(conceptos, node_name, row)

            if taxes:
                node_name = '{}:Impuestos'.format(self._pre)
                impuestos = ET.SubElement(concepto, node_name)
                if 'traslados' in taxes and taxes['traslados']:
                    node_name = '{}:Traslados'.format(self._pre)
                    traslados = ET.SubElement(impuestos, node_name)
                    for traslado in taxes['traslados']:
                        ET.SubElement(
                            traslados, '{}:Traslado'.format(self._pre), traslado)
                if 'retenciones' in taxes and taxes['retenciones']:
                    node_name = '{}:Retenciones'.format(self._pre)
                    retenciones = ET.SubElement(impuestos, node_name)
                    for retencion in taxes['retenciones']:
                        ET.SubElement(
                            retenciones, '{}:Retencion'.format(self._pre), retencion)

            if pedimento:
                attributes = {'NumeroPedimento': pedimento}
                node_name = '{}:InformacionAduanera'.format(self._pre)
                ET.SubElement(concepto, node_name, attributes)

            if cuenta_predial:
                attributes = {'Numero': cuenta_predial}
                node_name = '{}:CuentaPredial'.format(self._pre)
                ET.SubElement(concepto, node_name, attributes)

            if student:
                node_name = '{}:ComplementoConcepto'.format(self._pre)
                complemento = ET.SubElement(concepto, node_name)
                ET.SubElement(complemento, 'iedu:instEducativas', student)
        return

    def _impuestos(self, datos):
        if self._is_nomina or not datos:
            return

        if not datos:
            node_name = '{}:Impuestos'.format(self._pre)
            ET.SubElement(self._cfdi, node_name)
            return

        attributes = {}
        fields = ('TotalImpuestosTrasladados', 'TotalImpuestosRetenidos')
        for field in fields:
            if field in datos:
                attributes[field] = datos[field]

        if not attributes:
            return

        node_name = '{}:Impuestos'.format(self._pre)
        impuestos = ET.SubElement(self._cfdi, node_name, attributes)

        if 'retenciones' in datos and datos['retenciones']:
            retenciones = ET.SubElement(impuestos, '{}:Retenciones'.format(self._pre))
            for row in datos['retenciones']:
                ET.SubElement(retenciones, '{}:Retencion'.format(self._pre), row)

        if 'traslados' in datos and datos['traslados']:
            traslados = ET.SubElement(impuestos, '{}:Traslados'.format(self._pre))
            for row in datos['traslados']:
                ET.SubElement(traslados, '{}:Traslado'.format(self._pre), row)
        return

    def _nomina(self, datos):
        pre = SAT['nomina']['prefix']

        if self._complemento is None:
            self._complemento = ET.SubElement(
                self._cfdi, '{}:Complemento'.format(self._pre))

        emisor = datos.pop('emisor', None)
        receptor = datos.pop('receptor', None)
        percepciones = datos.pop('percepciones', None)
        deducciones = datos.pop('deducciones', None)
        otros_pagos = datos.pop('otros_pagos', ())
        incapacidades = datos.pop('incapacidades', ())

        nomina = ET.SubElement(
            self._complemento, '{}:Nomina'.format(pre), datos['nomina'])

        if emisor:
            ET.SubElement(nomina, '{}:Emisor'.format(pre), emisor)

        if receptor:
            node = ET.SubElement(nomina, '{}:Receptor'.format(pre), receptor)

        if percepciones:
            details = percepciones.pop('details', None)
            hours_extra = percepciones.pop('hours_extra', None)
            separacion = percepciones.pop('separacion', None)
            if details:
                node = ET.SubElement(nomina, '{}:Percepciones'.format(pre), percepciones)
                for row in details:
                    nodep = ET.SubElement(node, '{}:Percepcion'.format(pre), row)
                    if row['TipoPercepcion'] == '019' and hours_extra:
                        for he in hours_extra:
                            ET.SubElement(nodep, '{}:HorasExtra'.format(pre), he)
                        hours_extra = None
                if separacion:
                    ET.SubElement(node, '{}:SeparacionIndemnizacion'.format(pre), separacion)

        if deducciones:
            details = deducciones.pop('details', None)
            if details:
                deducciones = ET.SubElement(nomina, '{}:Deducciones'.format(pre), deducciones)
                for row in details:
                    ET.SubElement(deducciones, '{}:Deduccion'.format(pre), row)

        if otros_pagos:
            node = ET.SubElement(nomina, '{}:OtrosPagos'.format(pre))
            for row in otros_pagos:
                subsidio = row.pop('subsidio', None)
                subnode = ET.SubElement(node, '{}:OtroPago'.format(pre), row)
                if subsidio:
                    ET.SubElement(subnode, '{}:SubsidioAlEmpleo'.format(pre), subsidio)

        if incapacidades:
            node = ET.SubElement(nomina, '{}:Incapacidades'.format(pre))
            for row in incapacidades:
                ET.SubElement(node, '{}:Incapacidad'.format(pre), row)
        return

    def _locales(self, datos):
        if not self._impuestos_locales:
            return

        if self._complemento is None:
            self._complemento = ET.SubElement(
                self._cfdi, '{}:Complemento'.format(self._pre))

        attributes = {}
        attributes['version'] = SAT['locales']['version']
        if not datos['total_locales_trasladados']:
            datos['total_locales_trasladados'] = '0.00'
        attributes['TotaldeTraslados'] = datos['total_locales_trasladados']
        if not datos['total_locales_retenciones']:
            datos['total_locales_retenciones'] = '0.00'
        attributes['TotaldeRetenciones'] = datos['total_locales_retenciones']

        node = ET.SubElement(
            self._complemento, 'implocal:ImpuestosLocales', attributes)

        for retencion in datos['locales_retenciones']:
            ET.SubElement(node, 'implocal:RetencionesLocales', retencion)
        for traslado in datos['locales_trasladados']:
            ET.SubElement(node, 'implocal:TrasladosLocales', traslado)
        return

    def _donatarias(self, datos):
        if not datos:
            return

        if self._complemento is None:
            self._complemento = ET.SubElement(
                self._cfdi, '{}:Complemento'.format(self._pre))

        attributes = {}
        attributes['version'] = SAT['donativo']['version']
        attributes['leyenda'] = SAT['donativo']['leyenda']
        attributes.update(datos)

        node = ET.SubElement(self._complemento, 'donat:Donatarias', attributes)

        return

    def _complemento_comercio_exterior(self, datos):
        prefix = SAT['comercioe']['prefix']

        emisor = datos.pop('emisor')
        propietarios = datos.pop('propietarios', {})
        receptor = datos.pop('receptor')
        destinatario = datos.pop('destinatario', {})
        mercancias = datos.pop('mercancias')

        attr = {'Version': SAT['comercioe']['version']}
        attr.update(datos)
        ce = ET.SubElement(
            self._complemento, f'{prefix}:ComercioExterior', attr)

        attributes = {}
        if 'Curp' in emisor:
            attributes = {'Curp': emisor.pop('Curp')}
        node = ET.SubElement(ce, '{}:Emisor'.format(prefix), attributes)
        ET.SubElement(node, '{}:Domicilio'.format(prefix), emisor)

        attributes = {}
        if 'NumRegIdTrib' in receptor:
            attributes = {'NumRegIdTrib': receptor.pop('NumRegIdTrib')}
        node = ET.SubElement(ce, '{}:Receptor'.format(prefix), attributes)
        ET.SubElement(node, '{}:Domicilio'.format(prefix), receptor)

        node = ET.SubElement(ce, '{}:Mercancias'.format(prefix))
        fields = ('Marca', 'Modelo', 'SubModelo', 'NumeroSerie')
        for row in mercancias:
            detalle = {}
            for f in fields:
                if f in row and row[f]:
                    detalle[f] = row[f]
                row.pop(f)
            concepto = ET.SubElement(node, '{}:Mercancia'.format(prefix), row)
            if detalle:
                ET.SubElement(
                    concepto, '{}:DescripcionesEspecificas'.format(prefix), detalle)

        return

    def _complementos(self, datos):
        if not datos:
            return

        if self._complemento is None:
            self._complemento = ET.SubElement(
                self._cfdi, '{}:Complemento'.format(self._pre))

        if self._carta_porte:
            datos = datos['cartaporte']
            ubicaciones = datos.pop('ubicaciones')
            mercancias = datos.pop('mercancias', ())
            tiposfigura = datos.pop('tiposfigura', ())

            autotransporte = datos.pop('autotransporte', {})
            identificacion = autotransporte.pop('identificacion')
            seguros = autotransporte.pop('seguros')
            remolques = autotransporte.pop('remolques')

            atributos = {'Version': SAT['cartaporte']['version']}
            atributos.update(datos)

            prefix = SAT['cartaporte']['prefix']
            node_carta = ET.SubElement(self._complemento, f'{prefix}:CartaPorte', atributos)

            node = ET.SubElement(node_carta, f'{prefix}:Ubicaciones')
            for ubicacion in ubicaciones:
                domicilio = ubicacion.pop('domicilio', {})
                dt = parser.parse(ubicacion['FechaHoraSalidaLlegada'])
                ubicacion['FechaHoraSalidaLlegada'] = dt.isoformat()[:19]
                sub_node = ET.SubElement(node, f'{prefix}:Ubicacion', ubicacion)
                if domicilio:
                    ET.SubElement(sub_node, f'{prefix}:Domicilio', domicilio)

            attr = mercancias
            mercancias = attr.pop('mercancias')

            node = ET.SubElement(node_carta, f'{prefix}:Mercancias', attr)
            for mercancia in mercancias:
                ET.SubElement(node, f'{prefix}:Mercancia', mercancia)

            sub_node = ET.SubElement(node, f'{prefix}:Autotransporte', autotransporte)
            ET.SubElement(sub_node, f'{prefix}:IdentificacionVehicular', identificacion)
            ET.SubElement(sub_node, f'{prefix}:Seguros', seguros)
            if remolques:
                node_remolques = ET.SubElement(sub_node, f'{prefix}:Remolques')
                for remolque in remolques:
                    ET.SubElement(node_remolques, f'{prefix}:Remolque', remolque)

            if tiposfigura:
                sub_node = ET.SubElement(node_carta, f'{prefix}:FiguraTransporte')
                for figura in tiposfigura:
                    ET.SubElement(sub_node, f'{prefix}:TiposFigura', figura)

        if self._divisas:
            atributos = {
                'version': SAT['divisas']['version'],
                'tipoOperacion': self._divisas,
            }
            ET.SubElement(self._complemento, 'divisas:Divisas', atributos)

        if 'ine' in datos:
            atributos = {'Version': SAT['ine']['version']}
            ine_key_entidad = datos['ine'].pop('ClaveEntidad', '')
            ine_ambito = datos['ine'].pop('Ambito', '')
            if ine_key_entidad:
                ine_id_conta = datos['ine'].pop('IdContabilidad', '')
            atributos.update(datos['ine'])
            node_ine = ET.SubElement(self._complemento, 'ine:INE', atributos)
            if ine_key_entidad:
                attr = {'ClaveEntidad': ine_key_entidad}
                if ine_ambito:
                    attr['Ambito'] = ine_ambito
                node_entidad = ET.SubElement(node_ine, 'ine:Entidad', attr)
                attr = {'IdContabilidad': ine_id_conta}
                ET.SubElement(node_entidad, 'ine:Contabilidad', attr)

        if 'pagos' in datos:
            datos = datos.pop('pagos')
            totales = datos.pop('totales')
            relacionados = datos.pop('relacionados')
            taxes_pay = datos.pop('taxes_pay')
            pre = SAT['pagos']['prefix']

            attributes = {'Version': SAT['pagos']['version']}
            pagos = ET.SubElement(
                self._complemento, '{}:Pagos'.format(pre), attributes)

            ET.SubElement(pagos, '{}:Totales'.format(pre), totales)

            node_pago = ET.SubElement(pagos, '{}:Pago'.format(pre), datos)
            for row in relacionados:
                taxes = row.pop('taxes')
                node = ET.SubElement(node_pago, f'{pre}:DoctoRelacionado', row)
                nodex_tax = None
                if taxes['traslados'] or taxes['retenciones']:
                    node_tax = ET.SubElement(node, f'{pre}:ImpuestosDR')
                if taxes['retenciones']:
                    node = ET.SubElement(node_tax, f'{pre}:RetencionesDR')
                    for tax in taxes['retenciones']:
                        ET.SubElement(node, f'{pre}:RetencionDR', tax)
                if taxes['traslados']:
                    node = ET.SubElement(node_tax, f'{pre}:TrasladosDR')
                    for tax in taxes['traslados']:
                        ET.SubElement(node, f'{pre}:TrasladoDR', tax)

            if taxes_pay['traslados'] or taxes_pay['retenciones']:
                node_tax = ET.SubElement(node_pago, f'{pre}:ImpuestosP')
            if taxes_pay['retenciones']:
                node = ET.SubElement(node_tax, f'{pre}:RetencionesP')
                for key, importe in taxes_pay['retenciones'].items():
                    attr = {'ImpuestoP': key, 'ImporteP': importe}
                    ET.SubElement(node, f'{pre}:RetencionP', attr)
            if taxes_pay['traslados']:
                node = ET.SubElement(node_tax, f'{pre}:TrasladosP')
                for key, tax in taxes_pay['traslados'].items():
                    ET.SubElement(node, f'{pre}:TrasladoP', tax)

        if 'leyendas' in datos:
            pre = SAT['leyendas']['prefix']
            attributes = {'version': SAT['leyendas']['version']}
            node_leyend = ET.SubElement(
                self._complemento, '{}:LeyendasFiscales'.format(pre), attributes)
            for leyend in datos['leyendas']:
                ET.SubElement(node_leyend, '{}:Leyenda'.format(pre), leyend)

        if self._comercio_exterior:
            datos = datos.pop('comercioe')
            self._complemento_comercio_exterior(datos)

        return
