Complemento de Hidrocarburos en CFDI: cómo timbrar facturas de combustible sin que el SAT te las rechace (abril 2026) — Cesar Ayala
← Todos los artículos

Complemento de Hidrocarburos en CFDI: cómo timbrar facturas de combustible sin que el SAT te las rechace (abril 2026)

Desde el 24 de abril de 2026, todo CFDI 4.0 que facture gasolina o diésel debe llevar el Complemento de Hidrocarburos y Petrolíferos a nivel concepto, con cinco atributos: Version, TipoPermiso, NumeroPermiso, ClaveHYP y SubProductoHYP. El PAC cruza tu NumeroPermiso contra la lista L_CNE de la CNE, actualizada a diario, y rechaza el CFDI si el permiso no está o expiró. Las multas van de $17,000 a $97,000 MXN por comprobante (personas morales). Base legal: Regla 2.7.1.48, RMF 2026.

Qué cambió y por qué el 24 de abril de 2026 es una fecha sin prórroga

El 25 de marzo de 2026 el SAT publicó el Complemento de Hidrocarburos y Petrolíferos para CFDI 4.0, y entra en vigor el 24 de abril de 2026. No es una recomendación ni un periodo de gracia: desde esa fecha, todo CFDI 4.0 que facture gasolina o diésel (hidrocarburos y petrolíferos) tiene que llevar el complemento, o de plano no se timbra.

Esto sale de un esfuerzo conjunto del SAT, la SENER, la CNE y la ATDT, y el objetivo es claro: trazabilidad del combustible y cerrarle la puerta al huachicol fiscal. Para nosotros, los que construimos la facturación, lo que importa es el efecto operativo.

Las consecuencias si lo ignoras:

  • El PAC rechaza el CFDI automáticamente en el timbrado. No hay forma de “timbrar de todas formas y corregir después”.
  • Multas de $17,000 a $97,000 MXN por comprobante emitido sin el complemento después del 24 de abril (personas morales).
  • Un CFDI sin el complemento no se timbra, así que tu cliente se queda sin comprobante válido. Las implicaciones fiscales finas (deducibilidad, acreditamiento de IVA) confírmalas con un contador.

La base legal es la Regla 2.7.1.48 de la Resolución Miscelánea Fiscal 2026 (Anexo 29, DOF). Aclaración de ingeniero, no de contador: confirma siempre las reglas vigentes del SAT y deja los casos fiscales de frontera a un contador o a tu área de compliance. Aquí cubrimos la parte que sí te toca: que el XML salga bien y que el PAC no te lo regrese.

Si vienes llegando al mundo de CFDI 4.0 y timbrado por PAC, primero pasa por la base de facturación automática; este complemento se monta encima de ese flujo.

Dónde vive el complemento: a nivel concepto, no del comprobante

Aquí es donde la mayoría de los devs se equivoca. Este complemento no vive en la raíz del comprobante. Opera a nivel ComplementoConcepto, es decir, por cada línea o cfdi:Concepto que factura combustible.

Esto rompe el modelo mental que ya tienes de otros complementos. La Carta Porte y el de Pagos se enganchan en el cfdi:Complemento de la raíz del documento (uno por CFDI). Si quieres repasar cómo se ve un complemento a nivel comprobante, revisa la guía de desarrollador de Carta Porte. El de Hidrocarburos va más adentro: anidado dentro de cada concepto de combustible.

Consecuencias prácticas para tu builder de CFDI:

  • El bloque del complemento se inyecta dentro de cfdi:Concepto/cfdi:ComplementoConcepto, no en el root del documento.
  • Una factura con varias líneas de combustible necesita el bloque repetido por cada concepto. Si vendes gasolina regular y diésel en el mismo CFDI, son dos bloques, uno en cada concepto.
  • Si tu generador de XML solo sabe poner complementos en la raíz, tienes que extenderlo para que escriba a nivel concepto. Es el cambio de código más importante de toda esta historia.

Los cinco atributos obligatorios que debes llenar

El nodo de Hidrocarburos dentro del ComplementoConcepto lleva cinco atributos. Los cinco son obligatorios; si falta uno o viene mal formado, el CFDI se rechaza.

  • Version — la versión del complemento.
  • TipoPermiso — el tipo de permiso o actividad de la CNE que tiene el emisor.
  • NumeroPermiso — el número único de permiso de la CNE. Este es el campo que se cruza contra el catálogo L_CNE.
  • ClaveHYP — la clave del hidrocarburo o petrolífero (por ejemplo gasolina regular, gasolina premium, diésel).
  • SubProductoHYP — la clasificación de subproducto.

Para ClaveHYP y SubProductoHYP usa los valores de los catálogos del SAT, no texto libre. Si mandas un valor inventado o un código que no está en el catálogo, es causa de rechazo igual que si faltara el atributo. Trátalos como enums validados contra el catálogo, no como strings.

