Factura CFDI 4.0 con n8n¶
Esta receta conecta una hoja de cálculo de ventas con Cefidia usando n8n. Cada fila nueva se convierte en un CFDI: si la venta trae el correo del cliente, él captura sus datos fiscales desde una liga de autofacturación; si no, se timbra a público en general. El resultado (estado, UUID, liga o error) se escribe de vuelta en la misma fila.
Note
Es una receta de ejemplo, no una integración oficial. Ajusta los datos fiscales, el IVA y los textos a tu operación y revisa los CFDI de prueba antes de activarla.
Qué necesitas¶
- Tu cuenta de Cefidia con la razón social registrada, el CSD cargado y crédito disponible. Ver Crear tu cuenta.
- Una instancia de n8n (la nube de n8n o la tuya) con un nodo MCP Client.
- Una cuenta de Google con acceso a la hoja.
- Una cuenta de correo con SMTP para enviar la liga a tus clientes. Cefidia no envía correos por ti.
Experimenta con una razón social de prueba¶
Antes de facturar con tu razón social real, puedes ensayar todo el flujo con una razón social de pruebas: son RFC de prueba del SAT que Cefidia ya tiene registrados.
- Entra a la aplicación web y ve a Registrar una razón social.
- Elige Usar un RFC de pruebas del SAT y selecciona uno de la lista (personas morales o físicas). Se agrega a tu cuenta con su CSD ya cargado y la insignia «Pruebas».
- Pídele a tu agente «lista mis razones sociales» (
list_issuers) y copia elidde la de pruebas. Úsalo comoissueren los nodos MCP y usa su RFC, nombre, régimen y código postal en el nodo Preparar CFDI.
Las facturas de una razón social de pruebas se timbran en el ambiente de pruebas del SAT: no tienen valor fiscal ni consumen crédito, así que puedes repetir el flujo las veces que necesites. Estos CFDI aparecen con la insignia «Prueba» en tu lista. Cuando todo funcione, cambia el issuer y los datos del emisor por los de tu razón social real.
La hoja¶
Crea una hoja con estas columnas en la fila 1:
| Columna | La llenas tú | Contenido |
|---|---|---|
Folio |
Sí | Número de la fila (por ejemplo, 2 para la primera venta). |
Fecha |
Sí | Fecha de la venta. |
Concepto |
Sí | Descripción que irá en el CFDI. |
Monto |
Sí | Total cobrado, con IVA incluido. |
Correo |
Opcional | Correo del cliente. Vacío = público en general. |
Estado |
No | Lo escribe el flujo. |
UUID |
No | Lo escribe el flujo al timbrar a público en general. |
Enlace autofactura |
No | Lo escribe el flujo. |
Detalle |
No | Mensaje de apoyo o error. |
Cómo funciona¶
- Nueva fila. n8n revisa la hoja cada minuto y detecta las filas agregadas.
- Preparar CFDI. Un nodo de código separa el
Montoen subtotal e IVA (16 %) y arma el CFDI 4.0 completo. - Crear borrador. Se llama a la herramienta
create_invoice_draftdel servidor MCP. - ¿Tiene correo?
- Sí: se llama a
request_receiver_data, se envía la liga por correo y la fila queda como «Autofactura enviada». El timbrado ocurre cuando el cliente captura sus datos. - No: se llama a
sign_cfdiy la fila queda como «Timbrada (Público en General)» con su UUID.
- Sí: se llama a
- Errores. Si falla cualquier llamada a Cefidia, la fila queda con
Estado = Errory el detalle enDetalle.
Paso a paso¶
1. Conecta n8n con Cefidia¶
Crea una credencial MCP OAuth2 en n8n:
- URL del servidor:
https://cefidia.com/mcp - Cefidia usa OAuth con registro dinámico de cliente: no hay client ID ni secreto que capturar. Al autorizar, inicia sesión con tu cuenta de Cefidia.
- Los alcances necesarios son
issuers:readeinvoices:write(másinvoices:readsi quieres consultar facturas desde n8n). Ver Permisos.
Si tu n8n no completa el registro automático, revisa Si construyes tu propio cliente.
2. Obtén el identificador de tu razón social¶
Las herramientas piden el argumento issuer. Pídele a tu agente de IA «lista mis razones sociales» (herramienta list_issuers) y copia el id. Úsalo en los tres nodos MCP.
3. Crea el flujo¶
Arma estos nodos en n8n y conéctalos como se indica:
| Nodo | Tipo | Función |
|---|---|---|
| Nueva fila en hoja de facturas | Google Sheets Trigger (rowAdded, cada minuto) |
Dispara el flujo. |
| Preparar CFDI | Code (una vez por elemento) | Calcula IVA y arma el CFDI. |
| Crear borrador en Cefidia | MCP Client, create_invoice_draft |
Crea el borrador. |
| ¿Tiene correo? | If | Elige entre autofactura y público en general. |
| Generar enlace de autofacturación | MCP Client, request_receiver_data |
Genera la liga. |
| Enviar enlace por correo | Send Email | Entrega la liga al cliente. |
| Marcar autofactura enviada | Google Sheets, update | Registra estado y liga. |
| Timbrar factura Público en General | MCP Client, sign_cfdi |
Timbra sin receptor propio. |
| Marcar factura timbrada | Google Sheets, update | Registra el UUID. |
| Guardar error … | Google Sheets, update | Una rama de error por cada llamada a Cefidia. |
En los tres nodos MCP Client activa On Error → Continue (using error output) para poder mandar los fallos a las ramas de error.
Preparar CFDI¶
El nodo de código convierte la fila en el CFDI. Reemplaza los valores marcados con los de tu razón social:
const row = $json;
const correo = String(row.Correo || '').trim();
const conCorreo = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(correo);
const montoTexto = String(row.Monto ?? '').replace(/[^0-9.\-]/g, '');
const total = Math.round(parseFloat(montoTexto) * 100) / 100;
if (!isFinite(total) || total <= 0) {
throw new Error('Monto inválido en la fila ' + row.row_number + ': ' + row.Monto);
}
// El monto de la hoja incluye IVA 16%
const subtotal = Math.round((total / 1.16) * 100) / 100;
const iva = Math.round((total - subtotal) * 100) / 100;
const f = (n) => n.toFixed(2);
const ahora = $now.setZone('America/Mexico_City');
const traslado = { Base: f(subtotal), Impuesto: '002', TipoFactor: 'Tasa', TasaOCuota: '0.160000', Importe: f(iva) };
const cfdi = {
Version: '4.0',
Fecha: ahora.toFormat("yyyy-MM-dd'T'HH:mm:ss"),
FormaPago: '03',
MetodoPago: 'PUE',
Moneda: 'MXN',
TipoDeComprobante: 'I',
Exportacion: '01',
LugarExpedicion: '00000', // tu código postal de expedición
SubTotal: f(subtotal),
Total: f(total),
Emisor: { Rfc: 'TU_RFC', Nombre: 'TU RAZÓN SOCIAL', RegimenFiscal: '601' },
Receptor: {
Rfc: 'XAXX010101000',
Nombre: conCorreo ? 'POR CAPTURAR' : 'PUBLICO EN GENERAL',
DomicilioFiscalReceptor: '00000', // mismo código postal de expedición
RegimenFiscalReceptor: '616',
UsoCFDI: 'S01',
},
Conceptos: [{
ClaveProdServ: '80101500', // clave del SAT de tu producto o servicio
Cantidad: '1',
ClaveUnidad: 'E48',
Descripcion: String(row.Concepto || 'Servicios de consultoría'),
ValorUnitario: f(subtotal),
Importe: f(subtotal),
ObjetoImp: '02',
Impuestos: { Traslados: [traslado] },
}],
Impuestos: { TotalImpuestosTrasladados: f(iva), Traslados: [traslado] },
};
if (!conCorreo) {
// Sin correo se factura a público en general: requiere información global
cfdi.InformacionGlobal = { Periodicidad: '01', Meses: ahora.toFormat('MM'), 'Año': ahora.toFormat('yyyy') };
}
return { json: { row_number: row.row_number, correo, conCorreo, concepto: row.Concepto, total: f(total), cfdi } };
Qué ajustar:
Emisor: tu RFC, razón social y régimen fiscal, tal como están en tu CSD.LugarExpedicion: el código postal de tu domicilio fiscal.ClaveProdServ,ClaveUnidad,FormaPago,MetodoPago: los de tu operación.- Si tus ventas no llevan IVA al 16 %, cambia el cálculo de
subtotal,ivaytraslado.
Crear el borrador¶
En el nodo create_invoice_draft, modo JSON:
={{ JSON.stringify({ issuer: 'ID_DE_TU_RAZON_SOCIAL', cfdi: $json.cfdi }) }}
Generar la liga (rama con correo)¶
En el nodo request_receiver_data:
={{ JSON.stringify({
issuer: 'ID_DE_TU_RAZON_SOCIAL',
invoice_id: $json.structuredContent.invoice.id,
expected_version: $json.structuredContent.invoice.version
}) }}
La respuesta trae structuredContent.url y structuredContent.expires_at. La liga se devuelve una sola vez: por eso el flujo la envía y la guarda en la hoja en el mismo recorrido.
Enviar la liga¶
En Send Email, usa tu SMTP, pon como destinatario el correo de la fila y en el cuerpo la liga {{ $json.structuredContent.url }} junto con su vencimiento (expires_at). Redacta el mensaje con tu propia voz y tu propio remitente.
Timbrar a público en general (rama sin correo)¶
En el nodo sign_cfdi:
={{ JSON.stringify({
issuer: 'ID_DE_TU_RAZON_SOCIAL',
invoice_id: $json.structuredContent.invoice.id,
expected_version: $json.structuredContent.invoice.version
}) }}
Actualizar la hoja¶
Los nodos de Google Sheets usan la operación Update y se emparejan por row_number. En la rama de éxito con correo, el flujo escribe Estado = Autofactura enviada, la liga en Enlace autofactura y el vencimiento en Detalle. En la rama de público en general escribe Estado = Timbrada (Público en General) y el UUID. En las ramas de error escribe Estado = Error y los primeros 500 caracteres de la respuesta en Detalle.
4. Pruébalo¶
- Usa una razón social de pruebas. Con el flujo inactivo, agrega una fila de prueba con tu propio correo y ejecuta el flujo manualmente.
- Revisa el borrador y el correo recibido. Abre la liga, captura datos de prueba y confirma que se timbra.
- Agrega otra fila sin correo para probar la rama de público en general.
- Cuando todo coincida con lo esperado, activa el flujo.
Lo que debes saber¶
- Cada timbrado con tu razón social real consume crédito (con una razón social de pruebas no) ($2 MXN por CFDI timbrado, IVA incluido). Revisa tu saldo en Créditos y recargas. Si el timbrado falla, el crédito se devuelve.
- Las ligas de autofactura duran 30 días para capturar datos y 30 días tras el timbrado para descargar. Mientras la liga esté abierta, quien la tenga puede capturar datos y disparar el timbrado a tu cargo; envíala solo a tu cliente. Detalle en Autofacturación.
- El borrador queda bloqueado mientras la liga está abierta: no se puede modificar ni eliminar. Para cambiarlo, revoca la liga con
revoke_receiver_request. - Una liga abierta por factura. Si el flujo se vuelve a ejecutar sobre la misma venta, la herramienta responde
already_requested; ese caso cae en la rama de error. - Evita duplicados. El disparador procesa cada fila nueva una vez. Si editas una fila ya procesada, el flujo no vuelve a correr; si borras y vuelves a pegar la fila, se genera otro CFDI.
- Público en general se usa cuando no hay correo para pedir los datos. Confirma con tu contador que esa forma de facturar aplica a tus ventas.
- No pongas credenciales en la hoja ni en los nodos de código. Usa las credenciales de n8n.
Problemas frecuentes¶
| Síntoma | Qué revisar |
|---|---|
Estado = Error y insufficient_scope |
La credencial MCP no tiene invoices:write. Vuelve a autorizar. |
needs_csd |
La razón social aún no tiene CSD cargado. Súbelo en la aplicación web. |
| Error de crédito insuficiente | Recarga tu crédito. Ver Créditos y recargas. |
Monto inválido |
La columna Monto está vacía o no es un número. |
already_requested |
Esa factura ya tiene una liga abierta; revócala si necesitas otra. |
| Errores de validación del CFDI | Revisa Emisor, el código postal y las claves del SAT en el nodo Preparar CFDI. |
| El flujo no detecta filas | Verifica que esté activo y que el disparador apunte a la hoja correcta. |
Referencia¶
create_invoice_draft,request_receiver_data,revoke_receiver_requestysign_cfdi.- Conectar otro agente: cómo se autentica un cliente MCP.