Help us shape the future of Go! Take the 2026 Go Developer Survey

cfdi

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 27 Imported by: 0

README

cfdi-go

Librería en Go que lee, verifica y valida CFDI del SAT (México); no genera ni timbra. Lee CFDI 3.3 y 4.0 y CFDI de Retenciones e información de pagos 1.0 y 2.0 y los normaliza a structs y JSON con esquema versionado; arma la cadena original, verifica los sellos y valida contra los XSD y las reglas del SAT. Sin dependencias externas.

Estado: v0.x. La API puede cambiar entre versiones menores hasta la v1.0.0.

go get github.com/OrtaMarco/cfdi-go@latest
go install github.com/OrtaMarco/cfdi-go/cmd/cfdi@latest   # la CLI
import "github.com/OrtaMarco/cfdi-go" // el paquete se llama cfdi

Requiere Go 1.24 o más nuevo. Solo usa la biblioteca estándar.

Versiones soportadas, en resumen (el detalle está en Alcance):

Documento Lectura y JSON Cadena original y sello Esquema (XSD) Reglas
CFDI 4.0 sí sí sí sí
CFDI 3.3 sí sí sí sí
CFDI 3.2 / 3.0 parcial 3.2 no no
Timbre Fiscal Digital 1.0 / 1.1 sí sí sí —
Retenciones e información de pagos 1.0 / 2.0 sí sí sí no

Límites: no timbra, no cancela, no genera ni firma CFDI (solo arma la cadena original y verifica sellos), no consulta al SAT ni descarga certificados, no revisa revocación ni cadena de confianza de los certificados y no valida contra catálogos que no vengan en los XSD. Que un CFDI pase Validar no significa que el SAT lo acepte: la librería cubre lo que se puede revisar sin red. Ver Fuera de alcance y las reglas en Validación.

English summary: a dependency-free Go library to parse Mexican CFDI e-invoices (3.3 and 4.0, plus Retenciones 1.0/2.0 and most SAT complements) into structs and versioned JSON, build the original string (cadena original), verify issuer and SAT seals, and validate against the SAT XSD schemas and arithmetic/consistency rules. Documentation is in Spanish.

Alcance

  • CFDI 3.3 / 4.0: tipos I, E, P, N, T; PUE/PPD; CfdiRelacionados; impuestos por concepto y totales.
  • Complementos: TimbreFiscalDigital 1.0 y 1.1, Pagos 1.0 y 2.0 (REP), Nómina 1.2 (lectura).
  • Más complementos: ImpuestosLocales 1.0, CartaPorte 2.0 / 3.0 / 3.1, ComercioExterior 1.1 / 2.0, Divisas 1.0, CFDIRegistroFiscal 1.0, LeyendasFiscales 1.0, EstadoDeCuentaCombustible 1.2; de concepto: PorCuentadeTerceros 1.1, VentaVehiculos 1.0 / 1.1, HidroYPetro 1.0. Todos los nodos y atributos del XSD del SAT.
  • Y también: Aerolíneas 1.0, Donatarias 1.0 / 1.1, INE 1.0 / 1.1, ValesDeDespensa 1.0, ConsumoDeCombustibles 1.0 / 1.1, PagoEnEspecie 1.0, NotariosPublicos 1.0, servicios parciales de construcción 1.0, VehiculoUsado 1.0, TuristaPasajeroExtranjero 1.0, SPEI de terceros, y las versiones viejas CartaPorte 1.0, ComercioExterior 1.0, EstadoDeCuentaCombustible 1.1 y Nómina 1.1 (de CFDI 3.2); de concepto: instEducativas (iedu) 1.0 y acreditamientoIEPS 1.0; y renovacionysustitucionvehiculos 1.0, certificadodedestruccion 1.0, obrasarteantiguedades 1.0, PFintegranteCoordinado (pfic) 1.0, GastosHidrocarburos 1.0, IngresosHidrocarburos 1.0, EstadoDeCuentaBancario (ecb) 1.0 y EstadoDeCuentaCombustible sin versión (ecc, de CFDI 3.2). Lo que no se lee sigue como COMPLEMENTO_NO_SOPORTADO.
  • Sin leer todavía: Detallista (10 ejemplos públicos en testdata/publicos, en 3.3 y 4.0; su XSLT está en las fuentes pero falta su XSD, así que tampoco hay cadena ni esquema: COMPLEMENTO_SIN_CADENA y ESQUEMA_NO_SOPORTADO) y psgecfd (solo CFDI 3.2, sin XSD ni ejemplos).
  • Montos en decimal exacto (string en JSON).
  • Cadena original (CFDI 3.2, 3.3, 4.0 y TFD 1.0 / 1.1) y verificación del sello del emisor y del timbre (ver abajo).
  • Validación contra los XSD del SAT (CFDI 4.0 y 3.3, TFD 1.0 y 1.1 y 45 versiones de complementos, con sus catálogos; lista en Esquema) y reglas aritméticas y de coherencia, cada una con su fundamento en la matriz de errores, el estándar o la guía de llenado del SAT (ver abajo).
  • CLI: cfdi parse (un XML → JSON), cfdi verificar (cadena y sellos), cfdi validar (esquema y reglas) y cfdi lote (carpeta o ZIP → JSONL + resumen de fallos).
  • CFDI de Retenciones e información de pagos 1.0 y 2.0, con API aparte: lectura con sus trece complementos, JSON con su propio esquema, cadena original, sellos y XSD (ver Retenciones).
  • Chequeos del certificado del emisor sin red: vigencia en la fecha del documento, RFC del titular y número de serie (ver Certificado del emisor).

Fuera de alcance por ahora: reglas que necesitan red o catálogos que no vienen en los XSD (RFC en la lista del SAT, c_TasaOCuota, rangos de TipoCambio), cadena de confianza y revocación de los certificados, reglas aritméticas de Retenciones, timbrado, cancelación, descarga masiva.

CFDI 3.2 (y 3.0): lectura parcial

No son fatales: se leen los atributos y nodos que coinciden con 3.3 (los nombres de 3.2 van en minúsculas, total, fecha, rfc; la lectura no distingue mayúsculas) y se advierte VERSION_ANTIGUA. No se exige lo que introdujo 3.3 (Moneda, UsoCFDI, RegimenFiscal del emisor, ClaveProdServ, ClaveUnidad) y no se modela lo exclusivo de 3.2: formaDePago, metodoDePago, domicilios, RegimenFiscal como nodo, tasa de los traslados. Sus complementos sí (Nómina 1.1, por ejemplo). Los valores van tal cual vienen (tipo_de_comprobante puede ser "ingreso", moneda puede ser "Pesos"). Una Version que no sea 3.0, 3.2, 3.3 ni 4.0 se lee igual, según el namespace, con VERSION_NO_SOPORTADA.

Uso

c, err := cfdi.Parse(f) // err solo si no es XML o la raíz no es un Comprobante cfd/3 o cfd/4
if err != nil {
    log.Println(cfdi.ClaseDe(err)) // no_es_xml | xml_mal_formado | no_es_comprobante | lectura
}
for _, a := range c.Advertencias {
    fmt.Println(a.Codigo, a.Ruta, a.Mensaje)
}
fmt.Println(c.UUID(), c.Total.Original()) // "ABCDEF01-…", "1640.00"
b, _ := c.JSON()
cfdi parse factura.xml [-sello]
cfdi verificar factura.xml [-certs-sat dir-con-los-.cer-del-SAT]
cfdi validar factura.xml
cfdi lote carpeta/ otro.zip [-o salida.jsonl] [-resumen resumen.json] [-rutas] [-j N] [-sello | -verificar] [-certs-sat DIR] [-validar]

verificar escribe {"archivo", "cadena_original", "sello", "cadena_original_tfd", "tfd", "certificado"} y sale con 0 solo si todo lo verificado es válido (los chequeos de certificado no cuentan para la salida, como no cuentan para el sello). lote -sello incluye los sellos y agrega a cada línea verificacion_sello; -verificar agrega la verificación sin los sellos. Con cualquiera de las dos el resumen trae sellos: {valido, invalido: {motivo: n}, no_verificable: {motivo: n}}. lote -certs-sat DIR verifica además el timbre (el SelloSAT con los certificados de DIR, como verificar): cada línea lleva verificacion_tfd y el resumen sellos_tfd, con la misma forma.

validar escribe {"archivo", "estado", "esquema": {estado, hallazgos}, "reglas": {estado, hallazgos}} (más error si el documento no se pudo leer como CFDI; entonces no hay reglas) y sale con 1 solo si es inválido: un complemento sin esquema deja el estado incompleto y sale con 0. lote -validar agrega a cada línea validacion con la misma forma, y al resumen validacion: {por_estado, hallazgos_por_codigo} (conteos y huellas, como las advertencias).

lote recorre .xml, carpetas (recursivo) y .zip (también zips dentro de carpetas o de otros zips; ignora __MACOSX/ y ._*). Escribe una línea por documento en el orden en que los encontró:

  • leído: {"origen":"c.zip!dir/a.xml","schema_version":"1","cfdi":{…},"advertencias":[…]}
  • Retenciones leída: {"origen":"…","tipo_documento":"retenciones","schema_version":"1","retenciones":{…},"advertencias":[…]}
  • fatal: {"origen":"…","error":{"clase":"no_es_xml","mensaje":"…"}}

Las Retenciones se leen por omisión, sin bandera; la línea de un CFDI no lleva tipo_documento (es la que trae cfdi). Con -verificar, -certs-sat y -validar se verifican y validan con su API (VerificarRetenciones, ValidarRetenciones). En el resumen cuentan en total, leidos, advertidos, advertencias, sellos y validación; por_version, por_tipo y por_complemento siguen siendo solo de CFDI, y las Retenciones van aparte en retenciones: {leidos, por_version, por_complemento} (la clave no sale si el lote no trae ninguna).

La memoria depende de -j y del tamaño de los XML, no de cuántos haya: cada worker deja de su CFDI solo la línea JSON y lo que pide el resumen, y en espera del orden de salida hay a lo más 256×j líneas; si un documento lento retiene la cabeza, la lectura se detiene en vez de acumular. La Addenda no se guarda en memoria (no se lee). Para apretar más en máquinas chicas: GOMEMLIMIT=256MiB cfdi lote ….

El resumen trae total, leidos, fatales, advertidos, conteos por código de advertencia y por clase de error (con hasta 5 ejemplos), por versión, por tipo y por complemento. Está pensado para compartirse: no incluye RFC, nombres, montos ni rutas. Cada ejemplo es una huella, los primeros 12 hex del sha256 del contenido del XML (de la ruta, si ni se pudo leer) más la extensión del archivo de entrada o del zip más externo: 3f2a9c1b4d5e.zip. Se usa el contenido y no la ruta para que nadie pueda confirmar si un nombre de archivo adivinado (con un RFC, p. ej.) está en el lote. Para ubicar un ejemplo: sha256sum sobre los XML, o volver a correr con -rutas, que pone el origen completo (solo para uso local).

El JSONL, en cambio, siempre lleva origen con la ruta completa: es la salida del usuario, sobre sus propios archivos, y sin la ruta no hay cómo volver al documento.

JSON

{"schema_version": "1", "cfdi": {"version": "4.0", "total": "1640.00", "conceptos": […], …}, "advertencias": []}
  • Esquema: schema/cfdi.v1.schema.json (draft 2020-12), embebido como cfdi.Schema.
  • Claves en snake_case de los nombres del SAT (tipo_de_comprobante, uso_cfdi); Año → anio, Antigüedad → antiguedad.
  • Montos y cantidades: string con el texto original ("0.160000", no 0.16).
  • Fechas: el texto original; en Go, Fecha.Time es la hora civil sin zona (ver Fecha).
  • Vacíos omitidos. Sello, certificado y cadenas solo con IncluirSello / -sello.
  • Contenedores sin atributos se aplanan (conceptos, traslados, otros_pagos); los que tienen atributos se conservan como objeto con la lista adentro (percepciones.percepcion, pagos.pago).
  • UUID, IdDocumento y CfdiRelacionado en mayúsculas sin espacios; *_original solo si cambió.
Complementos

Cada complemento soportado tiene su clave dentro de cfdi, junto a timbre_fiscal_digital, pagos y nomina: impuestos_locales, carta_porte, comercio_exterior, divisas, cfdi_registro_fiscal, leyendas_fiscales, estado_de_cuenta_combustible, aerolineas, donatarias, ine, vales_de_despensa, consumo_de_combustibles, pago_en_especie, notarios_publicos, parcialesconstruccion, vehiculo_usado, turista_pasajero_extranjero, complemento_spei (lista de SPEI_Tercero), renovacionysustitucionvehiculos, certificadodedestruccion, obrasarteantiguedades, pf_integrante_coordinado, gastos_hidrocarburos, ingresos_hidrocarburos, estado_de_cuenta_bancario. Los de concepto van en cada concepto: por_cuentade_terceros, venta_vehiculos, hidro_y_petro, inst_educativas, acreditamiento_ieps. Lo que no se lee queda en complementos_no_soportados (y en complemento_concepto del concepto). Las claves salen del nombre del SAT tal cual aunque junte palabras: TotaldeTraslados → totalde_traslados, PorCuentadeTerceros → por_cuentade_terceros.

  • Versiones de un mismo complemento van en un solo objeto con version: los XSD de CartaPorte 2.0/3.0/3.1 y de ComercioExterior 1.1/2.0 solo agregan, quitan o mueven nodos, sin cambiar el tipo de los que comparten, así que la unión no pierde datos. Lo exclusivo de una versión (p. ej. pedimentos en CartaPorte 2.0, documentacion_aduanera en 3.x, regimenes_aduaneros en 3.1) viene vacío en las otras; el esquema lo dice en cada clave. La versión la dicta el namespace, como en Pagos.
  • Lo mismo con las versiones viejas: ComercioExterior 1.0 agrega receptor.curp y destinatario[].rfc/curp; EstadoDeCuentaCombustible 1.1, tar y no_identificacion en cada concepto (su TasaoCuota va en tasa_o_cuota); Nómina 1.1 lleva los datos del trabajador como atributos de Nomina (curp, num_empleado, tipo_regimen… y horas_extras), no en receptor, y sus deducciones importe_gravado/importe_exento. CartaPorte 1.0 comparte casi todo con 2.0, pero 2.0 cambió la forma de tres nodos: lo de 1.0 va en ubicaciones[].origen/destino, mercancias.autotransporte_federal y figura_transporte10 (en 1.0 FiguraTransporte es un nodo con CveTransporte, y figura_transporte es la lista de TiposFigura de 2.0+). Cada versión exige solo lo que pide su XSD.
  • impuestos_locales no se suma a impuestos, pero sí está en el total del comprobante: total = sub_total - descuento + trasladados - retenidos + totalde_traslados - totalde_retenciones.
  • HidroYPetro es complemento de concepto según su XSD; si viene en el Complemento del comprobante también se lee, en cfdi.hidro_y_petro (lista). Al revés, parcialesconstruccion va en el Complemento del comprobante, y si viene en un ComplementoConcepto se lee en el concepto.
  • SPEI no tiene versión (Complemento_SPEI en Complementos()); su sello y cadena_cda solo salen con IncluirSello, como los demás sellos. ConsumoDeCombustibles 1.0 y 1.1 van en un objeto: 1.0 trae tasa en cada determinado y 1.1 tasa_o_cuota y tipo_combustible.
  • Los de CFDI 3.2 sin versión en el atributo: EstadoDeCuentaCombustible del namespace .../ecc va en estado_de_cuenta_combustible con version "1.0" (la dicta el namespace) y su traslado trae tasa (porcentaje) en vez de tasa_o_cuota. En estado_de_cuenta_bancario, movimientos es un objeto con movimiento_ecb y movimiento_ecb_fiscal: el XSD los deja intercalados y cada lista conserva su orden.
  • renovacionysustitucionvehiculos: un decreto de renovación (decreto_renov_vehicular, varios vehículos usados) o de sustitución (decreto_sustit_vehicular, uno, que exige además num_fol_avisoint, num_ped_im, aduana y fecha_regul_veh). El atributo Año va en anio. Si el CFDI trae dos nodos (el ejemplo del SAT trae uno de cada decreto) se lee el primero con NODO_DUPLICADO, como con los demás complementos.
  • Comprobante.Complementos() (y el resumen de lote) cuenta también los de concepto.

Advertencias

Códigos estables (constantes cfdi.Adv*, lista en cfdi.Codigos):

Código Qué pasó
ENCODING_CORREGIDO el encoding declarado no coincide con los bytes (p. ej. dice UTF-8 y es latin1); se convirtió
ENCODING_DESCONOCIDO encoding declarado que no se reconoce
CARACTER_INVALIDO caracteres de control prohibidos en XML; se quitaron
XML_NO_ESTRICTO & suelto o entidad desconocida; se leyó en modo tolerante
CONTENIDO_TRAS_RAIZ basura después de </cfdi:Comprobante>
VERSION_ANTIGUA CFDI 3.0 o 3.2: lectura parcial (ver arriba)
VERSION_NO_SOPORTADA, VERSION_NAMESPACE Version rara o que no corresponde al namespace
NAMESPACE_INESPERADO nodo con el nombre esperado pero en otro namespace; se leyó
NODO_FALTANTE, NODO_DUPLICADO, ATRIBUTO_FALTANTE estructura incompleta o repetida
SIN_TIMBRE no hay TimbreFiscalDigital
COMPLEMENTO_NO_SOPORTADO, COMPLEMENTO_VERSION complemento que no se lee, o versión inesperada
DECIMAL_INVALIDO, FECHA_INVALIDA, FECHA_FORMATO, UUID_INVALIDO valores ilegibles o fuera de formato
EXENTO_CON_IMPORTE traslado Exento con TasaOCuota o Importe (se conservan)

Por campo, sin parsear Ruta:

if a, ok := c.AdvertenciaEn("/Comprobante", "SubTotal"); ok && a.Codigo == cfdi.AdvDecimalInvalido {
    fmt.Println("SubTotal ilegible:", a.Valor) // el texto original; c.SubTotal quedó nil
}
for _, a := range c.AdvertenciasDe("/Comprobante/Conceptos/Concepto[2]") { … } // el nodo y sus atributos
  • Atributo (atributo en JSON) es el nombre del atributo cuando Ruta termina en /@Nombre; Nodo() es la ruta sin él. El formato de Ruta es contrato estable y está documentado en Advertencia: nombres locales sin prefijo, [n] desde 1 en los nodos repetibles (siempre, también el primero), sin índice en los únicos; Complemento, Nomina y HidroYPetro llevan [n] solo desde el segundo.
  • Valor (valor) trae el texto original en DECIMAL_INVALIDO, FECHA_INVALIDA, FECHA_FORMATO y UUID_INVALIDO. Es la forma de recuperar un monto ilegible, porque el campo queda vacío.
  • Fecha.FueraDeFormato marca por campo las fechas legibles pero fuera del formato SAT. Con cfdi.ParseBytesOpciones(b, cfdi.Opciones{FechaEstricta: true}) esas fechas no se interpretan (Valida() da false) y la advertencia es FECHA_INVALIDA.

Cadena original y sellos

cadena, err := cfdi.CadenaOriginal(xml)      // ||4.0|A|1001|…||
tfd, err := cfdi.CadenaOriginalTFD(xml)      // ||1.1|UUID|…||
v := cfdi.Verificar(xml, cfdi.CertificadosEn(certsSAT...)) // nil: sin verificar el timbre
fmt.Println(v.Sello.Estado, v.Sello.Motivo)  // valido | invalido SELLO_NO_COINCIDE | no_verificable SIN_CERTIFICADO…
  • Go no tiene XSLT: internal/gencadena traduce los XSLT oficiales del SAT (copiados sin cambios en internal/gencadena/xslt/) a cadena_reglas.go, que interpreta cadena.go. El generador falla ante cualquier construcción XSLT que no conoce; go generate . lo vuelve a correr y una prueba exige que cadena_reglas.go esté al día. Entiende xsl:choose cuando todos sus xsl:when comparan el mismo atributo con valores distintos y no hay xsl:otherwise (Nómina 1.1 elige por @Version): a lo más uno se cumple y se traduce como un if por cada when. Acepta además una plantilla repetida si es idéntica (la hoja de 3.2 incluye ine10.xslt e ine11.xslt, que lo son) y el match="/" propio de vehiculousado.xslt, porque el de la hoja raíz va después y XSLT usa el último; una repetida distinta, o un match="/" incluido después del de la raíz, siguen deteniendo el generador.

  • La cadena se arma sobre los bytes, no sobre el Comprobante: orden de la hoja del SAT, nodos repetidos en orden de documento, normalize-space, opcionales presentes aunque vacíos.

  • Complementos en la cadena, según lo que incluye la hoja del SAT de cada versión:

    3.2 3.3 4.0
    TFD (no aporta), Nómina 1.2, implocal, Divisas, CFDIRegistroFiscal, LeyendasFiscales, ComercioExterior 1.1, VentaVehiculos 1.0/1.1, Aerolíneas, Donatarias 1.0/1.1, iedu, INE 1.0/1.1, ValesDeDespensa, PagoEnEspecie, NotariosPublicos, servicios parciales de construcción, VehiculoUsado, TuristaPasajeroExtranjero, renovacionysustitucionvehiculos, certificadodedestruccion, obrasarteantiguedades, pfic sí sí sí
    PorCuentadeTerceros 1.1 sí sí —
    EstadoDeCuentaCombustible 1.1, ConsumoDeCombustibles 1.0 sí sí —
    ComercioExterior 1.0, Nómina 1.1, acreditamientoIEPS, SPEI, EstadoDeCuentaBancario, EstadoDeCuentaCombustible sin versión (ecc) sí — —
    Pagos 1.0, CartaPorte 1.0 — sí —
    CartaPorte 2.0, EstadoDeCuentaCombustible 1.2, ConsumoDeCombustibles 1.1, GastosHidrocarburos 1.0, IngresosHidrocarburos 1.0 — sí sí
    Pagos 2.0, CartaPorte 3.0/3.1, ComercioExterior 2.0, HidroYPetro — — sí

    Cualquier otro complemento (o uno de la tabla en una versión cuya hoja no lo incluye) da *ErrorCadena con COMPLEMENTO_SIN_CADENA, nunca una cadena incompleta. También se niega con XML no estricto o cuyo encoding hubo que corregir (XML_ILEGIBLE).

  • Verificar: RSA PKCS#1 v1.5 con SHA-256 (3.2 y TFD 1.0 aceptan también SHA-1) y NoCertificado igual al número de serie del certificado (cfdi.NoCertificado). El certificado del SAT del timbre no viene en el XML: lo da quien llama con un CertificadosSAT (la librería no usa la red). No revisa la cadena de confianza ni la revocación del certificado; la vigencia, el RFC y el número los revisa aparte, sin cambiar el veredicto del sello (ver abajo).

  • Motivos estables en cfdi.Motivos (NO_ES_RETENCIONES es de las funciones de Retenciones); en el JSON, verificacion_sello es opcional (schema_version sigue en "1").

Certificado del emisor

Verificar y VerificarRetenciones devuelven en Verificacion.Certificado (certificado en cfdi verificar) tres chequeos del Certificado que trae el XML, cada uno con estado (cumple, no_cumple, no_verificable), motivo estable y mensaje. Son aparte: un sello válido con un certificado vencido sigue siendo sello.estado: valido. No usan la red: no dicen si el certificado estaba revocado ni si lo emitió el SAT. lote no los escribe (sus líneas de CFDI no cambian).

  • Vigencia: la Fecha del CFDI (FechaExp en Retenciones) entre NotBefore y NotAfter, los dos incluidos. La Fecha del CFDI y la FechaExp de Retenciones 2.0 son hora civil sin zona del lugar de expedición, y en México hay husos de UTC−5 (Quintana Roo) a UTC−8 (Baja California), contando el horario de verano que hubo hasta 2022. La librería no sabe cuál es (necesitaría el catálogo de códigos postales con su huso), así que la hora h es algún instante entre h+5h y h+8h UTC: cumple si cae en la vigencia con todos los husos, no cumple si no cae con ninguno, y si depende del huso (a menos de tres horas de un límite) es no_verificable con VIGENCIA_AMBIGUA. La FechaExp de Retenciones 1.0 trae su zona (2020-06-30T18:00:00-06:00): es un instante exacto, sin ambigüedad.
  • RFC: el x500UniqueIdentifier (OID 2.5.4.45) del sujeto es el RFC del emisor (Emisor/@Rfc; RfcE o RFCEmisor en Retenciones), sin distinguir mayúsculas ni espacios. En una persona moral el atributo trae RFC / RFC del representante legal y cuenta el primero (cfdi.RFCDelCertificado). Excepción: si el titular es el SAT (SAT970701NN3, CN «SERVICIO DE ADMINISTRACION TRIBUTARIA») y no el emisor, es un CFDI del servicio gratuito de facturación del SAT, que sella con su certificado a nombre del contribuyente: cumple con motivo CERTIFICADO_DEL_SAT, el único cumple con motivo, para que se vea que el RFC no coincide y por qué no es un problema (un no_verificable diría que falta un dato, y no falta). Si el timbre trae RfcProvCertif="SAT970701NN3" el mensaje lo dice; no es requisito, porque los timbres 1.0 no tienen ese atributo. El SAT como representante (después de la barra) no cuenta, y un certificado de cualquier otro tercero sigue en RFC_CERTIFICADO_DISTINTO.
  • NoCertificado: el del documento (NumCert en Retenciones 1.0) es el número de serie del certificado. Es lo mismo que exige el sello, pero aquí sale aunque la firma no verifique.
Motivo Chequeo Estado
CERTIFICADO_NO_VIGENTE_AUN vigencia no_cumple: la fecha es anterior a NotBefore
CERTIFICADO_VENCIDO vigencia no_cumple: la fecha es posterior a NotAfter
VIGENCIA_AMBIGUA vigencia no_verificable: dentro o fuera según el huso
FECHA_ILEGIBLE vigencia no_verificable: sin fecha o ilegible
RFC_CERTIFICADO_DISTINTO rfc no_cumple
CERTIFICADO_DEL_SAT rfc cumple: el titular es el SAT, que sella a nombre del emisor
CERTIFICADO_SIN_RFC rfc no_verificable: el certificado no trae 2.5.4.45
SIN_RFC_EMISOR rfc no_verificable: el documento no trae el RFC del emisor
SIN_NOCERTIFICADO no_certificado no_cumple: el documento no trae NoCertificado
NOCERTIFICADO_DISTINTO no_certificado no_cumple
SIN_CERTIFICADO, CERTIFICADO_ILEGIBLE, XML_ILEGIBLE los tres no_verificable

La lista está en cfdi.MotivosCertificado.

Validación

r := cfdi.ValidarEsquema(xml)    // nunca falla
fmt.Println(r.Estado)            // valido | invalido | incompleto
for _, h := range r.Hallazgos {  // Codigo, Nivel, Ruta, Atributo, Valor, Regla, Mensaje
    fmt.Println(h.Codigo, h.Ruta, h.Regla) // ESQUEMA_CATALOGO /Comprobante/@Moneda enumeration
}
hs := cfdi.ValidarReglas(c)      // sobre el *Comprobante de Parse
v := cfdi.Validar(xml, c)        // las dos juntas (c puede ser nil): v.Estado, v.Esquema, v.Reglas
Esquema (ValidarEsquema)
  • Go no trae validador XSD: internal/genxsd traduce los XSD oficiales (copiados sin cambios en internal/genxsd/xsd/) a esquema_reglas.go (elementos, secuencias, minOccurs/maxOccurs, atributos, tipos y facetas) y esquema_catalogos.go (los xs:enumeration, un valor por línea). Como con la cadena, el generador falla ante cualquier construcción que no conoce (xs:group, xs:choice repetible dentro de una secuencia o anidado, extensiones con contenido propio, list, union, minExclusive, xs:any lax…), revisa también los tipos que hoy no se usan, y una prueba exige que lo generado esté al día (go generate .). Un xs:choice repetible que es todo el contenido (Movimientos del ECB) sí entra: sus ocurrencias son las vueltas de la secuencia, con una opción por vuelta.
  • Esquemas: CFDI 4.0 y 3.3, TimbreFiscalDigital 1.1 y 1.0 y los complementos Pagos 2.0 y 1.0, Nómina 1.2, ImpuestosLocales, CartaPorte 3.1, 3.0 y 2.0, ComercioExterior 2.0 y 1.1, HidroYPetro, CFDIRegistroFiscal, Divisas, EstadoDeCuentaCombustible 1.2, PorCuentadeTerceros 1.1, VentaVehiculos 1.1, Aerolíneas, LeyendasFiscales, iedu y Donatarias 1.1. Terceros y VentaVehiculos traen un xs:choice (que no se repite) y tipos complejos con nombre; ComercioExterior, un catálogo sobre xs:int, que se compara por valor (00 está en el catálogo 0, 1) como lo hace libxml2.
  • Versiones viejas: CartaPorte 1.0, ComercioExterior 1.0 (con sus catálogos en línea, de 2016), EstadoDeCuentaCombustible 1.1, Nómina 1.1, y Donatarias y VentaVehiculos 1.0 como variantes (abajo). ECC 1.1 trae un minInclusive sobre xs:dateTime sin zona (Fecha desde 2015-01-01T00:00:00): sin zona contra sin zona se compara directo; con zona contra sin zona, con el margen de ±14 horas de abajo.
  • De uso común: INE 1.1 (y 1.0 como variante por Version="1.0": sus catálogos difieren, QTO solo está en 1.0 y QUE en 1.1), ValesDeDespensa, ConsumoDeCombustibles 1.0 y 1.1, PagoEnEspecie, NotariosPublicos, servicios parciales de construcción, VehiculoUsado, TuristaPasajeroExtranjero, acreditamientoIEPS y SPEI. Construcciones nuevas del generador, cada una con su prueba: fractionDigits 0 en un xs:int (INE; en los enteros es fixed 0, otro valor se rechaza), y en SPEI xs:all (sus elementos en cualquier orden, cada uno a lo más una vez; el generador exige maxOccurs 1), xs:time y minInclusive/maxInclusive sobre xs:date (se compara como el xs:dateTime de las 00:00:00 del día).
  • Decretos, hidrocarburos y estados de cuenta: renovacionysustitucionvehiculos, certificadodedestruccion, obrasarteantiguedades, pfic, GastosHidrocarburos10 e IngresosHidrocarburos10 (con catHidrocarburos), EstadoDeCuentaBancario (ecb) y EstadoDeCuentaCombustible sin versión (ecc). El generador acepta para Movimientos del ECB un xs:choice repetible que es todo el contenido. Las hojas del SAT los incluyen así: pfic, renovación, destrucción y obras de arte en 3.2, 3.3 y 4.0; gastos e ingresos de hidrocarburos en 3.3 y 4.0; ecb y ecc solo en 3.2. Como la librería no valida CFDI 3.2 contra XSD, el esquema de ecb y ecc solo aplica si vienen en un 3.3 o 4.0; sus fixtures (t10/ecb32.xml, t10/ecc32.xml) prueban la lectura y la cadena.
  • Retenciones 2.0 y 1.0 (retencionpagov2.xsd, retencionpagov1.xsd, con catRetenciones) y sus trece complementos: Plataformas Tecnológicas 1.0, Intereses, Pagos a extranjeros, Dividendos, Enajenación de acciones, Arrendamiento en fideicomiso, Operaciones con derivados, Intereses hipotecarios, Fideicomiso no empresarial, Planes de retiro 1.0 y 1.1, Premios y Sector financiero. Traen dos construcciones nuevas para el generador: un catálogo sobre xs:decimal (c_TasaCuota de Plataformas, por valor: 0.16 está en el catálogo 0.160000) y un minInclusive sobre xs:dateTime (FechaExp de 1.0 desde 2014-01-01T00:00:00-06:00), que se compara como instante con la zona; una fecha sin zona contra el límite se decide solo si queda del mismo lado con cualquier zona de −14:00 a +14:00, como el orden parcial de XML Schema. Plataformas Tecnológicas 1.1 y Intereses de Retenciones 2.0 no están en las fuentes: quedan ESQUEMA_NO_SOPORTADO.
  • TFD 1.0 y 1.1: los dos XSD (TimbreFiscalDigital.xsd y TimbreFiscalDigitalv11.xsd) declaran el mismo elemento en el mismo namespace. libxml2 no puede elegir entre ellos (carga un esquema por namespace), ValidarEsquema sí, elemento por elemento: un TimbreFiscalDigital con version="1.0" (minúsculas, el atributo fixed del XSD de 1.0) se valida con el de 1.0; con Version="1.1", sin versión o con cualquier otro valor, con el de 1.1. El TFD 1.0 es una variante en internal/genxsd.Variantes: el generador exige que su atributo selector sea requerido y fixed y que no exista en el XSD de 1.1. Así, una Retenciones 1.0 o un CFDI 3.3 con TFD 1.0 bien formado es valido. El oráculo de libxml2 lo imita con un segundo envoltorio que trae el XSD de 1.0 en ese namespace y valida con él los documentos cuyos timbres traen version="1.0".
  • También son variantes Donatarias 1.0 (donat.xsd) y VentaVehiculos 1.0 (ventavehiculos.xsd), con version="1.0", e INE 1.0 (ine10.xsd, con Version="1.0"). Ahí el selector sí existe en la raíz, pero fixed con otro valor (1.1), y eso basta: el generador lo acepta solo si los dos valores fixed difieren. Los tipos con nombre de una variante son suyos (VentaVehiculos 1.0 y 1.1 definen los dos t_InformacionAduanera en el mismo namespace). Donatarias 1.0 fija el texto de leyenda; un 1.0 con otra leyenda es ESQUEMA_VALOR_FIJO. El oráculo arma un envoltorio por combinación de variantes, y si un documento mezcla versiones (VentaVehiculos 1.0 y 1.1 en dos conceptos) valida con los dos y de cada uno descarta los errores dentro de los elementos de la otra versión, como hace Go elemento por elemento.
  • Los patrones XSD se traducen a RE2: anclaje implícito (^(?:…)$), ^ y $ literales, . sin \n ni \r, \s = [ \t\n\r], \d = \p{Nd}, \w/\W por categorías Unicode. La resta de clases, los bloques \p{Is…}, \i/\c y los cuantificadores de más de 1000 detienen la generación (ninguno aparece hoy en los XSD del SAT). Detalle en internal/genxsd/patron.go.
  • Valida como libxml2: whiteSpace del tipo antes de las facetas, fractionDigits y totalDigits sobre el valor (2.5000000 tiene un decimal), fechas y enteros con su forma y rango, texto prohibido también en los elementos vacíos (un espacio en <cfdi:Emisor> </cfdi:Emisor> es error), xsi:schemaLocation permitido en cualquier nodo.
  • Los xs:any del SAT (Complemento, ComplementoConcepto, Addenda) son estrictos: un elemento ahí sin esquema en la librería no se valida a medias, queda un aviso ESQUEMA_NO_SOPORTADO con su ruta y el estado es incompleto (si no hay errores). Lo mismo con una raíz que no es CFDI 3.3/4.0 (3.2 incluido) ni Retenciones 1.0/2.0.
  • Lee el XML como un procesador conforme, sin corregir: lo que Parse arregla con una advertencia de encoding, caracteres prohibidos o XML no estricto aquí es ESQUEMA_XML_ILEGIBLE.
  • Códigos (cfdi.CodigosEsquema): ESQUEMA_XML_ILEGIBLE, ESQUEMA_NO_SOPORTADO (aviso), ESQUEMA_ELEMENTO_NO_PERMITIDO, ESQUEMA_ELEMENTO_FALTANTE (minOccurs), ESQUEMA_ELEMENTO_EXCEDENTE (maxOccurs), ESQUEMA_ORDEN, ESQUEMA_CONTENIDO, ESQUEMA_ATRIBUTO_FALTANTE, ESQUEMA_ATRIBUTO_NO_PERMITIDO, ESQUEMA_TIPO, ESQUEMA_PATRON, ESQUEMA_CATALOGO, ESQUEMA_RANGO, ESQUEMA_DIGITOS, ESQUEMA_LONGITUD, ESQUEMA_VALOR_FIJO. Regla dice la faceta o restricción (pattern, enumeration, minInclusive, fractionDigits, minOccurs, orden, required…).
