Creación de CFDI 4.0
Para crear un CFDI versión 4.0 se ofrece el objeto CfdiUtils\CfdiCreator40
.
Este objeto trabaja directamente con la estructura CfdiUtils\Elements\Cfdi40\Comprobante
para facilitar la manipulación de la estructura y los datos, y contiene métodos que ayudan
a establecer el certificado, generar el sello, generar o almacenar el XML, y validar la estructura recién creada.
Esta clase es una especie de pegamento de todas las pequeñas utilerías y estructuras de datos.
Métodos de ayuda
-
comprobante(): Comprobante
: Obtiene el nodo raízComprobante
. Todos los métodos utilizan este objeto. -
putCertificado(Certificado $certificado, bool $putEmisorRfcNombre = true)
: Establece el valor de los atributosNoCertificado
yCertificado
, y si$putEmisorRfcNombre
es verdadero entonces también establece el valor deRfc
yNombre
en el nodoEmisor
. -
asXml(): string
: Genera y devuelve el contenido XML de la exportación del nodoComprobante
. -
saveXml(string $filename): bool
: Genera y almacena el contenido XML. -
buildCadenaDeOrigen(): string
: Construye la cadena de origen siempre que exista un resolvedor de recursos XML. -
buildSumasConceptos(int $precision = 2): SumasConceptos
: Genera un objeto de tipoSumasConceptos
según los datos de losConceptos
. -
addSumasConceptos(SumasConceptos $sumasConceptos = null, int $precision = 2)
: Establece los valores de$sumasConceptos
en el comprobante, si no se pasó el objeto entonces lo fabrica conbuildSumasConceptos()
. Las sumas en cuestión son los valores del comprobanteSubTotal
,Total
yDescuento
, nodo de impuestos del comprobante, y también los totalesTotaldeRetenciones
yTotaldeTraslados
del complemento de impuestos locales. -
addSello(string $key, string $passPhrase = '')
: Realiza el procedimiento de firma con la llave primaria y almacena el valor de dicha llave en base64 en el atributoSello
. Si el certificado existe como un objetoCertificado
entonces este método también verifica que la llave primaria pertenece al certificado y genera una excepción si no es así. -
validate(): Asserts
: Crea un validador que verifica la estructura XML contra su archivo XSD y realiza validaciones adicionales. Consulta la documentación de validaciones para más información. -
moveSatDefinitionsToComprobante(): void
: Mueve las declaraciones de espacios de nombresxmlns:*
y las declaraciones de ubicación de esquemasxsi:schemaLocation
al nodo raíz.
Pasos básicos de creación de un CFDI
No hay una sola forma de hacer las cosas, pero la receta de creación sería algo como:
<?php
$certificado = new \CfdiUtils\Certificado\Certificado('... ubicación archivo CER');
$comprobanteAtributos = [
'Serie' => 'XXX',
'Folio' => '0000123456',
// y otros atributos más...
];
$creator = new \CfdiUtils\CfdiCreator40($comprobanteAtributos, $certificado);
$comprobante = $creator->comprobante();
// No agrego (aunque puedo) el Rfc y Nombre porque uso los que están establecidos en el certificado
$comprobante->addEmisor([
'Nombre' => 'ESCUELA KEMPER URGATE',
'RegimenFiscal' => '601', // General de Ley Personas Morales
]);
$comprobante->addReceptor([/* Atributos del receptor */]);
$comprobante->addConcepto([
/* Atributos del concepto */
])->addTraslado([
/* Atributos del impuesto trasladado */
]);
// método de ayuda para establecer las sumas del comprobante e impuestos
// con base en la suma de los conceptos y la agrupación de sus impuestos
$creator->addSumasConceptos(null, 2);
// método de ayuda para generar el sello (obtener la cadena de origen y firmar con la llave privada)
$creator->addSello('file:// ... ruta para mi archivo key convertido a PEM ...', 'contraseña de la llave');
// método de ayuda para mover las declaraciones de espacios de nombre al nodo raíz
$creator->moveSatDefinitionsToComprobante();
// método de ayuda para validar usando las validaciones estándar de creación de la librería
$asserts = $creator->validate();
if ($asserts->hasErrors()) { // contiene si hay o no errores
print_r(['errors' => $asserts->errors()]);
return;
}
// método de ayuda para generar el xml y guardar los contenidos en un archivo
$creator->saveXml('... lugar para almacenar el cfdi ...');
// método de ayuda para generar el xml y retornarlo como un string
$creator->asXml();
En el ejemplo anterior en la línea que dice $comprobante = $creator->comprobante();
se está obteniendo el elemento CfdiUtils\Elements\Cfdi40\Comprobante
.
Todos los elementos son una especialización de los nodos.
A diferencia de los nodos, los elementos contienen métodos de ayuda que permiten entender los hijos que manejan,
por ejemplo CfdiUtils\Elements\Cfdi40\Comprobante
contiene un método llamado addReceptor()
con el que se puede insertar en el lugar correcto el nodo "Receptor" incluyendo un arreglo de atributos.
Acerca de las definiciones de espacios de nombre
A partir de la versión 2.12.0
se agregó el método moveSatDefinitionsToComprobante()
que ayuda a mover las
definiciones de espacios de nombres al nodo principal cfdi:Comprobante
.
Si no se llama a este método, las definiciones de espacios de nombres quedarán en el nodo que las utiliza, por ejemplo:
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4"
xsi:schemaLocation="http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd">
<!-- ... nodos del comprobante ... -->
<cfdi:Complemento>
<!-- ... otros complementos ... -->
<leyendasFisc:LeyendasFiscales version="1.0" xmlns:leyendasFisc="http://www.sat.gob.mx/leyendasFiscales"
xsi:schemaLocation="http://www.sat.gob.mx/leyendasFiscales http://www.sat.gob.mx/sitio_internet/cfd/leyendasFiscales/leyendasFisc.xsd">
<leyendasFisc:Leyenda disposicionFiscal="RESDERAUTH" norma = "Artículo 2. Fracción IV." textoLeyenda = "El software desarrollado se entrega con licencia MIT" />
</leyendasFisc:LeyendasFiscales>
</cfdi:Complemento>
</cfdi:Comprobante>
Y si aplica este método las definiciones cambiarán de lugar, quedando como:
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4" xmlns:leyendasFisc="http://www.sat.gob.mx/leyendasFiscales"
xsi:schemaLocation="http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd http://www.sat.gob.mx/leyendasFiscales http://www.sat.gob.mx/sitio_internet/cfd/leyendasFiscales/leyendasFisc.xsd">
<!-- ... nodos del comprobante ... -->
<cfdi:Complemento>
<!-- ... otros complementos ... -->
<leyendasFisc:LeyendasFiscales version="1.0">
<leyendasFisc:Leyenda disposicionFiscal="RESDERAUTH" norma = "Artículo 2. Fracción IV." textoLeyenda = "El software desarrollado se entrega con licencia MIT" />
</leyendasFisc:LeyendasFiscales>
</cfdi:Complemento>
</cfdi:Comprobante>
En realidad, esto no es una regla importarte e incluso se podría decir que sale de la práctica común de XML. Sin embargo, en la documentación técnica del SAT lo documenta como obligatorio.
Si no creaste tus CFDI con esta estructura malamente obligada por el SAT, no te preocupes, en caso de ser necesario podrías hasta modificar tus CFDI anteriores (aun cuando tengan sello) porque la ubicación de las definiciones de los espacios de nombres no participan en la formación de la cadena de origen.
Formación del texto de los códigos QR
La formación del texto que se incluye en los códigos QR tiene reglas específicas
y puede utilizarse el objeto \CfdiUtils\ConsultaCfdiSat\RequestParameters
para obtener el texto contenido en el código QR.
Este es un ejemplo para obtener la URL directamente de un contenido XML.
$xmlContents = '<cfdi:Comprobante Version="4.0">...</cfdi:Comprobante>';
$cfdi = \CfdiUtils\Cfdi::newFromString($xmlContents);
$parameters = \CfdiUtils\ConsultaCfdiSat\RequestParameters::createFromCfdi($cfdi);
echo $parameters->expression(); // https://verificacfdi.facturaelectronica.sat.gob.mx/...
Orden de los nodos de un CFDI
A pesar de tratarse de una estructura XML el SAT por las reglas impuestas en los archivos XSD ha puesto reglas de orden de aparición de nodos.
Por lo anterior esta estructura presentará error porque el nodo Receptor
debe ir después del nodo Emisor
:
<cfdi:Comprobante>
<cfdi:Receptor/>
<cfdi:Emisor/>
</cfdi:Comprobante>
Cuando se está usando el espacio de nombres CfdiUtils\Elements
las estructuras conocen el
orden en el que deben existir los nodos, por lo que no es necesario preocuparse por el orden de aparición.
Esta mejora fue introducida en la versión 2.4.0.
Si se está utilizando CfdiUtils\Nodes
de forma independiente a CfdiUtils\Elements
entonces será necesario
establecer el orden de los nodos con el método CfdiUtils\Nodes\Nodes::setOrder(array $order)
.
O simplemente insertar los nodos en el orden correcto.
Resolvedor de recursos XML
Los archivos XSD necesarios para validar la estructura XML de un CFDI y los archivos XSLT necesarios para generar la cadena de origen son almacenados localmente y reutilizados cada vez que se require.
Para establecer dicha configuración diferente a la predeterminada establezca el objeto XmlResolver
usando el método CfdiCreator40::setXmlResolver(XmlResolver $xmlResolver = null)
.
Si establece el valor a nulo (CfdiCreator40::hasXmlResolver()
es false
) entonces no se podrá
crear la cadena de origen (necesario para obtener la ruta de los archivos XSLT) y tampoco se podrá abastecer
a los objetos de validación que requieran de un resolvedor con el objeto apropiado resultando en varias revisiones
sin ejecutar.
Si lo que desea es no almacenar localmente los recursos entonces lo que debe hacer es establecer
una cadena de caracteres vacía mediante el método XmlResolver::setLocalPath
.
Migrar de CFDI 3.3 a CFDI 4.0
Por lo relacionado con esta librería, solo necesitarás cambiar lo que diga CfdiUtils\Elements\Cfdi33
por CfdiUtils\Elements\Cfdi40
. Así como usar el objeto CfdiUtils\CfdiCreator40
.
La versión 2.19.0 o mayor fue dotada con todos los elementos necesarios para hacer una migración de CFDI 3.3 a CFDI 4.0 lo menos dolorosa posible.
Ten en cuenta que tendrás que modificar la información que pasas a los elementos porque se han agregado varios de ellos, sin embargo, la compatibilidad ofrecida permite que te concentres en solo un problema: Aplicar los cambios del SAT a CFDI 4.0.
Uso de phpcfdi/credentials
Esta librería implementa objetos para trabajar con Certificados y Llaves primarias.
Sin embargo, se recomienda usar phpcfdi/credentials
dado que es una librería especializada para trabajar con archivos FIEL y CSD.
El siguiente ejemplo muestra cómo se puede usar phpcfdi/credentials
para abrir
el certificado y llave privada tal como son entregadas por el SAT.
<?php
use CfdiUtils\CfdiCreator40;
use PhpCfdi\Credentials\Credential;
/**
* @var string $cerFile Ruta del archivo local del certificado
* @var string $keyFile Ruta del archivo local de la llave privada
* @var string $passPhrase Contraseña de la llave privada
*/
$csd = Credential::openFiles($cerfile, $keyfile, $passPhrase);
// helper de creación del cfdi
$creator = new CfdiCreator40();
// poner el certificado y número del certificado
$creator->putCertificado(
new Certificado($csd->certificate()->pem()),
false // no establecer el RFC ni el nombre del emisor
);
// se construye el pre-cfdi ...
// sellado del cfdi
$creator->addSello($csd->privateKey()->pem(), $csd->privateKey()->passPhrase());
El CSD también se puede crear usando el contenido del certificado y llave privada.
<?php
use PhpCfdi\Credentials\Credential;
/**
* @var string $cerFile Ruta del archivo local del certificado
* @var string $keyFile Ruta del archivo local de la llave privada
* @var string $passPhrase Contraseña de la llave privada
*/
$csd = Credential::openFiles($cerfile, $keyfile, $passPhrase);