Son las 11 de la noche. Tu integración de CFDI lleva semanas funcionando sin problemas y de repente empieza a regresar errores. El código dice 401. El mensaje dice algo sobre "fecha de generación". Buscas en internet y encuentras foros de contadores que tampoco entienden bien qué pasó técnicamente.
Este artículo existe para esos momentos.
Por qué los errores de timbrado son tan frustrantes
El proceso de timbrar un CFDI involucra al menos tres capas de validación: la tuya (antes de enviar), la del PAC (antes de mandarlo al SAT), y la del SAT (la validación final). Cuando algo falla, el mensaje de error llega desde la capa más profunda que detectó el problema, y muchas veces el código es críptico.
Lo que hace más difícil el debugging es que los mismos errores pueden tener causas completamente distintas dependiendo del contexto. Un 301, por ejemplo, puede significar que te falta un campo requerido, que usaste el namespace de la versión anterior del complemento, o que tu decimal tiene más precisión de la permitida. El código es el mismo. La causa, no.
Esta guía organiza los errores por categoría para que puedas ir directo al problema.
Errores de estructura del XML
Son los más comunes cuando estás desarrollando una integración nueva, y también los más solucionables porque siempre tienen que ver con el XML que tú construyes.
301 — La estructura del comprobante es incorrecta
El error más genérico de todos. Significa que el XML no pasó la validación contra el XSD del SAT. Las causas más frecuentes:
- Campo requerido que no se incluyó
- Tipo de dato incorrecto (por ejemplo, un decimal con más de dos posiciones donde el SAT espera exactamente dos)
- Fecha en formato incorrecto (
YYYYMMDDen lugar deYYYY-MM-DDThh:mm:ss) - Valor de catálogo que no existe o ya fue descontinuado
La mejor forma de evitar el 301 es validar el XML contra el XSD oficial antes de enviarlo al PAC. En Python con lxml:
from lxml import etree
def validate_cfdi(xml_string: str) -> list[str]:
with open("cfdv40.xsd", "rb") as f:
schema = etree.XMLSchema(etree.parse(f))
doc = etree.fromstring(xml_string.encode())
schema.validate(doc)
return [str(e) for e in schema.error_log]Si el resultado es una lista vacía, tu XML pasa el XSD. Si no, el error te dice exactamente qué campo y en qué línea.
Errores de complemento: CO1001 al CO1005
Este grupo de errores ocurre cuando adjuntas un complemento (Carta Porte, Nómina, Pagos, etc.) y el XML no lo declara correctamente. El patrón más común:
CO1002 — xmlns:{complemento} no se encuentra a nivel ComprobanteEsto significa que pusiste la declaración del namespace del complemento en el nodo <cfdi:Complemento> en lugar del nodo raíz. El SAT es muy específico: todos los namespaces van en <cfdi:Comprobante>.
<!-- MAL: namespace en el nodo Complemento -->
<cfdi:Complemento>
<cartaporte31:CartaPorte xmlns:cartaporte31="http://www.sat.gob.mx/CartaPorte31" ...>
<!-- BIEN: namespace en el nodo raíz -->
<cfdi:Comprobante xmlns:cartaporte31="http://www.sat.gob.mx/CartaPorte31" ...>
<cfdi:Complemento>
<cartaporte31:CartaPorte ...>El CO1003 aparece cuando el xsi:schemaLocation del complemento está ausente en el nodo raíz. El CO1005 cuando está duplicado (en el nodo raíz y en el nodo Complemento al mismo tiempo).
Errores de certificado (CSD)
Este grupo es diferente porque el problema no está en el XML de negocio sino en las llaves criptográficas con las que firmas el CFDI.
302 — El sello del emisor no es válido
Tu XML está bien estructurado, pero el sello digital no coincide con la cadena original. Casi siempre pasa porque modificaste el XML después de generar el sello: aunque sea añadir un espacio o cambiar el orden de un atributo rompe la firma.
La solución es siempre regenerar el sello sobre el XML en su estado final. Nunca modificar el XML después de sellarlo.
303 al 308 — Problemas con el CSD en sí
| Código | Qué pasó | Qué hacer |
|---|---|---|
303 | El CSD pertenece a un RFC diferente al del emisor | Verificar que el CSD cargado corresponde al RFC del emisor |
304 | El CSD fue revocado por el SAT | El cliente necesita tramitar un CSD nuevo |
305 | La fecha del CFDI está fuera de la vigencia del certificado | Verificar notBefore y notAfter del CSD antes de usarlo |
306 | Se usó la e.firma (FIEL) en lugar del CSD | El CFDI se sella con CSD, nunca con FIEL |
308 | Certificado no expedido por el SAT (test en producción) | Cambiar al certificado de producción real |
Errores de tiempo
307 — El comprobante contiene un timbre previo
Este es el que más confunde porque puede significar dos cosas muy distintas:
Caso A: Tu sistema envió el mismo XML dos veces (por un retry automático) y el primero sí timbró. El UUID ya existe en el SAT. Lo que debes hacer es consultar ese UUID en el servicio de verificación del SAT antes de asumir que el timbrado falló.
Caso B: Hay un bug en tu generador de UUIDs y está produciendo duplicados. Mucho menos común, pero ocurre.
La regla práctica: ante un 307, primero consulta el UUID. Si está timbrado y válido, guarda el XML y no reenvíes. Si no existe, genera un nuevo UUID y reintenta.
401 — El rango de la fecha de generación supera las 72 horas
El SAT no acepta CFDIs con una fecha de emisión de más de 72 horas de antigüedad al momento del timbrado. Si tu sistema generó el XML pero lo dejó en cola demasiado tiempo, llega con la fecha vencida.
La solución no es modificar el Fecha del XML (eso rompería el sello). Es generar un nuevo XML con la fecha actual y sellarlo de nuevo.
El error que no tiene solución técnica: 402
El 402 significa que el RFC del emisor no aparece en la Lista de Contribuyentes Obligados (LCO) del SAT con obligaciones vigentes. El RFC puede ser válido y el CSD puede estar en regla, pero si el contribuyente no tiene obligaciones activas registradas, el SAT rechaza el timbrado.
Esto no se resuelve en el código. Es un problema administrativo que el cliente debe resolver directamente ante el SAT actualizando su situación fiscal. No hay retry, no hay workaround.
Errores de cancelación
Los errores CA2XX y CA3XX aparecen cuando llamas al endpoint de cancelación, no al timbrar. Los más importantes:
| Código | Qué significa |
|---|---|
CA207 | Motivo de cancelación inválido o no especificado |
CA208 | Motivo 01 (error con relación) requiere un FolioSustitucion válido |
CA209 | Motivos 02, 03 o 04 no deben incluir FolioSustitucion |
CA211 | Cancelación fuera del plazo fiscal del período (frecuente en Factura Global) |
El CA208 y CA209 son dos caras del mismo malentendido: el motivo 01 dice "cancelé este CFDI porque lo reemplacé con otro", así que exige el UUID del reemplazo. Los motivos 02, 03 y 04 no tienen reemplazo, así que si incluyes un FolioSustitucion, el SAT lo rechaza.
¿Qué errores se pueden reintentar y cuáles no?
No todos los errores se resuelven reintentando. Hacerlo sin criterio solo genera más problemas (y más errores 307 por duplicados).
Errores que SÍ pueden reintentarse (después de esperar y verificar):
- Timeouts de red
AU2000(credenciales inválidas, posiblemente un problema temporal del PAC)S2000(saldo agotado, después de recargar)
Errores que NO se reintentan sin modificar algo primero:
301— Arreglar el XML302— Regenerar el sello303al308— Resolver el problema del certificado401— Generar nuevo XML con fecha actualizada402— No hay solución técnica, escalar al cliente
Caso especial 307: Consultar el UUID en el SAT antes de decidir.
RETRIABLE = {"timeout", "AU2000", "S2000"}
FATAL = {"301", "302", "303", "304", "305", "306", "308", "401", "402"}
CHECK_FIRST = {"307"} # consultar UUID antes de reintentar
def handle_error(code: str, uuid: str, xml: str):
if code in CHECK_FIRST:
if sat_uuid_exists(uuid):
return save_stamped_xml(uuid) # ya timbró, guardar
return retry_with_new_uuid(xml) # no existe, reintentar
if code in FATAL:
alert_and_log(code, xml)
return StampResult.failed(code)
if code in RETRIABLE:
return retry_with_backoff(xml)
log_unknown_error(code)
return StampResult.unknown(code)El hábito que evita el 80% de los problemas
La mayoría de los errores 301 y de complemento se pueden detectar antes de tocar el endpoint del PAC con una validación local del XSD. No es glamoroso, pero es el cambio de una línea que más impacto tiene en una integración CFDI:
- Valida contra
cfdv40.xsd(y el XSD del complemento si aplica) - Verifica que la
Fechaestá dentro de las 72 horas - Verifica que el RFC del emisor coincide con el CSD cargado
- Verifica que el CSD está vigente y no revocado
- Genera siempre un UUID v4 válido, nunca reutilices uno
Si estas cinco validaciones pasan localmente, tu tasa de rechazos en producción baja drásticamente. Lo que queda son los errores que realmente requieren investigación, no los errores que un pre-check hubiera atrapado antes de gastar un timbre.