{
  "openapi": "3.1.0",
  "info": {
    "title": "Xenpia API",
    "version": "0.1.0",
    "description": "Generado con análisis estático de los decoradores de NestJS. Sin esquemas de cuerpo ni de respuesta: el backend no los declara."
  },
  "servers": [
    {
      "url": "https://api.xenpia.com",
      "description": "Producción"
    },
    {
      "url": "https://apitest.xenpia.com",
      "description": "Test"
    },
    {
      "url": "http://localhost:3000",
      "description": "Local"
    }
  ],
  "tags": [
    {
      "name": "billing"
    },
    {
      "name": "bot-safety"
    },
    {
      "name": "bots"
    },
    {
      "name": "bots/approvals"
    },
    {
      "name": "bots/inspector"
    },
    {
      "name": "bots/subflows"
    },
    {
      "name": "channels/email"
    },
    {
      "name": "channels/inbox"
    },
    {
      "name": "channels/meta"
    },
    {
      "name": "channels/meta/instagram"
    },
    {
      "name": "channels/meta/messenger"
    },
    {
      "name": "channels/meta/whatsapp"
    },
    {
      "name": "channels/telegram"
    },
    {
      "name": "channels/webchat"
    },
    {
      "name": "channels/x"
    },
    {
      "name": "copilot"
    },
    {
      "name": "data-sources"
    },
    {
      "name": "data-sync"
    },
    {
      "name": "embeddings"
    },
    {
      "name": "error-reporting"
    },
    {
      "name": "flow-assistant"
    },
    {
      "name": "modules"
    },
    {
      "name": "modules/invoicing"
    },
    {
      "name": "modules/kiosk"
    },
    {
      "name": "modules/scheduling"
    },
    {
      "name": "onboarding"
    },
    {
      "name": "payments"
    },
    {
      "name": "platform-settings"
    },
    {
      "name": "raíz"
    },
    {
      "name": "sandbox-channels"
    }
  ],
  "paths": {
    "/api": {
      "get": {
        "operationId": "AppController_getHello_get",
        "summary": "AppController.getHello",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/app.controller.ts:8`.",
        "tags": [
          "raíz"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/admin/data-sync": {
      "post": {
        "operationId": "DataSyncController_start_post",
        "summary": "DataSyncController.start",
        "description": "Protegido por: SuperadminGuard.\n\nImplementado en `backend/src/data-sync/data-sync.controller.ts:27`.",
        "tags": [
          "data-sync"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/admin/data-sync/status": {
      "get": {
        "operationId": "DataSyncController_status_get",
        "summary": "DataSyncController.status",
        "description": "Protegido por: SuperadminGuard.\n\nImplementado en `backend/src/data-sync/data-sync.controller.ts:14`.",
        "tags": [
          "data-sync"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/admin/data-sync/tenants": {
      "get": {
        "operationId": "DataSyncController_tenants_get",
        "summary": "DataSyncController.tenants",
        "description": "Protegido por: SuperadminGuard.\n\nImplementado en `backend/src/data-sync/data-sync.controller.ts:20`.",
        "tags": [
          "data-sync"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/admin/platform-settings": {
      "get": {
        "operationId": "PlatformSettingsController_list_get",
        "summary": "PlatformSettingsController.list",
        "description": "Protegido por: SuperadminGuard.\n\nImplementado en `backend/src/platform-settings/platform-settings.controller.ts:26`.",
        "tags": [
          "platform-settings"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/admin/platform-settings/{key}": {
      "put": {
        "operationId": "PlatformSettingsController_upsert_put",
        "summary": "PlatformSettingsController.upsert",
        "description": "Protegido por: SuperadminGuard.\n\nImplementado en `backend/src/platform-settings/platform-settings.controller.ts:31`.",
        "tags": [
          "platform-settings"
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/admin/sandbox-channels": {
      "get": {
        "operationId": "SandboxChannelsController_listPack_get",
        "summary": "SandboxChannelsController.listPack",
        "description": "Protegido por: SandboxModeGuard, SuperadminGuard.\n\nImplementado en `backend/src/sandbox-channels/sandbox-channels.controller.ts:31`.",
        "tags": [
          "sandbox-channels"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/admin/sandbox-channels/{channelCode}": {
      "put": {
        "operationId": "SandboxChannelsController_upsertPack_put",
        "summary": "SandboxChannelsController.upsertPack",
        "description": "Protegido por: SandboxModeGuard, SuperadminGuard.\n\nImplementado en `backend/src/sandbox-channels/sandbox-channels.controller.ts:60`.",
        "tags": [
          "sandbox-channels"
        ],
        "parameters": [
          {
            "name": "channelCode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/admin/sandbox-channels/leases": {
      "get": {
        "operationId": "SandboxChannelsController_listLeases_get",
        "summary": "SandboxChannelsController.listLeases",
        "description": "Protegido por: SandboxModeGuard, SuperadminGuard.\n\nImplementado en `backend/src/sandbox-channels/sandbox-channels.controller.ts:79`.",
        "tags": [
          "sandbox-channels"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/admin/sandbox-channels/leases/{channelCode}": {
      "delete": {
        "operationId": "SandboxChannelsController_releaseLease_delete",
        "summary": "SandboxChannelsController.releaseLease",
        "description": "Protegido por: SandboxModeGuard, SuperadminGuard.\n\nImplementado en `backend/src/sandbox-channels/sandbox-channels.controller.ts:101`.",
        "tags": [
          "sandbox-channels"
        ],
        "parameters": [
          {
            "name": "channelCode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "SandboxChannelsController_acquireLease_post",
        "summary": "SandboxChannelsController.acquireLease",
        "description": "Protegido por: SandboxModeGuard, SuperadminGuard.\n\nImplementado en `backend/src/sandbox-channels/sandbox-channels.controller.ts:85`.",
        "tags": [
          "sandbox-channels"
        ],
        "parameters": [
          {
            "name": "channelCode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/admin/sandbox-channels/whatsapp/webhook": {
      "get": {
        "operationId": "SandboxChannelsController_webhookWhatsapp_get",
        "summary": "A dónde entrega Meta el número de WhatsApp del sandbox: Test (override) o producción.",
        "description": "Protegido por: SandboxModeGuard, SuperadminGuard.\n\nImplementado en `backend/src/sandbox-channels/sandbox-channels.controller.ts:41`.",
        "tags": [
          "sandbox-channels"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "operationId": "SandboxChannelsController_moverWebhookWhatsapp_put",
        "summary": "Mueve la entrega del número: `produccion` quita el override para usarlo en producción; `test` lo vuelve a poner. La URL de Test sale del entorno, nunca del cuerpo: el cliente solo elige el destino.",
        "description": "Protegido por: SandboxModeGuard, SuperadminGuard.\n\nImplementado en `backend/src/sandbox-channels/sandbox-channels.controller.ts:51`.",
        "tags": [
          "sandbox-channels"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/billing/rollup": {
      "post": {
        "operationId": "BillingController_rollup_post",
        "summary": "Dispara el rollup diario de consumo hacia Odoo. Lo llama un ir.cron de Odoo (diario) con el secreto compartido. Idempotente/acumulativo.",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/billing/billing.controller.ts:120`.",
        "tags": [
          "billing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/billing/sync": {
      "post": {
        "operationId": "BillingController_manualSync_post",
        "summary": "Backfill completo bajo demanda (uso operativo). Protegido por el mismo secreto del webhook, enviado como `Authorization: Bearer <secret>`.",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/billing/billing.controller.ts:70`.",
        "tags": [
          "billing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/billing/tenant-subscription": {
      "post": {
        "operationId": "BillingController_setTenantSubscription_post",
        "summary": "Alta/cambio de la suscripción de un tenant (lo invoca la server-action del superadmin, que además valida is_superadmin del lado de Supabase). Escribe en Odoo y refresca el espejo. Protegido por el secreto compartido.",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/billing/billing.controller.ts:85`.",
        "tags": [
          "billing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/billing/webhook/odoo": {
      "post": {
        "operationId": "BillingController_odooWebhook_post",
        "summary": "Recibe el aviso de Odoo de que un plan/suscripción cambió. El body solo dice qué cambió; el dato real se re-consulta a Odoo (ver BillingSyncService).",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/billing/billing.controller.ts:42`.",
        "tags": [
          "billing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bot-safety/{botId}/decision": {
      "post": {
        "operationId": "BotSafetyController_decide_post",
        "summary": "BotSafetyController.decide",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bot-safety/bot-safety.controller.ts:64`.",
        "tags": [
          "bot-safety"
        ],
        "parameters": [
          {
            "name": "botId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bot-safety/{botId}/review": {
      "post": {
        "operationId": "BotSafetyController_review_post",
        "summary": "Revisión a demanda desde el panel del tenant (tras editar el bot).",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bot-safety/bot-safety.controller.ts:89`.",
        "tags": [
          "bot-safety"
        ],
        "parameters": [
          {
            "name": "botId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bot-safety/queue": {
      "get": {
        "operationId": "BotSafetyController_queue_get",
        "summary": "BotSafetyController.queue",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bot-safety/bot-safety.controller.ts:58`.",
        "tags": [
          "bot-safety"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bot-safety/tenants/{tenantId}/verify": {
      "post": {
        "operationId": "BotSafetyController_verifyTenant_post",
        "summary": "Quita los límites de cuenta nueva a un tenant (ver TenantTrustService).",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bot-safety/bot-safety.controller.ts:81`.",
        "tags": [
          "bot-safety"
        ],
        "parameters": [
          {
            "name": "tenantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/{botId}/flow-versions": {
      "get": {
        "operationId": "FlowVersionsController_list_get",
        "summary": "FlowVersionsController.list",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/flow-versions.controller.ts:27`.",
        "tags": [
          "bots"
        ],
        "parameters": [
          {
            "name": "botId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "FlowVersionsController_create_post",
        "summary": "FlowVersionsController.create",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/flow-versions.controller.ts:43`.",
        "tags": [
          "bots"
        ],
        "parameters": [
          {
            "name": "botId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/{botId}/flow-versions/{versionId}": {
      "delete": {
        "operationId": "FlowVersionsController_remove_delete",
        "summary": "FlowVersionsController.remove",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/flow-versions.controller.ts:76`.",
        "tags": [
          "bots"
        ],
        "parameters": [
          {
            "name": "botId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "get": {
        "operationId": "FlowVersionsController_getOne_get",
        "summary": "FlowVersionsController.getOne",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/flow-versions.controller.ts:33`.",
        "tags": [
          "bots"
        ],
        "parameters": [
          {
            "name": "botId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/{botId}/flow-versions/{versionId}/activate": {
      "post": {
        "operationId": "FlowVersionsController_activate_post",
        "summary": "FlowVersionsController.activate",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/flow-versions.controller.ts:66`.",
        "tags": [
          "bots"
        ],
        "parameters": [
          {
            "name": "botId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/agent-tools": {
      "get": {
        "operationId": "BotsController_getAgentToolsManifest_get",
        "summary": "Static manifest of all tools the flow editor can offer in the \"Herramientas del agente\" picker on LLM nodes. Per-bot runtime availability (module enabled, tools_config, indexed records…) is enforced server-side at agent invocation time, so this list is intentionally global. Every service a factory might need must be passed here. A factory whose dependency is missing returns null and its tool silently disappears from the picker — which is how the scheduling and catalog tools were invisible even though they worked at runtime.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/bots.controller.ts:121`.",
        "tags": [
          "bots"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/approvals/{id}/continue": {
      "post": {
        "operationId": "ApprovalsController_continue_post",
        "summary": "ApprovalsController.continue",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/approvals/approvals.controller.ts:19`.",
        "tags": [
          "bots/approvals"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/conversations/{conversationId}/state": {
      "get": {
        "operationId": "ConversationStateController_inspect_get",
        "summary": "Dónde está la conversación y por qué pasos pasó. Solo estructura, sin PHI.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/inspector/conversation-state.controller.ts:20`.",
        "tags": [
          "bots/inspector"
        ],
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/conversations/{conversationId}/state/history": {
      "get": {
        "operationId": "ConversationStateController_history_get",
        "summary": "Puntos de guardado de la conversación (uno por mensaje). Solo estructura.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/inspector/conversation-state.controller.ts:30`.",
        "tags": [
          "bots/inspector"
        ],
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/conversations/{conversationId}/state/replay": {
      "post": {
        "operationId": "ConversationStateController_replay_post",
        "summary": "Reproduce EN SECO un mensaje desde un punto de guardado: devuelve el recorrido que haría hoy el flujo, sin enviar nada ni tocar la conversación. Es una herramienta de depuración, por eso pide bot.update.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/inspector/conversation-state.controller.ts:44`.",
        "tags": [
          "bots/inspector"
        ],
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/conversations/{conversationId}/state/reset": {
      "post": {
        "operationId": "ConversationStateController_reset_post",
        "summary": "Olvida la memoria del bot en esta conversación (el próximo mensaje empieza de cero).",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/inspector/conversation-state.controller.ts:54`.",
        "tags": [
          "bots/inspector"
        ],
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/dashboards/generate": {
      "post": {
        "operationId": "DashboardsController_generate_post",
        "summary": "DashboardsController.generate",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/bots/canvabots.controller.ts:32`.",
        "tags": [
          "bots"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/dashboards/sessions": {
      "post": {
        "operationId": "DashboardsController_createSession_post",
        "summary": "DashboardsController.createSession",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/bots/canvabots.controller.ts:42`.",
        "tags": [
          "bots"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/dashboards/sessions/{sessionId}": {
      "get": {
        "operationId": "DashboardsController_getSession_get",
        "summary": "DashboardsController.getSession",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/bots/canvabots.controller.ts:50`.",
        "tags": [
          "bots"
        ],
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/dashboards/sessions/{sessionId}/files": {
      "post": {
        "operationId": "DashboardsController_addFiles_post",
        "summary": "DashboardsController.addFiles",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/bots/canvabots.controller.ts:57`.",
        "tags": [
          "bots"
        ],
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/dashboards/sessions/{sessionId}/message": {
      "post": {
        "operationId": "DashboardsController_message_post",
        "summary": "DashboardsController.message",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/bots/canvabots.controller.ts:72`.",
        "tags": [
          "bots"
        ],
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/message": {
      "post": {
        "operationId": "BotsController_sendMessage_post",
        "summary": "BotsController.sendMessage",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/bots.controller.ts:81`.",
        "tags": [
          "bots"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/reload": {
      "post": {
        "operationId": "BotsController_reloadBot_post",
        "summary": "BotsController.reloadBot",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/bots.controller.ts:75`.",
        "tags": [
          "bots"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/tenants": {
      "get": {
        "operationId": "BotsController_getTenants_get",
        "summary": "Qué tenants tiene cargados el registro: dato de operación, solo superadmin.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/bots.controller.ts:60`.",
        "tags": [
          "bots"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/bots/tenants/{tenantId}": {
      "get": {
        "operationId": "BotsController_getBotsForTenant_get",
        "summary": "BotsController.getBotsForTenant",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/bots.controller.ts:69`.",
        "tags": [
          "bots"
        ],
        "parameters": [
          {
            "name": "tenantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/channels/email/credentials": {
      "post": {
        "operationId": "EmailCredentialsController_save_post",
        "summary": "EmailCredentialsController.save",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/email/email-credentials.controller.ts:52`.",
        "tags": [
          "channels/email"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/channels/email/credentials/status": {
      "get": {
        "operationId": "EmailCredentialsController_status_get",
        "summary": "EmailCredentialsController.status",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/email/email-credentials.controller.ts:61`.",
        "tags": [
          "channels/email"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/channels/email/test-connection": {
      "post": {
        "operationId": "EmailCredentialsController_testConnection_post",
        "summary": "EmailCredentialsController.testConnection",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/email/email-credentials.controller.ts:45`.",
        "tags": [
          "channels/email"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/copilot/access": {
      "get": {
        "operationId": "CopilotController_access_get",
        "summary": "CopilotController.access",
        "description": "Protegido por: CopilotGuard.\n\nImplementado en `backend/src/copilot/copilot.controller.ts:22`.",
        "tags": [
          "copilot"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/copilot/message": {
      "post": {
        "operationId": "CopilotController_message_post",
        "summary": "CopilotController.message",
        "description": "Protegido por: CopilotGuard.\n\nImplementado en `backend/src/copilot/copilot.controller.ts:28`.",
        "tags": [
          "copilot"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/copilot/ping": {
      "get": {
        "operationId": "CopilotController_ping_get",
        "summary": "Utilidad para el guard: tenant_id también puede ir por query en GET.",
        "description": "Protegido por: CopilotGuard.\n\nImplementado en `backend/src/copilot/copilot.controller.ts:55`.",
        "tags": [
          "copilot"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/copilot/threads": {
      "get": {
        "operationId": "CopilotController_threads_get",
        "summary": "CopilotController.threads",
        "description": "Protegido por: CopilotGuard.\n\nImplementado en `backend/src/copilot/copilot.controller.ts:41`.",
        "tags": [
          "copilot"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/copilot/threads/{id}": {
      "get": {
        "operationId": "CopilotController_thread_get",
        "summary": "CopilotController.thread",
        "description": "Protegido por: CopilotGuard.\n\nImplementado en `backend/src/copilot/copilot.controller.ts:47`.",
        "tags": [
          "copilot"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/crm/products/{id}/embedding/enqueue": {
      "post": {
        "operationId": "EmbeddingsController_enqueue_post",
        "summary": "Encola la regeneración del embedding (BullMQ, async, recomendado).",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/embeddings/embeddings.controller.ts:39`.",
        "tags": [
          "embeddings"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/crm/products/{id}/embedding/regenerate-now": {
      "post": {
        "operationId": "EmbeddingsController_regenerateNow_post",
        "summary": "Regenera el embedding ahora mismo (síncrono). Útil para QA, para fix puntuales, o cuando Redis/BullMQ no está disponible. Bloquea el request unos cientos de ms (call a OpenAI).",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/embeddings/embeddings.controller.ts:52`.",
        "tags": [
          "embeddings"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/crm/products/embeddings/enqueue-pending": {
      "post": {
        "operationId": "EmbeddingsController_enqueuePending_post",
        "summary": "Encola en masa los productos pendientes del tenant/workspace.",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/embeddings/embeddings.controller.ts:60`.",
        "tags": [
          "embeddings"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/crm/products/search-semantic": {
      "post": {
        "operationId": "EmbeddingsController_semanticSearch_post",
        "summary": "Búsqueda semántica multi-industria.",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/embeddings/embeddings.controller.ts:73`.",
        "tags": [
          "embeddings"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/data-sources": {
      "get": {
        "operationId": "DataSourcesController_list_get",
        "summary": "DataSourcesController.list",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:72`.",
        "tags": [
          "data-sources"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/data-sources/{id}": {
      "delete": {
        "operationId": "DataSourcesController_remove_delete",
        "summary": "DataSourcesController.remove",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:134`.",
        "tags": [
          "data-sources"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "get": {
        "operationId": "DataSourcesController_getOne_get",
        "summary": "DataSourcesController.getOne",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:85`.",
        "tags": [
          "data-sources"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "patch": {
        "operationId": "DataSourcesController_update_patch",
        "summary": "DataSourcesController.update",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:121`.",
        "tags": [
          "data-sources"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/data-sources/{id}/node-attach": {
      "delete": {
        "operationId": "DataSourcesController_detach_delete",
        "summary": "DataSourcesController.detach",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:159`.",
        "tags": [
          "data-sources"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "DataSourcesController_attach_post",
        "summary": "DataSourcesController.attach",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:146`.",
        "tags": [
          "data-sources"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/data-sources/{id}/reindex": {
      "post": {
        "operationId": "DataSourcesController_reindex_post",
        "summary": "DataSourcesController.reindex",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:110`.",
        "tags": [
          "data-sources"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/data-sources/{id}/signed-url": {
      "get": {
        "operationId": "DataSourcesController_getSignedUrl_get",
        "summary": "DataSourcesController.getSignedUrl",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:96`.",
        "tags": [
          "data-sources"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/data-sources/api": {
      "post": {
        "operationId": "DataSourcesController_createApi_post",
        "summary": "DataSourcesController.createApi",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:63`.",
        "tags": [
          "data-sources"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/data-sources/document": {
      "post": {
        "operationId": "DataSourcesController_uploadDocument_post",
        "summary": "DataSourcesController.uploadDocument",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:27`.",
        "tags": [
          "data-sources"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/data-sources/rows": {
      "post": {
        "operationId": "DataSourcesController_importRows_post",
        "summary": "DataSourcesController.importRows",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/data-sources/data-sources.controller.ts:41`.",
        "tags": [
          "data-sources"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/errors/report": {
      "post": {
        "operationId": "ErrorReportingController_report_post",
        "summary": "ErrorReportingController.report",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/error-reporting/error-reporting.controller.ts:73`.",
        "tags": [
          "error-reporting"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/flow-assistant/access": {
      "get": {
        "operationId": "FlowAssistantController_access_get",
        "summary": "FlowAssistantController.access",
        "description": "Protegido por: FlowAssistantGuard.\n\nImplementado en `backend/src/flow-assistant/flow-assistant.controller.ts:28`.",
        "tags": [
          "flow-assistant"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/flow-assistant/message": {
      "post": {
        "operationId": "FlowAssistantController_message_post",
        "summary": "FlowAssistantController.message",
        "description": "Protegido por: FlowAssistantGuard.\n\nImplementado en `backend/src/flow-assistant/flow-assistant.controller.ts:38`.",
        "tags": [
          "flow-assistant"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/inbox/deliver-media": {
      "post": {
        "operationId": "InboxController_deliverMedia_post",
        "summary": "InboxController.deliverMedia",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/inbox/inbox.controller.ts:45`.",
        "tags": [
          "channels/inbox"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/instagram/send-text": {
      "post": {
        "operationId": "InstagramController_sendText_post",
        "summary": "InstagramController.sendText",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/meta/instagram/instagram.controller.ts:31`.",
        "tags": [
          "channels/meta/instagram"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoicing/invoices": {
      "get": {
        "operationId": "InvoicingController_list_get",
        "summary": "InvoicingController.list",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/invoicing/invoicing.controller.ts:55`.",
        "tags": [
          "modules/invoicing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "InvoicingController_issue_post",
        "summary": "InvoicingController.issue",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/invoicing/invoicing.controller.ts:87`.",
        "tags": [
          "modules/invoicing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoicing/invoices/{engineId}": {
      "get": {
        "operationId": "InvoicingController_getOne_get",
        "summary": "InvoicingController.getOne",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/invoicing/invoicing.controller.ts:73`.",
        "tags": [
          "modules/invoicing"
        ],
        "parameters": [
          {
            "name": "engineId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoicing/invoices/{engineId}/ride": {
      "get": {
        "operationId": "InvoicingController_ride_get",
        "summary": "InvoicingController.ride",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/invoicing/invoicing.controller.ts:80`.",
        "tags": [
          "modules/invoicing"
        ],
        "parameters": [
          {
            "name": "engineId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoicing/invoices/sync": {
      "post": {
        "operationId": "InvoicingController_syncPending_post",
        "summary": "Consulta al motor las facturas pendientes de este tenant. La pantalla lo pide al abrirse, para no mostrar \"Pendiente\" lo que el SRI ya autorizó.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/invoicing/invoicing.controller.ts:66`.",
        "tags": [
          "modules/invoicing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoicing/issuers": {
      "get": {
        "operationId": "InvoicingController_listIssuers_get",
        "summary": "Lectura con el permiso de consulta: quien emite necesita elegir emisor, y el listado no lleva nada secreto —el certificado vive solo en el motor—.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/invoicing/invoicing.controller.ts:104`.",
        "tags": [
          "modules/invoicing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "InvoicingController_registerIssuer_post",
        "summary": "InvoicingController.registerIssuer",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/invoicing/invoicing.controller.ts:111`.",
        "tags": [
          "modules/invoicing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoicing/issuers/{issuerId}": {
      "delete": {
        "operationId": "InvoicingController_deleteIssuer_delete",
        "summary": "Desactiva el emisor. En el motor no se borra nada: las facturas se conservan.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/invoicing/invoicing.controller.ts:141`.",
        "tags": [
          "modules/invoicing"
        ],
        "parameters": [
          {
            "name": "issuerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "patch": {
        "operationId": "InvoicingController_updateIssuer_patch",
        "summary": "Editar un emisor o cambiarle el certificado. Ver `InvoicingService.updateIssuer`.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/invoicing/invoicing.controller.ts:129`.",
        "tags": [
          "modules/invoicing"
        ],
        "parameters": [
          {
            "name": "issuerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoicing/status": {
      "get": {
        "operationId": "InvoicingController_status_get",
        "summary": "Si el plan incluye la facturación. La pantalla lo pregunta antes de pintar nada.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/invoicing/invoicing.controller.ts:48`.",
        "tags": [
          "modules/invoicing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/invoicing/webhook/engine": {
      "post": {
        "operationId": "InvoicingWebhookController_engine_post",
        "summary": "InvoicingWebhookController.engine",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/modules/invoicing/invoicing-webhook.controller.ts:42`.",
        "tags": [
          "modules/invoicing"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/admin/config": {
      "get": {
        "operationId": "KioskAdminController_getConfig_get",
        "summary": "KioskAdminController.getConfig",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-admin.controller.ts:217`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "KioskAdminController_saveConfig_post",
        "summary": "KioskAdminController.saveConfig",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-admin.controller.ts:224`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/admin/devices": {
      "get": {
        "operationId": "KioskAdminController_listDevices_get",
        "summary": "KioskAdminController.listDevices",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-admin.controller.ts:61`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/admin/devices/{id}/pair": {
      "post": {
        "operationId": "KioskAdminController_repair_post",
        "summary": "Vincula ese código a un dispositivo EXISTENTE: se cambió el PC de recepción o se reinstaló Windows. En cuanto la máquina nueva recoge su credencial, la de la vieja deja de valer.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-admin.controller.ts:93`.",
        "tags": [
          "modules/kiosk"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/admin/devices/{id}/revoke": {
      "post": {
        "operationId": "KioskAdminController_revoke_post",
        "summary": "KioskAdminController.revoke",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-admin.controller.ts:119`.",
        "tags": [
          "modules/kiosk"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/admin/devices/claim": {
      "post": {
        "operationId": "KioskAdminController_claim_post",
        "summary": "Vincula la pantalla que muestra ese código creando un dispositivo nuevo. El alta y la vinculación van juntas a propósito: un dispositivo que existe en el panel pero no en ninguna máquina es un estado que solo sirve para confundir. Si el código no vale, no se crea nada.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-admin.controller.ts:75`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/admin/tickets": {
      "get": {
        "operationId": "KioskAdminController_listTickets_get",
        "summary": "KioskAdminController.listTickets",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-admin.controller.ts:132`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/admin/tickets/call": {
      "post": {
        "operationId": "KioskAdminController_call_post",
        "summary": "Llamar al siguiente. Es lo que hace que la pantalla de la sala cambie.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-admin.controller.ts:143`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/admin/tickets/close": {
      "post": {
        "operationId": "KioskAdminController_close_post",
        "summary": "KioskAdminController.close",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-admin.controller.ts:186`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/admin/tickets/start": {
      "post": {
        "operationId": "KioskAdminController_start_post",
        "summary": "El paciente entró a consulta. Avanza las DOS cosas: la cita a `in_progress` y el turno a cerrado/atendido. El cambio de estado de la cita va por `SchedulingService.updateStatus` y no por un UPDATE propio — los efectos secundarios de una cita (recordatorios, eventos de dominio, validación de la transición) tienen un solo dueño.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-admin.controller.ts:166`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/board": {
      "get": {
        "operationId": "KioskDeviceController_board_get",
        "summary": "La pizarra de la sala de espera. Solo para dispositivos de tipo `display`: el token de una TV colgada de una pared no puede poder preguntar por cédulas.",
        "description": "Protegido por: KioskGuard, ModuleGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-device.controller.ts:208`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/checkin": {
      "post": {
        "operationId": "KioskDeviceController_checkIn_post",
        "summary": "Registra la llegada y emite el turno. El vale viene de un `lookup` previo.",
        "description": "Protegido por: KioskGuard, ModuleGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-device.controller.ts:160`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/lookup": {
      "post": {
        "operationId": "KioskDeviceController_lookup_post",
        "summary": "Busca por documento. Devuelve lo MISMO para un documento desconocido y para uno conocido sin cita hoy: distinguirlos convertiría el kiosko en un oráculo de padrón. Ver el comentario de `KioskService.lookup`.",
        "description": "Protegido por: KioskGuard, ModuleGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-device.controller.ts:125`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/pair/poll": {
      "post": {
        "operationId": "KioskDeviceController_pairPoll_post",
        "summary": "¿Ya me reclamaron? La credencial se entrega UNA vez, en la respuesta que encuentra la vinculación hecha. El vale va en el cuerpo y no en la URL: en la URL acabaría en los logs de acceso de cualquier proxy por el que pase.",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/modules/kiosk/kiosk-device.controller.ts:95`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/pair/start": {
      "post": {
        "operationId": "KioskDeviceController_pairStart_post",
        "summary": "Una máquina sin vincular pide un código para mostrarlo. Sin guard: todavía no tiene credencial, y conseguir una es justo lo que viene a hacer. No revela nada ni crea nada que pertenezca a un tenant; lo único que se lleva es un código y un vale que no sirven hasta que alguien con permiso reclama la pantalla desde el panel.",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/modules/kiosk/kiosk-device.controller.ts:81`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/printed": {
      "post": {
        "operationId": "KioskDeviceController_printed_post",
        "summary": "Telemetría de impresión. Registra el INTENTO, no que saliera papel.",
        "description": "Protegido por: KioskGuard, ModuleGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-device.controller.ts:193`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/session": {
      "get": {
        "operationId": "KioskDeviceController_session_get",
        "summary": "Con qué trabajar: quién es esta máquina, qué documentos pide su país y —lo importante— QUÉ HORA ES. El kiosko no usa su propio reloj en ninguna decisión. Un PC de vestíbulo sin NTP se desfasa, y un reloj adelantado dejaría a todo el mundo fuera de la ventana de check-in sin que nadie entendiera por qué.",
        "description": "Protegido por: KioskGuard, ModuleGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-device.controller.ts:111`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/kiosk/walk-in": {
      "post": {
        "operationId": "KioskDeviceController_walkIn_post",
        "summary": "Turno para quien llega sin cita. NO crea una cita.",
        "description": "Protegido por: KioskGuard, ModuleGuard.\n\nImplementado en `backend/src/modules/kiosk/kiosk-device.controller.ts:170`.",
        "tags": [
          "modules/kiosk"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/messenger/send-text": {
      "post": {
        "operationId": "MessengerController_sendText_post",
        "summary": "MessengerController.sendText",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/meta/messenger/messenger.controller.ts:31`.",
        "tags": [
          "channels/meta/messenger"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/modules/{tenantId}": {
      "get": {
        "operationId": "ModulesController_list_get",
        "summary": "Catálogo completo con el estado del tenant y sus dependencias resueltas.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/modules/modules.controller.ts:32`.",
        "tags": [
          "modules"
        ],
        "parameters": [
          {
            "name": "tenantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/modules/{tenantId}/enabled": {
      "get": {
        "operationId": "ModulesController_enabled_get",
        "summary": "Solo los códigos activos. Barato de llamar en cada arranque de sesión.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/modules/modules.controller.ts:40`.",
        "tags": [
          "modules"
        ],
        "parameters": [
          {
            "name": "tenantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/modules/toggle": {
      "post": {
        "operationId": "ModulesController_toggle_post",
        "summary": "Activa o desactiva un módulo. Desactivar NUNCA borra datos: marca enabled=false y sella disabled_at, de modo que reactivar devuelve todo intacto.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/modules/modules.controller.ts:54`.",
        "tags": [
          "modules"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/assistant": {
      "post": {
        "operationId": "OnboardingController_saveAssistant_post",
        "summary": "OnboardingController.saveAssistant",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:127`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/business": {
      "post": {
        "operationId": "OnboardingController_saveBusiness_post",
        "summary": "OnboardingController.saveBusiness",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:98`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/channel": {
      "post": {
        "operationId": "OnboardingController_saveChannel_post",
        "summary": "OnboardingController.saveChannel",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:136`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/complete": {
      "post": {
        "operationId": "OnboardingController_complete_post",
        "summary": "OnboardingController.complete",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:164`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/packs": {
      "get": {
        "operationId": "OnboardingController_listPacks_get",
        "summary": "OnboardingController.listPacks",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:79`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/packs/public": {
      "get": {
        "operationId": "OnboardingController_listPacksPublic_get",
        "summary": "Listado de packs sin exigir membership — para el alta de tenant (superadmin).",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:86`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/preseed": {
      "post": {
        "operationId": "OnboardingController_preseed_post",
        "summary": "Pre-siembra desde superadmin al crear el tenant.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:146`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/preview-chat": {
      "post": {
        "operationId": "OnboardingController_previewChat_post",
        "summary": "OnboardingController.previewChat",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:118`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/prompt-draft": {
      "post": {
        "operationId": "OnboardingController_draftPrompt_post",
        "summary": "OnboardingController.draftPrompt",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:109`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/skip": {
      "post": {
        "operationId": "OnboardingController_skip_post",
        "summary": "OnboardingController.skip",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:182`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/state": {
      "get": {
        "operationId": "OnboardingController_getState_get",
        "summary": "OnboardingController.getState",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:91`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/onboarding/user-state": {
      "get": {
        "operationId": "OnboardingController_getUserState_get",
        "summary": "OnboardingController.getUserState",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:191`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "OnboardingController_upsertUserState_post",
        "summary": "OnboardingController.upsertUserState",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/onboarding/onboarding.controller.ts:199`.",
        "tags": [
          "onboarding"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/payments/history": {
      "get": {
        "operationId": "PaymentsController_history_get",
        "summary": "PaymentsController.history",
        "description": "Protegido por: PaymentsEnabledGuard.\n\nImplementado en `backend/src/payments/payments.controller.ts:212`.",
        "tags": [
          "payments"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/payments/links": {
      "get": {
        "operationId": "PaymentsController_listLinks_get",
        "summary": "PaymentsController.listLinks",
        "description": "Protegido por: PaymentsEnabledGuard.\n\nImplementado en `backend/src/payments/payments.controller.ts:167`.",
        "tags": [
          "payments"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "PaymentsController_createLink_post",
        "summary": "PaymentsController.createLink",
        "description": "Protegido por: PaymentsEnabledGuard.\n\nImplementado en `backend/src/payments/payments.controller.ts:138`.",
        "tags": [
          "payments"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/payments/links/{id}/disable": {
      "post": {
        "operationId": "PaymentsController_disableLink_post",
        "summary": "PaymentsController.disableLink",
        "description": "Protegido por: PaymentsEnabledGuard.\n\nImplementado en `backend/src/payments/payments.controller.ts:188`.",
        "tags": [
          "payments"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/payments/sessions": {
      "post": {
        "operationId": "PaymentsController_createSession_post",
        "summary": "Crea sesión Checkout vía Odoo (create_pay_session). Protegido por el mismo secreto Odoo (Bearer) — lo invocan server actions.",
        "description": "Protegido por: PaymentsEnabledGuard.\n\nImplementado en `backend/src/payments/payments.controller.ts:74`.",
        "tags": [
          "payments"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/payments/sessions/{reference}": {
      "get": {
        "operationId": "PaymentsController_getSession_get",
        "summary": "PaymentsController.getSession",
        "description": "Protegido por: PaymentsEnabledGuard.\n\nImplementado en `backend/src/payments/payments.controller.ts:120`.",
        "tags": [
          "payments"
        ],
        "parameters": [
          {
            "name": "reference",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/sandbox-channels": {
      "get": {
        "operationId": "SandboxChannelsTenantController_listForTenant_get",
        "summary": "SandboxChannelsTenantController.listForTenant",
        "description": "Protegido por: SandboxModeGuard, AuthenticatedGuard.\n\nImplementado en `backend/src/sandbox-channels/sandbox-channels-tenant.controller.ts:63`.",
        "tags": [
          "sandbox-channels"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/sandbox-channels/leases/{channelCode}": {
      "delete": {
        "operationId": "SandboxChannelsTenantController_release_delete",
        "summary": "SandboxChannelsTenantController.release",
        "description": "Protegido por: SandboxModeGuard, AuthenticatedGuard.\n\nImplementado en `backend/src/sandbox-channels/sandbox-channels-tenant.controller.ts:91`.",
        "tags": [
          "sandbox-channels"
        ],
        "parameters": [
          {
            "name": "channelCode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "SandboxChannelsTenantController_acquire_post",
        "summary": "SandboxChannelsTenantController.acquire",
        "description": "Protegido por: SandboxModeGuard, AuthenticatedGuard.\n\nImplementado en `backend/src/sandbox-channels/sandbox-channels-tenant.controller.ts:72`.",
        "tags": [
          "sandbox-channels"
        ],
        "parameters": [
          {
            "name": "channelCode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/appointments": {
      "get": {
        "operationId": "SchedulingController_list_get",
        "summary": "Citas de un rango con los nombres resueltos. Alimenta el calendario.",
        "description": "Protegido por: ModuleGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:81`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "SchedulingController_create_post",
        "summary": "SchedulingController.create",
        "description": "Protegido por: ModuleGuard, AppointmentWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:95`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/appointments/cancel": {
      "post": {
        "operationId": "SchedulingController_cancel_post",
        "summary": "SchedulingController.cancel",
        "description": "Protegido por: ModuleGuard, AppointmentWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:172`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/appointments/contact": {
      "post": {
        "operationId": "SchedulingController_setContact_post",
        "summary": "Asignar o quitar el paciente de una cita existente.",
        "description": "Protegido por: ModuleGuard, AppointmentWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:147`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/appointments/offer-reschedule": {
      "post": {
        "operationId": "SchedulingController_offerReschedule_post",
        "summary": "Cancela cada cita listada y le ofrece al paciente reagendar por WhatsApp. Se llama desde la alerta de conflicto al guardar un bloqueo de agenda. El body solo trae `resource_id` (no un `appointment_id` suelto): con eso `AppointmentWriteGuard` toma la vía \"alta\" y comprueba que quien llama sea dueño de ESE recurso, que es exactamente lo que hace falta —todas las citas de la lista son, por construcción, de ese mismo recurso—. Las citas se recargan de la base por id: no se confía en los datos que mande el cliente.",
        "description": "Protegido por: ModuleGuard, AppointmentWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:191`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/appointments/reschedule": {
      "post": {
        "operationId": "SchedulingController_reschedule_post",
        "summary": "SchedulingController.reschedule",
        "description": "Protegido por: ModuleGuard, AppointmentWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:107`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/appointments/send-reminder": {
      "post": {
        "operationId": "SchedulingController_sendReminder_post",
        "summary": "Envía ya el recordatorio de una cita, a mano. Sale por el mismo canal por el que se reservó (la conversación de origen decide la ruta). Lleva `AppointmentWriteGuard`: avisar a un paciente es actuar sobre su cita, y el guard saca el tenant de la fila real y exige `manage` (con alcance propio si no hay `view_all`).",
        "description": "Protegido por: ModuleGuard, AppointmentWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:163`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/appointments/status": {
      "post": {
        "operationId": "SchedulingController_setStatus_post",
        "summary": "Confirmar, completar, marcar ausencia o reabrir.",
        "description": "Protegido por: ModuleGuard, AppointmentWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:119`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/appointments/substatus": {
      "post": {
        "operationId": "SchedulingController_setSubstatus_post",
        "summary": "Poner o quitar el sub-estado de una cita (etiqueta informativa). Mismo guard que el estado: quien no puede tocar esa agenda, tampoco etiqueta.",
        "description": "Protegido por: ModuleGuard, AppointmentWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:136`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/{entity}": {
      "get": {
        "operationId": "SchedulingConfigController_list_get",
        "summary": "Lista entidades de configuración del tenant. Query opcionales: `resource_id`, `location_id` (eq). Para `availabilityExceptions` también `from`/`to` (solape) y `resource_ids` (lista separada por comas) — una sola petición para el calendario.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:328`.",
        "tags": [
          "modules/scheduling"
        ],
        "parameters": [
          {
            "name": "entity",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "SchedulingConfigController_create_post",
        "summary": "SchedulingConfigController.create",
        "description": "Protegido por: ModuleGuard, PermissionGuard, SchedulingWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:365`.",
        "tags": [
          "modules/scheduling"
        ],
        "parameters": [
          {
            "name": "entity",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/{entity}/{id}": {
      "delete": {
        "operationId": "SchedulingConfigController_remove_delete",
        "summary": "SchedulingConfigController.remove",
        "description": "Protegido por: ModuleGuard, PermissionGuard, SchedulingWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:392`.",
        "tags": [
          "modules/scheduling"
        ],
        "parameters": [
          {
            "name": "entity",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "patch": {
        "operationId": "SchedulingConfigController_update_patch",
        "summary": "SchedulingConfigController.update",
        "description": "Protegido por: ModuleGuard, PermissionGuard, SchedulingWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:378`.",
        "tags": [
          "modules/scheduling"
        ],
        "parameters": [
          {
            "name": "entity",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/availability-exceptions/conflicts": {
      "get": {
        "operationId": "SchedulingConfigController_availabilityExceptionConflicts_get",
        "summary": "Citas que chocarían con un bloqueo de agenda antes de guardarlo. Ruta de dos segmentos (`availability-exceptions/conflicts`), así que no colisiona con el comodín `:entity` de una sola palabra — pero se declara aquí arriba igualmente, junto al resto de rutas específicas, por consistencia con ellas.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:294`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/block-reschedule-offer": {
      "get": {
        "operationId": "SchedulingConfigController_getBlockRescheduleOffer_get",
        "summary": "SchedulingConfigController.getBlockRescheduleOffer",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:185`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "operationId": "SchedulingConfigController_saveBlockRescheduleOffer_put",
        "summary": "SchedulingConfigController.saveBlockRescheduleOffer",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:192`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/calendar-view": {
      "get": {
        "operationId": "SchedulingConfigController_getCalendarView_get",
        "summary": "SchedulingConfigController.getCalendarView",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:199`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "operationId": "SchedulingConfigController_saveCalendarView_put",
        "summary": "SchedulingConfigController.saveCalendarView",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:206`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/linkable-users": {
      "get": {
        "operationId": "SchedulingConfigController_linkableUsers_get",
        "summary": "Usuarios vinculables a un recurso. Va antes de :entity para no colisionar.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:255`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/reminders": {
      "get": {
        "operationId": "SchedulingConfigController_getReminders_get",
        "summary": "SchedulingConfigController.getReminders",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:75`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "operationId": "SchedulingConfigController_saveReminders_put",
        "summary": "Cambiar cuándo se avisa a los pacientes es configuración de la sede, no mantenimiento de la ficha de un profesional: exige el permiso completo, no la vía \"propio\" de SchedulingWriteGuard.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:87`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/reminders/cold-outreach": {
      "put": {
        "operationId": "SchedulingConfigController_saveColdOutreach_put",
        "summary": "SchedulingConfigController.saveColdOutreach",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:114`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/reminders/outreach": {
      "get": {
        "operationId": "SchedulingConfigController_getReminderOutreach_get",
        "summary": "SchedulingConfigController.getReminderOutreach",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:107`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/reminders/whatsapp-template": {
      "post": {
        "operationId": "SchedulingConfigController_createReminderTemplate_post",
        "summary": "SchedulingConfigController.createReminderTemplate",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:124`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "operationId": "SchedulingConfigController_adoptReminderTemplate_put",
        "summary": "SchedulingConfigController.adoptReminderTemplate",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:150`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/reminders/whatsapp-template/candidates": {
      "get": {
        "operationId": "SchedulingConfigController_listReminderTemplateCandidates_get",
        "summary": "SchedulingConfigController.listReminderTemplateCandidates",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:138`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/reminders/whatsapp-template/refresh": {
      "post": {
        "operationId": "SchedulingConfigController_refreshReminderTemplate_post",
        "summary": "SchedulingConfigController.refreshReminderTemplate",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:176`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/resources/{id}/services": {
      "get": {
        "operationId": "SchedulingConfigController_resourceServices_get",
        "summary": "SchedulingConfigController.resourceServices",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:262`.",
        "tags": [
          "modules/scheduling"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "SchedulingConfigController_setResourceServices_post",
        "summary": "SchedulingConfigController.setResourceServices",
        "description": "Protegido por: ModuleGuard, PermissionGuard, SchedulingWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:268`.",
        "tags": [
          "modules/scheduling"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/services/{id}/locations": {
      "get": {
        "operationId": "SchedulingConfigController_serviceLocations_get",
        "summary": "SchedulingConfigController.serviceLocations",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:274`.",
        "tags": [
          "modules/scheduling"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "SchedulingConfigController_setServiceLocations_post",
        "summary": "SchedulingConfigController.setServiceLocations",
        "description": "Protegido por: ModuleGuard, PermissionGuard, SchedulingWriteGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:280`.",
        "tags": [
          "modules/scheduling"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/status-colors": {
      "get": {
        "operationId": "SchedulingConfigController_getStatusColors_get",
        "summary": "Color de cada estado de cita. Lo lee cualquiera del tenant: se pinta en todas las vistas.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:214`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "operationId": "SchedulingConfigController_saveStatusColors_put",
        "summary": "SchedulingConfigController.saveStatusColors",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:221`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/substatuses": {
      "get": {
        "operationId": "SchedulingConfigController_getSubstatuses_get",
        "summary": "Sub-estados de cita (etiquetas con color encima del estado). Los lee cualquiera del tenant: se pintan en calendario, citas y hoy.",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:232`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "operationId": "SchedulingConfigController_saveSubstatuses_put",
        "summary": "SchedulingConfigController.saveSubstatuses",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:239`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/config/substatuses/usage": {
      "get": {
        "operationId": "SchedulingConfigController_getSubstatusUsage_get",
        "summary": "Uso por sub-estado y estado: solo para quien configura (avisa antes de romper).",
        "description": "Protegido por: ModuleGuard, PermissionGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling-config.controller.ts:247`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/contacts/{contactId}/appointments": {
      "get": {
        "operationId": "SchedulingController_upcoming_get",
        "summary": "SchedulingController.upcoming",
        "description": "Protegido por: ModuleGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:207`.",
        "tags": [
          "modules/scheduling"
        ],
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/scheduling/slots": {
      "get": {
        "operationId": "SchedulingController_slots_get",
        "summary": "Huecos disponibles. `location_id` es opcional a propósito: sin él busca en todas las sedes y cada hueco viene etiquetado con la suya.",
        "description": "Protegido por: ModuleGuard.\n\nImplementado en `backend/src/modules/scheduling/scheduling.controller.ts:48`.",
        "tags": [
          "modules/scheduling"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/subflows": {
      "get": {
        "operationId": "SubflowsController_list_get",
        "summary": "SubflowsController.list",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/subflows/subflows.controller.ts:34`.",
        "tags": [
          "bots/subflows"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "SubflowsController_create_post",
        "summary": "SubflowsController.create",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/subflows/subflows.controller.ts:73`.",
        "tags": [
          "bots/subflows"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/subflows/{id}": {
      "delete": {
        "operationId": "SubflowsController_remove_delete",
        "summary": "SubflowsController.remove",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/subflows/subflows.controller.ts:115`.",
        "tags": [
          "bots/subflows"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "get": {
        "operationId": "SubflowsController_getOne_get",
        "summary": "SubflowsController.getOne",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/subflows/subflows.controller.ts:41`.",
        "tags": [
          "bots/subflows"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "patch": {
        "operationId": "SubflowsController_update_patch",
        "summary": "SubflowsController.update",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/subflows/subflows.controller.ts:88`.",
        "tags": [
          "bots/subflows"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/subflows/{id}/usages": {
      "get": {
        "operationId": "SubflowsController_usages_get",
        "summary": "SubflowsController.usages",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/subflows/subflows.controller.ts:66`.",
        "tags": [
          "bots/subflows"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/subflows/{id}/versions": {
      "get": {
        "operationId": "SubflowsController_listVersions_get",
        "summary": "SubflowsController.listVersions",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/subflows/subflows.controller.ts:48`.",
        "tags": [
          "bots/subflows"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "SubflowsController_publish_post",
        "summary": "SubflowsController.publish",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/subflows/subflows.controller.ts:98`.",
        "tags": [
          "bots/subflows"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/subflows/{id}/versions/{versionId}": {
      "get": {
        "operationId": "SubflowsController_getVersion_get",
        "summary": "SubflowsController.getVersion",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/bots/subflows/subflows.controller.ts:55`.",
        "tags": [
          "bots/subflows"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhook/whatsapp": {
      "get": {
        "operationId": "WhatsAppWebhookController_verify_get",
        "summary": "WhatsAppWebhookController.verify",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.webhook.controller.ts:45`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "WhatsAppWebhookController_handleWebhook_post",
        "summary": "WhatsAppWebhookController.handleWebhook",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.webhook.controller.ts:60`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/email": {
      "post": {
        "operationId": "EmailController_handleEmailWebhook_post",
        "summary": "EmailController.handleEmailWebhook",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/email/email.controller.ts:9`.",
        "tags": [
          "channels/email"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/meta": {
      "get": {
        "operationId": "MetaController_verifyWebhook_get",
        "summary": "MetaController.verifyWebhook",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/meta.controller.ts:56`.",
        "tags": [
          "channels/meta"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "MetaController_handleWebhook_post",
        "summary": "MetaController.handleWebhook",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/meta.controller.ts:70`.",
        "tags": [
          "channels/meta"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/meta/embedded-signup-token": {
      "post": {
        "operationId": "MetaController_exchangeEmbeddedSignupToken_post",
        "summary": "Exchange Embedded Signup authorization code for access token. Body: { code: string, redirect_uri?: string } (redirect_uri can be \"\" when using JS SDK).",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/meta.controller.ts:174`.",
        "tags": [
          "channels/meta"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/meta/fb-pages": {
      "get": {
        "operationId": "MetaController_getFbPages_get",
        "summary": "Fetch Facebook Pages the user manages. Used in Messenger and Instagram wizard Step 2. Query: access_token",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/meta.controller.ts:219`.",
        "tags": [
          "channels/meta"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/meta/ig-account": {
      "get": {
        "operationId": "MetaController_getIgAccount_get",
        "summary": "Fetch the Instagram Business Account linked to a Facebook Page. Used in Instagram wizard Step 2. Query: page_id, access_token",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/meta.controller.ts:241`.",
        "tags": [
          "channels/meta"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/meta/phone-numbers": {
      "post": {
        "operationId": "MetaController_getPhoneNumbers_post",
        "summary": "Fetch phone numbers for a WABA after Embedded Signup. Body: { access_token: string (required), waba_id?: string (optional — if omitted, resolves first assigned WABA) } Token is sent in the request body to avoid exposure in server logs / browser history.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/meta.controller.ts:98`.",
        "tags": [
          "channels/meta"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/telegram/{channelAccountId}": {
      "post": {
        "operationId": "TelegramController_handleUpdate_post",
        "summary": "Telegram sends POST updates to this URL",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/telegram/telegram.controller.ts:26`.",
        "tags": [
          "channels/telegram"
        ],
        "parameters": [
          {
            "name": "channelAccountId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/telegram/set-webhook": {
      "post": {
        "operationId": "TelegramController_setWebhook_post",
        "summary": "Register webhook for a bot token",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/telegram/telegram.controller.ts:58`.",
        "tags": [
          "channels/telegram"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/telegram/validate-token": {
      "get": {
        "operationId": "TelegramController_validateToken_get",
        "summary": "Validate bot token and return bot info",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/telegram/telegram.controller.ts:38`.",
        "tags": [
          "channels/telegram"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/webchat": {
      "post": {
        "operationId": "WebchatController_handleWebchatMessage_post",
        "summary": "WebchatController.handleWebchatMessage",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/webchat/webchat.controller.ts:22`.",
        "tags": [
          "channels/webchat"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/webchat/config/{channelAccountId}": {
      "get": {
        "operationId": "WebchatController_getConfig_get",
        "summary": "Public bootstrap config for the embeddable widget — called from any website.",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/webchat/webchat.controller.ts:32`.",
        "tags": [
          "channels/webchat"
        ],
        "parameters": [
          {
            "name": "channelAccountId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/webhooks/x": {
      "post": {
        "operationId": "XController_handleXWebhook_post",
        "summary": "XController.handleXWebhook",
        "description": "Sin guards: accesible sin autenticación desde el punto de vista del framework.\n\nImplementado en `backend/src/channels/x/x.controller.ts:9`.",
        "tags": [
          "channels/x"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/onboarding/complete": {
      "post": {
        "operationId": "WhatsAppOnboardingController_complete_post",
        "summary": "WhatsAppOnboardingController.complete",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp-onboarding.controller.ts:62`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/onboarding/select-number": {
      "post": {
        "operationId": "WhatsAppOnboardingController_selectNumber_post",
        "summary": "Cuando la WABA tenía varios números y el usuario eligió uno.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp-onboarding.controller.ts:82`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/onboarding/sync": {
      "post": {
        "operationId": "WhatsAppOnboardingController_retrySync_post",
        "summary": "Reintenta la sincronización de contactos/historial (solo dentro de 24 h).",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp-onboarding.controller.ts:96`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/send-text": {
      "post": {
        "operationId": "WhatsAppController_sendText_post",
        "summary": "WhatsAppController.sendText",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:58`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/templates": {
      "get": {
        "operationId": "WhatsAppController_listTemplates_get",
        "summary": "Approved message templates (including FLOW) from the WABA linked to this tenant/bot.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:137`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/xenpia-numbers": {
      "get": {
        "operationId": "WhatsAppController_getXenpiaNumbers_get",
        "summary": "Returns the phone numbers registered in a Xenpia-owned WABA. Uses WHATSAPP_ACCESS_TOKEN. Optional `waba_id` selects which owned WABA to list; defaults to WHATSAPP_WABA_ID.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:632`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "operationId": "WhatsAppController_createXenpiaNumber_post",
        "summary": "Add a phone number to a specific WABA (client's own WABA under Xenpia's BM). Body: { waba_id, phone_number, country_code, verified_name } Next step: call /xenpia-numbers/request-otp to send verification code.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:299`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/xenpia-numbers/register": {
      "post": {
        "operationId": "WhatsAppController_registerXenpiaNumber_post",
        "summary": "Register a Xenpia-owned number on the Cloud API so it can send/receive. This is the activation step that follows OTP verification: verifying the OTP only proves ownership, while `/register` enables the number on the Cloud API and sets its two-step-verification PIN. Body: { phone_number_id, pin } — pin is a 6-digit code chosen for the number. An \"already registered\" response is treated as success so retries are safe.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:481`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/xenpia-numbers/request-otp": {
      "post": {
        "operationId": "WhatsAppController_requestXenpiaOtp_post",
        "summary": "Send OTP (SMS or voice) to a phone number registered in Xenpia's WABA. Body: { phone_number_id, method: 'SMS' | 'VOICE', language? }",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:349`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/xenpia-numbers/status": {
      "get": {
        "operationId": "WhatsAppController_getXenpiaNumberStatus_get",
        "summary": "Inspect a Xenpia-owned number and report the single next step needed before it can send/receive on the Cloud API: 'verify' (OTP), 'register', or 'done'. Used by the \"número existente\" flow for numbers created in the Meta panel. Query: ?phone_number_id=...",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:437`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/xenpia-numbers/verify-otp": {
      "post": {
        "operationId": "WhatsAppController_verifyXenpiaOtp_post",
        "summary": "Verify OTP for a phone number in Xenpia's WABA. Body: { phone_number_id, code } On success the number is active and ready to use.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:394`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/xenpia-waba": {
      "post": {
        "operationId": "WhatsAppController_createTenantWaba_post",
        "summary": "Create a new WABA under Xenpia's Business Manager for a specific client/tenant. Then subscribes Xenpia's app to the new WABA. Body: { waba_name, currency?, timezone_id? } Returns: { ok, waba_id }",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:227`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/xenpia-waba/{tenantId}": {
      "get": {
        "operationId": "WhatsAppController_getTenantWaba_get",
        "summary": "Check if a tenant already has a WABA under Xenpia's BM. Returns { ok, waba_id, waba_name } — waba_id is null if none found.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:184`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "parameters": [
          {
            "name": "tenantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/whatsapp/xenpia-wabas": {
      "get": {
        "operationId": "WhatsAppController_getXenpiaWabas_get",
        "summary": "Lists WhatsApp Business Accounts owned by Xenpia's Business Manager. Used in the \"Xenpia-owned number\" wizard: pick a WABA, then list its phones.",
        "description": "Protegido por: PermissionGuard.\n\nImplementado en `backend/src/channels/meta/whatsapp/whatsapp.controller.ts:538`.",
        "tags": [
          "channels/meta/whatsapp"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  }
}