Reglas (ValidarReglas)

Lo que se puede comprobar sin red ni catálogos externos, en CFDI 3.3 y 4.0, con decimal exacto: importe de cada concepto y de cada impuesto con los límites del Anexo 20, SubTotal, Descuento, Total (con implocal), totales de impuestos y grupos por impuesto/factor/tasa, decimales y TipoCambio según c_Moneda, reglas por TipoDeComprobante, ObjetoImp, el comprobante de pago, Pagos 1.0 y 2.0, Comercio Exterior 2.0 y Nómina 1.2. La lista está en cfdi.Reglas; cada regla lleva en Regla.Citas su fundamento en los documentos oficiales del SAT: el código de la matriz de errores (CFDI40167, CRP20217, NOM37…) y la página del estándar o de la guía de llenado. Hallazgo.Regla trae ese texto.

El detalle está en docs/reglas.md: niveles (error, advertencia y aviso), las reglas con dos niveles y su margen, vigencia por fecha, lo que solo pide la guía de llenado, las decisiones de Nómina 1.2 y Comercio Exterior 2.0, lo que queda fuera y la lista de documentos del SAT citados.

Las reglas, con su fundamento
Código Nivel Qué revisa Fundamento (SAT)
REGLA_IMPORTE_CONCEPTO error Importe del concepto entre (Cantidad − ½·10^-dC)·(ValorUnitario − ½·10^-dV) truncado y (Cantidad + ½·10^-dC − 10^-12)·(ValorUnitario + ½·10^-dV − 10^-12) redondeado hacia arriba, a los decimales del propio Importe Matriz CFDI 4.0: CFDI40167; Anexo 20 4.0: p. 76; Matriz CFDI 3.3: CFDI33149; Anexo 20 3.3: p. 68
REGLA_DESCUENTO_CONCEPTO error Descuento del concepto menor o igual que su Importe Matriz CFDI 4.0: CFDI40169; Guía 4.0: p. 24; Matriz CFDI 3.3: CFDI33151
REGLA_IMPORTE_IMPUESTO error Importe del impuesto del concepto entre (Base − ½·10^-dB)·TasaOCuota truncado y (Base + ½·10^-dB − 10^-12)·TasaOCuota redondeado hacia arriba, a los decimales del propio Importe. Dos niveles: fuera de eso pero dentro de los límites a los decimales de la moneda, advertencia (un PAC lo timbra) Matriz CFDI 4.0: CFDI40180, CFDI40186; Anexo 20 4.0: p. 77, 78; Matriz CFDI 3.3: CFDI33161, CFDI33169; Anexo 20 3.3: p. 69, 70
REGLA_EXENTO_CON_IMPORTE error traslado Exento sin TasaOCuota ni Importe Matriz CFDI 4.0: CFDI40177; Anexo 20 4.0: p. 77; Matriz CFDI 3.3: CFDI33157
REGLA_TASA_SIN_IMPORTE error traslado Tasa o Cuota con TasaOCuota e Importe Matriz CFDI 4.0: CFDI40178; Anexo 20 4.0: p. 77; Matriz CFDI 3.3: CFDI33158
REGLA_RETENCION_EXENTA error retención con TipoFactor distinto de Exento Matriz CFDI 4.0: CFDI40184; Anexo 20 4.0: p. 78; Matriz CFDI 3.3: CFDI33166
REGLA_OBJETO_IMP error 4.0: ObjetoImp 02 con nodo Impuestos; 01, 03, 04 y 05 sin él; 06 y 08 sin IVA ni IEPS; 07 sin IVA y con traslado de IEPS Matriz CFDI 4.0: CFDI40171, CFDI40172, CFDI140226, CFDI140227; Anexo 20 4.0: p. 76
REGLA_SUBTOTAL error I, E y N: SubTotal igual a la suma de los Importe de los conceptos o a su redondeo Matriz CFDI 4.0: CFDI40108; Guía 4.0: p. 8; Matriz CFDI 3.3: CFDI33107; Anexo 20 3.3: p. 64
REGLA_DESCUENTO error Descuento igual a la suma de los descuentos de los conceptos o a su redondeo Matriz CFDI 4.0: CFDI40111; Guía 4.0: p. 8; Matriz CFDI 3.3: CFDI33110
REGLA_DESCUENTO_MAYOR_SUBTOTAL error Descuento menor o igual que SubTotal Matriz CFDI 4.0: CFDI40110; Guía 4.0: p. 8; Matriz CFDI 3.3: CFDI33109
REGLA_TOTAL error Total = SubTotal − Descuento + trasladados − retenidos ± locales (implocal), redondeado a la moneda Matriz CFDI 4.0: CFDI40119; Anexo 20 4.0: p. 72; Guía 4.0: p. 10; Matriz CFDI 3.3: CFDI33118
REGLA_TOTAL_TRASLADOS error TotalImpuestosTrasladados igual a la suma de Impuestos/Traslados o a su redondeo. Dos niveles: fuera de eso pero entre la suma de cada Importe truncado y la de cada uno redondeado hacia arriba, advertencia (un PAC lo timbra) Matriz CFDI 4.0: CFDI40205; Anexo 20 4.0: p. 81; Matriz CFDI 3.3: CFDI33183
REGLA_TOTAL_RETENCIONES error TotalImpuestosRetenidos igual a la suma de Impuestos/Retenciones o a su redondeo. Dos niveles: como REGLA_TOTAL_TRASLADOS Matriz CFDI 4.0: CFDI40203; Anexo 20 4.0: p. 81; Matriz CFDI 3.3: CFDI33181
REGLA_IMPUESTOS_SIN_TOTAL error con traslados (no exentos) va TotalImpuestosTrasladados; con retenciones, TotalImpuestosRetenidos Matriz CFDI 4.0: CFDI40212, CFDI40206, CFDI40209; Matriz CFDI 3.3: CFDI33190, CFDI33184, CFDI33187
REGLA_TRASLADO_SUMA error cada Traslado del comprobante (Impuesto, TipoFactor, TasaOCuota) con Importe (y Base en 4.0) igual a la suma de los conceptos o a su redondeo. Dos niveles: fuera de eso pero dentro del margen de redondeo por concepto (cada Importe escrito o Base × TasaOCuota, truncado o redondeado hacia arriba antes de sumar), advertencia (un PAC lo timbra) Matriz CFDI 4.0: CFDI40221, CFDI40215, CFDI40216; Guía 4.0: p. 90; Matriz CFDI 3.3: CFDI33195; Guía 3.3: p. 80
REGLA_TRASLADO_FALTANTE error cada grupo de traslados de los conceptos está en el comprobante; los Exento solo en 4.0 Matriz CFDI 4.0: CFDI40221, CFDI40213; Guía 4.0: p. 90; Matriz CFDI 3.3: CFDI33195; Guía 3.3: p. 32
REGLA_TRASLADO_SOBRANTE error el comprobante no tiene grupos de traslados que no vengan de los conceptos Matriz CFDI 4.0: CFDI40221; Matriz CFDI 3.3: CFDI33195
REGLA_RETENCION_SUMA error cada Retencion del comprobante (Impuesto) igual a la suma de los conceptos o a su redondeo. Dos niveles: como REGLA_TRASLADO_SUMA Matriz CFDI 4.0: CFDI40211; Matriz CFDI 3.3: CFDI33189
REGLA_RETENCION_FALTANTE error cada impuesto retenido en los conceptos está en el comprobante Matriz CFDI 4.0: CFDI40211; Matriz CFDI 3.3: CFDI33189
REGLA_RETENCION_SOBRANTE error el comprobante no tiene retenciones que no vengan de los conceptos Matriz CFDI 4.0: CFDI40211; Matriz CFDI 3.3: CFDI33189
REGLA_IMPLOCAL_TOTALES error TotaldeTraslados y TotaldeRetenciones iguales a la suma de los Importe locales Estándar ImpuestosLocales: p. 2 (sin matriz de errores)
REGLA_DECIMALES_MONEDA error SubTotal, Descuento, Total y totales de impuestos con no más decimales que la moneda Matriz CFDI 4.0: CFDI40107, CFDI40112, CFDI40202, CFDI40204; Anexo 20 4.0: p. 72; Matriz CFDI 3.3: CFDI33106, CFDI33111, CFDI33180, CFDI33182
REGLA_DECIMALES_IMPUESTO error 4.0: Base e Importe de los traslados e Importe de las retenciones del comprobante con no más decimales que la moneda Matriz CFDI 4.0: CFDI40210, CFDI40214, CFDI40220; Anexo 20 4.0: p. 81, 82; Guía 4.0: p. 38, 40
REGLA_DECIMALES_IMPUESTO_33 advertencia 3.3: lo mismo en los traslados y retenciones del comprobante Matriz CFDI 3.3: CFDI33188, CFDI33194 (la matriz pide no aplicarlos); Guía 3.3: p. 32, 34
REGLA_TIPOCAMBIO_MXN error con Moneda MXN, TipoCambio ausente o 1 Matriz CFDI 4.0: CFDI40114; Matriz CFDI 3.3: CFDI33113
REGLA_TIPOCAMBIO_XXX error con Moneda XXX, sin TipoCambio Matriz CFDI 4.0: CFDI40116; Matriz CFDI 3.3: CFDI33115
REGLA_TIPOCAMBIO_REQUERIDO error con otra moneda, TipoCambio requerido Matriz CFDI 4.0: CFDI40115; Matriz CFDI 3.3: CFDI33114
REGLA_TIPOCOMP_CONDICIONES error T, P y N sin CondicionesDePago Anexo 20 4.0: p. 73; Guía 4.0: p. 11; Anexo 20 3.3: p. 66
REGLA_TIPOCOMP_IMPUESTOS error T, P y N sin nodo Impuestos Matriz CFDI 4.0: CFDI40201; Anexo 20 4.0: p. 73; Matriz CFDI 3.3: CFDI33179; Anexo 20 3.3: p. 66
REGLA_TIPOCOMP_FORMAPAGO error T y P sin FormaPago; en 4.0 también N Matriz CFDI 4.0: CFDI40103; Anexo 20 4.0: p. 71; Anexo 20 3.3: p. 66
REGLA_TIPOCOMP_METODOPAGO error T y P sin MetodoPago Matriz CFDI 4.0: CFDI40125; Matriz CFDI 3.3: CFDI33123
REGLA_TIPOCOMP_DESCUENTO error T y P sin Descuento, ni en el comprobante ni en los conceptos Matriz CFDI 4.0: CFDI40111; Anexo 20 4.0: p. 73; Matriz CFDI 3.3: CFDI33110
REGLA_TIPOCOMP_SUBTOTAL_CERO error T y P con SubTotal 0 Matriz CFDI 4.0: CFDI40109; Matriz CFDI 3.3: CFDI33108
REGLA_TIPOCOMP_TOTAL_CERO error T y P con Total 0 Anexo 20 4.0: p. 72; Guía 4.0: p. 10
REGLA_TIPOCOMP_VALOR_UNITARIO error I, E y N con ValorUnitario mayor que cero Matriz CFDI 4.0: CFDI40166; Guía 4.0: p. 23; Matriz CFDI 3.3: CFDI33147
REGLA_TIPOCOMP_MONEDA_NOMINA error N con Moneda MXN Anexo 20 4.0: p. 71; Anexo 20 3.3: p. 65
REGLA_PAGO_COMPROBANTE error tipo P: Moneda XXX, UsoCFDI CP01 (4.0) o P01 (3.3), Exportacion 01, un solo concepto 84111506 / 1 / ACT / Pago / 0 / 0 / ObjetoImp 01, sin NoIdentificacion, Unidad ni nodos hijo Matriz Pagos 2.0: CRP20102, CRP20104, CRP20111-20120, CRP20122; Guía Pagos 2.0: p. 9, 17, 18; Matriz Pagos 1.0: CRP103, CRP110-120; Guía Pagos 1.0: p. 8, 13-15
REGLA_PAGO_SALDO_INSOLUTO error ImpSaldoInsoluto = ImpSaldoAnt − ImpPagado Matriz Pagos 2.0: CRP20244; Guía Pagos 2.0: p. 42; Matriz Pagos 1.0: CRP226; Guía Pagos 1.0: p. 28
REGLA_PAGO_EQUIVALENCIA error Pagos 2.0: EquivalenciaDR 1 con la misma moneda; requerida con otra Matriz Pagos 2.0: CRP20237, CRP20238; Guía Pagos 2.0: p. 40
REGLA_PAGO_TIPOCAMBIODR error Pagos 1.0: TipoCambioDR requerido con otra moneda; ausente con la misma Matriz Pagos 1.0: CRP218, CRP219; Guía Pagos 1.0: p. 26
REGLA_PAGO_MONTO error Monto ≥ suma de ImpPagado / EquivalenciaDR (TipoCambioDR en 1.0) truncada; con otra moneda basta Monto ≥ suma de los límites inferiores truncados Matriz Pagos 2.0: CRP20217, CRP20275-20277; Guía Pagos 2.0: p. 31-34; Matriz Pagos 1.0: CRP206; Guía Pagos 1.0: p. 20
REGLA_PAGO_TIPOCAMBIO error MonedaP distinta de MXN con TipoCambioP; MXN con TipoCambioP 1 (en 1.0 también puede omitirse) Matriz Pagos 2.0: CRP20214, CRP20215; Guía Pagos 2.0: p. 30; Matriz Pagos 1.0: CRP203, CRP204; Guía Pagos 1.0: p. 18, 43
REGLA_PAGOS_TOTALES error Pagos 2.0: cada atributo de Totales igual al redondeo de la suma de los ImpuestosP (y Monto) × TipoCambioP; con ImpuestosP de IVA o retenciones, su atributo en Totales. Dos niveles: fuera de eso pero entre la suma de cada producto truncado y la de cada uno redondeado hacia arriba a 2 decimales, advertencia Matriz Pagos 2.0: CRP20201-20211, CRP20264, CRP20267, CRP20273; Estándar Pagos 2.0: p. 52-57 (sin tolerancia); Guía Pagos 2.0: p. 21, 27
REGLA_PAGO_MONEDA_XXX error MonedaP y MonedaDR distintas de XXX Matriz Pagos 2.0: CRP20213, CRP20236; Matriz Pagos 1.0: CRP202, CRP217
REGLA_PAGO_MAYOR_CERO error Monto, ImpSaldoAnt e ImpPagado mayores que cero Matriz Pagos 2.0: CRP20218, CRP20239, CRP20241; Matriz Pagos 1.0: CRP207, CRP221, CRP223
REGLA_PAGO_DECIMALES error Monto con los decimales de MonedaP; saldos e ImpPagado con los de MonedaDR Matriz Pagos 2.0: CRP20219, CRP20240, CRP20242, CRP20243; Matriz Pagos 1.0: CRP208, CRP222, CRP224, CRP225
REGLA_PAGO_FORMAPAGO error FormaDePagoP distinta de 99 Matriz Pagos 2.0: CRP20212; Matriz Pagos 1.0: CRP201
REGLA_PAGO_CUENTAS error cuentas y RFC ordenante/beneficiario y TipoCadPago según la forma de pago; NomBancoOrdExt con XEXX010101000 Matriz Pagos 2.0: CRP20221, CRP20223, CRP20224, CRP20227-20229; Matriz Pagos 1.0: CRP211, CRP212, CRP214-216, CRP238
REGLA_PAGO_CADENA error CertPago, CadPago y SelloPago van los tres con TipoCadPago y ninguno sin él Matriz Pagos 2.0: CRP20230-20235; Matriz Pagos 1.0: CRP227-232
REGLA_PAGO10_PARCIALIDADES error Pagos 1.0: con PPD, NumParcialidad, ImpSaldoAnt e ImpSaldoInsoluto; ImpPagado con varios documentos o con TipoCambioDR Matriz Pagos 1.0: CRP233-236; Guía Pagos 1.0: p. 27, 28
REGLA_PAGO10_IMPUESTOS error Pagos 1.0: el Pago no lleva nodo Impuestos Matriz Pagos 1.0: CRP237; Guía Pagos 1.0: p. 29
REGLA_PAGO_OBJETO_IMP_DR error Pagos 2.0: ObjetoImpDR 02 con ImpuestosDR; 01, 03, 04 y 05 sin él; 06 y 08 sin IVA ni IEPS; 07 sin IVA y con TrasladoDR de IEPS Matriz Pagos 2.0: CRP20246, CRP20247, CRP20278, CRP20279; Estándar Pagos 2.0: p. 61; Guía Pagos 2.0: p. 43
REGLA_PAGO_IMPUESTO_DR error Pagos 2.0: ImpuestosDR con al menos un traslado o una retención; BaseDR mayor que cero; RetencionDR distinta de Exento; TrasladoDR Exento sin TasaOCuotaDR ni ImporteDR, Tasa o Cuota con los dos Matriz Pagos 2.0: CRP20248, CRP20249, CRP20252, CRP20255, CRP20258, CRP20259; Estándar Pagos 2.0: p. 61-63; Guía Pagos 2.0: p. 43-48
REGLA_PAGO_IMPORTE_DR error Pagos 2.0: ImporteDR entre (BaseDR − ½·10^-dB)·TasaOCuotaDR truncado y (BaseDR + ½·10^-dB − 10^-12)·TasaOCuotaDR redondeado hacia arriba, a los decimales del propio ImporteDR. Dos niveles: fuera de eso pero dentro de los límites a los decimales de MonedaDR ampliados en ±10 unidades (20 conceptos × ½; 0.10 en MXN), advertencia: el impuesto de la factura redondeado concepto por concepto Matriz Pagos 2.0: CRP20254, CRP20261; Estándar Pagos 2.0: p. 62, 63; Guía Pagos 2.0: p. 4, 46; Anexo 20 4.0: p. 77, 78
REGLA_PAGO_IMPUESTOS_P error Pagos 2.0: con impuestos en los documentos, ImpuestosP con una RetencionP por cada ImpuestoDR retenido y un TrasladoP por cada ImpuestoDR, TipoFactorDR y TasaOCuotaDR trasladado, sin repetir ni sobrar; TrasladoP Exento sin TasaOCuotaP ni ImporteP Matriz Pagos 2.0: CRP20263, CRP20266, CRP20271; Estándar Pagos 2.0: p. 25, 27, 63-65; Guía Pagos 2.0: p. 49-51
REGLA_PAGO_IMPUESTOS_P_SUMA error Pagos 2.0: ImporteP de cada RetencionP, y BaseP e ImporteP de cada TrasladoP, iguales a la suma de ImporteDR (BaseDR) / EquivalenciaDR de su grupo en los documentos del pago: exacta, redondeada o truncada a los decimales del atributo (al menos los de MonedaP). Dos niveles: fuera de eso pero entre la suma de cada sumando truncado y la de cada uno redondeado hacia arriba a los decimales de MonedaP, advertencia Matriz Pagos 2.0: CRP20265, CRP20268, CRP20269, CRP20274; Estándar Pagos 2.0: p. 64-66 (sin tolerancia ni redondeo); Guía Pagos 2.0: p. 50, 51, 53
REGLA_CCE_TIPOCOMPROBANTE error Comercio Exterior 2.0 en I, E o T Matriz CCE 2.0: CCE101; Guía CCE 2.0: p. 13
REGLA_CCE_CERTIFICADO error NumCertificadoOrigen solo con CertificadoOrigen 1 Matriz CCE 2.0: CCE118; Guía CCE 2.0: p. 43, 44
REGLA_CCE_TOTALUSD error TotalUSD = suma de ValorDolares redondeada a 2 decimales Matriz CCE 2.0: CCE122; Guía CCE 2.0: p. 45, 46
REGLA_CCE_NOIDENTIFICACION error NoIdentificacion en cada concepto, un concepto por mercancía, sin repetir NoIdentificacion + FraccionArancelaria Matriz CCE 2.0: CCE151-153; Guía CCE 2.0: p. 55
REGLA_CCE_DATOS_ADUANA error CantidadAduana, UnidadAduana y ValorUnitarioAduana todos o ninguno, y en todas las mercancías si alguna los trae o si su NoIdentificacion se repite; ValorUnitarioAduana > 0 salvo UnidadAduana 99 Matriz CCE 2.0: CCE165, CCE166, CCE168; Estándar CCE 2.0: p. 43, 44; Guía CCE 2.0: p. 56, 57
REGLA_CCE_VALOR_DOLARES error ValorDolares entre los límites de CantidadAduana × ValorUnitarioAduana con los decimales de Cantidad y ValorUnitario del concepto, a 2 decimales (0 y 1 no se revisan). Dos niveles: en I y E, fuera de eso pero dentro de los límites del Importe de su concepto en dólares (REGLA_CCE_VALOR_IMPORTE), advertencia Matriz CCE 2.0: CCE169, CCE171; Estándar CCE 2.0: p. 44; Guía CCE 2.0: p. 59, 60, 72
REGLA_NOMINA_TOTALES error TotalPercepciones o TotalOtrosPagos; TotalPercepciones, TotalDeducciones y TotalOtrosPagos solo con sus nodos y contra ellos Matriz Nómina 1.2 Rev D: NOM31, NOM36-40; Guía Nómina: p. 26, 27
REGLA_NOMINA_PERCEPCIONES error totales de Percepciones por tipo de percepción y TotalGravado + TotalExento Matriz Nómina 1.2 Rev D: NOM69-74; Guía Nómina: p. 48-51
REGLA_NOMINA_DEDUCCIONES error TotalImpuestosRetenidos = deducciones 002; sin 002, ausente Matriz Nómina 1.2 Rev D: NOM91, NOM92; Guía Nómina: p. 66
REGLA_NOMINA_IMPORTES error percepciones, deducciones y otros pagos (salvo 002) mayores que cero Matriz Nómina 1.2 Rev D: NOM75, NOM96, NOM100; Guía Nómina: p. 56, 69, 76
REGLA_NOMINA_CONCEPTO error ValorUnitario e Importe = TotalPercepciones + TotalOtrosPagos; Descuento = TotalDeducciones Matriz Nómina 1.2 Rev D: NOM20-22; Guía Nómina: p. 19
REGLA_NOMINA_FECHAS error FechaInicialPago ≤ FechaFinalPago Matriz Nómina 1.2 Rev D: NOM35; Guía Nómina: p. 24
REGLA_NOMINA_SUBCONTRATACION error PorcentajeTiempo de SubContratacion suma 100 Matriz Nómina 1.2 Rev D: NOM68; Guía Nómina: p. 48
REGLA_NOMINA_OTROS_PAGOS error SaldoAFavor ≥ RemanenteSalFav; subsidio ≤ SubsidioCausado Matriz Nómina 1.2 Rev D: NOM102, NOM107; Guía Nómina: p. 77
REGLA_NOMINA_COMPROBANTE error CFDI 4.0 de nómina: Exportacion 01, MetodoPago PUE, sin InformacionGlobal ni FacAtrAdquirente; Nomina/Emisor/Curp solo y siempre con emisor persona física; receptor persona física (Rfc de 13), 605, CN01, sin ResidenciaFiscal ni NumRegIdTrib; un concepto 84111505, Cantidad 1 sin decimales, ACT, «Pago de nómina», ObjetoImp 01, sin NoIdentificacion, Unidad ni nodos hijo. Solo guía: MetodoPago PUE (p. 10), ResidenciaFiscal y NumRegIdTrib (p. 16), advertencia Matriz Nómina 1.2 Rev D: NOM3-8, NOM11-19, NOM23-29; Guía Nómina: p. 10, 12, 13, 15-19
REGLA_NOMINA_PERIODICIDAD error TipoNomina O sin PeriodicidadPago 99; E con 99 Matriz Nómina 1.2 Rev D: NOM33, NOM34; Guía Nómina: p. 22, 42
REGLA_NOMINA_REGISTRO_PATRONAL error RegistroPatronal con TipoContrato 01-08 y sin él con 09, 10 y 99; con él, NumSeguridadSocial, FechaInicioRelLaboral, Antigüedad, RiesgoPuesto y SalarioDiarioIntegrado, y sin él ninguno. Solo guía: «sin él ninguno» (p. 29), advertencia Matriz Nómina 1.2 Rev D: NOM42-44; Estándar Nómina 1.2 Rev D: p. 51; Guía Nómina: p. 28, 29
REGLA_NOMINA_ENTIDAD_SNCF error MontoRecursoPropio solo y siempre con OrigenRecurso IM, y menor que TotalPercepciones + TotalOtrosPagos Matriz Nómina 1.2 Rev D: NOM48-50; Guía Nómina: p. 31, 32
REGLA_NOMINA_ANTIGUEDAD error FechaInicioRelLaboral ≤ FechaFinalPago; Antigüedad no mayor que el tiempo entre las dos, contando el día final (PnW: n × 7 ≤ días + 1; PaYmMdD: inicio + a años + m meses + d días ≤ final + 1 día) Matriz Nómina 1.2 Rev D: NOM53-55; Guía Nómina: p. 34-36
REGLA_NOMINA_TIPO_REGIMEN error TipoRegimen 02-04 con TipoContrato 01-08 y 05-99 con 09 o más; TipoRegimen 02 con OtroPago 002 (salvo 007 u 008); otro TipoRegimen sin 002, 007 ni 008. Vigencia: lo del OtroPago (NOM105, NOM106) es error desde el 2020-02-01; antes, advertencia Matriz Nómina 1.2 Rev D: NOM57, NOM58, NOM105, NOM106; Matriz Nómina 1.2 Rev C: NOM105, NOM106; Matriz Nómina 1.2 Rev B: NOM226, NOM227; Estándar Nómina 1.2 Rev D: p. 52, 57; Guía Nómina: p. 39
REGLA_NOMINA_CUENTA_BANCARIA error CuentaBancaria de 10, 11, 15, 16 o 18 posiciones; CLABE (18) sin Banco y con dígito de control correcto; las demás con Banco Matriz Nómina 1.2 Rev D: NOM62-65; Guía Nómina: p. 43
REGLA_NOMINA_NODOS_PERCEPCION error TotalSueldos, TotalSeparacionIndemnizacion + SeparacionIndemnizacion y TotalJubilacionPensionRetiro + JubilacionPensionRetiro según los tipos de percepción; 039 en una exhibición, 044 en parcialidades; AccionesOTitulos solo y siempre en 045; HorasExtra solo y siempre en 019 Matriz Nómina 1.2 Rev D: NOM77-85, NOM89, NOM90; Guía Nómina: p. 53, 54, 56, 57, 59, 60
REGLA_NOMINA_INCAPACIDADES error percepción 014 o deducción 006 con Incapacidades, y la suma de ImporteMonetario igual a su importe Matriz Nómina 1.2 Rev D: NOM86, NOM87, NOM94, NOM95; Guía Nómina: p. 54, 69
REGLA_NOMINA_COMPENSACION_SALDO error OtroPago 004 con CompensacionSaldosAFavor, 002 con SubsidioAlEmpleo; Año el anterior al de FechaPago, o el mismo si el periodo es diciembre. Vigencia: el Año (NOM103) es error desde el 2020-02-01; antes, advertencia Matriz Nómina 1.2 Rev D: NOM98, NOM99, NOM103; Matriz Nómina 1.2 Rev C: NOM103; Matriz Nómina 1.2 Rev B: NOM223; Estándar Nómina 1.2 Rev D: p. 56, 57; Guía Nómina: p. 73, 78, 123
REGLA_CCE_EXPORTACION error 4.0: Exportacion 02 con Comercio Exterior; con CCE 2.0, Exportacion distinta de 03 y, con 02, Incoterm Matriz CCE 2.0: CCE104, CCE105, CCE120; Estándar CCE 2.0: p. 37; Guía CCE 2.0: p. 14, 45
REGLA_CCE_NODOS_CFDI error CCE 2.0: sin InformacionGlobal, ni ACuentaTerceros, InformacionAduanera o CuentaPredial en los conceptos; en T, ObjetoImp 01; solo con TimbreFiscalDigital, ImpuestosLocales, LeyendasFiscales, CFDIRegistroFiscal y CartaPorte (de los complementos que lee la librería). Solo guía: InformacionAduanera, advertencia Matriz CCE 2.0: CCE106, CCE110-112, CCE115; Estándar CCE 2.0: p. 35, 36; Guía CCE 2.0: p. 17, 28, 32, 42
REGLA_CCE_MOTIVO_TRASLADO error CCE 2.0: MotivoTraslado en T y solo en T; con 05, al menos un Propietario, y sin 05 ninguno; con 01, CfdiRelacionados con TipoRelacion y UUID Matriz CCE 2.0: CCE102, CCE103, CCE116, CCE117; Estándar CCE 2.0: p. 35, 36; Guía CCE 2.0: p. 13, 14, 42, 43
REGLA_CCE_TRASLADO error CCE 2.0 en T: Rfc del receptor igual al del emisor; un solo Destinatario; sin CantidadAduana, los conceptos de la mercancía con Cantidad ≥ 0.001 de hasta 3 decimales, Unidad de c_UnidadAduana y ValorUnitario de hasta 4 decimales (y los de la moneda) Matriz CCE 2.0: CCE108, CCE142, CCE154-156; Estándar CCE 2.0: p. 40-42; Guía CCE 2.0: p. 54, 55, 70
REGLA_CCE_EMISOR error CCE 2.0: Emisor/Curp solo y siempre con Rfc del emisor de 13 caracteres; domicilio del emisor en MEX Matriz CCE 2.0: CCE123-125; Estándar CCE 2.0: p. 37; Guía CCE 2.0: p. 46, 47
REGLA_CCE_NUMREGIDTRIB error CCE 2.0: Receptor/NumRegIdTrib solo y siempre con el Rfc genérico XEXX010101000; con NumRegIdTrib, Receptor y Destinatario fuera de MEX Matriz CCE 2.0: CCE133, CCE134, CCE143; Estándar CCE 2.0: p. 38, 40; Guía CCE 2.0: p. 48, 51
REGLA_CCE_FRACCION error CCE 2.0: FraccionArancelaria ausente en servicios (UnidadAduana 99 o ClaveUnidad E48 en un concepto de la mercancía) y requerida en lo demás Matriz CCE 2.0: CCE159, CCE160, CCE170; Estándar CCE 2.0: p. 43, 44; Guía CCE 2.0: p. 56, 57
REGLA_CCE_VALOR_IMPORTE error CCE 2.0 en I y E: por NoIdentificacion sin ValorDolares 0 ni 1, Σ ValorDolares entre los límites de Σ Importe de sus conceptos × TipoCambio / TipoCambioUSD (cada valor ± ½ de su último decimal, inferior truncado y superior hacia arriba a centésimas); MXN con TipoCambio 1 exacto; USD también Σ Importe ± ½ de su último decimal Matriz CCE 2.0: CCE157, CCE158, CCE172; Estándar CCE 2.0: p. 42-44; Guía CCE 2.0: p. 55, 56, 58, 59
REGLA_CCE_MUESTRAS error CCE 2.0 en I y E: las mercancías 9801000100 (muestras) suman en ValorDolares no más que el Descuento de sus conceptos en dólares (límite superior de la conversión de arriba) Matriz CCE 2.0: CCE163, CCE164; Estándar CCE 2.0: p. 43; Guía CCE 2.0: p. 56, 57

Retenciones

El CFDI de Retenciones e información de pagos (1.0 de 2014, 2.0 desde 2022) tiene su propia API y su propio JSON. Parse no cambia: una raíz Retenciones sigue siendo no_es_comprobante.

switch cfdi.Detectar(xml) {        // TipoComprobante | TipoRetenciones | TipoOtro (solo mira la raíz)
case cfdi.TipoRetenciones:
    rt, err := cfdi.ParseRetencionesBytes(xml) // err: no_es_xml | xml_mal_formado | no_es_retenciones | lectura
    fmt.Println(rt.Version, rt.Totales.MontoTotRet.Original(), rt.Complementos())
    b, _ := rt.JSON()                          // {"tipo_documento":"retenciones","schema_version":"1","retenciones":{…},…}
    cadena, _ := cfdi.CadenaOriginalRetenciones(xml)
    v := cfdi.VerificarRetenciones(xml, certsSAT) // como Verificar
    val := cfdi.ValidarRetenciones(xml)           // solo XSD
}

Modelo. Las dos versiones van en un solo Retenciones con los nombres de la 2.0; la lectura es tan tolerante como la del CFDI (mismas advertencias, rutas desde /Retenciones, con el nombre del atributo que trae el documento). Lo que cambia de nombre en 1.0:

Modelo (2.0) 1.0
NoCertificado, Certificado NumCert, Cert
Emisor.RfcE RFCEmisor (y CURPE → Emisor.CurpE, solo 1.0)
Receptor.NacionalidadR Nacionalidad
Nacional.RfcR, Nacional.CurpR RFCRecep, CURPR
Extranjero.NumRegIdTribR NumRegIdTrib
Periodo.Ejercicio Ejerc
Totales.MontoTotOperacion… MontoTotRet, ImpRetenidos.MontoRet montoTotOperacion… montoTotRet, montoRet
ImpRetenidos.ImpuestoRet Impuesto

Lo exclusivo de 2.0 (LugarExpRetenc, CfdiRetenRelacionados, RegimenFiscalE, DomicilioFiscalR, UtilidadBimestral, ISRCorrespondiente) viene vacío en 1.0. La FechaExp de 1.0 trae zona (el XSD la exige): se lee sin advertencia, Fecha.Time es la hora escrita y el texto conserva la zona; sin zona es FECHA_FORMATO. En 2.0 es como la Fecha del CFDI.

Complementos, todos modelados completos (claves de retenciones en el JSON): timbre_fiscal_digital, servicios_plataformas_tecnologicas (1.0), intereses, pagosaextranjeros, dividendos, enajenacionde_acciones, arrendamientoenfideicomiso, operacionesconderivados, intereseshipotecarios, fideicomisonoempresarial, planesderetiro (1.0 y 1.1 en un objeto con version), premios, sector_financiero. Lo demás (Plataformas Tecnológicas 1.1, Intereses de Retenciones 2.0) queda en complementos_no_soportados con COMPLEMENTO_NO_SOPORTADO: no hay XSD ni XSLT en las fuentes.

JSON. schema_version propio (SchemaVersionRetenciones, "1") y schema/retenciones.v1.schema.json, embebido como cfdi.SchemaRetenciones. Las definiciones compartidas (decimal, fecha, advertencia, hallazgo, sellos, validación, timbre) son las mismas del esquema del CFDI; una prueba lo exige, y otra por reflexión que el esquema y el modelo tengan exactamente las mismas claves.

Cadena original. internal/gencadena traduce también esquemas/retencionpago/1/retenciones.xslt y 2/retenciones.xslt con los trece complementos que incluyen (la 2.0 no incluye Planes de retiro 1.0: en una 2.0 es COMPLEMENTO_SIN_CADENA). Esas hojas traen dos cosas que el generador no conocía: el valor de un atributo de un hijo (./retenciones:Nacional/@RfcR, el del primer Nacional, como en XPath) y apply-templates sobre ImpRetenidos, que no tiene plantilla (XSLT le aplica la integrada, que no escribe nada salvo texto; el generador lo detecta y lo deja anotado en la hoja). Las 52 cadenas de Retenciones de los fixtures coinciden con libxslt, y los dos rechazos son los que dice el oráculo. La del timbre es CadenaOriginalTFD, la misma del CFDI.