VersionVersión del complemento
TipoPermisoTipo de permiso CNE del emisor
NumeroPermisoNúmero de permiso CNE — se cruza vs L_CNE
ClaveHYPClave del hidrocarburo (catálogo SAT)
SubProductoHYPSubproducto (catálogo SAT)

El catálogo L_CNE: la trampa que se actualiza a diario y te rechaza

Este es el corazón operativo del post. En el timbrado, el PAC hace dos cosas: verifica que el emisor tenga un permiso de la CNE vigente y cruza tu NumeroPermiso contra la lista L_CNE.

L_CNE es un catálogo que la CNE (Comisión Nacional de Energía) publica y actualiza a diario. No es un catálogo estático que descargas una vez y guardas para siempre.

Las reglas del cruce:

  • Si el permiso no está en L_CNE (expiró, fue revocado, o trae un typo), el CFDI se rechaza.
  • El RFC del emisor debe coincidir con el titular del permiso. No basta con que el número exista; tiene que ser tuyo.
  • Un permiso que estaba válido ayer puede desaparecer de la lista hoy. Por eso no puedes hardcodear ni dejar el cache desactualizado.

El movimiento de ingeniero: cachea la lista L_CNE, refréscala a diario con un cron, y valida el permiso ANTES de llamar a timbrar. Así fallas rápido y en local, con un mensaje claro, en vez de comerte un rechazo del PAC que llega más lento, te gasta una llamada y te deja descifrando un código de error genérico.

Construir el conceptoArma cfdi:Concepto con la línea de combustible
Adjuntar HidrocarburosInyecta el bloque con los 5 atributos en ComplementoConcepto
Validar vs L_CNECruza NumeroPermiso contra el cache L_CNE (refrescado a diario) + RFC
PAC timbra o RECHAZASi pasa la validación local, timbra; si no, falla rápido y claro

Cómo construirlo: un concepto de CFDI con el complemento adjunto

Veamos el patrón real. Primero el XML del concepto con el complemento anidado. Es ilustrativo y vendor-neutral: funciona contra cualquier PAC porque es CFDI 4.0 estándar.

<!-- Ilustrativo: concepto de combustible con el complemento a nivel concepto -->
<!-- prefijo/namespace y nombre del nodo ilustrativos: usa el XSD oficial del complemento -->
<cfdi:Concepto
    ClaveProdServ="15101514"
    Cantidad="1000.000"
    ClaveUnidad="LTR"
    Descripcion="Gasolina regular menor a 92 octanos"
    ValorUnitario="22.50"
    Importe="22500.00"
    ObjetoImp="02">
  <cfdi:ComplementoConcepto>
    <!-- El nodo Hidrocarburos vive AQUÍ, dentro del concepto -->
    <hidrocarburos:Hidrocarburos
        Version="1.0"
        TipoPermiso="PER_EXP"
        NumeroPermiso="PL/12345/EXP/ES/2024"
        ClaveHYP="PR03"
        SubProductoHYP="03" />
  </cfdi:ComplementoConcepto>
</cfdi:Concepto>

Nota: todos los valores de ejemplo de arriba son ilustrativos. El prefijo y el nombre del nodo (hidrocarburos:Hidrocarburos) tómalos del esquema XSD oficial del SAT, no del que aparece aquí. ClaveHYP y SubProductoHYP salen de los catálogos del SAT. Y ojo: TipoPermiso es una clave corta del tipo de actividad CNE (no un folio), mientras que NumeroPermiso sí es el folio largo del permiso del emisor; los formatos exactos salen del catálogo y del permiso CNE de cada emisor. Lo importante estructural es dónde vive el nodo, no los códigos exactos.

Ahora la validación pre-timbrado. La idea es no llegar al PAC si ya sabemos que va a rechazar:

# Ilustrativo: validación local contra el cache L_CNE antes de timbrar.
# El cache lo refresca un cron diario (ver siguiente sección).

class PermisoInvalido(Exception):
    pass

def validar_permiso(numero_permiso: str, emisor_rfc: str, l_cne_cache: dict) -> None:
    """Falla rápido y en local. Lanza PermisoInvalido con un mensaje claro
    en vez de mandar el CFDI al PAC y comerte el rechazo."""
    permiso = l_cne_cache.get(numero_permiso)

    if permiso is None:
        # Expirado, revocado, o un typo en el NumeroPermiso
        raise PermisoInvalido(
            f"NumeroPermiso {numero_permiso} no está en L_CNE. "
            f"Revisa vigencia, revocación o un error de captura."
        )

    if permiso["rfc_titular"] != emisor_rfc:
        raise PermisoInvalido(
            f"El RFC del emisor {emisor_rfc} no coincide con el "
            f"titular del permiso {numero_permiso}."
        )

