Validación de CFDI 4.0
Validar un CFDI
POST /api/v1/cfdi/validar Authorization: Bearer <token> Content-Type: application/xml <?xml version="1.0" ...> (XML crudo del CFDI en el cuerpo)El pipeline ejecuta: XML bien formado → esquema oficial del SAT → claves de catálogos contra la base de datos → reglas de negocio y congruencia (montos, RFC, régimen fiscal vs tipo de persona, UsoCFDI vs régimen del receptor, receptor genérico, forma/método de pago, tipo de cambio, impuestos y congruencias por tipo de comprobante) → certificado del emisor (validez, número de certificado, RFC del titular y vigencia a la fecha de emisión) → verificación criptográfica del sello contra la cadena original oficial (CFDI40102). El esquema se valida con el envoltorio propio cfdv40_complementos.xsd, que reúne el esquema del comprobante y las declaraciones del TimbreFiscalDigital 1.1 y del complemento de Pagos 2.0: el xs:any del nodo Complemento es strict y exige la declaración de cada complemento presente. Si el CFDI ya está timbrado (trae el complemento TimbreFiscalDigital), se verifica que el SelloCFD del timbre coincida con el Sello del comprobante y que la FechaTimbrado no sea anterior a la fecha de expedición; además, con el certificado del PAC en el almacén local cfdi/sat/pac/*.cer (localizado por NoCertificadoSAT), se valida primero que cuelgue de las raíces del SAT vendorizadas en cfdi/sat/raiz/ a la FechaTimbrado del timbre: si la cadena es inválida (una CA ajena al SAT o una raíz sin vigencia a esa fecha) es error; si las raíces no están disponibles en disco es advertencia de configuración y la verificación degrada a la comprobación del sello; y si el certificado no está en el almacén, es advertencia, no error. Con la cadena válida se verifica criptográficamente el SelloSAT del timbre contra la cadena original del TFD y la llave pública de ese certificado.
Si el CFDI trae el complemento Pagos 2.0 (comprobantes de tipo P), la verificación Complemento de Pagos 2.0 congruente valida su aritmética con las fórmulas oficiales del SAT: ImpPagado no mayor que el ImpSaldoAnt; ImpSaldoInsoluto igual al saldo anterior menos lo pagado y sin valores negativos; Monto de cada pago igual a la suma de los ImpPagado de sus documentos relacionados convertidos con su EquivalenciaDR; MontoTotalPagos igual a la suma de los pagos en pesos con su TipoCambioP; y los atributos condicionales EquivalenciaDR/TipoCambioP presentes cuando las monedas lo exigen. Cuando el complemento declara impuestos (ObjetoImpDR="02") también valida su aritmética fiscal: el ImporteDR de cada traslado/retención igual a su BaseDR por su TasaOCuotaDR (el factor Exento no lleva tasa ni importe); el impuesto trasladable o retenible según c_Impuesto; los ImpuestosP de cada pago como la suma de los impuestos de sus documentos relacionados convertidos a la MonedaP; y cada atributo Total* del nodo Totales (IVA 16/8/0/exento y retenciones de ISR, IVA e IEPS) como la suma en pesos de su tipo, requerido cuando hay documentos de ese tipo y prohibido cuando no. Sin el complemento la verificación queda en no_aplica.
La verificación permanente 18, Emisor en listas del SAT (EFOS / CSD sin efectos) cruza el RFC del emisor contra dos listas públicas del SAT mantenidas como tablas locales (cero red en tiempo de validación): la lista EFOS 69-B (contribuyentes con operaciones presuntamente inexistentes) y la lista 17-H de certificados de sello digitales sin efectos. Emisor en EFOS con situación Definitivo → error; Presunto → advertencia; Desvirtuado o Sentencia favorable → advertencia informativa. CSD del emisor cancelado en fecha anterior o igual a la del comprobante → error; cancelado después de la fecha del comprobante → advertencia informativa. Si las listas nunca se han cargado la verificación queda en no_aplica con advertencia, y si solo una está cargada el cruce corre con esa lista advirtiendo la faltante. Estos errores no llevan código de la matriz (la matriz CFDI 4.0 no cubre EFOS/17-H).
Las listas se importan desde los CSV públicos de Datos Abiertos del SAT (Azure Blob, codificación windows-1252) y se refrescan solas en cada make update o botón "Actualizar" del panel de actualizaciones: la descarga es condicional por ETag, así que cuando el SAT no ha publicado un archivo nuevo (respuesta 304) no se reimporta nada. La carga inicial o forzada se hace con make import-listas.
Con el parámetro opcional ?consultar_sat=true se agrega la verificación 17, Estado del CFDI ante el SAT: consulta el WS público del SAT (el mismo del código QR de la factura) y reporta si el comprobante está Vigente o Cancelado (cancelado invalida la validación). Implica una llamada de red externa; sin el parámetro la validación es 100% local. Si el servicio del SAT no responde o no encuentra el CFDI, es advertencia, no error.
Responde {"valido": bool, "errores": [...], "advertencias": [...], "verificaciones": [...], "comprobante": {...}}; cada error incluye codigo (de la matriz de errores del SAT, cuando existe), campo, valor y mensaje. verificaciones es el checklist de las 17 fases permanentes (18 con ?consultar_sat=true) con su estatus (valido, error, advertencia, no_aplica), sus errores/advertencias y reglas: las reglas oficiales CFDI40xxx de la matriz de errores del SAT que cada fase aplica, como {codigo, validacion} (el texto oficial se resuelve desde la matriz importada; sin ella solo va el código). Las advertencias no invalidan el comprobante. comprobante resume el encabezado del documento (fecha, serie, folio, tipo, RFC y nombre del emisor, y uuid cuando está timbrado). Un cuerpo que no es XML bien formado responde 400; un CFDI inválido responde 200 con valido: false. La misma validación está disponible en el panel web (con sesión) en /cfdi/validar y como herramienta MCP validar_cfdi.
Consulta de estado ante el SAT
POST /api/v1/cfdi/consulta-estado Authorization: Bearer <tu api key> Content-Type: application/json {"uuid": "...", "rfc_emisor": "...", "rfc_receptor": "...", "total": "116.00"}Consulta directa del estado de un CFDI timbrado ante el WS público del SAT (el mismo detrás del código QR de la factura; sin autenticación ante el SAT). Solo se necesitan los 4 datos del timbre: uuid, rfc_emisor, rfc_receptor y total tal como aparecen en el XML. Responde 200 con {"uuid", "rfc_emisor", "rfc_receptor", "total", "codigo_estatus", "es_cancelable", "estado", "estatus_cancelacion", "validacion_efos"}, donde estado es Vigente, Cancelado o No Encontrado. Responde 400 si el JSON es inválido o incompleto, 422 si el SAT no pudo procesar la consulta (código de estatus "N - ...") y 502 si el servicio del SAT no responde (timeout de 15 s). La misma consulta está disponible en el panel web (con sesión) en /cfdi/consulta —con indicador de carga y mensaje de tiempo de espera agotado—, desde el resultado de /cfdi/validar cuando el CFDI trae timbre, y como herramienta MCP consultar_estado_cfdi (que además acepta el xml completo y extrae los 4 datos).
Limitaciones actuales
- La LCO (lista de certificados vigentes) y la LRFC ya no son públicas: desde el Anexo 29 de la RMF 2026 su descarga diaria es exclusiva para contribuyentes con e.firma (PCCFDI), por lo que el cruce del certificado del emisor se hace contra la lista 17-H de CSD sin efectos.
- El CSV acumulado de la lista 69-B tiene un desfase respecto a los oficios incrementales publicados en el DOF: un RFC recién incluido puede aún no aparecer en la lista.
- El cruce contra EFOS/CSD sin efectos aplica solo al RFC del emisor; el receptor no se verifica contra estas listas.
- El almacén local de certificados de PAC (
cfdi/sat/pac/) se alimenta manualmente por el operador (del propio PAC; no hay descarga masiva pública): sin el certificado del PAC elSelloSATqueda en advertencia, y con un certificado que no cuelga de las raíces del SAT a laFechaTimbradola verificación 15 es error. - Los complementos distintos del TimbreFiscalDigital y Pagos 2.0 (nómina, CartaPorte…) no tienen esquema propio: un CFDI que los incluya falla la verificación de esquema (verificación 2).