Carta Porte 3.1: la guía de un dev para timbrar el complemento de traslado del SAT (México, 2026) — Cesar Ayala
← Todos los artículos

Carta Porte 3.1: la guía de un dev para timbrar el complemento de traslado del SAT (México, 2026)

La Carta Porte 3.1 es un complemento del CFDI del SAT obligatorio para mover mercancía en México. Emites un CFDI de Traslado (tipo T) si trasladas mercancía propia, o un CFDI de Ingreso con el complemento si un transportista cobra el flete. No armas el XML a mano: mandas los datos (Ubicaciones, Mercancías, Autotransporte, Figura del Transporte) a una API de PAC, ésta timbra y guardas el XML sellado.

¿Por qué mover una tarima en México necesita un CFDI?

Te lo pongo directo: en México no puedes mover mercancía de un punto a otro sin un papel del SAT que la acompañe. Ese papel es la Carta Porte 3.1, un complemento del CFDI obligatorio cada vez que transportas mercancía dentro del país — ya sea por medios propios o contratando a un transportista. No es un PDF que imprimes “por si las dudas”: es un CFDI timbrado por un PAC, exactamente igual que una factura normal.

Si ya leíste cómo armamos la facturación CFDI automática desde Stripe o Mercado Pago, esto es el hermano de logística: mismo modelo de PAC y timbrado, distinto complemento. Si vienes del mundo de cobros, el contexto general está en pasarelas de pago en México.

La versión 3.1 es la única vigente. La 3.0 y la 2.0 ya están retiradas (la 3.1 es obligatoria desde mediados de 2024), así que cualquier PAC va a rechazar algo más viejo. Y las apuestas son reales: el incumplimiento puede escalar hasta multas reportadas de ~$97,330 MXN por documento en los casos graves (cifra de 2026 reportada como tope; el rango base por omisión o error en Carta Porte bajo el art. 84-IV del CFF suele ser menor — confirma la vigente con tu contador). Además, una Carta Porte inválida o faltante deja la mercancía en tránsito expuesta a aseguramiento.

Otro detalle que truena integraciones viejas: el SAT actualizó los catálogos de Carta Porte para 2026 (la carga mayor para autotransporte —miles de nuevas relaciones de pedimentos del ejercicio— se publicó en enero de 2026; confirma la fecha y versión vigente con tu PAC). Si traes claves de catálogo viejas, el timbrado se rechaza, y las operaciones del ejercicio actual (por ejemplo, pedimentos del ejercicio) requieren claves 2026.

Y un encuadre honesto antes de seguir: soy ingeniero, no contador. Aquí te muestro el patrón de integración — el flujo, los bloques, las trampas que truenan en producción. Para los casos fiscales de orilla, mete a tu contador y confirma las reglas vigentes del SAT.

¿Traslado (tipo T) o Ingreso? Cuál CFDI emites de verdad

Antes de escribir una línea de código, decide qué CFDI vas a emitir, porque el complemento es el mismo pero viaja sobre dos tipos de comprobante distintos.

CFDI de Traslado (tipo de comprobante “T”): mueves mercancía propia, sin venta de por medio. El dueño de la mercancía es el emisor, y el importe del comprobante va en cero (Total = 0; UsoCFDI S01, sin efectos fiscales). Este es el caso clásico del que tiene inventario y lo mueve entre su bodega y su sucursal, o a casa del cliente con su propia flotilla.

CFDI de Ingreso con el complemento Carta Porte: un transportista cobra el flete. Aquí el transportista es el emisor y factura al dueño de la mercancía como receptor. El complemento Carta Porte va montado sobre ese CFDI de Ingreso porque sí hay un cobro (el servicio de transporte).

El complemento es idéntico en ambos casos — lo que cambia es el CFDI que lo lleva (T vs I) y quién lo emite. La regla mental que uso para no equivocarme: pregúntate “¿alguien está cobrando por el flete?”. Si sí → Ingreso, y lo emite el transportista. Si no, es mercancía mía → Traslado, y lo emito yo.

Mi opinión de ingeniero que ha integrado esto: si eres el embarcador moviendo tu propio inventario, casi siempre emites tú el CFDI de Traslado tipo T. Si subcontratas a un transportista, él emite el Ingreso y tú solo lo recibes. No te compliques tratando de emitir el comprobante de alguien más.

