# Migrar un proveedor de facturación a gigstack: guía para agentes de código

Fecha de revisión: 19 de octubre de 2026. API objetivo: v2.
Audiencia: agentes de código y equipos de ingeniería de clientes de gigstack.
Idioma del documento: español; los identificadores de la API conservan su escritura exacta.

## 1. Objetivo, alcance y reglas de ejecución

Diseña y prueba una integración que sustituya al proveedor actual para operaciones futuras, conservando la trazabilidad de clientes, conceptos, documentos históricos y pagos pendientes. El proveedor de origen es deliberadamente abierto: identifica sus contratos antes de mapearlos. No se promete compatibilidad directa, migración automática de todas sus funciones ni cero interrupciones.

El recorrido fiscal concreto de esta guía es **México, CFDI**. Confirma primero el país configurado para el negocio emisor. No lo deduzcas del país del cliente, de la moneda ni de la dirección del equipo. Para Colombia, usa sus contratos específicos; no ejecutes los ejemplos fiscales mexicanos. [S3, S4]

### Instrucciones obligatorias para el agente

1. Empieza en modo de análisis y preparación. Este archivo no autoriza llamadas ni cambios en cuentas.
2. Solicita aprobación humana explícita antes de emitir o cancelar documentos de producción, importar históricos/backfill, borrar datos, habilitar automatizaciones o cambiar el enrutamiento productivo. Presenta equipo, modo, registros, cantidades, efectos y plan de recuperación.
3. No vuelvas a timbrar CFDIs históricos para “migrarlos”. Conserva UUID y XML originales. Importar un XML y emitir un nuevo CFDI son operaciones distintas.
4. No solicites claves, contraseñas de CSD/FIEL ni llaves privadas por chat. Usa variables de entorno o un gestor de secretos autorizado. El dueño debe introducir certificados y contraseñas en el flujo seguro autenticado.
5. No copies información fiscal real a fixtures, commits, capturas o logs. Usa identificadores ficticios y datos de prueba aprobados. Conserva datos reales únicamente en almacenamiento autorizado con acceso mínimo.
6. No cobres, reembolses ni envíes correos por el mero hecho de migrar. Requieren alcance y aprobación propios. Revisa automatizaciones, notificaciones, destinatarios y webhooks antes de cualquier prueba.
7. Después de un timeout no asumas que falló la operación. Concilia antes de reintentar; nunca cambies la clave de idempotencia para eludir un resultado incierto.
8. Un `200` no demuestra por sí solo timbrado, cancelación final, complemento terminado ni movimiento de dinero.

### Fuentes y nivel de validación

La dirección facilitada, `docs.gitstack.pro`, no pudo verificarse. `https://docs.gigstack.pro/` devolvió 404 y su índice estaba vacío. Se consultó la documentación actual de gigstack en **https://docs.gigstack.io/**, con índice y contrato OpenAPI enlazados por ese sitio. No se afirma que exista una redirección desde el dominio facilitado. Evita mezclarla con resultados antiguos de Apidog.

Revisión del índice consultado: `315973e86f85ebc0dc7a86ab07990ad04d58852b1320f7ea3461f7a604c4bd7e`.

Los cuerpos y rutas de los ejemplos se contrastaron con el OpenAPI publicado y las guías citadas. **No se ejecutaron llamadas a cuentas ni pruebas de API para preparar este documento.** Las pruebas del proveedor descritas en [S2] son de staging, principalmente del 8 de octubre de 2026, no certificación de producción. Antes de implementar, vuelve a descargar [S1], fija su versión/hash en tu proyecto y valida cada payload. Si la receta y OpenAPI discrepan, registra la discrepancia y no inventes campos.

## 2. Preguntas que debes resolver antes de escribir el adaptador

Entrega primero una ficha con respuestas, responsables y bloqueos:

- ¿Cuál es el proveedor de origen, su versión, documentación, límites y formato de exportación? ¿Qué acceso de lectura está autorizado?
- ¿Qué entidades emisoras, países, RFC, equipos gigstack y cuentas de facturación intervienen? ¿Quién confirma su identidad y configuración fiscal?
- ¿Se migran solo operaciones nuevas, también clientes/productos, históricos XML, saldos PPD o todo lo anterior? ¿Qué fechas y volumen?
- ¿Qué tipos de documentos existen: ingresos, egresos, complementos, globales, retenciones, nómina, Carta Porte u otros? ¿Cuáles quedan fuera?
- ¿Qué sistema es la fuente de verdad para pedidos, cobros, fiscalidad, saldos y cancelaciones? ¿Quién decide tasas y códigos fiscales?
- ¿Cómo se expresan precios, descuentos, importes con/sin impuestos, divisas, redondeos, zonas horarias y fechas en el proveedor actual?
- ¿Hay PPD abiertos, pagos parciales, sustituciones o cancelaciones pendientes? ¿Pueden terminarse en el proveedor original?
- ¿Qué automatizaciones pueden generar facturas o mensajes a partir de un cliente, pago, recibo o webhook? ¿Qué integraciones actuales ya reciben el mismo cobro?
- ¿Qué URLs y secretos de webhooks están autorizados? ¿Qué eventos y versiones consume tu aplicación?
- ¿Quién aprueba el corte, cuánto tiempo de pausa se acepta, qué métricas bloquean la salida y quién puede revertir el enrutamiento?
- ¿Durante cuánto tiempo se mantiene acceso al proveedor original para cancelaciones, descargas y auditoría? ¿Qué retención aplica a los documentos?

