{
  "info": {
    "_postman_id": "6a1f0f5a-3a2e-4d7f-9a6d-51f1c2c9a034",
    "name": "Cufy · Sandbox",
    "description": "Emite una factura de prueba y descarga su PDF sin escribir código.\n\n1. Crea tu cuenta en https://sandbox.cufy.co y copia una API key.\n2. Elige el ambiente **Cufy · Sandbox** arriba a la derecha y pega la key en `api_key`.\n3. Ejecuta en orden las peticiones de «1. Tu primera factura».\n\nLas facturas van al ambiente de **habilitación (pruebas)** de la DIAN: las respuestas son reales, los documentos **no tienen validez fiscal** y el emisor es un emisor de pruebas compartido. No pongas datos de personas reales.\n\nEl sandbox admite 50 documentos al día y 60 peticiones por minuto. Al pasarse, responde `429` con `Retry-After`: espera esos segundos y reintenta con la misma `Idempotency-Key`. Una petición rechazada por el límite no gasta número ni cupo.\n\nDocumentación: https://cufy.co",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-API-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{api_key}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "item": [
    {
      "name": "1. Tu primera factura",
      "description": "El recorrido completo: comprobar que el sandbox responde, mirar tu empresa de pruebas, buscar el rango de numeración, revisar la factura, enviarla, consultar qué respondió la DIAN y descargar el PDF. Ejecútalos en orden: cada uno guarda en el ambiente lo que el siguiente necesita.",
      "item": [
        {
          "name": "Comprobar que el sandbox responde",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('el sandbox responde', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "auth": {
              "type": "noauth"
            },
            "header": [],
            "url": {
              "raw": "{{base_url}}/health/ready",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "health",
                "ready"
              ]
            },
            "description": "`GET /health/ready` · Sondeo de disponibilidad\n\nLa única petición que no lleva API key. Responde `200` si el sandbox está en pie y `503` si está arrancando o sin base de datos."
          }
        },
        {
          "name": "Comprobar tu API key",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la API key sirve', () => pm.response.to.have.status(200));",
                  "pm.test('estás en habilitación (pruebas)', () => pm.expect(pm.response.json().environment).to.eql('qualification'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/capabilities",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "capabilities"
              ]
            },
            "description": "`GET /v1/capabilities` · Consultar perfiles y límites efectivos\n\nResponde `200` si tu API key sirve, y dice en qué ambiente estás y qué puede emitir tu cuenta. Un `401` con `INVALID_API_KEY` significa que la variable `api_key` del ambiente está vacía o mal pegada."
          }
        },
        {
          "name": "Ver tu empresa de pruebas",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la empresa existe', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/company",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "company"
              ]
            },
            "description": "`GET /v1/company` · Consultar empresa propia\n\nTu cuenta del sandbox ya tiene una empresa lista para facturar. No tienes que configurar resolución, rango ni certificado: el sandbox usa un emisor de pruebas compartido y tus facturas no tienen validez fiscal."
          }
        },
        {
          "name": "Buscar tu rango de numeración",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const range = pm.response.json().items.find((item) => item.document_type === 'invoice' && item.status === 'active');",
                  "pm.test('hay un rango de facturas activo', () => pm.expect(range, 'ningún rango de facturas activo').to.exist);",
                  "pm.environment.set('numbering_range_id', range.id);\nconsole.log('numbering_range_id =', pm.environment.get('numbering_range_id'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/numbering-ranges",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "numbering-ranges"
              ]
            },
            "description": "`GET /v1/numbering-ranges` · Listar rangos sin claves técnicas\n\nUna factura se numera con un rango autorizado. La lista trae un rango por tipo de documento, así que hay que buscar el de las facturas: esta petición lo guarda en `numbering_range_id`. Enviar una factura con el rango de una nota responde `422 NUMBERING_TYPE_MISMATCH`."
          }
        },
        {
          "name": "Revisar la factura antes de enviarla",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const bogota = new Date(Date.now() - 5 * 60 * 60 * 1000).toISOString().replace('Z', '-05:00');",
                  "pm.environment.set('issued_at', bogota);",
                  "pm.environment.set('external_reference', 'POSTMAN-' + Date.now());",
                  "pm.environment.set('idempotency_key', 'postman-' + Date.now());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la factura pasa las reglas', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotency_key}}",
                "description": "Un texto único por envío, de 8 a 128 caracteres. Reintentar con el mismo no duplica el documento."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"document_type\": \"invoice\",\n  \"external_reference\": \"{{external_reference}}\",\n  \"profile_id\": \"co-sale-cop-v1\",\n  \"numbering_range_id\": \"{{numbering_range_id}}\",\n  \"issued_at\": \"{{issued_at}}\",\n  \"currency\": \"COP\",\n  \"buyer\": {\n    \"party_type\": \"person\",\n    \"identification\": {\n      \"type_code\": \"13\",\n      \"number\": \"1000000000\"\n    },\n    \"name\": \"Comprador de ejemplo\",\n    \"email\": \"comprador@example.com\",\n    \"tax_registration\": {\n      \"responsibility_codes\": [\n        \"R-99-PN\"\n      ],\n      \"tax_scheme_codes\": [\n        \"01\"\n      ]\n    }\n  },\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Servicio de ejemplo, datos ficticios\",\n      \"product_identification\": {\n        \"scheme_code\": \"999\",\n        \"value\": \"DEMO-001\"\n      },\n      \"quantity\": \"1\",\n      \"unit_code\": \"94\",\n      \"unit_price\": \"10000.00\",\n      \"price_base_quantity\": \"1\",\n      \"net_amount\": \"10000.00\",\n      \"adjustments\": [],\n      \"taxes\": [\n        {\n          \"code\": \"01\",\n          \"rate\": \"19\",\n          \"taxable_amount\": \"10000.00\",\n          \"amount\": \"1900.00\"\n        }\n      ],\n      \"withholdings\": []\n    }\n  ],\n  \"adjustments\": [],\n  \"tax_totals\": [\n    {\n      \"code\": \"01\",\n      \"rate\": \"19\",\n      \"taxable_amount\": \"10000.00\",\n      \"amount\": \"1900.00\"\n    }\n  ],\n  \"withholding_totals\": [],\n  \"totals\": {\n    \"line_extension_amount\": \"10000.00\",\n    \"tax_exclusive_amount\": \"10000.00\",\n    \"tax_inclusive_amount\": \"11900.00\",\n    \"allowance_total_amount\": \"0.00\",\n    \"charge_total_amount\": \"0.00\",\n    \"prepaid_amount\": \"0.00\",\n    \"payable_rounding_amount\": \"0.00\",\n    \"payable_amount\": \"11900.00\"\n  },\n  \"payment\": {\n    \"terms\": \"cash\",\n    \"means_code\": \"10\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/invoice-validations",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "invoice-validations"
              ]
            },
            "description": "`POST /v1/invoice-validations` · Validar entrada sin emitir\n\nAplica las mismas reglas que el envío, pero no numera, no firma y no manda nada a la DIAN. Sirve para corregir una factura sin gastar un número ni un documento de tu cupo diario."
          }
        },
        {
          "name": "Enviar la factura",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const bogota = new Date(Date.now() - 5 * 60 * 60 * 1000).toISOString().replace('Z', '-05:00');",
                  "pm.environment.set('issued_at', bogota);",
                  "pm.environment.set('external_reference', 'POSTMAN-' + Date.now());",
                  "pm.environment.set('idempotency_key', 'postman-' + Date.now());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la factura quedó recibida', () => pm.response.to.have.status(202));",
                  "pm.environment.set('document_id', pm.response.json().document_id);\nconsole.log('document_id =', pm.environment.get('document_id'));",
                  "pm.environment.set('operation_id', pm.response.json().operation_id);\nconsole.log('operation_id =', pm.environment.get('operation_id'));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotency_key}}",
                "description": "Un texto único por envío, de 8 a 128 caracteres. Reintentar con el mismo no duplica el documento."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"document_type\": \"invoice\",\n  \"external_reference\": \"{{external_reference}}\",\n  \"profile_id\": \"co-sale-cop-v1\",\n  \"numbering_range_id\": \"{{numbering_range_id}}\",\n  \"issued_at\": \"{{issued_at}}\",\n  \"currency\": \"COP\",\n  \"buyer\": {\n    \"party_type\": \"person\",\n    \"identification\": {\n      \"type_code\": \"13\",\n      \"number\": \"1000000000\"\n    },\n    \"name\": \"Comprador de ejemplo\",\n    \"email\": \"comprador@example.com\",\n    \"tax_registration\": {\n      \"responsibility_codes\": [\n        \"R-99-PN\"\n      ],\n      \"tax_scheme_codes\": [\n        \"01\"\n      ]\n    }\n  },\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Servicio de ejemplo, datos ficticios\",\n      \"product_identification\": {\n        \"scheme_code\": \"999\",\n        \"value\": \"DEMO-001\"\n      },\n      \"quantity\": \"1\",\n      \"unit_code\": \"94\",\n      \"unit_price\": \"10000.00\",\n      \"price_base_quantity\": \"1\",\n      \"net_amount\": \"10000.00\",\n      \"adjustments\": [],\n      \"taxes\": [\n        {\n          \"code\": \"01\",\n          \"rate\": \"19\",\n          \"taxable_amount\": \"10000.00\",\n          \"amount\": \"1900.00\"\n        }\n      ],\n      \"withholdings\": []\n    }\n  ],\n  \"adjustments\": [],\n  \"tax_totals\": [\n    {\n      \"code\": \"01\",\n      \"rate\": \"19\",\n      \"taxable_amount\": \"10000.00\",\n      \"amount\": \"1900.00\"\n    }\n  ],\n  \"withholding_totals\": [],\n  \"totals\": {\n    \"line_extension_amount\": \"10000.00\",\n    \"tax_exclusive_amount\": \"10000.00\",\n    \"tax_inclusive_amount\": \"11900.00\",\n    \"allowance_total_amount\": \"0.00\",\n    \"charge_total_amount\": \"0.00\",\n    \"prepaid_amount\": \"0.00\",\n    \"payable_rounding_amount\": \"0.00\",\n    \"payable_amount\": \"11900.00\"\n  },\n  \"payment\": {\n    \"terms\": \"cash\",\n    \"means_code\": \"10\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/invoices",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "invoices"
              ]
            },
            "description": "`POST /v1/invoices` · Admitir Invoice\n\n`202` significa **recibida**, no aprobada: la factura se numera, se firma y viaja a la DIAN en segundo plano. Esta petición estrena una `Idempotency-Key`, que queda guardada para la siguiente."
          }
        },
        {
          "name": "Reenviar la misma factura",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('responde la factura de antes', () => pm.response.to.have.status(202));",
                  "pm.test('no se creó una factura nueva', () => pm.expect(pm.response.json().document_id).to.eql(pm.environment.get('document_id')));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotency_key}}",
                "description": "Un texto único por envío, de 8 a 128 caracteres. Reintentar con el mismo no duplica el documento."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"document_type\": \"invoice\",\n  \"external_reference\": \"{{external_reference}}\",\n  \"profile_id\": \"co-sale-cop-v1\",\n  \"numbering_range_id\": \"{{numbering_range_id}}\",\n  \"issued_at\": \"{{issued_at}}\",\n  \"currency\": \"COP\",\n  \"buyer\": {\n    \"party_type\": \"person\",\n    \"identification\": {\n      \"type_code\": \"13\",\n      \"number\": \"1000000000\"\n    },\n    \"name\": \"Comprador de ejemplo\",\n    \"email\": \"comprador@example.com\",\n    \"tax_registration\": {\n      \"responsibility_codes\": [\n        \"R-99-PN\"\n      ],\n      \"tax_scheme_codes\": [\n        \"01\"\n      ]\n    }\n  },\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Servicio de ejemplo, datos ficticios\",\n      \"product_identification\": {\n        \"scheme_code\": \"999\",\n        \"value\": \"DEMO-001\"\n      },\n      \"quantity\": \"1\",\n      \"unit_code\": \"94\",\n      \"unit_price\": \"10000.00\",\n      \"price_base_quantity\": \"1\",\n      \"net_amount\": \"10000.00\",\n      \"adjustments\": [],\n      \"taxes\": [\n        {\n          \"code\": \"01\",\n          \"rate\": \"19\",\n          \"taxable_amount\": \"10000.00\",\n          \"amount\": \"1900.00\"\n        }\n      ],\n      \"withholdings\": []\n    }\n  ],\n  \"adjustments\": [],\n  \"tax_totals\": [\n    {\n      \"code\": \"01\",\n      \"rate\": \"19\",\n      \"taxable_amount\": \"10000.00\",\n      \"amount\": \"1900.00\"\n    }\n  ],\n  \"withholding_totals\": [],\n  \"totals\": {\n    \"line_extension_amount\": \"10000.00\",\n    \"tax_exclusive_amount\": \"10000.00\",\n    \"tax_inclusive_amount\": \"11900.00\",\n    \"allowance_total_amount\": \"0.00\",\n    \"charge_total_amount\": \"0.00\",\n    \"prepaid_amount\": \"0.00\",\n    \"payable_rounding_amount\": \"0.00\",\n    \"payable_amount\": \"11900.00\"\n  },\n  \"payment\": {\n    \"terms\": \"cash\",\n    \"means_code\": \"10\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/invoices",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "invoices"
              ]
            },
            "description": "`POST /v1/invoices` · Admitir Invoice\n\nEl mismo cuerpo con la **misma** `Idempotency-Key`. No se crea una segunda factura: responde la primera, con su mismo `document_id`. Así se reintenta una petición que se cortó sin arriesgar un duplicado. Con una clave distinta sí habría una factura nueva."
          }
        },
        {
          "name": "Consultar qué respondió la DIAN",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const document = pm.response.json();",
                  "pm.test('la factura existe', () => pm.response.to.have.status(200));",
                  "console.log('processing_status =', document.processing_status, '| dian =', document.dian && document.dian.status);",
                  "const identity = document.fiscal_identity || {};",
                  "const cufe = identity.unique_code || (document.signature || {}).cufe;",
                  "if (cufe) {",
                  "    pm.environment.set('cufe', cufe);\n    console.log('cufe =', pm.environment.get('cufe'));",
                  "    console.log('número =', identity.prefix, identity.number);",
                  "}",
                  "pm.test('la DIAN ya respondió', () => pm.expect(document.processing_status).to.eql('completed'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/documents/{{document_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "documents",
                "{{document_id}}"
              ]
            },
            "description": "`GET /v1/documents/{id}` · Consultar estado actual del documento\n\nRepite esta petición cada pocos segundos hasta que `processing_status` diga `completed`. Entonces `dian.status` dice `validated` o `rejected`, `fiscal_identity` trae el prefijo y el número, y `signature.cufe` trae el CUFE. Si `dian.status` dice `unknown`, no vuelvas a enviar la factura: sigue consultando."
          }
        },
        {
          "name": "Ver los archivos de la factura",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const available = pm.response.json().items.filter((item) => item.status === 'available');",
                  "const pdf = available.find((item) => item.kind === 'pdf');",
                  "const xml = available.find((item) => item.kind === 'signed_xml');",
                  "pm.test('el PDF ya está disponible', () => pm.expect(pdf, 'ningún archivo pdf disponible todavía').to.exist);",
                  "if (pdf) {",
                  "    pm.environment.set('pdf_artifact_id', pdf.id);\n    console.log('pdf_artifact_id =', pm.environment.get('pdf_artifact_id'));",
                  "}",
                  "if (xml) {",
                  "    pm.environment.set('xml_artifact_id', xml.id);\n    console.log('xml_artifact_id =', pm.environment.get('xml_artifact_id'));",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/documents/{{document_id}}/artifacts",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "documents",
                "{{document_id}}",
                "artifacts"
              ]
            },
            "description": "`GET /v1/documents/{id}/artifacts` · Consultar archivos de una revisión\n\nEl PDF (`kind: pdf`), el XML firmado (`kind: signed_xml`) y la respuesta de la DIAN (`kind: dian_response`). Un archivo `pending` todavía se está generando: espera unos segundos y vuelve a consultar."
          }
        },
        {
          "name": "Descargar el PDF",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('el PDF se descargó', () => pm.response.to.have.status(200));",
                  "pm.test('es un PDF', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/pdf'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/artifacts/{{pdf_artifact_id}}/content",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "artifacts",
                "{{pdf_artifact_id}}",
                "content"
              ]
            },
            "description": "`GET /v1/artifacts/{id}/content` · Descargar documento autorizado\n\nDevuelve el archivo. En Postman, **Save response → Save to a file** lo baja a tu computador. El encabezado `ETag` trae su SHA-256: compáralo con el que traía la lista si quieres comprobar que llegó completo."
          }
        },
        {
          "name": "Descargar el XML firmado",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('el XML se descargó', () => pm.response.to.have.status(200));",
                  "pm.test('es un XML', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('xml'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/artifacts/{{xml_artifact_id}}/content",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "artifacts",
                "{{xml_artifact_id}}",
                "content"
              ]
            },
            "description": "`GET /v1/artifacts/{id}/content` · Descargar documento autorizado\n\nEl documento que la DIAN validó, tal como se firmó. Es el que conviene guardar: el PDF es su representación, no el documento fiscal."
          }
        }
      ]
    },
    {
      "name": "2. Consultar tus documentos",
      "description": "Buscar lo que ya enviaste y seguir el rastro de una factura.",
      "item": [
        {
          "name": "Listar tus facturas",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la lista responde', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/documents?limit=10",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "documents"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "10"
                }
              ]
            },
            "description": "`GET /v1/documents` · Buscar documentos propios\n\nTrae las más recientes primero. `limit` y `cursor` pasan a la página siguiente; el cursor viene en la respuesta anterior."
          }
        },
        {
          "name": "Ver el resultado de un envío",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la operación existe', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/operations/{{operation_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "operations",
                "{{operation_id}}"
              ]
            },
            "description": "`GET /v1/operations/{id}` · Consultar ejecución por operation_id\n\nCada envío deja una operación con su propio identificador. Sirve para saber qué pasó con una petición concreta, incluso si la repetiste."
          }
        },
        {
          "name": "Ver a quién se le envió la factura",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la lista responde', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/documents/{{document_id}}/deliveries",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "documents",
                "{{document_id}}",
                "deliveries"
              ]
            },
            "description": "`GET /v1/documents/{id}/deliveries` · Consultar historial de entrega\n\nEl envío al comprador se registra aparte de la validación de la DIAN: una factura validada puede tener una entrega todavía en curso."
          }
        }
      ]
    },
    {
      "name": "3. Webhooks",
      "description": "En vez de consultar una y otra vez, puedes pedir que te avisemos. Los destinos se crean desde el panel (https://sandbox.cufy.co); tu API key los puede leer, no cambiar.",
      "item": [
        {
          "name": "Ver tus destinos",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la lista responde', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/webhook-endpoints",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "webhook-endpoints"
              ]
            },
            "description": "`GET /v1/webhook-endpoints` · Listar destinos sin secretos\n\nLas direcciones a las que te avisamos. Si la lista está vacía, crea una desde el panel."
          }
        },
        {
          "name": "Ver los avisos generados",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la lista responde', () => pm.response.to.have.status(200));",
                  "const [event] = pm.response.json().items;",
                  "if (event) {",
                  "    pm.environment.set('webhook_event_id', event.event_id);\n    console.log('webhook_event_id =', pm.environment.get('webhook_event_id'));",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/webhook-events?limit=10",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "webhook-events"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "10"
                }
              ]
            },
            "description": "`GET /v1/webhook-events` · Consultar eventos\n\nCada cambio importante de un documento genera un evento. Esta petición guarda el más reciente en `webhook_event_id`."
          }
        },
        {
          "name": "Ver si tu receptor contestó",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('los intentos responden', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/webhook-events/{{webhook_event_id}}/attempts",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "webhook-events",
                "{{webhook_event_id}}",
                "attempts"
              ]
            },
            "description": "`GET /v1/webhook-events/{id}/attempts` · Consultar intentos\n\nCada intento con su código de respuesta. Si todos fallan, el problema está en tu receptor: revisa [Webhooks](https://cufy.co/docs/webhooks)."
          }
        }
      ]
    },
    {
      "name": "5. Clientes guardados",
      "description": "Guarda los datos fiscales de un cliente una vez y factura enviando solo su id (`customer_id`) en lugar del comprador completo. La factura copia los datos del cliente al recibirla: si después lo editas o lo archivas, esa factura no cambia. También los ves y editas en el panel, en Clientes (https://sandbox.cufy.co).",
      "item": [
        {
          "name": "Guardar un cliente",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.environment.set('customer_external_id', 'POSTMAN-CLIENTE-' + Date.now());",
                  "pm.environment.set('idempotency_key', 'postman-' + Date.now());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('el cliente quedó guardado', () => pm.response.to.have.status(201));",
                  "pm.environment.set('customer_id', pm.response.json().id);\nconsole.log('customer_id =', pm.environment.get('customer_id'));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotency_key}}",
                "description": "Un texto único por envío, de 8 a 128 caracteres. Reintentar con el mismo no duplica el documento."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_id\": \"{{customer_external_id}}\",\n  \"buyer\": {\n    \"party_type\": \"person\",\n    \"identification\": {\n      \"type_code\": \"13\",\n      \"number\": \"1000000000\"\n    },\n    \"name\": \"Comprador de ejemplo\",\n    \"email\": \"comprador@example.com\",\n    \"tax_registration\": {\n      \"responsibility_codes\": [\n        \"R-99-PN\"\n      ],\n      \"tax_scheme_codes\": [\n        \"01\"\n      ]\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/customers",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "customers"
              ]
            },
            "description": "`POST /v1/customers` · Guardar un cliente para reutilizarlo al emitir\n\nEl mismo comprador que llevaría una factura, con las mismas reglas. `external_id` es el código del cliente en tu ERP: no se repite entre tus clientes activos. Esta petición guarda el `id` en `customer_id`."
          }
        },
        {
          "name": "Buscar tus clientes",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la búsqueda responde', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/customers?q=comprador",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "customers"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "comprador"
                }
              ]
            },
            "description": "`GET /v1/customers` · Listar y buscar clientes guardados\n\nBusca por nombre, identificación, ciudad, correo o código del ERP, sin importar mayúsculas ni tildes. Trae primero los más recientes."
          }
        },
        {
          "name": "Ver el cliente",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('el cliente existe', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/customers/{{customer_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "customers",
                "{{customer_id}}"
              ]
            },
            "description": "`GET /v1/customers/{id}` · Consultar un cliente guardado\n\nEl encabezado `ETag` trae su versión: la necesitas en `If-Match` para editarlo o archivarlo."
          }
        },
        {
          "name": "Facturar con el cliente guardado",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const bogota = new Date(Date.now() - 5 * 60 * 60 * 1000).toISOString().replace('Z', '-05:00');",
                  "pm.environment.set('issued_at', bogota);",
                  "pm.environment.set('external_reference', 'POSTMAN-' + Date.now());",
                  "pm.environment.set('idempotency_key', 'postman-' + Date.now());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('la factura quedó recibida', () => pm.response.to.have.status(202));",
                  "pm.environment.set('document_id', pm.response.json().document_id);\nconsole.log('document_id =', pm.environment.get('document_id'));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotency_key}}",
                "description": "Un texto único por envío, de 8 a 128 caracteres. Reintentar con el mismo no duplica el documento."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"document_type\": \"invoice\",\n  \"external_reference\": \"{{external_reference}}\",\n  \"profile_id\": \"co-sale-cop-v1\",\n  \"numbering_range_id\": \"{{numbering_range_id}}\",\n  \"issued_at\": \"{{issued_at}}\",\n  \"currency\": \"COP\",\n  \"customer_id\": \"{{customer_id}}\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Servicio de ejemplo, datos ficticios\",\n      \"product_identification\": {\n        \"scheme_code\": \"999\",\n        \"value\": \"DEMO-001\"\n      },\n      \"quantity\": \"1\",\n      \"unit_code\": \"94\",\n      \"unit_price\": \"10000.00\",\n      \"price_base_quantity\": \"1\",\n      \"net_amount\": \"10000.00\",\n      \"adjustments\": [],\n      \"taxes\": [\n        {\n          \"code\": \"01\",\n          \"rate\": \"19\",\n          \"taxable_amount\": \"10000.00\",\n          \"amount\": \"1900.00\"\n        }\n      ],\n      \"withholdings\": []\n    }\n  ],\n  \"adjustments\": [],\n  \"tax_totals\": [\n    {\n      \"code\": \"01\",\n      \"rate\": \"19\",\n      \"taxable_amount\": \"10000.00\",\n      \"amount\": \"1900.00\"\n    }\n  ],\n  \"withholding_totals\": [],\n  \"totals\": {\n    \"line_extension_amount\": \"10000.00\",\n    \"tax_exclusive_amount\": \"10000.00\",\n    \"tax_inclusive_amount\": \"11900.00\",\n    \"allowance_total_amount\": \"0.00\",\n    \"charge_total_amount\": \"0.00\",\n    \"prepaid_amount\": \"0.00\",\n    \"payable_rounding_amount\": \"0.00\",\n    \"payable_amount\": \"11900.00\"\n  },\n  \"payment\": {\n    \"terms\": \"cash\",\n    \"means_code\": \"10\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/invoices",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "invoices"
              ]
            },
            "description": "`POST /v1/invoices` · Admitir Invoice\n\nLa factura de «Tu primera factura» con `customer_id` en lugar de `buyer`. Nunca los dos. Necesita `numbering_range_id`: ejecuta antes «Buscar tu rango de numeración». Guarda el `document_id` para consultar la respuesta de la DIAN con «Consultar qué respondió la DIAN»."
          }
        }
      ]
    },
    {
      "name": "4. Catálogos y estado",
      "description": "Los códigos que pide la DIAN y cómo va el servicio.",
      "item": [
        {
          "name": "Ver el estado del servicio",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('el estado responde', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/status",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "status"
              ]
            },
            "description": "`GET /v1/status` · Consultar salud de servicio y dependencias\n\nCómo está cada parte del servicio y si hay algún incidente abierto. Es lo primero que conviene mirar cuando algo que funcionaba deja de responder."
          }
        },
        {
          "name": "Ver qué catálogos hay",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('el catálogo responde', () => pm.response.to.have.status(200));",
                  "console.log('catálogos =', pm.response.json().items.map((item) => item.code).join(', '));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/catalogs/catalogs",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "catalogs",
                "catalogs"
              ]
            },
            "description": "`GET /v1/catalogs/{catalog}` · Consultar catálogo versionado\n\nEl catálogo de los catálogos. Cambia `catalogs` en la dirección por el código que quieras consultar —`identification_types`, `tax_codes`, `unit_codes`, `payment_means`…— y te devuelve sus valores vigentes. Los códigos que pide la DIAN salen de aquí, no de una tabla que tengas que copiar a mano."
          }
        }
      ]
    }
  ]
}