Sellos. 1.0 se sella con SHA-1 (todos los ejemplos públicos del SAT, de 2014 a 2021, verifican con SHA-1; se acepta también SHA-256); 2.0 con SHA-256. NoCertificado (NumCert) tiene que ser el del certificado, como en el CFDI.

Validación. ValidarRetenciones es solo el esquema. No hay reglas aritméticas: el SAT no publica para Retenciones matriz de errores ni guía con fórmulas (no están entre los documentos del repo), y la librería no inventa reglas sin fuente. Que MontoTotRet sea la suma de los MontoRet (lo dice la documentación del XSD 2.0) o los totales de Plataformas quedan para cuando haya un documento del SAT que citar.

Pruebas

go test ./...
go test -run TestGolden -update .   # regenera los .golden.json de testdata/
python3 scripts/generar_cadenas.py  # regenera las cadenas esperadas con libxslt (necesita lxml)
go test -run TestSellados -update . # vuelve a sellar testdata/sinteticos/sellados/ con los CSD de prueba
python3 scripts/generar_mutantes.py # regenera testdata/mutantes/ (comprueba cada uno con libxml2)
python3 scripts/validar_xsd.py      # regenera los .esquema.json con libxml2 (necesita lxml)
go generate .                       # regenera cadena_reglas.go y esquema_*.go

Los dos oráculos cubren también Retenciones (esquemas/retencionpago, al lado de sitio_internet), con la lista de complementos que deben llevarse a la cadena escrita a mano en generar_cadenas.py. Las Retenciones selladas de testdata/sinteticos/sellados/ (ret20-plataformas.xml con SHA-256, ret10-extranjero.xml con SHA-1) se regeneran con go test -run TestSelladosRetenciones -update ..

El oráculo del esquema es scripts/validar_xsd.py: valida cada .xml de testdata/ con lxml.etree.XMLSchema y los XSD oficiales (resolver local, sin red) y guarda X.esquema.json con el veredicto (valido, invalido, incompleto) y los errores de libxml2. La prueba exige que ValidarEsquema dé el mismo veredicto en todos. testdata/mutantes/ son sintéticos inválidos a propósito, uno por regla, y MANIFIESTO.json dice qué código debe dar cada uno.

Las cadenas esperadas (*.cadena.txt, *.cadena-tfd.txt, o *.error con el motivo del rechazo) las genera scripts/generar_cadenas.py corriendo los mismos XSLT del SAT con libxslt: son un oráculo independiente del código Go. Además, la cadena que publica CfdiUtils para cfdi33-valid.xml y los sellos genuinos de los fixtures de CfdiUtils, nodecfdi y satcfdi.

Cada .xml bajo testdata/ se compara contra el .golden.json de al lado y se valida contra el esquema; los que están en un directorio fatales/ deben fallar. Agregar fixtures = soltar un directorio y correr -update.

  • testdata/sinteticos/: escritos a mano para este repo.
  • testdata/publicos/<org>-<repo>/: ejemplos de pruebas de proyectos MIT (SAT-CFDI/python-satcfdi, phpcfdi, nodecfdi, eclipxe13, lunasoft), sin modificar salvo los RFC, CURP y nombres que no eran de prueba, que se sustituyeron por identidades de prueba del SAT (la lista está en NOTICE); licencias en NOTICE. INVENTARIO.json dice de dónde sale cada uno (repo, commit, ruta) y su forma (versión, complementos, BOM…), sin RFC ni nombres. Se regenera con python3 scripts/generar_inventario.py, que además revisa los RFC: aborta si un archivo trae certificado de producción (NoCertificado 00001…) y un RFC de persona física que no sea de prueba.

Licencia

MIT (ver LICENSE). El código es propio: no se copió código de phpcfdi, nodecfdi ni CfdiUtils. Lo que viene de terceros conserva su aviso en NOTICE: fixtures y certificados de prueba de proyectos MIT, los casos portados de CfdiUtils en reglas_test.go, el catálogo de monedas de satcfdi, y los XSD y XSLT del SAT tomados de la réplica de phpcfdi/resources-sat-xml (The Unlicense).

Documentation

Overview

Package cfdi lee CFDI 3.3 y 4.0 (facturas electrónicas del SAT, México) y los normaliza a structs Go y a un JSON con esquema versionado.

Lectura tolerante: Parse solo falla (con un *Error de Clase estable) si el documento no es XML o si la raíz no es un Comprobante de los namespaces http://www.sat.gob.mx/cfd/3 o /cfd/4. Lo demás —encodings que mienten, atributos con formato raro, complementos desconocidos, falta de timbre— se lee como se pueda y se reporta en Comprobante.Advertencias con códigos estables (las constantes Adv*).

Montos: decimal.Decimal exacto, nunca float; en JSON van como string con el texto original del XML.

Leer no valida: eso es aparte y sobre los bytes o el Comprobante ya leído. CadenaOriginal y Verificar arman la cadena y verifican los sellos; ValidarEsquema valida contra los XSD del SAT (el CFDI, el timbre y los complementos que lista el README), ValidarReglas revisa las reglas aritméticas y de coherencia, y Validar junta las dos. Cada regla de Reglas cita su fundamento en la matriz de errores, el estándar o la guía de llenado del SAT (Regla.Citas). Nada consulta al SAT ni usa la red.

El CFDI de Retenciones e información de pagos (1.0 y 2.0) tiene su propia API: Detectar dice qué es un XML, ParseRetenciones lo lee (con su JSON y su SchemaRetenciones), CadenaOriginalRetenciones y VerificarRetenciones arman la cadena y verifican los sellos, y ValidarRetenciones lo valida contra los XSD. Parse sigue rechazando una Retenciones (no_es_comprobante).

c, err := cfdi.Parse(f)
if err != nil { ... }            // no es un CFDI
for _, a := range c.Advertencias { ... }
b, _ := c.JSON()                 // {"schema_version":"1","cfdi":{...},"advertencias":[...]}

Index

Examples

Constants

View Source
const (
	// Codificación y forma del XML.
	AdvEncodingCorregido   = "ENCODING_CORREGIDO"   // los bytes no coinciden con el encoding declarado; se convirtieron
	AdvEncodingDesconocido = "ENCODING_DESCONOCIDO" // encoding declarado que no se reconoce; se leyó como UTF-8 o windows-1252
	AdvCaracterInvalido    = "CARACTER_INVALIDO"    // caracteres de control prohibidos en XML 1.0; se quitaron
	AdvXMLNoEstricto       = "XML_NO_ESTRICTO"      // el XML solo se pudo leer en modo no estricto (entidades o & sueltos)
	AdvContenidoTrasRaiz   = "CONTENIDO_TRAS_RAIZ"  // texto o basura después del cierre de cfdi:Comprobante; se ignoró

	// Estructura.
	AdvVersionNoSoportada     = "VERSION_NO_SOPORTADA" // Version que no es 3.0, 3.2, 3.3 ni 4.0
	AdvVersionAntigua         = "VERSION_ANTIGUA"      // CFDI 3.0 o 3.2: lectura parcial, solo lo que coincide con 3.3
	AdvVersionNamespace       = "VERSION_NAMESPACE"    // Version no corresponde al namespace (p. ej. 3.3 en cfd/4)
	AdvNamespaceInesperado    = "NAMESPACE_INESPERADO" // nodo con el nombre esperado pero en otro namespace; se leyó
	AdvNodoFaltante           = "NODO_FALTANTE"        // falta un nodo requerido (Emisor, Receptor, Conceptos…)
	AdvNodoDuplicado          = "NODO_DUPLICADO"       // nodo que debe ser único aparece varias veces; se usa el primero
	AdvAtributoFaltante       = "ATRIBUTO_FALTANTE"    // falta un atributo requerido
	AdvSinTimbre              = "SIN_TIMBRE"           // no hay TimbreFiscalDigital
	AdvComplementoNoSoportado = "COMPLEMENTO_NO_SOPORTADO"
	AdvComplementoVersion     = "COMPLEMENTO_VERSION" // versión de complemento desconocida o que no corresponde a su namespace

	// Valores.
	AdvDecimalInvalido  = "DECIMAL_INVALIDO"   // monto o cantidad que no es xs:decimal; el campo queda vacío
	AdvFechaInvalida    = "FECHA_INVALIDA"     // fecha ilegible; queda solo el texto original
	AdvFechaFormato     = "FECHA_FORMATO"      // fecha legible pero fuera del formato SAT (zona, fracción, sin hora…)
	AdvUUIDInvalido     = "UUID_INVALIDO"      // UUID que no tiene la forma 8-4-4-4-12 hexadecimal
	AdvExentoConImporte = "EXENTO_CON_IMPORTE" // traslado Exento con TasaOCuota o Importe; se conservan, pero no deberían venir
)

Códigos de advertencia. Son parte de la API pública: no se renombran.

View Source
const (
	MotivoXMLIlegible          = "XML_ILEGIBLE"           // XML que un procesador XSLT no leería igual (mal formado, encoding que miente, caracteres prohibidos)
	MotivoNoEsComprobante      = "NO_ES_COMPROBANTE"      // la raíz no es un Comprobante cfd/3 o cfd/4
	MotivoVersionNoSoportada   = "VERSION_NO_SOPORTADA"   // versión del CFDI o del TFD sin hoja de cadena (p. ej. 3.0)
	MotivoComplementoSinCadena = "COMPLEMENTO_SIN_CADENA" // complemento que la librería no sabe llevar a la cadena
	MotivoSinTimbre            = "SIN_TIMBRE"             // no hay TimbreFiscalDigital
)

Motivos por los que no se puede armar una cadena original. Son estables, como los códigos de advertencia, y son también motivos de un sello NO_VERIFICABLE (ver Verificar).

View Source
const (
	ChequeoCumple        = "cumple"         // el documento y el certificado coinciden
	ChequeoNoCumple      = "no_cumple"      // no coinciden
	ChequeoNoVerificable = "no_verificable" // falta un dato para decidir
)

Estados de un chequeo del certificado.

View Source
const (
	MotivoCertificadoNoVigenteAun = "CERTIFICADO_NO_VIGENTE_AUN" // no cumple: la fecha es anterior a NotBefore
	MotivoCertificadoVencido      = "CERTIFICADO_VENCIDO"        // no cumple: la fecha es posterior a NotAfter
	MotivoVigenciaAmbigua         = "VIGENCIA_AMBIGUA"           // no verificable: la fecha sin zona cae a menos de la diferencia de husos del límite
	MotivoFechaIlegible           = "FECHA_ILEGIBLE"             // no verificable: no hay fecha o no se puede leer
	MotivoRFCCertificadoDistinto  = "RFC_CERTIFICADO_DISTINTO"   // no cumple: el RFC del certificado no es el del emisor
	MotivoCertificadoSinRFC       = "CERTIFICADO_SIN_RFC"        // no verificable: el certificado no trae x500UniqueIdentifier
	MotivoSinRFCEmisor            = "SIN_RFC_EMISOR"             // no verificable: el documento no trae el RFC del emisor
	MotivoSinNoCertificado        = "SIN_NOCERTIFICADO"          // no cumple: el documento no trae NoCertificado
	MotivoCertificadoDelSAT       = "CERTIFICADO_DEL_SAT"        // cumple: el titular del certificado es el SAT, que sella a nombre del emisor

)

Motivos de los chequeos del certificado, además de MotivoSinCertificado, MotivoCertificadoIlegible y MotivoXMLIlegible (no verificable). Son estables.

View Source
const (
	ClaseNoEsXML         = "no_es_xml"         // no hay un elemento raíz legible
	ClaseXMLMalFormado   = "xml_mal_formado"   // empieza como XML pero se rompe (truncado, etiquetas cruzadas)
	ClaseNoEsComprobante = "no_es_comprobante" // la raíz no es Comprobante de cfd/3 o cfd/4
	ClaseNoEsRetenciones = "no_es_retenciones" // ParseRetenciones: la raíz no es Retenciones de retencionpago/1 o /2
	ClaseLectura         = "lectura"           // falló la lectura del io.Reader
)

Clases de error fatal. Son estables: el modo lote agrupa por ellas.

View Source
const (
	EsquemaXMLIlegible         = "ESQUEMA_XML_ILEGIBLE"          // un procesador XML conforme no lo leería (mal formado, encoding que miente…)
	EsquemaNoSoportado         = "ESQUEMA_NO_SOPORTADO"          // aviso: raíz o complemento sin esquema en la librería; esa parte no se validó
	EsquemaElementoNoPermitido = "ESQUEMA_ELEMENTO_NO_PERMITIDO" // elemento que el esquema no admite en ese lugar
	EsquemaElementoFaltante    = "ESQUEMA_ELEMENTO_FALTANTE"     // falta un elemento requerido (minOccurs)
	EsquemaElementoExcedente   = "ESQUEMA_ELEMENTO_EXCEDENTE"    // un elemento se repite más de lo que permite maxOccurs
	EsquemaOrden               = "ESQUEMA_ORDEN"                 // elemento fuera del orden de la secuencia
	EsquemaContenido           = "ESQUEMA_CONTENIDO"             // texto donde el esquema no admite texto
	EsquemaAtributoFaltante    = "ESQUEMA_ATRIBUTO_FALTANTE"     // falta un atributo requerido
	EsquemaAtributoNoPermitido = "ESQUEMA_ATRIBUTO_NO_PERMITIDO" // atributo que el esquema no declara
	EsquemaTipo                = "ESQUEMA_TIPO"                  // valor que no es del tipo XSD (decimal, fecha, entero…)
	EsquemaPatron              = "ESQUEMA_PATRON"                // valor que no cumple el pattern
	EsquemaRango               = "ESQUEMA_RANGO"                 // valor fuera de minInclusive/maxInclusive
	EsquemaDigitos             = "ESQUEMA_DIGITOS"               // más decimales (fractionDigits) o dígitos (totalDigits) de los permitidos
	EsquemaLongitud            = "ESQUEMA_LONGITUD"              // longitud fuera de length/minLength/maxLength
	EsquemaValorFijo           = "ESQUEMA_VALOR_FIJO"            // valor distinto del fixed del esquema
)

Códigos de hallazgo de ValidarEsquema. Son parte de la API pública: no se renombran.

View Source
const (
	NivelError       = "error"
	NivelAdvertencia = "advertencia"
	NivelAviso       = "aviso"
)

Niveles de un Hallazgo.

View Source
const (
	ValidacionValida     = "valido"     // sin errores ni avisos (puede traer advertencias)
	ValidacionInvalida   = "invalido"   // al menos un hallazgo de nivel error
	ValidacionIncompleta = "incompleto" // sin errores, pero con partes que no se pudieron validar (avisos)
)

Estados de ResultadoValidacion.

View Source
const (
	NSCFDI33                      = "http://www.sat.gob.mx/cfd/3"
	NSCFDI40                      = "http://www.sat.gob.mx/cfd/4"
	NSTimbre                      = "http://www.sat.gob.mx/TimbreFiscalDigital"
	NSPagos10                     = "http://www.sat.gob.mx/Pagos"
	NSPagos20                     = "http://www.sat.gob.mx/Pagos20"
	NSNomina11                    = "http://www.sat.gob.mx/nomina"
	NSNomina12                    = "http://www.sat.gob.mx/nomina12"
	NSImpLocal                    = "http://www.sat.gob.mx/implocal"
	NSCartaPorte10                = "http://www.sat.gob.mx/CartaPorte"
	NSCartaPorte20                = "http://www.sat.gob.mx/CartaPorte20"
	NSCartaPorte30                = "http://www.sat.gob.mx/CartaPorte30"
	NSCartaPorte31                = "http://www.sat.gob.mx/CartaPorte31"
	NSComercioExterior10          = "http://www.sat.gob.mx/ComercioExterior"
	NSComercioExterior11          = "http://www.sat.gob.mx/ComercioExterior11"
	NSComercioExterior20          = "http://www.sat.gob.mx/ComercioExterior20"
	NSDivisas                     = "http://www.sat.gob.mx/divisas"
	NSRegistroFiscal              = "http://www.sat.gob.mx/registrofiscal"
	NSLeyendasFiscales            = "http://www.sat.gob.mx/leyendasFiscales"
	NSEstadoDeCuentaCombustible11 = "http://www.sat.gob.mx/EstadoDeCuentaCombustible"
	NSEstadoDeCuentaCombustible12 = "http://www.sat.gob.mx/EstadoDeCuentaCombustible12"
	NSHidroYPetro                 = "http://www.sat.gob.mx/hidrocarburospetroliferos"
	NSTerceros                    = "http://www.sat.gob.mx/terceros"
	NSVentaVehiculos              = "http://www.sat.gob.mx/ventavehiculos"
	NSAerolineas                  = "http://www.sat.gob.mx/aerolineas"
	NSDonatarias                  = "http://www.sat.gob.mx/donat"
	NSIEDU                        = "http://www.sat.gob.mx/iedu"
	NSINE                         = "http://www.sat.gob.mx/ine"
	NSValesDeDespensa             = "http://www.sat.gob.mx/valesdedespensa"
	NSConsumoDeCombustibles10     = "http://www.sat.gob.mx/consumodecombustibles"
	NSConsumoDeCombustibles11     = "http://www.sat.gob.mx/ConsumoDeCombustibles11"
	NSPagoEnEspecie               = "http://www.sat.gob.mx/pagoenespecie"
	NSNotariosPublicos            = "http://www.sat.gob.mx/notariospublicos"
	NSServicioParcialConstruccion = "http://www.sat.gob.mx/servicioparcialconstruccion"
	NSVehiculoUsado               = "http://www.sat.gob.mx/vehiculousado"
	NSTuristaPasajeroExtranjero   = "http://www.sat.gob.mx/TuristaPasajeroExtranjero"
	NSAcreditamientoIEPS          = "http://www.sat.gob.mx/acreditamiento"
	NSSPEI                        = "http://www.sat.gob.mx/spei"
	NSRenovacionVehiculos         = "http://www.sat.gob.mx/renovacionysustitucionvehiculos"
	NSCertificadoDestruccion      = "http://www.sat.gob.mx/certificadodestruccion"
	NSObrasArteAntiguedades       = "http://www.sat.gob.mx/arteantiguedades"
	NSPFIC                        = "http://www.sat.gob.mx/pfic"
	NSGastosHidrocarburos         = "http://www.sat.gob.mx/GastosHidrocarburos10"
	NSIngresosHidrocarburos       = "http://www.sat.gob.mx/IngresosHidrocarburos10"
	NSEstadoDeCuentaBancario      = "http://www.sat.gob.mx/ecb"
	NSEstadoDeCuentaCombustible10 = "http://www.sat.gob.mx/ecc"
)

Namespaces del SAT que reconoce la librería.

View Source
const (
	ReglaVersionNoSoportada = "REGLA_VERSION_NO_SOPORTADA" // aviso: versión sin reglas (3.2, 3.0…)

	ReglaImporteConcepto       = "REGLA_IMPORTE_CONCEPTO"
	ReglaDescuentoConcepto     = "REGLA_DESCUENTO_CONCEPTO"
	ReglaImporteImpuesto       = "REGLA_IMPORTE_IMPUESTO"
	ReglaExentoConImporte      = "REGLA_EXENTO_CON_IMPORTE"
	ReglaTasaSinImporte        = "REGLA_TASA_SIN_IMPORTE"
	ReglaRetencionExenta       = "REGLA_RETENCION_EXENTA"
	ReglaObjetoImp             = "REGLA_OBJETO_IMP"
	ReglaSubTotal              = "REGLA_SUBTOTAL"
	ReglaDescuento             = "REGLA_DESCUENTO"
	ReglaDescuentoMayor        = "REGLA_DESCUENTO_MAYOR_SUBTOTAL"
	ReglaTotal                 = "REGLA_TOTAL"
	ReglaTotalTraslados        = "REGLA_TOTAL_TRASLADOS"
	ReglaTotalRetenciones      = "REGLA_TOTAL_RETENCIONES"
	ReglaImpuestosSinTotal     = "REGLA_IMPUESTOS_SIN_TOTAL"
	ReglaTrasladoSuma          = "REGLA_TRASLADO_SUMA"
	ReglaTrasladoFaltante      = "REGLA_TRASLADO_FALTANTE"
	ReglaTrasladoSobrante      = "REGLA_TRASLADO_SOBRANTE"
	ReglaRetencionSuma         = "REGLA_RETENCION_SUMA"
	ReglaRetencionFaltante     = "REGLA_RETENCION_FALTANTE"
	ReglaRetencionSobrante     = "REGLA_RETENCION_SOBRANTE"
	ReglaImpLocalTotales       = "REGLA_IMPLOCAL_TOTALES"
	ReglaDecimalesMoneda       = "REGLA_DECIMALES_MONEDA"
	ReglaDecimalesImpuesto     = "REGLA_DECIMALES_IMPUESTO"
	ReglaDecimalesImpuesto33   = "REGLA_DECIMALES_IMPUESTO_33"
	ReglaTipoCambioMXN         = "REGLA_TIPOCAMBIO_MXN"
	ReglaTipoCambioXXX         = "REGLA_TIPOCAMBIO_XXX"
	ReglaTipoCambioRequerido   = "REGLA_TIPOCAMBIO_REQUERIDO"
	ReglaTipoCompCondiciones   = "REGLA_TIPOCOMP_CONDICIONES"
	ReglaTipoCompImpuestos     = "REGLA_TIPOCOMP_IMPUESTOS"
	ReglaTipoCompFormaPago     = "REGLA_TIPOCOMP_FORMAPAGO"
	ReglaTipoCompMetodoPago    = "REGLA_TIPOCOMP_METODOPAGO"
	ReglaTipoCompDescuento     = "REGLA_TIPOCOMP_DESCUENTO"
	ReglaTipoCompSubTotalCero  = "REGLA_TIPOCOMP_SUBTOTAL_CERO"
	ReglaTipoCompTotalCero     = "REGLA_TIPOCOMP_TOTAL_CERO"
	ReglaTipoCompValorUnitario = "REGLA_TIPOCOMP_VALOR_UNITARIO"
	ReglaTipoCompMonedaNomina  = "REGLA_TIPOCOMP_MONEDA_NOMINA"
	ReglaPagoComprobante       = "REGLA_PAGO_COMPROBANTE"
	ReglaPagoSaldoInsoluto     = "REGLA_PAGO_SALDO_INSOLUTO"
	ReglaPagoEquivalencia      = "REGLA_PAGO_EQUIVALENCIA"
	ReglaPagoTipoCambioDR      = "REGLA_PAGO_TIPOCAMBIODR"
	ReglaPagoMonto             = "REGLA_PAGO_MONTO"
	ReglaPagoTipoCambio        = "REGLA_PAGO_TIPOCAMBIO"
	ReglaPagosTotales          = "REGLA_PAGOS_TOTALES"
	ReglaPagoMonedaXXX         = "REGLA_PAGO_MONEDA_XXX"
	ReglaPagoMayorCero         = "REGLA_PAGO_MAYOR_CERO"
	ReglaPagoDecimales         = "REGLA_PAGO_DECIMALES"
	ReglaPagoFormaPago         = "REGLA_PAGO_FORMAPAGO"
	ReglaPagoCuentas           = "REGLA_PAGO_CUENTAS"
	ReglaPagoCadena            = "REGLA_PAGO_CADENA"
	ReglaPago10Parcialidades   = "REGLA_PAGO10_PARCIALIDADES"
	ReglaPago10Impuestos       = "REGLA_PAGO10_IMPUESTOS"
)

Códigos de hallazgo de ValidarReglas. Son parte de la API pública: no se renombran. La referencia de cada uno (de dónde sale la regla) está en Reglas.

View Source
const (
	DocMatriz40      = "Matriz de errores CFDI 4.0 (2026-03-25)"
	DocEstandar40    = "Anexo 20 CFDI 4.0 (DOF 2022-01-13)"
	DocGuia40        = "Guía de llenado CFDI 4.0 (2026-01-16)"
	DocMatriz33      = "Matriz de errores CFDI 3.3"
	DocEstandar33    = "Anexo 20 CFDI 3.3 (DOF 2017-07-28)"
	DocGuia33        = "Guía de llenado CFDI 3.3 (2019-10-14)"
	DocMatrizPagos20 = "Matriz de errores Pagos 2.0 Rev B"
	DocGuiaPagos20   = "Guía de llenado Pagos 2.0 (2026-01-16)"
	DocEstPagos20    = "Estándar Pagos 2.0 Rev B"
	DocMatrizPagos10 = "Matriz de errores Pagos 1.0"
	DocGuiaPagos10   = "Guía de llenado Pagos 1.0 (2018-08-31)"
	DocImpLocal      = "Estándar ImpuestosLocales"
)

Documentos oficiales del SAT que citan las reglas.

View Source
const (
	ReglaCCEExportacion    = "REGLA_CCE_EXPORTACION"
	ReglaCCENodosCFDI      = "REGLA_CCE_NODOS_CFDI"
	ReglaCCEMotivoTraslado = "REGLA_CCE_MOTIVO_TRASLADO"
	ReglaCCETraslado       = "REGLA_CCE_TRASLADO"
	ReglaCCEEmisor         = "REGLA_CCE_EMISOR"
	ReglaCCENumRegIdTrib   = "REGLA_CCE_NUMREGIDTRIB"
	ReglaCCEFraccion       = "REGLA_CCE_FRACCION"
	ReglaCCEValorImporte   = "REGLA_CCE_VALOR_IMPORTE"
	ReglaCCEMuestras       = "REGLA_CCE_MUESTRAS"
)

Reglas de presencia y coherencia de Comercio Exterior 2.0 (matriz CCE 2.0, CCE102-172) que se pueden comprobar sin catálogos externos: lo que pide c_Pais (Unión Europea, patrones de NumRegIdTrib y de código postal), c_Estado, c_Municipio, c_Localidad, c_Colonia, c_CodigoPostal, c_FraccionArancelaria (vigencia y UMT), la lista de RFC y el tipo de cambio publicado queda fuera (ver el README).

View Source
const (
	ReglaCCETipoComprobante  = "REGLA_CCE_TIPOCOMPROBANTE"
	ReglaCCECertificado      = "REGLA_CCE_CERTIFICADO"
	ReglaCCETotalUSD         = "REGLA_CCE_TOTALUSD"
	ReglaCCENoIdentificacion = "REGLA_CCE_NOIDENTIFICACION"
	ReglaCCEDatosAduana      = "REGLA_CCE_DATOS_ADUANA"
	ReglaCCEValorDolares     = "REGLA_CCE_VALOR_DOLARES"

	ReglaNominaTotales         = "REGLA_NOMINA_TOTALES"
	ReglaNominaPercepciones    = "REGLA_NOMINA_PERCEPCIONES"
	ReglaNominaDeducciones     = "REGLA_NOMINA_DEDUCCIONES"
	ReglaNominaImportes        = "REGLA_NOMINA_IMPORTES"
	ReglaNominaConcepto        = "REGLA_NOMINA_CONCEPTO"
	ReglaNominaFechas          = "REGLA_NOMINA_FECHAS"
	ReglaNominaSubContratacion = "REGLA_NOMINA_SUBCONTRATACION"
	ReglaNominaOtrosPagos      = "REGLA_NOMINA_OTROS_PAGOS"
)

Códigos de las reglas de Comercio Exterior 2.0 y Nómina 1.2.

View Source
const (
	DocMatrizCCE20    = "Matriz de errores Comercio Exterior 2.0"
	DocGuiaCCE20      = "Guía de llenado Comercio Exterior 2.0"
	DocMatrizNomina12 = "Matriz de errores Nómina 1.2 Rev D (2025-02-21)"
	DocGuiaNomina12   = "Guía de llenado Nómina 1.2"
	// DocMatrizNomina12B es la matriz de la Rev B (publicada el
	// 2019-12-17), la primera que trae lo que agregó la Rev B del estándar;
	// fija su vigencia (ver Regla.Vigencia). DocMatrizNomina12C es la de la
	// Rev C, con la numeración actual de esos códigos.
	DocMatrizNomina12B = "Matriz de errores Nómina 1.2 Rev B (2019-12-17)"
	DocMatrizNomina12C = "Matriz de errores Nómina 1.2 Rev C (2021-12-29)"
	// DocEstNomina12 es el estándar del complemento; sus páginas son las
	// del PDF (no las imprime). Su control de cambios (p. 56-57) dice qué
	// agregó cada revisión.
	DocEstNomina12 = "Estándar Nómina 1.2 Rev D (2025-02-21)"
)

Documentos del SAT de los complementos.

View Source
const (
	ReglaNominaComprobante       = "REGLA_NOMINA_COMPROBANTE"
	ReglaNominaPeriodicidad      = "REGLA_NOMINA_PERIODICIDAD"
	ReglaNominaRegistroPatronal  = "REGLA_NOMINA_REGISTRO_PATRONAL"
	ReglaNominaEntidadSNCF       = "REGLA_NOMINA_ENTIDAD_SNCF"
	ReglaNominaAntiguedad        = "REGLA_NOMINA_ANTIGUEDAD"
	ReglaNominaTipoRegimen       = "REGLA_NOMINA_TIPO_REGIMEN"
	ReglaNominaCuentaBancaria    = "REGLA_NOMINA_CUENTA_BANCARIA"
	ReglaNominaNodosPercepcion   = "REGLA_NOMINA_NODOS_PERCEPCION"
	ReglaNominaIncapacidades     = "REGLA_NOMINA_INCAPACIDADES"
	ReglaNominaCompensacionSaldo = "REGLA_NOMINA_COMPENSACION_SALDO"
)

Reglas de Nómina 1.2 que llegaron en T8 (matriz Rev D, NOM3-29 del comprobante y NOM31-106 del complemento): presencia de nodos por tipo de percepción, deducción y otro pago, relación laboral, cuenta bancaria y el CFDI de nómina 4.0. Lo que necesita la lista de RFC, la marca del SNCF o los topes del subsidio vigentes queda fuera (ver el README).

View Source
const (
	ReglaPagoObjetoImpDR    = "REGLA_PAGO_OBJETO_IMP_DR"
	ReglaPagoImpuestoDR     = "REGLA_PAGO_IMPUESTO_DR"
	ReglaPagoImporteDR      = "REGLA_PAGO_IMPORTE_DR"
	ReglaPagoImpuestosP     = "REGLA_PAGO_IMPUESTOS_P"
	ReglaPagoImpuestosPSuma = "REGLA_PAGO_IMPUESTOS_P_SUMA"
)

Reglas de los impuestos de Pagos 2.0: los ImpuestosDR de cada documento relacionado y los ImpuestosP de cada pago (matriz de Pagos 2.0 Rev B, CRP20246-20279; estándar Rev B p. 61-66).

View Source
const (
	NSRetenciones10                 = "http://www.sat.gob.mx/esquemas/retencionpago/1"
	NSRetenciones20                 = "http://www.sat.gob.mx/esquemas/retencionpago/2"
	NSRetPlataformasTecnologicas10  = "http://www.sat.gob.mx/esquemas/retencionpago/1/PlataformasTecnologicas10"
	NSRetIntereses                  = "http://www.sat.gob.mx/esquemas/retencionpago/1/intereses"
	NSRetPagosAExtranjeros          = "http://www.sat.gob.mx/esquemas/retencionpago/1/pagosaextranjeros"
	NSRetDividendos                 = "http://www.sat.gob.mx/esquemas/retencionpago/1/dividendos"
	NSRetEnajenacionDeAcciones      = "http://www.sat.gob.mx/esquemas/retencionpago/1/enajenaciondeacciones"
	NSRetArrendamientoEnFideicomiso = "http://www.sat.gob.mx/esquemas/retencionpago/1/arrendamientoenfideicomiso"
	NSRetOperacionesConDerivados    = "http://www.sat.gob.mx/esquemas/retencionpago/1/operacionesconderivados"
	NSRetInteresesHipotecarios      = "http://www.sat.gob.mx/esquemas/retencionpago/1/intereseshipotecarios"
	NSRetFideicomisoNoEmpresarial   = "http://www.sat.gob.mx/esquemas/retencionpago/1/fideicomisonoempresarial"
	NSRetPlanesDeRetiro10           = "http://www.sat.gob.mx/esquemas/retencionpago/1/planesderetiro"
	NSRetPlanesDeRetiro11           = "http://www.sat.gob.mx/esquemas/retencionpago/1/planesderetiro11"
	NSRetPremios                    = "http://www.sat.gob.mx/esquemas/retencionpago/1/premios"
	NSRetSectorFinanciero           = "http://www.sat.gob.mx/esquemas/retencionpago/1/sectorfinanciero"
)

Namespaces del CFDI de Retenciones e información de pagos y de sus complementos.

View Source
const (
	SelloValido        = "valido"         // la firma corresponde a la cadena y al certificado
	SelloInvalido      = "invalido"       // el documento dice algo que su sello no respalda
	SelloNoVerificable = "no_verificable" // no se pudo decidir (cadena no garantizable, certificado ilegible o ausente…)
)

Estado del resultado de verificar un sello.

View Source
const (
	MotivoSinSello                 = "SIN_SELLO"                     // inválido: no hay Sello (o SelloSAT)
	MotivoSelloIlegible            = "SELLO_ILEGIBLE"                // inválido: el sello no es base64
	MotivoSelloNoCoincide          = "SELLO_NO_COINCIDE"             // inválido: la firma no es de esa cadena con esa llave
	MotivoNoCertificadoDistinto    = "NOCERTIFICADO_DISTINTO"        // inválido: NoCertificado no es el número del certificado
	MotivoSelloCFDDistinto         = "SELLO_CFD_DISTINTO"            // inválido: el SelloCFD del TFD no es el Sello del comprobante
	MotivoSinCertificado           = "SIN_CERTIFICADO"               // no verificable: no viene el Certificado
	MotivoCertificadoIlegible      = "CERTIFICADO_ILEGIBLE"          // no verificable: no es base64 de un X.509 DER
	MotivoLlaveNoRSA               = "LLAVE_NO_RSA"                  // no verificable: el certificado no trae llave RSA
	MotivoCertificadoSATFaltante   = "CERTIFICADO_SAT_NO_DISPONIBLE" // no verificable: el proveedor no dio el certificado del SAT
	MotivoNoCertificadoSATDistinto = "NOCERTIFICADO_SAT_DISTINTO"    // no verificable: el proveedor dio otro certificado
)

Motivos de un resultado de sello, además de los de ErrorCadena (que dan SelloNoVerificable). Son estables.

View Source
const DocEstCCE20 = "Estándar Comercio Exterior 2.0"

DocEstCCE20 es el estándar del complemento; sus páginas son las del PDF (no las imprime).

View Source
const MotivoNoEsRetenciones = "NO_ES_RETENCIONES"

MotivoNoEsRetenciones es el motivo de CadenaOriginalRetenciones (y de un sello no verificable de VerificarRetenciones) cuando la raíz no es un Retenciones de retencionpago/1 o retencionpago/2.

View Source
const SchemaVersion = "1"

SchemaVersion es la versión del formato JSON. Sube cuando un cambio deja de ser compatible (renombrar o quitar una clave, cambiar un tipo); agregar claves opcionales no la sube.

View Source
const SchemaVersionRetenciones = "1"

SchemaVersionRetenciones es la versión del formato JSON de Retenciones (schema/retenciones.v1.schema.json). Sube por su cuenta, como SchemaVersion la del CFDI.

Variables

Codigos lista todos los códigos de advertencia, en el orden de arriba.

CodigosEsquema lista los códigos de ValidarEsquema, en el orden de arriba.

View Source
var CodigosReglas = func() []string {
	out := make([]string, len(Reglas))
	for i, r := range Reglas {
		out[i] = r.Codigo
	}
	return out
}()

CodigosReglas lista los códigos de ValidarReglas, en el orden de Reglas.

View Source
var ErrCertificadoNoEncontrado = errors.New("certificado no encontrado")

ErrCertificadoNoEncontrado lo devuelve el proveedor de CertificadosEn.

Motivos lista todos los motivos de cadena y de sello, en el orden de arriba.