CFDI de Traslado (tipo T)

  • Mueves mercancía PROPIA, sin venta
  • Emisor: el dueño de la mercancía
  • Importe en cero (Total = 0; UsoCFDI S01)
  • Lo emites TÚ, el embarcador
  • Caso: flotilla propia, bodega a sucursal

CFDI de Ingreso + complemento

  • Un transportista COBRA el flete
  • Emisor: el transportista
  • Factura al dueño como receptor
  • Lo emite el TRANSPORTISTA, tú lo recibes
  • Caso: subcontratas el transporte

Los cuatro bloques que estructura el complemento

El complemento Carta Porte 3.1 estructura cuatro bloques de datos. Estos cuatro bloques son, ni más ni menos, los datos de negocio que vas a mandarle a la API del PAC en la siguiente sección. Te doy los nombres reales de nodo/atributo, pero trátalos como ilustrativos: confirma los nombres exactos y los catálogos vigentes contra la documentación técnica del SAT y de tu PAC.

Ubicaciones — el Origen y el Destino. Cada uno trae RFC, fecha y hora de salida/llegada, la DistanciaRecorrida, y un domicilio con su código postal. Aquí es donde el SAT empieza a ser quisquilloso con los CP.

Mercancías — lo que transportas. Lleva BienesTransp (la clave del bien transportado, del catálogo c_ClaveProdServCP específico de Carta Porte, no el c_ClaveProdServ general de facturación), Descripción, Cantidad, ClaveUnidad, PesoEnKg, y la bandera MaterialPeligroso cuando aplica.

Autotransporte — el vehículo. Trae PlacaVM, ConfigVehicular, el permiso PermSCT + NumPermisoSCT, y el seguro de responsabilidad civil ASeguraRespCivil/PolizaRespCivil.

Figura del Transporte (nodo FiguraTransporte) — el operador/chofer: su RFC y su licencia.

Otra vez: confirma los nombres exactos de nodo/atributo y los catálogos vigentes contra los docs técnicos del SAT y tu PAC. Yo dejo los labels como ilustrativos a propósito — los PACs normalizan algunos nombres en sus APIs de alto nivel.

Cómo una API de PAC convierte datos de negocio en un CFDI sellado

Aquí está la buena noticia para el dev: no armas el XML de Carta Porte a mano. Facturapi, Facturama, Fiscalapi y otros exponen APIs REST que construyen el XML 3.1 por ti. Mantente vendor-neutral — elige por ajuste de SDK y documentación, no por marketing. El patrón es el mismo en todos.

Tú mandas los cuatro bloques (origen, destino, qué transportas, el vehículo, el operador); la API regresa el CFDI de traslado sellado más su UUID. Algo así, con nombres de campo ilustrativos (revisa los docs de tu PAC para la forma exacta):

// Ilustrativo — los nombres de campo varían según el PAC.
// Revisa la documentación de tu PAC para la forma exacta.
import { PacClient } from "tu-pac-sdk";

const pac = new PacClient({ apiKey: process.env.PAC_API_KEY });

async function emitirCartaPorteTraslado(envio) {
  const payload = {
    tipo_comprobante: "T", // Traslado: mercancía propia, sin venta
    complemento: {
      carta_porte: {
        version: "3.1",
        // 1) Ubicaciones: Origen + Destino
        ubicaciones: [
          {
            tipo: "Origen",
            rfc: envio.emisorRfc,
            fecha_salida: envio.fechaSalida,
            domicilio: { codigo_postal: envio.cpOrigen },
          },
          {
            tipo: "Destino",
            rfc: envio.receptorRfc,
            fecha_llegada: envio.fechaLlegada,
            distancia_recorrida: envio.distanciaKm, // punto común de rechazo
            domicilio: { codigo_postal: envio.cpDestino },
          },
        ],
        // 2) Mercancías: lo que transportas (catálogos Carta Porte 2026)
        mercancias: envio.items.map((it) => ({
          // clave del bien transportado: catálogo c_ClaveProdServCP
          // (Carta Porte), no el c_ClaveProdServ general de facturación
          bienes_transp: it.bienesTransp,
          descripcion: it.descripcion,
          cantidad: it.cantidad,
          clave_unidad: it.claveUnidad,
          peso_en_kg: it.pesoKg,
          material_peligroso: it.peligroso ?? false,
        })),
        // 3) Autotransporte: el vehículo
        autotransporte: {
          placa_vm: envio.placa,
          config_vehicular: envio.configVehicular, // catálogo SAT
          perm_sct: envio.permSct,
          num_permiso_sct: envio.numPermisoSct,
          asegura_resp_civil: envio.aseguradora,
          poliza_resp_civil: envio.poliza,
        },
        // 4) Figura del Transporte: el operador
        figura_transporte: [
          { tipo_figura: "Operador", rfc: envio.operadorRfc, licencia: envio.licencia },
        ],
      },
    },
  };

  // Si tu PAC soporta idempotency key, úsala para no timbrar doble;
  // si no, deduplica de tu lado (guarda el UUID por referencia de
  // envío antes de reintentar). Verifica el soporte en sus docs.
  const cfdi = await pac.cartaPorte.create(payload, {
    idempotencyKey: `cp-${envio.id}`,
  });

  return { uuid: cfdi.uuid, xml: cfdi.xml }; // guarda el XML sellado
}

