{
  "info": {
    "_postman_id": "d28a0708-34b4-53cc-8a86-6857f8f14ce3",
    "name": "Dialer Core API (/v1)",
    "description": "Superficie /v1 de dd/core (fuente de verdad: lib/dialer/api/v1_router.ex; shapes en API.md).\n\nAuth: Bearer {{api_token}} a nivel colección (token de tenant; importa dialer-public.postman_environment.json y pega tu propio token en `api_token`).\n\nDiseñada para correrse COMPLETA y EN ORDEN (newman lo hace por defecto): la carpeta '03 · golden-flow' siembra las variables {{golden_*}} que consumen las carpetas de dominio. Requests sueltos contra ids no sembrados responden el 404 fail-closed del contrato.\n\nGate anti-drift: .ci/qa/check_postman_drift.py compara las rutas del router contra esta colección en ambas direcciones (rutas faltantes Y requests huérfanos).",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{api_token}}",
        "type": "string"
      }
    ]
  },
  "item": [
    {
      "name": "00 · smoke",
      "description": "Liveness sin auth (fuera de /v1; el gate anti-drift solo compara /v1).",
      "item": [
        {
          "name": "GET /healthz",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/healthz",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "healthz"
              ]
            },
            "description": "Liveness del control plane (sin bearer).",
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "pm.test(\"body ok\", function () { pm.expect(pm.response.text()).to.not.be.empty; });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "01 · identity",
      "description": "Tenant autenticado por el bearer (RLS; nunca se acepta tenant_id del cliente).",
      "item": [
        {
          "name": "GET /v1/me",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "me"
              ]
            },
            "description": "Tenant del token. Siembra {{tenant_id}} y {{tenant_name}} para el resto de la carpeta."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: tenant con id/name/tier\", function () {",
                  "  pm.expect(j.tenant).to.be.an(\"object\");",
                  "  pm.expect(j.tenant.id).to.be.a(\"string\");",
                  "  pm.expect(j.tenant).to.have.property(\"name\");",
                  "  pm.expect(j.tenant).to.have.property(\"tier\");",
                  "});",
                  "pm.collectionVariables.set(\"tenant_id\", j.tenant.id);",
                  "pm.collectionVariables.set(\"tenant_name\", j.tenant.name || \"localdev\");"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/tenants",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/tenants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tenants"
              ]
            },
            "description": "Lista de un elemento: el propio tenant (RLS)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: tenants[] con exactamente el propio tenant\", function () {",
                  "  pm.expect(j.tenants).to.be.an(\"array\").with.lengthOf(1);",
                  "  pm.expect(j.tenants[0].id).to.eql(pm.collectionVariables.get(\"tenant_id\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/tenants/:id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/tenants/{{tenant_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tenants",
                "{{tenant_id}}"
              ]
            },
            "description": "El propio tenant; cualquier otro id es 404 (nunca 403, sin fuga de existencia)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: tenant.id coincide\", function () {",
                  "  pm.expect(j.tenant.id).to.eql(pm.collectionVariables.get(\"tenant_id\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "PATCH /v1/tenant",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/tenant",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tenant"
              ]
            },
            "description": "Solo `name` es editable por el tenant (tier/seats/voice_ai son superficie admin). Idempotente: re-escribe el nombre actual.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"{{tenant_name}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: tenant.name persiste\", function () {",
                  "  pm.expect(j.tenant.name).to.eql(pm.collectionVariables.get(\"tenant_name\"));",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "02 · api-keys",
      "description": "Mint/revocación de llaves del tenant. El plaintext se muestra UNA vez y jamás se loguea aquí.",
      "item": [
        {
          "name": "GET /v1/api_keys",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/api_keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "api_keys"
              ]
            },
            "description": "Lista (hashes nunca expuestos)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: api_keys[]\", function () { pm.expect(j.api_keys).to.be.an(\"array\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/api_keys",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/api_keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "api_keys"
              ]
            },
            "description": "Mint. La respuesta trae `token` en claro UNA sola vez — este test NO lo imprime ni lo guarda.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"label\": \"postman-smoke-{{$timestamp}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: api_key.id + token presente (no se persiste)\", function () {",
                  "  pm.expect(j.api_key.id).to.be.a(\"string\");",
                  "  pm.expect(j.token).to.be.a(\"string\").and.to.not.be.empty;",
                  "});",
                  "pm.collectionVariables.set(\"api_key_id\", j.api_key.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "DELETE /v1/api_keys/:id",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/api_keys/{{api_key_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "api_keys",
                "{{api_key_id}}"
              ]
            },
            "description": "Revoca (nunca borra) la llave recién minteada."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: api_key revocada (revoked_at)\", function () {",
                  "  pm.expect(j.api_key.id).to.eql(pm.collectionVariables.get(\"api_key_id\"));",
                  "  pm.expect(j.api_key.revoked_at).to.not.be.null;",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "03 · golden-flow",
      "description": "Flujo encadenado tipo golden: crear campaña → importar leads → next-lead → checkin → start → dial manual → stats → stop → checkout. Siembra {{golden_*}} para las carpetas de dominio. Correr EN ORDEN (newman lo hace solo).",
      "item": [
        {
          "name": "1. crear campaña",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns"
              ]
            },
            "description": "Nace en `draft`. La ventana de campaña queda abierta; la gate legal de quiet-hours es aparte (por eso el pre-request elige timezone en ventana).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"postman-golden-{{$timestamp}}\",\n  \"default_timezone\": \"{{golden_tz}}\",\n  \"calling_window_start\": \"00:00:00\",\n  \"calling_window_end\": \"23:59:59\",\n  \"abandonment_threshold\": 0.03,\n  \"caller_ids\": [\n    \"+13125550199\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Timezone en ventana quiet-hours AHORA (mismo truco que tools/demo.sh pick_tz):",
                  "// la gate legal [08:00,21:00) evalúa hora local del lead; elegimos margen [9,20).",
                  "const zones = [",
                  "  { tz: \"America/New_York\", state: \"NY\", city: \"Buffalo\" },",
                  "  { tz: \"America/Chicago\", state: \"TX\", city: \"Dallas\" },",
                  "  { tz: \"America/Denver\", state: \"CO\", city: \"Denver\" },",
                  "  { tz: \"America/Phoenix\", state: \"AZ\", city: \"Phoenix\" },",
                  "  { tz: \"America/Los_Angeles\", state: \"CA\", city: \"Fresno\" },",
                  "  { tz: \"Pacific/Honolulu\", state: \"HI\", city: \"Hilo\" }",
                  "];",
                  "function localHour(tz) {",
                  "  try {",
                  "    return parseInt(new Intl.DateTimeFormat(\"en-US\", { timeZone: tz, hour: \"2-digit\", hour12: false }).format(new Date()), 10) % 24;",
                  "  } catch (e) { return null; }",
                  "}",
                  "const pick = zones.find(z => { const h = localHour(z.tz); return h !== null && h >= 9 && h < 20; }) || zones[1];",
                  "pm.collectionVariables.set(\"golden_tz\", pick.tz);",
                  "pm.collectionVariables.set(\"golden_state\", pick.state);",
                  "pm.collectionVariables.set(\"golden_city\", pick.city);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: campaign draft con id\", function () {",
                  "  pm.expect(j.campaign.id).to.be.a(\"string\");",
                  "  pm.expect(j.campaign.state).to.eql(\"draft\");",
                  "});",
                  "pm.collectionVariables.set(\"golden_campaign_id\", j.campaign.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "2. importar leads",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/debts/import",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "debts",
                "import"
              ]
            },
            "description": "Import bulk idempotente (upsert por external_ref / phone). 3 deudas asignadas a la campaña golden.",
            "body": {
              "mode": "raw",
              "raw": "{{golden_import_body}}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// 3 deudas frescas con refs únicos por corrida; contactos landline (sin gate TCPA-consent).",
                  "const ts = Date.now();",
                  "const tz = pm.collectionVariables.get(\"golden_tz\") || \"America/Chicago\";",
                  "const st = pm.collectionVariables.get(\"golden_state\") || \"TX\";",
                  "const city = pm.collectionVariables.get(\"golden_city\") || \"Dallas\";",
                  "const debts = [];",
                  "for (let i = 0; i < 3; i++) {",
                  "  debts.push({",
                  "    external_ref: `postman-${ts}-${i}`,",
                  "    consumer_ref: `postman-consumer-${ts}-${i}`,",
                  "    debt_type: \"other\",",
                  "    amount_cents: 125000 + i * 2500,",
                  "    currency: \"USD\",",
                  "    campaign_id: pm.collectionVariables.get(\"golden_campaign_id\"),",
                  "    contacts: [{",
                  "      phone_e164: \"+1312557\" + String((ts + i) % 10000).padStart(4, \"0\"),",
                  "      line_type: \"landline\",",
                  "      timezone: tz,",
                  "      us_state: st,",
                  "      city: city,",
                  "      is_primary: true",
                  "    }]",
                  "  });",
                  "}",
                  "pm.collectionVariables.set(\"golden_import_body\", JSON.stringify({ debts }, null, 2));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: 3/3 importadas, 0 fallidas\", function () {",
                  "  pm.expect(j.imported).to.eql(3);",
                  "  pm.expect(j.failed).to.be.an(\"array\").with.lengthOf(0);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "3. next-lead (preview)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{golden_campaign_id}}/next-lead",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{golden_campaign_id}}",
                "next-lead"
              ]
            },
            "description": "El SIGUIENTE lead marcable, sin marcarlo. Siembra {{golden_debt_id}} y {{golden_contact_phone}}."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: lead no nulo con debt_id + contact_phone\", function () {",
                  "  pm.expect(j.lead).to.be.an(\"object\").and.to.not.be.null;",
                  "  pm.expect(j.lead.debt_id).to.be.a(\"string\");",
                  "  pm.expect(j.lead.contact_phone).to.be.a(\"string\");",
                  "});",
                  "if (j.lead) {",
                  "  pm.collectionVariables.set(\"golden_debt_id\", j.lead.debt_id);",
                  "  pm.collectionVariables.set(\"golden_contact_phone\", j.lead.contact_phone);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "4. roster de agentes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/agents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents"
              ]
            },
            "description": "Captura el seat 1000 del seed del harness (Dialer.Release.seed()); si no está, usa el primero del roster."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: agents[] con al menos un seat (corre el seed si falla)\", function () {",
                  "  pm.expect(j.agents).to.be.an(\"array\").and.to.have.length.above(0);",
                  "});",
                  "const seat = (j.agents || []).find(a => a.sip_extension === \"1000\") || (j.agents || [])[0];",
                  "if (seat) {",
                  "  pm.collectionVariables.set(\"golden_agent_id\", seat.id);",
                  "  pm.collectionVariables.set(\"golden_agent_ext\", seat.sip_extension || \"1000\");",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "5. checkin del agente",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/checkin",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "checkin"
              ]
            },
            "description": "Seat online + bootstrap softphone (cred SIP efímera vía kamailio RPC; el password jamás se loguea aquí).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sip_extension\": \"{{golden_agent_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: agent presente (sip solo si hay SBC)\", function () {",
                  "  pm.expect(j.agent).to.be.an(\"object\");",
                  "  pm.expect(j.agent.id).to.be.a(\"string\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "6. start campaña",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{golden_campaign_id}}/start",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{golden_campaign_id}}",
                "start"
              ]
            },
            "description": "Persiste running + arranca el runner (gateway `default` → SIPp mock carrier del harness).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tick_ms\": 1000,\n  \"batch_size\": 2,\n  \"gateway\": \"default\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: campaign running\", function () {",
                  "  pm.expect(j.campaign.state).to.eql(\"running\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "7. dial manual",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{golden_campaign_id}}/dial",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{golden_campaign_id}}",
                "dial"
              ]
            },
            "description": "Dial iniciado por humano (preview/manual). La campaña de esta carpeta es `predictive` porque el cuerpo de `1. crear campaña` NO manda `campaign_type` y el default del schema es `predictive` — NO porque el API lo impida: `campaign_type` sí es campo aceptado en POST /v1/campaigns. Añadirlo a ese cuerpo cambiaría el perfil de compliance de la campaña (incluido si aplica el gate ArtificialVoice) y esta rama dejaría de valer. Con el cuerpo actual lo esperado contra este stack es 422 `not_human_initiated`; en una campaña manual/preview sería 200 {dialing|blocked}. Ambos se aceptan y se valida la shape de cada rama.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"debt_id\": \"{{golden_debt_id}}\",\n  \"agent_id\": \"{{golden_agent_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200 (manual/preview) o 422 (predictive: auto-marca)\", function () {",
                  "  pm.expect([200, 422]).to.include(pm.response.code);",
                  "});",
                  "const j = pm.response.json();",
                  "if (pm.response.code === 200) {",
                  "  pm.test(\"shape 200: status dialing|blocked\", function () {",
                  "    pm.expect([\"dialing\", \"blocked\"]).to.include(j.status);",
                  "  });",
                  "  if (j.status === \"dialing\") { pm.collectionVariables.set(\"golden_call_id\", j.call_id); }",
                  "} else {",
                  "  pm.test(\"shape 422: error.code unprocessable\", function () {",
                  "    pm.expect(j.error.code).to.eql(\"unprocessable\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "8. stats de campaña",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{golden_campaign_id}}/stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{golden_campaign_id}}",
                "stats"
              ]
            },
            "description": "Contadores vivos del runner + pacing."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: campaign_id + flag running\", function () {",
                  "  pm.expect(j.campaign_id).to.eql(pm.collectionVariables.get(\"golden_campaign_id\"));",
                  "  pm.expect(j).to.have.property(\"running\");",
                  "  pm.expect(j).to.have.property(\"stats\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "8b. compliance-preflight de campaña (dry-run)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{golden_campaign_id}}/compliance-preflight",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{golden_campaign_id}}",
                "compliance-preflight"
              ]
            },
            "description": "Dry-run del motor de compliance sobre los leads marcables de la campaña (core#924, MT-UX-11): un agregado por estado (reglas, elegibles, suprimidos por regla y motivo), sin marcar nada y sin devolver leads. Honesto sobre el gate estatal configurable: con `state_matrix_unknown_state_fails_strict` apagada (default) `unknown_state_policy` y `tenant_gates.state_matrix` dicen `federal_fallback`, nunca «protegido»."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: campaign_id + posture + totals + states\", function () {",
                  "  pm.expect(j.campaign_id).to.eql(pm.collectionVariables.get(\"golden_campaign_id\"));",
                  "  pm.expect(j.unknown_state_policy).to.be.oneOf([\"strict\", \"federal_fallback\"]);",
                  "  pm.expect(j.tenant_gates).to.have.property(\"state_matrix\");",
                  "  pm.expect(j.tenant_gates.state_matrix).to.be.oneOf([\"active\", \"federal_fallback\"]);",
                  "  pm.expect(j.totals).to.have.all.keys(\"leads\", \"evaluated\", \"eligible\", \"suppressed\", \"unknown_state\");",
                  "  pm.expect(j.truncated).to.be.a(\"boolean\");",
                  "  pm.expect(j.states).to.be.an(\"object\");",
                  "});",
                  "pm.test(\"honesto: la postura del gate estatal y la bandera cuentan lo mismo\", function () {",
                  "  const strict = j.unknown_state_policy === \"strict\";",
                  "  pm.expect(j.tenant_gates.state_matrix).to.eql(strict ? \"active\" : \"federal_fallback\");",
                  "});",
                  "pm.test(\"contadores cuadran: eligible + suppressed == evaluated, por estado y en total\", function () {",
                  "  pm.expect(j.totals.eligible + j.totals.suppressed).to.eql(j.totals.evaluated);",
                  "  for (const [state, b] of Object.entries(j.states)) {",
                  "    pm.expect(b.eligible + b.suppressed, state).to.eql(b.leads);",
                  "  }",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "9. stop campaña",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{golden_campaign_id}}/stop",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{golden_campaign_id}}",
                "stop"
              ]
            },
            "description": "Persiste completed + detiene el runner (in-flight termina solo)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: campaign completed\", function () {",
                  "  pm.expect(j.campaign.state).to.eql(\"completed\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "10. checkout del agente",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/checkout",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "checkout"
              ]
            },
            "description": "Seat offline (revoca la cred SIP). 409 si el seat sigue en llamada in-flight — se acepta.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sip_extension\": \"{{golden_agent_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200 (libre) o 409 (seat aún en llamada)\", function () {",
                  "  pm.expect([200, 409]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: agent presente\", function () { pm.expect(j.agent).to.be.an(\"object\"); });",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "04 · campaigns",
      "description": "CRUD + ciclo de vida completo + rewind sobre una campaña propia de esta carpeta.",
      "item": [
        {
          "name": "POST /v1/campaigns",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns"
              ]
            },
            "description": "Alta mínima (nace draft, defaults del schema).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"postman-crud-{{$timestamp}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: campaign draft\", function () {",
                  "  pm.expect(j.campaign.id).to.be.a(\"string\");",
                  "  pm.expect(j.campaign.state).to.eql(\"draft\");",
                  "});",
                  "pm.collectionVariables.set(\"campaign_id\", j.campaign.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/campaigns",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: campaigns[]\", function () {",
                  "  pm.expect(j.campaigns).to.be.an(\"array\").and.to.have.length.above(0);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/campaigns/:id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: campaign.id coincide\", function () {",
                  "  pm.expect(j.campaign.id).to.eql(pm.collectionVariables.get(\"campaign_id\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "PATCH /v1/campaigns/:id",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}"
              ]
            },
            "description": "Actualiza config (nunca state).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"record_calls\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: record_calls actualizado\", function () {",
                  "  pm.expect(j.campaign.record_calls).to.eql(false);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/campaigns/:id/survey-results",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}/survey-results",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}",
                "survey-results"
              ]
            },
            "description": "MT-UX-07 (core#916): distribución de teclas DTMF de las respuestas de encuesta/IVR de la campaña sobre [from, to] (30 días UTC por defecto), tenant-scoped (la campaña de otro tenant ⇒ 404). Sobre una campaña recién creada la distribución es vacía y honesta (total 0), no un error. `?format=csv` devuelve el mismo artefacto en CSV."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: campaign_id, ventana, total y distribution\", function () {",
                  "  pm.expect(j.campaign_id).to.eql(pm.collectionVariables.get(\"campaign_id\"));",
                  "  pm.expect(j.from).to.be.a(\"string\");",
                  "  pm.expect(j.to).to.be.a(\"string\");",
                  "  pm.expect(j.total).to.be.a(\"number\");",
                  "  pm.expect(j.distribution).to.be.an(\"array\");",
                  "});",
                  "pm.test(\"campaña recién creada: distribución vacía, no error\", function () {",
                  "  pm.expect(j.total).to.eql(0);",
                  "  pm.expect(j.distribution).to.eql([]);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/campaigns/:id/start",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}/start",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}",
                "start"
              ]
            },
            "description": "Tick de 60 s: campaña sin leads, el runner no tiene nada que marcar.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tick_ms\": 60000,\n  \"batch_size\": 1,\n  \"gateway\": \"default\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: running\", function () { pm.expect(j.campaign.state).to.eql(\"running\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/campaigns/:id/pause",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}/pause",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}",
                "pause"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: paused\", function () { pm.expect(j.campaign.state).to.eql(\"paused\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/campaigns/:id/resume",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}/resume",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}",
                "resume"
              ]
            },
            "description": "Resume ES start (paused → running): una semántica, dos ortografías."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: running otra vez\", function () { pm.expect(j.campaign.state).to.eql(\"running\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/campaigns/:id/rewind/preview",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}/rewind/preview?max_attempts=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}",
                "rewind",
                "preview"
              ],
              "query": [
                {
                  "key": "max_attempts",
                  "value": "1"
                }
              ]
            },
            "description": "Dry-run con filtros en query (forma dashboard). Nunca escribe audit."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: matching/excluded_by_compliance/truncated\", function () {",
                  "  pm.expect(j.matching).to.be.a(\"number\");",
                  "  pm.expect(j).to.have.property(\"excluded_by_compliance\");",
                  "  pm.expect(j).to.have.property(\"truncated\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/campaigns/:id/rewind/preview",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}/rewind/preview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}",
                "rewind",
                "preview"
              ]
            },
            "description": "El mismo dry-run con filtros en el body (shape del handoff B1).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"max_attempts\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: matching numérico\", function () { pm.expect(j.matching).to.be.a(\"number\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/campaigns/:id/rewind",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}/rewind",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}",
                "rewind"
              ]
            },
            "description": "Re-encola las deudas que matchean (campaña fresca: 0). CDR intacto (append-only).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"max_attempts\": 1,\n  \"keep_scheduled_callbacks\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: requeued + callbacks_cancelled\", function () {",
                  "  pm.expect(j.requeued).to.be.a(\"number\");",
                  "  pm.expect(j.callbacks_cancelled).to.be.a(\"number\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/campaigns/:id/stop",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}/stop",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}",
                "stop"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: completed\", function () { pm.expect(j.campaign.state).to.eql(\"completed\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/campaigns/:id/archive",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/campaigns/{{campaign_id}}/archive",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}",
                "archive"
              ]
            },
            "description": "Archivo terminal."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: archived\", function () { pm.expect(j.campaign.state).to.eql(\"archived\"); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "05 · debts",
      "description": "Deuda + contactos + CDR por deuda + Litigation Defense Packet. Usa {{golden_debt_id}} del golden-flow.",
      "item": [
        {
          "name": "GET /v1/debts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/debts?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "debts"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                }
              ]
            },
            "description": "Listado + búsqueda de cuentas (core#847, MT-CTI-12 FASE 1), keyset-paginado sobre la vista `debts_wide` (limit por defecto 50, máx 200). Filtros opcionales: campaign_id, state, min_amount_cents, max_amount_cents, dialable_by, phone, consumer_ref, external_ref, cursor. Es superficie de BARRIDO: el teléfono viaja SIEMPRE enmascarado y account_number no está en la proyección."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: debts[] + next_cursor\", function () {",
                  "  pm.expect(j.debts).to.be.an(\"array\");",
                  "  pm.expect(j).to.have.property(\"next_cursor\");",
                  "});",
                  "pm.test(\"el listado NO publica E.164 en claro ni account_number\", function () {",
                  "  (j.debts || []).forEach(function (d) {",
                  "    pm.expect(d).to.not.have.property(\"account_number\");",
                  "    pm.expect(d).to.have.property(\"primary_phone_masked\");",
                  "    if (d.primary_phone_masked) { pm.expect(d.primary_phone_masked).to.match(/^\\*\\*\\*/); }",
                  "  });",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/debts?phone=banana — E.164 estricto: 400",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/debts?phone=banana",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "debts"
              ],
              "query": [
                {
                  "key": "phone",
                  "value": "banana"
                }
              ]
            },
            "description": "El blind index es un HMAC de lo que se le dé: una cadena basura produce un token válido que no casa con nada, así que una búsqueda mal escrita contestaría «no hay» en vez de decir que estaba mal escrita. El E.164 se valida ANTES de bajar al contexto: 400, nunca página vacía."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"400 bad_request\", function () {",
                  "  pm.expect(pm.response.code).to.eql(400);",
                  "  pm.expect(pm.response.json().error.code).to.eql(\"bad_request\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/debts/:id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/debts/{{golden_debt_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "debts",
                "{{golden_debt_id}}"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: debt.id + contacts[]\", function () {",
                  "  pm.expect(j.debt.id).to.eql(pm.collectionVariables.get(\"golden_debt_id\"));",
                  "  pm.expect(j.debt.contacts).to.be.an(\"array\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/debts/:id/call_attempts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/debts/{{golden_debt_id}}/call_attempts?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "debts",
                "{{golden_debt_id}}",
                "call_attempts"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                }
              ]
            },
            "description": "CDR durable por deuda, keyset-paginado (limit por defecto 100, máx 1000)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: call_attempts[] + next_cursor\", function () {",
                  "  pm.expect(j.call_attempts).to.be.an(\"array\");",
                  "  pm.expect(j).to.have.property(\"next_cursor\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/debts/:id/defense-packet",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/debts/{{golden_debt_id}}/defense-packet",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "debts",
                "{{golden_debt_id}}",
                "defense-packet"
              ]
            },
            "description": "Bundle de evidencia sellado (sha256 en integrity.digest). Variante ZIP: ?format=zip."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: integrity.digest sha256\", function () {",
                  "  pm.expect(j.integrity).to.be.an(\"object\");",
                  "  pm.expect(j.integrity.digest).to.be.a(\"string\").and.to.have.length.above(16);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "06 · call-attempts",
      "description": "CDR filtrado/paginado + disposition tipada (corrección append-only sobre el original).",
      "item": [
        {
          "name": "GET /v1/call_attempts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/call_attempts?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "call_attempts"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                }
              ]
            },
            "description": "Captura un attempt ORIGINAL (corrects_id null) para la disposition si el stack ya marcó."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: call_attempts[] + next_cursor\", function () {",
                  "  pm.expect(j.call_attempts).to.be.an(\"array\");",
                  "  pm.expect(j).to.have.property(\"next_cursor\");",
                  "});",
                  "const orig = (j.call_attempts || []).find(a => !a.corrects_id);",
                  "if (orig) { pm.collectionVariables.set(\"attempt_id\", orig.id); }"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/call_attempts/:id/disposition",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/call_attempts/{{attempt_id}}/disposition",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "call_attempts",
                "{{attempt_id}}",
                "disposition"
              ]
            },
            "description": "Outcome tipado anclado al attempt original. 201 con CDR real; 404 si la corrida aún no tiene attempts ({{attempt_id}} vacío).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"disposition\": \"wrong_number\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 (con CDR) o 404 (sin attempts aún)\", function () {",
                  "  pm.expect([201, 404]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 201) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 201: call_attempt corrección + promise null\", function () {",
                  "    pm.expect(j.call_attempt).to.be.an(\"object\");",
                  "    pm.expect(j.call_attempt.corrects_id).to.not.be.null;",
                  "  });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            },
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// attempt_id queda vacío cuando la corrida aún no generó call attempts (CDR vacío).",
                  "// Con la variable vacía la URL tendría un segmento vacío y caería en el",
                  "// catch-all del router ('no such route') SIN ejercitar la ruta real:",
                  "// sembramos un UUID aleatorio para que el 404 sea el del endpoint.",
                  "if (!pm.collectionVariables.get(\"attempt_id\")) {",
                  "  pm.collectionVariables.set(\"attempt_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "07 · callbacks",
      "description": "Citas de re-marcado acordadas. Usa la deuda/el teléfono del golden-flow (teléfono desconocido → 422).",
      "item": [
        {
          "name": "POST /v1/callbacks (para complete)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/callbacks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "callbacks"
              ]
            },
            "description": "Redial acordado con el consumidor (regla de aviso mínimo: 10 min; agendamos +30 min).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"debt_id\": \"{{golden_debt_id}}\",\n  \"contact_phone\": \"{{golden_contact_phone}}\",\n  \"scheduled_at\": \"{{callback_when}}\",\n  \"note\": \"postman: consumer pidió el martes\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.collectionVariables.set(\"callback_when\", new Date(Date.now() + 30 * 60 * 1000).toISOString());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: callback pending\", function () {",
                  "  pm.expect(j.callback.id).to.be.a(\"string\");",
                  "  pm.expect(j.callback.status).to.eql(\"pending\");",
                  "});",
                  "pm.collectionVariables.set(\"callback_id_complete\", j.callback.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/callbacks",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/callbacks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "callbacks"
              ]
            },
            "description": "Vista cola (status pending por defecto)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: callbacks[] con el recién creado\", function () {",
                  "  pm.expect(j.callbacks).to.be.an(\"array\").and.to.have.length.above(0);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/callbacks?due=now",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/callbacks?due=now",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "callbacks"
              ],
              "query": [
                {
                  "key": "due",
                  "value": "now"
                }
              ]
            },
            "description": "Feed de vencidos para el runner (pending Y scheduled_at <= now): el de +30 min NO debe salir."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: callbacks[] (feed due)\", function () { pm.expect(j.callbacks).to.be.an(\"array\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/callbacks/:id/complete",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/callbacks/{{callback_id_complete}}/complete",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "callbacks",
                "{{callback_id_complete}}",
                "complete"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: done\", function () { pm.expect(j.callback.status).to.eql(\"done\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/callbacks (para cancel)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/callbacks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "callbacks"
              ]
            },
            "description": "Redial acordado con el consumidor (regla de aviso mínimo: 10 min; agendamos +45 min).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"debt_id\": \"{{golden_debt_id}}\",\n  \"contact_phone\": \"{{golden_contact_phone}}\",\n  \"scheduled_at\": \"{{callback_when}}\",\n  \"note\": \"postman: se cancelará\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.collectionVariables.set(\"callback_when\", new Date(Date.now() + 45 * 60 * 1000).toISOString());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: callback pending\", function () {",
                  "  pm.expect(j.callback.id).to.be.a(\"string\");",
                  "  pm.expect(j.callback.status).to.eql(\"pending\");",
                  "});",
                  "pm.collectionVariables.set(\"callback_id_cancel\", j.callback.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/callbacks/:id/cancel",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/callbacks/{{callback_id_cancel}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "callbacks",
                "{{callback_id_cancel}}",
                "cancel"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: cancelled\", function () { pm.expect(j.callback.status).to.eql(\"cancelled\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/callbacks (para miss)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/callbacks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "callbacks"
              ]
            },
            "description": "Redial acordado con el consumidor (regla de aviso mínimo: 10 min; agendamos +60 min).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"debt_id\": \"{{golden_debt_id}}\",\n  \"contact_phone\": \"{{golden_contact_phone}}\",\n  \"scheduled_at\": \"{{callback_when}}\",\n  \"note\": \"postman: se marcará missed\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.collectionVariables.set(\"callback_when\", new Date(Date.now() + 60 * 60 * 1000).toISOString());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: callback pending\", function () {",
                  "  pm.expect(j.callback.id).to.be.a(\"string\");",
                  "  pm.expect(j.callback.status).to.eql(\"pending\");",
                  "});",
                  "pm.collectionVariables.set(\"callback_id_miss\", j.callback.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/callbacks/:id/miss",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/callbacks/{{callback_id_miss}}/miss",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "callbacks",
                "{{callback_id_miss}}",
                "miss"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: missed\", function () { pm.expect(j.callback.status).to.eql(\"missed\"); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "08 · promises",
      "description": "Pipeline PTP. Las promesas nacen vía POST /call_attempts/:id/disposition (promise_to_pay), no por POST directo.",
      "item": [
        {
          "name": "GET /v1/promises",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/promises?status=pending&limit=5",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "promises"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "pending"
                },
                {
                  "key": "limit",
                  "value": "5"
                }
              ]
            },
            "description": "Captura una promesa pending si el pipeline tiene (p. ej. tras demo-check)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: promises[]\", function () { pm.expect(j.promises).to.be.an(\"array\"); });",
                  "if (j.promises.length > 0) { pm.collectionVariables.set(\"promise_id\", j.promises[0].id); }"
                ]
              }
            }
          ]
        },
        {
          "name": "PATCH /v1/promises/:id",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/promises/{{promise_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "promises",
                "{{promise_id}}"
              ]
            },
            "description": "Transición pending → kept. 404 (real del endpoint, id aleatorio sembrado en pre-request) si esta corrida no tiene promesas pending.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": \"kept\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200 (había pending) o 404 (pipeline vacío)\", function () {",
                  "  pm.expect([200, 404]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: promise kept\", function () { pm.expect(j.promise.status).to.eql(\"kept\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            },
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// promise_id queda vacío cuando el pipeline no tiene promesas pending en esta corrida.",
                  "// Con la variable vacía la URL tendría un segmento vacío y caería en el",
                  "// catch-all del router ('no such route') SIN ejercitar la ruta real:",
                  "// sembramos un UUID aleatorio para que el 404 sea el del endpoint.",
                  "if (!pm.collectionVariables.get(\"promise_id\")) {",
                  "  pm.collectionVariables.set(\"promise_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "09 · agents",
      "description": "Roster + presencia + checkin/checkout de un seat propio (extensión única por corrida).",
      "item": [
        {
          "name": "GET /v1/agents",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/agents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: agents[]\", function () { pm.expect(j.agents).to.be.an(\"array\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/agents",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents"
              ]
            },
            "description": "Alta de seat (name + email requeridos; sip_extension única por tenant → se genera por corrida).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Postman Seat\",\n  \"email\": \"postman+{{$timestamp}}@example.test\",\n  \"role\": \"agent\",\n  \"sip_extension\": \"{{postman_agent_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const ext = \"9\" + String(Date.now()).slice(-6);",
                  "pm.collectionVariables.set(\"postman_agent_ext\", ext);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: agent.id + extensión generada\", function () {",
                  "  pm.expect(j.agent.id).to.be.a(\"string\");",
                  "  pm.expect(j.agent.sip_extension).to.eql(pm.collectionVariables.get(\"postman_agent_ext\"));",
                  "});",
                  "pm.collectionVariables.set(\"postman_agent_id\", j.agent.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/agents/:id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/{{postman_agent_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "{{postman_agent_id}}"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: agent.id coincide\", function () {",
                  "  pm.expect(j.agent.id).to.eql(pm.collectionVariables.get(\"postman_agent_id\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/agents/checkin",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/checkin",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "checkin"
              ]
            },
            "description": "Seat online. Con SBC configurado además provisiona cred SIP efímera (jamás se imprime).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sip_extension\": \"{{postman_agent_ext}}\",\n  \"device_mode\": \"external\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: agent online\", function () { pm.expect(j.agent).to.be.an(\"object\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/agents/:id/pause",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/{{postman_agent_id}}/pause",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "{{postman_agent_id}}",
                "pause"
              ]
            },
            "description": "Saca el asiento de la rotación con un motivo del conjunto cerrado (MT-ROAD-R11 FASE 2a, macro-tasks#524). Tenant scope; un ddw_ es 403 por la allowlist.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"lunch\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: agent.presence == paused (leída del asiento)\", function () {",
                  "  pm.expect(j.agent.id).to.eql(pm.collectionVariables.get(\"postman_agent_id\"));",
                  "  pm.expect(j.agent.presence).to.eql(\"paused\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/agents/:id/pause (motivo fuera del conjunto -> 400)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/{{postman_agent_id}}/pause",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "{{postman_agent_id}}",
                "pause"
              ]
            },
            "description": "Fail-closed: un motivo fuera de {manual, break, lunch, training, meeting} es 400 y el asiento no se toca.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"coffee\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", function () { pm.response.to.have.status(400); });",
                  "pm.test(\"shape: error nombra el conjunto\", function () {",
                  "  pm.expect(pm.response.json().error.message).to.include(\"reason must be one of\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/agents/:id/resume",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/{{postman_agent_id}}/resume",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "{{postman_agent_id}}",
                "resume"
              ]
            },
            "description": "Devuelve el asiento en pausa a la rotación, al final de la cola de reserva."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: agent.presence == available\", function () {",
                  "  pm.expect(j.agent.presence).to.eql(\"available\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/agents/:id/wrap-up (sin ACW en curso -> 409)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/{{postman_agent_id}}/wrap-up",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "{{postman_agent_id}}",
                "wrap-up"
              ]
            },
            "description": "Cierra el trabajo posterior a la llamada. El asiento del golden-flow está available y el tope del tenant es el default (0: sin ACW), así que no hay ACW que cerrar: 409 fail-closed. El camino 200 exige una llamada contestada y colgada con tope > 0, que newman no fabrica; lo cubre test/db/agent_wrap_up_api_test.exs de core."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 409\", function () { pm.response.to.have.status(409); });",
                  "pm.test(\"shape: conflict, no catch-all\", function () {",
                  "  const j = pm.response.json();",
                  "  pm.expect(j.error.code).to.eql(\"conflict\");",
                  "  pm.expect(j.error.message).to.include(\"after-call work\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/agents/checkout",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/checkout",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "checkout"
              ]
            },
            "description": "Seat offline (revoca la cred SIP en el SBC).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sip_extension\": \"{{postman_agent_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: agent presente\", function () { pm.expect(j.agent).to.be.an(\"object\"); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "10 · supervision",
      "description": "Audio del supervisor sobre una llamada del CDR del tenant. Determinista solo con una llamada viva ({{golden_call_id}} se siembra si el dial manual devolvió `dialing`); sin ella el contrato responde 404. Desde core#930 (ADR #116 de core) `supervisor_ext` debe ser un agente con rol `supervisor|admin`: la carpeta siembra su propio seat supervisor ({{postman_supervisor_ext}}) y cierra con la request negativa — la extensión del seat `agent` de la carpeta 09 ⇒ exactamente 403 `role_forbidden`. El gate se demuestra, no se tolera.",
      "item": [
        {
          "name": "POST /v1/agents (seat con rol supervisor para esta carpeta)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents"
              ]
            },
            "description": "Siembra el seat que las 4 requests de supervisión nombran en `supervisor_ext`. El seed de core (agente 1000) nace con rol `agent` y core#930 lo rechaza con 403; el rol se fija aquí, en la propia colección, sin tocar el seed.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Postman Supervisor\",\n  \"email\": \"postman-supervisor+{{$timestamp}}@example.test\",\n  \"role\": \"supervisor\",\n  \"sip_extension\": \"{{postman_supervisor_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Prefijo 8: no colisiona con el seat agent de la carpeta 09 (prefijo 9) ni con el 1000 del seed.",
                  "const ext = \"8\" + String(Date.now()).slice(-6);",
                  "pm.collectionVariables.set(\"postman_supervisor_ext\", ext);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: agent.role == supervisor (lo que exige el gate de core#930)\", function () {",
                  "  pm.expect(j.agent.id).to.be.a(\"string\");",
                  "  pm.expect(j.agent.role).to.eql(\"supervisor\");",
                  "  pm.expect(j.agent.sip_extension).to.eql(pm.collectionVariables.get(\"postman_supervisor_ext\"));",
                  "});",
                  "pm.collectionVariables.set(\"postman_supervisor_id\", j.agent.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/supervision/calls/:call_id/listen",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/supervision/calls/{{golden_call_id}}/listen",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "supervision",
                "calls",
                "{{golden_call_id}}",
                "listen"
              ]
            },
            "description": "Supervisión `listen` (evidence-first: audit row ANTES del comando ESL). Con llamada viva → 200; sin call_id/llamada → 404; switch rechaza → 502; audit/ESL caído → 503. `supervisor_ext` es el seat supervisor sembrado arriba: con rol `agent` sería 403 `role_forbidden` (core#930).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supervisor_ext\": \"{{postman_supervisor_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|502|503 (según haya llamada viva)\", function () {",
                  "  pm.expect([200, 404, 502, 503]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: supervision presente\", function () { pm.expect(j.supervision).to.be.an(\"object\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/supervision/calls/:call_id/whisper",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/supervision/calls/{{golden_call_id}}/whisper",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "supervision",
                "calls",
                "{{golden_call_id}}",
                "whisper"
              ]
            },
            "description": "Supervisión `whisper` (evidence-first: audit row ANTES del comando ESL). Con llamada viva → 200; sin call_id/llamada → 404; switch rechaza → 502; audit/ESL caído → 503. `supervisor_ext` es el seat supervisor sembrado arriba: con rol `agent` sería 403 `role_forbidden` (core#930).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supervisor_ext\": \"{{postman_supervisor_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|502|503 (según haya llamada viva)\", function () {",
                  "  pm.expect([200, 404, 502, 503]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: supervision presente\", function () { pm.expect(j.supervision).to.be.an(\"object\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/supervision/calls/:call_id/barge",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/supervision/calls/{{golden_call_id}}/barge",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "supervision",
                "calls",
                "{{golden_call_id}}",
                "barge"
              ]
            },
            "description": "Supervisión `barge` (evidence-first: audit row ANTES del comando ESL). Con llamada viva → 200; sin call_id/llamada → 404; switch rechaza → 502; audit/ESL caído → 503. `supervisor_ext` es el seat supervisor sembrado arriba: con rol `agent` sería 403 `role_forbidden` (core#930).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supervisor_ext\": \"{{postman_supervisor_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|502|503 (según haya llamada viva)\", function () {",
                  "  pm.expect([200, 404, 502, 503]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: supervision presente\", function () { pm.expect(j.supervision).to.be.an(\"object\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/supervision/calls/:call_id/takeover",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/supervision/calls/{{golden_call_id}}/takeover",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "supervision",
                "calls",
                "{{golden_call_id}}",
                "takeover"
              ]
            },
            "description": "Supervisión `takeover` (evidence-first: audit row ANTES del comando ESL). Con llamada viva → 200; sin call_id/llamada → 404; switch rechaza → 502; audit/ESL caído → 503. `supervisor_ext` es el seat supervisor sembrado arriba: con rol `agent` sería 403 `role_forbidden` (core#930).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supervisor_ext\": \"{{postman_supervisor_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|502|503 (según haya llamada viva)\", function () {",
                  "  pm.expect([200, 404, 502, 503]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: supervision presente\", function () { pm.expect(j.supervision).to.be.an(\"object\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/supervision/calls/:call_id/listen (ext con rol agent -> 403 role_forbidden)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/supervision/calls/{{golden_call_id}}/listen",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "supervision",
                "calls",
                "{{golden_call_id}}",
                "listen"
              ]
            },
            "description": "Negativa del gate de rol (core#930, ADR #116 de core): `supervisor_ext` es el seat de la carpeta 09, creado con `role: agent`. Exactamente 403 y `error.code == role_forbidden`, haya o no llamada viva: el WHO va antes que el lookup de la llamada (sin oráculo 404-vs-403). La evidencia queda como `supervision_actions.result = refused`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supervisor_ext\": \"{{postman_agent_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 403 exacto (gate de rol antes que la llamada)\", function () { pm.response.to.have.status(403); });",
                  "pm.test(\"error.code == role_forbidden\", function () {",
                  "  const e = pm.response.json().error;",
                  "  pm.expect(e.code).to.eql(\"role_forbidden\");",
                  "  pm.expect(e.message).to.include(\"supervisor or admin\");",
                  "});"
                ]
              }
            }
          ]
        }
      ],
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// golden_call_id queda vacío cuando el dial del golden-flow tomó la rama 422 (la campaña del golden-flow no manda campaign_type ⇒ predictive; el API sí acepta el campo).",
              "// Con la variable vacía la URL tendría un segmento vacío y caería en el",
              "// catch-all del router ('no such route') SIN ejercitar la ruta real:",
              "// sembramos un UUID aleatorio para que el 404 sea el del endpoint.",
              "if (!pm.collectionVariables.get(\"golden_call_id\")) {",
              "  pm.collectionVariables.set(\"golden_call_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
              "}"
            ]
          }
        }
      ]
    },
    {
      "name": "11 · stats",
      "description": "Contadores operativos + evidencia durable de bloqueos + metering voice-AI.",
      "item": [
        {
          "name": "GET /v1/stats/summary",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/stats/summary",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "stats",
                "summary"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: contadores del día\", function () {",
                  "  pm.expect(j.calls_today).to.be.a(\"number\");",
                  "  pm.expect(j.agents_online).to.be.a(\"number\");",
                  "  pm.expect(j).to.have.property(\"campaigns_running\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/stats/promises",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/stats/promises",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "stats",
                "promises"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: pipeline PTP (open)\", function () { pm.expect(j).to.have.property(\"open\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/stats/penetration",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/stats/penetration?campaign_ids={{golden_campaign_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "stats",
                "penetration"
              ],
              "query": [
                {
                  "key": "campaign_ids",
                  "value": "{{golden_campaign_id}}"
                }
              ]
            },
            "description": "Penetración a nivel persona de la campaña golden."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: totals con unique_accounts\", function () {",
                  "  pm.expect(j.totals).to.be.an(\"object\");",
                  "  pm.expect(j.totals.unique_accounts).to.be.a(\"number\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/stats/blocked",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/stats/blocked?campaign_id={{golden_campaign_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "stats",
                "blocked"
              ],
              "query": [
                {
                  "key": "campaign_id",
                  "value": "{{golden_campaign_id}}"
                }
              ]
            },
            "description": "Rollup durable de dials REHUSADOS por gate (gate_blocks append-only, ADR #8)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: total + by_gate\", function () {",
                  "  pm.expect(j.total).to.be.a(\"number\");",
                  "  pm.expect(j.by_gate).to.be.an(\"object\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/stats/capacity",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/stats/capacity",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "stats",
                "capacity"
              ]
            },
            "description": "Techos de capacidad admin (max_cps, max_concurrent_channels; null = sin techo) + uso en vivo leido del capacity gate. limits: null = los techos no se pudieron leer y nunca se cachearon (la condicion fail-closed del gate), reportado con honestidad — nunca fabricado como \"ilimitado\". (ADR #12)"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: techos + uso en vivo\", function () {",
                  "  pm.expect(j).to.have.property(\"limits\");",
                  "  pm.expect(j.channels_in_use).to.be.a(\"number\");",
                  "  pm.expect(j.dials_last_second).to.be.a(\"number\");",
                  "  if (j.limits !== null) {",
                  "    pm.expect(j.limits).to.have.property(\"max_cps\");",
                  "    pm.expect(j.limits).to.have.property(\"max_concurrent_channels\");",
                  "  }",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/stats/ai-usage",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/stats/ai-usage",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "stats",
                "ai-usage"
              ]
            },
            "description": "Metering de minutos voice-AI (feed ai_usage exactly-once). CSV vía ?format=csv."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: ventana from/to\", function () {",
                  "  pm.expect(j).to.have.property(\"from\");",
                  "  pm.expect(j).to.have.property(\"to\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/stats/sms-usage",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/stats/sms-usage",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "stats",
                "sms-usage"
              ]
            },
            "description": "Metering de segmentos SMS (ledger unificado billable_events, filas sms_segment con snapshot de precio por región). CSV vía ?format=csv."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: ventana from/to + totales del ledger\", function () {",
                  "  pm.expect(j).to.have.property(\"from\");",
                  "  pm.expect(j).to.have.property(\"to\");",
                  "  pm.expect(j).to.have.property(\"messages\");",
                  "  pm.expect(j).to.have.property(\"segments\");",
                  "  pm.expect(j).to.have.property(\"amount_usd\");",
                  "  pm.expect(j.daily).to.be.an(\"array\");",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "12 · billing",
      "description": "La lectura \"qué facturo\": seats por tier + overage de DIDs + uso voice-AI.",
      "item": [
        {
          "name": "GET /v1/billing/account",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/billing/account",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "billing",
                "account"
              ]
            },
            "description": "Saldo, techo en vigor, consumo contra el techo y modo de enforcement, en un solo objeto. `balance` y `cap` son nulables de forma independiente: un tenant con billing apagado responde 200 con `billing_enabled: false` y dinero nulo (no 404), y `cap` es nulo además siempre que el carril esté inerte (plan no activo, sin wallet, sin funding, o sin grant todavía)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: identidad y modo\", function () {",
                  "    pm.expect(j.tenant_id).to.be.a(\"string\");",
                  "    pm.expect(j.currency).to.be.a(\"string\");",
                  "    pm.expect(j.billing_enabled).to.be.a(\"boolean\");",
                  "    pm.expect(j.alert_thresholds).to.be.an(\"array\");",
                  "    // enforcement_mode es nulable por contrato: el router lo sirve como",
                  "    // `plan && plan.enforcement_mode`, asi que un tenant SIN plan responde",
                  "    // 200 con billing_enabled:false y este campo en null.",
                  "    if (j.enforcement_mode !== null) {",
                  "        pm.expect(j.enforcement_mode).to.be.a(\"string\");",
                  "    }",
                  "});",
                  "// balance y cap son nulables por contrato (billing apagado / carril inerte):",
                  "// se afirma la forma SOLO cuando vienen, para no convertir un 200 valido en rojo.",
                  "pm.test(\"shape: balance nulo o montos en string\", function () {",
                  "    if (j.balance === null) return;",
                  "    pm.expect(j.balance.available_usd).to.be.a(\"string\");",
                  "    pm.expect(j.balance.cash_usd).to.be.a(\"string\");",
                  "});",
                  "pm.test(\"shape: cap nulo o techo con consumo\", function () {",
                  "    if (j.cap === null) return;",
                  "    pm.expect(j.cap.limit_usd).to.be.a(\"string\");",
                  "    pm.expect(j.cap.spent_usd).to.be.a(\"string\");",
                  "    // used_pct es nulable DENTRO de un cap presente: `used_pct/2` es un",
                  "    // `if limit > 0` sin `else`, o sea null cuando no hay techo positivo.",
                  "    if (j.cap.used_pct !== null) {",
                  "        pm.expect(j.cap.used_pct).to.be.a(\"string\");",
                  "    }",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/billing/alerts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/billing/alerts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "billing",
                "alerts"
              ]
            },
            "description": "Los cruces de umbral registrados en el epoch de facturación vigente, más nuevos primero. Cada fila se sirve TAL CUAL se grabó: `spent_usd`, `limit_usd` y `enforcement_mode` son el snapshot AL cruce, no un recálculo contra el saldo de hoy. `period_key` es el mismo epoch que `cap.period_key` de `GET /v1/billing/account`, y es nulo cuando el carril no tiene epoch — en cuyo caso `alerts` viene vacío. Sin plan responde 200 con lista vacía, nunca 404."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: identidad y epoch\", function () {",
                  "    pm.expect(j.tenant_id).to.be.a(\"string\");",
                  "    pm.expect(j.alerts).to.be.an(\"array\");",
                  "    // period_key es nulable por contrato: null cuando el carril no",
                  "    // tiene epoch (sin plan activo, sin wallet, sin funding, o sin",
                  "    // grant todavia). Se afirma la forma SOLO cuando viene, para no",
                  "    // convertir un 200 valido en rojo.",
                  "    if (j.period_key !== null) {",
                  "        pm.expect(j.period_key).to.be.a(\"string\");",
                  "    }",
                  "});",
                  "// La lista puede estar vacia legitimamente: un epoch recien abierto",
                  "// re-arma los umbrales, asi que \"nada cruzado AUN\" no es un fallo.",
                  "pm.test(\"shape: cada cruce trae su snapshot entero\", function () {",
                  "    j.alerts.forEach(function (a) {",
                  "        // El contrato dice integer y enum, no number y string: con los",
                  "        // asertos laxos pasaban 90.5 y \"bogus_scheme\" (it#185).",
                  "        pm.expect(Number.isInteger(a.threshold_pct), \"threshold_pct entero\").to.be.true;",
                  "        pm.expect(a.scheme).to.be.oneOf([\"subscription\", \"prepaid\", \"postpaid\"]);",
                  "        pm.expect(a.enforcement_mode).to.be.oneOf([\"graduated\", \"hard_block\"]);",
                  "        pm.expect(a.spent_usd).to.be.a(\"string\");",
                  "        pm.expect(a.limit_usd).to.be.a(\"string\");",
                  "        pm.expect(a.crossed_at).to.be.a(\"string\");",
                  "    });",
                  "});",
                  "pm.test(\"orden: mas nuevos primero\", function () {",
                  "    // Date.parse trunca a MILISEGUNDOS y crossed_at es utc_datetime_usec:",
                  "    // dos cruces del mismo ms en orden invertido pasaban el aserto (it#185).",
                  "    // Se compara el par (instante, fraccion), con la fraccion a 6 digitos.",
                  "    const t = j.alerts.map(function (a) {",
                  "        const m = /^(.+?)(?:\\.(\\d+))?Z$/.exec(a.crossed_at);",
                  "        const base = m ? m[1] : String(a.crossed_at).replace(/Z$/, \"\");",
                  "        const frac = (m && m[2] ? m[2] : \"\").padEnd(6, \"0\").slice(0, 6);",
                  "        return { s: Date.parse(base + \"Z\"), f: Number(frac) };",
                  "    });",
                  "    for (let i = 1; i < t.length; i++) {",
                  "        const prev = t[i - 1], cur = t[i];",
                  "        const ordenado = prev.s > cur.s || (prev.s === cur.s && prev.f >= cur.f);",
                  "        pm.expect(ordenado, \"cruce \" + (i - 1) + \" no es mas nuevo que \" + i).to.be.true;",
                  "    }",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/billing/summary",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/billing/summary",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "billing",
                "summary"
              ]
            },
            "description": "Line items facturables del período. CSV vía ?format=csv.\n\nCada línea lleva `fiscal_bucket` (`saas | telecom | pass_through | tax`) desde macro-tasks#448 MT-D: cómo se clasifica ese dinero fiscalmente. Antes se retiraba de la respuesta, porque el encuadre era una propuesta y publicarlo habría afirmado una postura que nadie había tomado (ADR #73 decisión 6); el founder la confirmó y el filtro se quitó."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: line_items[]\", function () { pm.expect(j.line_items).to.be.an(\"array\"); });",
                  "// macro-tasks#448 MT-D: el cubo fiscal ES publico. Se afirma sobre TODAS",
                  "// las lineas y contra el vocabulario cerrado, no sobre la primera ni",
                  "// contra `undefined`: un filtro que volviera solo para algunas lineas",
                  "// pasaria un test que mirase una sola.",
                  "pm.test(\"cada linea publica su fiscal_bucket, del vocabulario cerrado\", function () {",
                  "  const buckets = [\"saas\", \"telecom\", \"pass_through\", \"tax\"];",
                  "  pm.expect(j.line_items).to.not.be.empty;",
                  "  j.line_items.forEach(function (item) {",
                  "    pm.expect(buckets, \"kind=\" + item.kind).to.include(item.fiscal_bucket);",
                  "  });",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/billing/invoice",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/billing/invoice",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "billing",
                "invoice"
              ]
            },
            "description": "La factura. La ventana la fija el SERVIDOR — sin query params responde el mes UTC en curso; el único mando es ?period=YYYY-MM y un from/to en el query-string es 400 (core#465, MT-BIL-05 MT.4). CSV vía ?format=csv.\n\nOJO con el origen de las líneas, que NO siempre es el mismo (core#473, MT-BIL-05 MT.5d): si el periodo pedido tiene una factura EMITIDA, se sirven sus filas CONGELADAS tal cual quedaron al cierre; sólo cuando no la hay se cae al read-model vivo, que es el que comparte line items con summary. Es decir, para un mes cerrado esta ruta y /v1/billing/summary pueden diferir a propósito: la factura es el documento, el summary es la lectura de hoy."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: line_items[]\", function () { pm.expect(j.line_items).to.be.an(\"array\"); });",
                  "pm.test(\"shape: period resuelto por el servidor\", function () { pm.expect(j.period).to.have.property(\"from\"); pm.expect(j.period).to.have.property(\"to\"); });",
                  "// macro-tasks#448 MT-D: el cubo fiscal ES publico tambien aqui — la rama",
                  "// VIVA de esta ruta pasa por el MISMO BillingSummary.for_api/1 que",
                  "// /billing/summary (v1_router.ex), y ADR #73 decision 6 la nombra a mano.",
                  "//",
                  "// Se discrimina por `source` y NO se afirma a ciegas: cuando hay documento",
                  "// EMITIDO esta ruta sirve Render.frozen_invoice/1, cuyas lineas llevan",
                  "// `line_type` y NO llevan `fiscal_bucket` — el documento congelado no se",
                  "// retro-clasifica (ADR #73 decision 5). Afirmarlo alli seria exigirle a la",
                  "// factura emitida un campo que su contrato no promete.",
                  "pm.test(\"la rama VIVA publica su fiscal_bucket; la CONGELADA no lo promete\", function () {",
                  "  if (j.source === \"frozen_invoice\") {",
                  "    pm.expect(j.line_items[0] || {}, \"documento congelado\").to.have.property(\"line_type\");",
                  "    return;",
                  "  }",
                  "  const buckets = [\"saas\", \"telecom\", \"pass_through\", \"tax\"];",
                  "  pm.expect(j.line_items).to.not.be.empty;",
                  "  j.line_items.forEach(function (item) {",
                  "    pm.expect(buckets, \"kind=\" + item.kind).to.include(item.fiscal_bucket);",
                  "  });",
                  "})"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "13 · reports",
      "description": "Artefactos de compliance empaquetados (cadena de custodia x-artifact-sha256 en la variante CSV).",
      "item": [
        {
          "name": "GET /v1/reports/violations-prevented",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/reports/violations-prevented",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reports",
                "violations-prevented"
              ]
            },
            "description": "Violaciones PREVENIDAS (conteo real sobre gate_blocks, nunca estimado). CSV vía ?format=csv."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: period presente\", function () { pm.expect(j.period).to.be.an(\"object\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/reports/attempts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/reports/attempts?from=2026-09-01T00:00:00Z&to=2026-09-17T00:00:00Z&format=csv",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reports",
                "attempts"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-09-01T00:00:00Z"
                },
                {
                  "key": "to",
                  "value": "2026-09-17T00:00:00Z"
                },
                {
                  "key": "format",
                  "value": "csv"
                }
              ]
            },
            "description": "Export CSV de intentos por rango (≤ 31 días, `from`/`to` obligatorios): una fila por intento con su disposición FINAL y `source` (`platform|cms_import`), para que el 7-en-7 de un CMS externo no diverja del nuestro. Reanudación con `?after=<export_cursor>`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"content-type text/csv\", () => pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"text/csv\"));",
                  "pm.test(\"x-artifact-sha256: sha256 hex del artefacto\", () => pm.expect(pm.response.headers.get(\"X-Artifact-SHA256\")).to.match(/^[0-9a-f]{64}$/));",
                  "pm.test(\"cabecera: las 8 columnas del contrato\", () => {",
                  "  const header = pm.response.text().split(/\\r?\\n/)[0];",
                  "  pm.expect(header).to.eql(\"debt_key,attempt_at,channel,source,outcome,agent_id,call_id,export_cursor\");",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "14 · dids",
      "description": "Registro de números propios del tenant + salud computada del CDR. e164 inmutable; DELETE retira, nunca borra.",
      "item": [
        {
          "name": "POST /v1/dids",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/dids",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "dids"
              ]
            },
            "description": "Alta (NPA derivado del NANP; número aleatorio por corrida para evitar el 409 por duplicado).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"e164\": \"{{postman_did_e164}}\",\n  \"us_state\": \"IL\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const nxx = 200 + Math.floor(Math.random() * 800);",
                  "const line = String(Math.floor(Math.random() * 10000)).padStart(4, \"0\");",
                  "pm.collectionVariables.set(\"postman_did_e164\", \"+1312\" + nxx + line);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: did activo con npa derivado\", function () {",
                  "  pm.expect(j.did.id).to.be.a(\"string\");",
                  "  pm.expect(j.did.npa).to.eql(\"312\");",
                  "  pm.expect(j.did.status).to.eql(\"active\");",
                  "});",
                  "pm.collectionVariables.set(\"did_id\", j.did.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/dids",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/dids",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "dids"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: dids[]\", function () { pm.expect(j.dids).to.be.an(\"array\").and.to.have.length.above(0); });"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/dids/:id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/dids/{{did_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "dids",
                "{{did_id}}"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: did.id coincide\", function () {",
                  "  pm.expect(j.did.id).to.eql(pm.collectionVariables.get(\"did_id\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "PATCH /v1/dids/:id",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/dids/{{did_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "dids",
                "{{did_id}}"
              ]
            },
            "description": "Metadata/status (e164 inmutable tras el alta).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"attestation\": \"A\",\n  \"labels\": [\n    \"postman\"\n  ],\n  \"notes\": \"postman smoke\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: attestation A\", function () { pm.expect(j.did.attestation).to.eql(\"A\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/dids/:id/health",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/dids/{{did_id}}/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "dids",
                "{{did_id}}",
                "health"
              ]
            },
            "description": "Salud computada del CDR (dials, answer rate, short-call rate, serie diaria)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: objeto de salud\", function () { pm.expect(j).to.be.an(\"object\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "DELETE /v1/dids/:id",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/dids/{{did_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "dids",
                "{{did_id}}"
              ]
            },
            "description": "Retira (el registro nunca pierde filas: la proveniencia from_number del CDR debe seguir resolviendo)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: retired\", function () { pm.expect(j.did.status).to.eql(\"retired\"); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "15 · carriers",
      "description": "Trunks SIP de la sede (account_id/tenant_id JAMÁS del body; scope empresa = compartido).",
      "item": [
        {
          "name": "POST /v1/carriers",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/carriers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "carriers"
              ]
            },
            "description": "Alta BYOC per-sede (scope por defecto).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"byoc\",\n  \"name\": \"postman-trunk-{{$timestamp}}\",\n  \"sip_proxy\": \"sip:pstn.example.test:5060\",\n  \"source_ips\": [\n    \"{{postman_trunk_ip}}\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// El IP del troncal se deriva por corrida. La unicidad de source_ips NO es",
                  "// un detalle: es la guarda anti-hijack cross-tenant de MT-SEC-04, y es GLOBAL",
                  "// entre cuentas. Con un literal fijo, la segunda corrida contra el mismo stack",
                  "// moria en 400 'source_ips already assigned to another trunk' (docs#67).",
                  "// Espacio: 100.64.0.0/10 (RFC 6598), ~4,1 M de combinaciones. Un /24 de",
                  "// documentacion solo da 254, y las colisiones volverian enseguida.",
                  "const rnd = (n) => Math.floor(Math.random() * n);",
                  "pm.variables.set('postman_trunk_ip', '100.' + (64 + rnd(64)) + '.' + rnd(256) + '.' + (1 + rnd(254)));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: carrier byoc scope sede\", function () {",
                  "  pm.expect(j.carrier.id).to.be.a(\"string\");",
                  "  pm.expect(j.carrier.kind).to.eql(\"byoc\");",
                  "});",
                  "pm.collectionVariables.set(\"carrier_id\", j.carrier.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/carriers",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/carriers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "carriers"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: carriers[]\", function () { pm.expect(j.carriers).to.be.an(\"array\").and.to.have.length.above(0); });"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/carriers/:id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/carriers/{{carrier_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "carriers",
                "{{carrier_id}}"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: carrier.id coincide\", function () {",
                  "  pm.expect(j.carrier.id).to.eql(pm.collectionVariables.get(\"carrier_id\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/carriers/:id/provision",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/carriers/{{carrier_id}}/provision",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "carriers",
                "{{carrier_id}}",
                "provision"
              ]
            },
            "description": "Empuja el trunk al SIP edge. `sbc_disabled` (sin KAMAILIO_RPC_URL) es 200 con flag explícito, no error."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: provisioned|sbc_disabled\", function () {",
                  "  pm.expect([\"provisioned\", \"sbc_disabled\"]).to.include(j.status);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/carriers/:id/revoke",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/carriers/{{carrier_id}}/revoke",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "carriers",
                "{{carrier_id}}",
                "revoke"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: provisioned|sbc_disabled\", function () {",
                  "  pm.expect([\"provisioned\", \"sbc_disabled\"]).to.include(j.status);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "16 · inbound-routes",
      "description": "Rutas DID entrantes (un número suena en UN solo lugar: e164 activo globalmente único → 409).",
      "item": [
        {
          "name": "POST /v1/inbound-routes",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/inbound-routes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inbound-routes"
              ]
            },
            "description": "Alta con destino IVR (dest_type agent requiere dest_ref). Número aleatorio por corrida.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"e164\": \"{{postman_inbound_e164}}\",\n  \"dest_type\": \"ivr\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const nxx = 200 + Math.floor(Math.random() * 800);",
                  "const line = String(Math.floor(Math.random() * 10000)).padStart(4, \"0\");",
                  "pm.collectionVariables.set(\"postman_inbound_e164\", \"+1773\" + nxx + line);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: route activa\", function () {",
                  "  pm.expect(j.inbound_route.id).to.be.a(\"string\");",
                  "  pm.expect(j.inbound_route.status).to.eql(\"active\");",
                  "});",
                  "pm.collectionVariables.set(\"inbound_route_id\", j.inbound_route.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/inbound-routes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/inbound-routes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inbound-routes"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: inbound_routes[]\", function () {",
                  "  pm.expect(j.inbound_routes).to.be.an(\"array\").and.to.have.length.above(0);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/inbound-routes/:id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/inbound-routes/{{inbound_route_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inbound-routes",
                "{{inbound_route_id}}"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: route.id coincide\", function () {",
                  "  pm.expect(j.inbound_route.id).to.eql(pm.collectionVariables.get(\"inbound_route_id\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "PATCH /v1/inbound-routes/:id",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/inbound-routes/{{inbound_route_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inbound-routes",
                "{{inbound_route_id}}"
              ]
            },
            "description": "Update de destino/flags (e164 inmutable).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"recording\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: recording false\", function () { pm.expect(j.inbound_route.recording).to.eql(false); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/inbound-routes/:id/provision",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/inbound-routes/{{inbound_route_id}}/provision",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inbound-routes",
                "{{inbound_route_id}}",
                "provision"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: provisioned|sbc_disabled\", function () {",
                  "  pm.expect([\"provisioned\", \"sbc_disabled\"]).to.include(j.status);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/inbound-routes/:id/disable",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/inbound-routes/{{inbound_route_id}}/disable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inbound-routes",
                "{{inbound_route_id}}",
                "disable"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: disabled\", function () { pm.expect(j.inbound_route.status).to.eql(\"disabled\"); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "17 · sms-providers",
      "description": "Registro de proveedores SMS por tenant/región (molde carriers: account_id/tenant_id JAMÁS del body; scope empresa = compartido). El webhook_secret es write-only: las respuestas exponen has_secret, nunca el secreto. Retire-only (sin DELETE).",
      "item": [
        {
          "name": "POST /v1/sms/providers",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/sms/providers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sms",
                "providers"
              ]
            },
            "description": "Alta per-sede (scope por defecto). El secreto viaja solo en el request.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"adapter\": \"http\",\n  \"region\": \"us\",\n  \"name\": \"postman-sms-{{$timestamp}}\",\n  \"base_url\": \"\",\n  \"webhook_secret\": \"{{$guid}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: sms_provider http scope sede\", function () {",
                  "  pm.expect(j.sms_provider.id).to.be.a(\"string\");",
                  "  pm.expect(j.sms_provider.adapter).to.eql(\"http\");",
                  "  pm.expect(j.sms_provider.scope).to.eql(\"sede\");",
                  "});",
                  "pm.test(\"secreto write-only: has_secret sin webhook_secret\", function () {",
                  "  pm.expect(j.sms_provider.has_secret).to.eql(true);",
                  "  pm.expect(j.sms_provider).to.not.have.property(\"webhook_secret\");",
                  "});",
                  "pm.collectionVariables.set(\"sms_provider_id\", j.sms_provider.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/sms/providers",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sms/providers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sms",
                "providers"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: sms_providers[] sin secretos\", function () {",
                  "  pm.expect(j.sms_providers).to.be.an(\"array\").and.to.have.length.above(0);",
                  "  j.sms_providers.forEach(p => pm.expect(p).to.not.have.property(\"webhook_secret\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/sms/providers/:id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sms/providers/{{sms_provider_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sms",
                "providers",
                "{{sms_provider_id}}"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: sms_provider.id coincide\", function () {",
                  "  pm.expect(j.sms_provider.id).to.eql(pm.collectionVariables.get(\"sms_provider_id\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "PATCH /v1/sms/providers/:id",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/sms/providers/{{sms_provider_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sms",
                "providers",
                "{{sms_provider_id}}"
              ]
            },
            "description": "Config/credenciales/status; el scope es inmutable tras el alta.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"postman-sms-renamed\",\n  \"status\": \"disabled\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: patch aplicado, secreto intacto\", function () {",
                  "  pm.expect(j.sms_provider.name).to.eql(\"postman-sms-renamed\");",
                  "  pm.expect(j.sms_provider.status).to.eql(\"disabled\");",
                  "  pm.expect(j.sms_provider.has_secret).to.eql(true);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/sms/providers/:id/retire",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sms/providers/{{sms_provider_id}}/retire",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sms",
                "providers",
                "{{sms_provider_id}}",
                "retire"
              ]
            },
            "description": "Retiro terminal — la fila nunca se borra (sin grant DELETE)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: status retired\", function () {",
                  "  pm.expect(j.sms_provider.status).to.eql(\"retired\");",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "18 · sms-messages",
      "description": "Espejo de /v1/sms/messages (T24): el choke point Dialer.Sms.Sender. El POST prueba el contrato FAIL-CLOSED contra el stack real: se registra un provider activo (el mock del harness) y aun así el envío a un destino SIN consentimiento es 422 compliance_blocked/tcpa_no_consent con decision_id citable — cero llamadas al provider, cero filas de ledger. El ciclo 202 completo (accept→DLR→STOP→metering) vive en tests/smoke/test_sms_e2e.py.",
      "item": [
        {
          "name": "0. alta provider mock (para el send)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/sms/providers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sms",
                "providers"
              ]
            },
            "description": "Provider activo del harness (mocks/sms-provider) para que el POST de mensajes NO caiga en 422 sms_provider_unconfigured: lo que se prueba es la cadena de gates, no la resolución. Retirado al final de la carpeta.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"adapter\": \"http\",\n  \"region\": \"us\",\n  \"name\": \"postman-sms-send-{{$timestamp}}\",\n  \"base_url\": \"\",\n  \"webhook_secret\": \"{{$guid}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.collectionVariables.set(\"sms_send_provider_id\", j.sms_provider.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/sms/messages",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/sms/messages",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sms",
                "messages"
              ]
            },
            "description": "UN envío gated. Canal SMS: TODO destino exige consentimiento previo (fail-closed, line_type irrelevante) y este número no lo tiene ⇒ 422 compliance_blocked / gate tcpa_no_consent con decision_id del audit chain. Cero tráfico al provider, cero filas sms_messages/ledger.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to\": \"+19995550123\",\n  \"body\": \"postman probe. Responda STOP para no recibir mas mensajes.\",\n  \"client_ref\": \"postman-{{$guid}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 422 (fail-closed sin consentimiento)\", function () {",
                  "  pm.response.to.have.status(422);",
                  "});",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: compliance_blocked + gate + decision_id citable\", function () {",
                  "  pm.expect(j.error.code).to.eql(\"compliance_blocked\");",
                  "  pm.expect(j.error.gate).to.eql(\"tcpa_no_consent\");",
                  "  pm.expect(j.error.decision_id).to.be.a(\"string\").and.to.not.be.empty;",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/sms/messages",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sms/messages?limit=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sms",
                "messages"
              ]
            },
            "description": "Ledger de mensajes (originales + transiciones compensatorias, keyset-paginado). En esta corrida el envío bloqueado NO dejó filas: el shape es lo verificable."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: sms_messages[] + next_cursor\", function () {",
                  "  pm.expect(j.sms_messages).to.be.an(\"array\");",
                  "  pm.expect(j).to.have.property(\"next_cursor\");",
                  "});",
                  "pm.test(\"listas llevan body_preview, nunca body completo\", function () {",
                  "  j.sms_messages.forEach(function (m) {",
                  "    pm.expect(m).to.have.property(\"body_preview\");",
                  "    pm.expect(m).to.not.have.property(\"body\");",
                  "  });",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/sms/messages/:id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sms/messages/{{sms_message_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sms",
                "messages",
                "{{sms_message_id}}"
              ]
            },
            "description": "Vista COLAPSADA de un mensaje (status actual + timestamps + snapshot + transiciones). 404 real del endpoint (id aleatorio sembrado en pre-request) porque el flujo de esta colección no produce mensajes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 404 (id inexistente)\", function () {",
                  "  pm.response.to.have.status(404);",
                  "});",
                  "pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "  pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "});"
                ]
              }
            },
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// La colección no siembra sms_message_id (el POST de arriba es un 422",
                  "// fail-closed a propósito): con la variable vacía la URL caería en el",
                  "// catch-all del router SIN ejercitar la ruta real — UUID aleatorio.",
                  "if (!pm.collectionVariables.get(\"sms_message_id\")) {",
                  "  pm.collectionVariables.set(\"sms_message_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "9. retire provider mock (limpieza)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sms/providers/{{sms_send_provider_id}}/retire",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sms",
                "providers",
                "{{sms_send_provider_id}}",
                "retire"
              ]
            },
            "description": "Deja al tenant demo sin provider activo (el estado con el que empezó la corrida): la colección es re-ejecutable sin acumular filas activas."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "pm.test(\"retired\", function () {",
                  "  pm.expect(pm.response.json().sms_provider.status).to.eql(\"retired\");",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "19 · calls (CTI)",
      "item": [
        {
          "name": "GET /v1/calls",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls"
              ]
            },
            "description": "Superficie de LECTURA del CTI (la mitad que el WidgetAllowlist pre-autoriza para ddw_): las mismas filas CDR que /v1/call_attempts, clavadas por call_uuid para los verbos de control. Con dd_ lista el piso completo del tenant."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: calls[] + next_cursor presente\", () => {",
                  "  pm.expect(j.calls).to.be.an(\"array\");",
                  "  pm.expect(j).to.have.property(\"next_cursor\");",
                  "});",
                  "pm.test(\"cada fila trae id + call_uuid (la clave de los verbos)\", () => {",
                  "  j.calls.forEach(c => { pm.expect(c).to.have.property(\"id\"); pm.expect(c).to.have.property(\"call_uuid\"); });",
                  "});",
                  "// Captura un call_uuid REAL para el GET por-id determinista de abajo.",
                  "const withUuid = (j.calls || []).find(c => c.call_uuid);",
                  "if (withUuid) { pm.collectionVariables.set(\"probe_call_uuid\", withUuid.call_uuid); }",
                  "else { pm.collectionVariables.unset(\"probe_call_uuid\"); }"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/calls?live=true",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls?live=true",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls"
              ],
              "query": [
                {
                  "key": "live",
                  "value": "true"
                }
              ]
            },
            "description": "Solo llamadas EN VUELO: ended_at null y solo originales (una correccion es post-llamada por definicion). Aserta la semantica del filtro sin depender de cuantas filas haya."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test(\"todas las filas estan en vuelo (ended_at null, corrects_id null)\", () => {",
                  "  j.calls.forEach(c => { pm.expect(c.ended_at).to.eql(null); pm.expect(c.corrects_id).to.eql(null); });",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/calls?live=banana — booleano estricto: 400",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls?live=banana",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls"
              ],
              "query": [
                {
                  "key": "live",
                  "value": "banana"
                }
              ]
            },
            "description": "Un live malformado jamas ensancha en silencio una lectura \"solo en vuelo\" al CDR completo: booleano estricto o 400."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"400 bad_request\", () => {",
                  "  pm.expect(pm.response.code).to.eql(400);",
                  "  pm.expect(pm.response.json().error.code).to.eql(\"bad_request\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/calls/:call_id (fila capturada de la lista)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls/{{probe_call_uuid}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls",
                "{{probe_call_uuid}}"
              ]
            },
            "description": "Lee por call_uuid una fila REAL capturada de GET /v1/calls (determinista en el stack sembrado). El read clava la fila ORIGINAL (corrects_id null). Si la lista estuviera vacía, un uuid ausente da 404 — tolerado, nunca 500."
          },
          "response": [],
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.collectionVariables.get(\"probe_call_uuid\")) { pm.collectionVariables.set(\"probe_call_uuid\", \"00000000-0000-7000-8000-0000000000ff\"); }"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const probed = pm.collectionVariables.get(\"probe_call_uuid\");",
                  "if (probed) {",
                  "  pm.test(\"status 200 (la fila capturada se lee por su call_uuid)\", () => pm.response.to.have.status(200));",
                  "  if (pm.response.code === 200) {",
                  "    const j = pm.response.json();",
                  "    pm.test(\"call.call_uuid coincide y es la fila ORIGINAL (corrects_id null)\", () => {",
                  "      pm.expect(j.call.call_uuid).to.eql(probed);",
                  "      pm.expect(j.call.corrects_id).to.eql(null);",
                  "    });",
                  "  }",
                  "} else {",
                  "  pm.test(\"sin fila que capturar -> 404 determinista (nunca 500/catch-all)\", () => {",
                  "    pm.expect(pm.response.code).to.eql(404);",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/calls/:call_id — uuid desconocido: 404 determinista",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls/{{unknown_call_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls",
                "{{unknown_call_id}}"
              ]
            },
            "description": "Sonda determinista: un uuid bien formado que no existe en el tenant es SIEMPRE 404 (tenant_id explicito + RLS fail-closed). No depende del golden call."
          },
          "response": [],
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.variables.set(\"unknown_call_id\", \"00000000-0000-7000-8000-000000000000\");"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"404 determinista para uuid desconocido\", function () {",
                  "  pm.expect(pm.response.code).to.eql(404);",
                  "});",
                  "pm.test(\"envelope not_found del endpoint real (no catch-all)\", function () {",
                  "  const e = pm.response.json().error;",
                  "  pm.expect(e.code).to.eql(\"not_found\");",
                  "  pm.expect(e.message).to.not.eql(\"no such route\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/calls/:call_id/hangup — uuid desconocido: 404 determinista",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.variables.set(\"unknown_call_id\", \"00000000-0000-7000-8000-000000000000\");"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"404 determinista para uuid desconocido\", function () {",
                  "  pm.expect(pm.response.code).to.eql(404);",
                  "});",
                  "pm.test(\"envelope not_found del endpoint real (no catch-all)\", function () {",
                  "  const e = pm.response.json().error;",
                  "  pm.expect(e.code).to.eql(\"not_found\");",
                  "  pm.expect(e.message).to.not.eql(\"no such route\");",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls/{{unknown_call_id}}/hangup",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls",
                "{{unknown_call_id}}",
                "hangup"
              ]
            },
            "description": "Sonda determinista: un uuid bien formado que no existe en el tenant es SIEMPRE 404 (RLS fail-closed, cero comandos ESL, fila de auditoria failed/call_not_found). No depende del golden call."
          },
          "response": []
        },
        {
          "name": "POST /v1/calls/:call_id/hold",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls/{{golden_call_id}}/hold",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls",
                "{{golden_call_id}}",
                "hold"
              ]
            },
            "description": "CTI verb hold sobre llamada viva (evidence-first: fila call_control_actions ANTES del ESL). Con llamada viva -> 200; sin/ajena -> 404; no bridged -> 409; unhold fuera de ventana -> 422; switch rechaza (leg muerto) -> 502; audit/ESL caido -> 503."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|409|502|503 (segun haya llamada viva)\", function () {",
                  "  pm.expect([200, 404, 409, 502, 503]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: call presente\", function () { pm.expect(j.call).to.be.an(\"object\"); });",
                  "  pm.test(\"shape 200: verb/audit_id/to_number enmascarado\", function () {",
                  "    pm.expect(j.call.verb).to.eql(\"hold\");",
                  "    pm.expect(j.call.audit_id).to.match(/^[0-9a-f-]{36}$/);",
                  "    if (j.call.to_number) { pm.expect(j.call.to_number).to.match(/^\\*\\*\\*/); }",
                  "  });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all)\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}",
                  "if (pm.response.code === 409) {",
                  "  pm.test(\"409 not_bridged del endpoint real\", function () {",
                  "    pm.expect(pm.response.json().error.code).to.eql(\"not_bridged\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/calls/:call_id/unhold",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls/{{golden_call_id}}/unhold",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls",
                "{{golden_call_id}}",
                "unhold"
              ]
            },
            "description": "CTI verb unhold sobre llamada viva (evidence-first: fila call_control_actions ANTES del ESL). Con llamada viva -> 200; sin/ajena -> 404; no bridged -> 409; unhold fuera de ventana -> 422; switch rechaza (leg muerto) -> 502; audit/ESL caido -> 503."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|409|422|502|503 (segun haya llamada viva)\", function () {",
                  "  pm.expect([200, 404, 409, 422, 502, 503]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: call presente\", function () { pm.expect(j.call).to.be.an(\"object\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all)\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}",
                  "if (pm.response.code === 422) {",
                  "  pm.test(\"422: envelope mid_call completo\", function () {",
                  "    const e = pm.response.json().error;",
                  "    pm.expect(e.gate).to.eql(\"mid_call\");",
                  "    pm.expect([\"quiet_hours\", \"consent_revoked\", \"cease_and_desist\"]).to.include(e.reason_code);",
                  "    pm.expect(e.auto_hangup_at).to.be.a(\"string\");",
                  "  });",
                  "  pm.test(\"422 compliance_blocked mid_call\", function () {",
                  "    pm.expect(pm.response.json().error.code).to.eql(\"compliance_blocked\");",
                  "  });",
                  "}",
                  "if (pm.response.code === 409) {",
                  "  pm.test(\"409 not_bridged del endpoint real\", function () {",
                  "    pm.expect(pm.response.json().error.code).to.eql(\"not_bridged\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/calls/:call_id/mute",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls/{{golden_call_id}}/mute",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls",
                "{{golden_call_id}}",
                "mute"
              ]
            },
            "description": "CTI verb mute sobre llamada viva (evidence-first: fila call_control_actions ANTES del ESL). Con llamada viva -> 200; sin/ajena -> 404; no bridged -> 409; unhold fuera de ventana -> 422; switch rechaza (leg muerto) -> 502; audit/ESL caido -> 503."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|409|502|503 (segun haya llamada viva)\", function () {",
                  "  pm.expect([200, 404, 409, 502, 503]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: call presente\", function () { pm.expect(j.call).to.be.an(\"object\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all)\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}",
                  "if (pm.response.code === 409) {",
                  "  pm.test(\"409 not_bridged del endpoint real\", function () {",
                  "    pm.expect(pm.response.json().error.code).to.eql(\"not_bridged\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/calls/:call_id/unmute",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls/{{golden_call_id}}/unmute",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls",
                "{{golden_call_id}}",
                "unmute"
              ]
            },
            "description": "CTI verb unmute sobre llamada viva (evidence-first: fila call_control_actions ANTES del ESL). Con llamada viva -> 200; sin/ajena -> 404; no bridged -> 409; unhold fuera de ventana -> 422; switch rechaza (leg muerto) -> 502; audit/ESL caido -> 503."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|409|502|503 (segun haya llamada viva)\", function () {",
                  "  pm.expect([200, 404, 409, 502, 503]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: call presente\", function () { pm.expect(j.call).to.be.an(\"object\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all)\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}",
                  "if (pm.response.code === 409) {",
                  "  pm.test(\"409 not_bridged del endpoint real\", function () {",
                  "    pm.expect(pm.response.json().error.code).to.eql(\"not_bridged\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/calls/:call_id/hangup",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/calls/{{golden_call_id}}/hangup",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "calls",
                "{{golden_call_id}}",
                "hangup"
              ]
            },
            "description": "CTI verb hangup sobre llamada viva (evidence-first: fila call_control_actions ANTES del ESL). Con llamada viva -> 200; sin/ajena -> 404; no bridged -> 409; unhold fuera de ventana -> 422; switch rechaza (leg muerto) -> 502; audit/ESL caido -> 503."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|502|503 (segun haya llamada viva)\", function () {",
                  "  pm.expect([200, 404, 502, 503]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: call presente\", function () { pm.expect(j.call).to.be.an(\"object\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all)\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "20 · widget",
      "description": "CTI-4: lifecycle del widget embebible. Gestión (mint/list/revoke tokens + origins CRUD) es dd_; renew/introspect son ddw_-self (con el flag OFF, un dd_ -> 401). ddw_ nunca alcanza esta gestión (default-deny).",
      "item": [
        {
          "name": "GET /v1/widget_origins",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_origins",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_origins"
              ]
            },
            "description": "Lista de orígenes allowlisted del tenant (habilitados + deshabilitados)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: widget_origins[]\", () => pm.expect(j.widget_origins).to.be.an(\"array\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/widget_origins",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_origins",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_origins"
              ]
            },
            "description": "Agrega un origen (normalizado a forma canónica). Captura id + origen canónico.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"https://w{{$timestamp}}.example.com\",\n  \"label\": \"postman-widget\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: widget_origin canónico (sin tenant_id/hash)\", () => {",
                  "  pm.expect(j.widget_origin.id).to.be.a(\"string\");",
                  "  pm.expect(j.widget_origin.origin).to.match(/^https:\\/\\//);",
                  "  pm.expect(j.widget_origin).to.not.have.property(\"tenant_id\");",
                  "});",
                  "pm.collectionVariables.set(\"widget_origin_id\", j.widget_origin.id);",
                  "pm.collectionVariables.set(\"widget_origin_canonical\", j.widget_origin.origin);"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/widget_origins (duplicado -> 409)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_origins",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_origins"
              ]
            },
            "description": "Re-add del mismo origen canónico -> 409 UNIQUE(tenant_id,origin).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"{{widget_origin_canonical}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Reenvía el origen CANÓNICO capturado en OTRA grafía (mayúsculas) para",
                  "// probar normalize-then-conflict sobre UNIQUE(tenant_id,origin) sin flakear.",
                  "pm.test(\"status 409 (normalize-then-conflict)\", () => pm.response.to.have.status(409));"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/widget_origins/:id/enable",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_origins/{{widget_origin_id}}/enable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_origins",
                "{{widget_origin_id}}",
                "enable"
              ]
            },
            "description": "Re-habilita el origen (idempotente sobre uno ya habilitado)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: disabled_at null (habilitado)\", () => pm.expect(j.widget_origin.disabled_at).to.be.null);"
                ]
              }
            }
          ]
        },
        {
          "name": "DELETE /v1/widget_origins/:id (disable)",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_origins/{{widget_origin_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_origins",
                "{{widget_origin_id}}"
              ]
            },
            "description": "Deshabilita (soft) el origen — los orígenes nunca se borran."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: disabled_at presente (deshabilitado)\", () => pm.expect(j.widget_origin.disabled_at).to.not.be.null);"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/widget_tokens",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_tokens",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_tokens"
              ]
            },
            "description": "Mint de un ddw_ para el agente golden. Plaintext UNA vez; TTLs server-side.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"agent_id\": \"{{golden_agent_id}}\",\n  \"label\": \"postman-widget\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "const j = pm.response.json();",
                  "pm.test(\"plaintext ddw_ una vez; sin hash\", () => {",
                  "  pm.expect(j.token).to.be.a(\"string\").and.to.match(/^ddw_/);",
                  "  pm.expect(j.widget_token).to.not.have.property(\"token_hash\");",
                  "  pm.expect(j.widget_token.agent_id).to.eql(pm.collectionVariables.get(\"golden_agent_id\"));",
                  "});",
                  "pm.collectionVariables.set(\"widget_token_id\", j.widget_token.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/widget_tokens",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_tokens",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_tokens"
              ]
            },
            "description": "Lista de tokens (jamás el hash)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test(\"lista sin token_hash\", () => {",
                  "  pm.expect(j.widget_tokens).to.be.an(\"array\");",
                  "  j.widget_tokens.forEach((t) => pm.expect(t).to.not.have.property(\"token_hash\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "DELETE /v1/widget_tokens/:id",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_tokens/{{widget_token_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_tokens",
                "{{widget_token_id}}"
              ]
            },
            "description": "Revoca el token recién minteado (idempotente)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200 (revoca; idempotente)\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "DELETE /v1/widget_tokens/:id (fantasma -> 404)",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_tokens/{{ghost_uuid}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_tokens",
                "{{ghost_uuid}}"
              ]
            },
            "description": "Revoca un id inexistente -> 404 (mismo 404 que uno cross-tenant)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// id inexistente vía VARIABLE (nunca literal — el normalizador de drift",
                  "// solo colapsa segmentos {{var}}). 404 determinista, sin oráculo de existencia.",
                  "pm.test(\"status 404\", () => pm.response.to.have.status(404));"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/widget_tokens/renew (dd_ sin sesión -> 401)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_tokens/renew",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_tokens",
                "renew"
              ]
            },
            "description": "Self-renew: un dd_ no porta sesión de widget -> 401 (dark)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Con WIDGET_AUTH_ENABLED=OFF (dark) y bajo el bearer dd_ de la colección,",
                  "// no hay :widget_token_id -> 401 estable e invariante al flag.",
                  "pm.test(\"status 401\", () => pm.response.to.have.status(401));",
                  "pm.test(\"code unauthorized\", () => pm.expect(pm.response.json().error.code).to.eql(\"unauthorized\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/widget_tokens/introspect (dd_ sin sesión -> 401)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/widget_tokens/introspect",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "widget_tokens",
                "introspect"
              ]
            },
            "description": "Self-introspect: un dd_ no porta sesión de widget -> 401 (dark)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 401\", () => pm.response.to.have.status(401));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "21 · users+auth",
      "description": "Usuarios del dashboard (email+password) y sesiones ddu_ (login/logout). Orden dependiente: crear -> login -> me -> rotar password -> disable/enable -> logout idempotente.",
      "item": [
        {
          "name": "POST /v1/users",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users"
              ]
            },
            "description": "Alta con la dd_ (bootstrap). El password jamas vuelve en la respuesta.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"newman-{{$timestamp}}@e2e.test\",\n  \"name\": \"Newman User\",\n  \"password\": \"newman-e2e-password-1\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: user sin material secreto\", function () {",
                  "  pm.expect(j.user.id).to.be.a(\"string\");",
                  "  pm.expect(j.user.email).to.include(\"@e2e.test\");",
                  "  pm.expect(j.user.status).to.eql(\"active\");",
                  "  pm.expect(JSON.stringify(j)).to.not.include(\"password_hash\");",
                  "  pm.expect(JSON.stringify(j)).to.not.include(\"pbkdf2\");",
                  "});",
                  "pm.collectionVariables.set(\"postman_user_id\", j.user.id);",
                  "pm.collectionVariables.set(\"postman_user_email\", j.user.email);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/users",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users"
              ]
            },
            "description": ""
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"lista contiene el usuario recien creado\", function () {",
                  "  const mine = j.users.find(u => u.id === pm.collectionVariables.get(\"postman_user_id\"));",
                  "  pm.expect(mine).to.be.an(\"object\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/auth/login",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "login"
              ]
            },
            "description": "SIN bearer: esta llamada lo acuna. El plaintext ddu_ se muestra UNA vez.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"{{postman_user_email}}\",\n  \"password\": \"newman-e2e-password-1\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: token ddu_ + session + user + tenant\", function () {",
                  "  pm.expect(j.token).to.match(/^ddu_/);",
                  "  pm.expect(j.session.user_id).to.eql(pm.collectionVariables.get(\"postman_user_id\"));",
                  "  pm.expect(j.user.email).to.eql(pm.collectionVariables.get(\"postman_user_email\"));",
                  "  pm.expect(j.tenant.id).to.be.a(\"string\");",
                  "  pm.expect(JSON.stringify(j)).to.not.include(\"token_hash\");",
                  "});",
                  "pm.collectionVariables.set(\"ddu_token\", j.token);"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/auth/login (password malo -> 401 uniforme)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "login"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"{{postman_user_email}}\",\n  \"password\": \"definitely-not-the-password\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 401\", function () { pm.response.to.have.status(401); });",
                  "pm.test(\"envelope anti-enumeracion\", function () {",
                  "  const j = pm.response.json();",
                  "  pm.expect(j.error.code).to.eql(\"unauthorized\");",
                  "  pm.expect(j.error.message).to.eql(\"invalid email or password\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/me (sesion ddu_)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "me"
              ]
            },
            "description": "",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{ddu_token}}",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"me trae tenant Y user con la ddu_\", function () {",
                  "  pm.expect(j.tenant.id).to.be.a(\"string\");",
                  "  pm.expect(j.user.id).to.eql(pm.collectionVariables.get(\"postman_user_id\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/me/password (rotacion)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/me/password",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "me",
                "password"
              ]
            },
            "description": "Rota con la PROPIA sesion; las demas sesiones del usuario se revocan.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"current_password\": \"newman-e2e-password-1\",\n  \"new_password\": \"newman-e2e-password-2\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{ddu_token}}",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 204\", function () { pm.response.to.have.status(204); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/me/password (dd_ machine key -> 403)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/me/password",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "me",
                "password"
              ]
            },
            "description": "La dd_ no tiene persona: 403 (hereda el bearer de coleccion).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"current_password\": \"x\",\n  \"new_password\": \"irrelevant-here-1\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 403\", function () { pm.response.to.have.status(403); });"
                ]
              }
            }
          ]
        },
        {
          "name": "DELETE /v1/users/:id (disable)",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/users/{{postman_user_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users",
                "{{postman_user_id}}"
              ]
            },
            "description": "Deshabilita (jamas borra) y revoca sus sesiones vivas."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "pm.test(\"status disabled\", function () {",
                  "  pm.expect(pm.response.json().user.status).to.eql(\"disabled\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/me (sesion muerta tras disable -> 401)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "me"
              ]
            },
            "description": "",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{ddu_token}}",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 401\", function () { pm.response.to.have.status(401); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/users/:id/enable",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/users/{{postman_user_id}}/enable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users",
                "{{postman_user_id}}",
                "enable"
              ]
            },
            "description": ""
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "pm.test(\"status active (las sesiones viejas siguen muertas)\", function () {",
                  "  pm.expect(pm.response.json().user.status).to.eql(\"active\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/auth/logout (idempotente -> 204)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/logout",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "logout"
              ]
            },
            "description": "La sesion ya murio con el disable; logout sigue siendo 204 (idempotente, sin enumeracion).",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{ddu_token}}",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 204\", function () { pm.response.to.have.status(204); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/auth/password-reset (solicitud: 202 opaco)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/password-reset",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "password-reset"
              ]
            },
            "description": "SIN bearer. UN solo 202 para todo desenlace (auth_router.ex @accepted): la ruta niega el oraculo de existencia por diseno. El correo con el token ddr_ cae en el mail-sink del harness.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"{{postman_user_email}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 202\", function () { pm.response.to.have.status(202); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: el 202 opaco documentado\", function () {",
                  "  pm.expect(j.status).to.eql(\"accepted\");",
                  "  pm.expect(j.message).to.eql(\"if that address has an account, a reset link is on its way\");",
                  "});",
                  "pm.collectionVariables.set(\"reset_202_body\", pm.response.text());"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/auth/password-reset (email inexistente -> MISMO 202, cero oraculo)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/password-reset",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "password-reset"
              ]
            },
            "description": "SIN bearer. La propiedad anti-enumeracion ejercitada contra el stack real: cuenta inexistente = exactamente los mismos bytes que la existente.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"nadie-{{$timestamp}}@e2e.test\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 202 tambien sin cuenta\", function () { pm.response.to.have.status(202); });",
                  "pm.test(\"cuerpo BYTE-IDENTICO al del email existente (anti-enumeracion)\", function () {",
                  "  pm.expect(pm.response.text()).to.eql(pm.collectionVariables.get(\"reset_202_body\"));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/auth/password-reset/confirm (happy: 200 y mata sesiones)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/password-reset/confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "password-reset",
                "confirm"
              ]
            },
            "description": "SIN bearer. Canjea el token ddr_ capturado del mail-sink. Toda sesion ddu_ viva del usuario muere con el reset (el usuario de esta corrida es efimero: email por timestamp, nada posterior lo reusa).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{reset_token}}\",\n  \"new_password\": \"newman-reset-password-2\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "pm.test(\"shape: {status: ok}\", function () {",
                  "  pm.expect(pm.response.json().status).to.eql(\"ok\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/auth/password-reset/confirm (token quemado -> 401 uniforme)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/password-reset/confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "password-reset",
                "confirm"
              ]
            },
            "description": "SIN bearer. El MISMO token otra vez: un-solo-uso ejercitado contra el stack real, y el 401 es el uniforme (quemado indistinguible de inexistente/expirado).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{reset_token}}\",\n  \"new_password\": \"newman-reset-password-3\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 401: un solo uso, contra el stack real\", function () { pm.response.to.have.status(401); });",
                  "pm.test(\"el 401 es el uniforme del contrato\", function () {",
                  "  pm.expect(pm.response.text()).to.include(\"invalid or expired token\");",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "22 · agent-login (widget)",
      "description": "Login de agente para el widget (CTI-4). La cara ddw_ va DARK (flag widget_auth_enabled OFF en el stack) → agent-login responde 404; la gestión de credencial (dd_) NO es flag-gated y funciona. Orden: crear agente → habilitar credencial → agent-login dark 404 → deshabilitar.",
      "item": [
        {
          "name": "POST /v1/agents (seat para login)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents"
              ]
            },
            "description": "Crea el seat que tendrá credencial de widget-login.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Widget Agent\",\n  \"email\": \"widget-agent-{{$timestamp}}@example.test\",\n  \"role\": \"agent\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function(){ pm.response.to.have.status(201); });",
                  "const j=pm.response.json();",
                  "pm.collectionVariables.set(\"widget_agent_id\", j.agent.id);",
                  "pm.collectionVariables.set(\"widget_agent_email\", j.agent.email);",
                  "pm.test(\"login_enabled arranca en false\", function(){ pm.expect(j.agent.login_enabled).to.eql(false); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/agents/:id/credential (habilitar login)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/{{widget_agent_id}}/credential",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "{{widget_agent_id}}",
                "credential"
              ]
            },
            "description": "Habilita el widget-login del agente. El password jamás vuelve en la respuesta.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"password\": \"widget-agent-password-1\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function(){ pm.response.to.have.status(200); });",
                  "const j=pm.response.json();",
                  "pm.test(\"login_enabled ahora true, sin material secreto\", function(){",
                  "  pm.expect(j.agent.login_enabled).to.eql(true);",
                  "  pm.expect(JSON.stringify(j)).to.not.include(\"password_hash\");",
                  "  pm.expect(JSON.stringify(j)).to.not.include(\"pbkdf2\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/agents/:id/credential (password requerido -> 400)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/{{widget_agent_id}}/credential",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "{{widget_agent_id}}",
                "credential"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", function(){ pm.response.to.have.status(400); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/auth/agent-login (DARK: flag off -> 404)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Origin",
                "value": "https://crm.example.com"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/agent-login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "agent-login"
              ]
            },
            "description": "La cara ddw_ está DARK en el stack (widget_auth_enabled OFF): agent-login es indistinguible de una ruta inexistente (404). Cuando el operador prenda el flag, este mismo request acuña un ddw_.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tenant_id\": \"{{tenant_id}}\",\n  \"email\": \"{{widget_agent_email}}\",\n  \"password\": \"widget-agent-password-1\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 404 (lane dark)\", function(){ pm.response.to.have.status(404); });",
                  "pm.test(\"envelope not_found\", function(){ pm.expect(pm.response.json().error.code).to.eql(\"not_found\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "DELETE /v1/agents/:id/credential (deshabilitar login)",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/agents/{{widget_agent_id}}/credential",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents",
                "{{widget_agent_id}}",
                "credential"
              ]
            },
            "description": "Deshabilita el widget-login (idempotente). Revoca cualquier ddw_ vivo del agente."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function(){ pm.response.to.have.status(200); });",
                  "pm.test(\"login_enabled de vuelta a false\", function(){ pm.expect(pm.response.json().agent.login_enabled).to.eql(false); });"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/auth/widget-config (DARK: flag off -> 404)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/widget-config?tenant_id={{tenant_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "widget-config"
              ],
              "query": [
                {
                  "key": "tenant_id",
                  "value": "{{tenant_id}}"
                }
              ]
            },
            "description": "Bootstrap PUBLICO del widget: los origins HABILITADOS del tenant — el mismo set que agent-login value-matchea, servido al frame host (frame-ancestors) y al frame (validacion de peer postMessage). Flag-gated: con WIDGET_AUTH_ENABLED off es un 404 plano, indistinguible de una ruta inexistente (la invariante dark del carril).",
            "auth": {
              "type": "noauth"
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"DARK: 404 con el flag apagado\", () => pm.expect(pm.response.code).to.eql(404));",
                  "pm.test(\"envelope not_found (invariante dark del carril)\", () => {",
                  "  pm.expect(pm.response.json().error.code).to.eql(\"not_found\");",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "23 · recordings",
      "description": "Grabaciones: metadata retención-locked (piso regulatorio 39 meses), playback firmado (el audio NUNCA transita el API), legal hold, export bulk (top-level /v1/recording-exports) y política per-tenant. Contrato: contract-recordings.yaml. No hay POST de alta (las grabaciones nacen de llamadas reales): un id sin sembrar → 404 fail-closed.",
      "item": [
        {
          "name": "GET /v1/recordings",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/recordings?limit=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "recordings"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "20"
                }
              ]
            },
            "description": "Lista filtrada, keyset-paginada, newest-first. Tenant nuevo → página vacía."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: RecordingPage {recordings[], next_cursor}\", function () {",
                  "  pm.expect(j.recordings).to.be.an(\"array\");",
                  "  pm.expect(j).to.have.property(\"next_cursor\");",
                  "  if (j.recordings.length > 0) {",
                  "    const r = j.recordings[0];",
                  "    pm.expect(r.id).to.be.a(\"string\");",
                  "    pm.expect(r.call_id).to.be.a(\"string\");",
                  "    pm.expect(r.started_at).to.be.a(\"string\");",
                  "    pm.expect(r.phone_number).to.be.a(\"string\");",
                  "    pm.expect(r.ai_used).to.be.a(\"boolean\");",
                  "    pm.expect(r.legal_hold).to.be.a(\"boolean\");",
                  "    pm.collectionVariables.set(\"recording_id\", r.id);",
                  "  }",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/recording-policy",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/recording-policy",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "recording-policy"
              ]
            },
            "description": "Política efectiva (defaults materializados en la primera lectura)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: RecordingPolicy {enabled, retention_months>=39, channels}\", function () {",
                  "  pm.expect(j.recording_policy.enabled).to.be.a(\"boolean\");",
                  "  pm.expect(j.recording_policy.retention_months).to.be.at.least(39);",
                  "  pm.expect([\"mono\", \"stereo\"]).to.include(j.recording_policy.channels);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "PUT /v1/recording-policy",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/recording-policy",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "recording-policy"
              ]
            },
            "description": "Full-set de la política singleton. retention_months se acota al piso 39.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"enabled\": true,\n  \"retention_months\": 39,\n  \"channels\": \"stereo\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: política aplicada en el piso\", function () {",
                  "  pm.expect(j.recording_policy.retention_months).to.eql(39);",
                  "  pm.expect(j.recording_policy.channels).to.eql(\"stereo\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "PUT /v1/recording-policy — bajo el piso (422)",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/recording-policy",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "recording-policy"
              ]
            },
            "description": "retention_months < 39 (piso regulatorio) → 422 unprocessable.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"retention_months\": 12\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 422 (bajo el piso de 39 meses)\", function () { pm.response.to.have.status(422); });",
                  "pm.test(\"error envelope presente\", function () {",
                  "  pm.expect(pm.response.json().error).to.be.an(\"object\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/recording-exports",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/recording-exports",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "recording-exports"
              ]
            },
            "description": "Encola export async del set que matchea los filtros; 202 + job_id (evidencia durable).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"from\": \"{{export_from}}\",\n  \"to\": \"{{export_to}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Rango RELATIVO al reloj, nunca un literal de calendario.",
                  "// core#151 (anti export-bomb) rechaza con 422 range_too_large cualquier",
                  "// rango > recording_export_max_range_days, asi que un",
                  "// 'from' fijo se vuelve 422 con solo dejar pasar el tiempo: el literal",
                  "// 2020-01-01 -> 2030-01-01 eran 3653 dias y caducaba pasara lo que pasara.",
                  "// 30 dias hacia atras cubre de sobra lo que el harness graba en una corrida.",
                  "// El valor vigente del tope lo declara core y aqui NO se copia: una copia",
                  "// en comentario se desfasa en silencio. Que estos 30 dias sigan por DEBAJO",
                  "// del tope lo ata el gate .ci/qa/check_runtime_limits_drift.py en cada PR.",
                  "const to = new Date();",
                  "const from = new Date(to.getTime() - 30 * 24 * 60 * 60 * 1000);",
                  "pm.collectionVariables.set(\"export_from\", from.toISOString());",
                  "pm.collectionVariables.set(\"export_to\", to.toISOString());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 202 (export encolado)\", function () { pm.response.to.have.status(202); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: RecordingExportJob {job_id, status}\", function () {",
                  "  pm.expect(j.export.job_id).to.be.a(\"string\");",
                  "  pm.expect([\"queued\", \"running\", \"done\", \"failed\"]).to.include(j.export.status);",
                  "});",
                  "pm.collectionVariables.set(\"recording_export_job_id\", j.export.job_id);"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/recording-exports (rango excesivo -> 422: el tope anti export-bomb SIGUE puesto)",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Rango deliberadamente excesivo, RELATIVO al reloj (nunca un literal:",
                  "// un literal caduca solo, que es como se rompio esta coleccion).",
                  "// 400 dias > recording_export_max_range_days. El valor vigente del tope lo",
                  "// declara core y aqui NO se copia: una copia en comentario se desfasa en",
                  "// silencio. Que estos 400 dias sigan por ENCIMA del tope (y por tanto que",
                  "// esta peticion siga comprobando algo) lo ata el gate OFFLINE",
                  "// .ci/qa/check_runtime_limits_drift.py en cada PR.",
                  "const to = new Date();",
                  "const from = new Date(to.getTime() - 400 * 24 * 60 * 60 * 1000);",
                  "pm.collectionVariables.set(\"export_from_oversized\", from.toISOString());",
                  "pm.collectionVariables.set(\"export_to_oversized\", to.toISOString());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// ROJO AQUI = SE AFLOJO EL TOPE, no = se rompio el export.",
                  "// El export normal lo cubre la peticion de arriba; esta cubre la otra",
                  "// mitad del contrato: un export desmedido DEBE rechazarse. Sin ella,",
                  "// subir el limite, dejarlo mal en una variable o borrar",
                  "// validate_export_range/1 entero pasa en silencio (MT-REP-03).",
                  "pm.test(\"status 422 (rango excesivo rechazado)\", function () { pm.response.to.have.status(422); });",
                  "const j = pm.response.json();",
                  "// El CODIGO, no solo el status: un 422 por otra causa no vale como prueba",
                  "// de que el tope siga vivo.",
                  "pm.test(\"error.code == range_too_large (es el tope, no otro 422)\", function () {",
                  "  pm.expect(j.error.code).to.eql(\"range_too_large\");",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/recording-exports",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "recording-exports"
              ]
            },
            "description": "Caso NEGATIVO del tope anti export-bomb (core#151, MT-REP-03): un rango mayor que recording_export_max_range_days (valor vigente declarado por core, no copiado aqui) debe rechazarse con 422 range_too_large. Si esta peticion se pone en rojo, lo que se aflojo es el tope, no el export.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"from\": \"{{export_from_oversized}}\",\n  \"to\": \"{{export_to_oversized}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /v1/recording-exports/:job_id",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/recording-exports/{{recording_export_job_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "recording-exports",
                "{{recording_export_job_id}}"
              ]
            },
            "description": "Poll del job; done → url firmada. job_id cross-tenant/desconocido → 404."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// recording_export_job_id vacío (no sembrado por una corrida real) → sembramos un UUID",
                  "// aleatorio: un segmento vacío caería en el catch-all del router",
                  "// ('no such route') sin ejercitar la ruta real; así el 404 es el del",
                  "// endpoint. Con la variable ya sembrada, la request usa el id real.",
                  "if (!pm.collectionVariables.get(\"recording_export_job_id\")) {",
                  "  pm.collectionVariables.set(\"recording_export_job_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404 (poll del job o id desconocido)\", function () {",
                  "  pm.expect([200, 404]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: RecordingExportJob\", function () {",
                  "    pm.expect(j.export.job_id).to.be.a(\"string\");",
                  "    pm.expect([\"queued\", \"running\", \"done\", \"failed\"]).to.include(j.export.status);",
                  "  });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/recordings/:id",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/recordings/{{recording_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "recordings",
                "{{recording_id}}"
              ]
            },
            "description": "Metadata completa (evidencia): consent, gates_at_dial, decision_ref, storage, legal hold. Id desconocido → 404."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// recording_id vacío (no sembrado por una corrida real) → sembramos un UUID",
                  "// aleatorio: un segmento vacío caería en el catch-all del router",
                  "// ('no such route') sin ejercitar la ruta real; así el 404 es el del",
                  "// endpoint. Con la variable ya sembrada, la request usa el id real.",
                  "if (!pm.collectionVariables.get(\"recording_id\")) {",
                  "  pm.collectionVariables.set(\"recording_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404 (detalle o id desconocido)\", function () {",
                  "  pm.expect([200, 404]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: Recording {id, gates_at_dial[], storage}\", function () {",
                  "    pm.expect(j.recording.id).to.be.a(\"string\");",
                  "    pm.expect(j.recording.gates_at_dial).to.be.an(\"array\");",
                  "    pm.expect(j.recording.storage).to.be.an(\"object\");",
                  "  });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/recordings/:id/playback-url",
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{baseUrl}}/v1/recordings/{{recording_id}}/playback-url",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "recordings",
                "{{recording_id}}",
                "playback-url"
              ]
            },
            "description": "Mint de URL firmada corta (R2/S3); el audio no transita el API. Id desconocido → 404; sin audio → 409."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// recording_id vacío (no sembrado por una corrida real) → sembramos un UUID",
                  "// aleatorio: un segmento vacío caería en el catch-all del router",
                  "// ('no such route') sin ejercitar la ruta real; así el 404 es el del",
                  "// endpoint. Con la variable ya sembrada, la request usa el id real.",
                  "if (!pm.collectionVariables.get(\"recording_id\")) {",
                  "  pm.collectionVariables.set(\"recording_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|409 (mint o id desconocido/sin audio)\", function () {",
                  "  pm.expect([200, 404, 409]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: RecordingPlaybackUrl {url, expires_at}\", function () {",
                  "    pm.expect(j.url).to.be.a(\"string\");",
                  "    pm.expect(j.expires_at).to.be.a(\"string\");",
                  "  });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "PUT /v1/recordings/:id/legal-hold",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/recordings/{{recording_id}}/legal-hold",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "recordings",
                "{{recording_id}}",
                "legal-hold"
              ]
            },
            "description": "Set/lift idempotente del legal hold. active:true exige reason. Scope dd_ (ddw_ → 403). Id desconocido → 404.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"active\": true,\n  \"reason\": \"Postman integration hold \\u2014 synthetic\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// recording_id vacío (no sembrado por una corrida real) → sembramos un UUID",
                  "// aleatorio: un segmento vacío caería en el catch-all del router",
                  "// ('no such route') sin ejercitar la ruta real; así el 404 es el del",
                  "// endpoint. Con la variable ya sembrada, la request usa el id real.",
                  "if (!pm.collectionVariables.get(\"recording_id\")) {",
                  "  pm.collectionVariables.set(\"recording_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404 (hold seteado o id desconocido)\", function () {",
                  "  pm.expect([200, 404]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: legal_hold activo\", function () { pm.expect(j.recording.legal_hold).to.eql(true); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "24 · voice-ai",
      "description": "Voice AI: agentes (builder + policy versionada), conversaciones live/histórico + transcript/outcome, takeover del supervisor (reusa la maquinaria de supervisión), kill-switch pause/resume/status del AI-dialing, métricas del día y elegibilidad de campañas. Contrato: contract-voiceai.yaml (+ GET /v1/ai/status y GET /v1/ai/eligibility). Las conversaciones nacen de llamadas reales: id sin sembrar → 404.",
      "item": [
        {
          "name": "GET /v1/ai/agents",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/agents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "agents"
              ]
            },
            "description": "Roster de agentes AI del tenant."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: {ai_agents[]}\", function () {",
                  "  pm.expect(j.ai_agents).to.be.an(\"array\");",
                  "  if (j.ai_agents.length > 0) {",
                  "    const a = j.ai_agents[0];",
                  "    pm.expect(a.id).to.be.a(\"string\");",
                  "    pm.expect(a.name).to.be.a(\"string\");",
                  "    pm.expect([\"draft\", \"live\", \"paused\", \"retired\"]).to.include(a.status);",
                  "    pm.expect(a.policy_version).to.be.a(\"number\");",
                  "  }",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/ai/agents",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/ai/agents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "agents"
              ]
            },
            "description": "Alta de agente draft (policy_version 1). disclosure.text mandado; regla human_requested siempre presente y locked.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"postman-ai-{{$timestamp}}\",\n  \"voice\": {\n    \"language\": \"en-US\",\n    \"voice_id\": \"neural-3-female\",\n    \"pace\": 0.5\n  },\n  \"disclosure\": {\n    \"text\": \"This call is from an automated assistant calling on behalf of the account holder. This call is recorded.\"\n  },\n  \"policy\": {\n    \"max_settlement_pct\": 70,\n    \"min_payment_cents\": 5000,\n    \"max_plan_months\": 12,\n    \"take_payments\": \"card_on_file\",\n    \"prohibited\": [\n      \"legal threats\",\n      \"third-party disclosure\"\n    ]\n  },\n  \"handoff_rules\": [\n    {\n      \"trigger\": \"keywords\",\n      \"params\": {\n        \"keywords\": [\n          \"attorney\",\n          \"lawyer\",\n          \"dispute\",\n          \"cease\",\n          \"bankruptcy\"\n        ]\n      },\n      \"action\": \"transfer\",\n      \"severity\": \"hard\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: draft v1, human_requested handoff locked\", function () {",
                  "  pm.expect(j.ai_agent.id).to.be.a(\"string\");",
                  "  pm.expect(j.ai_agent.status).to.eql(\"draft\");",
                  "  pm.expect(j.ai_agent.policy_version).to.eql(1);",
                  "  pm.expect(j.ai_agent.disclosure.mandated).to.eql(true);",
                  "  const hr = (j.ai_agent.handoff_rules || []).find(function (r) { return r.trigger === \"human_requested\"; });",
                  "  pm.expect(hr, \"regla human_requested presente\").to.be.an(\"object\");",
                  "  pm.expect(hr.locked).to.eql(true);",
                  "});",
                  "pm.collectionVariables.set(\"ai_agent_id\", j.ai_agent.id);",
                  "pm.collectionVariables.set(\"ai_agent_name\", j.ai_agent.name);"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/ai/agents — sin disclosure (400)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/ai/agents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "agents"
              ]
            },
            "description": "Create sin disclosure.text (mandado) → 400.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"postman-ai-nodisc-{{$timestamp}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400 (disclosure.text requerido)\", function () { pm.response.to.have.status(400); });",
                  "pm.test(\"error envelope presente\", function () { pm.expect(pm.response.json().error).to.be.an(\"object\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/ai/agents — nombre duplicado (409)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/ai/agents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "agents"
              ]
            },
            "description": "Reusa el nombre del agente recién creado → 409 conflict (400 si el nombre no quedó sembrado).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"{{ai_agent_name}}\",\n  \"disclosure\": {\n    \"text\": \"duplicate-name probe\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 409|400 (nombre duplicado → 409; sin sembrar → 400)\", function () {",
                  "  pm.expect([400, 409]).to.include(pm.response.code);",
                  "});",
                  "pm.test(\"error envelope presente\", function () { pm.expect(pm.response.json().error).to.be.an(\"object\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/ai/agents/:id",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/agents/{{ai_agent_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "agents",
                "{{ai_agent_id}}"
              ]
            },
            "description": "Detalle del agente. Id desconocido → 404."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// ai_agent_id vacío (no sembrado por una corrida real) → sembramos un UUID",
                  "// aleatorio: un segmento vacío caería en el catch-all del router",
                  "// ('no such route') sin ejercitar la ruta real; así el 404 es el del",
                  "// endpoint. Con la variable ya sembrada, la request usa el id real.",
                  "if (!pm.collectionVariables.get(\"ai_agent_id\")) {",
                  "  pm.collectionVariables.set(\"ai_agent_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404\", function () { pm.expect([200, 404]).to.include(pm.response.code); });",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: AiAgent\", function () {",
                  "    pm.expect(j.ai_agent.id).to.be.a(\"string\");",
                  "    pm.expect(j.ai_agent.policy_version).to.be.a(\"number\");",
                  "  });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "PATCH /v1/ai/agents/:id",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/ai/agents/{{ai_agent_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "agents",
                "{{ai_agent_id}}"
              ]
            },
            "description": "Merge-update; cada cambio aceptado bumpea policy_version (monotónico). Disclosure inmutable post-live → 422.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"policy\": {\n    \"max_settlement_pct\": 65\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// ai_agent_id vacío (no sembrado por una corrida real) → sembramos un UUID",
                  "// aleatorio: un segmento vacío caería en el catch-all del router",
                  "// ('no such route') sin ejercitar la ruta real; así el 404 es el del",
                  "// endpoint. Con la variable ya sembrada, la request usa el id real.",
                  "if (!pm.collectionVariables.get(\"ai_agent_id\")) {",
                  "  pm.collectionVariables.set(\"ai_agent_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|409|422\", function () {",
                  "  pm.expect([200, 404, 409, 422]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: policy_version bumpeada\", function () { pm.expect(j.ai_agent.policy_version).to.be.at.least(2); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/ai/agents/:id/test",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/ai/agents/{{ai_agent_id}}/test",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "agents",
                "{{ai_agent_id}}",
                "test"
              ]
            },
            "description": "Conversación SANDBOX (sin llamada real, sin fila ai_conversations). Id desconocido → 404.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"message\": \"Yes \\u2014 I can pay $150 on the 27th.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// ai_agent_id vacío (no sembrado por una corrida real) → sembramos un UUID",
                  "// aleatorio: un segmento vacío caería en el catch-all del router",
                  "// ('no such route') sin ejercitar la ruta real; así el 404 es el del",
                  "// endpoint. Con la variable ya sembrada, la request usa el id real.",
                  "if (!pm.collectionVariables.get(\"ai_agent_id\")) {",
                  "  pm.collectionVariables.set(\"ai_agent_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Camino VIVO en el stack e2e: core proxya al sandbox de voice-ai",
                  "// (POST /sandbox/messages :8767, bearer service-token \\u2014 voice-ai 430f08f)",
                  "// v\\u00eda VOICEAI_RUNTIME_URL, y el LocalLLM responde \\u2192 200 {session_id, reply}.",
                  "// 404 = agente inexistente. 503 SOLO como rama documentada fail-closed",
                  "// (un env sin runtime deja VOICEAI_RUNTIME_URL vac\\u00eda \\u2192 sandbox_unavailable).",
                  "pm.test(\"status 200|404|503\", function () { pm.expect([200, 404, 503]).to.include(pm.response.code); });",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: AiTestReply {session_id, reply}\", function () {",
                  "    pm.expect(j.session_id).to.be.a(\"string\").and.to.not.be.empty;",
                  "    pm.expect(j.reply).to.be.a(\"string\").and.to.not.be.empty;",
                  "  });",
                  "}",
                  "if (pm.response.code === 503) {",
                  "  pm.test(\"503 fail-closed-sin-runtime: sandbox_unavailable\", function () {",
                  "    pm.expect(pm.response.json().error.code).to.eql(\"sandbox_unavailable\");",
                  "  });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "DELETE /v1/ai/agents/:id",
          "request": {
            "method": "DELETE",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/agents/{{ai_agent_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "agents",
                "{{ai_agent_id}}"
              ]
            },
            "description": "Soft-retire (status retired): la fila y sus snapshots sobreviven. Id desconocido → 404."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// ai_agent_id vacío (no sembrado por una corrida real) → sembramos un UUID",
                  "// aleatorio: un segmento vacío caería en el catch-all del router",
                  "// ('no such route') sin ejercitar la ruta real; así el 404 es el del",
                  "// endpoint. Con la variable ya sembrada, la request usa el id real.",
                  "if (!pm.collectionVariables.get(\"ai_agent_id\")) {",
                  "  pm.collectionVariables.set(\"ai_agent_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404\", function () { pm.expect([200, 404]).to.include(pm.response.code); });",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: agente retired\", function () { pm.expect(j.ai_agent.status).to.eql(\"retired\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/ai/conversations",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/conversations?limit=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "conversations"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "20"
                }
              ]
            },
            "description": "Conversaciones cerradas, keyset-paginadas."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: {ai_conversations[], next_cursor}\", function () {",
                  "  pm.expect(j.ai_conversations).to.be.an(\"array\");",
                  "  pm.expect(j).to.have.property(\"next_cursor\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/ai/conversations?active=true",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/conversations?active=true",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "conversations"
              ],
              "query": [
                {
                  "key": "active",
                  "value": "true"
                }
              ]
            },
            "description": "Feed LIVE (en curso), unpaginated (next_cursor null)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: feed activo (next_cursor null)\", function () {",
                  "  pm.expect(j.ai_conversations).to.be.an(\"array\");",
                  "  pm.expect(j.next_cursor).to.be.null;",
                  "});",
                  "if (j.ai_conversations.length > 0) {",
                  "  pm.collectionVariables.set(\"ai_conversation_id\", j.ai_conversations[0].id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/ai/conversations/:id",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/conversations/{{ai_conversation_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "conversations",
                "{{ai_conversation_id}}"
              ]
            },
            "description": "Conversación con transcript + outcome. Id desconocido → 404."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// ai_conversation_id vacío (no sembrado por una corrida real) → sembramos un UUID",
                  "// aleatorio: un segmento vacío caería en el catch-all del router",
                  "// ('no such route') sin ejercitar la ruta real; así el 404 es el del",
                  "// endpoint. Con la variable ya sembrada, la request usa el id real.",
                  "if (!pm.collectionVariables.get(\"ai_conversation_id\")) {",
                  "  pm.collectionVariables.set(\"ai_conversation_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404\", function () { pm.expect([200, 404]).to.include(pm.response.code); });",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: AiConversation\", function () { pm.expect(j.ai_conversation.id).to.be.a(\"string\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/ai/conversations/:id/takeover",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/ai/conversations/{{ai_conversation_id}}/takeover",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "conversations",
                "{{ai_conversation_id}}",
                "takeover"
              ]
            },
            "description": "Takeover del supervisor (evidence-first, reusa Dialer.Supervision). Conv viva → 200; sin conv → 404; switch/audit caído → 502/503.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supervisor_ext\": \"{{golden_agent_ext}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// ai_conversation_id vacío (no sembrado por una corrida real) → sembramos un UUID",
                  "// aleatorio: un segmento vacío caería en el catch-all del router",
                  "// ('no such route') sin ejercitar la ruta real; así el 404 es el del",
                  "// endpoint. Con la variable ya sembrada, la request usa el id real.",
                  "if (!pm.collectionVariables.get(\"ai_conversation_id\")) {",
                  "  pm.collectionVariables.set(\"ai_conversation_id\", pm.variables.replaceIn(\"{{$guid}}\"));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200|404|409|502|503 (conv viva → transfer; si no, fail-closed)\", function () {",
                  "  pm.expect([200, 404, 409, 502, 503]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const j = pm.response.json();",
                  "  pm.test(\"shape 200: supervision presente\", function () { pm.expect(j.supervision).to.be.an(\"object\"); });",
                  "}",
                  "if (pm.response.code === 404) {",
                  "  pm.test(\"404 del endpoint real (no catch-all 'no such route')\", function () {",
                  "    pm.expect(pm.response.json().error.message).to.not.eql(\"no such route\");",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/ai/pause",
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/pause",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "pause"
              ]
            },
            "description": "Kill-switch: pausa TODO el AI-dialing del tenant (idempotente). In-flight sigue."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: ai_dialing paused\", function () {",
                  "  pm.expect(j.ai_dialing).to.eql(\"paused\");",
                  "  pm.expect(j).to.have.property(\"paused_at\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/ai/status",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "status"
              ]
            },
            "description": "Estado del kill-switch: {ai_dialing, paused_at?, paused_by?}."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: ai_dialing active|paused\", function () {",
                  "  pm.expect([\"active\", \"paused\"]).to.include(j.ai_dialing);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/ai/resume",
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/resume",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "resume"
              ]
            },
            "description": "Inverso idempotente de pause: reanuda el AI-dialing."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: ai_dialing active\", function () { pm.expect(j.ai_dialing).to.eql(\"active\"); });"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/ai/metrics/today",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/metrics/today",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "metrics",
                "today"
              ]
            },
            "description": "Agregado del día UTC (ai_conversations + outcomes + meter ai_usage). handled_delta_pct opcional."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: AiMetricsToday\", function () {",
                  "  pm.expect(j).to.be.an(\"object\");",
                  "  if (j.handled !== undefined) pm.expect(j.handled).to.be.a(\"number\");",
                  "  if (j.containment_rate !== undefined) pm.expect(j.containment_rate).to.be.a(\"number\");",
                  "  if (j.avg_handle_secs !== undefined) pm.expect(j.avg_handle_secs).to.be.a(\"number\");",
                  "  if (j.handoffs !== undefined) pm.expect(j.handoffs).to.be.an(\"object\");",
                  "  if (j.ai_minutes !== undefined) pm.expect(j.ai_minutes).to.be.a(\"number\");",
                  "  // handled_delta_pct is nullable per openapi v0.15.0 (oneOf number|null): null when",
                  "  // yesterday had no AI conversations up to this time-of-day. Assert number only when present.",
                  "  if (j.handled_delta_pct !== undefined && j.handled_delta_pct !== null) pm.expect(j.handled_delta_pct).to.be.a(\"number\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/ai/eligibility",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/ai/eligibility",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ai",
                "eligibility"
              ]
            },
            "description": "Elegibilidad AI por campaña: {campaigns:[{campaign_id, name, eligible, reason}]}."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: eligibility[] con veredicto por campaña\", function () {",
                  "  pm.expect(j.eligibility).to.be.an(\"array\");",
                  "  if (j.eligibility.length > 0) {",
                  "    const c = j.eligibility[0];",
                  "    pm.expect(c).to.have.property(\"campaign_id\");",
                  "    pm.expect(c.name).to.be.a(\"string\");",
                  "    pm.expect(c.eligible).to.be.a(\"boolean\");",
                  "    pm.expect(c).to.have.property(\"reason\");",
                  "  }",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "25 · ws-ticket",
      "item": [
        {
          "name": "POST /v1/ws/ticket",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/ws/ticket",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ws",
                "ticket"
              ]
            },
            "description": "Acuna el credencial EFIMERO y de UN SOLO USO con el que se abre `GET /v1/ws` (MT-SEC-05 / MT-SEC-10).\n\nEl bearer de larga vida viaja donde SI puede — la cabecera `Authorization` de esta peticion, que la coleccion pone a nivel raiz. Lo unico que acaba viajando en la URL del handshake (donde los navegadores obligan a poner el credencial, y donde queda en logs, historial y `Referer`) es el ticket, que caduca en segundos y se quema al primer canje.\n\nEl ticket hereda el carril EXACTO del bearer que lo pidio y lo anuncia en el prefijo: `ddtt_` tenant, `ddta_` agente. Aqui el bearer es el token de tenant de la coleccion, asi que el ticket tiene que salir `ddtt_`: el borde publico admite en `/v1/ws` SOLO el carril de agente mirando ese prefijo, de modo que un `ddta_` acunado por un bearer de tenant seria un socket con el suelo entero de la empresa detras.\n\nEl canje NO se ejercita aqui: es un handshake WebSocket, fuera del alcance de newman. Sus propiedades (un solo uso incluso con dos canjes concurrentes, y caducidad) viven en test/db/ws_ticket_test.exs de core."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "const j = pm.response.json();",
                  "pm.test(\"el ticket sale en el carril del bearer que lo pidio (ddtt_, no ddta_)\", () => {",
                  "  pm.expect(j.ticket).to.be.a(\"string\").and.to.match(/^ddtt_/);",
                  "});",
                  "pm.test(\"es efimero: vida en segundos, no en horas\", () => {",
                  "  pm.expect(j.expires_in).to.be.a(\"number\").and.to.be.above(0);",
                  "  pm.expect(j.expires_in).to.be.at.most(120);",
                  "  const restan = (new Date(j.expires_at) - Date.now()) / 1000;",
                  "  pm.expect(restan).to.be.above(0);",
                  "  pm.expect(restan).to.be.at.most(j.expires_in + 30);",
                  "});",
                  "pm.test(\"no devuelve el hash almacenado\", () => {",
                  "  pm.expect(j).to.not.have.property(\"token_hash\");",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "26 · suppressions",
      "description": "Carga de la lista de supresion INTERNA del tenant (MT-SEC-60). Las filas entran en la lista DNC `internal_tenant` que el gate PRE-DIAL ya consulta en cada llamada y cada SMS — la misma que escribe el `STOP` de SMS. Tercera arista del triangulo de docs: sin este request, `POST /v1/suppressions/import` viviria en la spec publicable sin que nadie la ejercite.",
      "item": [
        {
          "name": "POST /v1/suppressions/import",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Numeros NUEVOS por corrida: `recorded` solo cuenta filas realmente",
                  "// escritas, asi que reusar un fijo haria que la 2a corrida contra el",
                  "// mismo stack leyera 0 y el test fallara por solapamiento, no por bug.",
                  "const rnd = () => String(Math.floor(Math.random() * 9000000) + 1000000);",
                  "pm.variables.set(\"sup_e164\", \"+1312\" + rnd());",
                  "pm.variables.set(\"sup_bare\", \"312\" + rnd());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: los 5 campos que el contrato declara required\", () => {",
                  "  [\"received\", \"accepted\", \"recorded\", \"rejected_count\"].forEach(k =>",
                  "    pm.expect(j[k], k).to.be.a(\"number\"));",
                  "  pm.expect(j.rejected).to.be.an(\"array\");",
                  "});",
                  "pm.test(\"comentario y linea en blanco NO cuentan como filas de datos\", () => {",
                  "  pm.expect(j.received).to.eql(3);",
                  "});",
                  "pm.test(\"la NANP desnuda normaliza igual que la E.164: 2 aceptadas\", () => {",
                  "  pm.expect(j.accepted).to.eql(2);",
                  "});",
                  "pm.test(\"numeros nuevos por corrida: lo aceptado se escribe\", () => {",
                  "  pm.expect(j.recorded).to.eql(j.accepted);",
                  "});",
                  "pm.test(\"la fila mala se REPORTA, no aborta el documento\", () => {",
                  "  pm.expect(j.rejected_count).to.eql(1);",
                  "  pm.expect(j.rejected).to.have.lengthOf(1);",
                  "  const r = j.rejected[0];",
                  "  pm.expect(r.line, \"linea 1-based del documento\").to.be.a(\"number\").and.to.be.above(0);",
                  "  pm.expect(r.value).to.be.a(\"string\");",
                  "  pm.expect(r.reason).to.be.a(\"string\");",
                  "});",
                  "pm.test(\"el valor rechazado vuelve ENMASCARADO, no en claro\", () => {",
                  "  pm.expect(j.rejected[0].value).to.not.eql(\"12345\");",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "text/csv"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "number,reason\n{{sup_e164}},client internal DNC\n{{sup_bare}},litigation hold\n# una linea de comentario se salta\n\n12345,numero corto: fila malformada a proposito\n",
              "options": {
                "raw": {
                  "language": "text"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/suppressions/import",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "suppressions",
                "import"
              ]
            },
            "description": "Sube el DNC interno del tenant como `text/csv`, una fila por numero (`number[,reason]`).\n\nEl documento de este request ejercita a proposito las cuatro clases de fila que la spec declara: una E.164, una NANP desnuda de 10 digitos (que debe normalizarse a `+1XXXXXXXXXX`), una linea de comentario y una en blanco (ambas se saltan), y una fila malformada.\n\nLa fila malformada es la que importa: la spec promete que un error tipografico NO es fatal — se REPORTA y el resto del fichero entra igual. Rechazar el documento entero por una fila dejaria marcables a los demas consumidores, que es la direccion CARA del error. Por eso el 200 no significa que todo se acepto: `rejected_count` es el autoritativo."
          }
        }
      ]
    },
    {
      "name": "27 · artificial-voice-disclosure",
      "description": "Divulgacion de voz artificial del carril NO-IA (core#211). El orden importa: el PUT valido va PRIMERO para que el GET tenga algo que leer. El caso 404 (tenant sin divulgacion) NO se ejercita aqui a proposito: la coleccion corre contra un stack cuyo tenant sobrevive entre corridas, asi que un 404 solo pasaria la primera vez — es justo la no-idempotencia de docs#67. Su cobertura vive en la suite :db de core.",
      "item": [
        {
          "name": "PUT /v1/artificial-voice-disclosure",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/artificial-voice-disclosure",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "artificial-voice-disclosure"
              ]
            },
            "description": "Alta del texto. Upsert por tenant: una sola fila viva, gana la ultima. source y actor los pone el SERVIDOR y no se mandan.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"text\": \"Hola, esta llamada usa un asistente virtual automatizado de Acme Recovery.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: ArtificialVoiceDisclosure {text, source, actor, valid}\", function () {",
                  "  pm.expect(j.artificial_voice_disclosure.text).to.be.a(\"string\");",
                  "  pm.expect(j.artificial_voice_disclosure.valid).to.eql(true);",
                  "});",
                  "pm.test(\"la procedencia la pone el servidor, no el cuerpo\", function () {",
                  "  pm.expect(j.artificial_voice_disclosure.source).to.eql(\"api\");",
                  "  pm.expect(j.artificial_voice_disclosure.actor).to.be.a(\"string\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/artificial-voice-disclosure",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/artificial-voice-disclosure",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "artificial-voice-disclosure"
              ]
            },
            "description": "Lectura de lo almacenado. Un tenant SIN divulgacion contesta 404, nunca un 200 con cuerpo nulo."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"devuelve el texto que acaba de escribir el PUT\", function () {",
                  "  pm.expect(j.artificial_voice_disclosure.text).to.include(\"automatizado\");",
                  "  pm.expect(j.artificial_voice_disclosure.valid).to.eql(true);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "PUT /v1/artificial-voice-disclosure — texto que no divulga (400)",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/artificial-voice-disclosure",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "artificial-voice-disclosure"
              ]
            },
            "description": "El contenido lo juzga el MISMO validador del carril de IA. Un texto que nunca dice que la voz es automatica es 400 y no se almacena nada.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"text\": \"Hello, this is Acme Recovery calling about your account.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", function () { pm.response.to.have.status(400); });",
                  "const j = pm.response.json();",
                  "pm.test(\"el error nombra que el texto debe identificarse como automatizado\", function () {",
                  "  pm.expect(j.error.message).to.include(\"automated\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "PUT /v1/artificial-voice-disclosure — la procedencia no es del cliente (400)",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/artificial-voice-disclosure",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "artificial-voice-disclosure"
              ]
            },
            "description": "source y actor son procedencia del servidor. Mandarlos es 400 nombrando la clave, no un descarte silencioso: un cliente que cree haber firmado una procedencia que no viajo es peor que uno que recibe un error.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"text\": \"Hola, esta llamada usa un asistente virtual automatizado de Acme Recovery.\",\n  \"source\": \"migration\",\n  \"actor\": \"someone-else\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", function () { pm.response.to.have.status(400); });",
                  "const j = pm.response.json();",
                  "pm.test(\"el error nombra las claves rechazadas\", function () {",
                  "  pm.expect(j.error.message).to.include(\"source\");",
                  "  pm.expect(j.error.message).to.include(\"actor\");",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "28 · planned-shifts",
      "description": "Turno planificado (MT-ROAD-R11 FASE 2b, ADR #113 de core). Rutas de gestión, tenant scope. Los dos requests están aquí porque el triángulo router↔OpenAPI↔Postman se gatea en cada PR de este repo contra core@main y esas rutas ya viven allí (core#896); integration-tests#266 las trae también, reescribiendo el fichero entero.",
      "item": [
        {
          "name": "POST /v1/planned-shifts",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/planned-shifts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "planned-shifts"
              ]
            },
            "description": "Crea un turno planificado para el agente del golden-flow. tenant_id nunca viaja en el cuerpo: lo pone el principal autenticado.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"agent_id\": \"{{postman_agent_id}}\",\n  \"start_time\": \"{{planned_shift_start}}\",\n  \"end_time\": \"{{planned_shift_end}}\",\n  \"planned_state\": \"available\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Un turno de una hora que empieza dentro de un día: el agente es el del golden-flow.",
                  "const start = new Date(Date.now() + 24 * 3600 * 1000);",
                  "const end = new Date(start.getTime() + 3600 * 1000);",
                  "pm.collectionVariables.set(\"planned_shift_start\", start.toISOString());",
                  "pm.collectionVariables.set(\"planned_shift_end\", end.toISOString());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: id uuid\", function () { pm.expect(j.id).to.match(/^[0-9a-f-]{36}$/); });",
                  "pm.collectionVariables.set(\"planned_shift_id\", j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/planned-shifts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/planned-shifts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "planned-shifts"
              ]
            },
            "description": "Lista los turnos planificados del tenant."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: shifts array con el turno recién creado\", function () {",
                  "  pm.expect(j.shifts).to.be.an(\"array\");",
                  "  const mine = j.shifts.find(s => s.id === pm.collectionVariables.get(\"planned_shift_id\"));",
                  "  pm.expect(mine, \"el turno creado aparece\").to.be.an(\"object\");",
                  "  pm.expect(mine.planned_state).to.eql(\"available\");",
                  "  pm.expect(mine.agent_id).to.eql(pm.collectionVariables.get(\"postman_agent_id\"));",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "29 · webhooks",
      "description": "Webhooks salientes para CRMs (core#959, MT-CTI-04 fase 1, ADR #121): registro de endpoints. El secreto de firma viaja SOLO en el 201 del alta y en el 200 del rotate; aquí se asserta su forma y nunca se persiste (regla de las api_keys). La URL es un destino inerte que el allowlist del operador del compose (`SMS_EGRESS_ALLOWLIST`, entrada `crm.example.test`) exime del juicio de DNS: solo se guarda, nunca se llama en esta fase.",
      "item": [
        {
          "name": "POST /v1/webhooks",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks"
              ]
            },
            "description": "Alta. `tenant_id` y `secret` nunca viajan en el cuerpo (400 si se envían). El secreto se muestra UNA vez, fuera del objeto `webhook`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://crm.example.test/hooks/dialerdigital\",\n  \"events\": [\"call.ended\", \"promise.recorded\"]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", function () { pm.response.to.have.status(201); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: webhook enabled con url y events\", function () {",
                  "  pm.expect(j.webhook.id).to.match(/^[0-9a-f-]{36}$/);",
                  "  pm.expect(j.webhook.url).to.eql(\"https://crm.example.test/hooks/dialerdigital\");",
                  "  pm.expect(j.webhook.events).to.eql([\"call.ended\", \"promise.recorded\"]);",
                  "  pm.expect(j.webhook.enabled).to.eql(true);",
                  "  pm.expect(j.webhook.disabled_reason).to.eql(null);",
                  "  pm.expect(j.webhook.previous_secret_expires_at).to.eql(null);",
                  "});",
                  "pm.test(\"secreto una sola vez: whsec_ de 49 chars fuera del objeto, secret_hint = ultimos 4\", function () {",
                  "  pm.expect(j.secret).to.be.a(\"string\");",
                  "  pm.expect(j.secret).to.match(/^whsec_[A-Za-z0-9_-]{43}$/);",
                  "  pm.expect(j.webhook.secret_hint).to.eql(j.secret.slice(-4));",
                  "  pm.expect(j.webhook).to.not.have.property(\"secret\");",
                  "  pm.expect(j.webhook).to.not.have.property(\"tenant_id\");",
                  "});",
                  "// Solo el id y el hint se guardan: el secreto NO se persiste (regla de las api_keys).",
                  "pm.collectionVariables.set(\"webhook_id\", j.webhook.id);",
                  "pm.collectionVariables.set(\"webhook_secret_hint\", j.webhook.secret_hint);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/webhooks",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks"
              ]
            },
            "description": "Listado: hint, nunca el secreto."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"lista contiene el endpoint recien creado, sin secreto\", function () {",
                  "  const mine = j.webhooks.find(w => w.id === pm.collectionVariables.get(\"webhook_id\"));",
                  "  pm.expect(mine).to.be.an(\"object\");",
                  "  pm.expect(mine.secret_hint).to.eql(pm.collectionVariables.get(\"webhook_secret_hint\"));",
                  "  j.webhooks.forEach(w => pm.expect(w).to.not.have.property(\"secret\"));",
                  "  pm.expect(pm.response.text()).to.not.include(\"whsec_\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/webhooks/{{webhook_id}}",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/{{webhook_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                "{{webhook_id}}"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: el mismo endpoint, hint y sin secreto\", function () {",
                  "  pm.expect(j.webhook.id).to.eql(pm.collectionVariables.get(\"webhook_id\"));",
                  "  pm.expect(j.webhook.secret_hint).to.eql(pm.collectionVariables.get(\"webhook_secret_hint\"));",
                  "  pm.expect(j.webhook).to.not.have.property(\"secret\");",
                  "  pm.expect(pm.response.text()).to.not.include(\"whsec_\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "PATCH /v1/webhooks/{{webhook_id}}",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/{{webhook_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                "{{webhook_id}}"
              ]
            },
            "description": "Cambia la suscripcion; el secreto no es campo del PATCH.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\"call.ended\", \"sms.received\"]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"events actualizados, hint intacto\", function () {",
                  "  pm.expect(j.webhook.events).to.eql([\"call.ended\", \"sms.received\"]);",
                  "  pm.expect(j.webhook.secret_hint).to.eql(pm.collectionVariables.get(\"webhook_secret_hint\"));",
                  "  pm.expect(j.webhook).to.not.have.property(\"secret\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/webhooks/{{webhook_id}}/rotate-secret",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/{{webhook_id}}/rotate-secret",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                "{{webhook_id}}",
                "rotate-secret"
              ]
            },
            "description": "Rotacion: secreto NUEVO una vez; el anterior verifica hasta previous_secret_expires_at (24 h)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"secreto nuevo una vez, hint cambia, ventana abierta\", function () {",
                  "  pm.expect(j.secret).to.match(/^whsec_[A-Za-z0-9_-]{43}$/);",
                  "  pm.expect(j.webhook.secret_hint).to.eql(j.secret.slice(-4));",
                  "  pm.expect(j.webhook.secret_hint).to.not.eql(pm.collectionVariables.get(\"webhook_secret_hint\"));",
                  "  pm.expect(j.webhook.previous_secret_expires_at).to.be.a(\"string\");",
                  "  const closes = new Date(j.webhook.previous_secret_expires_at).getTime() - Date.now();",
                  "  pm.expect(closes).to.be.within(23 * 3600 * 1000, 25 * 3600 * 1000);",
                  "  pm.expect(j.webhook).to.not.have.property(\"secret\");",
                  "});",
                  "pm.collectionVariables.set(\"webhook_secret_hint\", j.webhook.secret_hint);"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/webhooks/{{webhook_id}}/deliveries",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/{{webhook_id}}/deliveries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                "{{webhook_id}}",
                "deliveries"
              ]
            },
            "description": "Evidencia append-only del endpoint, mas nuevas primero y paginada por keyset. El endpoint creado en esta corrida todavia no tiene deliveries: la pagina vacia con `next_cursor: null` ES el contrato (200, nunca 404), y por eso el assert es determinista."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"shape: pagina keyset con deliveries y next_cursor\", function () {",
                  "  pm.expect(j.deliveries).to.be.an(\"array\");",
                  "  pm.expect(j).to.have.property(\"next_cursor\");",
                  "  const STATUSES = [\"pending\", \"retry\", \"delivered\", \"deadletter\", \"endpoint_disabled\"];",
                  "  j.deliveries.forEach(function (d) {",
                  "    pm.expect(d.endpoint_id).to.eql(pm.collectionVariables.get(\"webhook_id\"));",
                  "    pm.expect(d.status).to.be.oneOf(STATUSES);",
                  "    pm.expect(d.attempts).to.be.an(\"array\");",
                  "  });",
                  "});",
                  "pm.test(\"endpoint recien creado: pagina vacia, no 404\", function () {",
                  "  pm.expect(j.deliveries.length).to.eql(0);",
                  "  pm.expect(j.next_cursor).to.eql(null);",
                  "});",
                  "pm.test(\"el secreto tampoco viaja en la evidencia\", function () {",
                  "  pm.expect(pm.response.text()).to.not.include(\"whsec_\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/webhooks/{{webhook_id}}/deliveries?status=inexistente",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/{{webhook_id}}/deliveries?status=inexistente",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                "{{webhook_id}}",
                "deliveries"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "inexistente"
                }
              ]
            },
            "description": "Un filtro con valor desconocido es 400 NOMBRANDO el conjunto aceptado, nunca una pagina vacia: una lista vacia se lee como `no se envio nada`, que es justo la conclusion que este endpoint existe para desmentir."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", function () { pm.response.to.have.status(400); });",
                  "const j = pm.response.json();",
                  "pm.test(\"400 que nombra el conjunto aceptado de status\", function () {",
                  "  pm.expect(j.error.code).to.eql(\"bad_request\");",
                  "  pm.expect(j.error.message).to.include(\"status must be one of\");",
                  "  pm.expect(j.error.message).to.include(\"deadletter\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/webhooks/{{webhook_id}}/deliveries/{{ghost_uuid}}/redeliver",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/{{webhook_id}}/deliveries/{{ghost_uuid}}/redeliver",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                "{{webhook_id}}",
                "deliveries",
                "{{ghost_uuid}}",
                "redeliver"
              ]
            },
            "description": "Reintento manual: APPEND de un intento `retry` (`reason: manual_redelivery`) que el dispatcher recoge en su siguiente tick, de ahi el 202 y no 200. Aqui se ejercita con un delivery_id inexistente via VARIABLE (el endpoint de la corrida no tiene deliveries que reintentar), asi que el determinista es el 404 del handler. Sin cuerpo."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 404\", function () { pm.response.to.have.status(404); });",
                  "const j = pm.response.json();",
                  "pm.test(\"404 del handler (not_found), no del catch-all\", function () {",
                  "  pm.expect(j.error.code).to.eql(\"not_found\");",
                  "  pm.expect(j.error.message).to.not.include(\"no such route\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "DELETE /v1/webhooks/{{webhook_id}}",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/{{webhook_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                "{{webhook_id}}"
              ]
            },
            "description": "Deshabilita, nunca borra: la fila sigue en el listado con enabled=false."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", function () { pm.response.to.have.status(200); });",
                  "const j = pm.response.json();",
                  "pm.test(\"deshabilitado con razon, fila conservada\", function () {",
                  "  pm.expect(j.webhook.id).to.eql(pm.collectionVariables.get(\"webhook_id\"));",
                  "  pm.expect(j.webhook.enabled).to.eql(false);",
                  "  pm.expect(j.webhook.disabled_reason).to.eql(\"disabled_by_api\");",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/webhooks/{{ghost_uuid}}",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/{{ghost_uuid}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                "{{ghost_uuid}}"
              ]
            },
            "description": "id inexistente via VARIABLE (nunca literal): 404 determinista del handler, no del catch-all."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 404\", function () { pm.response.to.have.status(404); });",
                  "const j = pm.response.json();",
                  "pm.test(\"404 del handler (not_found), no del catch-all\", function () {",
                  "  pm.expect(j.error.code).to.eql(\"not_found\");",
                  "  pm.expect(j.error.message).to.not.include(\"no such route\");",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.usa.dialerdigital.com"
    },
    {
      "key": "tenant_id",
      "value": ""
    },
    {
      "key": "tenant_name",
      "value": ""
    },
    {
      "key": "api_key_id",
      "value": ""
    },
    {
      "key": "golden_tz",
      "value": ""
    },
    {
      "key": "golden_state",
      "value": ""
    },
    {
      "key": "golden_city",
      "value": ""
    },
    {
      "key": "golden_campaign_id",
      "value": ""
    },
    {
      "key": "golden_import_body",
      "value": ""
    },
    {
      "key": "golden_debt_id",
      "value": ""
    },
    {
      "key": "golden_contact_phone",
      "value": ""
    },
    {
      "key": "golden_agent_id",
      "value": ""
    },
    {
      "key": "golden_agent_ext",
      "value": ""
    },
    {
      "key": "golden_call_id",
      "value": ""
    },
    {
      "key": "campaign_id",
      "value": ""
    },
    {
      "key": "attempt_id",
      "value": ""
    },
    {
      "key": "callback_when",
      "value": ""
    },
    {
      "key": "callback_id_complete",
      "value": ""
    },
    {
      "key": "callback_id_cancel",
      "value": ""
    },
    {
      "key": "callback_id_miss",
      "value": ""
    },
    {
      "key": "promise_id",
      "value": ""
    },
    {
      "key": "postman_agent_ext",
      "value": ""
    },
    {
      "key": "postman_agent_id",
      "value": ""
    },
    {
      "key": "did_id",
      "value": ""
    },
    {
      "key": "postman_did_e164",
      "value": ""
    },
    {
      "key": "carrier_id",
      "value": ""
    },
    {
      "key": "inbound_route_id",
      "value": ""
    },
    {
      "key": "postman_inbound_e164",
      "value": ""
    },
    {
      "key": "sms_provider_id",
      "value": ""
    },
    {
      "key": "sms_send_provider_id",
      "value": ""
    },
    {
      "key": "sms_message_id",
      "value": ""
    },
    {
      "key": "ghost_uuid",
      "value": "00000000-0000-0000-0000-000000000000",
      "type": "string"
    },
    {
      "key": "postman_user_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "postman_user_email",
      "value": "",
      "type": "string"
    },
    {
      "key": "ddu_token",
      "value": "",
      "type": "string"
    },
    {
      "key": "widget_agent_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "widget_agent_email",
      "value": "",
      "type": "string"
    },
    {
      "key": "recording_id",
      "value": ""
    },
    {
      "key": "recording_export_job_id",
      "value": ""
    },
    {
      "key": "ai_agent_id",
      "value": ""
    },
    {
      "key": "ai_agent_name",
      "value": ""
    },
    {
      "key": "ai_conversation_id",
      "value": ""
    },
    {
      "key": "export_from",
      "value": ""
    },
    {
      "key": "export_to",
      "value": ""
    },
    {
      "key": "export_from_oversized",
      "value": ""
    },
    {
      "key": "export_to_oversized",
      "value": ""
    },
    {
      "key": "reset_202_body",
      "value": ""
    },
    {
      "key": "reset_token",
      "value": ""
    },
    {
      "key": "webhook_id",
      "value": ""
    },
    {
      "key": "webhook_secret_hint",
      "value": ""
    }
  ]
}