MotivosCertificado lista los motivos que pueden salir en ChequeosCertificado, en el orden de arriba y luego los compartidos.

View Source
var Reglas = concatenar(reglasCFDI, reglasPagos, reglasPagosImpuestos, reglasComplementos, reglasCCE, reglasNomina)

Reglas lista las reglas que evalúa ValidarReglas, en el orden de los códigos. Cada una con su fundamento en los documentos del SAT (T7: se contrastaron todas contra la matriz de errores, el estándar y la guía de llenado de cada versión; lo que no coincidía se corrigió).

View Source
var Schema []byte

Schema es el JSON Schema (draft 2020-12) del Documento: el mismo archivo que schema/cfdi.v1.schema.json, embebido para quien quiera validar o publicar la salida.

View Source
var SchemaRetenciones []byte

SchemaRetenciones es el JSON Schema del DocumentoRetenciones: schema/retenciones.v1.schema.json.

Functions

func CadenaOriginal

func CadenaOriginal(xml []byte) (string, error)

CadenaOriginal arma la cadena original del comprobante (CFDI 3.2, 3.3 o 4.0) tal como la arman los XSLT del SAT: el orden y los atributos los fijan esas hojas (traducidas a Go en cadena_reglas.go), los nodos que se repiten van en el orden del documento y cada valor pasa por normalize-space.

Trabaja sobre los bytes y no sobre el Comprobante porque la cadena depende del documento tal cual (atributos vacíos, espacios, nodos en otro namespace) y no de lo que el modelo normaliza. Devuelve *ErrorCadena si el XML no es estrictamente válido, si la versión no tiene hoja o si trae un complemento que la librería no sabe llevar a la cadena: en ese caso el SAT lo incluiría y una cadena sin él saldría mal.

Tampoco arma la cadena (MotivoXMLIlegible) si los bytes no son del encoding que declaran o traen caracteres prohibidos en XML: Parse los lee corrigiéndolos, pero un procesador XSLT los rechazaría o leería otros caracteres, y no hay forma de saber cuáles firmó el emisor.

Example
package main

import (
	"fmt"
	"log"

	"github.com/OrtaMarco/cfdi-go"
)

// Una factura 4.0 mínima, sin sello ni timbre, con el RFC de prueba del SAT.
const facturaEjemplo = `<?xml version="1.0" encoding="UTF-8"?>
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4" Version="4.0" Serie="A" Folio="1"
    Fecha="2026-01-15T10:30:00" Sello="" NoCertificado="30001000000500003416" Certificado=""
    SubTotal="100.00" Moneda="MXN" Total="116.00" TipoDeComprobante="I" Exportacion="01"
    MetodoPago="PUE" FormaPago="03" LugarExpedicion="20000">
  <cfdi:Emisor Rfc="EKU9003173C9" Nombre="ESCUELA KEMPER URGATE" RegimenFiscal="601"/>
  <cfdi:Receptor Rfc="XAXX010101000" Nombre="PUBLICO EN GENERAL" DomicilioFiscalReceptor="20000"
      RegimenFiscalReceptor="616" UsoCFDI="S01"/>
  <cfdi:Conceptos>
    <cfdi:Concepto ClaveProdServ="01010101" Cantidad="1" ClaveUnidad="H87" Descripcion="Artículo"
        ValorUnitario="100.00" Importe="100.00" ObjetoImp="02">
      <cfdi:Impuestos>
        <cfdi:Traslados>
          <cfdi:Traslado Base="100.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="16.00"/>
        </cfdi:Traslados>
      </cfdi:Impuestos>
    </cfdi:Concepto>
  </cfdi:Conceptos>
  <cfdi:Impuestos TotalImpuestosTrasladados="16.00">
    <cfdi:Traslados>
      <cfdi:Traslado Base="100.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="16.00"/>
    </cfdi:Traslados>
  </cfdi:Impuestos>
</cfdi:Comprobante>`

func main() {
	cadena, err := cfdi.CadenaOriginal([]byte(facturaEjemplo))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(cadena[:40] + "…")
}
Output:
||4.0|A|1|2026-01-15T10:30:00|03|3000100…

func CadenaOriginalRetenciones

func CadenaOriginalRetenciones(xml []byte) (string, error)

CadenaOriginalRetenciones arma la cadena original de un CFDI de Retenciones e información de pagos 1.0 o 2.0, como CadenaOriginal la del CFDI: con las hojas retenciones.xslt del SAT (traducidas en cadena_reglas.go), que incluyen los trece complementos de la 1.0 (ServiciosPlataformasTecnologicas, Intereses, Pagosaextranjeros, Dividendos, EnajenaciondeAcciones, Arrendamientoenfideicomiso, Operacionesconderivados, Intereseshipotecarios, Fideicomisonoempresarial, Planesderetiro 1.0 y 1.1, Premios y SectorFinanciero). La hoja 2.0 no incluye Planesderetiro 1.0. Cualquier otro complemento, o uno que la hoja de esa versión no incluye, da *ErrorCadena con COMPLEMENTO_SIN_CADENA.

La cadena del TimbreFiscalDigital de una Retenciones la arma CadenaOriginalTFD, igual que la de un CFDI.

func CadenaOriginalTFD

func CadenaOriginalTFD(xml []byte) (string, error)

CadenaOriginalTFD arma la cadena original del TimbreFiscalDigital (1.0 o 1.1) del CFDI, que es lo que firma el SelloSAT.

func ClaseDe

func ClaseDe(err error) string

ClaseDe devuelve la clase de un error de Parse, o "" si no es un *Error.

func EstadoDe

func EstadoDe(hs []Hallazgo) string

EstadoDe resume una lista de hallazgos en uno de los Validacion*. Solo los errores hacen inválido un documento; las advertencias no cambian el estado.

func NoCertificado

func NoCertificado(c *x509.Certificate) string

NoCertificado es el número de certificado en el formato del SAT: el número de serie del certificado, cuyos bytes son los dígitos en ASCII (serie 0x3330303031… → "30001…"). Si los bytes no son ASCII imprimible (un certificado que no es del SAT), devuelve la serie en hexadecimal.

func RFCDelCertificado

func RFCDelCertificado(c *x509.Certificate) string

RFCDelCertificado devuelve el RFC del titular de un certificado del SAT: el x500UniqueIdentifier (OID 2.5.4.45) del sujeto, que en una persona moral trae "RFC / RFC del representante" (cuenta el primero). "" si no lo trae.

func TieneHallazgo

func TieneHallazgo(hs []Hallazgo, codigo string) bool

TieneHallazgo dice si hay al menos un hallazgo con ese código.

Types

type ACuentaTerceros

type ACuentaTerceros struct {
	RfcACuentaTerceros             string `json:"rfc_a_cuenta_terceros,omitempty"`
	NombreACuentaTerceros          string `json:"nombre_a_cuenta_terceros,omitempty"`
	RegimenFiscalACuentaTerceros   string `json:"regimen_fiscal_a_cuenta_terceros,omitempty"`
	DomicilioFiscalACuentaTerceros string `json:"domicilio_fiscal_a_cuenta_terceros,omitempty"`
}

ACuentaTerceros es el nodo cfdi:ACuentaTerceros de un concepto (CFDI 4.0).

type AccionesOTitulos

type AccionesOTitulos struct {
	ValorMercado      *decimal.Decimal `json:"valor_mercado,omitempty"`
	PrecioAlOtorgarse *decimal.Decimal `json:"precio_al_otorgarse,omitempty"`
}

AccionesOTitulos es el nodo nomina12:AccionesOTitulos de una Percepcion del complemento Nómina.

type AcreditamientoIEPS

type AcreditamientoIEPS struct {
	Version string `json:"version,omitempty"`
	TAR     string `json:"tar,omitempty"`
}

AcreditamientoIEPS 1.0 (acreditamiento, nodo acreditamientoIEPS), complemento de concepto: TAR de la enajenación de combustible.

type Advertencia

type Advertencia struct {
	Codigo  string `json:"codigo"`
	Ruta    string `json:"ruta,omitempty"`
	Mensaje string `json:"mensaje,omitempty"`
	// Atributo es el nombre del atributo de la advertencia ("" si es de un
	// nodo o del documento). Es lo que va después de "/@" en Ruta.
	Atributo string `json:"atributo,omitempty"`
	// Valor es el texto original del atributo, tal como venía, en las
	// advertencias de valores: DECIMAL_INVALIDO (el campo quedó vacío y este
	// es el único lugar con el texto), FECHA_INVALIDA, FECHA_FORMATO y
	// UUID_INVALIDO. En el JSON se omite si es "".
	Valor string `json:"valor,omitempty"`
}

Advertencia describe algo raro que se encontró al leer un CFDI sin que impida leerlo. Codigo es estable entre versiones (sirve para agrupar en lote); Ruta señala el nodo o atributo; Mensaje es para personas y puede cambiar.

Formato de Ruta (contrato estable)

Ruta es una ruta de nodos por nombre local, sin prefijos de namespace, desde la raíz: "/Comprobante/Conceptos/Concepto[2]/Impuestos/Traslados/Traslado[1]". Si la advertencia es de un atributo, termina en "/@Nombre" y Atributo lleva ese Nombre; Nodo() devuelve la ruta sin él.

  • Los nodos que el esquema permite repetir llevan su posición, desde 1, entre corchetes y siempre, también el primero: Concepto[1], CfdiRelacionados[1], Pago[1], Traslado[1]…
  • Los nodos únicos (Emisor, Impuestos, Totales…) van sin índice. Si un nodo único viene repetido, la ruta es la del primero.
  • Excepción: Complemento y los complementos que se acumulan (Nomina, HidroYPetro) van sin índice el primero y con [n] desde el segundo ("/Comprobante/Complemento/Nomina", ".../Nomina[2]").
  • Las advertencias del documento entero (encoding, XML no estricto, contenido tras la raíz) van con Ruta "".

El nombre del atributo es el que pide el SAT ("Version", "UUID"), aunque el documento lo traiga en minúsculas (TFD 1.0, CFDI 3.2).

func (Advertencia) Nodo

func (a Advertencia) Nodo() string

Nodo es la ruta del nodo de la advertencia: Ruta sin el "/@Atributo" final.

type Aerolineas

type Aerolineas struct {
	Version     string                 `json:"version,omitempty"`
	TUA         *decimal.Decimal       `json:"tua,omitempty"`
	OtrosCargos *AerolineasOtrosCargos `json:"otros_cargos,omitempty"`
}

Aerolineas 1.0: TUA y otros cargos del boleto de avión.

type AerolineasCargo

type AerolineasCargo struct {
	CodigoCargo string           `json:"codigo_cargo,omitempty"`
	Importe     *decimal.Decimal `json:"importe,omitempty"`
}

AerolineasCargo es el nodo aerolineas:Cargo de OtrosCargos del complemento Aerolíneas.

type AerolineasOtrosCargos

type AerolineasOtrosCargos struct {
	TotalCargos *decimal.Decimal  `json:"total_cargos,omitempty"`
	Cargo       []AerolineasCargo `json:"cargo,omitempty"`
}

AerolineasOtrosCargos es el nodo aerolineas:OtrosCargos del complemento Aerolíneas.

type AportacionesODepositos

type AportacionesODepositos struct {
	TipoAportacionODeposito string           `json:"tipo_aportacion_o_deposito,omitempty"`
	MontAportODep           *decimal.Decimal `json:"mont_aport_o_dep,omitempty"`
	RFCFiduciaria           string           `json:"rfc_fiduciaria,omitempty"`
}

AportacionesODepositos es el nodo planesderetiro11:AportacionesODepositos del complemento de Retenciones PlanesDeRetiro 1.1.

type Arrendamientoenfideicomiso

type Arrendamientoenfideicomiso struct {
	Version                    string           `json:"version,omitempty"`
	PagProvEfecPorFiduc        *decimal.Decimal `json:"pag_prov_efec_por_fiduc,omitempty"`
	RendimFideicom             *decimal.Decimal `json:"rendim_fideicom,omitempty"`
	DeduccCorresp              *decimal.Decimal `json:"deducc_corresp,omitempty"`
	MontTotRet                 *decimal.Decimal `json:"mont_tot_ret,omitempty"`
	MontResFiscDistFibras      *decimal.Decimal `json:"mont_res_fisc_dist_fibras,omitempty"`
	MontOtrosConceptDistr      *decimal.Decimal `json:"mont_otros_concept_distr,omitempty"`
	DescrMontOtrosConceptDistr string           `json:"descr_mont_otros_concept_distr,omitempty"`
}

Arrendamientoenfideicomiso es el complemento de Retenciones arrendamientoenfideicomiso:Arrendamientoenfideicomiso.

type Autotransporte

type Autotransporte struct {
	PermSCT                 string                   `json:"perm_sct,omitempty"`
	NumPermisoSCT           string                   `json:"num_permiso_sct,omitempty"`
	IdentificacionVehicular *IdentificacionVehicular `json:"identificacion_vehicular,omitempty"`
	Seguros                 *Seguros                 `json:"seguros,omitempty"`
	Remolques               []Remolque               `json:"remolques,omitempty"`
}

Autotransporte es el nodo Autotransporte del complemento CartaPorte (2.0 y 3.x).

type AutotransporteFederal

type AutotransporteFederal struct {
	PermSCT                 string                   `json:"perm_sct,omitempty"`
	NumPermisoSCT           string                   `json:"num_permiso_sct,omitempty"`
	NombreAseg              string                   `json:"nombre_aseg,omitempty"`
	NumPolizaSeguro         string                   `json:"num_poliza_seguro,omitempty"`
	IdentificacionVehicular *IdentificacionVehicular `json:"identificacion_vehicular,omitempty"`
	Remolques               []Remolque               `json:"remolques,omitempty"`
}

AutotransporteFederal es el autotransporte de CartaPorte 1.0: los seguros van en atributos (en 2.0, nodo Seguros).

type BeneficiarioExtranjero

type BeneficiarioExtranjero struct {
	RFC                 string `json:"rfc,omitempty"`
	CURP                string `json:"curp,omitempty"`
	NomDenRazSocB       string `json:"nom_den_raz_soc_b,omitempty"`
	ConceptoPago        string `json:"concepto_pago,omitempty"`
	DescripcionConcepto string `json:"descripcion_concepto,omitempty"`
}

BeneficiarioExtranjero es el nodo pagosaextranjeros:Beneficiario del complemento Pagosaextranjeros.

type CFDIRegistroFiscal

type CFDIRegistroFiscal struct {
	Version string `json:"version,omitempty"`
	Folio   string `json:"folio,omitempty"`
}

CFDIRegistroFiscal 1.0 (registrofiscal).

type CantidadTransporta

type CantidadTransporta struct {
	Cantidad       *decimal.Decimal `json:"cantidad,omitempty"`
	IDOrigen       string           `json:"id_origen,omitempty"`
	IDDestino      string           `json:"id_destino,omitempty"`
	CvesTransporte string           `json:"cves_transporte,omitempty"`
}

CantidadTransporta es el nodo CantidadTransporta de una Mercancia del complemento CartaPorte.

type Carro

type Carro struct {
	TipoCarro           string                  `json:"tipo_carro,omitempty"`
	MatriculaCarro      string                  `json:"matricula_carro,omitempty"`
	GuiaCarro           string                  `json:"guia_carro,omitempty"`
	ToneladasNetasCarro *decimal.Decimal        `json:"toneladas_netas_carro,omitempty"`
	Contenedor          []ContenedorFerroviario `json:"contenedor,omitempty"`
}

Carro es el nodo Carro del TransporteFerroviario del complemento CartaPorte.

type CartaPorte

type CartaPorte struct {
	Version              string           `json:"version,omitempty"`
	IdCCP                string           `json:"id_ccp,omitempty"`
	TranspInternac       string           `json:"transp_internac,omitempty"`
	RegimenAduanero      string           `json:"regimen_aduanero,omitempty"` // 3.0
	EntradaSalidaMerc    string           `json:"entrada_salida_merc,omitempty"`
	PaisOrigenDestino    string           `json:"pais_origen_destino,omitempty"`
	ViaEntradaSalida     string           `json:"via_entrada_salida,omitempty"`
	TotalDistRec         *decimal.Decimal `json:"total_dist_rec,omitempty"`
	RegistroISTMO        string           `json:"registro_istmo,omitempty"`
	UbicacionPoloOrigen  string           `json:"ubicacion_polo_origen,omitempty"`
	UbicacionPoloDestino string           `json:"ubicacion_polo_destino,omitempty"`

	RegimenesAduaneros []RegimenAduaneroCCP          `json:"regimenes_aduaneros,omitempty"` // 3.1
	Ubicaciones        []Ubicacion                   `json:"ubicaciones,omitempty"`
	Mercancias         *CartaPorteMercancias         `json:"mercancias,omitempty"`
	FiguraTransporte   []TiposFigura                 `json:"figura_transporte,omitempty"`
	FiguraTransporte10 *CartaPorte10FiguraTransporte `json:"figura_transporte10,omitempty"` // 1.0
}

CartaPorte 2.0, 3.0 y 3.1 normalizadas en un solo struct: los XSD solo agregan, quitan o mueven nodos y atributos, sin cambiar el tipo de ninguno que compartan, así que la unión no pierde datos. Version dice cuál era y lo exclusivo de una versión queda vacío en las otras:

  • 2.0: Mercancia.Pedimentos, TransporteMaritimo.NumCertITC.
  • 3.0 y 3.1: IdCCP, RegistroISTMO, UbicacionPoloOrigen/Destino, Mercancia.DocumentacionAduanera y los datos de COFEPRIS de Mercancia, IdentificacionVehicular.PesoBrutoVehicular, TransporteMaritimo.Puntal y PermisoTempNavegacion, los datos CCP de los contenedores marítimos.
  • 3.0: RegimenAduanero (atributo) y TransporteMaritimo.RemolquesCCP.
  • 3.1: RegimenesAduaneros (lista) y Contenedor.RemolquesCCP (los remolques pasaron del transporte marítimo a cada contenedor).

CartaPorte 1.0 (namespace .../CartaPorte) entra en el mismo struct con lo que comparte (Ubicacion con su Domicilio, Mercancia, CantidadTransporta, DetalleMercancia, TransporteMaritimo, TransporteAereo, TransporteFerroviario) y campos propios para lo que 2.0 cambió de forma: Ubicacion.Origen y Destino, Mercancias.AutotransporteFederal y FiguraTransporte10 (en 1.0 FiguraTransporte es un nodo con CveTransporte y sus Operadores, Propietario, Arrendatario y Notificado, y no cabe en la lista de TiposFigura de 2.0).

type CartaPorte10Arrendatario

type CartaPorte10Arrendatario struct {
	RFCArrendatario              string     `json:"rfc_arrendatario,omitempty"`
	NombreArrendatario           string     `json:"nombre_arrendatario,omitempty"`
	NumRegIdTribArrendatario     string     `json:"num_reg_id_trib_arrendatario,omitempty"`
	ResidenciaFiscalArrendatario string     `json:"residencia_fiscal_arrendatario,omitempty"`
	Domicilio                    *Domicilio `json:"domicilio,omitempty"`
}

CartaPorte10Arrendatario es el nodo Arrendatario de la FiguraTransporte de CartaPorte 1.0.

type CartaPorte10Destino

type CartaPorte10Destino struct {
	IDDestino            string `json:"id_destino,omitempty"`
	RFCDestinatario      string `json:"rfc_destinatario,omitempty"`
	NombreDestinatario   string `json:"nombre_destinatario,omitempty"`
	NumRegIdTrib         string `json:"num_reg_id_trib,omitempty"`
	ResidenciaFiscal     string `json:"residencia_fiscal,omitempty"`
	NumEstacion          string `json:"num_estacion,omitempty"`
	NombreEstacion       string `json:"nombre_estacion,omitempty"`
	NavegacionTrafico    string `json:"navegacion_trafico,omitempty"`
	FechaHoraProgLlegada Fecha  `json:"fecha_hora_prog_llegada,omitzero"`
}

CartaPorte10Destino es el nodo Destino de una Ubicacion de CartaPorte 1.0.

type CartaPorte10FiguraTransporte

type CartaPorte10FiguraTransporte struct {
	CveTransporte string                     `json:"cve_transporte,omitempty"`
	Operadores    []CartaPorte10Operador     `json:"operadores,omitempty"`
	Propietario   []CartaPorte10Propietario  `json:"propietario,omitempty"`
	Arrendatario  []CartaPorte10Arrendatario `json:"arrendatario,omitempty"`
	Notificado    []CartaPorte10Notificado   `json:"notificado,omitempty"`
}

CartaPorte10FiguraTransporte es la FiguraTransporte de 1.0. Operadores puede repetirse y no tiene atributos: sus Operador van en una sola lista, en orden de documento.

type CartaPorte10Notificado

type CartaPorte10Notificado struct {
	RFCNotificado              string     `json:"rfc_notificado,omitempty"`
	NombreNotificado           string     `json:"nombre_notificado,omitempty"`
	NumRegIdTribNotificado     string     `json:"num_reg_id_trib_notificado,omitempty"`
	ResidenciaFiscalNotificado string     `json:"residencia_fiscal_notificado,omitempty"`
	Domicilio                  *Domicilio `json:"domicilio,omitempty"`
}

CartaPorte10Notificado es el nodo Notificado de la FiguraTransporte de CartaPorte 1.0.

type CartaPorte10Operador

type CartaPorte10Operador struct {
	RFCOperador              string     `json:"rfc_operador,omitempty"`
	NumLicencia              string     `json:"num_licencia,omitempty"`
	NombreOperador           string     `json:"nombre_operador,omitempty"`
	NumRegIdTribOperador     string     `json:"num_reg_id_trib_operador,omitempty"`
	ResidenciaFiscalOperador string     `json:"residencia_fiscal_operador,omitempty"`
	Domicilio                *Domicilio `json:"domicilio,omitempty"`
}

CartaPorte10Operador es el nodo Operador de la FiguraTransporte de CartaPorte 1.0.

type CartaPorte10Origen

type CartaPorte10Origen struct {
	IDOrigen          string `json:"id_origen,omitempty"`
	RFCRemitente      string `json:"rfc_remitente,omitempty"`
	NombreRemitente   string `json:"nombre_remitente,omitempty"`
	NumRegIdTrib      string `json:"num_reg_id_trib,omitempty"`
	ResidenciaFiscal  string `json:"residencia_fiscal,omitempty"`
	NumEstacion       string `json:"num_estacion,omitempty"`
	NombreEstacion    string `json:"nombre_estacion,omitempty"`
	NavegacionTrafico string `json:"navegacion_trafico,omitempty"`
	FechaHoraSalida   Fecha  `json:"fecha_hora_salida,omitzero"`
}

CartaPorte10Origen y CartaPorte10Destino son los nodos Origen y Destino de la Ubicacion de CartaPorte 1.0 (2.0 los juntó en atributos de Ubicacion).

type CartaPorte10Propietario

type CartaPorte10Propietario struct {
	RFCPropietario              string     `json:"rfc_propietario,omitempty"`
	NombrePropietario           string     `json:"nombre_propietario,omitempty"`
	NumRegIdTribPropietario     string     `json:"num_reg_id_trib_propietario,omitempty"`
	ResidenciaFiscalPropietario string     `json:"residencia_fiscal_propietario,omitempty"`
	Domicilio                   *Domicilio `json:"domicilio,omitempty"`
}

CartaPorte10Propietario es el nodo Propietario de la FiguraTransporte de CartaPorte 1.0.

type CartaPorteMercancia

type CartaPorteMercancia struct {
	BienesTransp                       string           `json:"bienes_transp,omitempty"`
	ClaveSTCC                          string           `json:"clave_stcc,omitempty"`
	Descripcion                        string           `json:"descripcion,omitempty"`
	Cantidad                           *decimal.Decimal `json:"cantidad,omitempty"`
	ClaveUnidad                        string           `json:"clave_unidad,omitempty"`
	Unidad                             string           `json:"unidad,omitempty"`
	Dimensiones                        string           `json:"dimensiones,omitempty"`
	MaterialPeligroso                  string           `json:"material_peligroso,omitempty"`
	CveMaterialPeligroso               string           `json:"cve_material_peligroso,omitempty"`
	Embalaje                           string           `json:"embalaje,omitempty"`
	DescripEmbalaje                    string           `json:"descrip_embalaje,omitempty"`
	SectorCOFEPRIS                     string           `json:"sector_cofepris,omitempty"`
	NombreIngredienteActivo            string           `json:"nombre_ingrediente_activo,omitempty"`
	NomQuimico                         string           `json:"nom_quimico,omitempty"`
	DenominacionGenericaProd           string           `json:"denominacion_generica_prod,omitempty"`
	DenominacionDistintivaProd         string           `json:"denominacion_distintiva_prod,omitempty"`
	Fabricante                         string           `json:"fabricante,omitempty"`
	FechaCaducidad                     Fecha            `json:"fecha_caducidad,omitzero"`
	LoteMedicamento                    string           `json:"lote_medicamento,omitempty"`
	FormaFarmaceutica                  string           `json:"forma_farmaceutica,omitempty"`
	CondicionesEspTransp               string           `json:"condiciones_esp_transp,omitempty"`
	RegistroSanitarioFolioAutorizacion string           `json:"registro_sanitario_folio_autorizacion,omitempty"`
	PermisoImportacion                 string           `json:"permiso_importacion,omitempty"`
	FolioImpoVUCEM                     string           `json:"folio_impo_vucem,omitempty"`
	NumCAS                             string           `json:"num_cas,omitempty"`
	RazonSocialEmpImp                  string           `json:"razon_social_emp_imp,omitempty"`
	NumRegSanPlagCOFEPRIS              string           `json:"num_reg_san_plag_cofepris,omitempty"`
	DatosFabricante                    string           `json:"datos_fabricante,omitempty"`
	DatosFormulador                    string           `json:"datos_formulador,omitempty"`
	DatosMaquilador                    string           `json:"datos_maquilador,omitempty"`
	UsoAutorizado                      string           `json:"uso_autorizado,omitempty"`
	PesoEnKg                           *decimal.Decimal `json:"peso_en_kg,omitempty"`
	ValorMercancia                     *decimal.Decimal `json:"valor_mercancia,omitempty"`
	Moneda                             string           `json:"moneda,omitempty"`
	FraccionArancelaria                string           `json:"fraccion_arancelaria,omitempty"`
	UUIDComercioExt                    string           `json:"uuid_comercio_ext,omitempty"`
	TipoMateria                        string           `json:"tipo_materia,omitempty"`
	DescripcionMateria                 string           `json:"descripcion_materia,omitempty"`

	Pedimentos            []Pedimentos            `json:"pedimentos,omitempty"`             // 2.0
	DocumentacionAduanera []DocumentacionAduanera `json:"documentacion_aduanera,omitempty"` // 3.x
	GuiasIdentificacion   []GuiasIdentificacion   `json:"guias_identificacion,omitempty"`
	CantidadTransporta    []CantidadTransporta    `json:"cantidad_transporta,omitempty"`
	DetalleMercancia      *DetalleMercancia       `json:"detalle_mercancia,omitempty"`
}

CartaPorteMercancia es el nodo Mercancia del complemento CartaPorte.

type CartaPorteMercancias

type CartaPorteMercancias struct {
	PesoBrutoTotal                        *decimal.Decimal `json:"peso_bruto_total,omitempty"`
	UnidadPeso                            string           `json:"unidad_peso,omitempty"`
	PesoNetoTotal                         *decimal.Decimal `json:"peso_neto_total,omitempty"`
	NumTotalMercancias                    string           `json:"num_total_mercancias,omitempty"`
	CargoPorTasacion                      *decimal.Decimal `json:"cargo_por_tasacion,omitempty"`
	LogisticaInversaRecoleccionDevolucion string           `json:"logistica_inversa_recoleccion_devolucion,omitempty"` // 3.x

	Mercancia             []CartaPorteMercancia  `json:"mercancia,omitempty"`
	AutotransporteFederal *AutotransporteFederal `json:"autotransporte_federal,omitempty"` // 1.0
	Autotransporte        *Autotransporte        `json:"autotransporte,omitempty"`
	TransporteMaritimo    *TransporteMaritimo    `json:"transporte_maritimo,omitempty"`
	TransporteAereo       *TransporteAereo       `json:"transporte_aereo,omitempty"`
	TransporteFerroviario *TransporteFerroviario `json:"transporte_ferroviario,omitempty"`
}

CartaPorteMercancias es el nodo Mercancias del complemento CartaPorte.

type CertificadoDeDestruccion

type CertificadoDeDestruccion struct {
	Version             string                              `json:"version,omitempty"`
	Serie               string                              `json:"serie,omitempty"`
	NumFolDesVeh        string                              `json:"num_fol_des_veh,omitempty"`
	VehiculoDestruido   *CertificadoDestruccionVehiculo     `json:"vehiculo_destruido,omitempty"`
	InformacionAduanera *CertificadoDestruccionInfoAduanera `json:"informacion_aduanera,omitempty"`
}

CertificadoDeDestruccion 1.0 (nodo certificadodedestruccion).

type CertificadoDestruccionInfoAduanera

type CertificadoDestruccionInfoAduanera struct {
	NumPedImp string `json:"num_ped_imp,omitempty"`
	Fecha     Fecha  `json:"fecha,omitzero"`
	Aduana    string `json:"aduana,omitempty"`
}

CertificadoDestruccionInfoAduanera es el nodo destruccion:InformacionAduanera del complemento certificadodedestruccion.

type CertificadoDestruccionVehiculo

type CertificadoDestruccionVehiculo struct {
	Marca         string `json:"marca,omitempty"`
	TipooClase    string `json:"tipoo_clase,omitempty"`
	Anio          string `json:"anio,omitempty"` // atributo "Año"
	Modelo        string `json:"modelo,omitempty"`
	NIV           string `json:"niv,omitempty"`
	NumSerie      string `json:"num_serie,omitempty"`
	NumPlacas     string `json:"num_placas,omitempty"`
	NumMotor      string `json:"num_motor,omitempty"`
	NumFolTarjCir string `json:"num_fol_tarj_cir,omitempty"`
}

CertificadoDestruccionVehiculo es el nodo destruccion:VehiculoDestruido del complemento certificadodedestruccion.

type CertificadosSAT

type CertificadosSAT func(noCertificadoSAT string) (*x509.Certificate, error)

CertificadosSAT da el certificado del SAT (o del PAC) con que se timbró, por su número (NoCertificadoSAT del timbre). El certificado no viene en el XML y la librería no usa la red: quien llama decide de dónde sale (un directorio, una caché, una descarga). Para no encontrado, devolver un error.

func CertificadosEn

func CertificadosEn(certs ...*x509.Certificate) CertificadosSAT

CertificadosEn es un proveedor sobre certificados ya cargados, indexados por NoCertificado.

type CfdiRelacionado

type CfdiRelacionado struct {
	UUID         string `json:"uuid,omitempty"`
	UUIDOriginal string `json:"uuid_original,omitempty"` // solo si difiere de UUID
}

CfdiRelacionado es un UUID relacionado, normalizado como el del timbre.

type CfdiRelacionados

type CfdiRelacionados struct {
	TipoRelacion    string            `json:"tipo_relacion,omitempty"`
	CfdiRelacionado []CfdiRelacionado `json:"cfdi_relacionado,omitempty"`
}

CfdiRelacionados agrupa los UUID relacionados con un mismo TipoRelacion.

type CfdiRetenRelacionados

type CfdiRetenRelacionados struct {
	TipoRelacion string `json:"tipo_relacion,omitempty"`
	UUID         string `json:"uuid,omitempty"`
	UUIDOriginal string `json:"uuid_original,omitempty"` // solo si difiere de UUID
}

CfdiRetenRelacionados (2.0) es el comprobante de retenciones que este sustituye u otra relación.

type ChequeosCertificado

type ChequeosCertificado struct {
	Vigencia      ResultadoChequeo `json:"vigencia"`
	Rfc           ResultadoChequeo `json:"rfc"`
	NoCertificado ResultadoChequeo `json:"no_certificado"`
}

ChequeosCertificado compara el certificado del emisor (el Certificado del XML) con el documento, sin red: no dice si el certificado estaba revocado ni si lo emitió el SAT.

  • Vigencia: la Fecha del CFDI (FechaExp en Retenciones) entre NotBefore y NotAfter del certificado, los dos incluidos. La Fecha del CFDI y la FechaExp de Retenciones 2.0 son hora civil sin zona del lugar de expedición, y México tiene husos de UTC−5 a UTC−8 (contando el horario de verano que hubo hasta 2022): la librería no sabe cuál es, así que esa hora es algún instante entre hora+5h y hora+8h UTC. Cumple si cae en la vigencia con todos los husos; no cumple si no cae con ninguno; si depende del huso (la fecha está a menos de tres horas de un límite), no verificable con VIGENCIA_AMBIGUA. Una fecha con zona (la FechaExp de Retenciones 1.0) es un instante exacto.
  • Rfc: el RFC del certificado es el del emisor. Es el atributo x500UniqueIdentifier (OID 2.5.4.45) del sujeto; en una persona moral trae "RFC / RFC del representante" y cuenta el primero. Si el titular es el SAT (SAT970701NN3) y no el emisor, es un CFDI del servicio gratuito de facturación del SAT, que sella con su certificado a nombre del contribuyente: cumple con motivo CERTIFICADO_DEL_SAT (el único cumple con motivo), para que quien lo lea sepa que el RFC no coincide y por qué está bien. El SAT como representante, después de la barra, no cuenta.
  • NoCertificado: el del documento es el número de serie del certificado (ver NoCertificado). Es lo mismo que exige el sello, pero aquí se revisa aunque la firma no verifique.

type CitaSAT

type CitaSAT struct {
	Documento string `json:"documento"`
	Codigo    string `json:"codigo,omitempty"`
	Pagina    int    `json:"pagina,omitempty"`
}

CitaSAT es el fundamento de una regla en un documento oficial del SAT: un código de su matriz de errores o una página de un estándar o de una guía de llenado (la página impresa del PDF). La lista de documentos, con su URL oficial, está en el README.

func (CitaSAT) String

func (c CitaSAT) String() string

type ComercioExterior

type ComercioExterior struct {
	Version                   string           `json:"version,omitempty"`
	MotivoTraslado            string           `json:"motivo_traslado,omitempty"`
	TipoOperacion             string           `json:"tipo_operacion,omitempty"` // 1.1
	ClaveDePedimento          string           `json:"clave_de_pedimento,omitempty"`
	CertificadoOrigen         string           `json:"certificado_origen,omitempty"`
	NumCertificadoOrigen      string           `json:"num_certificado_origen,omitempty"`
	NumeroExportadorConfiable string           `json:"numero_exportador_confiable,omitempty"`
	Incoterm                  string           `json:"incoterm,omitempty"`
	Subdivision               string           `json:"subdivision,omitempty"` // 1.1
	Observaciones             string           `json:"observaciones,omitempty"`
	TipoCambioUSD             *decimal.Decimal `json:"tipo_cambio_usd,omitempty"`
	TotalUSD                  *decimal.Decimal `json:"total_usd,omitempty"`

	Emisor       *ComercioExteriorEmisor        `json:"emisor,omitempty"`
	Propietario  []ComercioExteriorPropietario  `json:"propietario,omitempty"`
	Receptor     *ComercioExteriorReceptor      `json:"receptor,omitempty"`
	Destinatario []ComercioExteriorDestinatario `json:"destinatario,omitempty"`
	Mercancias   []ComercioExteriorMercancia    `json:"mercancias,omitempty"`
}