def emitir_cfdi_combustible(cfdi, emisor_rfc, l_cne_cache, pac):
    for concepto in cfdi.conceptos_combustible():
        validar_permiso(concepto.numero_permiso, emisor_rfc, l_cne_cache)
    # Solo si TODOS los conceptos pasan, llamamos al PAC.
    return pac.timbrar(cfdi)

El patrón clave: si validar_permiso falla, le regresas al caller un error claro y accionable (“el permiso no está en L_CNE”, “el RFC no coincide”) sin gastar una llamada al PAC. El round-trip al PAC lo dejas para cuando ya tienes alta confianza de que va a pasar.

Ponlo en producción: datos del permiso, cachea L_CNE, valida y maneja rechazos

El checklist operativo para llevarlo a producción sin sustos:

  1. Consigue los datos del permiso CNE (TipoPermiso, NumeroPermiso) de cada emisor y mapéalos a la configuración de tu tenant. Si manejas multi-tenant, cada emisor trae su propio permiso y su RFC titular.
  2. Cachea la lista L_CNE y refréscala a diario con un job programado. La CNE la actualiza todos los días; tu cache también.
  3. Valida el NumeroPermiso antes de llamar a timbrar: que esté presente en L_CNE y que el RFC del emisor coincida con el titular.
  4. Maneja los rechazos: cuando el PAC regrese un error, expón el código tal cual, loguéalo y alerta a ops. La lógica de retry constrúyela solo después de corregir los datos del permiso; reintentar con el mismo permiso malo no arregla nada.

Una pieza de monitoreo que se olvida fácil: alerta cuando el refresh diario de L_CNE falle. Si el cron truena en silencio, terminas validando contra un cache desactualizado y aprobando localmente permisos que la CNE ya quitó. El PAC los rechaza igual, pero perdiste el fail-fast.

  1. Datos del permiso CNEMapea TipoPermiso y NumeroPermiso por emisor/tenant
  2. Cachea L_CNEJob diario que refresca la lista de la CNE
  3. Valida antes de timbrarPresencia en L_CNE + RFC del emisor coincide con titular
  4. Maneja rechazosExpón el código del PAC, loguea, alerta a ops; retry tras corregir

Preguntas frecuentes: rechazos, facturas con varios conceptos y a quién aplica

¿Por qué me rechazaron el CFDI de combustible? Tres causas típicas: falta el complemento, el NumeroPermiso no está en L_CNE (expirado, revocado o con typo), o el RFC del emisor no coincide con el titular del permiso. Empieza por validar esas tres antes de buscar más lejos.

¿Va en el comprobante o en el concepto? En el concepto (ComplementoConcepto), por cada línea de combustible. No en la raíz del comprobante.

Vendo gasolina Y diésel en una sola factura, ¿un bloque o varios? Un bloque de complemento por cada concepto de combustible. Dos líneas de combustible, dos bloques, cada uno dentro de su concepto.

¿Quién tiene que cumplir? Cualquiera que emita CFDI 4.0 por gasolina o diésel y tenga un permiso de la CNE: estaciones de servicio, distribuidores, comercializadores. Si facturas combustible, te toca.

¿De cuánto es la multa? De $17,000 a $97,000 MXN por comprobante para personas morales, más la pérdida de validez del CFDI. Confirma las cifras vigentes y, para las dudas fiscales finas, apóyate en un contador.

Si quieres ubicar este complemento dentro del panorama más amplio de facturación regional, revisa el mapa de facturación electrónica en LATAM 2026, y para más guías de integración fiscal y de pagos, pásate por integraciones.

Falta el complementoCFDI de combustible sin el bloque = rechazo del PAC
Permiso no en L_CNEExpirado, revocado o typo en NumeroPermiso = rechazo
RFC no coincideEl emisor no es el titular del permiso = rechazo
Multa$17,000–$97,000 MXN por comprobante (personas morales)

En resumen

Desde el 24 de abril de 2026 el Complemento de Hidrocarburos y Petrolíferos no es opcional para ningún CFDI 4.0 que facture gasolina o diésel. El riesgo operativo no son los cinco atributos en sí; es el refresh diario de L_CNE. Un permiso bueno ayer puede caerse hoy.

La regla de oro de ingeniero: valida antes de timbrar. Cachea L_CNE, refréscalo a diario, valida el NumeroPermiso y el RFC localmente, y falla rápido con un mensaje claro en lugar de comerte el rechazo del PAC.

Y lo de siempre: soy ingeniero, no contador. Confirma las reglas vigentes del SAT y de la CNE y apóyate en compliance para los casos fiscales de frontera. Lo que sí te puedo dar es el patrón de código para que el XML salga bien a la primera.

Fuentes oficiales: Facturama — Complemento de Hidrocarburos · BDO Mexico — Flash Fiscal · SAT