No rellenes preguntas fiscales con valores de ejemplo. Registra `pendiente` y detén la operación dependiente.

## 3. Preparación del emisor y del entorno

### Autenticación y modo

- Base pública: `https://api.gigstack.io/v2`.
- Header: `Authorization: Bearer <API_KEY>`; incluye literalmente `Bearer `.
- Las claves estándar de prueba y producción usan esa misma base. El modo lo determina la clave; enviar `livemode: false` no convierte una clave real en una de prueba.
- Staging interno es un despliegue separado, con otra base de datos y otras claves. No es el sandbox público por defecto; usa únicamente el entorno autorizado que te indique gigstack.
- Comprueba `livemode: false` en recursos nuevos antes de continuar con pruebas. Antes de timbrar un borrador, comprueba también el modo **almacenado en ese borrador**.
- La API puede requerir habilitación en el plan incluso en prueba. No intentes arreglar un `403` de plan creando nuevas claves.
- Para Connect, añade `?team=TEAM_ID` solamente con un acceso autorizado a equipos de la misma cuenta y funciones habilitadas. Un token OAuth está ligado a un equipo; no uses el parámetro para cambiarlo. Mantén equipo y modo en cada clave local de conciliación. [S3]

Variables necesarias para los ejemplos: `GIGSTACK_BASE_URL`, `GIGSTACK_API_KEY`; para operaciones concretas, IDs que hayan sido devueltos por la API. Usa Bash, `curl` y `jq`. No actives `set -x` y no guardes headers de autenticación en trazas.

```bash
set -euo pipefail
umask 077
: "${GIGSTACK_API_KEY:?Configura la clave de prueba mediante tu flujo seguro}"
export GIGSTACK_BASE_URL='https://api.gigstack.io/v2'
# No envíes escrituras hasta verificar cuenta, equipo, modo y permisos.
curl --fail-with-body "$GIGSTACK_BASE_URL/clients?limit=1" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" > clients-check.json
```

Una respuesta de lista vacía no verifica el modo. Los endpoints públicos `/auth/health` y equivalentes verifican disponibilidad, no autenticación. [S3, S14]

### Emisor y certificados

El dueño confirma el país/proveedor de emisión, razón social, CSD vigente y configuración de series/folios. La respuesta pública del equipo no prueba por sí sola el país/proveedor. Configura un equipo de prueba y sus destinatarios conocidos; un preview no demuestra que el CSD funcione. [S4]

Para México, el **CSD** sirve para timbrar; la **FIEL/e.firma** sirve para autenticarse ante SAT en descarga masiva. No son intercambiables. El flujo seguro `POST /teams/{id}/fiscal-upload-sessions` permite al dueño cargar material privado en navegador con `purpose: "csd"`; crear sesiones requiere una credencial personal del dueño admitida por el contrato, no una clave de API de equipo. No automatices aquí la carga de secretos. [S15]

La configuración fiscal se comparte entre modos de prueba y producción (`effect_scope: "team_shared"`). FIEL/PFX puede alcanzar al proveedor de producción incluso desde staging. Una clave de prueba no vuelve inocua esa configuración. Verifica el estado seguro con la operación documentada `GET /teams/{id}/fiscal-status`; estado guardado no equivale a verificación fiscal completa. [S15]

## 4. Inventario y hoja de mapeo

Crea una fila por entidad y conserva el original antes de transformar. Nunca uses un nombre o email como identidad única sin revisar ambigüedades.

| Origen | Destino gigstack | Regla / validación |
| --- | --- | --- |
| ID estable del cliente | tabla local + `metadata.external_id` | Incluye namespace del proveedor; persiste `data.id` |
| Nombre visible / razón social | `name` / `legal_name` | No confundas alias comercial con identidad fiscal |
| RFC, régimen, uso, CP | `tax_id`, `tax_system`, `use`, `address.zip` | Códigos y CP como strings; valores reales aprobados |
| País del receptor | `address.country` | México: `MEX`; no selecciona país del emisor |
| SKU / concepto | `/services`, `sku`, `description` | Tabla local de IDs; no presupongas upsert de servicios |
| Clasificación y unidad | `product_key`, `unit_key`, `unit_name` | Validar catálogos SAT y caso fiscal |
| Cantidad / precio | `items[].quantity`, `unit_price` | `unit_price` en unidades de moneda, no centavos; evitar flotantes binarios en cálculo interno |
| Impuestos | `items[].taxes[]` | Mapear tipo, tasa, factor, inclusión, retención y base cuando proceda |
| Moneda / tipo de cambio | `currency`, `exchange_rate` | Guardar origen y fecha del tipo; probar multimoneda aparte |
| Momento / instrumento de pago | `payment_method` / `payment_form` | `PUE`/`PPD` no son métodos del procesador; `03` es transferencia, `99` por definir |
| Pedido / evento de cobro | `metadata` + clave estable local | No usar IDs nuevos para cada reintento |
| Factura emitida | UUID fiscal + ID origen + ID destino | No reemplazar UUID histórico con ID local |
| Borrador | ID de borrador | Tras timbrar, guardar el nuevo `data.uuid`; el ID anterior puede devolver 404 |
| Relación/sustitución | `related_documents` | Preservar UUIDs y códigos; revisión humana de la relación |
| Parcialidad PPD | UUID padre, pago, complemento, saldo | Conciliar historial completo antes de generar lo siguiente |
| Serie / folio | configuración de equipo, `series`, `folio_number` si aprobado | No copiar contadores automáticamente ni reutilizar folios |
| PDF/XML, cancelación | archivo original y evidencia asociada | Hash, acceso restringido, estado fiscal separado del archivo |

