Complemento para incluir al Comprobante Fiscal Digital por Internet (CFDI) la información sobre el
manejo de la enajenación de obras de artes plásticas y antigüedades.
| Nombre | Documento | Versión |
|---|---|---|
| Estándar | 📥 Descargar | 1.0 Revisión A |
| Esquema (XSD) | 📥 Descargar | 1.0 Revisión A |
| Secuencia Cadena Original (XSLT) | 📥 Descargar | 1.0 Revisión A |
| Catálogos | 📥 Descargar | 1.0 Revisión A |
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4"
xmlns:obrasarte="http://www.sat.gob.mx/arteantiguedades"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd http://www.sat.gob.mx/arteantiguedades http://www.sat.gob.mx/sitio_internet/cfd/arteantiguedades/obrasarteantiguedades.xsd" Certificado="MIIFsDCCA5igAwIBAgIUMzAwMDEwMDAwMDA1MDAwMDM0MTYwDQYJKoZIhvcNAQELBQAwggErMQ8wDQYDVQQDDAZBQyBVQVQxLjAsBgNVBAoMJVNFUlZJQ0lPIERFIEFETUlOSVNUUkFDSU9OIFRSSUJVVEFSSUExGjAYBgNVBAsMEVNBVC1JRVMgQXV0aG9yaXR5MSgwJgYJKoZIhvcNAQkBFhlvc2Nhci5tYXJ0aW5lekBzYXQuZ29iLm14MR0wGwYDVQQJDBQzcmEgY2VycmFkYSBkZSBjYWxpejEOMAwGA1UEEQwFMDYzNzAxCzAJBgNVBAYTAk1YMRkwFwYDVQQIDBBDSVVEQUQgREUgTUVYSUNPMREwDwYDVQQHDAhDT1lPQUNBTjERMA8GA1UELRMIMi41LjQuNDUxJTAjBgkqhkiG9w0BCQITFnJlc3BvbnNhYmxlOiBBQ0RNQS1TQVQwHhcNMjMwNTE4MTE0MzUxWhcNMjcwNTE4MTE0MzUxWjCB1zEnMCUGA1UEAxMeRVNDVUVMQSBLRU1QRVIgVVJHQVRFIFNBIERFIENWMScwJQYDVQQpEx5FU0NVRUxBIEtFTVBFUiBVUkdBVEUgU0EgREUgQ1YxJzAlBgNVBAoTHkVTQ1VFTEEgS0VNUEVSIFVSR0FURSBTQSBERSBDVjElMCMGA1UELRMcRUtVOTAwMzE3M0M5IC8gVkFEQTgwMDkyN0RKMzEeMBwGA1UEBRMVIC8gVkFEQTgwMDkyN0hTUlNSTDA1MRMwEQYDVQQLEwpTdWN1cnNhbCAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtmecO6n2GS0zL025gbHGQVxznPDICoXzR2uUngz4DqxVUC/w9cE6FxSiXm2ap8Gcjg7wmcZfm85EBaxCx/0J2u5CqnhzIoGCdhBPuhWQnIh5TLgj/X6uNquwZkKChbNe9aeFirU/JbyN7Egia9oKH9KZUsodiM/pWAH00PCtoKJ9OBcSHMq8Rqa3KKoBcfkg1ZrgueffwRLws9yOcRWLb02sDOPzGIm/jEFicVYt2Hw1qdRE5xmTZ7AGG0UHs+unkGjpCVeJ+BEBn0JPLWVvDKHZAQMj6s5Bku35+d/MyATkpOPsGT/VTnsouxekDfikJD1f7A1ZpJbqDpkJnss3vQIDAQABox0wGzAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIGwDANBgkqhkiG9w0BAQsFAAOCAgEAFaUgj5PqgvJigNMgtrdXZnbPfVBbukAbW4OGnUhNrA7SRAAfv2BSGk16PI0nBOr7qF2mItmBnjgEwk+DTv8Zr7w5qp7vleC6dIsZFNJoa6ZndrE/f7KO1CYruLXr5gwEkIyGfJ9NwyIagvHHMszzyHiSZIA850fWtbqtythpAliJ2jF35M5pNS+YTkRB+T6L/c6m00ymN3q9lT1rB03YywxrLreRSFZOSrbwWfg34EJbHfbFXpCSVYdJRfiVdvHnewN0r5fUlPtR9stQHyuqewzdkyb5jTTw02D2cUfL57vlPStBj7SEi3uOWvLrsiDnnCIxRMYJ2UA2ktDKHk+zWnsDmaeleSzonv2CHW42yXYPCvWi88oE1DJNYLNkIjua7MxAnkNZbScNw01A6zbLsZ3y8G6eEYnxSTRfwjd8EP4kdiHNJftm7Z4iRU7HOVh79/lRWB+gd171s3d/mI9kte3MRy6V8MMEMCAnMboGpaooYwgAmwclI2XZCczNWXfhaWe0ZS5PmytD/GDpXzkX0oEgY9K/uYo5V77NdZbGAjmyi8cE2B2ogvyaN2XfIInrZPgEffJ4AB7kFA2mwesdLOCh0BLD9itmCve3A1FGR4+stO2ANUoiI3w3Tv2yQSg4bjeDlJ08lXaaFCLW2peEXMXjQUk7fmpb5MNuOUTW6BE=" CondicionesDePago="Condiciones" Exportacion="01" Fecha="2026-03-24T08:39:12" Folio="1111111" FormaPago="02" LugarExpedicion="26000" MetodoPago="PUE" Moneda="MXN" NoCertificado="30001000000500003416" Sello="cGgnINSood7Dry6JjYMxtIjRjQyeCfJ6GIoi9rXC3sJePilVxAWuBByVFjeHCj54RqL5IRI7kBo1PyF+WNphdKprYqjDg5BDvCsO9H3mebs/KeyKxPz2sby/XRtOKysjggzaPgUFPDTX4cxDmHUDxqLzUNgM5TrRSITSBTvZvLsgatoY1L4WKpfmqWHt29vuWwSGVJqfl/WGT3uwT4mY2x5s3iVXawTg4fmsKoUiNz2Vhr/3W3ti3W6VOkVTnZTNJm3eVVKikiIRYTWBVAbTpklTP87wNiU0eHlPHKSDjNeJoeJheWj1r/NMfv5aduygxkthefDo7T6gHd+XY2FXqQ==" SubTotal="1000.00" TipoDeComprobante="E" Total="1160.00" Version="4.0" >
<cfdi:Emisor Nombre="ESCUELA KEMPER URGATE" RegimenFiscal="601" Rfc="EKU9003173C9"/>
<cfdi:Receptor DomicilioFiscalReceptor="57740" Nombre="COMERCIALIZADORA TEODORIKAS" RegimenFiscalReceptor="601" Rfc="CTE950627K46" UsoCFDI="G03"/>
<cfdi:Conceptos>
<cfdi:Concepto Cantidad="1.00" ClaveProdServ="93141703" ClaveUnidad="H87" Descripcion="PLATOS" Importe="1000.00" NoIdentificacion="00001" ObjetoImp="02" Unidad="PIEZA" ValorUnitario="1000.00">
<cfdi:Impuestos>
<cfdi:Traslados>
<cfdi:Traslado Base="1000.00" Importe="160.00" Impuesto="002" TasaOCuota="0.160000" TipoFactor="Tasa"/>
</cfdi:Traslados>
</cfdi:Impuestos>
</cfdi:Concepto>
</cfdi:Conceptos>
<cfdi:Impuestos TotalImpuestosTrasladados="160.00">
<cfdi:Traslados>
<cfdi:Traslado Base="1000.00" Importe="160.00" Impuesto="002" TasaOCuota="0.160000" TipoFactor="Tasa"/>
</cfdi:Traslados>
</cfdi:Impuestos>
<cfdi:Complemento>
<obrasarte:obrasarteantiguedades CaracterísticasDeObraoPieza="01" FechaAdquisicion="2025-01-21" IVA="160.00" Subtotal="1000.00" TipoBien="01" TituloAdquirido="01" Version="1.0"/>
</cfdi:Complemento>
</cfdi:Comprobante>
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import base64
import hashlib
from datetime import datetime
from lxml import etree
import xml.etree.ElementTree as ET
from M2Crypto.RSA import load_key_string
class CFDI40Generator:
def __init__(self):
# ===== RUTAS =====
self.base_path = "/ruta/CrearCFDI4.0"
self.xml_dir = "/ruta/CrearCFDI4.0/XML"
self.xml_path = os.path.join(self.xml_dir, "ObrasArte.xml")
self.cer_path = os.path.join(self.base_path, "CSD/EKU9003173C9.cer")
self.key_path = os.path.join(self.base_path, "CSD/EKU9003173C9Key.pem")
self.xslt_path = os.path.join(self.base_path, "XSLT/cadenaoriginal_4_0.xslt")
os.makedirs(self.xml_dir, exist_ok=True)
self.generar_xml()
self.cadena_original = self.generar_cadena_original()
self.sello = self.generar_sello(self.cadena_original)
self.agregar_sello()
# =========================================
# CERTIFICADO
# =========================================
def obtener_certificado(self):
with open(self.cer_path, "rb") as f:
cert_data = f.read()
cert_b64 = base64.b64encode(cert_data).decode()
os.system(f'openssl x509 -inform DER -in "{self.cer_path}" -noout -serial > serial.txt')
with open("serial.txt") as f:
serial = f.read()
no_certificado = serial[::2][4:]
return cert_b64, no_certificado
# =========================================
# GENERAR XML
# =========================================
def generar_xml(self):
cert_b64, no_certificado = self.obtener_certificado()
# Namespaces
ET.register_namespace('cfdi', "http://www.sat.gob.mx/cfd/4")
ET.register_namespace('obrasarte', "http://www.sat.gob.mx/arteantiguedades")
namespace = {
'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance",
'xsi:schemaLocation':
"http://www.sat.gob.mx/cfd/4 "
"http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd "
"http://www.sat.gob.mx/arteantiguedades "
"http://www.sat.gob.mx/sitio_internet/cfd/arteantiguedades/obrasarteantiguedades.xsd"
}
Comprobante = ET.Element(
"{http://www.sat.gob.mx/cfd/4}Comprobante",
namespace,
Version="4.0",
Serie="Serie",
Folio="renovacion",
Fecha="2024-03-01T00:08:10",
SubTotal="200",
Descuento="1",
Moneda="MXN",
TipoCambio="1",
Total="230.84",
TipoDeComprobante="I",
Exportacion="01",
MetodoPago="PPD",
FormaPago="99",
LugarExpedicion="20000",
NoCertificado=no_certificado,
Certificado=cert_b64
)
# =====================
# EMISOR
# =====================
ET.SubElement(
Comprobante,
"{http://www.sat.gob.mx/cfd/4}Emisor",
Rfc="EKU9003173C9",
Nombre="ESCUELA KEMPER URGATE",
RegimenFiscal="601"
)
# =====================
# RECEPTOR
# =====================
ET.SubElement(
Comprobante,
"{http://www.sat.gob.mx/cfd/4}Receptor",
Rfc="URE180429TM6",
Nombre="UNIVERSIDAD ROBOTICA ESPAÑOLA",
DomicilioFiscalReceptor="86991",
RegimenFiscalReceptor="601",
UsoCFDI="G01"
)
# =====================
# CONCEPTOS
# =====================
conceptos = ET.SubElement(
Comprobante,
"{http://www.sat.gob.mx/cfd/4}Conceptos"
)
concepto = ET.SubElement(
conceptos,
"{http://www.sat.gob.mx/cfd/4}Concepto",
ClaveProdServ="50211503",
Cantidad="1",
ClaveUnidad="H87",
Unidad="Pieza",
Descripcion="Arte",
ValorUnitario="200.00",
Descuento="1",
Importe="200.00",
ObjetoImp="02"
)
# Impuestos concepto
impuestos = ET.SubElement(
concepto,
"{http://www.sat.gob.mx/cfd/4}Impuestos"
)
traslados = ET.SubElement(
impuestos,
"{http://www.sat.gob.mx/cfd/4}Traslados"
)
ET.SubElement(
traslados,
"{http://www.sat.gob.mx/cfd/4}Traslado",
Base="199",
Importe="31.84",
Impuesto="002",
TasaOCuota="0.160000",
TipoFactor="Tasa"
)
# =====================
# IMPUESTOS CFDI
# =====================
impuestos_cfdi = ET.SubElement(
Comprobante,
"{http://www.sat.gob.mx/cfd/4}Impuestos",
TotalImpuestosTrasladados="31.84"
)
tras = ET.SubElement(
impuestos_cfdi,
"{http://www.sat.gob.mx/cfd/4}Traslados"
)
ET.SubElement(
tras,
"{http://www.sat.gob.mx/cfd/4}Traslado",
Base="199",
Importe="31.84",
Impuesto="002",
TasaOCuota="0.160000",
TipoFactor="Tasa"
)
# =====================
# COMPLEMENTO (OBRAS DE ARTE)
# =====================
complemento = ET.SubElement(
Comprobante,
"{http://www.sat.gob.mx/cfd/4}Complemento"
)
ET.SubElement(
complemento,
"{http://www.sat.gob.mx/arteantiguedades}obrasarteantiguedades",
Version="1.0",
TipoBien="01",
OtrosTipoBien="NA",
TituloAdquirido="01",
OtrosTituloAdquirido="NA",
Subtotal="100.00",
IVA="16.00",
FechaAdquisicion="2022-07-24",
**{"CaracterísticasDeObraoPieza": "01"}
)
tree = ET.ElementTree(Comprobante)
tree.write(self.xml_path, encoding="utf-8", xml_declaration=True)
print("XML generado correctamente:")
print(self.xml_path)
# =========================================
# CADENA ORIGINAL
# =========================================
def generar_cadena_original(self):
xml = etree.parse(self.xml_path)
xslt = etree.parse(self.xslt_path)
transform = etree.XSLT(xslt)
cadena = transform(xml)
cadena = str(cadena).replace("\n", "").strip()
print("Cadena original:")
print(cadena)
return cadena
# =========================================
# SELLO
# =========================================
def generar_sello(self, cadena):
with open(self.key_path) as f:
key = f.read()
rsa = load_key_string(key.encode())
digest = hashlib.sha256(cadena.encode()).digest()
firma = rsa.sign(digest, "sha256")
sello = base64.b64encode(firma).decode()
return sello
# =========================================
# INSERTAR SELLO
# =========================================
def agregar_sello(self):
tree = ET.parse(self.xml_path)
root = tree.getroot()
root.set("Sello", self.sello)
tree.write(self.xml_path, encoding="utf-8", xml_declaration=True)
print("XML sellado correctamente")
print(self.xml_path)
if __name__ == "__main__":
CFDI40Generator()
<?php
date_default_timezone_set("America/Mexico_City");
error_reporting(E_ALL);
// ================= RUTAS =================
$rutaCer = "/ruta/certificado.cer";
$rutaKey = "/ruta/llave.key.pem";
$rutaXML = "/ruta/xml/obrasarte.xml";
$rutaCadena= "/ruta/xml/cadena.txt";
$rutaXSLT = "/ruta/xslt/cadenaoriginal_4_0.xslt";
// ================= CERT =================
$certDer = file_get_contents($rutaCer);
$certificado = base64_encode($certDer);
$certPem = "-----BEGIN CERTIFICATE-----\n".
chunk_split(base64_encode($certDer), 64, "\n").
"-----END CERTIFICATE-----\n";
$parsed = openssl_x509_parse($certPem);
$noCertificado = $parsed['serialNumber'];
// ================= XML =================
$xml = new DOMDocument("1.0", "UTF-8");
$xml->formatOutput = true;
$cfdi = $xml->createElementNS("http://www.sat.gob.mx/cfd/4", "cfdi:Comprobante");
$xml->appendChild($cfdi);
// Namespaces
$cfdi->setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
$cfdi->setAttribute("xmlns:obrasarte", "http://www.sat.gob.mx/arteantiguedades");
// SchemaLocation
$cfdi->setAttribute("xsi:schemaLocation",
"http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd ".
"http://www.sat.gob.mx/arteantiguedades http://www.sat.gob.mx/sitio_internet/cfd/arteantiguedades/obrasarteantiguedades.xsd"
);
// ================= ATRIBUTOS =================
$cfdi->setAttribute("Version", "4.0");
$cfdi->setAttribute("Serie", "Serie");
$cfdi->setAttribute("Folio", "renovacion");
$cfdi->setAttribute("Fecha", "2026-03-31T00:08:10");
$cfdi->setAttribute("SubTotal", "200");
$cfdi->setAttribute("Descuento", "1");
$cfdi->setAttribute("Total", "230.84");
$cfdi->setAttribute("Moneda", "MXN");
$cfdi->setAttribute("TipoCambio", "1");
$cfdi->setAttribute("TipoDeComprobante", "I");
$cfdi->setAttribute("MetodoPago", "PPD");
$cfdi->setAttribute("FormaPago", "99");
$cfdi->setAttribute("LugarExpedicion", "20000");
$cfdi->setAttribute("Exportacion", "01");
$cfdi->setAttribute("CondicionesDePago", "CondicionesDePago");
$cfdi->setAttribute("NoCertificado", $noCertificado);
$cfdi->setAttribute("Certificado", $certificado);
// ================= EMISOR =================
$emisor = $xml->createElement("cfdi:Emisor");
$emisor->setAttribute("Rfc", "EKU9003173C9");
$emisor->setAttribute("Nombre", "ESCUELA KEMPER URGATE");
$emisor->setAttribute("RegimenFiscal", "601");
$cfdi->appendChild($emisor);
// ================= RECEPTOR =================
$receptor = $xml->createElement("cfdi:Receptor");
$receptor->setAttribute("Rfc", "URE180429TM6");
$receptor->setAttribute("Nombre", "UNIVERSIDAD ROBOTICA ESPAÑOLA");
$receptor->setAttribute("DomicilioFiscalReceptor", "86991");
$receptor->setAttribute("RegimenFiscalReceptor", "601");
$receptor->setAttribute("UsoCFDI", "G01");
$cfdi->appendChild($receptor);
// ================= CONCEPTO =================
$conceptos = $xml->createElement("cfdi:Conceptos");
$c = $xml->createElement("cfdi:Concepto");
$c->setAttribute("ClaveProdServ", "50211503");
$c->setAttribute("Cantidad", "1");
$c->setAttribute("ClaveUnidad", "H87");
$c->setAttribute("Unidad", "Pieza");
$c->setAttribute("Descripcion", "Arte");
$c->setAttribute("ValorUnitario", "200.00");
$c->setAttribute("Importe", "200.00");
$c->setAttribute("Descuento", "1");
$c->setAttribute("ObjetoImp", "02");
// Impuestos concepto
$imp = $xml->createElement("cfdi:Impuestos");
$trs = $xml->createElement("cfdi:Traslados");
$t = $xml->createElement("cfdi:Traslado");
$t->setAttribute("Base", "199");
$t->setAttribute("Impuesto", "002");
$t->setAttribute("TipoFactor", "Tasa");
$t->setAttribute("TasaOCuota", "0.160000");
$t->setAttribute("Importe", "31.84");
$trs->appendChild($t);
$imp->appendChild($trs);
$c->appendChild($imp);
$conceptos->appendChild($c);
$cfdi->appendChild($conceptos);
// ================= IMPUESTOS =================
$impTot = $xml->createElement("cfdi:Impuestos");
$impTot->setAttribute("TotalImpuestosTrasladados", "31.84");
$trs2 = $xml->createElement("cfdi:Traslados");
$trs2->appendChild($t->cloneNode(true));
$impTot->appendChild($trs2);
$cfdi->appendChild($impTot);
// ================= COMPLEMENTO =================
$complemento = $xml->createElement("cfdi:Complemento");
$obra = $xml->createElementNS("http://www.sat.gob.mx/arteantiguedades", "obrasarte:obrasarteantiguedades");
$obra->setAttribute("Version", "1.0");
$obra->setAttribute("TipoBien", "01");
$obra->setAttribute("OtrosTipoBien", "NA");
$obra->setAttribute("TituloAdquirido", "01");
$obra->setAttribute("OtrosTituloAdquirido", "NA");
$obra->setAttribute("Subtotal", "100.00");
$obra->setAttribute("IVA", "16.00");
$obra->setAttribute("FechaAdquisicion", "2022-07-24");
$obra->setAttribute("CaracterísticasDeObraoPieza", "01");
$complemento->appendChild($obra);
$cfdi->appendChild($complemento);
// ================= CADENA ORIGINAL =================
$xsl = new DOMDocument();
$xsl->load($rutaXSLT);
$proc = new XSLTProcessor();
$proc->importStylesheet($xsl);
$cadena = trim($proc->transformToXML($xml));
file_put_contents($rutaCadena, $cadena);
// ================= SELLO =================
$privKey = openssl_pkey_get_private(file_get_contents($rutaKey));
openssl_sign($cadena, $sello, $privKey, OPENSSL_ALGO_SHA256);
$cfdi->setAttribute("Sello", base64_encode($sello));
// ================= GUARDAR =================
$xml->save($rutaXML);
echo "CFDI Obras de arte generado\n";
?>