Las mismas disciplinas que cualquier CFDI aplican aquí: timbras bajo tu CSD (el PAC puede administrarlo por ti), evitas timbrar doble en los reintentos (con idempotency key si tu PAC la soporta, o deduplicando tú por referencia de envío), y si el timbrado tarda, encolas y haces ack en lugar de bloquear el request del usuario. Este es el mismo rigor que aplico en la integración de Conekta con OXXO, SPEI y webhooks — la fiscalidad y los pagos comparten el patrón de “dispara, confirma, guarda”.

Y lo más importante: guarda el XML sellado. Ese XML es el documento legal que tiene que viajar con la mercancía. La representación en PDF es una cortesía visual, no el documento.

Datos de negocioOrigen, destino, mercancía, vehículo, operador
Validar CPOrigen/destino contra el catálogo SAT, antes de timbrar
Armar los 4 bloquesUbicaciones, Mercancías, Autotransporte, Figura — claves 2026
PAC timbraEnvía al SAT, valida catálogos, sella
CFDI de traslado XMLRegresa sellado, con UUID
Guardar el XMLDocumento legal que viaja con la carga

De punta a punta: emitir una Carta Porte 3.1 con un PAC

Pongamos el pipeline completo en orden. Estos son los pasos que sigo cada vez:

Paso 1 — Recolecta los datos de negocio: origen y destino, la mercancía, el vehículo, el operador. Si te falta uno de los cuatro bloques, ni intentes timbrar.

Paso 2 — Valida los códigos postales contra el catálogo del SAT ANTES de timbrar. Esto no es opcional. El catálogo de CP del SAT no incluye todos los CP de México — los CP nuevos o rurales pueden faltar y rechazar el timbrado. Atájalo aquí, no en el andén:

// Valida CP de origen/destino contra el catálogo SAT antes de timbrar.
// Ilustrativo: usa el catálogo c_CodigoPostal vigente (2026) que
// publica el SAT / expone tu PAC. Mantenlo actualizado.
import codigosPostalesSat from "./catalogos/c_CodigoPostal_2026.json";

const setCP = new Set(codigosPostalesSat); // claves de 5 dígitos

function validarCP(cp) {
  if (!/^\d{5}$/.test(cp)) {
    return { ok: false, motivo: `CP con formato inválido: ${cp}` };
  }
  if (!setCP.has(cp)) {
    // CP nuevo o rural ausente del catálogo => el PAC rechazaría el timbrado
    return { ok: false, motivo: `CP ${cp} no está en el catálogo SAT 2026` };
  }
  return { ok: true };
}

function validarEnvioAntesDeTimbrar(envio) {
  const errores = [];
  for (const cp of [envio.cpOrigen, envio.cpDestino]) {
    const r = validarCP(cp);
    if (!r.ok) errores.push(r.motivo);
  }
  // Atrapa también las trampas clásicas antes de gastar un timbre
  if (!(envio.distanciaKm > 0)) errores.push("DistanciaRecorrida ausente o cero");
  return { ok: errores.length === 0, errores };
}

Paso 3 — Arma los cuatro bloques del complemento (Ubicaciones, Mercancías, Autotransporte, Figura del Transporte) con las claves de catálogo 2026 vigentes.

Paso 4 — El PAC timbra: envía al SAT, valida contra los catálogos 2026, y regresa el sello + UUID.

Paso 5 — Recibe el CFDI de traslado sellado y GUÁRDALO. Ese XML tiene que acompañar la mercancía en tránsito.