ComercioExterior 1.0, 1.1 y 2.0 en un solo struct: 1.1 y 2.0 tienen los mismos nodos y atributos salvo TipoOperacion y Subdivision, que 2.0 quitó. 2.0 vuelve requeridos ClaveDePedimento, CertificadoOrigen, TipoCambioUSD, TotalUSD, Mercancias y el Domicilio del Emisor, y acepta más decimales en ValorUnitarioAduana y ValorDolares (se conservan tal cual). 1.0 no tiene MotivoTraslado, Propietario ni domicilios del Emisor y el Receptor; su Receptor trae Curp y su único Destinatario Rfc y Curp.

type ComercioExteriorDestinatario

type ComercioExteriorDestinatario struct {
	NumRegIdTrib string      `json:"num_reg_id_trib,omitempty"`
	Rfc          string      `json:"rfc,omitempty"`  // 1.0
	Curp         string      `json:"curp,omitempty"` // 1.0
	Nombre       string      `json:"nombre,omitempty"`
	Domicilio    []Domicilio `json:"domicilio,omitempty"`
}

ComercioExteriorDestinatario puede tener varios domicilios (en 1.0, uno solo y un solo Destinatario).

type ComercioExteriorEmisor

type ComercioExteriorEmisor struct {
	Curp      string     `json:"curp,omitempty"`
	Domicilio *Domicilio `json:"domicilio,omitempty"`
}

ComercioExteriorEmisor es el nodo Emisor del complemento ComercioExterior.

type ComercioExteriorMercancia

type ComercioExteriorMercancia struct {
	NoIdentificacion         string                     `json:"no_identificacion,omitempty"`
	FraccionArancelaria      string                     `json:"fraccion_arancelaria,omitempty"`
	CantidadAduana           *decimal.Decimal           `json:"cantidad_aduana,omitempty"`
	UnidadAduana             string                     `json:"unidad_aduana,omitempty"`
	ValorUnitarioAduana      *decimal.Decimal           `json:"valor_unitario_aduana,omitempty"`
	ValorDolares             *decimal.Decimal           `json:"valor_dolares,omitempty"`
	DescripcionesEspecificas []DescripcionesEspecificas `json:"descripciones_especificas,omitempty"`
}

ComercioExteriorMercancia no es la Mercancia de CartaPorte: aquí lo central es la fracción arancelaria y el valor en dólares.

type ComercioExteriorPropietario

type ComercioExteriorPropietario struct {
	NumRegIdTrib     string `json:"num_reg_id_trib,omitempty"`
	ResidenciaFiscal string `json:"residencia_fiscal,omitempty"`
}

ComercioExteriorPropietario es el nodo Propietario del complemento ComercioExterior.

type ComercioExteriorReceptor

type ComercioExteriorReceptor struct {
	Curp         string     `json:"curp,omitempty"` // 1.0
	NumRegIdTrib string     `json:"num_reg_id_trib,omitempty"`
	Domicilio    *Domicilio `json:"domicilio,omitempty"`
}

ComercioExteriorReceptor es el nodo Receptor del complemento ComercioExterior.

type ComisionDelServicio

type ComisionDelServicio struct {
	Base       *decimal.Decimal `json:"base,omitempty"`
	Porcentaje *decimal.Decimal `json:"porcentaje,omitempty"`
	Importe    *decimal.Decimal `json:"importe,omitempty"`
}

ComisionDelServicio es el nodo plataformasTecnologicas:ComisionDelServicio de un DetallesDelServicio.

type CompensacionSaldosAFavor

type CompensacionSaldosAFavor struct {
	SaldoAFavor     *decimal.Decimal `json:"saldo_a_favor,omitempty"`
	Anio            string           `json:"anio,omitempty"` // atributo "Año"
	RemanenteSalFav *decimal.Decimal `json:"remanente_sal_fav,omitempty"`
}

CompensacionSaldosAFavor es el nodo nomina12:CompensacionSaldosAFavor de un OtroPago del complemento Nómina.

type Comprobante

type Comprobante struct {
	Version           string           `json:"version,omitempty"`
	Serie             string           `json:"serie,omitempty"`
	Folio             string           `json:"folio,omitempty"`
	Fecha             Fecha            `json:"fecha,omitzero"`
	Sello             string           `json:"sello,omitempty"`
	FormaPago         string           `json:"forma_pago,omitempty"`
	NoCertificado     string           `json:"no_certificado,omitempty"`
	Certificado       string           `json:"certificado,omitempty"`
	CondicionesDePago string           `json:"condiciones_de_pago,omitempty"`
	SubTotal          *decimal.Decimal `json:"sub_total,omitempty"`
	Descuento         *decimal.Decimal `json:"descuento,omitempty"`
	Moneda            string           `json:"moneda,omitempty"`
	TipoCambio        *decimal.Decimal `json:"tipo_cambio,omitempty"`
	Total             *decimal.Decimal `json:"total,omitempty"`
	TipoDeComprobante string           `json:"tipo_de_comprobante,omitempty"`
	Exportacion       string           `json:"exportacion,omitempty"`
	MetodoPago        string           `json:"metodo_pago,omitempty"`
	LugarExpedicion   string           `json:"lugar_expedicion,omitempty"`
	Confirmacion      string           `json:"confirmacion,omitempty"`

	InformacionGlobal *InformacionGlobal `json:"informacion_global,omitempty"`
	// CfdiRelacionados: en 3.3 hay a lo más uno; en 4.0 puede haber varios.
	// Siempre se normaliza a lista.
	CfdiRelacionados []CfdiRelacionados `json:"cfdi_relacionados,omitempty"`
	Emisor           *Emisor            `json:"emisor,omitempty"`
	Receptor         *Receptor          `json:"receptor,omitempty"`
	Conceptos        []Concepto         `json:"conceptos,omitempty"`
	Impuestos        *Impuestos         `json:"impuestos,omitempty"`

	// Complementos soportados.
	TimbreFiscalDigital       *TimbreFiscalDigital       `json:"timbre_fiscal_digital,omitempty"`
	Pagos                     *Pagos                     `json:"pagos,omitempty"`
	Nomina                    []Nomina                   `json:"nomina,omitempty"`
	ImpuestosLocales          *ImpuestosLocales          `json:"impuestos_locales,omitempty"`
	CartaPorte                *CartaPorte                `json:"carta_porte,omitempty"`
	ComercioExterior          *ComercioExterior          `json:"comercio_exterior,omitempty"`
	Divisas                   *Divisas                   `json:"divisas,omitempty"`
	CFDIRegistroFiscal        *CFDIRegistroFiscal        `json:"cfdi_registro_fiscal,omitempty"`
	LeyendasFiscales          *LeyendasFiscales          `json:"leyendas_fiscales,omitempty"`
	EstadoDeCuentaCombustible *EstadoDeCuentaCombustible `json:"estado_de_cuenta_combustible,omitempty"`
	// HidroYPetro es complemento de concepto (Concepto.HidroYPetro); aquí van
	// los que vienen fuera de lugar, en el Complemento del comprobante.
	HidroYPetro                     []HidroYPetro                    `json:"hidro_y_petro,omitempty"`
	Aerolineas                      *Aerolineas                      `json:"aerolineas,omitempty"`
	Donatarias                      *Donatarias                      `json:"donatarias,omitempty"`
	INE                             *INE                             `json:"ine,omitempty"`
	ValesDeDespensa                 *ValesDeDespensa                 `json:"vales_de_despensa,omitempty"`
	ConsumoDeCombustibles           *ConsumoDeCombustibles           `json:"consumo_de_combustibles,omitempty"`
	PagoEnEspecie                   *PagoEnEspecie                   `json:"pago_en_especie,omitempty"`
	NotariosPublicos                *NotariosPublicos                `json:"notarios_publicos,omitempty"`
	ParcialesConstruccion           *ParcialesConstruccion           `json:"parcialesconstruccion,omitempty"`
	VehiculoUsado                   *VehiculoUsado                   `json:"vehiculo_usado,omitempty"`
	TuristaPasajeroExtranjero       *TuristaPasajeroExtranjero       `json:"turista_pasajero_extranjero,omitempty"`
	ComplementoSPEI                 []SPEITercero                    `json:"complemento_spei,omitempty"` // Complemento_SPEI/SPEI_Tercero
	RenovacionySustitucionVehiculos *RenovacionySustitucionVehiculos `json:"renovacionysustitucionvehiculos,omitempty"`
	CertificadoDeDestruccion        *CertificadoDeDestruccion        `json:"certificadodedestruccion,omitempty"`
	ObrasArteAntiguedades           *ObrasArteAntiguedades           `json:"obrasarteantiguedades,omitempty"`
	PFintegranteCoordinado          *PFintegranteCoordinado          `json:"pf_integrante_coordinado,omitempty"`
	GastosHidrocarburos             *GastosHidrocarburos             `json:"gastos_hidrocarburos,omitempty"`
	IngresosHidrocarburos           *IngresosHidrocarburos           `json:"ingresos_hidrocarburos,omitempty"`
	EstadoDeCuentaBancario          *EstadoDeCuentaBancario          `json:"estado_de_cuenta_bancario,omitempty"`
	// ComplementosNoSoportados lista los nodos de Complemento (y de
	// ComplementoConcepto) que no se leen, como "{namespace}NombreLocal",
	// sin repetir.
	ComplementosNoSoportados []string `json:"complementos_no_soportados,omitempty"`

	// Advertencias de la lectura. En JSON van fuera de "cfdi", en el sobre.
	Advertencias []Advertencia `json:"-"`
}

Comprobante es un CFDI 3.3 o 4.0 leído y normalizado.

func Parse

func Parse(r io.Reader) (*Comprobante, error)

Parse lee un CFDI 3.3 o 4.0.

Solo devuelve error (un *Error) si el documento no es XML o si su raíz no es un Comprobante de los namespaces cfd/3 o cfd/4. Cualquier otra rareza se lee como se pueda y queda en Comprobante.Advertencias.

Example
package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/OrtaMarco/cfdi-go"
)

// Una factura 4.0 mínima, sin sello ni timbre, con el RFC de prueba del SAT.
const facturaEjemplo = `<?xml version="1.0" encoding="UTF-8"?>
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4" Version="4.0" Serie="A" Folio="1"
    Fecha="2026-01-15T10:30:00" Sello="" NoCertificado="30001000000500003416" Certificado=""
    SubTotal="100.00" Moneda="MXN" Total="116.00" TipoDeComprobante="I" Exportacion="01"
    MetodoPago="PUE" FormaPago="03" LugarExpedicion="20000">
  <cfdi:Emisor Rfc="EKU9003173C9" Nombre="ESCUELA KEMPER URGATE" RegimenFiscal="601"/>
  <cfdi:Receptor Rfc="XAXX010101000" Nombre="PUBLICO EN GENERAL" DomicilioFiscalReceptor="20000"
      RegimenFiscalReceptor="616" UsoCFDI="S01"/>
  <cfdi:Conceptos>
    <cfdi:Concepto ClaveProdServ="01010101" Cantidad="1" ClaveUnidad="H87" Descripcion="Artículo"
        ValorUnitario="100.00" Importe="100.00" ObjetoImp="02">
      <cfdi:Impuestos>
        <cfdi:Traslados>
          <cfdi:Traslado Base="100.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="16.00"/>
        </cfdi:Traslados>
      </cfdi:Impuestos>
    </cfdi:Concepto>
  </cfdi:Conceptos>
  <cfdi:Impuestos TotalImpuestosTrasladados="16.00">
    <cfdi:Traslados>
      <cfdi:Traslado Base="100.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="16.00"/>
    </cfdi:Traslados>
  </cfdi:Impuestos>
</cfdi:Comprobante>`

func main() {
	c, err := cfdi.Parse(strings.NewReader(facturaEjemplo))
	if err != nil {
		log.Fatal(cfdi.ClaseDe(err))
	}
	fmt.Println(c.Version, c.TipoDeComprobante, c.Emisor.Rfc, c.Total.Original())
	for _, a := range c.Advertencias {
		fmt.Println(a.Codigo, a.Ruta)
	}
}
Output:
4.0 I EKU9003173C9 116.00
SIN_TIMBRE /Comprobante/Complemento

func ParseBytes

func ParseBytes(b []byte) (*Comprobante, error)

ParseBytes es Parse sobre un []byte. No modifica b.

func ParseBytesOpciones

func ParseBytesOpciones(b []byte, o Opciones) (*Comprobante, error)

ParseBytesOpciones es ParseBytes con opciones.

func ParseOpciones

func ParseOpciones(r io.Reader, o Opciones) (*Comprobante, error)

ParseOpciones es Parse con opciones.

func (*Comprobante) AdvertenciaEn

func (c *Comprobante) AdvertenciaEn(rutaNodo, atributo string) (Advertencia, bool)

AdvertenciaEn devuelve la primera advertencia del atributo de ese nodo: AdvertenciaEn("/Comprobante", "Fecha") en vez de buscar el sufijo "/@Fecha" en Ruta.

func (*Comprobante) AdvertenciasDe

func (c *Comprobante) AdvertenciasDe(rutaNodo string) []Advertencia

AdvertenciasDe devuelve las advertencias del nodo con esa ruta (ver el formato en Advertencia) y las de sus atributos, sin las de sus hijos. Con rutaNodo "" devuelve las del documento entero.

func (*Comprobante) Complementos

func (c *Comprobante) Complementos() []string

Complementos lista los complementos presentes, también los de concepto y sin repetir: los soportados como "Nombre versión" ("TimbreFiscalDigital 1.1", "Pagos 2.0", "CartaPorte 3.1", "VentaVehiculos 1.1") y los no soportados como "{namespace}NombreLocal".

func (*Comprobante) Documento

func (c *Comprobante) Documento(o OpcionesJSON) Documento

Documento arma el sobre. Sin IncluirSello trabaja sobre una copia: el Comprobante original no se toca.

func (*Comprobante) JSON

func (c *Comprobante) JSON() ([]byte, error)

JSON devuelve el Documento sin sellos y sin indentar.

Example
package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/OrtaMarco/cfdi-go"
)

// Una factura 4.0 mínima, sin sello ni timbre, con el RFC de prueba del SAT.
const facturaEjemplo = `<?xml version="1.0" encoding="UTF-8"?>
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4" Version="4.0" Serie="A" Folio="1"
    Fecha="2026-01-15T10:30:00" Sello="" NoCertificado="30001000000500003416" Certificado=""
    SubTotal="100.00" Moneda="MXN" Total="116.00" TipoDeComprobante="I" Exportacion="01"
    MetodoPago="PUE" FormaPago="03" LugarExpedicion="20000">
  <cfdi:Emisor Rfc="EKU9003173C9" Nombre="ESCUELA KEMPER URGATE" RegimenFiscal="601"/>
  <cfdi:Receptor Rfc="XAXX010101000" Nombre="PUBLICO EN GENERAL" DomicilioFiscalReceptor="20000"
      RegimenFiscalReceptor="616" UsoCFDI="S01"/>
  <cfdi:Conceptos>
    <cfdi:Concepto ClaveProdServ="01010101" Cantidad="1" ClaveUnidad="H87" Descripcion="Artículo"
        ValorUnitario="100.00" Importe="100.00" ObjetoImp="02">
      <cfdi:Impuestos>
        <cfdi:Traslados>
          <cfdi:Traslado Base="100.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="16.00"/>
        </cfdi:Traslados>
      </cfdi:Impuestos>
    </cfdi:Concepto>
  </cfdi:Conceptos>
  <cfdi:Impuestos TotalImpuestosTrasladados="16.00">
    <cfdi:Traslados>
      <cfdi:Traslado Base="100.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="16.00"/>
    </cfdi:Traslados>
  </cfdi:Impuestos>
</cfdi:Comprobante>`