Esquema mínimo de control local recomendado (no es un endpoint gigstack):

```text
migration_run_id, source_provider, source_entity_type, source_id,
team_id, mode, destination_id, fiscal_uuid, business_event_key,
payload_hash, source_updated_at, migration_status, last_error_code,
approval_reference, attempted_at, verified_at
```

Impón unicidad en `(source_provider, source_entity_type, source_id, team_id, mode)` y en la clave del evento de negocio. Conserva un estado `outcome_unknown` distinto de `failed`. Añade leases/locks por evento para que dos workers no emitan la misma operación.

### Impuestos y contratos estrictos

`rate: 0.16` representa 16%, no `16`. `inclusive` indica impuesto incluido en precio; `withholding` indica retención. IVA cero con `factor: "Tasa"` no equivale a exento con `factor: "Exento"`. El ejemplo de IVA 16% no es una regla universal. [S9]

No traslades campos del proveedor origen sin un mapa explícito: varios cuerpos rechazan propiedades desconocidas. `team`, `owner` y `livemode` son administrados por autenticación, no campos para inyectar al body. El contrato actual de servicios usa `description`, `unit_price` y `taxes`, no el esquema antiguo basado en `name`/`total`. [S1]

## 5. Secuencia de implementación y ejemplos

Todos los ejemplos de escritura requieren autorización para el entorno y finalidad. No pegues un bloque entero en producción. Los valores ilustrativos solo describen un caso de consultoría en México.

### 5.1 Cliente: buscar antes de crear, persistir el ID

Prepara `customer-body.json` en almacenamiento local autorizado. Ejemplo estructural, con placeholders deliberadamente no fiscales:

```json
{
  "name": "MIGRATION_TEST_CUSTOMER",
  "legal_name": "APPROVED_TEST_LEGAL_NAME",
  "tax_id": "APPROVED_TEST_RFC",
  "tax_system": "APPROVED_TAX_REGIME",
  "use": "APPROVED_CFDI_USE",
  "address": {"country": "MEX", "zip": "APPROVED_POSTAL_CODE"},
  "metadata": {"external_id": "source:customer:sample-001"},
  "search": {
    "on_key": "metadata.external_id",
    "on_value": "source:customer:sample-001",
    "update": false
  }
}
```

Resuelve los placeholders con un fixture autorizado antes de enviar; no es un RFC válido ni un payload listo para timbrar.

```bash
curl --fail-with-body "$GIGSTACK_BASE_URL/clients" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' --data @customer-body.json > client.json
CLIENT_ID=$(jq -er '.data.id' client.json)
export CLIENT_ID
jq -e '.data.livemode == false' client.json >/dev/null
```

Resultado documentado: `201` nuevo; `200` encontrado. Inspecciona `data.fiscal_validation`; guardar un contacto puede omitir la validación. `search.update: false` no modifica el encontrado. Varios resultados requieren desambiguación; esta búsqueda no garantiza creación atómica bajo concurrencia. [S5]

**Peligro durante actualizaciones:** `PUT /clients/{id}` tiene `check_pending_receipts: true` por defecto; actualizar datos fiscales válidos puede timbrar recibos pendientes. Durante una migración controlada envía explícitamente `check_pending_receipts: false`, además de los campos revisados, y verifica que ninguna automatización adicional cause emisión. No ejecutes actualizaciones masivas sin aprobar sus efectos. [S1: ClientUpdateInput]

### 5.2 Productos y servicios

`POST /services` crea entradas reutilizables; `GET /services/{id}` verifica. Ejemplo de cuerpo conforme al contrato actual:

```json
{
  "description": "Consultoría de prueba",
  "sku": "MIGRATION-TEST-001",
  "product_key": "80101500",
  "unit_key": "E48",
  "unit_name": "Servicio",
  "unit_price": 1000,
  "taxes": [{"type": "IVA", "rate": 0.16, "factor": "Tasa", "inclusive": false, "withholding": false}]
}
```

No asumas que SKU es único ni que POST tiene idempotencia. Usa tu tabla local, lista/lee y serializa la creación. También puedes usar conceptos inline en la factura; no crees catálogo si no lo necesita el cliente. [S9]

### 5.3 Borrador y preview antes de emitir

Para un caso de prueba pagado completamente por transferencia: subtotal MXN 1,000 + IVA 160 = total 1,160. Sustituye códigos e impuestos solo después de revisión fiscal.

```bash
jq -n --arg client "$CLIENT_ID" '{
  invoice_type: "I", client: {id: $client}, currency: "MXN", exchange_rate: 1,
  use: "G03", payment_method: "PUE", payment_form: "03",
  items: [{description: "Consultoría de prueba", product_key: "80101500",
    unit_key: "E48", quantity: 1, unit_price: 1000,
    taxes: [{type: "IVA", rate: 0.16, inclusive: false, withholding: false, factor: "Tasa"}]}],
  automation_type: "none", send_email: false, ignore_emails: true,
  metadata: {external_id: "source:order:sample-001"}
}' > draft-body.json
curl --fail-with-body "$GIGSTACK_BASE_URL/invoices/draft" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' --data @draft-body.json > draft.json
DRAFT_ID=$(jq -er '.data.id' draft.json)
export DRAFT_ID
jq -e '.data.status == "draft" and .data.livemode == false' draft.json >/dev/null
curl --fail-with-body -X POST "$GIGSTACK_BASE_URL/invoices/draft/$DRAFT_ID/preview" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' --data '{}' > preview.json
jq -er '.data.pdf' preview.json | base64 --decode > preview.pdf
```

Creación: `201`; preview: `200`, PDF base64 en `data.pdf`. Revisa receptor, emisor, conceptos, total, impuestos, moneda y condiciones. No es un CFDI timbrado. Un `PUT` parcial del borrador puede borrar `items` o aplicar defaults: envía el cuerpo completo pretendido y relee antes de emitir. [S6]

Los flags `send_email: false`, `ignore_emails: true`, `automation_type: "none"` no suprimen todos los eventos/notificaciones de cuenta. Confirma el enrutamiento de prueba con el dueño. [S3, S6]

### 5.4 Emisión: escoger una sola ruta

**A. Finalizar el borrador revisado**, únicamente después de aprobación y readback del modo:

```bash
curl --fail-with-body "$GIGSTACK_BASE_URL/invoices/draft/$DRAFT_ID" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" > draft-readback.json
jq -e '.data.status == "draft" and .data.livemode == false' draft-readback.json >/dev/null
# DETENER AQUÍ hasta aprobar explícitamente este timbrado.
curl --fail-with-body -X POST "$GIGSTACK_BASE_URL/invoices/draft/$DRAFT_ID/stamp" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' --data '{"send_email":false}' > issued.json
INVOICE_UUID=$(jq -er '.data.uuid' issued.json)
export INVOICE_UUID
```

La receta publicada también muestra `ignore_emails` en stamp, pero el esquema específico de stamp solo declara `send_email` y `return_files`. Aquí se usa el campo declarado y se mantiene la revisión de notificaciones. No presupongas una clave de idempotencia para stamp: el contrato específico no la expone. Reconcilia una respuesta perdida mediante el borrador, documentos emitidos y soporte antes de repetir.

Resultado observado por el proveedor: `200`, `data.uuid`, `status: "valid"`; no necesariamente `data.id`. Después de timbrar usa el UUID nuevo. [S1, S2, S6]

**B. Emisión directa con clave persistente**, para el adaptador aprobado que ya validó su payload, sin emitir también A:

```bash
: "${INCOME_EVENT_KEY:?Clave estable persistida para esta venta y este documento}"
jq --arg key "$INCOME_EVENT_KEY" 'del(.invoice_type) + {idempotency_key: $key}' \
  draft-body.json > income-body.json
# POST /invoices/income timbra inmediatamente; requiere aprobación.
curl --fail-with-body "$GIGSTACK_BASE_URL/invoices/income" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' --data @income-body.json > issued.json
INVOICE_UUID=$(jq -er '.data.uuid' issued.json)
export INVOICE_UUID
```

No uses la ruta A y B para la misma venta. Para conciliar por clave usa `GET /invoices/income?idempotency_key=...` con URL encoding; no combines esa consulta exacta con filtros de metadata. El esquema `InvoiceIncomeInput` requiere `client`, `currency`, `use`, `items`, `payment_form`, `payment_method`. La clave se acota al equipo y modo; conserva payload y clave. En producción, el wrapper debe capturar también los cuerpos de error para conciliación, no tratar todo no-2xx como ausencia de factura. [S1, S10]

### 5.5 Recuperar documento y archivos

```bash
curl --fail-with-body "$GIGSTACK_BASE_URL/invoices/income/$INVOICE_UUID" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" > invoice-readback.json
curl --fail-with-body "$GIGSTACK_BASE_URL/invoices/$INVOICE_UUID/files" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" > invoice-files.json
```

Verifica UUID, `status`, modo y totales. `/files` retorna `data[]` con `filename`, MIME `type` y `content` base64; no presupongas URLs. `file_type=pdf` o `xml` limita la respuesta. Decodifica y valida XML/PDF; limita rutas y tamaño de archivos, no uses nombres remotos sin sanear. Conserva hash y vínculo con el UUID. [S1, S6]

### 5.6 PPD y complementos: no duplicar la venta

Para ingreso diferido usa `payment_method: "PPD"`, `payment_form: "99"` y una clave única de ingreso. Emitirlo es una acción fiscal; un cobro posterior no debe emitir otro ingreso por la misma venta. [S7]

Ruta automática documentada para un PPD **ya emitido y conciliado en gigstack**: `POST /payments/register`, con `ppd_invoice_id` igual al **UUID fiscal** padre. Para el primer pago de MXN 580 del ejemplo:

```bash
: "${PPD_UUID:?UUID verificado de la factura PPD}"
: "${PAYMENT_EVENT_KEY:?Clave estable del cobro real recibido}"
jq --arg client "$CLIENT_ID" --arg uuid "$PPD_UUID" --arg key "$PAYMENT_EVENT_KEY" '{
  client: {id: $client}, currency: "MXN", exchange_rate: 1,
  payment_form: "03", automation_type: "none", ppd_invoice_id: $uuid,
  idempotency_key: $key, send_email: false, ignore_emails: true,
  items: [.items[0] + {unit_price: 500}]
}' draft-body.json > partial-payment-body.json
# Esta llamada puede timbrar un complemento: requiere aprobación fiscal.
curl --fail-with-body "$GIGSTACK_BASE_URL/payments/register" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' --data @partial-payment-body.json > payment.json
PAYMENT_ID=$(jq -er '.data.id' payment.json)
curl --fail-with-body "$GIGSTACK_BASE_URL/payments/$PAYMENT_ID" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" > payment-state.json
curl --fail-with-body "$GIGSTACK_BASE_URL/invoices/income/$PPD_UUID" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" > ppd-state.json
```

**`ppd_invoice_id` activa el complemento incluso con `automation_type: "none"`.** No envíes ese campo al registrar un cobro sin efectos fiscales. No llames además a `/invoices/payment` por el mismo abono. La creación del pago (`201`) y el timbrado del complemento son resultados separados: concilia UUID del complemento, `payment_complements`, `last_balance` e `installments`. En el ejemplo el saldo pasa de 1,160 a 580; `installments: 2` indica la siguiente parcialidad, no dos pagos ya realizados. [S7]

Ruta manual alternativa: `POST /invoices/payment`, esquema `PaymentComplementInput`. Requiere `client` y `complements`, normalmente `[{"type":"pago","data":[...]}]`. Cada pago requiere `payment_form`, `date` ISO 8601, `currency`, `related_documents`; cada relacionado requiere `uuid`, `amount`, `installment`, `last_balance`, `currency`. En este contrato `taxes[].rate` es **string**, a diferencia del impuesto numérico de los items de ingreso. `idempotency_key` está documentado. Implementa esta ruta solo después de revisar el contrato completo y su fiscalidad. [S1, S10]

El contrato de complemento directo acepta un UUID externo como referencia; eso no garantiza que se reconstruyan o actualicen automáticamente saldos del origen. Antes de usarlo, valida importe, saldo anterior, impuestos y siguiente parcialidad contra el historial completo.

Para PPD históricos del proveedor anterior, no asumas que importar XML reconstruye saldo, parcialidades ni habilita complementos. Concilia todos los XML de pagos y acuerda con gigstack y el responsable fiscal si continúan en el proveedor original o mediante una ruta admitida y probada.

## 6. Históricos, cancelaciones y casos que no son equivalentes

### Importar archivos, no reemitir

El contrato documenta `POST /invoices/import` con `files` de hasta 50 elementos. Cada elemento lleva `filename` y `xml` como texto o `content` base64; `xml` prevalece si se suministran ambos. Ejemplo de preparación local, sin datos reales incrustados:

```bash
: "${APPROVED_XML_PATH:?Ruta autorizada a un XML original}"
jq -n --rawfile xml "$APPROVED_XML_PATH" \
  '{files:[{filename:"historical-cfdi.xml",xml:$xml}]}' > import-body.json
# Solo tras aprobación del lote exacto:
# POST $GIGSTACK_BASE_URL/invoices/import, Authorization Bearer, JSON import-body.json
```

Reglas documentadas: si el equipo es emisor, se guarda como invoice; si es receptor, en SAT recibidas; si no participa, se rechaza. UUID existente: `already_exists`; otra cuenta: `conflict`. Nómina recibida se rechaza por datos personales. La importación de XML que ya tienes no se factura según este contrato; descarga masiva es otro servicio con costos y cuotas. Inspecciona **cada** `data.results[]`, no solo `success` global. [S1: importInvoiceXml]

**Estado importado:** se asume `Vigente`; un XML no evidencia una cancelación posterior. Conserva el estado del proveedor origen y concílialo con evidencia actual. No presentes esa suposición como consulta vigente al SAT. La importación no promete replicar workflows, recibos, cobros, permisos, conciliación ni contadores.

### Cancelar

`DELETE /invoices/{UUID}` solicita cancelación fiscal; no equivale a borrar un registro y no reembolsa dinero. Requiere aprobación humana para el documento y motivo exactos. [S10]

| Motivo | Uso documentado | Body |
| --- | --- | --- |
| `01` | Error con sustitución | `{"motive":"01","substitution_uuid":"REPLACEMENT_UUID"}` |
| `02` | Error sin sustitución | `{"motive":"02"}` |
| `03` | No se realizó la operación | `{"motive":"03"}` |
| `04` | Operación nominativa incluida en global | Seguir flujo fiscal específico; no usar como atajo |

Antes y después, lee `GET /invoices/{UUID}`. Un `200` con aceptación del proveedor puede dejar `status: "valid"` y `cancellation_status: "pending"`; no declares cancelado hasta conciliar el estado guardado y resultado fiscal. Conserva acuse. No hay clave de idempotencia documentada para cancelación. Un importado timbrado por PAC externo puede devolver `422`: se cancela con el proveedor original, no mediante gigstack. [S10]

Una nota de crédito, cancelación, devolución comercial y refund del procesador son operaciones distintas. No las desencadenes juntas por inferencia. Retenciones, nómina, globales, Carta Porte y variantes avanzadas necesitan análisis separado; la presencia de un endpoint no demuestra que sustituya la función del origen.

## 7. Reintentos, errores y paginación

### Tabla de decisión del adaptador

| Resultado | Tratamiento |
| --- | --- |
| Ingreso duplicado: `400`, `message.code: INVALID_INVOICE`, `duplicate: true`, `uuid` | Leer UUID existente y comparar equipo, modo y contenido antes de marcar conciliado |
| Pago/recibo duplicado: `400`, `error.code: resource_conflict` | Conciliar registro existente; no esperar necesariamente `409` ni un replay exitoso |
| `409 idempotency_in_progress` | Esperar y usar la misma clave, sin crear otro evento |
| `503 PAC_UNAVAILABLE`, `retryable: true` | Backoff acotado, misma clave; el proveedor indica que no evaluó el documento |
| `503 PAC_OUTCOME_UNKNOWN` | Puede existir CFDI. Para `POST /invoices/income`, reintentar solo con la misma clave JSON persistida y `retryable: true`; para las demás rutas, conciliar y verificar su contrato específico antes de repetir |
| `409 STAMP_NEEDS_REVIEW` | Detener; soporte con clave y referencia de proceso. Nunca clave nueva |
| Validación `400` | Corregir mapeo; no reintentar payload idéntico |
| `401` / `403` | Revisar credencial, prefijo, equipo, modo, permisos y plan |
| `404` | Revisar ID/UUID, modo, equipo y disponibilidad de ruta; no asumir borrado |
| `412 SAT_NOT_CONNECTED` / `CSD_VALIDATION_ERROR` | Resolver configuración fiscal mediante dueño; no insistir |
| `429` de créditos | Revisar cuota; esperar milisegundos no añade créditos ni autoriza ampliarla |
| Timeout / `500` / respuesta perdida | Registrar `outcome_unknown`, conciliar; los reintentos ciegos de escrituras están prohibidos |

Errores de timbrado pueden estar en `body.message.code`; cancelación puede exponer `body.code`; errores estándar usan `body.error.code` y validación puede devolver `errors[]`. Normaliza sin descartar el código, HTTP status ni referencia `[pcs_…]`. Redacta valores fiscales y PII antes de logs; conserva evidencias completas solo donde esté autorizado. [S8, S10]

No existe promesa de header universal `Idempotency-Key`. Mantén cuerpo y clave por operación donde el contrato declare `idempotency_key`. El comportamiento de ingreso no se puede extrapolar a refunds, cancelación, clientes, borradores ni a garantías de concurrencia de complementos directos. La detección de pagos existentes es más débil que el bloqueo de ingreso: no presupongas exactamente una creación bajo concurrencia; serializa cada cobro en tu aplicación.

### Páginas y límites

Listas comunes: `data`, `has_more`, `next`; envía `next`, preservando filtros, equipo y modo. Default usual 10, máximo 100. Search y ciertos filtros por metadata usan `page`, `found`, `per_page`, sin cursor. Webhooks usan `cursor` y `next_cursor`. Valida cada operación por separado; registra checkpoint y detecta cursores repetidos. Las listas no son necesariamente snapshots: usa exportación consistente o delta final durante la pausa de corte. [S8, S11]

Los límites publicados incluyen créditos de equipo para ingresos/stamp/recibos y 10 solicitudes manuales SAT por equipo al día con reinicio a medianoche de Ciudad de México. No se envía `Retry-After` para esos límites. No hay una cuota universal de requests/segundo documentada que puedas asumir; pueden existir límites de infraestructura. Usa concurrencia configurable y backoff con jitter, sin tratar todo `429` como temporal. [S14]

## 8. Webhooks: diseñar un receptor antes de cambiar emisores

Usa una URL HTTPS propia y aprobada. La configuración del equipo aplica a eventos de producción **y prueba**; no asumas separación por clave. Revisa los permisos, destino y payload antes de registrar o activar nada. [S11]

- Configura explícitamente versión `v2` cuando sea apropiado. Recursos v2: `id`, `type`, `created` en milisegundos, `livemode`, `data.object`.
- v1 usa `event`, `team`, `webhook`, `data` y no tiene ID de entrega único. No dedupliques permanentemente por `(event, data.id)`: descartarías actualizaciones legítimas.
- En v2, `id` permanece en retries/resends; el recurso se lee al enviar y puede haber cambiado. Registra entregas e interpreta eventos como señales para leer estado actual; protege efectos irreversibles con tu ledger de negocio.
- Orden no garantizado; pueden existir duplicados. Valida autenticación, persiste en inbox durable y responde `2xx` antes de 10 s; procesa después.
- Eventos de recursos reintentan `408`, `429`, `5xx`, timeouts/red hasta 16 intentos. Otros `4xx` son finales. `sat.invoice.synced` e `invoice_batch.completed` tienen cuerpo propio y un solo intento; necesitan polling de conciliación.
- Eventos como `invoice.created`/`invoice.failed`/`invoice.canceled` no autorizan por sí solos nuevas emisiones, cobros o cancelaciones.

Firma configurada: `Webhook-Id`, `Webhook-Timestamp` (segundos Unix), `Webhook-Signature: v1,<base64 HMAC-SHA256>`. El mensaje firmado es `id + "." + timestamp + "." + rawBody`; la clave es la cadena completa, incluido `whsec_`, **sin** base64-decodificarla. Usa bytes originales, comparación constante, tolerancia temporal corta y deduplicación. Habilita y verifica signing; no asumas que viene activo. Secretos se configuran en navegador autenticado, no en prompts/argumentos del agente. [S11]

Legacy `X-Gigstack-Signature` para ciertos eventos SAT/batch usa `sha256=` + HMAC hex del raw body. No es intercambiable con la firma nueva y nuevos endpoints no generan ese secret legacy. Los ejemplos largos Node/Python/PHP de la guía ilustran la firma legacy; no los copies como implementación de la firma configurada nueva. La guía contiene texto antiguo sobre ausencia de `version` aunque su objeto actual sí la declara: usa contrato/readback actuales y comprueba el cuerpo real autorizado, no heurísticas de un párrafo.

## 9. Matriz mínima de pruebas y evidencia de aceptación

Ejecuta únicamente en un entorno aprobado. Guarda petición redactada, respuesta, equipo/modo, IDs, timestamp y resultado esperado/real. Los tests con efectos necesitan aprobación de prueba explícita.

| Prueba | Evidencia de éxito |
| --- | --- |
| Auth correcta/incorrecta; equipo y modo | Lectura autorizada; errores controlados; recursos test con `livemode: false` |
| Cliente nuevo, repetido, múltiples coincidencias | ID estable; no duplicado elegido arbitrariamente; validación fiscal inspeccionada |
| Actualización cliente con recibos pendientes | `check_pending_receipts: false` explícito; ninguna emisión inesperada |
| Catálogo y conceptos inline | Mapa estable; códigos y precios correctos; unknown keys rechazadas |
| PUE 1,000 + 160 | Draft/preview revisados; UUID emitido autorizado; XML y total 1,160 |
| IVA cero, exento, retenciones, descuentos | XML y totales comparados con fixtures aprobados por fiscal |
| Moneda distinta / redondeos | Tipos de cambio y sumas comparados a precisión aprobada |
| Draft update completo / modo incorrecto | Items preservados; bloqueo previo a stamp de un live draft |
| Timeout y concurrencia de ingreso | Una sola operación fiscal; misma clave; recuperación de UUID |
| PPD 1,160, dos pagos 580 | Dos complementos, saldo final 0; rechazo de sobrepago |
| Pago sin fiscalización | `automation_type: none`, sin `ppd_invoice_id`; ninguna emisión no aprobada |
| Importación XML repetida, ajena, conflictiva | Resultado por archivo; UUID original; fallos aislados; estado no asumido vigente |
| Cancelación autorizada / pendiente | Readback y evidencia; no éxito falso por HTTP 200 |
| XML externo cancelado en origen | Estado conciliado; `422` esperado si se intenta ruta no admitida en prueba autorizada |
| Webhook válido, inválido, duplicado, desordenado | Firma rechazada/aceptada correctamente; inbox durable; efectos únicos |
| Más de una página y recuperación de checkpoint | Ningún registro perdido; filtros intactos; finalización demostrada |
| Créditos agotados / provider incierto | Sin tormenta de reintentos; excepción visible y responsable asignado |

Añade casos del proveedor origen. No declares paridad por superar únicamente el caso de consultoría. El proveedor documenta pruebas staging de clientes, drafts, PPD y complementos; cancelación, setup y muchas variantes se revisaron por fuente, no se probaron de extremo a extremo. [S2]

## 10. Conciliación, corte y recuperación

### Antes del corte

1. Exporta datos y XML originales con autorización; calcula hashes y conteos por equipo, moneda, tipo y estado. Preserva una copia inmutable y el delta/cursor del origen.
2. Construye el adaptador detrás de una selección de proveedor. Mantén contratos internos estables y una tabla de IDs; no acoples el dominio a envelopes gigstack.
3. Ejecuta transformación y comparación sin emisión. Prueba escrituras solo en sandbox aprobado. Nunca hagas “doble escritura” de CFDIs productivos para comparar proveedores.
4. Importa únicamente maestros y archivos aprobados. No reconstruyas cobros o recibos históricos con automatización activa.
5. Conciliación financiera: compara subtotal, impuestos trasladados/retenidos, total, moneda, pagos aplicados y saldo por factura; compara conjuntos de UUID, no solo sumas globales.
6. Resuelve todas las operaciones `outcome_unknown`, pendientes de cancelación y PPD. Si no pueden migrarse, documenta qué continúa en el origen y quién lo opera.
7. Firma humana de aceptación: resultados, diferencias explicadas, credenciales productivas preparadas de forma segura, destinos de eventos revisados, ventana y responsable.

### Corte controlado

1. Pausa o encola eventos nuevos según la ventana acordada. Evita dos consumidores fiscales activos para el mismo pedido/cobro.
2. Captura delta final y concilia en-flight del proveedor original. Fija un watermark de corte y guarda la asignación de cada evento a su proveedor.
3. Habilita gigstack para un conjunto pequeño **aprobado** de operaciones nuevas; verifica UUID/XML, totales y eventos antes de aumentar volumen.
4. Drena la cola respetando claves y asignación de proveedor. Monitoriza éxito fiscal, incertidumbre, errores, latencia, backlog, divergencias de saldo y fallos de webhook.
5. Mantén acceso al origen para consulta y para operaciones que solo él pueda realizar, especialmente cancelación de CFDIs externos. No borres ni cierres la cuenta por inferencia.

### Rollback realista

Rollback significa detener el nuevo enrutamiento y recuperar servicio para **operaciones futuras**; no deshace CFDIs timbrados, importaciones, complementos ni mensajes ya enviados.

- Ante divergencias o emisión incierta, pausa workers y conserva cola/ledger; no reenvíes todo al proveedor anterior.
- Reconcilia cada evento intentado en gigstack; marca UUID y resultado antes de decidir dónde procesarlo.
- Vuelve al origen solo para eventos inequívocamente no emitidos y dentro de la aprobación de recuperación.
- Mantén operaciones ya emitidas asociadas a su proveedor. No canceles automáticamente para “volver atrás”. Cualquier corrección fiscal requiere revisión separada.
- Revisa credenciales, webhooks y automatizaciones cambiados; restaura solamente los cambios aprobados y verificables. No revoques el acceso necesario para investigación pendiente.

### Entregables del agente y definición de terminado

Entrega adaptador y pruebas, hoja de mapeo, inventario de endpoints utilizados, snapshot/hash del contrato, ledger de migración, reporte de conciliación, runbook de corte/rollback y lista de excepciones con dueño.

Terminado significa: cada registro/evento del alcance está verificado o excluido explícitamente; no hay duplicados fiscales ni resultados desconocidos sin responsable; saldos y archivos están conciliados; operación y monitoreo tienen responsables; aprobación de cierre registrada. Un script que terminó sin excepciones no basta.

## 11. Limitaciones y decisiones que requieren confirmación adicional

- No hay una ruta universal que replique todo el historial operacional de otro proveedor. XML importado no reconstruye automáticamente saldos, pagos, recibos, cancelaciones ni relaciones fuera de lo que contiene/documenta.
- El tratamiento de PPD externos abiertos y sus complementos necesita validación específica; no se da por habilitado tras importar XML.
- Cancelación de facturas importadas de otro PAC no está disponible por la ruta de cancelación gigstack descrita.
- No se presupone idempotencia global, conservación indefinida de claves, SLA de polling ni tasa universal de requests; confirma lo que tu carga requiera.
- Las páginas de referencia pueden documentar rutas sin despliegue de gateway: [S2] reporta `/documents` no disponible en su prueba. Verifica disponibilidad antes de elegir ese recurso para un archivo histórico; `/invoices/import` y `/invoices/{id}/files` son contratos distintos.
- La paridad con el proveedor origen, modelos fiscales avanzados y comportamiento productivo no se han probado en este documento.
- Las reglas fiscales aplicables deben aprobarlas el responsable del negocio/contabilidad. Esta guía es de integración, no un dictamen fiscal.

## 12. Fuentes primarias consultadas

Todas consultadas el 19 de octubre de 2026. Los enlaces `.md` sirven para agentes; las rutas sin extensión ofrecen lectura web.

- **S1:** [Índice](https://docs.gigstack.io/llms.txt), [OpenAPI completo](https://docs.gigstack.io/openapi.json). Schemas: `ClientInput`, `ClientUpdateInput`, `ServiceInput`, `DraftInvoiceInput`, `InvoiceIncomeInput`, `RegisterPaymentInput`, `PaymentComplementInput`; operaciones `importInvoiceXml`, `cancelInvoice`, `getInvoicesByIdFiles`.
- **S2:** [Cobertura de verificación](https://docs.gigstack.io/verification.md).
- **S3:** [Autenticación y modo de prueba](https://docs.gigstack.io/authentication.md).
- **S4:** [Configuración del emisor](https://docs.gigstack.io/concepts/issuer-setup.md), [México](https://docs.gigstack.io/countries/mexico.md).
- **S5:** [Crear cliente](https://docs.gigstack.io/recipes/customer.md).
- **S6:** [Borrador, preview y emisión](https://docs.gigstack.io/recipes/invoice.md).
- **S7:** [PPD y parcialidades](https://docs.gigstack.io/recipes/paid-later.md).
- **S8:** [Respuestas, paginación y reintentos](https://docs.gigstack.io/responses.md).
- **S9:** [Servicios e impuestos](https://docs.gigstack.io/guides/services.md).
- **S10:** [Facturas: emisión, idempotencia, cancelación, complementos y errores](https://docs.gigstack.io/guides/invoices.md).
- **S11:** [Webhooks](https://docs.gigstack.io/guides/webhooks.md).
- **S12:** [Archivos de factura](https://docs.gigstack.io/reference/getInvoicesByIdFiles.md).
- **S13:** [Descarga masiva SAT](https://docs.gigstack.io/guides/descarga-masiva.md).
- **S14:** [Fundamentos y límites](https://docs.gigstack.io/guides/welcome.md).
- **S15:** [Carga fiscal segura](https://docs.gigstack.io/guides/fiscal-upload.md).
- **S16:** [Construir con agentes](https://docs.gigstack.io/agents.md).

### Prompt de arranque para el agente del cliente

> Lee esta guía y el OpenAPI actual de gigstack. Inspecciona mi integración existente sin cambiar producción. Identifica proveedor origen, país/emisor, flujos, automatizaciones y formatos. Devuelve un plan por fases, matriz de mapeo, preguntas pendientes y pruebas. Implementa primero la transformación y el adaptador en una rama con fixtures aprobados. Usa secretos del entorno, nunca del chat. No emitas/canceles CFDIs productivos, no importes históricos, no cobres/reembolses, no envíes mensajes ni borres datos sin mi aprobación específica. Concilia escrituras inciertas antes de reintentar. No reemitas históricos. Explica qué verificaste por contrato, qué probaste en sandbox y qué sigue sin verificar.
