Saltar al contenido
integraciones / · 5 min

Timbrar un CFDI 4.0 desde Laravel: lo que no dice la documentacion del PAC

El PAC solo sella. Todo lo demas —catalogos del SAT, validar al receptor, la factura global, la cancelacion que tarda dias— es tuyo. Ocho cosas que hubiera querido saber antes.

Administrador · Desarrollador Laravel
Timbrar un CFDI 4.0 desde Laravel: lo que no dice la documentacion del PAC

Timbrar una factura no es generar un PDF bonito. Es armar un XML que cumple un estándar del SAT, mandárselo a un tercero autorizado para que lo selle, y guardar lo que te devuelve como si fuera dinero — porque legalmente lo es.

Llevo un POS con facturación CFDI 4.0 en producción. Esto es lo que me hubiera gustado que alguien me explicara antes de empezar.

El PAC hace una sola cosa

Un PAC (Proveedor Autorizado de Certificación) recibe tu comprobante y le pone el sello del SAT. No valida tu lógica de negocio, no calcula impuestos por ti, y no te avisa si tu catálogo está desactualizado. Timbra o rechaza.

El servicio termina siendo pequeño: mandar, cancelar, consultar.

public function timbrar(array $payload): array
{
    $response = $this->request('timbrar', $payload);

    if (! $response->successful()) {
        return ['ok' => false, 'mensaje' => $this->resolverMensajeError($response)];
    }

    $data = $response->json();
    $uuid = data_get($data, 'data.uuid') ?? data_get($data, 'uuid');

    // Un 200 sin UUID es un fracaso disfrazado de exito.
    if (! is_string($uuid) || trim($uuid) === '') {
        return ['ok' => false, 'mensaje' => 'El PAC respondio sin UUID.'];
    }

    return ['ok' => true, 'uuid' => $uuid, 'xml' => data_get($data, 'data.xml')];
}

Esa comprobación del UUID vacío no es paranoia. Los PAC devuelven 200 con cuerpos de error dentro, y si no revisas, guardas una factura "timbrada" que no existe en el SAT. Te enteras a fin de mes.

Dos entornos, y el switch en configuración

'sw' => [
    'modo' => env('SW_MODO', env('APP_ENV') === 'production' ? 'produccion' : 'desarrollo'),
    'urls' => [
        'desarrollo' => env('SW_URL_DESARROLLO'),
        'produccion' => env('SW_URL_PRODUCTIVO'),
    ],
    'token' => [
        'desarrollo' => env('SW_TOKEN_DESARROLLO'),
        'produccion' => env('SW_TOKEN_PRODUCTIVO'),
    ],
],

Que el modo se derive de APP_ENV pero se pueda forzar es deliberado: en local nunca timbras de verdad por accidente, y en producción puedes bajar a pruebas un rato sin editar código.

Los catálogos del SAT son tablas, no enums

Uso de CFDI, régimen fiscal, forma de pago, clave de unidad, clave de producto o servicio. Son catálogos oficiales, cambian, y son largos: el de productos tiene decenas de miles de filas.

La tentación es meterlos en un enum de PHP. No lo hagas. Van en tablas, se actualizan con un comando, y el receptor elige de ahí. Un catálogo desactualizado es un rechazo del PAC con un mensaje que no dice cuál de los treinta campos está mal.

Validar al receptor antes de timbrar

El error más común no es tuyo, es del cliente: RFC mal escrito, código postal que no corresponde, régimen fiscal que no le toca. En CFDI 4.0 esos tres datos tienen que coincidir con lo que el SAT tiene registrado.

Poner esa validación antes de mandar al PAC cambia por completo la experiencia: en vez de "el timbrado falló, error 40.147", el usuario ve "el código postal del receptor no coincide con su RFC".

La factura global

Es la que agrupa todos los tickets del periodo que nadie pidió facturar. Suena simple y tiene una trampa: hay que saber qué ventas ya se facturaron individualmente, o las declaras dos veces.

Eso pide una consulta con criterio propio —ventas del periodo, de esta sucursal, sin CFDI vigente asociado, no canceladas— y una ventana de tiempo clara. La ventana importa: si dejas facturar un ticket de hace tres meses después de haberlo incluido en una global, ya tienes un problema fiscal.

Autofacturación: el portal que te ahorra el teléfono

Una URL pública donde el cliente teclea el folio de su ticket y sus datos fiscales, y se factura solo. Es la pieza que más agradece el mostrador, porque le quita de encima la llamada de "oiga, ¿me puede facturar la compra del martes?".

Dos cuidados: el folio del ticket es adivinable, así que pide un segundo dato que solo tenga quien estuvo ahí —el total, por ejemplo— y respeta la misma ventana de tiempo que el resto del sistema.

Guarda el XML, no solo el PDF

El PDF es una representación. El XML timbrado es el comprobante. Guárdalo tal cual lo devolvió el PAC, sin reformatear, en almacenamiento que no borres por error. El PDF se puede regenerar; el XML, no.

Yo los mando a S3 desde el primer día, y el envío por correo va en cola: timbrar y notificar son cosas distintas y no deben fallar juntas.

Cancelar es más difícil que timbrar

Desde 2022 la cancelación pide motivo y, en varios casos, la aceptación del receptor. No es un DELETE: es una solicitud que puede quedar pendiente días. Modela eso desde el principio —un estado "cancelación en proceso"— o vas a acabar con facturas que en tu sistema están canceladas y en el SAT no.

#laravel #api #facturacion #sat
Comentarios 0

Nadie ha comentado todavía. Estrena la sección.

Deja tu comentario
Tu correo no se publica. Reviso los comentarios antes de publicarlos.

Seguir leyendo