func main() {
	c, err := cfdi.ParseBytes([]byte(facturaEjemplo))
	if err != nil {
		log.Fatal(err)
	}
	b, err := c.JSON()
	if err != nil {
		log.Fatal(err)
	}
	inicio, _, _ := strings.Cut(string(b), `"serie"`)
	fmt.Println(inicio + "…")
}
Output:
{"schema_version":"1","cfdi":{"version":"4.0",…

func (*Comprobante) JSONOpciones

func (c *Comprobante) JSONOpciones(o OpcionesJSON) ([]byte, error)

JSONOpciones devuelve el Documento con las opciones dadas.

func (*Comprobante) TieneAdvertencia

func (c *Comprobante) TieneAdvertencia(codigo string) bool

TieneAdvertencia dice si la lectura produjo al menos una advertencia con ese código.

func (*Comprobante) UUID

func (c *Comprobante) UUID() string

UUID del timbre, normalizado; "" si no hay timbre.

type Concepto

type Concepto struct {
	ClaveProdServ    string           `json:"clave_prod_serv,omitempty"`
	NoIdentificacion string           `json:"no_identificacion,omitempty"`
	Cantidad         *decimal.Decimal `json:"cantidad,omitempty"`
	ClaveUnidad      string           `json:"clave_unidad,omitempty"`
	Unidad           string           `json:"unidad,omitempty"`
	Descripcion      string           `json:"descripcion,omitempty"`
	ValorUnitario    *decimal.Decimal `json:"valor_unitario,omitempty"`
	Importe          *decimal.Decimal `json:"importe,omitempty"`
	Descuento        *decimal.Decimal `json:"descuento,omitempty"`
	ObjetoImp        string           `json:"objeto_imp,omitempty"`

	Impuestos           *ImpuestosConcepto    `json:"impuestos,omitempty"`
	ACuentaTerceros     *ACuentaTerceros      `json:"a_cuenta_terceros,omitempty"`
	InformacionAduanera []InformacionAduanera `json:"informacion_aduanera,omitempty"`
	// CuentaPredial: 3.3 admite una, 4.0 varias. Siempre lista.
	CuentaPredial []CuentaPredial `json:"cuenta_predial,omitempty"`
	// Complementos de concepto soportados.
	PorCuentadeTerceros *PorCuentadeTerceros `json:"por_cuentade_terceros,omitempty"`
	VentaVehiculos      *VentaVehiculos      `json:"venta_vehiculos,omitempty"`
	HidroYPetro         *HidroYPetro         `json:"hidro_y_petro,omitempty"`
	InstEducativas      *InstEducativas      `json:"inst_educativas,omitempty"`
	AcreditamientoIEPS  *AcreditamientoIEPS  `json:"acreditamiento_ieps,omitempty"`
	// ParcialesConstruccion fuera de lugar: el XSD lo pone en el Complemento
	// del comprobante (Comprobante.ParcialesConstruccion).
	ParcialesConstruccion *ParcialesConstruccion `json:"parcialesconstruccion,omitempty"`
	// ComplementoConcepto lista los complementos del concepto que no se
	// leen, como "{namespace}NombreLocal".
	ComplementoConcepto []string `json:"complemento_concepto,omitempty"`
	Parte               []Parte  `json:"parte,omitempty"`
}

Concepto es el nodo cfdi:Concepto del comprobante, con sus impuestos y complementos de concepto.

type ConceptoConsumoDeCombustibles

type ConceptoConsumoDeCombustibles struct {
	Identificador     string                             `json:"identificador,omitempty"`
	Fecha             Fecha                              `json:"fecha,omitzero"`
	Rfc               string                             `json:"rfc,omitempty"`
	ClaveEstacion     string                             `json:"clave_estacion,omitempty"`
	TipoCombustible   string                             `json:"tipo_combustible,omitempty"` // 1.1
	Cantidad          *decimal.Decimal                   `json:"cantidad,omitempty"`
	NombreCombustible string                             `json:"nombre_combustible,omitempty"`
	FolioOperacion    string                             `json:"folio_operacion,omitempty"`
	ValorUnitario     *decimal.Decimal                   `json:"valor_unitario,omitempty"`
	Importe           *decimal.Decimal                   `json:"importe,omitempty"`
	Determinados      []ConsumoDeCombustiblesDeterminado `json:"determinados,omitempty"`
}

ConceptoConsumoDeCombustibles es el nodo ConceptoConsumoDeCombustibles del complemento ConsumoDeCombustibles.

type ConceptoEstadoDeCuentaCombustible

type ConceptoEstadoDeCuentaCombustible struct {
	Identificador     string           `json:"identificador,omitempty"`
	Fecha             Fecha            `json:"fecha,omitzero"`
	Rfc               string           `json:"rfc,omitempty"`
	ClaveEstacion     string           `json:"clave_estacion,omitempty"`
	TAR               string           `json:"tar,omitempty"` // 1.1
	Cantidad          *decimal.Decimal `json:"cantidad,omitempty"`
	TipoCombustible   string           `json:"tipo_combustible,omitempty"`  // 1.2
	NoIdentificacion  string           `json:"no_identificacion,omitempty"` // 1.1
	Unidad            string           `json:"unidad,omitempty"`
	NombreCombustible string           `json:"nombre_combustible,omitempty"`
	FolioOperacion    string           `json:"folio_operacion,omitempty"`
	ValorUnitario     *decimal.Decimal `json:"valor_unitario,omitempty"`
	Importe           *decimal.Decimal `json:"importe,omitempty"`
	// Traslados usa el mismo struct que los del CFDI: Impuesto ("IVA" o
	// "IEPS", no la clave 002/003), TasaOCuota (TasaoCuota en 1.1) e
	// Importe; no hay Base ni TipoFactor.
	Traslados []Impuesto `json:"traslados,omitempty"`
}

ConceptoEstadoDeCuentaCombustible es el nodo ConceptoEstadoDeCuentaCombustible del complemento EstadoDeCuentaCombustible.

type ConsumoDeCombustibles

type ConsumoDeCombustibles struct {
	Version        string                          `json:"version,omitempty"`
	TipoOperacion  string                          `json:"tipo_operacion,omitempty"`
	NumeroDeCuenta string                          `json:"numero_de_cuenta,omitempty"`
	SubTotal       *decimal.Decimal                `json:"sub_total,omitempty"`
	Total          *decimal.Decimal                `json:"total,omitempty"`
	Conceptos      []ConceptoConsumoDeCombustibles `json:"conceptos,omitempty"`
}

ConsumoDeCombustibles 1.1 (ConsumoDeCombustibles11) y 1.0 (consumodecombustibles). 1.1 agregó tipoCombustible al concepto y renombró la tasa del Determinado (tasa → tasaOCuota): cada una en su campo.

type ConsumoDeCombustiblesDeterminado

type ConsumoDeCombustiblesDeterminado struct {
	Impuesto   string           `json:"impuesto,omitempty"`
	Tasa       *decimal.Decimal `json:"tasa,omitempty"`         // 1.0
	TasaOCuota *decimal.Decimal `json:"tasa_o_cuota,omitempty"` // 1.1
	Importe    *decimal.Decimal `json:"importe,omitempty"`
}

ConsumoDeCombustiblesDeterminado es el nodo Determinado de un concepto del complemento ConsumoDeCombustibles.

type ContenedorFerroviario

type ContenedorFerroviario struct {
	TipoContenedor      string           `json:"tipo_contenedor,omitempty"`
	PesoContenedorVacio *decimal.Decimal `json:"peso_contenedor_vacio,omitempty"`
	PesoNetoMercancia   *decimal.Decimal `json:"peso_neto_mercancia,omitempty"`
}

ContenedorFerroviario es el nodo Contenedor de un Carro del complemento CartaPorte.

type ContenedorMaritimo

type ContenedorMaritimo struct {
	MatriculaContenedor   string        `json:"matricula_contenedor,omitempty"`
	TipoContenedor        string        `json:"tipo_contenedor,omitempty"`
	NumPrecinto           string        `json:"num_precinto,omitempty"`
	IdCCPRelacionado      string        `json:"id_ccp_relacionado,omitempty"`     // 3.x
	PlacaVMCCP            string        `json:"placa_vmccp,omitempty"`            // 3.x
	FechaCertificacionCCP Fecha         `json:"fecha_certificacion_ccp,omitzero"` // 3.x
	RemolquesCCP          []RemolqueCCP `json:"remolques_ccp,omitempty"`          // 3.1
}

ContenedorMaritimo es el nodo Contenedor del TransporteMaritimo del complemento CartaPorte.

type ContribucionGubernamental

type ContribucionGubernamental struct {
	ImpContrib                     *decimal.Decimal `json:"imp_contrib,omitempty"`
	EntidadDondePagaLaContribucion string           `json:"entidad_donde_paga_la_contribucion,omitempty"`
}

ContribucionGubernamental es el nodo plataformasTecnologicas:ContribucionGubernamental de un DetallesDelServicio.

type CuentaPredial

type CuentaPredial struct {
	Numero string `json:"numero,omitempty"`
}

CuentaPredial es el nodo cfdi:CuentaPredial de un concepto.

type DeduccOSalidas

type DeduccOSalidas struct {
	MontTotEgresPeriodo *decimal.Decimal `json:"mont_tot_egres_periodo,omitempty"`
	PartPropDelFideicom *decimal.Decimal `json:"part_prop_del_fideicom,omitempty"`
	PropDelMontTot      *decimal.Decimal `json:"prop_del_mont_tot,omitempty"`
	// IntegracEgresos/@ConceptoS.
	IntegracEgresos string `json:"integrac_egresos,omitempty"`
}

DeduccOSalidas es el nodo fideicomisonoempresarial:DeduccOSalidas del complemento Fideicomisonoempresarial.

type Deduccion

type Deduccion struct {
	TipoDeduccion  string           `json:"tipo_deduccion,omitempty"`
	Clave          string           `json:"clave,omitempty"`
	Concepto       string           `json:"concepto,omitempty"`
	Importe        *decimal.Decimal `json:"importe,omitempty"`
	ImporteGravado *decimal.Decimal `json:"importe_gravado,omitempty"` // 1.1
	ImporteExento  *decimal.Decimal `json:"importe_exento,omitempty"`  // 1.1
}

Deduccion es el nodo nomina12:Deduccion del complemento Nómina.

type Deducciones

type Deducciones struct {
	TotalOtrasDeducciones   *decimal.Decimal `json:"total_otras_deducciones,omitempty"`
	TotalImpuestosRetenidos *decimal.Decimal `json:"total_impuestos_retenidos,omitempty"`
	TotalGravado            *decimal.Decimal `json:"total_gravado,omitempty"` // 1.1
	TotalExento             *decimal.Decimal `json:"total_exento,omitempty"`  // 1.1
	Deduccion               []Deduccion      `json:"deduccion,omitempty"`
}

Deducciones es el nodo nomina12:Deducciones del complemento Nómina.

type DerechosDePaso

type DerechosDePaso struct {
	TipoDerechoDePaso string           `json:"tipo_derecho_de_paso,omitempty"`
	KilometrajePagado *decimal.Decimal `json:"kilometraje_pagado,omitempty"`
}

DerechosDePaso es el nodo DerechosDePaso del TransporteFerroviario del complemento CartaPorte.

type DescripcionesEspecificas

type DescripcionesEspecificas struct {
	Marca       string `json:"marca,omitempty"`
	Modelo      string `json:"modelo,omitempty"`
	SubModelo   string `json:"sub_modelo,omitempty"`
	NumeroSerie string `json:"numero_serie,omitempty"`
}

DescripcionesEspecificas es el nodo DescripcionesEspecificas de una Mercancia del complemento ComercioExterior.

type DetalleMercancia

type DetalleMercancia struct {
	UnidadPeso     string           `json:"unidad_peso,omitempty"` // 1.0 (2.0 lo llamó UnidadPesoMerc)
	UnidadPesoMerc string           `json:"unidad_peso_merc,omitempty"`
	PesoBruto      *decimal.Decimal `json:"peso_bruto,omitempty"`
	PesoNeto       *decimal.Decimal `json:"peso_neto,omitempty"`
	PesoTara       *decimal.Decimal `json:"peso_tara,omitempty"`
	NumPiezas      string           `json:"num_piezas,omitempty"`
}

DetalleMercancia es el nodo DetalleMercancia de una Mercancia del complemento CartaPorte.

type DetallesDelServicio

type DetallesDelServicio struct {
	FormaPagoServ                   string                           `json:"forma_pago_serv,omitempty"`
	TipoDeServ                      string                           `json:"tipo_de_serv,omitempty"`
	SubTipServ                      string                           `json:"sub_tip_serv,omitempty"`
	RFCTerceroAutorizado            string                           `json:"rfc_tercero_autorizado,omitempty"`
	FechaServ                       Fecha                            `json:"fecha_serv,omitzero"`
	PrecioServSinIVA                *decimal.Decimal                 `json:"precio_serv_sin_iva,omitempty"`
	ImpuestosTrasladadosdelServicio *ImpuestosTrasladadosdelServicio `json:"impuestos_trasladadosdel_servicio,omitempty"`
	ContribucionGubernamental       *ContribucionGubernamental       `json:"contribucion_gubernamental,omitempty"`
	ComisionDelServicio             *ComisionDelServicio             `json:"comision_del_servicio,omitempty"`
}

DetallesDelServicio es el nodo plataformasTecnologicas:DetallesDelServicio del complemento de Retenciones ServiciosPlataformasTecnologicas.

type DividOUtil

type DividOUtil struct {
	CveTipDivOUtil            string           `json:"cve_tip_div_o_util,omitempty"`
	MontISRAcredRetMexico     *decimal.Decimal `json:"mont_isr_acred_ret_mexico,omitempty"`
	MontISRAcredRetExtranjero *decimal.Decimal `json:"mont_isr_acred_ret_extranjero,omitempty"`
	MontRetExtDivExt          *decimal.Decimal `json:"mont_ret_ext_div_ext,omitempty"`
	TipoSocDistrDiv           string           `json:"tipo_soc_distr_div,omitempty"`
	MontISRAcredNal           *decimal.Decimal `json:"mont_isr_acred_nal,omitempty"`
	MontDivAcumNal            *decimal.Decimal `json:"mont_div_acum_nal,omitempty"`
	MontDivAcumExt            *decimal.Decimal `json:"mont_div_acum_ext,omitempty"`
}

DividOUtil es el nodo dividendos:DividOUtil del complemento Dividendos.

type Dividendos

type Dividendos struct {
	Version    string      `json:"version,omitempty"`
	DividOUtil *DividOUtil `json:"divid_o_util,omitempty"`
	Remanente  *Remanente  `json:"remanente,omitempty"`
}

Dividendos es el complemento de Retenciones dividendos:Dividendos.

type Divisas

type Divisas struct {
	Version       string `json:"version,omitempty"`
	TipoOperacion string `json:"tipo_operacion,omitempty"` // atributo "tipoOperacion": compra | venta
}

Divisas 1.0: compra o venta de divisas.

type DoctoRelacionado

type DoctoRelacionado struct {
	// IdDocumento normalizado como el UUID del timbre (mayúsculas), para
	// cruzarlo contra él.
	IdDocumento         string           `json:"id_documento,omitempty"`
	IdDocumentoOriginal string           `json:"id_documento_original,omitempty"` // solo si difiere
	Serie               string           `json:"serie,omitempty"`
	Folio               string           `json:"folio,omitempty"`
	MonedaDR            string           `json:"moneda_dr,omitempty"`
	EquivalenciaDR      *decimal.Decimal `json:"equivalencia_dr,omitempty"`   // 2.0
	TipoCambioDR        *decimal.Decimal `json:"tipo_cambio_dr,omitempty"`    // 1.0
	MetodoDePagoDR      string           `json:"metodo_de_pago_dr,omitempty"` // 1.0
	NumParcialidad      string           `json:"num_parcialidad,omitempty"`
	ImpSaldoAnt         *decimal.Decimal `json:"imp_saldo_ant,omitempty"`
	ImpPagado           *decimal.Decimal `json:"imp_pagado,omitempty"`
	ImpSaldoInsoluto    *decimal.Decimal `json:"imp_saldo_insoluto,omitempty"`
	ObjetoImpDR         string           `json:"objeto_imp_dr,omitempty"` // 2.0
	ImpuestosDR         *ImpuestosDR     `json:"impuestos_dr,omitempty"`  // 2.0
}

DoctoRelacionado es el nodo DoctoRelacionado de un Pago del complemento Pagos.

type DocumentacionAduanera

type DocumentacionAduanera struct {
	TipoDocumento    string `json:"tipo_documento,omitempty"`
	NumPedimento     string `json:"num_pedimento,omitempty"`
	IdentDocAduanero string `json:"ident_doc_aduanero,omitempty"`
	RFCImpo          string `json:"rfc_impo,omitempty"`
}

DocumentacionAduanera es el nodo DocumentacionAduanera de una Mercancia del complemento CartaPorte (3.x).

type Documento

type Documento struct {
	// Origen es de dónde salió el CFDI (lo llena quien lee en lote).
	Origen        string        `json:"origen,omitempty"`
	SchemaVersion string        `json:"schema_version"`
	CFDI          *Comprobante  `json:"cfdi"`
	Advertencias  []Advertencia `json:"advertencias"`
	// VerificacionSello es el resultado de verificar el sello del emisor
	// (Verificar(...).Sello). Documento no lo llena: lo agrega quien
	// verifica, como `cfdi lote -sello`.
	VerificacionSello *ResultadoSello `json:"verificacion_sello,omitempty"`
	// VerificacionTFD es el resultado de verificar el SelloSAT del timbre
	// (Verificar(...).TFD). Lo agrega quien verifica, como
	// `cfdi lote -certs-sat`.
	VerificacionTFD *ResultadoSello `json:"verificacion_tfd,omitempty"`
	// Validacion es el resultado de Validar. Lo agrega quien valida, como
	// `cfdi lote -validar`.
	Validacion *Validacion `json:"validacion,omitempty"`
}

Documento es el sobre del JSON: versión del esquema, el CFDI y las advertencias de la lectura (siempre presente, [] si no hubo).

func (Documento) Escribir

func (d Documento) Escribir(w io.Writer, indentar bool) error

Escribir manda el Documento como JSON a w, terminado en salto de línea (una línea JSONL si no se indenta). No escapa <, > ni & como hace json.Marshal: un RFC como "E&Ñ…" sale tal cual y no como "E\u0026Ñ…".

type DocumentoRetenciones

type DocumentoRetenciones struct {
	Origen string `json:"origen,omitempty"`
	// TipoDocumento es siempre "retenciones".
	TipoDocumento     TipoDocumento   `json:"tipo_documento"`
	SchemaVersion     string          `json:"schema_version"`
	Retenciones       *Retenciones    `json:"retenciones"`
	Advertencias      []Advertencia   `json:"advertencias"`
	VerificacionSello *ResultadoSello `json:"verificacion_sello,omitempty"`
	VerificacionTFD   *ResultadoSello `json:"verificacion_tfd,omitempty"`
	Validacion        *Validacion     `json:"validacion,omitempty"`
}

DocumentoRetenciones es el sobre del JSON de Retenciones: como Documento, con tipo_documento para distinguirlo de un CFDI en la misma salida (una línea de `cfdi lote`).

func (DocumentoRetenciones) Escribir

func (d DocumentoRetenciones) Escribir(w io.Writer, indentar bool) error

Escribir manda el documento como JSON a w, como Documento.Escribir.

type Domicilio

type Domicilio struct {
	Calle          string `json:"calle,omitempty"`
	NumeroExterior string `json:"numero_exterior,omitempty"`
	NumeroInterior string `json:"numero_interior,omitempty"`
	Colonia        string `json:"colonia,omitempty"`
	Localidad      string `json:"localidad,omitempty"`
	Referencia     string `json:"referencia,omitempty"`
	Municipio      string `json:"municipio,omitempty"`
	Estado         string `json:"estado,omitempty"`
	Pais           string `json:"pais,omitempty"`
	CodigoPostal   string `json:"codigo_postal,omitempty"`
}

Domicilio de carta porte y de comercio exterior (mismos atributos).

type Donatarias

type Donatarias struct {
	Version           string `json:"version,omitempty"`
	NoAutorizacion    string `json:"no_autorizacion,omitempty"`
	FechaAutorizacion Fecha  `json:"fecha_autorizacion,omitzero"` // AAAA-MM-DD
	Leyenda           string `json:"leyenda,omitempty"`
}

Donatarias 1.1 (donat11) y 1.0 (donat): comparten namespace y atributos; 1.1 solo cambió la versión fija. Los atributos van en minúsculas.

type ECBMovimiento

type ECBMovimiento struct {
	Fecha        Fecha            `json:"fecha,omitzero"`
	Referencia   string           `json:"referencia,omitempty"`
	Descripcion  string           `json:"descripcion,omitempty"`
	Importe      *decimal.Decimal `json:"importe,omitempty"`
	Moneda       string           `json:"moneda,omitempty"`
	SaldoInicial *decimal.Decimal `json:"saldo_inicial,omitempty"`
	SaldoAlCorte *decimal.Decimal `json:"saldo_al_corte,omitempty"`
}

ECBMovimiento es el nodo ecb:MovimientoECB de los Movimientos del complemento EstadoDeCuentaBancario.

type ECBMovimientoFiscal

type ECBMovimientoFiscal struct {
	Fecha         Fecha            `json:"fecha,omitzero"`
	Referencia    string           `json:"referencia,omitempty"`
	Descripcion   string           `json:"descripcion,omitempty"`
	RFCEnajenante string           `json:"rfc_enajenante,omitempty"` // atributo "RFCenajenante"
	Importe       *decimal.Decimal `json:"importe,omitempty"`
	Moneda        string           `json:"moneda,omitempty"`
	SaldoInicial  *decimal.Decimal `json:"saldo_inicial,omitempty"`
	SaldoAlCorte  *decimal.Decimal `json:"saldo_al_corte,omitempty"`
}

ECBMovimientoFiscal: el XSD escribe RFCenajenante (e minúscula) e Importe con mayúscula, a diferencia de MovimientoECB.

type ECBMovimientos

type ECBMovimientos struct {
	MovimientoECB       []ECBMovimiento       `json:"movimiento_ecb,omitempty"`
	MovimientoECBFiscal []ECBMovimientoFiscal `json:"movimiento_ecb_fiscal,omitempty"`
}

ECBMovimientos es el nodo ecb:Movimientos del complemento EstadoDeCuentaBancario.

type Emisor

type Emisor struct {
	Rfc              string `json:"rfc,omitempty"`
	Nombre           string `json:"nombre,omitempty"`
	RegimenFiscal    string `json:"regimen_fiscal,omitempty"`
	FacAtrAdquirente string `json:"fac_atr_adquirente,omitempty"`
}

Emisor es el nodo cfdi:Emisor del comprobante.

type EmisorRetenciones

type EmisorRetenciones struct {
	RfcE           string `json:"rfc_e,omitempty"`
	NomDenRazSocE  string `json:"nom_den_raz_soc_e,omitempty"`
	RegimenFiscalE string `json:"regimen_fiscal_e,omitempty"` // 2.0
	CurpE          string `json:"curp_e,omitempty"`           // 1.0 (CURPE)
}

EmisorRetenciones es el nodo retenciones:Emisor del CFDI de Retenciones.

type EnajenaciondeAcciones

type EnajenaciondeAcciones struct {
	Version                string           `json:"version,omitempty"`
	ContratoIntermediacion string           `json:"contrato_intermediacion,omitempty"`
	Ganancia               *decimal.Decimal `json:"ganancia,omitempty"`
	Perdida                *decimal.Decimal `json:"perdida,omitempty"`
}

EnajenaciondeAcciones es el complemento de Retenciones enajenaciondeacciones:EnajenaciondeAcciones.

type EntidadSNCF

type EntidadSNCF struct {
	OrigenRecurso      string           `json:"origen_recurso,omitempty"`
	MontoRecursoPropio *decimal.Decimal `json:"monto_recurso_propio,omitempty"`
}

EntidadSNCF es el nodo nomina12:EntidadSNCF del Emisor del complemento Nómina.

type Error

type Error struct {
	Clase string
	Err   error
}

Error es un error fatal de Parse: el documento no se pudo leer como CFDI.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type ErrorCadena

type ErrorCadena struct {
	Motivo string // uno de los Motivo* de arriba
	Nodo   string // "{namespace}Nombre" del nodo que la impide, si aplica
	Err    error
}

ErrorCadena explica por qué no se pudo armar una cadena original. La librería no devuelve nunca una cadena que no pueda garantizar.

func (*ErrorCadena) Error

func (e *ErrorCadena) Error() string

func (*ErrorCadena) Unwrap

func (e *ErrorCadena) Unwrap() error

type EstadoDeCuentaBancario

type EstadoDeCuentaBancario struct {
	Version       string          `json:"version,omitempty"`
	NumeroCuenta  string          `json:"numero_cuenta,omitempty"`
	NombreCliente string          `json:"nombre_cliente,omitempty"`
	Periodo       string          `json:"periodo,omitempty"`
	Sucursal      string          `json:"sucursal,omitempty"`
	Movimientos   *ECBMovimientos `json:"movimientos,omitempty"`
}

EstadoDeCuentaBancario 1.0 (ecb), de CFDI 3.2. Movimientos no tiene atributos pero sí dos clases de hijos: se conserva como objeto con las dos listas.

type EstadoDeCuentaCombustible

type EstadoDeCuentaCombustible struct {
	Version        string                              `json:"version,omitempty"`
	TipoOperacion  string                              `json:"tipo_operacion,omitempty"`
	NumeroDeCuenta string                              `json:"numero_de_cuenta,omitempty"`
	SubTotal       *decimal.Decimal                    `json:"sub_total,omitempty"`
	Total          *decimal.Decimal                    `json:"total,omitempty"`
	Conceptos      []ConceptoEstadoDeCuentaCombustible `json:"conceptos,omitempty"`
}

EstadoDeCuentaCombustible 1.2 (ecc12) y 1.1 (namespace .../EstadoDeCuentaCombustible). Conceptos aplana Conceptos/ConceptoEstadoDeCuentaCombustible.

type Fecha

type Fecha struct {
	Original string    // texto como venía
	Time     time.Time // hora civil; cero si el texto no se pudo leer
	// FueraDeFormato dice que el texto es una fecha legible pero no en el
	// formato del SAT (con zona, con fracción de segundo, con espacios, sin
	// hora o al revés): es la versión por campo de FECHA_FORMATO. Con
	// Opciones.FechaEstricta esas fechas no se interpretan (Time queda en
	// cero) y FueraDeFormato sigue en true. No va al JSON.
	FueraDeFormato bool
}

Fecha guarda una fecha del CFDI como texto original y como hora civil.

El SAT escribe las fechas sin zona horaria ("2026-01-15T10:30:00"): es la hora local del lugar de expedición, que puede ser cualquiera de los husos de México. Esta librería no inventa la zona: Time lleva la fecha y hora tal cual (hora civil) con time.UTC como contenedor, sin que eso signifique que el instante sea UTC. Para un instante real use En con la zona que corresponda.

Los nodos de nómina usan fechas sin hora ("2026-01-15"); entonces la hora es 00:00:00.

func (Fecha) En

func (f Fecha) En(loc *time.Location) time.Time

En interpreta la hora civil en la zona loc (p. ej. America/Mexico_City).

func (Fecha) MarshalJSON

func (f Fecha) MarshalJSON() ([]byte, error)

MarshalJSON escribe el texto original.

func (Fecha) String

func (f Fecha) String() string

func (*Fecha) UnmarshalJSON

func (f *Fecha) UnmarshalJSON(b []byte) error

UnmarshalJSON lee el texto y vuelve a interpretar la hora civil; si no se puede, deja Time en cero sin error (igual que Parse). Como el JSON no dice si el campo era fecha u hora, FueraDeFormato acepta los dos formatos SAT.

func (Fecha) Valida

func (f Fecha) Valida() bool

Valida dice si el texto se pudo interpretar.

type Fideicomisonoempresarial

type Fideicomisonoempresarial struct {
	Version             string               `json:"version,omitempty"`
	IngresosOEntradas   *IngresosOEntradas   `json:"ingresos_o_entradas,omitempty"`
	DeduccOSalidas      *DeduccOSalidas      `json:"deducc_o_salidas,omitempty"`
	RetEfectFideicomiso *RetEfectFideicomiso `json:"ret_efect_fideicomiso,omitempty"`
}

Fideicomisonoempresarial es el complemento de Retenciones fideicomisonoempresarial:Fideicomisonoempresarial.

type GastosHidrocarburos

type GastosHidrocarburos struct {
	Version         string                         `json:"version,omitempty"`
	NumeroContrato  string                         `json:"numero_contrato,omitempty"`
	AreaContractual string                         `json:"area_contractual,omitempty"`
	Erogacion       []GastosHidrocarburosErogacion `json:"erogacion,omitempty"`
}

GastosHidrocarburos 1.0 (GastosHidrocarburos10).

type GastosHidrocarburosActividades

type GastosHidrocarburosActividades struct {
	ActividadRelacionada string                              `json:"actividad_relacionada,omitempty"`
	SubActividades       []GastosHidrocarburosSubActividades `json:"sub_actividades,omitempty"`
}

GastosHidrocarburosActividades es el nodo gceh:Actividades de una Erogacion del complemento GastosHidrocarburos.

type GastosHidrocarburosCentroCostos

type GastosHidrocarburosCentroCostos struct {
	Campo       string                           `json:"campo,omitempty"`
	Yacimientos []GastosHidrocarburosYacimientos `json:"yacimientos,omitempty"`
}

GastosHidrocarburosCentroCostos es el nodo gceh:CentroCostos de una Erogacion del complemento GastosHidrocarburos.

type GastosHidrocarburosDocumentoRelacionado

type GastosHidrocarburosDocumentoRelacionado struct {
	OrigenErogacion                string           `json:"origen_erogacion,omitempty"`
	FolioFiscalVinculado           string           `json:"folio_fiscal_vinculado,omitempty"`
	RFCProveedor                   string           `json:"rfc_proveedor,omitempty"`
	MontoTotalIVA                  *decimal.Decimal `json:"monto_total_iva,omitempty"`
	MontoRetencionISR              *decimal.Decimal `json:"monto_retencion_isr,omitempty"`
	MontoRetencionIVA              *decimal.Decimal `json:"monto_retencion_iva,omitempty"`
	MontoRetencionOtrosImpuestos   *decimal.Decimal `json:"monto_retencion_otros_impuestos,omitempty"`
	NumeroPedimentoVinculado       string           `json:"numero_pedimento_vinculado,omitempty"`
	ClavePedimentoVinculado        string           `json:"clave_pedimento_vinculado,omitempty"`
	ClavePagoPedimentoVinculado    string           `json:"clave_pago_pedimento_vinculado,omitempty"`
	MontoIVAPedimento              *decimal.Decimal `json:"monto_iva_pedimento,omitempty"`
	OtrosImpuestosPagadosPedimento *decimal.Decimal `json:"otros_impuestos_pagados_pedimento,omitempty"`
	FechaFolioFiscalVinculado      Fecha            `json:"fecha_folio_fiscal_vinculado,omitzero"`
	Mes                            string           `json:"mes,omitempty"`
	MontoTotalErogaciones          *decimal.Decimal `json:"monto_total_erogaciones,omitempty"`
}

GastosHidrocarburosDocumentoRelacionado es el nodo gceh:DocumentoRelacionado de una Erogacion del complemento GastosHidrocarburos.

type GastosHidrocarburosErogacion

type GastosHidrocarburosErogacion struct {
	TipoErogacion        string                                    `json:"tipo_erogacion,omitempty"`
	MontocuErogacion     *decimal.Decimal                          `json:"montocu_erogacion,omitempty"`
	Porcentaje           *decimal.Decimal                          `json:"porcentaje,omitempty"`
	DocumentoRelacionado []GastosHidrocarburosDocumentoRelacionado `json:"documento_relacionado,omitempty"`
	Actividades          []GastosHidrocarburosActividades          `json:"actividades,omitempty"`
	CentroCostos         []GastosHidrocarburosCentroCostos         `json:"centro_costos,omitempty"`
}

GastosHidrocarburosErogacion es el nodo gceh:Erogacion del complemento GastosHidrocarburos.

type GastosHidrocarburosPozos

type GastosHidrocarburosPozos struct {
	Pozo string `json:"pozo,omitempty"`
}

GastosHidrocarburosPozos es el nodo gceh:Pozos del complemento GastosHidrocarburos.

type GastosHidrocarburosSubActividades

type GastosHidrocarburosSubActividades struct {
	SubActividadRelacionada string                      `json:"sub_actividad_relacionada,omitempty"`
	Tareas                  []GastosHidrocarburosTareas `json:"tareas,omitempty"`
}

GastosHidrocarburosSubActividades es el nodo gceh:SubActividades del complemento GastosHidrocarburos.

type GastosHidrocarburosTareas

type GastosHidrocarburosTareas struct {
	TareaRelacionada string `json:"tarea_relacionada,omitempty"`
}

GastosHidrocarburosTareas es el nodo gceh:Tareas del complemento GastosHidrocarburos.

type GastosHidrocarburosYacimientos

type GastosHidrocarburosYacimientos struct {
	Yacimiento string                     `json:"yacimiento,omitempty"`
	Pozos      []GastosHidrocarburosPozos `json:"pozos,omitempty"`
}

GastosHidrocarburosYacimientos es el nodo gceh:Yacimientos del complemento GastosHidrocarburos.

type GuiasIdentificacion

type GuiasIdentificacion struct {
	NumeroGuiaIdentificacion  string           `json:"numero_guia_identificacion,omitempty"`
	DescripGuiaIdentificacion string           `json:"descrip_guia_identificacion,omitempty"`
	PesoGuiaIdentificacion    *decimal.Decimal `json:"peso_guia_identificacion,omitempty"`
}

GuiasIdentificacion es el nodo GuiasIdentificacion de una Mercancia del complemento CartaPorte.

type Hallazgo

type Hallazgo struct {
	// Codigo es estable entre versiones (sirve para agrupar en lote): uno
	// de los Esquema* o Regla*.
	Codigo string `json:"codigo"`
	// Nivel es NivelError (el documento incumple), NivelAdvertencia
	// (incumple una regla sin fuente citable que la haga obligatoria: puede
	// estar mal, no se afirma) o NivelAviso (una parte del documento no se
	// pudo validar; no dice que esté mal).
	Nivel    string `json:"nivel"`
	Ruta     string `json:"ruta,omitempty"`
	Atributo string `json:"atributo,omitempty"`
	// Valor es el texto original del atributo en los hallazgos de valores.
	Valor string `json:"valor,omitempty"`
	// Regla es la norma concreta: en el esquema, la faceta o restricción
	// XSD ("pattern", "enumeration", "minOccurs", "orden"…); en las reglas,
	// la referencia a la fuente (ver ValidarReglas).
	Regla string `json:"regla"`
	// Mensaje es para personas y puede cambiar.
	Mensaje string `json:"mensaje,omitempty"`
}

Hallazgo es una violación de una norma encontrada al validar: del esquema XSD del SAT (ValidarEsquema) o de una regla aritmética o de coherencia (ValidarReglas).

Es un tipo propio y no Advertencia porque dicen cosas distintas: una Advertencia cuenta qué tuvo que hacer la lectura tolerante para leer el documento (se lee igual), un Hallazgo dice qué norma incumple (el SAT lo rechazaría). Un CFDI puede leerse sin una sola advertencia y tener hallazgos, y al revés. Lo que sí comparten es la forma de señalar el lugar: Ruta y Atributo siguen exactamente el contrato de Advertencia (nombres locales, índices [n] en los nodos que el esquema deja repetir, "/@Atributo" al final), así que un mismo nodo se busca igual en las dos listas.

func ValidarReglas

func ValidarReglas(c *Comprobante) []Hallazgo

ValidarReglas evalúa las reglas aritméticas y de coherencia que se pueden comprobar sin red ni catálogos externos (ver Reglas): importes de los conceptos y de sus impuestos con los límites del Anexo 20, SubTotal, Descuento, Total, totales de impuestos agrupados por impuesto, tipo de factor y tasa, decimales y tipo de cambio según la moneda, reglas por TipoDeComprobante, ImpuestosLocales, Pagos 1.0 y 2.0, Comercio Exterior 2.0 y Nómina 1.2. Todo con decimal, sin float.

Es aparte de ValidarEsquema y trabaja sobre el Comprobante, no sobre los bytes: las reglas son de valores (montos ya leídos como decimales) y no de la forma del documento; y quien ya hizo Parse no relee el XML. Usan el mismo tipo Hallazgo con el mismo contrato de Ruta, así que se pueden juntar en una lista.

Cada hallazgo lleva el nivel de su regla (Regla.Nivel): error si la regla tiene fundamento en un documento oficial del SAT, advertencia si no; las advertencias no hacen inválido el documento (EstadoDe). Las reglas con Regla.Margen tienen dos niveles: lo que no cumple la letra del SAT pero cae en el margen de redondeo por concepto sale como advertencia.

Una regla que necesita un valor ausente o ilegible no se evalúa: de eso avisan la lectura (Advertencias) y ValidarEsquema. CFDI 3.3 y 4.0; otra versión da un solo aviso ReglaVersionNoSoportada. Las reglas propias de 4.0 (ObjetoImp, Base de los traslados del comprobante, Pagos 2.0, Comercio Exterior 2.0) solo se evalúan en 4.0; las de Pagos 1.0, solo en 3.3.

Nunca devuelve nil: sin hallazgos es una lista vacía.

func (Hallazgo) Nodo

func (h Hallazgo) Nodo() string

Nodo es la ruta del nodo del hallazgo: Ruta sin el "/@Atributo" final.

type HidroYPetro

type HidroYPetro struct {
	Version        string `json:"version,omitempty"`
	TipoPermiso    string `json:"tipo_permiso,omitempty"`
	NumeroPermiso  string `json:"numero_permiso,omitempty"`
	ClaveHYP       string `json:"clave_hyp,omitempty"`
	SubProductoHYP string `json:"sub_producto_hyp,omitempty"`
}

HidroYPetro 1.0 (hidrocarburospetroliferos): permiso de la operación con hidrocarburos o petrolíferos. El XSD lo define como complemento de concepto; si viene en el Complemento del comprobante también se lee (Comprobante.HidroYPetro).

type HorasExtra

type HorasExtra struct {
	Dias          string           `json:"dias,omitempty"`
	TipoHoras     string           `json:"tipo_horas,omitempty"`
	HorasExtra    string           `json:"horas_extra,omitempty"`
	ImportePagado *decimal.Decimal `json:"importe_pagado,omitempty"`
}

HorasExtra es el nodo nomina12:HorasExtra de una Percepcion del complemento Nómina.

type INE

type INE struct {
	Version        string       `json:"version,omitempty"`
	TipoProceso    string       `json:"tipo_proceso,omitempty"`
	TipoComite     string       `json:"tipo_comite,omitempty"`
	IdContabilidad string       `json:"id_contabilidad,omitempty"`
	Entidad        []INEEntidad `json:"entidad,omitempty"`
}

INE 1.1 y 1.0 (ine): proceso electoral. Comparten namespace y nodos; 1.1 solo admite más entidades.

type INEContabilidad

type INEContabilidad struct {
	IdContabilidad string `json:"id_contabilidad,omitempty"`
}

INEContabilidad es el nodo ine:Contabilidad de una Entidad del complemento INE.

type INEEntidad

type INEEntidad struct {
	ClaveEntidad string            `json:"clave_entidad,omitempty"`
	Ambito       string            `json:"ambito,omitempty"`
	Contabilidad []INEContabilidad `json:"contabilidad,omitempty"`
}

INEEntidad es el nodo ine:Entidad del complemento INE.

type IdentificacionVehicular

type IdentificacionVehicular struct {
	ConfigVehicular    string           `json:"config_vehicular,omitempty"`
	PesoBrutoVehicular *decimal.Decimal `json:"peso_bruto_vehicular,omitempty"` // 3.x
	PlacaVM            string           `json:"placa_vm,omitempty"`
	AnioModeloVM       string           `json:"anio_modelo_vm,omitempty"`
}

IdentificacionVehicular es el nodo IdentificacionVehicular del Autotransporte del complemento CartaPorte.

type ImpRetenidos

type ImpRetenidos struct {
	BaseRet     *decimal.Decimal `json:"base_ret,omitempty"`
	ImpuestoRet string           `json:"impuesto_ret,omitempty"`
	MontoRet    *decimal.Decimal `json:"monto_ret,omitempty"`
	TipoPagoRet string           `json:"tipo_pago_ret,omitempty"`
}

ImpRetenidos es el nodo retenciones:ImpRetenidos de los Totales del CFDI de Retenciones.

type Impuesto

type Impuesto struct {
	Base       *decimal.Decimal `json:"base,omitempty"`
	Impuesto   string           `json:"impuesto,omitempty"`
	TipoFactor string           `json:"tipo_factor,omitempty"`
	TasaOCuota *decimal.Decimal `json:"tasa_o_cuota,omitempty"`
	// Tasa en porcentaje (16.00): solo en los traslados del
	// EstadoDeCuentaCombustible sin versión (ecc). El tasa de los traslados
	// de CFDI 3.2 no se lee.
	Tasa    *decimal.Decimal `json:"tasa,omitempty"`
	Importe *decimal.Decimal `json:"importe,omitempty"`
}

Impuesto es un Traslado o una Retencion, del concepto o del comprobante. Con TipoFactor "Exento" no hay TasaOCuota ni Importe (nil).

type ImpuestoDR

type ImpuestoDR struct {
	BaseDR       *decimal.Decimal `json:"base_dr,omitempty"`
	ImpuestoDR   string           `json:"impuesto_dr,omitempty"`
	TipoFactorDR string           `json:"tipo_factor_dr,omitempty"`
	TasaOCuotaDR *decimal.Decimal `json:"tasa_o_cuota_dr,omitempty"`
	ImporteDR    *decimal.Decimal `json:"importe_dr,omitempty"`
}

ImpuestoDR es un nodo pago20:RetencionDR o pago20:TrasladoDR de ImpuestosDR, en el complemento Pagos 2.0.

type ImpuestoP

type ImpuestoP struct {
	BaseP       *decimal.Decimal `json:"base_p,omitempty"`
	ImpuestoP   string           `json:"impuesto_p,omitempty"`
	TipoFactorP string           `json:"tipo_factor_p,omitempty"`
	TasaOCuotaP *decimal.Decimal `json:"tasa_o_cuota_p,omitempty"`
	ImporteP    *decimal.Decimal `json:"importe_p,omitempty"`
}

ImpuestoP es un nodo pago20:RetencionP o pago20:TrasladoP de ImpuestosP, en el complemento Pagos 2.0.

type ImpuestoTerceros

type ImpuestoTerceros struct {
	Impuesto string           `json:"impuesto,omitempty"`
	Tasa     *decimal.Decimal `json:"tasa,omitempty"`
	Importe  *decimal.Decimal `json:"importe,omitempty"`
}

ImpuestoTerceros: Impuesto va por nombre (ISR, IVA, IEPS); las retenciones no llevan Tasa.

type Impuestos

type Impuestos struct {
	TotalImpuestosRetenidos   *decimal.Decimal `json:"total_impuestos_retenidos,omitempty"`
	TotalImpuestosTrasladados *decimal.Decimal `json:"total_impuestos_trasladados,omitempty"`
	Retenciones               []Impuesto       `json:"retenciones,omitempty"`
	Traslados                 []Impuesto       `json:"traslados,omitempty"`
}

Impuestos son los totales de impuestos del comprobante. El mismo struct sirve para el nodo Impuestos de un Pago 1.0.

type ImpuestosConcepto

type ImpuestosConcepto struct {
	Traslados   []Impuesto `json:"traslados,omitempty"`
	Retenciones []Impuesto `json:"retenciones,omitempty"`
}

ImpuestosConcepto son los traslados y retenciones de un concepto.

type ImpuestosDR

type ImpuestosDR struct {
	RetencionesDR []ImpuestoDR `json:"retenciones_dr,omitempty"`
	TrasladosDR   []ImpuestoDR `json:"traslados_dr,omitempty"`
}

ImpuestosDR es el nodo pago20:ImpuestosDR de un DoctoRelacionado del complemento Pagos 2.0.

type ImpuestosLocales

type ImpuestosLocales struct {
	Version            string           `json:"version,omitempty"`
	TotaldeRetenciones *decimal.Decimal `json:"totalde_retenciones,omitempty"`
	TotaldeTraslados   *decimal.Decimal `json:"totalde_traslados,omitempty"`
	// El XSD repite la secuencia (RetencionesLocales?, TrasladosLocales?):
	// cada uno puede venir varias veces.
	RetencionesLocales []RetencionesLocales `json:"retenciones_locales,omitempty"`
	TrasladosLocales   []TrasladosLocales   `json:"traslados_locales,omitempty"`
}

ImpuestosLocales 1.0 (implocal). Los importes locales no están en Comprobante.Impuestos pero sí en el Total:

Total = SubTotal - Descuento + TotalImpuestosTrasladados - TotalImpuestosRetenidos
        + TotaldeTraslados - TotaldeRetenciones

type ImpuestosP

type ImpuestosP struct {
	RetencionesP []ImpuestoP `json:"retenciones_p,omitempty"`
	TrasladosP   []ImpuestoP `json:"traslados_p,omitempty"`
}

ImpuestosP es el nodo pago20:ImpuestosP de un Pago del complemento Pagos 2.0.

type ImpuestosTerceros

type ImpuestosTerceros struct {
	Retenciones []ImpuestoTerceros `json:"retenciones,omitempty"`
	Traslados   []ImpuestoTerceros `json:"traslados,omitempty"`
}

ImpuestosTerceros es el nodo terceros:Impuestos del complemento de concepto PorCuentadeTerceros.

type ImpuestosTrasladadosdelServicio

type ImpuestosTrasladadosdelServicio struct {
	Base       *decimal.Decimal `json:"base,omitempty"`
	Impuesto   string           `json:"impuesto,omitempty"`
	TipoFactor string           `json:"tipo_factor,omitempty"`
	TasaCuota  *decimal.Decimal `json:"tasa_cuota,omitempty"`
	Importe    *decimal.Decimal `json:"importe,omitempty"`
}

ImpuestosTrasladadosdelServicio es el nodo plataformasTecnologicas:ImpuestosTrasladadosdelServicio de un DetallesDelServicio.

type Incapacidad

type Incapacidad struct {
	DiasIncapacidad  string           `json:"dias_incapacidad,omitempty"`
	TipoIncapacidad  string           `json:"tipo_incapacidad,omitempty"`
	ImporteMonetario *decimal.Decimal `json:"importe_monetario,omitempty"`
	Descuento        *decimal.Decimal `json:"descuento,omitempty"` // 1.1
}

Incapacidad es el nodo nomina12:Incapacidad del complemento Nómina.

type InformacionAduanera

type InformacionAduanera struct {
	NumeroPedimento string `json:"numero_pedimento,omitempty"`
}

InformacionAduanera es el nodo cfdi:InformacionAduanera de un concepto o de una parte.

type InformacionAduaneraComplemento

type InformacionAduaneraComplemento struct {
	Numero string `json:"numero,omitempty"`
	Fecha  Fecha  `json:"fecha,omitzero"`
	Aduana string `json:"aduana,omitempty"`
}

InformacionAduaneraComplemento es el t_InformacionAduanera de terceros y ventavehiculos: pedimento con fecha y aduana. No es la InformacionAduanera del concepto (solo NumeroPedimento).

type InformacionGlobal

type InformacionGlobal struct {
	Periodicidad string `json:"periodicidad,omitempty"`
	Meses        string `json:"meses,omitempty"`
	Anio         string `json:"anio,omitempty"` // atributo "Año"
}

InformacionGlobal (4.0) para CFDI a público en general.

type IngresosHidrocarburos

type IngresosHidrocarburos struct {
	Version                        string                                      `json:"version,omitempty"`
	NumeroContrato                 string                                      `json:"numero_contrato,omitempty"`
	ContraprestacionPagadaOperador *decimal.Decimal                            `json:"contraprestacion_pagada_operador,omitempty"`
	Porcentaje                     *decimal.Decimal                            `json:"porcentaje,omitempty"`
	DocumentoRelacionado           []IngresosHidrocarburosDocumentoRelacionado `json:"documento_relacionado,omitempty"`
}

IngresosHidrocarburos 1.0 (IngresosHidrocarburos10).

type IngresosHidrocarburosDocumentoRelacionado

type IngresosHidrocarburosDocumentoRelacionado struct {
	FolioFiscalVinculado      string `json:"folio_fiscal_vinculado,omitempty"`
	FechaFolioFiscalVinculado Fecha  `json:"fecha_folio_fiscal_vinculado,omitzero"`
	Mes                       string `json:"mes,omitempty"`
}

IngresosHidrocarburosDocumentoRelacionado es el nodo ieeh:DocumentoRelacionado del complemento IngresosHidrocarburos.

type IngresosOEntradas

type IngresosOEntradas struct {
	MontTotEntradasPeriodo  *decimal.Decimal `json:"mont_tot_entradas_periodo,omitempty"`
	PartPropAcumDelFideicom *decimal.Decimal `json:"part_prop_acum_del_fideicom,omitempty"`
	PropDelMontTot          *decimal.Decimal `json:"prop_del_mont_tot,omitempty"`
	// IntegracIngresos/@Concepto.
	IntegracIngresos string `json:"integrac_ingresos,omitempty"`
}

IngresosOEntradas es el nodo fideicomisonoempresarial:IngresosOEntradas del complemento Fideicomisonoempresarial.

type InstEducativas

type InstEducativas struct {
	Version        string `json:"version,omitempty"`
	NombreAlumno   string `json:"nombre_alumno,omitempty"`
	CURP           string `json:"curp,omitempty"`
	NivelEducativo string `json:"nivel_educativo,omitempty"`
	AutRVOE        string `json:"aut_rvoe,omitempty"`
	RfcPago        string `json:"rfc_pago,omitempty"`
}

InstEducativas 1.0 (iedu): colegiatura de una institución educativa privada. El nodo del SAT es instEducativas, en minúscula.

type Intereses

type Intereses struct {
	Version           string           `json:"version,omitempty"`
	SistFinanciero    string           `json:"sist_financiero,omitempty"`
	RetiroAORESRetInt string           `json:"retiro_aores_ret_int,omitempty"`
	OperFinancDerivad string           `json:"oper_financ_derivad,omitempty"`
	MontIntNominal    *decimal.Decimal `json:"mont_int_nominal,omitempty"`
	MontIntReal       *decimal.Decimal `json:"mont_int_real,omitempty"`
	Perdida           *decimal.Decimal `json:"perdida,omitempty"`
}

Intereses es el complemento de Retenciones intereses:Intereses.

type Intereseshipotecarios

type Intereseshipotecarios struct {
	Version                    string           `json:"version,omitempty"`
	CreditoDeInstFinanc        string           `json:"credito_de_inst_financ,omitempty"`
	SaldoInsoluto              *decimal.Decimal `json:"saldo_insoluto,omitempty"`
	PropDeducDelCredit         *decimal.Decimal `json:"prop_deduc_del_credit,omitempty"`
	MontTotIntNominalesDev     *decimal.Decimal `json:"mont_tot_int_nominales_dev,omitempty"`
	MontTotIntNominalesDevYPag *decimal.Decimal `json:"mont_tot_int_nominales_dev_y_pag,omitempty"`
	MontTotIntRealPagDeduc     *decimal.Decimal `json:"mont_tot_int_real_pag_deduc,omitempty"`
	NumContrato                string           `json:"num_contrato,omitempty"`
}

Intereseshipotecarios es el complemento de Retenciones intereseshipotecarios:Intereseshipotecarios.

type JubilacionPensionRetiro

type JubilacionPensionRetiro struct {
	TotalUnaExhibicion  *decimal.Decimal `json:"total_una_exhibicion,omitempty"`
	TotalParcialidad    *decimal.Decimal `json:"total_parcialidad,omitempty"`
	MontoDiario         *decimal.Decimal `json:"monto_diario,omitempty"`
	IngresoAcumulable   *decimal.Decimal `json:"ingreso_acumulable,omitempty"`
	IngresoNoAcumulable *decimal.Decimal `json:"ingreso_no_acumulable,omitempty"`
}

JubilacionPensionRetiro es el nodo nomina12:JubilacionPensionRetiro de las Percepciones del complemento Nómina.

type Leyenda

type Leyenda struct {
	DisposicionFiscal string `json:"disposicion_fiscal,omitempty"`
	Norma             string `json:"norma,omitempty"`
	TextoLeyenda      string `json:"texto_leyenda,omitempty"`
}

Leyenda es el nodo leyendasFisc:Leyenda del complemento LeyendasFiscales.

type LeyendasFiscales

type LeyendasFiscales struct {
	Version string    `json:"version,omitempty"`
	Leyenda []Leyenda `json:"leyenda,omitempty"`
}

LeyendasFiscales 1.0.

type NoBeneficiarioExtranjero

type NoBeneficiarioExtranjero struct {
	PaisDeResidParaEfecFisc string `json:"pais_de_resid_para_efec_fisc,omitempty"`
	ConceptoPago            string `json:"concepto_pago,omitempty"`
	DescripcionConcepto     string `json:"descripcion_concepto,omitempty"`
}

NoBeneficiarioExtranjero es el nodo pagosaextranjeros:NoBeneficiario del complemento Pagosaextranjeros.

type Nomina

type Nomina struct {
	Version            string           `json:"version,omitempty"`
	TipoNomina         string           `json:"tipo_nomina,omitempty"`
	RegistroPatronal   string           `json:"registro_patronal,omitempty"`    // 1.1
	NumEmpleado        string           `json:"num_empleado,omitempty"`         // 1.1
	CURP               string           `json:"curp,omitempty"`                 // 1.1
	TipoRegimen        string           `json:"tipo_regimen,omitempty"`         // 1.1
	NumSeguridadSocial string           `json:"num_seguridad_social,omitempty"` // 1.1
	FechaPago          Fecha            `json:"fecha_pago,omitzero"`
	FechaInicialPago   Fecha            `json:"fecha_inicial_pago,omitzero"`
	FechaFinalPago     Fecha            `json:"fecha_final_pago,omitzero"`
	NumDiasPagados     *decimal.Decimal `json:"num_dias_pagados,omitempty"`
	TotalPercepciones  *decimal.Decimal `json:"total_percepciones,omitempty"`
	TotalDeducciones   *decimal.Decimal `json:"total_deducciones,omitempty"`
	TotalOtrosPagos    *decimal.Decimal `json:"total_otros_pagos,omitempty"`

	// 1.1: el resto de los atributos del trabajador. Antiguedad son semanas
	// (un entero), no la duración P…W de 1.2.
	Departamento           string           `json:"departamento,omitempty"`
	CLABE                  string           `json:"clabe,omitempty"`
	Banco                  string           `json:"banco,omitempty"`
	FechaInicioRelLaboral  Fecha            `json:"fecha_inicio_rel_laboral,omitzero"`
	Antiguedad             string           `json:"antiguedad,omitempty"`
	Puesto                 string           `json:"puesto,omitempty"`
	TipoContrato           string           `json:"tipo_contrato,omitempty"`
	TipoJornada            string           `json:"tipo_jornada,omitempty"`
	PeriodicidadPago       string           `json:"periodicidad_pago,omitempty"`
	SalarioBaseCotApor     *decimal.Decimal `json:"salario_base_cot_apor,omitempty"`
	RiesgoPuesto           string           `json:"riesgo_puesto,omitempty"`
	SalarioDiarioIntegrado *decimal.Decimal `json:"salario_diario_integrado,omitempty"`

	Emisor        *NominaEmisor   `json:"emisor,omitempty"`
	Receptor      *NominaReceptor `json:"receptor,omitempty"`
	Percepciones  *Percepciones   `json:"percepciones,omitempty"`
	Deducciones   *Deducciones    `json:"deducciones,omitempty"`
	OtrosPagos    []OtroPago      `json:"otros_pagos,omitempty"`
	Incapacidades []Incapacidad   `json:"incapacidades,omitempty"`
	HorasExtras   []HorasExtra    `json:"horas_extras,omitempty"` // 1.1: HorasExtras/HorasExtra
}

Nomina es el complemento de nómina 1.2 o 1.1 (CFDI 3.2), normalizados en un solo struct; Version dice cuál. 1.1 lleva los datos del trabajador como atributos del nodo Nomina (los campos marcados 1.1, vacíos en 1.2) y no tiene Emisor, Receptor, OtrosPagos ni los totales de 1.2. Cada atributo queda donde lo pone su XSD: un 1.1 no llena Receptor.

type NominaEmisor

type NominaEmisor struct {
	Curp             string       `json:"curp,omitempty"`
	RegistroPatronal string       `json:"registro_patronal,omitempty"`
	RfcPatronOrigen  string       `json:"rfc_patron_origen,omitempty"`
	EntidadSNCF      *EntidadSNCF `json:"entidad_sncf,omitempty"`
}

NominaEmisor es el nodo nomina12:Emisor del complemento Nómina.

type NominaReceptor

type NominaReceptor struct {
	Curp                   string            `json:"curp,omitempty"`
	NumSeguridadSocial     string            `json:"num_seguridad_social,omitempty"`
	FechaInicioRelLaboral  Fecha             `json:"fecha_inicio_rel_laboral,omitzero"`
	Antiguedad             string            `json:"antiguedad,omitempty"` // atributo "Antigüedad"
	TipoContrato           string            `json:"tipo_contrato,omitempty"`
	Sindicalizado          string            `json:"sindicalizado,omitempty"`
	TipoJornada            string            `json:"tipo_jornada,omitempty"`
	TipoRegimen            string            `json:"tipo_regimen,omitempty"`
	NumEmpleado            string            `json:"num_empleado,omitempty"`
	Departamento           string            `json:"departamento,omitempty"`
	Puesto                 string            `json:"puesto,omitempty"`
	RiesgoPuesto           string            `json:"riesgo_puesto,omitempty"`
	PeriodicidadPago       string            `json:"periodicidad_pago,omitempty"`
	Banco                  string            `json:"banco,omitempty"`
	CuentaBancaria         string            `json:"cuenta_bancaria,omitempty"`
	SalarioBaseCotApor     *decimal.Decimal  `json:"salario_base_cot_apor,omitempty"`
	SalarioDiarioIntegrado *decimal.Decimal  `json:"salario_diario_integrado,omitempty"`
	ClaveEntFed            string            `json:"clave_ent_fed,omitempty"`
	SubContratacion        []SubContratacion `json:"sub_contratacion,omitempty"`
}

NominaReceptor es el nodo nomina12:Receptor del complemento Nómina.

type NotariosPublicos

type NotariosPublicos struct {
	Version          string                            `json:"version,omitempty"`
	DescInmuebles    []NotariosPublicosDescInmueble    `json:"desc_inmuebles,omitempty"`
	DatosOperacion   *NotariosPublicosDatosOperacion   `json:"datos_operacion,omitempty"`
	DatosNotario     *NotariosPublicosDatosNotario     `json:"datos_notario,omitempty"`
	DatosEnajenante  *NotariosPublicosDatosEnajenante  `json:"datos_enajenante,omitempty"`
	DatosAdquiriente *NotariosPublicosDatosAdquiriente `json:"datos_adquiriente,omitempty"`
}

NotariosPublicos 1.0: enajenación de inmuebles ante notario. DescInmuebles aplana DescInmuebles/DescInmueble.

type NotariosPublicosDatosAdquiriente

type NotariosPublicosDatosAdquiriente struct {
	CoproSocConyugalE      string                               `json:"copro_soc_conyugal_e,omitempty"`
	DatosUnAdquiriente     *NotariosPublicosDatosPersona        `json:"datos_un_adquiriente,omitempty"`
	DatosAdquirientesCopSC []NotariosPublicosDatosCopropietario `json:"datos_adquirientes_cop_sc,omitempty"`
}

NotariosPublicosDatosAdquiriente es como DatosEnajenante, con los adquirientes.

type NotariosPublicosDatosCopropietario

type NotariosPublicosDatosCopropietario struct {
	Nombre          string           `json:"nombre,omitempty"`
	ApellidoPaterno string           `json:"apellido_paterno,omitempty"`
	ApellidoMaterno string           `json:"apellido_materno,omitempty"`
	RFC             string           `json:"rfc,omitempty"`
	CURP            string           `json:"curp,omitempty"`
	Porcentaje      *decimal.Decimal `json:"porcentaje,omitempty"`
}

NotariosPublicosDatosCopropietario es DatosEnajenanteCopSC o DatosAdquirienteCopSC.

type NotariosPublicosDatosEnajenante

type NotariosPublicosDatosEnajenante struct {
	CoproSocConyugalE     string                               `json:"copro_soc_conyugal_e,omitempty"`
	DatosUnEnajenante     *NotariosPublicosDatosPersona        `json:"datos_un_enajenante,omitempty"`
	DatosEnajenantesCopSC []NotariosPublicosDatosCopropietario `json:"datos_enajenantes_cop_sc,omitempty"`
}

NotariosPublicosDatosEnajenante trae uno de dos: DatosUnEnajenante, o la lista de copropietarios DatosEnajenantesCopSC (aplanada).

type NotariosPublicosDatosNotario

type NotariosPublicosDatosNotario struct {
	CURP              string `json:"curp,omitempty"`
	NumNotaria        string `json:"num_notaria,omitempty"`
	EntidadFederativa string `json:"entidad_federativa,omitempty"`
	Adscripcion       string `json:"adscripcion,omitempty"`
}

NotariosPublicosDatosNotario es el nodo notariospublicos:DatosNotario del complemento NotariosPublicos.

type NotariosPublicosDatosOperacion

type NotariosPublicosDatosOperacion struct {
	NumInstrumentoNotarial string           `json:"num_instrumento_notarial,omitempty"`
	FechaInstNotarial      Fecha            `json:"fecha_inst_notarial,omitzero"`
	MontoOperacion         *decimal.Decimal `json:"monto_operacion,omitempty"`
	Subtotal               *decimal.Decimal `json:"subtotal,omitempty"`
	IVA                    *decimal.Decimal `json:"iva,omitempty"`
}

NotariosPublicosDatosOperacion es el nodo notariospublicos:DatosOperacion del complemento NotariosPublicos.

type NotariosPublicosDatosPersona

type NotariosPublicosDatosPersona struct {
	Nombre          string `json:"nombre,omitempty"`
	ApellidoPaterno string `json:"apellido_paterno,omitempty"`
	ApellidoMaterno string `json:"apellido_materno,omitempty"`
	RFC             string `json:"rfc,omitempty"`
	CURP            string `json:"curp,omitempty"`
}

NotariosPublicosDatosPersona es DatosUnEnajenante o DatosUnAdquiriente (mismos atributos; el XSD exige más en el enajenante).

type NotariosPublicosDescInmueble

type NotariosPublicosDescInmueble struct {
	TipoInmueble string `json:"tipo_inmueble,omitempty"`
	Calle        string `json:"calle,omitempty"`
	NoExterior   string `json:"no_exterior,omitempty"`
	NoInterior   string `json:"no_interior,omitempty"`
	Colonia      string `json:"colonia,omitempty"`
	Localidad    string `json:"localidad,omitempty"`
	Referencia   string `json:"referencia,omitempty"`
	Municipio    string `json:"municipio,omitempty"`
	Estado       string `json:"estado,omitempty"`
	Pais         string `json:"pais,omitempty"`
	CodigoPostal string `json:"codigo_postal,omitempty"`
}

NotariosPublicosDescInmueble es el nodo notariospublicos:DescInmueble del complemento NotariosPublicos.

type ObrasArteAntiguedades

type ObrasArteAntiguedades struct {
	Version                     string           `json:"version,omitempty"`
	TipoBien                    string           `json:"tipo_bien,omitempty"`
	OtrosTipoBien               string           `json:"otros_tipo_bien,omitempty"`
	TituloAdquirido             string           `json:"titulo_adquirido,omitempty"`
	OtrosTituloAdquirido        string           `json:"otros_titulo_adquirido,omitempty"`
	Subtotal                    *decimal.Decimal `json:"subtotal,omitempty"`
	IVA                         *decimal.Decimal `json:"iva,omitempty"`
	FechaAdquisicion            Fecha            `json:"fecha_adquisicion,omitzero"`
	CaracteristicasDeObraoPieza string           `json:"caracteristicas_de_obrao_pieza,omitempty"` // atributo "CaracterísticasDeObraoPieza"
}

ObrasArteAntiguedades 1.0 (nodo obrasarteantiguedades).

type Opciones

type Opciones struct {
	// FechaEstricta solo interpreta las fechas que vienen exactamente en el
	// formato del SAT (AAAA-MM-DDThh:mm:ss, o AAAA-MM-DD en los campos de
	// solo fecha). Las demás se quedan con Time en cero (Fecha.Valida() da
	// false), Fecha.FueraDeFormato en true, y la advertencia es
	// FECHA_INVALIDA en vez de FECHA_FORMATO.
	FechaEstricta bool
}

Opciones ajusta la lectura. El valor cero es la lectura tolerante de Parse y ParseBytes.

Es un struct (y no opciones funcionales) por lo mismo que OpcionesJSON: son interruptores independientes, el valor cero es el comportamiento de siempre y un campo nuevo no rompe a nadie.

type OpcionesJSON

type OpcionesJSON struct {
	// IncluirSello conserva Sello, Certificado, SelloCFD, SelloSAT, los
	// sellos/certificados/cadenas de los pagos y el sello y la cadena de
	// cada SPEI_Tercero. Por omisión se quitan: pesan varios KB y no aportan
	// a quien solo quiere los datos.
	IncluirSello bool
	// Indentar con dos espacios.
	Indentar bool
}

OpcionesJSON ajusta la salida JSON.

type Operacionesconderivados

type Operacionesconderivados struct {
	Version     string           `json:"version,omitempty"`
	MontGanAcum *decimal.Decimal `json:"mont_gan_acum,omitempty"`
	MontPerdDed *decimal.Decimal `json:"mont_perd_ded,omitempty"`
}

Operacionesconderivados es el complemento de Retenciones operacionesconderivados:Operacionesconderivados.

type OtroPago

type OtroPago struct {
	TipoOtroPago             string                    `json:"tipo_otro_pago,omitempty"`
	Clave                    string                    `json:"clave,omitempty"`
	Concepto                 string                    `json:"concepto,omitempty"`
	Importe                  *decimal.Decimal          `json:"importe,omitempty"`
	SubsidioAlEmpleo         *SubsidioAlEmpleo         `json:"subsidio_al_empleo,omitempty"`
	CompensacionSaldosAFavor *CompensacionSaldosAFavor `json:"compensacion_saldos_a_favor,omitempty"`
}

OtroPago es el nodo nomina12:OtroPago del complemento Nómina.

type PFintegranteCoordinado

type PFintegranteCoordinado struct {
	Version        string `json:"version,omitempty"`
	ClaveVehicular string `json:"clave_vehicular,omitempty"`
	Placa          string `json:"placa,omitempty"`
	RFCPF          string `json:"rfcpf,omitempty"`
}

PFintegranteCoordinado 1.0 (pfic): persona física integrante de una coordinación de autotransporte.

type Pago

type Pago struct {
	FechaPago        Fecha              `json:"fecha_pago,omitzero"`
	FormaDePagoP     string             `json:"forma_de_pago_p,omitempty"`
	MonedaP          string             `json:"moneda_p,omitempty"`
	TipoCambioP      *decimal.Decimal   `json:"tipo_cambio_p,omitempty"`
	Monto            *decimal.Decimal   `json:"monto,omitempty"`
	NumOperacion     string             `json:"num_operacion,omitempty"`
	RfcEmisorCtaOrd  string             `json:"rfc_emisor_cta_ord,omitempty"`
	NomBancoOrdExt   string             `json:"nom_banco_ord_ext,omitempty"`
	CtaOrdenante     string             `json:"cta_ordenante,omitempty"`
	RfcEmisorCtaBen  string             `json:"rfc_emisor_cta_ben,omitempty"`
	CtaBeneficiario  string             `json:"cta_beneficiario,omitempty"`
	TipoCadPago      string             `json:"tipo_cad_pago,omitempty"`
	CertPago         string             `json:"cert_pago,omitempty"`
	CadPago          string             `json:"cad_pago,omitempty"`
	SelloPago        string             `json:"sello_pago,omitempty"`
	DoctoRelacionado []DoctoRelacionado `json:"docto_relacionado,omitempty"`
	ImpuestosP       *ImpuestosP        `json:"impuestos_p,omitempty"` // 2.0
	Impuestos        []Impuestos        `json:"impuestos,omitempty"`   // 1.0
}

Pago es el nodo Pago del complemento Pagos (pago10:Pago o pago20:Pago).

type PagoEnEspecie

type PagoEnEspecie struct {
	Version      string `json:"version,omitempty"`
	CvePIC       string `json:"cve_pic,omitempty"`
	FolioSolDon  string `json:"folio_sol_don,omitempty"`
	PzaArtNombre string `json:"pza_art_nombre,omitempty"`
	PzaArtTecn   string `json:"pza_art_tecn,omitempty"`
	PzaArtAProd  string `json:"pza_art_a_prod,omitempty"`
	PzaArtDim    string `json:"pza_art_dim,omitempty"`
}

PagoEnEspecie 1.0: donación de obras de arte en pago en especie.

type Pagos

type Pagos struct {
	Version string        `json:"version,omitempty"`
	Totales *TotalesPagos `json:"totales,omitempty"`
	Pago    []Pago        `json:"pago,omitempty"`
}

Pagos es el complemento de recepción de pagos, 1.0 o 2.0 normalizados en un solo struct. Los campos que solo existen en una versión quedan vacíos en la otra (Totales, ImpuestosP, EquivalenciaDR, ObjetoImpDR, ImpuestosDR son de 2.0; TipoCambioDR, MetodoDePagoDR e Impuestos del pago son de 1.0).

type Pagosaextranjeros

type Pagosaextranjeros struct {
	Version              string                    `json:"version,omitempty"`
	EsBenefEfectDelCobro string                    `json:"es_benef_efect_del_cobro,omitempty"`
	NoBeneficiario       *NoBeneficiarioExtranjero `json:"no_beneficiario,omitempty"`
	Beneficiario         *BeneficiarioExtranjero   `json:"beneficiario,omitempty"`
}

Pagosaextranjeros es el complemento de Retenciones pagosaextranjeros:Pagosaextranjeros.

type ParcialesConstruccion

type ParcialesConstruccion struct {
	Version       string                         `json:"version,omitempty"`
	NumPerLicoAut string                         `json:"num_per_lico_aut,omitempty"`
	Inmueble      *ParcialesConstruccionInmueble `json:"inmueble,omitempty"`
}

ParcialesConstruccion 1.0 (servicioparcialconstruccion, nodo parcialesconstruccion): servicios parciales de construcción de inmuebles destinados a casa habitación. El XSD lo pone en el Complemento del comprobante; si viene en un ComplementoConcepto también se lee, en el concepto.

type ParcialesConstruccionInmueble

type ParcialesConstruccionInmueble struct {
	Calle        string `json:"calle,omitempty"`
	NoExterior   string `json:"no_exterior,omitempty"`
	NoInterior   string `json:"no_interior,omitempty"`
	Colonia      string `json:"colonia,omitempty"`
	Localidad    string `json:"localidad,omitempty"`
	Referencia   string `json:"referencia,omitempty"`
	Municipio    string `json:"municipio,omitempty"`
	Estado       string `json:"estado,omitempty"`
	CodigoPostal string `json:"codigo_postal,omitempty"`
}

ParcialesConstruccionInmueble es el nodo servicioparcial:Inmueble del complemento de servicios parciales de construcción.

type Parte

type Parte struct {
	ClaveProdServ       string                `json:"clave_prod_serv,omitempty"`
	NoIdentificacion    string                `json:"no_identificacion,omitempty"`
	Cantidad            *decimal.Decimal      `json:"cantidad,omitempty"`
	Unidad              string                `json:"unidad,omitempty"`
	Descripcion         string                `json:"descripcion,omitempty"`
	ValorUnitario       *decimal.Decimal      `json:"valor_unitario,omitempty"`
	Importe             *decimal.Decimal      `json:"importe,omitempty"`
	InformacionAduanera []InformacionAduanera `json:"informacion_aduanera,omitempty"`
}

Parte es el nodo cfdi:Parte de un concepto.

type ParteComplemento

type ParteComplemento struct {
	Cantidad            *decimal.Decimal                 `json:"cantidad,omitempty"`
	Unidad              string                           `json:"unidad,omitempty"`
	NoIdentificacion    string                           `json:"no_identificacion,omitempty"`
	Descripcion         string                           `json:"descripcion,omitempty"`
	ValorUnitario       *decimal.Decimal                 `json:"valor_unitario,omitempty"`
	Importe             *decimal.Decimal                 `json:"importe,omitempty"`
	InformacionAduanera []InformacionAduaneraComplemento `json:"informacion_aduanera,omitempty"`
}

ParteComplemento es la Parte de terceros y ventavehiculos.

type PartesTransporte

type PartesTransporte struct {
	ParteTransporte string `json:"parte_transporte,omitempty"`
}

PartesTransporte es el nodo PartesTransporte de TiposFigura del complemento CartaPorte.

type Pedimentos

type Pedimentos struct {
	Pedimento string `json:"pedimento,omitempty"`
}

Pedimentos es el nodo Pedimentos de una Mercancia del complemento CartaPorte.

type Percepcion

type Percepcion struct {
	TipoPercepcion   string            `json:"tipo_percepcion,omitempty"`
	Clave            string            `json:"clave,omitempty"`
	Concepto         string            `json:"concepto,omitempty"`
	ImporteGravado   *decimal.Decimal  `json:"importe_gravado,omitempty"`
	ImporteExento    *decimal.Decimal  `json:"importe_exento,omitempty"`
	AccionesOTitulos *AccionesOTitulos `json:"acciones_o_titulos,omitempty"`
	HorasExtra       []HorasExtra      `json:"horas_extra,omitempty"`
}

Percepcion es el nodo nomina12:Percepcion del complemento Nómina.

type Percepciones

type Percepciones struct {
	TotalSueldos                 *decimal.Decimal         `json:"total_sueldos,omitempty"`
	TotalSeparacionIndemnizacion *decimal.Decimal         `json:"total_separacion_indemnizacion,omitempty"`
	TotalJubilacionPensionRetiro *decimal.Decimal         `json:"total_jubilacion_pension_retiro,omitempty"`
	TotalGravado                 *decimal.Decimal         `json:"total_gravado,omitempty"`
	TotalExento                  *decimal.Decimal         `json:"total_exento,omitempty"`
	Percepcion                   []Percepcion             `json:"percepcion,omitempty"`
	JubilacionPensionRetiro      *JubilacionPensionRetiro `json:"jubilacion_pension_retiro,omitempty"`
	SeparacionIndemnizacion      *SeparacionIndemnizacion `json:"separacion_indemnizacion,omitempty"`
}

Percepciones es el nodo nomina12:Percepciones del complemento Nómina.

type PeriodoRetenciones

type PeriodoRetenciones struct {
	MesIni    string `json:"mes_ini,omitempty"`
	MesFin    string `json:"mes_fin,omitempty"`
	Ejercicio string `json:"ejercicio,omitempty"`
}

PeriodoRetenciones: meses y ejercicio como texto, como los demás enteros del SAT.

type Planesderetiro

type Planesderetiro struct {
	Version                        string                   `json:"version,omitempty"`
	SistemaFinanc                  string                   `json:"sistema_financ,omitempty"`
	MontTotAportAnioInmAnterior    *decimal.Decimal         `json:"mont_tot_aport_anio_inm_anterior,omitempty"`
	MontIntRealesDevengAniooInmAnt *decimal.Decimal         `json:"mont_int_reales_deveng_anioo_inm_ant,omitempty"`
	HuboRetirosAnioInmAntPer       string                   `json:"hubo_retiros_anio_inm_ant_per,omitempty"`
	MontTotRetiradoAnioInmAntPer   *decimal.Decimal         `json:"mont_tot_retirado_anio_inm_ant_per,omitempty"`
	MontTotExentRetiradoAnioInmAnt *decimal.Decimal         `json:"mont_tot_exent_retirado_anio_inm_ant,omitempty"`
	MontTotExedenteAnioInmAnt      *decimal.Decimal         `json:"mont_tot_exedente_anio_inm_ant,omitempty"`
	HuboRetirosAnioInmAnt          string                   `json:"hubo_retiros_anio_inm_ant,omitempty"`
	MontTotRetiradoAnioInmAnt      *decimal.Decimal         `json:"mont_tot_retirado_anio_inm_ant,omitempty"`
	NumReferencia                  string                   `json:"num_referencia,omitempty"`
	AportacionesODepositos         []AportacionesODepositos `json:"aportaciones_o_depositos,omitempty"`
}

Planesderetiro 1.0 y 1.1 en un objeto con version: la 1.1 agrega AportacionesODepositos y NumReferencia, que vienen vacíos en la 1.0.

type PorCuentadeTerceros

type PorCuentadeTerceros struct {
	Version                  string                          `json:"version,omitempty"`
	Rfc                      string                          `json:"rfc,omitempty"`
	Nombre                   string                          `json:"nombre,omitempty"`
	InformacionFiscalTercero *UbicacionFiscal                `json:"informacion_fiscal_tercero,omitempty"`
	InformacionAduanera      *InformacionAduaneraComplemento `json:"informacion_aduanera,omitempty"`
	Parte                    []ParteComplemento              `json:"parte,omitempty"`
	CuentaPredial            *CuentaPredial                  `json:"cuenta_predial,omitempty"`
	Impuestos                *ImpuestosTerceros              `json:"impuestos,omitempty"`
}

PorCuentadeTerceros 1.1 (terceros), de CFDI 3.3: el concepto se cobra por cuenta de un tercero. En 4.0 lo sustituye el nodo ACuentaTerceros.

type Premios

type Premios struct {
	Version           string           `json:"version,omitempty"`
	EntidadFederativa string           `json:"entidad_federativa,omitempty"`
	MontTotPago       *decimal.Decimal `json:"mont_tot_pago,omitempty"`
	MontTotPagoGrav   *decimal.Decimal `json:"mont_tot_pago_grav,omitempty"`
	MontTotPagoExent  *decimal.Decimal `json:"mont_tot_pago_exent,omitempty"`
}

Premios es el complemento de Retenciones premios:Premios.

type Receptor

type Receptor struct {
	Rfc                     string `json:"rfc,omitempty"`
	Nombre                  string `json:"nombre,omitempty"`
	DomicilioFiscalReceptor string `json:"domicilio_fiscal_receptor,omitempty"`
	ResidenciaFiscal        string `json:"residencia_fiscal,omitempty"`
	NumRegIdTrib            string `json:"num_reg_id_trib,omitempty"`
	RegimenFiscalReceptor   string `json:"regimen_fiscal_receptor,omitempty"`
	UsoCFDI                 string `json:"uso_cfdi,omitempty"`
}

Receptor es el nodo cfdi:Receptor del comprobante.

type ReceptorExtranjeroRetenciones

type ReceptorExtranjeroRetenciones struct {
	NumRegIdTribR string `json:"num_reg_id_trib_r,omitempty"`
	NomDenRazSocR string `json:"nom_den_raz_soc_r,omitempty"`
}

ReceptorExtranjeroRetenciones es el nodo retenciones:Extranjero del Receptor del CFDI de Retenciones.

type ReceptorNacionalRetenciones

type ReceptorNacionalRetenciones struct {
	RfcR             string `json:"rfc_r,omitempty"`
	NomDenRazSocR    string `json:"nom_den_raz_soc_r,omitempty"`
	CurpR            string `json:"curp_r,omitempty"`
	DomicilioFiscalR string `json:"domicilio_fiscal_r,omitempty"` // 2.0
}

ReceptorNacionalRetenciones es el nodo retenciones:Nacional del Receptor del CFDI de Retenciones.

type ReceptorRetenciones

type ReceptorRetenciones struct {
	NacionalidadR string                         `json:"nacionalidad_r,omitempty"`
	Nacional      *ReceptorNacionalRetenciones   `json:"nacional,omitempty"`
	Extranjero    *ReceptorExtranjeroRetenciones `json:"extranjero,omitempty"`
}

ReceptorRetenciones trae Nacional o Extranjero según NacionalidadR.

type RegimenAduaneroCCP

type RegimenAduaneroCCP struct {
	RegimenAduanero string `json:"regimen_aduanero,omitempty"`
}

RegimenAduaneroCCP es el nodo RegimenAduaneroCCP de CartaPorte 3.1.

type Regla

type Regla struct {
	Codigo string `json:"codigo"`
	// Nivel es NivelError si la regla tiene fundamento en un documento
	// oficial del SAT (Citas: matriz de errores, estándar o guía de
	// llenado); NivelAdvertencia si no lo tiene o si el SAT pide no
	// aplicarla.
	Nivel string `json:"nivel"`
	// Referencia es el texto de Citas más las fuentes secundarias
	// (CfdiUtils, satcfdi) cuando ayudan a entender la regla.
	Referencia  string    `json:"referencia"`
	Descripcion string    `json:"descripcion"`
	Citas       []CitaSAT `json:"citas,omitempty"`
	// Margen, si no está vacío, hace de la regla una de dos niveles: lo
	// que no cumple la letra del SAT (Descripcion) pero cae dentro de
	// Margen sale con NivelAdvertencia, con el mismo código y la misma
	// referencia; lo que queda fuera también del margen, con Nivel. Lo
	// llevan el Importe del impuesto del concepto y las sumas de impuestos:
	// los PAC timbran documentos con un centavo de diferencia por el
	// redondeo de cada concepto, y marcarlos como error engañaría a quien
	// usa la librería.
	Margen string `json:"margen,omitempty"`
	// Vigencia, si no está vacía, dice qué partes de la regla son error
	// solo desde una fecha: en un comprobante con Fecha anterior, el
	// hallazgo de esas partes sale con NivelAdvertencia y el mensaje lo
	// dice. Lo llevan las reglas de Nómina que el SAT agregó después de la
	// primera versión del estándar.
	Vigencia string `json:"vigencia,omitempty"`
	// SoloGuia, si no está vacío, dice qué partes de la regla solo salen de
	// la guía de llenado, sin código en la matriz de errores ni texto en el
	// estándar: sus hallazgos salen con NivelAdvertencia.
	SoloGuia string `json:"solo_guia,omitempty"`
}

Regla describe una regla de ValidarReglas: su código, el nivel de sus hallazgos, de dónde sale (la referencia que va en Hallazgo.Regla) y qué comprueba.

type Remanente

type Remanente struct {
	ProporcionRem *decimal.Decimal `json:"proporcion_rem,omitempty"`
}

Remanente es el nodo dividendos:Remanente del complemento Dividendos.

type Remolque

type Remolque struct {
	SubTipoRem string `json:"sub_tipo_rem,omitempty"`
	Placa      string `json:"placa,omitempty"`
}

Remolque es el nodo Remolque del Autotransporte del complemento CartaPorte.

type RemolqueCCP

type RemolqueCCP struct {
	SubTipoRemCCP string `json:"sub_tipo_rem_ccp,omitempty"`
	PlacaCCP      string `json:"placa_ccp,omitempty"`
}

RemolqueCCP es el nodo RemolqueCCP de un Contenedor marítimo de CartaPorte 3.1.

type RenovacionDecretoRenovVehicular

type RenovacionDecretoRenovVehicular struct {
	VehEnaj                           string                    `json:"veh_enaj,omitempty"`
	VehiculosUsadosEnajenadoPermAlFab []RenovacionVehiculoUsado `json:"vehiculos_usados_enajenado_perm_al_fab,omitempty"`
	VehiculoNuvoSemEnajenadoFabAlPerm *RenovacionVehiculoNuevo  `json:"vehiculo_nuvo_sem_enajenado_fab_al_perm,omitempty"`
}

RenovacionDecretoRenovVehicular es el nodo decreto:DecretoRenovVehicular del complemento renovacionysustitucionvehiculos.

type RenovacionDecretoSustitVehicular

type RenovacionDecretoSustitVehicular struct {
	VehEnaj                           string                   `json:"veh_enaj,omitempty"`
	VehiculoUsadoEnajenadoPermAlFab   *RenovacionVehiculoUsado `json:"vehiculo_usado_enajenado_perm_al_fab,omitempty"`
	VehiculoNuvoSemEnajenadoFabAlPerm *RenovacionVehiculoNuevo `json:"vehiculo_nuvo_sem_enajenado_fab_al_perm,omitempty"`
}

RenovacionDecretoSustitVehicular es el nodo decreto:DecretoSustitVehicular del complemento renovacionysustitucionvehiculos.

type RenovacionVehiculoNuevo

type RenovacionVehiculoNuevo struct {
	Anio      string `json:"anio,omitempty"` // atributo "Año"
	Modelo    string `json:"modelo,omitempty"`
	NumPlacas string `json:"num_placas,omitempty"`
	RFC       string `json:"rfc,omitempty"`
}

RenovacionVehiculoNuevo es el VehiculoNuvoSemEnajenadoFabAlPerm (así, sin e) de los dos decretos.

type RenovacionVehiculoUsado

type RenovacionVehiculoUsado struct {
	PrecioVehUsado *decimal.Decimal `json:"precio_veh_usado,omitempty"`
	TipoVeh        string           `json:"tipo_veh,omitempty"`
	Marca          string           `json:"marca,omitempty"`
	TipooClase     string           `json:"tipoo_clase,omitempty"`
	Anio           string           `json:"anio,omitempty"` // atributo "Año"
	Modelo         string           `json:"modelo,omitempty"`
	NIV            string           `json:"niv,omitempty"`
	NumSerie       string           `json:"num_serie,omitempty"`
	NumPlacas      string           `json:"num_placas,omitempty"`
	NumMotor       string           `json:"num_motor,omitempty"`
	NumFolTarjCir  string           `json:"num_fol_tarj_cir,omitempty"`
	NumFolAvisoint string           `json:"num_fol_avisoint,omitempty"` // solo sustitución
	NumPedIm       string           `json:"num_ped_im,omitempty"`
	Aduana         string           `json:"aduana,omitempty"`
	FechaRegulVeh  Fecha            `json:"fecha_regul_veh,omitzero"`
	Foliofiscal    string           `json:"foliofiscal,omitempty"`
}

RenovacionVehiculoUsado es un vehículo usado enajenado al fabricante: el de un decreto de sustitución trae además NumFolAvisoint y exige más.

type RenovacionySustitucionVehiculos

type RenovacionySustitucionVehiculos struct {
	Version                string                            `json:"version,omitempty"`
	TipoDeDecreto          string                            `json:"tipo_de_decreto,omitempty"`
	DecretoRenovVehicular  *RenovacionDecretoRenovVehicular  `json:"decreto_renov_vehicular,omitempty"`
	DecretoSustitVehicular *RenovacionDecretoSustitVehicular `json:"decreto_sustit_vehicular,omitempty"`
}

RenovacionySustitucionVehiculos 1.0 (nodo renovacionysustitucionvehiculos): decretos de renovación (varios vehículos usados) o sustitución (uno) del parque vehicular.

type ResultadoChequeo

type ResultadoChequeo struct {
	Estado  string `json:"estado"`            // ChequeoCumple, ChequeoNoCumple o ChequeoNoVerificable
	Motivo  string `json:"motivo,omitempty"`  // código estable; vacío si cumple, salvo CERTIFICADO_DEL_SAT
	Mensaje string `json:"mensaje,omitempty"` // para personas; puede cambiar
}

ResultadoChequeo es uno de los chequeos del certificado.

type ResultadoSello

type ResultadoSello struct {
	Estado  string `json:"estado"`            // SelloValido, SelloInvalido o SelloNoVerificable
	Motivo  string `json:"motivo,omitempty"`  // código estable; vacío si es válido
	Mensaje string `json:"mensaje,omitempty"` // para personas; puede cambiar
	// Algoritmo con que verificó la firma ("SHA256withRSA", "SHA1withRSA").
	Algoritmo string `json:"algoritmo,omitempty"`
	// NoCertificado del certificado con que se verificó (ver NoCertificado).
	NoCertificado string `json:"no_certificado,omitempty"`
}

ResultadoSello es la verificación de un sello (el del emisor o el del SAT en el timbre).

type ResultadoValidacion

type ResultadoValidacion struct {
	Estado    string     `json:"estado"`
	Hallazgos []Hallazgo `json:"hallazgos"`
}

ResultadoValidacion es el resultado de ValidarEsquema.

func ValidarEsquema

func ValidarEsquema(xml []byte) *ResultadoValidacion

ValidarEsquema valida el documento contra los esquemas XSD del SAT que trae la librería (traducidos a Go en esquema_reglas.go y esquema_catalogos.go): CFDI 4.0 y 3.3, TimbreFiscalDigital 1.1, y los complementos Pagos 2.0 y 1.0, Nómina 1.2, ImpuestosLocales, CartaPorte 3.1, 3.0 y 2.0, ComercioExterior 2.0 y 1.1, HidroYPetro, CFDIRegistroFiscal, Divisas, EstadoDeCuentaCombustible 1.2, PorCuentadeTerceros 1.1, VentaVehiculos 1.1, Aerolineas, LeyendasFiscales, instEducativas y Donatarias 1.1, con sus catálogos.

Valida como lo haría un validador XSD (libxml2, p. ej.) con esos esquemas cargados: estructura (orden, minOccurs, maxOccurs, elementos y atributos no declarados, texto), atributos requeridos y fijos, tipos y facetas (pattern, catálogos, rangos, dígitos, longitudes), después de aplicar el whiteSpace del tipo. Los xs:any del SAT (Complemento, ComplementoConcepto, Addenda) son estrictos: un elemento ahí sin esquema en la librería no se valida y queda como aviso EsquemaNoSoportado (el resultado es ValidacionIncompleta si no hay errores), igual que la raíz si no es un Comprobante 3.3 o 4.0.

Trabaja sobre los bytes, como CadenaOriginal: el esquema habla del documento tal cual y no de lo que el modelo normaliza. Por lo mismo, lee el XML como un procesador conforme: con el encoding que declara y sin corregir nada. Un documento que Parse lee con advertencias de encoding, caracteres prohibidos o XML no estricto aquí es EsquemaXMLIlegible.

Nunca falla: todo lo que encuentra va en los hallazgos.

type RetEfectFideicomiso

type RetEfectFideicomiso struct {
	MontRetRelPagFideic *decimal.Decimal `json:"mont_ret_rel_pag_fideic,omitempty"`
	DescRetRelPagFideic string           `json:"desc_ret_rel_pag_fideic,omitempty"`
}

RetEfectFideicomiso es el nodo fideicomisonoempresarial:RetEfectFideicomiso del complemento Fideicomisonoempresarial.

type Retenciones

type Retenciones struct {
	Version       string `json:"version,omitempty"`
	FolioInt      string `json:"folio_int,omitempty"`
	Sello         string `json:"sello,omitempty"`
	NoCertificado string `json:"no_certificado,omitempty"`
	Certificado   string `json:"certificado,omitempty"`
	// FechaExp: en 2.0, hora civil sin zona como la Fecha del CFDI; en 1.0
	// trae su zona («2015-02-24T11:02:46-06:00»). Fecha.Time es la hora tal
	// cual viene escrita, sin aplicar la zona; el texto la conserva.
	FechaExp       Fecha  `json:"fecha_exp,omitzero"`
	LugarExpRetenc string `json:"lugar_exp_retenc,omitempty"`
	CveRetenc      string `json:"cve_retenc,omitempty"`
	DescRetenc     string `json:"desc_retenc,omitempty"`

	CfdiRetenRelacionados *CfdiRetenRelacionados `json:"cfdi_reten_relacionados,omitempty"`
	Emisor                *EmisorRetenciones     `json:"emisor,omitempty"`
	Receptor              *ReceptorRetenciones   `json:"receptor,omitempty"`
	Periodo               *PeriodoRetenciones    `json:"periodo,omitempty"`
	Totales               *TotalesRetenciones    `json:"totales,omitempty"`

	// Complementos soportados.
	TimbreFiscalDigital              *TimbreFiscalDigital              `json:"timbre_fiscal_digital,omitempty"`
	ServiciosPlataformasTecnologicas *ServiciosPlataformasTecnologicas `json:"servicios_plataformas_tecnologicas,omitempty"`
	Intereses                        *Intereses                        `json:"intereses,omitempty"`
	Pagosaextranjeros                *Pagosaextranjeros                `json:"pagosaextranjeros,omitempty"`
	Dividendos                       *Dividendos                       `json:"dividendos,omitempty"`
	EnajenaciondeAcciones            *EnajenaciondeAcciones            `json:"enajenacionde_acciones,omitempty"`
	Arrendamientoenfideicomiso       *Arrendamientoenfideicomiso       `json:"arrendamientoenfideicomiso,omitempty"`
	Operacionesconderivados          *Operacionesconderivados          `json:"operacionesconderivados,omitempty"`
	Intereseshipotecarios            *Intereseshipotecarios            `json:"intereseshipotecarios,omitempty"`
	Fideicomisonoempresarial         *Fideicomisonoempresarial         `json:"fideicomisonoempresarial,omitempty"`
	Planesderetiro                   *Planesderetiro                   `json:"planesderetiro,omitempty"`
	Premios                          *Premios                          `json:"premios,omitempty"`
	SectorFinanciero                 *SectorFinanciero                 `json:"sector_financiero,omitempty"`
	// ComplementosNoSoportados lista los nodos de Complemento que no se
	// leen, como "{namespace}NombreLocal", sin repetir.
	ComplementosNoSoportados []string `json:"complementos_no_soportados,omitempty"`

	// Advertencias de la lectura. En JSON van fuera de "retenciones", en el
	// sobre.
	Advertencias []Advertencia `json:"-"`
}

Retenciones es un CFDI de Retenciones e información de pagos (1.0 o 2.0) leído y normalizado.

Las dos versiones van en el mismo modelo con los nombres de la 2.0; en 1.0 cambian estos (el nombre de 1.0 entre paréntesis): NoCertificado (NumCert), Certificado (Cert), Emisor.RfcE (RFCEmisor), Emisor.CurpE (CURPE, solo 1.0), Receptor.NacionalidadR (Nacionalidad), Nacional.RfcR (RFCRecep), Nacional.CurpR (CURPR), Extranjero.NumRegIdTribR (NumRegIdTrib), Periodo.Ejercicio (Ejerc), Totales.MontoTot* y MontoRet (montoTot*, montoRet) e ImpRetenidos.ImpuestoRet (Impuesto). Las rutas de las advertencias usan el nombre que trae el documento. Lo exclusivo de 2.0 (LugarExpRetenc, CfdiRetenRelacionados, RegimenFiscalE, DomicilioFiscalR, UtilidadBimestral, ISRCorrespondiente) viene vacío en 1.0.

func ParseRetenciones

func ParseRetenciones(r io.Reader) (*Retenciones, error)

ParseRetenciones lee un CFDI de Retenciones e información de pagos 1.0 o 2.0, con la misma tolerancia que Parse.

Solo devuelve error (un *Error) si el documento no es XML o si su raíz no es un Retenciones de retencionpago/1 o retencionpago/2 (ClaseNoEsRetenciones). Cualquier otra rareza se lee como se pueda y queda en Retenciones.Advertencias.

func ParseRetencionesBytes

func ParseRetencionesBytes(b []byte) (*Retenciones, error)

ParseRetencionesBytes es ParseRetenciones sobre un []byte. No modifica b.

func (*Retenciones) AdvertenciaEn

func (rt *Retenciones) AdvertenciaEn(rutaNodo, atributo string) (Advertencia, bool)

AdvertenciaEn es Comprobante.AdvertenciaEn para Retenciones.

func (*Retenciones) Complementos

func (rt *Retenciones) Complementos() []string

Complementos lista los complementos presentes sin repetir: los soportados como "Nombre versión" ("TimbreFiscalDigital 1.1", "ServiciosPlataformasTecnologicas 1.0", "Planesderetiro 1.1") y los no soportados como "{namespace}NombreLocal".

func (*Retenciones) Documento

Documento arma el sobre. Sin IncluirSello trabaja sobre una copia.

func (*Retenciones) JSON

func (rt *Retenciones) JSON() ([]byte, error)

JSON devuelve el DocumentoRetenciones sin sellos y sin indentar.

func (*Retenciones) JSONOpciones

func (rt *Retenciones) JSONOpciones(o OpcionesJSON) ([]byte, error)

JSONOpciones devuelve el DocumentoRetenciones con las opciones dadas.

func (*Retenciones) TieneAdvertencia

func (rt *Retenciones) TieneAdvertencia(codigo string) bool

TieneAdvertencia dice si la lectura produjo al menos una advertencia con ese código.

func (*Retenciones) UUID

func (rt *Retenciones) UUID() string

UUID del timbre, normalizado; "" si no hay timbre.

type RetencionesLocales

type RetencionesLocales struct {
	ImpLocRetenido  string           `json:"imp_loc_retenido,omitempty"`
	TasadeRetencion *decimal.Decimal `json:"tasade_retencion,omitempty"`
	Importe         *decimal.Decimal `json:"importe,omitempty"`
}

RetencionesLocales es el nodo implocal:RetencionesLocales del complemento ImpuestosLocales.

type SPEIBeneficiario

type SPEIBeneficiario struct {
	BancoReceptor string           `json:"banco_receptor,omitempty"`
	Nombre        string           `json:"nombre,omitempty"`
	TipoCuenta    *decimal.Decimal `json:"tipo_cuenta,omitempty"`
	Cuenta        *decimal.Decimal `json:"cuenta,omitempty"`
	RFC           string           `json:"rfc,omitempty"`
	Concepto      string           `json:"concepto,omitempty"`
	IVA           *decimal.Decimal `json:"iva,omitempty"`
	MontoPago     *decimal.Decimal `json:"monto_pago,omitempty"`
}

SPEIBeneficiario es el nodo spei:Beneficiario del complemento SPEI de terceros.

type SPEIOrdenante

type SPEIOrdenante struct {
	BancoEmisor string           `json:"banco_emisor,omitempty"`
	Nombre      string           `json:"nombre,omitempty"`
	TipoCuenta  *decimal.Decimal `json:"tipo_cuenta,omitempty"`
	Cuenta      *decimal.Decimal `json:"cuenta,omitempty"`
	RFC         string           `json:"rfc,omitempty"`
}

SPEIOrdenante y SPEIBeneficiario: TipoCuenta y Cuenta son xs:decimal en el XSD; el decimal conserva el texto (los ceros a la izquierda de una CLABE incluidos).

type SPEITercero

type SPEITercero struct {
	FechaOperacion    Fecha             `json:"fecha_operacion,omitzero"`
	Hora              string            `json:"hora,omitempty"` // xs:time, tal cual
	ClaveSPEI         string            `json:"clave_spei,omitempty"`
	Sello             string            `json:"sello,omitempty"`
	NumeroCertificado string            `json:"numero_certificado,omitempty"`
	CadenaCDA         string            `json:"cadena_cda,omitempty"`
	Ordenante         *SPEIOrdenante    `json:"ordenante,omitempty"`
	Beneficiario      *SPEIBeneficiario `json:"beneficiario,omitempty"`
}

SPEITercero es un SPEI_Tercero del complemento SPEI (spei), que no tiene versión: el comprobante de una transferencia SPEI a terceros que emite Banxico. Complemento_SPEI no tiene atributos y se aplana (Comprobante.ComplementoSPEI). Sello y CadenaCDA son de Banxico y, como los demás sellos, solo salen en JSON con IncluirSello.

type SectorFinanciero

type SectorFinanciero struct {
	Version         string `json:"version,omitempty"`
	IdFideicom      string `json:"id_fideicom,omitempty"`
	NomFideicom     string `json:"nom_fideicom,omitempty"`
	DescripFideicom string `json:"descrip_fideicom,omitempty"`
}

SectorFinanciero es el complemento de Retenciones sectorfinanciero:SectorFinanciero.

type Seguros

type Seguros struct {
	AseguraRespCivil   string           `json:"asegura_resp_civil,omitempty"`
	PolizaRespCivil    string           `json:"poliza_resp_civil,omitempty"`
	AseguraMedAmbiente string           `json:"asegura_med_ambiente,omitempty"`
	PolizaMedAmbiente  string           `json:"poliza_med_ambiente,omitempty"`
	AseguraCarga       string           `json:"asegura_carga,omitempty"`
	PolizaCarga        string           `json:"poliza_carga,omitempty"`
	PrimaSeguro        *decimal.Decimal `json:"prima_seguro,omitempty"`
}

Seguros es el nodo Seguros del Autotransporte del complemento CartaPorte.

type SeparacionIndemnizacion

type SeparacionIndemnizacion struct {
	TotalPagado         *decimal.Decimal `json:"total_pagado,omitempty"`
	NumAniosServicio    string           `json:"num_anios_servicio,omitempty"` // atributo "NumAñosServicio"
	UltimoSueldoMensOrd *decimal.Decimal `json:"ultimo_sueldo_mens_ord,omitempty"`
	IngresoAcumulable   *decimal.Decimal `json:"ingreso_acumulable,omitempty"`
	IngresoNoAcumulable *decimal.Decimal `json:"ingreso_no_acumulable,omitempty"`
}

SeparacionIndemnizacion es el nodo nomina12:SeparacionIndemnizacion de las Percepciones del complemento Nómina.

type ServiciosPlataformasTecnologicas

type ServiciosPlataformasTecnologicas struct {
	Version                           string                `json:"version,omitempty"`
	Periodicidad                      string                `json:"periodicidad,omitempty"`
	NumServ                           string                `json:"num_serv,omitempty"`
	MonTotServSIVA                    *decimal.Decimal      `json:"mon_tot_serv_siva,omitempty"`
	TotalIVATrasladado                *decimal.Decimal      `json:"total_iva_trasladado,omitempty"`
	TotalIVARetenido                  *decimal.Decimal      `json:"total_iva_retenido,omitempty"`
	TotalISRRetenido                  *decimal.Decimal      `json:"total_isr_retenido,omitempty"`
	DifIVAEntregadoPrestServ          *decimal.Decimal      `json:"dif_iva_entregado_prest_serv,omitempty"`
	MonTotalporUsoPlataforma          *decimal.Decimal      `json:"mon_totalpor_uso_plataforma,omitempty"`
	MonTotalContribucionGubernamental *decimal.Decimal      `json:"mon_total_contribucion_gubernamental,omitempty"`
	Servicios                         []DetallesDelServicio `json:"servicios,omitempty"` // Servicios/DetallesDelServicio
}

ServiciosPlataformasTecnologicas 1.0: servicios de plataformas tecnológicas de intermediación.

type SubContratacion

type SubContratacion struct {
	RfcLabora        string           `json:"rfc_labora,omitempty"`
	PorcentajeTiempo *decimal.Decimal `json:"porcentaje_tiempo,omitempty"`
}

SubContratacion es el nodo nomina12:SubContratacion del Receptor del complemento Nómina.

type SubsidioAlEmpleo

type SubsidioAlEmpleo struct {
	SubsidioCausado *decimal.Decimal `json:"subsidio_causado,omitempty"`
}

SubsidioAlEmpleo es el nodo nomina12:SubsidioAlEmpleo de un OtroPago del complemento Nómina.

type TimbreFiscalDigital

type TimbreFiscalDigital struct {
	Version string `json:"version,omitempty"`
	// UUID normalizado: sin espacios y en mayúsculas.
	UUID string `json:"uuid,omitempty"`
	// UUIDOriginal es el texto como venía, solo si difiere de UUID.
	UUIDOriginal     string `json:"uuid_original,omitempty"`
	FechaTimbrado    Fecha  `json:"fecha_timbrado,omitzero"`
	RfcProvCertif    string `json:"rfc_prov_certif,omitempty"`
	Leyenda          string `json:"leyenda,omitempty"`
	SelloCFD         string `json:"sello_cfd,omitempty"`
	NoCertificadoSAT string `json:"no_certificado_sat,omitempty"`
	SelloSAT         string `json:"sello_sat,omitempty"`
}

TimbreFiscalDigital 1.0 o 1.1. En 1.0 los atributos van en minúsculas (version, selloCFD…) y no existe RfcProvCertif; se leen igual.

type TipoDocumento

type TipoDocumento string

TipoDocumento es lo que dice Detectar de un XML.

const (
	TipoComprobante TipoDocumento = "comprobante" // raíz Comprobante de cfd/3 o cfd/4: se lee con Parse
	TipoRetenciones TipoDocumento = "retenciones" // raíz Retenciones de retencionpago/1 o /2: se lee con ParseRetenciones
	TipoOtro        TipoDocumento = "otro"        // otra raíz, o no es XML
)

Tipos de documento de Detectar.

func Detectar

func Detectar(b []byte) TipoDocumento

Detectar mira solo el elemento raíz para decidir con qué función leer el documento. Lee tolerante como Parse (encoding corregido, XML no estricto): lo que Parse o ParseRetenciones rechazarían por no ser XML sale como TipoOtro.

type TiposFigura

type TiposFigura struct {
	TipoFigura             string             `json:"tipo_figura,omitempty"`
	RFCFigura              string             `json:"rfc_figura,omitempty"`
	NumLicencia            string             `json:"num_licencia,omitempty"`
	NombreFigura           string             `json:"nombre_figura,omitempty"`
	NumRegIdTribFigura     string             `json:"num_reg_id_trib_figura,omitempty"`
	ResidenciaFiscalFigura string             `json:"residencia_fiscal_figura,omitempty"`
	PartesTransporte       []PartesTransporte `json:"partes_transporte,omitempty"`
	Domicilio              *Domicilio         `json:"domicilio,omitempty"`
}

TiposFigura es el nodo TiposFigura de la FiguraTransporte del complemento CartaPorte (2.0 y 3.x).

type TotalesPagos

type TotalesPagos struct {
	TotalRetencionesIVA         *decimal.Decimal `json:"total_retenciones_iva,omitempty"`
	TotalRetencionesISR         *decimal.Decimal `json:"total_retenciones_isr,omitempty"`
	TotalRetencionesIEPS        *decimal.Decimal `json:"total_retenciones_ieps,omitempty"`
	TotalTrasladosBaseIVA16     *decimal.Decimal `json:"total_traslados_base_iva16,omitempty"`
	TotalTrasladosImpuestoIVA16 *decimal.Decimal `json:"total_traslados_impuesto_iva16,omitempty"`
	TotalTrasladosBaseIVA8      *decimal.Decimal `json:"total_traslados_base_iva8,omitempty"`
	TotalTrasladosImpuestoIVA8  *decimal.Decimal `json:"total_traslados_impuesto_iva8,omitempty"`
	TotalTrasladosBaseIVA0      *decimal.Decimal `json:"total_traslados_base_iva0,omitempty"`
	TotalTrasladosImpuestoIVA0  *decimal.Decimal `json:"total_traslados_impuesto_iva0,omitempty"`
	TotalTrasladosBaseIVAExento *decimal.Decimal `json:"total_traslados_base_iva_exento,omitempty"`
	MontoTotalPagos             *decimal.Decimal `json:"monto_total_pagos,omitempty"`
}

TotalesPagos es el nodo pago20:Totales del complemento Pagos 2.0.

type TotalesRetenciones

type TotalesRetenciones struct {
	MontoTotOperacion  *decimal.Decimal `json:"monto_tot_operacion,omitempty"`
	MontoTotGrav       *decimal.Decimal `json:"monto_tot_grav,omitempty"`
	MontoTotExent      *decimal.Decimal `json:"monto_tot_exent,omitempty"`
	MontoTotRet        *decimal.Decimal `json:"monto_tot_ret,omitempty"`
	UtilidadBimestral  *decimal.Decimal `json:"utilidad_bimestral,omitempty"`  // 2.0
	ISRCorrespondiente *decimal.Decimal `json:"isr_correspondiente,omitempty"` // 2.0
	ImpRetenidos       []ImpRetenidos   `json:"imp_retenidos,omitempty"`
}

TotalesRetenciones es el nodo retenciones:Totales del CFDI de Retenciones.

type TransporteAereo

type TransporteAereo struct {
	PermSCT                  string `json:"perm_sct,omitempty"`
	NumPermisoSCT            string `json:"num_permiso_sct,omitempty"`
	MatriculaAeronave        string `json:"matricula_aeronave,omitempty"`
	NombreAseg               string `json:"nombre_aseg,omitempty"`
	NumPolizaSeguro          string `json:"num_poliza_seguro,omitempty"`
	NumeroGuia               string `json:"numero_guia,omitempty"`
	LugarContrato            string `json:"lugar_contrato,omitempty"`
	RFCTransportista         string `json:"rfc_transportista,omitempty"` // 1.0
	CodigoTransportista      string `json:"codigo_transportista,omitempty"`
	NumRegIdTribTranspor     string `json:"num_reg_id_trib_transpor,omitempty"`   // 1.0
	ResidenciaFiscalTranspor string `json:"residencia_fiscal_transpor,omitempty"` // 1.0
	NombreTransportista      string `json:"nombre_transportista,omitempty"`       // 1.0
	RFCEmbarcador            string `json:"rfc_embarcador,omitempty"`
	NumRegIdTribEmbarc       string `json:"num_reg_id_trib_embarc,omitempty"`
	ResidenciaFiscalEmbarc   string `json:"residencia_fiscal_embarc,omitempty"`
	NombreEmbarcador         string `json:"nombre_embarcador,omitempty"`
}

TransporteAereo es el nodo TransporteAereo del complemento CartaPorte.

type TransporteFerroviario

type TransporteFerroviario struct {
	TipoDeServicio  string           `json:"tipo_de_servicio,omitempty"`
	TipoDeTrafico   string           `json:"tipo_de_trafico,omitempty"`
	NombreAseg      string           `json:"nombre_aseg,omitempty"`
	NumPolizaSeguro string           `json:"num_poliza_seguro,omitempty"`
	Concesionario   string           `json:"concesionario,omitempty"` // 1.0
	DerechosDePaso  []DerechosDePaso `json:"derechos_de_paso,omitempty"`
	Carro           []Carro          `json:"carro,omitempty"`
}

TransporteFerroviario es el nodo TransporteFerroviario del complemento CartaPorte.

type TransporteMaritimo

type TransporteMaritimo struct {
	PermSCT                string           `json:"perm_sct,omitempty"`
	NumPermisoSCT          string           `json:"num_permiso_sct,omitempty"`
	NombreAseg             string           `json:"nombre_aseg,omitempty"`
	NumPolizaSeguro        string           `json:"num_poliza_seguro,omitempty"`
	TipoEmbarcacion        string           `json:"tipo_embarcacion,omitempty"`
	Matricula              string           `json:"matricula,omitempty"`
	NumeroOMI              string           `json:"numero_omi,omitempty"`
	AnioEmbarcacion        string           `json:"anio_embarcacion,omitempty"`
	NombreEmbarc           string           `json:"nombre_embarc,omitempty"`
	NacionalidadEmbarc     string           `json:"nacionalidad_embarc,omitempty"`
	UnidadesDeArqBruto     *decimal.Decimal `json:"unidades_de_arq_bruto,omitempty"`
	TipoCarga              string           `json:"tipo_carga,omitempty"`
	NumCertITC             string           `json:"num_cert_itc,omitempty"` // 2.0
	Eslora                 *decimal.Decimal `json:"eslora,omitempty"`
	Manga                  *decimal.Decimal `json:"manga,omitempty"`
	Calado                 *decimal.Decimal `json:"calado,omitempty"`
	Puntal                 *decimal.Decimal `json:"puntal,omitempty"` // 3.x
	LineaNaviera           string           `json:"linea_naviera,omitempty"`
	NombreAgenteNaviero    string           `json:"nombre_agente_naviero,omitempty"`
	NumAutorizacionNaviero string           `json:"num_autorizacion_naviero,omitempty"`
	NumViaje               string           `json:"num_viaje,omitempty"`
	NumConocEmbarc         string           `json:"num_conoc_embarc,omitempty"`
	PermisoTempNavegacion  string           `json:"permiso_temp_navegacion,omitempty"` // 3.x

	Contenedor   []ContenedorMaritimo `json:"contenedor,omitempty"`
	RemolquesCCP []RemolqueCCP        `json:"remolques_ccp,omitempty"` // 3.0
}

TransporteMaritimo es el nodo TransporteMaritimo del complemento CartaPorte.

type TrasladosLocales

type TrasladosLocales struct {
	ImpLocTrasladado string           `json:"imp_loc_trasladado,omitempty"`
	TasadeTraslado   *decimal.Decimal `json:"tasade_traslado,omitempty"`
	Importe          *decimal.Decimal `json:"importe,omitempty"`
}

TrasladosLocales es el nodo implocal:TrasladosLocales del complemento ImpuestosLocales.

type TuristaPasajeroExtranjero

type TuristaPasajeroExtranjero struct {
	Version         string                                  `json:"version,omitempty"`
	FechadeTransito Fecha                                   `json:"fechade_transito,omitzero"`
	TipoTransito    string                                  `json:"tipo_transito,omitempty"`
	DatosTransito   *TuristaPasajeroExtranjeroDatosTransito `json:"datos_transito,omitempty"`
}

TuristaPasajeroExtranjero 1.0.

type TuristaPasajeroExtranjeroDatosTransito

type TuristaPasajeroExtranjeroDatosTransito struct {
	Via               string `json:"via,omitempty"`
	TipoId            string `json:"tipo_id,omitempty"`
	NumeroId          string `json:"numero_id,omitempty"`
	Nacionalidad      string `json:"nacionalidad,omitempty"`
	EmpresaTransporte string `json:"empresa_transporte,omitempty"`
	IdTransporte      string `json:"id_transporte,omitempty"`
}

TuristaPasajeroExtranjeroDatosTransito es el nodo tpe:datosTransito del complemento TuristaPasajeroExtranjero.

type Ubicacion

type Ubicacion struct {
	TipoUbicacion               string               `json:"tipo_ubicacion,omitempty"`
	IDUbicacion                 string               `json:"id_ubicacion,omitempty"`
	RFCRemitenteDestinatario    string               `json:"rfc_remitente_destinatario,omitempty"`
	NombreRemitenteDestinatario string               `json:"nombre_remitente_destinatario,omitempty"`
	NumRegIdTrib                string               `json:"num_reg_id_trib,omitempty"`
	ResidenciaFiscal            string               `json:"residencia_fiscal,omitempty"`
	NumEstacion                 string               `json:"num_estacion,omitempty"`
	NombreEstacion              string               `json:"nombre_estacion,omitempty"`
	NavegacionTrafico           string               `json:"navegacion_trafico,omitempty"`
	FechaHoraSalidaLlegada      Fecha                `json:"fecha_hora_salida_llegada,omitzero"`
	TipoEstacion                string               `json:"tipo_estacion,omitempty"`
	DistanciaRecorrida          *decimal.Decimal     `json:"distancia_recorrida,omitempty"`
	Origen                      *CartaPorte10Origen  `json:"origen,omitempty"`  // 1.0
	Destino                     *CartaPorte10Destino `json:"destino,omitempty"` // 1.0
	Domicilio                   *Domicilio           `json:"domicilio,omitempty"`
}

Ubicacion es el nodo Ubicacion del complemento CartaPorte.

type UbicacionFiscal

type UbicacionFiscal struct {
	Calle        string `json:"calle,omitempty"`
	NoExterior   string `json:"no_exterior,omitempty"`
	NoInterior   string `json:"no_interior,omitempty"`
	Colonia      string `json:"colonia,omitempty"`
	Localidad    string `json:"localidad,omitempty"`
	Referencia   string `json:"referencia,omitempty"`
	Municipio    string `json:"municipio,omitempty"`
	Estado       string `json:"estado,omitempty"`
	Pais         string `json:"pais,omitempty"`
	CodigoPostal string `json:"codigo_postal,omitempty"`
}

UbicacionFiscal es el t_UbicacionFiscal de terceros (atributos en minúsculas: calle, noExterior…).

type ValesDeDespensa

type ValesDeDespensa struct {
	Version          string                    `json:"version,omitempty"`
	TipoOperacion    string                    `json:"tipo_operacion,omitempty"`
	RegistroPatronal string                    `json:"registro_patronal,omitempty"`
	NumeroDeCuenta   string                    `json:"numero_de_cuenta,omitempty"`
	Total            *decimal.Decimal          `json:"total,omitempty"`
	Conceptos        []ValesDeDespensaConcepto `json:"conceptos,omitempty"`
}

ValesDeDespensa 1.0. Conceptos aplana Conceptos/Concepto.

type ValesDeDespensaConcepto

type ValesDeDespensaConcepto struct {
	Identificador      string           `json:"identificador,omitempty"`
	Fecha              Fecha            `json:"fecha,omitzero"`
	Rfc                string           `json:"rfc,omitempty"`
	Curp               string           `json:"curp,omitempty"`
	Nombre             string           `json:"nombre,omitempty"`
	NumSeguridadSocial string           `json:"num_seguridad_social,omitempty"`
	Importe            *decimal.Decimal `json:"importe,omitempty"`
}

ValesDeDespensaConcepto es el nodo valesdedespensa:Concepto del complemento ValesDeDespensa.

type Validacion

type Validacion struct {
	// Estado es el peor de los dos: invalido si alguno lo es; incompleto
	// si alguno lo es y ninguno es inválido; si no, valido.
	Estado  string               `json:"estado"`
	Esquema *ResultadoValidacion `json:"esquema"`
	// Reglas es nil si el documento no se pudo leer como CFDI (Parse
	// falló).
	Reglas *ResultadoValidacion `json:"reglas,omitempty"`
}

Validacion junta las dos validaciones de un documento: la del esquema (sobre los bytes) y la de las reglas (sobre el Comprobante). Es lo que escriben `cfdi validar` y `cfdi lote -validar`.

func Validar

func Validar(xml []byte, c *Comprobante) *Validacion

Validar corre ValidarEsquema sobre xml y ValidarReglas sobre c. Si c es nil lee xml con ParseBytes; si tampoco se puede leer, Reglas queda nil y el estado es el del esquema (que entonces es inválido). Nunca falla.

Example
package main

import (
	"fmt"

	"github.com/OrtaMarco/cfdi-go"
)

// Una factura 4.0 mínima, sin sello ni timbre, con el RFC de prueba del SAT.
const facturaEjemplo = `<?xml version="1.0" encoding="UTF-8"?>
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4" Version="4.0" Serie="A" Folio="1"
    Fecha="2026-01-15T10:30:00" Sello="" NoCertificado="30001000000500003416" Certificado=""
    SubTotal="100.00" Moneda="MXN" Total="116.00" TipoDeComprobante="I" Exportacion="01"
    MetodoPago="PUE" FormaPago="03" LugarExpedicion="20000">
  <cfdi:Emisor Rfc="EKU9003173C9" Nombre="ESCUELA KEMPER URGATE" RegimenFiscal="601"/>
  <cfdi:Receptor Rfc="XAXX010101000" Nombre="PUBLICO EN GENERAL" DomicilioFiscalReceptor="20000"
      RegimenFiscalReceptor="616" UsoCFDI="S01"/>
  <cfdi:Conceptos>
    <cfdi:Concepto ClaveProdServ="01010101" Cantidad="1" ClaveUnidad="H87" Descripcion="Artículo"
        ValorUnitario="100.00" Importe="100.00" ObjetoImp="02">
      <cfdi:Impuestos>
        <cfdi:Traslados>
          <cfdi:Traslado Base="100.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="16.00"/>
        </cfdi:Traslados>
      </cfdi:Impuestos>
    </cfdi:Concepto>
  </cfdi:Conceptos>
  <cfdi:Impuestos TotalImpuestosTrasladados="16.00">
    <cfdi:Traslados>
      <cfdi:Traslado Base="100.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="16.00"/>
    </cfdi:Traslados>
  </cfdi:Impuestos>
</cfdi:Comprobante>`

func main() {
	v := cfdi.Validar([]byte(facturaEjemplo), nil)
	fmt.Println(v.Estado)
	for _, h := range v.Hallazgos() {
		fmt.Println(h.Nivel, h.Codigo, h.Ruta)
	}
}
Output:
valido

func ValidarRetenciones

func ValidarRetenciones(xml []byte) *Validacion

ValidarRetenciones valida un CFDI de Retenciones 1.0 o 2.0 contra los XSD del SAT (retencionpagov1/v2, sus trece complementos y el TFD 1.1): es ValidarEsquema en una Validacion sin Reglas. No hay reglas aritméticas de Retenciones: el SAT no publica para ellas matriz de errores ni guía con las fórmulas, y la librería no inventa reglas sin fuente. Nunca falla.

func (*Validacion) Hallazgos

func (v *Validacion) Hallazgos() []Hallazgo

Hallazgos devuelve los hallazgos del esquema y luego los de las reglas.

type VehiculoUsado

type VehiculoUsado struct {
	Version             string                           `json:"version,omitempty"`
	MontoAdquisicion    *decimal.Decimal                 `json:"monto_adquisicion,omitempty"`
	MontoEnajenacion    *decimal.Decimal                 `json:"monto_enajenacion,omitempty"`
	ClaveVehicular      string                           `json:"clave_vehicular,omitempty"`
	Marca               string                           `json:"marca,omitempty"`
	Tipo                string                           `json:"tipo,omitempty"`
	Modelo              string                           `json:"modelo,omitempty"`
	NumeroMotor         string                           `json:"numero_motor,omitempty"`
	NumeroSerie         string                           `json:"numero_serie,omitempty"`
	NIV                 string                           `json:"niv,omitempty"`
	Valor               *decimal.Decimal                 `json:"valor,omitempty"`
	InformacionAduanera []InformacionAduaneraComplemento `json:"informacion_aduanera,omitempty"`
}

VehiculoUsado 1.0: enajenación de un vehículo usado por una persona física. InformacionAduanera es la de terceros y ventavehiculos.

type VentaVehiculos

type VentaVehiculos struct {
	Version             string                           `json:"version,omitempty"`
	ClaveVehicular      string                           `json:"clave_vehicular,omitempty"`
	Niv                 string                           `json:"niv,omitempty"`
	InformacionAduanera []InformacionAduaneraComplemento `json:"informacion_aduanera,omitempty"`
	Parte               []ParteComplemento               `json:"parte,omitempty"`
}

VentaVehiculos 1.0 y 1.1 (ventavehiculos). 1.1 agregó Niv.

type Verificacion

type Verificacion struct {
	// CadenaOriginal del comprobante; vacía si no se pudo garantizar.
	CadenaOriginal string `json:"cadena_original,omitempty"`
	// Sello del emisor sobre CadenaOriginal, con el Certificado del XML.
	Sello ResultadoSello `json:"sello"`
	// CadenaOriginalTFD del timbre, si lo hay y se pudo armar.
	CadenaOriginalTFD string `json:"cadena_original_tfd,omitempty"`
	// TFD es el SelloSAT del timbre; nil si no se pidió (certificados nil).
	TFD *ResultadoSello `json:"tfd,omitempty"`
	// Certificado son los chequeos del certificado del emisor (vigencia,
	// RFC y número), aparte del sello: no cambian Sello.Estado.
	Certificado *ChequeosCertificado `json:"certificado,omitempty"`
}

Verificacion es el resultado de Verificar.

func Verificar

func Verificar(xml []byte, certs CertificadosSAT) *Verificacion

Verificar arma la cadena original y verifica el sello del emisor con el Certificado que trae el XML: RSA PKCS#1 v1.5 con SHA-256 (CFDI 3.3 y 4.0; en 3.2 también SHA-1), y que NoCertificado sea el número de ese certificado. No revisa la cadena de confianza del certificado ni si estaba revocado (eso necesita red).

Aparte, sin cambiar el veredicto del sello, Verificacion.Certificado dice si la Fecha cae en la vigencia del certificado, si su RFC es el del Emisor y si su número es el NoCertificado (ver ChequeosCertificado).

Si certs no es nil, verifica también el timbre: SelloSAT sobre la cadena del TFD con el certificado que dé certs para NoCertificadoSAT (SHA-256 en TFD 1.1; en 1.0 también SHA-1), y que SelloCFD sea el Sello del comprobante.

Nunca falla: lo que no se puede decidir sale como SelloNoVerificable con su motivo.

Example

Verificar arma la cadena original y comprueba el sello del emisor con el certificado que trae el XML, sin red. Este CFDI está sellado con el CSD de pruebas del SAT.

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/OrtaMarco/cfdi-go"
)

func main() {
	xml, err := os.ReadFile("testdata/sinteticos/sellados/ingreso40-pue.xml")
	if err != nil {
		log.Fatal(err)
	}
	v := cfdi.Verificar(xml, nil)
	fmt.Println(v.Sello.Estado, v.Sello.Algoritmo, v.Sello.NoCertificado)
}
Output:
valido SHA256withRSA 30001000000500003416

func VerificarRetenciones

func VerificarRetenciones(xml []byte, certs CertificadosSAT) *Verificacion

VerificarRetenciones es Verificar para un CFDI de Retenciones 1.0 o 2.0: el sello del emisor sobre CadenaOriginalRetenciones con el certificado del XML (SHA-256; en 1.0 también SHA-1) y que su NoCertificado (NumCert en 1.0) sea el número del certificado; el timbre, si certs no es nil, como en Verificar; y los chequeos del certificado, con FechaExp. Nunca falla.

Directories

Path Synopsis
cmd
cfdi command
Comando cfdi lee CFDI 3.3/4.0 y de Retenciones 1.0/2.0 y los escribe como JSON.
Comando cfdi lee CFDI 3.3/4.0 y de Retenciones 1.0/2.0 y los escribe como JSON.
Package decimal es un número decimal exacto para los montos de un CFDI.
Package decimal es un número decimal exacto para los montos de un CFDI.
internal
gencadena
Package gencadena traduce las hojas XSLT del SAT que arman la cadena original (CFDI 3.2, 3.3 y 4.0, TFD 1.0 y 1.1, y los complementos que lee la librería) a reglas Go: el archivo cadena_reglas.go del paquete cfdi.
Package gencadena traduce las hojas XSLT del SAT que arman la cadena original (CFDI 3.2, 3.3 y 4.0, TFD 1.0 y 1.1, y los complementos que lee la librería) a reglas Go: el archivo cadena_reglas.go del paquete cfdi.
gencadena/generar command
Comando generar escribe cadena_reglas.go a partir de los XSLT del SAT.
Comando generar escribe cadena_reglas.go a partir de los XSLT del SAT.
genxsd
Package genxsd traduce los esquemas XSD del SAT (CFDI 4.0 y 3.3, TFD 1.1 y 1.0 y los complementos de Raices y Variantes, con los catálogos y tipos de datos que importan) a tablas Go: los archivos esquema_reglas.go y esquema_catalogos.go del paquete cfdi, que usa ValidarEsquema.
Package genxsd traduce los esquemas XSD del SAT (CFDI 4.0 y 3.3, TFD 1.1 y 1.0 y los complementos de Raices y Variantes, con los catálogos y tipos de datos que importan) a tablas Go: los archivos esquema_reglas.go y esquema_catalogos.go del paquete cfdi, que usa ValidarEsquema.
genxsd/generar command
Comando generar escribe esquema_reglas.go y esquema_catalogos.go a partir de los XSD del SAT.
Comando generar escribe esquema_reglas.go y esquema_catalogos.go a partir de los XSD del SAT.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL