Complemento opcional para incorporar en el CFDI los datos de identificación, tránsito y transporte de turistas o pasajeros extranjeros. Es utilizado principalmente por prestadores de servicios de transporte y turismo para documentar operaciones con no residentes.
Este complemento permite desglosar la siguiente información dentro de la factura:
Enlace directo al sitio del SAT: liga
| Nombre | Documento |
|---|---|
| Estándar del complemento | 📥 Descargar |
| Esquema XSD | 📥 Descargar |
| Utilería XSLT | 📥 Descargar |
<?xml version="1.0" encoding="utf-8"?>
<cfdi:Comprobante xmlns:tpe="http://www.sat.gob.mx/TuristaPasajeroExtranjero" 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/TuristaPasajeroExtranjero http://www.sat.gob.mx/sitio_internet/cfd/TuristaPasajeroExtranjero/TuristaPasajeroExtranjero.xsd" Version="4.0" Fecha="2026-03-24T00:00:10" Sello="rafFfJxCwGdftaFtGmTtjdpTYPS4LQDQaegmDzexM1AzAYJB9sRZicSTMlZ2gHmICzNlICNsCaCYvr9y9v747nRHlC/HXrz2kt/xVitBwmR9Ac5oOAvBJg2yOLknDF9F7NutdJCUpAnZw3Jvvw/aDHXtWdxLsT1dDyr/URVJWcSzvOzYO0NBFVrWhqJvuTjGI761Pf82pg2fKM6McoeblZvXIGkCW3RZzkZmb06TC59LLEsWHLqKgfCmiAdbHul+hp4AsoHFlr+oLRbbo4/wUab9po56POXU3+nPSVlOyJ190ekeXzvOAI+ADXQxdTuh4P5PT3FsyOFq26Z1GZ017Q==" CondicionesDePago="Condiciones" SubTotal="100.00" Descuento="1" Moneda="MXN" TipoCambio="1" Total="115.00" TipoDeComprobante="I" Exportacion="01" MetodoPago="PPD" FormaPago="99" LugarExpedicion="41000" xmlns:cfdi="http://www.sat.gob.mx/cfd/4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" NoCertificado="30001000000500003416" 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=">
<cfdi:Emisor Rfc="EKU9003173C9" Nombre="ESCUELA KEMPER URGATE" RegimenFiscal="601" />
<cfdi:Receptor Rfc="XIQB891116QE4" Nombre="BERENICE XIMO QUEZADA" DomicilioFiscalReceptor="21855" RegimenFiscalReceptor="616" UsoCFDI="S01" />
<cfdi:Conceptos>
<cfdi:Concepto ClaveProdServ="50211503" Cantidad="1" ClaveUnidad="H87" Unidad="Pieza" Descripcion="Viaje" ValorUnitario="100.00" Descuento="1" Importe="100.00" ObjetoImp="02">
<cfdi:Impuestos>
<cfdi:Traslados>
<cfdi:Traslado Base="100.00" Importe="16.00" Impuesto="002" TasaOCuota="0.160000" TipoFactor="Tasa" />
</cfdi:Traslados>
</cfdi:Impuestos>
</cfdi:Concepto>
</cfdi:Conceptos>
<cfdi:Impuestos TotalImpuestosTrasladados="16.00">
<cfdi:Traslados>
<cfdi:Traslado Base="100.00" Importe="16.00" Impuesto="002" TasaOCuota="0.160000" TipoFactor="Tasa" />
</cfdi:Traslados>
</cfdi:Impuestos>
<cfdi:Complemento>
<tpe:TuristaPasajeroExtranjero version="1.0" fechadeTransito="2025-01-01T00:00:00" tipoTransito="Salida">
<tpe:datosTransito Via="Terrestre" TipoId="789456123" NumeroId="789456123" Nacionalidad="MEXICANA" EmpresaTransporte="AUTOBUSES" IdTransporte="EQ" />
</tpe:TuristaPasajeroExtranjero>
</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/Python/cfdi40 y compl/OtrosEjemplos/XML"
self.xml_path = os.path.join(self.xml_dir, "TuristaPasajeroExtranjero.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()
# =========================================
# OBTENER 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
# =========================================
# CREAR XML CFDI
# =========================================
def generar_xml(self):
certificado_base64, no_certificado = self.obtener_certificado()
ET.register_namespace('cfdi', "http://www.sat.gob.mx/cfd/4")
ET.register_namespace('tpe', "http://www.sat.gob.mx/TuristaPasajeroExtranjero")
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/TuristaPasajeroExtranjero "
"http://www.sat.gob.mx/sitio_internet/cfd/TuristaPasajeroExtranjero/TuristaPasajeroExtranjero.xsd"
}
Comprobante = ET.Element(
"{http://www.sat.gob.mx/cfd/4}Comprobante",
namespace,
Version="4.0",
Serie="A",
Folio="167ABC",
Fecha=datetime.now().replace(microsecond=0).isoformat('T'),
SubTotal="1000.00",
Total="1120.00",
Moneda="MXN",
TipoCambio="1",
TipoDeComprobante="I",
LugarExpedicion="58000",
CondicionesDePago="CONDICIONES",
FormaPago="03",
MetodoPago="PUE",
Exportacion="01",
NoCertificado=no_certificado,
Certificado=certificado_base64
)
# =====================
# 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="ICV060329BY0",
Nombre="INMOBILIARIA CVA",
UsoCFDI="G03",
RegimenFiscalReceptor="601",
DomicilioFiscalReceptor="33826"
)
# =====================
# 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",
Cantidad="1.00",
ClaveProdServ="78101802",
ClaveUnidad="E48",
Descripcion="servicios de transporte",
Unidad="Servicio",
ValorUnitario="1000.00",
Importe="1000.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="1000.00",
Impuesto="002",
TipoFactor="Tasa",
TasaOCuota="0.160000",
Importe="160.00"
)
retenciones = ET.SubElement(
impuestos,
"{http://www.sat.gob.mx/cfd/4}Retenciones"
)
ET.SubElement(
retenciones,
"{http://www.sat.gob.mx/cfd/4}Retencion",
Base="1000.00",
Impuesto="002",
TipoFactor="Tasa",
TasaOCuota="0.040000",
Importe="40.00"
)
# =====================
# IMPUESTOS COMPROBANTE
# =====================
impuestos_comprobante = ET.SubElement(
Comprobante,
"{http://www.sat.gob.mx/cfd/4}Impuestos",
TotalImpuestosRetenidos="40.00",
TotalImpuestosTrasladados="160.00"
)
ret = ET.SubElement(
impuestos_comprobante,
"{http://www.sat.gob.mx/cfd/4}Retenciones"
)
ET.SubElement(
ret,
"{http://www.sat.gob.mx/cfd/4}Retencion",
Impuesto="002",
Importe="40.00"
)
tra = ET.SubElement(
impuestos_comprobante,
"{http://www.sat.gob.mx/cfd/4}Traslados"
)
ET.SubElement(
tra,
"{http://www.sat.gob.mx/cfd/4}Traslado",
Base="1000.00",
Impuesto="002",
TipoFactor="Tasa",
TasaOCuota="0.160000",
Importe="160.00"
)
# =====================
# COMPLEMENTO
# =====================
complemento = ET.SubElement(
Comprobante,
"{http://www.sat.gob.mx/cfd/4}Complemento"
)
turista = ET.SubElement(
complemento,
"{http://www.sat.gob.mx/TuristaPasajeroExtranjero}TuristaPasajeroExtranjero",
version="1.0",
fechadeTransito="2025-07-02T17:20:45",
tipoTransito="Salida"
)
ET.SubElement(
turista,
"{http://www.sat.gob.mx/TuristaPasajeroExtranjero}datosTransito",
Via="Aérea",
TipoId="832912830-E2341",
NumeroId="EGA93812273-PLM3821",
Nacionalidad="GRECIA",
EmpresaTransporte="IBERIA"
)
tree = ET.ElementTree(Comprobante)
tree.write(self.xml_path, encoding="utf-8", xml_declaration=True)
print("XML generado en:")
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
# =========================================
# GENERAR 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 & ~E_WARNING);
// ================= CONFIG =================
$config = [
"cer" => "/ruta/EKU9003173C9.cer",
"key" => "/EKU9003173C9Key.pem",
"xml" => "/ruta/tpe.xml",
"cadena"=> "/CadenaOriginal.txt",
"xslt" => "/cadenaoriginal_4_0.xslt"
];
// ================= FUNCIONES =================
function crearNodo($doc, $ns, $nombre, $attrs = [])
{
$nodo = $doc->createElementNS($ns, $nombre);
foreach ($attrs as $k => $v) {
$nodo->setAttribute($k, $v);
}
return $nodo;
}
function obtenerCert($ruta)
{
$raw = file_get_contents($ruta);
$pem = "-----BEGIN CERTIFICATE-----\n" .
chunk_split(base64_encode($raw), 64, "\n") .
"-----END CERTIFICATE-----\n";
$data = openssl_x509_parse($pem);
$serial = null;
if ($data && !empty($data['serialNumberHex'])) {
$hex = preg_replace('/\s+/', '', $data['serialNumberHex']);
$serial = strtoupper(hex2bin($hex));
}
return [
"cert" => base64_encode($raw),
"noCert" => $serial
];
}
// ================= CERT =================
$cert = obtenerCert($config["cer"]);
$ns = "http://www.sat.gob.mx/cfd/4";
$nsTPE = "http://www.sat.gob.mx/TuristaPasajeroExtranjero";
// ================= XML =================
$xml = new DOMDocument("1.0", "UTF-8");
$xml->formatOutput = true;
// Comprobante
$comp = $xml->createElementNS($ns, "cfdi:Comprobante");
$xml->appendChild($comp);
// Namespaces
$comp->setAttribute("xmlns:cfdi", $ns);
$comp->setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
$comp->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:tpe", $nsTPE);
$comp->setAttribute("xsi:schemaLocation",
"$ns http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd " .
"$nsTPE http://www.sat.gob.mx/sitio_internet/cfd/TuristaPasajeroExtranjero/TuristaPasajeroExtranjero.xsd"
);
// ================= ATRIBUTOS CFDI =================
$attrs = [
"Version"=>"4.0",
"Serie"=>"A",
"Folio"=>"167ABC",
"Fecha"=>date('Y-m-d\TH:i:s'),
"SubTotal"=>"1000.00",
"Total"=>"1120.00",
"Moneda"=>"MXN",
"TipoCambio"=>"1",
"TipoDeComprobante"=>"I",
"LugarExpedicion"=>"58000",
"CondicionesDePago"=>"CONDICIONES",
"FormaPago"=>"03",
"MetodoPago"=>"PUE",
"Exportacion"=>"01",
"Certificado"=>$cert["cert"]
];
foreach ($attrs as $k=>$v) $comp->setAttribute($k,$v);
if ($cert["noCert"]) $comp->setAttribute("NoCertificado",$cert["noCert"]);
// ================= EMISOR =================
$comp->appendChild(crearNodo($xml,$ns,"cfdi:Emisor",[
"Rfc"=>"EKU9003173C9",
"Nombre"=>"ESCUELA KEMPER URGATE",
"RegimenFiscal"=>"601"
]));
// ================= RECEPTOR =================
$comp->appendChild(crearNodo($xml,$ns,"cfdi:Receptor",[
"Rfc"=>"ICV060329BY0",
"Nombre"=>"INMOBILIARIA CVA",
"UsoCFDI"=>"G03",
"RegimenFiscalReceptor"=>"601",
"DomicilioFiscalReceptor"=>"33826"
]));
// ================= CONCEPTOS =================
$conceptos = crearNodo($xml,$ns,"cfdi:Conceptos");
$concepto = crearNodo($xml,$ns,"cfdi:Concepto",[
"Cantidad"=>"1.00",
"ClaveProdServ"=>"78101802",
"ClaveUnidad"=>"E48",
"Unidad"=>"Servicio",
"Descripcion"=>"servicios de transporte",
"ValorUnitario"=>"1000.00",
"Importe"=>"1000.00",
"ObjetoImp"=>"02"
]);
$imp = crearNodo($xml,$ns,"cfdi:Impuestos");
// Traslados
$tras = crearNodo($xml,$ns,"cfdi:Traslados");
$tras->appendChild(crearNodo($xml,$ns,"cfdi:Traslado",[
"Base"=>"1000.00",
"Impuesto"=>"002",
"TipoFactor"=>"Tasa",
"TasaOCuota"=>"0.160000",
"Importe"=>"160.00"
]));
// Retenciones
$ret = crearNodo($xml,$ns,"cfdi:Retenciones");
$ret->appendChild(crearNodo($xml,$ns,"cfdi:Retencion",[
"Base"=>"1000.00",
"Impuesto"=>"002",
"TipoFactor"=>"Tasa",
"TasaOCuota"=>"0.040000",
"Importe"=>"40.00"
]));
$imp->appendChild($tras);
$imp->appendChild($ret);
$concepto->appendChild($imp);
$conceptos->appendChild($concepto);
$comp->appendChild($conceptos);
// ================= IMPUESTOS GLOBALES =================
$impT = crearNodo($xml,$ns,"cfdi:Impuestos",[
"TotalImpuestosRetenidos"=>"40.00",
"TotalImpuestosTrasladados"=>"160.00"
]);
$retT = crearNodo($xml,$ns,"cfdi:Retenciones");
$retT->appendChild(crearNodo($xml,$ns,"cfdi:Retencion",[
"Impuesto"=>"002",
"Importe"=>"40.00"
]));
$trasT = crearNodo($xml,$ns,"cfdi:Traslados");
$trasT->appendChild(crearNodo($xml,$ns,"cfdi:Traslado",[
"Base"=>"1000.00",
"Impuesto"=>"002",
"TipoFactor"=>"Tasa",
"TasaOCuota"=>"0.160000",
"Importe"=>"160.00"
]));
$impT->appendChild($retT);
$impT->appendChild($trasT);
$comp->appendChild($impT);
// ================= COMPLEMENTO =================
$complemento = $xml->createElementNS($ns, "cfdi:Complemento");
$comp->appendChild($complemento);
// Nodo principal TPE
$tpe = $xml->createElementNS($nsTPE, "tpe:TuristaPasajeroExtranjero");
$tpe->setAttribute("version","1.0"); // ojo: minúscula
$tpe->setAttribute("fechadeTransito","2025-07-02T17:20:45");
$tpe->setAttribute("tipoTransito","Salida");
$complemento->appendChild($tpe);
// Nodo hijo
$datos = crearNodo($xml,$nsTPE,"tpe:datosTransito",[
"Via"=>"Aérea",
"TipoId"=>"832912830-E2341",
"NumeroId"=>"EGA93812273-PLM3821",
"Nacionalidad"=>"GRECIA",
"EmpresaTransporte"=>"IBERIA"
]);
$tpe->appendChild($datos);
// ================= CADENA =================
$xsl = new DOMDocument();
$xsl->load($config["xslt"]);
$proc = new XSLTProcessor();
$proc->importStylesheet($xsl);
$cadena = trim($proc->transformToXML($xml));
file_put_contents($config["cadena"], $cadena);
// ================= SELLO =================
$key = openssl_pkey_get_private(file_get_contents($config["key"]));
openssl_sign($cadena, $firma, $key, OPENSSL_ALGO_SHA256);
$comp->setAttribute("Sello", base64_encode($firma));
// ================= SAVE =================
$xml->save($config["xml"]);
echo "XML generado:\n{$config["xml"]}\n";
echo "Cadena:\n$cadena\n";