Una nota de implementación: DistanciaRecorrida y MaterialPeligroso se calculan/marcan en estos pasos y son puntos comunes de rechazo. Resuélvelos antes de llamar a timbrar, no después de quemar el timbre.

  1. 1. Recolecta los datosOrigen, destino, mercancía, vehículo, operador
  2. 2. Valida los CPOrigen/destino contra el catálogo SAT — atrapa CP rurales/nuevos ausentes
  3. 3. Arma los 4 bloquesUbicaciones, Mercancías, Autotransporte, Figura — con claves 2026
  4. 4. PAC timbra y guardas el XMLSello + UUID; el XML sellado viaja con la carga

Las trampas de rechazo que te truenan en el andén

Estas son las cuatro que me han pegado a mí o a equipos con los que he trabajado. Cada una con su arreglo de una línea:

Códigos postales ausentes. El catálogo de CP del SAT no incluye todos los CP de México — los nuevos o rurales pueden faltar y rechazar el timbrado. Arreglo: valida origen y destino contra el catálogo por adelantado (Paso 2), y ten un camino de excepción para CP legítimos que falten.

Catálogos viejos. El SAT actualizó los catálogos de Carta Porte para 2026 (la carga mayor para autotransporte, con miles de relaciones de pedimentos del ejercicio, se publicó en enero de 2026 — confirma la fecha y versión vigente con tu PAC). Los PACs rechazan claves desactualizadas y auto-actualizan sus validadores. Arreglo: mantén tu data de catálogos al día con la del PAC; no hardcodees claves de hace dos años.

DistanciaRecorrida. Punto común de rechazo — tiene que estar presente y ser consistente a lo largo de las Ubicaciones. Arreglo: calcúlala y valídala (mayor a 0 y coherente) antes de timbrar.

MaterialPeligroso. Marcar mal los materiales peligrosos (y los campos relacionados) es un rechazo frecuente. Arreglo: prende la bandera correctamente cuando aplica, y mete a tu contador si no estás seguro de la clasificación.

Y el recordatorio operativo que cierra todo: guarda el XML sellado, porque es el documento legal que viaja con el embarque.

CP ausentesEl catálogo SAT no trae todos los CP — valida origen/destino por adelantado
Catálogos viejosSet actualizado en enero de 2026 — mantén tus claves al día con el PAC
DistanciaRecorridaDebe estar presente y consistente entre Ubicaciones — mayor a 0
MaterialPeligrosoPrende la bandera correctamente cuando aplica — frecuente punto de rechazo

FAQ: tipo T, quién emite, sandbox y multas

¿Cuándo emito tipo T vs Ingreso? Tipo T cuando mueves mercancía propia sin venta; Ingreso cuando un transportista cobra por el flete (y en ese caso lo emite el transportista).

Si contrato a un transportista, ¿quién emite la Carta Porte? El transportista. Él emite el CFDI de Ingreso con el complemento, y tú lo recibes como receptor. No intentes emitir su comprobante.

¿Puedo probar sin quemar timbres? Sí. Los PACs dev-first te dan una sandbox/llave de prueba. Valida tus payloads contra los catálogos 2026 antes de salir a producción.

¿Cuál es la multa por incumplir? El rango base por omisión o error en Carta Porte (art. 84-IV del CFF) suele ser menor, y en los casos graves se reportan cifras de hasta ~$97,330 MXN por documento (cifra de 2026 reportada como tope; confirma las vigentes con tu contador). Además, la mercancía en tránsito queda expuesta a aseguramiento.

¿Necesito mi propio CSD? Sí, timbras bajo el CSD de tu RFC — pero muchas APIs de PAC lo almacenan y administran por ti.

Lanza la capa de traslado, delega lo fiscal

En una línea: elige Traslado (tipo T) o Ingreso → recolecta los datos → valida los CP contra el catálogo del SAT → arma los cuatro bloques con claves 2026 → el PAC timbra → guarda el XML sellado. Ese es todo el flujo.

El patrón es portable entre PACs — elige por ajuste de SDK y documentación, no te cases con uno por marketing. Si quieres más guías de este tipo, tengo varias en integraciones de pagos y fiscal.

Y lo repito porque importa: soy ingeniero, no contador. Para los casos de orilla de catálogos, material peligroso y pedimentos, mete a tu contador y confirma las reglas vigentes del SAT 2026.

Fuentes oficiales para guardar en marcadores: el SAT, Fiscalapi — Carta Porte y Facturama — Carta Porte.