{
  "components": {
    "responses": {
      "Forbidden": {
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        },
        "description": "Autenticado pero sin permiso: rol o scope insuficiente (403)."
      },
      "TooManyRequests": {
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        },
        "description": "Rate limit excedido (429).",
        "headers": {
          "Retry-After": {
            "description": "Segundos hasta reintentar.",
            "schema": {
              "type": "integer"
            }
          }
        }
      },
      "Unauthorized": {
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        },
        "description": "Sin autenticar / credencial inválida (401)."
      }
    },
    "schemas": {
      "AddContactTagDto": {
        "properties": {
          "tagId": {
            "maxLength": 64,
            "type": "string"
          }
        },
        "required": [
          "tagId"
        ],
        "type": "object"
      },
      "AddImageDto": {
        "properties": {
          "isPrimary": {
            "type": "boolean"
          },
          "url": {
            "maxLength": 1000,
            "type": "string"
          }
        },
        "required": [
          "url"
        ],
        "type": "object"
      },
      "AdjustDto": {
        "properties": {
          "idempotencyKey": {
            "maxLength": 80,
            "type": "string"
          },
          "programId": {
            "minLength": 1,
            "type": "string"
          },
          "reason": {
            "maxLength": 280,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "programId",
          "reason"
        ],
        "type": "object"
      },
      "AdjustStockDto": {
        "properties": {
          "qty": {
            "maximum": 1000000,
            "minimum": 0,
            "type": "number"
          },
          "reason": {
            "maxLength": 280,
            "type": "string"
          },
          "type": {
            "enum": [
              "IN",
              "OUT",
              "ADJUST"
            ],
            "type": "string"
          }
        },
        "required": [
          "type",
          "qty"
        ],
        "type": "object"
      },
      "ApiError": {
        "description": "Cuerpo de error uniforme. Nunca incluye secretos ni stack.",
        "properties": {
          "error": {
            "description": "Código estable del catálogo de errores (shared/errors).",
            "enum": [
              "UNAUTHENTICATED",
              "INVALID_CREDENTIALS",
              "TOKEN_EXPIRED",
              "TOKEN_REUSED",
              "TENANT_NOT_RESOLVED",
              "TENANT_MISMATCH",
              "FORBIDDEN",
              "CROSS_TENANT_DENIED",
              "PLAN_LIMIT_EXCEEDED",
              "QUOTA_EXCEEDED",
              "FEATURE_NOT_IN_PLAN",
              "PAYMENT_REQUIRED",
              "TENANT_SUSPENDED",
              "PROVIDER_ERROR",
              "INVALID_SIGNATURE",
              "REPLAY_DETECTED",
              "DUPLICATE_EVENT",
              "INVALID_OAUTH_STATE",
              "WABA_ALREADY_CLAIMED",
              "ONBOARDING_FAILED",
              "AUTOMATION_INVALID",
              "APPOINTMENT_SLOT_UNAVAILABLE",
              "LOYALTY_INVALID",
              "LOYALTY_PROGRAM_NOT_FOUND",
              "LOYALTY_MEMBER_BLOCKED",
              "LOYALTY_INSUFFICIENT_BALANCE",
              "WALLET_NOT_READY",
              "RAG_DISABLED",
              "RAG_EMBED_NOT_CONFIGURED",
              "RAG_SOURCE_NOT_FOUND",
              "MESSAGE_NOT_AUDIO",
              "AI_STT_NOT_CONFIGURED",
              "AI_INTELLIGENCE_NOT_CONFIGURED",
              "COPILOT_DISABLED",
              "AI_CHAT_NOT_CONFIGURED",
              "AI_REWRITE_TOO_LONG",
              "AI_PLATFORM_CAP",
              "AI_INSIGHT_TOO_SOON",
              "PLATFORM_CONFIG_MISSING",
              "APP_VERSION_UNSUPPORTED",
              "MAINTENANCE",
              "USER_SESSION_REQUIRED",
              "INVALID_PAYLOAD",
              "PAYLOAD_TOO_LARGE",
              "SCREENSHOT_UNSUPPORTED",
              "SCREENSHOT_TOO_LARGE",
              "CHANNELS_DISABLED",
              "INVALID_STATE",
              "INCOMPLETE_SCOPES",
              "PAGE_ALREADY_CLAIMED",
              "NO_PAGES_GRANTED",
              "META_UNAVAILABLE",
              "CHANNEL_CAPABILITY_MISSING",
              "CHANNEL_OPS_UNSUPPORTED",
              "TEXT_TOO_LONG",
              "CHANNEL_DISCONNECTED",
              "COMMENTS_DISABLED",
              "COMMENT_ALREADY_REPLIED",
              "COMMENT_REPLY_WINDOW_EXPIRED",
              "COMMENT_DELETED",
              "CALLING_DISABLED",
              "CALLING_COEXISTENCE_UNSUPPORTED",
              "CALLING_NOT_ENABLED_ON_NUMBER",
              "CALL_PERMISSION_REQUIRED",
              "CALL_PERMISSION_RATE_LIMITED",
              "CALL_PERMISSION_ALREADY_GRANTED",
              "CALL_PERMISSION_TEMPLATE_REQUIRED",
              "CALL_ALREADY_CLAIMED",
              "CALL_AGENT_BUSY",
              "CALL_NOT_ACTIVE",
              "CALL_ACCEPT_FAILED",
              "CALL_RECORDING_OWNER_ONLY",
              "CALL_RECORDING_NOT_ALLOWED",
              "CALLING_CAP_REACHED",
              "MODE_NOT_ALLOWED",
              "ASSISTANT_NOT_READY",
              "NO_DECISION_BOT",
              "MODE_CONFLICT_META_AGENT",
              "MODE_BOTH_DISABLED",
              "HOSTED_LINKS_NOT_ALLOWED",
              "HOSTED_LINKS_NOT_ENABLED",
              "VALIDATION_FAILED",
              "NOT_FOUND",
              "CONFLICT",
              "RATE_LIMITED",
              "INTERNAL",
              "CONFIG_ERROR"
            ],
            "example": "FORBIDDEN",
            "type": "string"
          },
          "message": {
            "description": "Mensaje legible (string o lista de errores de validación).",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            ]
          },
          "requestId": {
            "description": "Correlación (logs).",
            "nullable": true,
            "type": "string"
          },
          "statusCode": {
            "example": 403,
            "type": "integer"
          }
        },
        "required": [
          "statusCode",
          "error",
          "message"
        ],
        "type": "object"
      },
      "BlockContactDto": {
        "properties": {
          "note": {
            "description": "Nota interna (nunca sale al cliente).",
            "maxLength": 280,
            "type": "string"
          },
          "reason": {
            "description": "Por qué se bloquea. `otro` pide nota (recomendado, no obligatorio).",
            "enum": [
              "spam",
              "acoso",
              "fraude",
              "otro"
            ],
            "type": "string"
          }
        },
        "required": [
          "reason"
        ],
        "type": "object"
      },
      "BookInConversationDto": {
        "properties": {
          "appointmentTypeId": {
            "description": "AppointmentType (NATIVO) a reservar.",
            "type": "string"
          },
          "contactEmail": {
            "description": "Correo del contacto (invitación de calendario).",
            "maxLength": 320,
            "type": "string"
          },
          "modality": {
            "description": "Default = del tipo.",
            "enum": [
              "IN_PERSON",
              "ONLINE"
            ],
            "type": "string"
          },
          "notes": {
            "description": "Notas internas de la cita.",
            "maxLength": 2000,
            "type": "string"
          },
          "resourceId": {
            "description": "Recurso (staff/sala) específico.",
            "type": "string"
          },
          "startAt": {
            "description": "Inicio de la cita (ISO instant). Debe coincidir con un slot.",
            "type": "string"
          },
          "tz": {
            "deprecated": true,
            "description": "Solo display: el servidor la IGNORA — la zona es SIEMPRE la del negocio. Se acepta por compatibilidad.",
            "type": "string"
          }
        },
        "required": [
          "appointmentTypeId",
          "startAt"
        ],
        "type": "object"
      },
      "BulkContactsDto": {
        "properties": {
          "action": {
            "enum": [
              "addTag",
              "removeTag",
              "archive",
              "unarchive"
            ],
            "type": "string"
          },
          "ids": {
            "description": "Ids de contacto (máximo 200).",
            "items": {
              "maxLength": 64,
              "type": "string"
            },
            "type": "array"
          },
          "tagId": {
            "description": "Para `addTag` / `removeTag`.",
            "maxLength": 64,
            "type": "string"
          }
        },
        "required": [
          "ids",
          "action"
        ],
        "type": "object"
      },
      "BulkConversationsDto": {
        "properties": {
          "action": {
            "enum": [
              "close",
              "reopen",
              "assign",
              "read",
              "tag",
              "untag",
              "snooze"
            ],
            "type": "string"
          },
          "assignedToId": {
            "description": "Para `assign`: User.id o null para desasignar.",
            "maxLength": 64,
            "nullable": true,
            "type": "string"
          },
          "ids": {
            "description": "Ids de conversación (máximo 100).",
            "items": {
              "maxLength": 64,
              "type": "string"
            },
            "type": "array"
          },
          "note": {
            "description": "Para `snooze`: nota opcional.",
            "maxLength": 280,
            "type": "string"
          },
          "tagId": {
            "description": "Para `tag` / `untag`.",
            "maxLength": 64,
            "type": "string"
          },
          "until": {
            "description": "Para `snooze`: hasta cuándo, ISO-8601, en el futuro (400 si ya pasó).",
            "type": "string"
          }
        },
        "required": [
          "ids",
          "action"
        ],
        "type": "object"
      },
      "CallAgentRef": {
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "CallCost": {
        "properties": {
          "billable": {
            "type": "boolean"
          },
          "estimated": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CallCostEstimate"
              }
            ],
            "nullable": true
          },
          "pulses": {
            "type": "number"
          }
        },
        "required": [
          "billable",
          "pulses"
        ],
        "type": "object"
      },
      "CallCostEstimate": {
        "properties": {
          "amount": {
            "example": "0.64",
            "type": "string"
          },
          "currency": {
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          }
        },
        "required": [
          "amount",
          "currency"
        ],
        "type": "object"
      },
      "CallFailureInfo": {
        "properties": {
          "code": {
            "type": "string"
          },
          "metaCode": {
            "nullable": true,
            "type": "number"
          }
        },
        "required": [
          "code"
        ],
        "type": "object"
      },
      "CallLimitInfo": {
        "properties": {
          "cutAt": {
            "type": "string"
          },
          "maxMinutes": {
            "type": "number"
          },
          "warnAt": {
            "type": "string"
          }
        },
        "required": [
          "warnAt",
          "cutAt",
          "maxMinutes"
        ],
        "type": "object"
      },
      "CallMediaInfo": {
        "properties": {
          "requested": {
            "type": "boolean"
          },
          "status": {
            "enum": [
              "NONE",
              "PENDING",
              "AVAILABLE",
              "FAILED",
              "EXPIRED"
            ],
            "type": "string"
          }
        },
        "required": [
          "requested",
          "status"
        ],
        "type": "object"
      },
      "CallMediaLinkResponse": {
        "properties": {
          "callId": {
            "type": "string"
          },
          "expiresAt": {
            "type": "string"
          },
          "kind": {
            "enum": [
              "recording",
              "transcript"
            ],
            "type": "string"
          },
          "mime": {
            "type": "string"
          },
          "url": {
            "description": "URL firmada de vida corta (≤ 60 s).",
            "type": "string"
          }
        },
        "required": [
          "callId",
          "kind",
          "url",
          "mime",
          "expiresAt"
        ],
        "type": "object"
      },
      "CallPermissionResponse": {
        "properties": {
          "blockedReason": {
            "enum": [
              "rate_limited",
              "already_granted",
              "cap_reached"
            ],
            "nullable": true,
            "type": "string"
          },
          "canCall": {
            "type": "boolean"
          },
          "canRequest": {
            "type": "boolean"
          },
          "channelAccountId": {
            "type": "string"
          },
          "conversationId": {
            "type": "string"
          },
          "expiresAt": {
            "nullable": true,
            "type": "string"
          },
          "missedCallsBeforeRevoke": {
            "type": "number"
          },
          "nextRequestAt": {
            "nullable": true,
            "type": "string"
          },
          "pendingSince": {
            "nullable": true,
            "type": "string"
          },
          "preview": {
            "type": "string"
          },
          "reason": {
            "enum": [
              "rate_limited",
              "already_granted",
              "cap_reached"
            ],
            "nullable": true,
            "type": "string"
          },
          "requestsLast7d": {
            "type": "number"
          },
          "retryAt": {
            "nullable": true,
            "type": "string"
          },
          "retryReason": {
            "enum": [
              "per24h",
              "per7d"
            ],
            "nullable": true,
            "type": "string"
          },
          "status": {
            "enum": [
              "NONE",
              "TEMPORARY",
              "PERMANENT",
              "REJECTED",
              "REVOKED"
            ],
            "type": "string"
          },
          "templateAvailable": {
            "type": "boolean"
          },
          "templateCost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MoneyOut"
              }
            ],
            "nullable": true
          },
          "windowOpen": {
            "type": "boolean"
          }
        },
        "required": [
          "conversationId",
          "channelAccountId",
          "status",
          "canRequest",
          "requestsLast7d",
          "missedCallsBeforeRevoke",
          "windowOpen",
          "templateAvailable",
          "preview",
          "canCall"
        ],
        "type": "object"
      },
      "CallResponseDto": {
        "properties": {
          "agent": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CallAgentRef"
              }
            ],
            "nullable": true
          },
          "answeredAt": {
            "nullable": true,
            "type": "string"
          },
          "channelAccountId": {
            "type": "string"
          },
          "channelCallId": {
            "nullable": true,
            "type": "string"
          },
          "claimedAt": {
            "nullable": true,
            "type": "string"
          },
          "contactId": {
            "nullable": true,
            "type": "string"
          },
          "conversationId": {
            "type": "string"
          },
          "cost": {
            "$ref": "#/components/schemas/CallCost"
          },
          "direction": {
            "enum": [
              "INBOUND",
              "OUTBOUND"
            ],
            "type": "string"
          },
          "durationSec": {
            "nullable": true,
            "type": "number"
          },
          "endedAt": {
            "nullable": true,
            "type": "string"
          },
          "failure": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CallFailureInfo"
              }
            ],
            "nullable": true
          },
          "id": {
            "type": "string"
          },
          "limit": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CallLimitInfo"
              }
            ],
            "nullable": true
          },
          "recording": {
            "$ref": "#/components/schemas/CallMediaInfo"
          },
          "rejectedBy": {
            "enum": [
              "AGENT",
              "CONTACT"
            ],
            "nullable": true,
            "type": "string"
          },
          "result": {
            "enum": [
              "ANSWERED",
              "MISSED",
              "REJECTED",
              "NO_ANSWER",
              "FAILED"
            ],
            "nullable": true,
            "type": "string"
          },
          "startedAt": {
            "type": "string"
          },
          "state": {
            "enum": [
              "RINGING",
              "DIALING",
              "CONNECTING",
              "ACTIVE",
              "ENDED"
            ],
            "type": "string"
          },
          "transcript": {
            "$ref": "#/components/schemas/CallMediaInfo"
          }
        },
        "required": [
          "id",
          "conversationId",
          "channelAccountId",
          "direction",
          "state",
          "startedAt",
          "cost",
          "recording",
          "transcript"
        ],
        "type": "object"
      },
      "CampaignRefDto": {
        "properties": {
          "campaign": {
            "maxLength": 120,
            "type": "string"
          },
          "medium": {
            "maxLength": 120,
            "type": "string"
          },
          "source": {
            "maxLength": 120,
            "type": "string"
          }
        },
        "type": "object"
      },
      "CancelAppointmentDto": {
        "properties": {
          "reason": {
            "description": "Motivo de cancelación (auditoría / notificación).",
            "maxLength": 500,
            "type": "string"
          }
        },
        "type": "object"
      },
      "CancelInvoiceDto": {
        "properties": {
          "motivo": {
            "enum": [
              "01",
              "02",
              "03",
              "04"
            ],
            "type": "string"
          },
          "uuidSustituto": {
            "maxLength": 64,
            "type": "string"
          }
        },
        "required": [
          "motivo"
        ],
        "type": "object"
      },
      "CapturePhotoDto": {
        "properties": {
          "appointmentId": {
            "description": "Cita del MISMO contacto a la que se liga la foto (la teje en el timeline).",
            "minLength": 1,
            "type": "string"
          },
          "caption": {
            "description": "Nota/caption de la foto (trim + cap server-side).",
            "maxLength": 500,
            "type": "string"
          },
          "consentAck": {
            "description": "El agente afirma que el cliente consintió archivar su imagen (§6). Obligatorio true.",
            "type": "boolean"
          },
          "kind": {
            "description": "Antes / Después / General.",
            "enum": [
              "BEFORE",
              "AFTER",
              "GENERAL"
            ],
            "type": "string"
          },
          "messageId": {
            "description": "Message.id (type=image) del hilo que se archiva como foto de servicio.",
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "messageId",
          "kind",
          "consentAck"
        ],
        "type": "object"
      },
      "CfdiConceptoImpuestosInputDto": {
        "properties": {
          "retenciones": {
            "items": {
              "$ref": "#/components/schemas/CfdiRetencionInputDto"
            },
            "type": "array"
          },
          "traslados": {
            "items": {
              "$ref": "#/components/schemas/CfdiTrasladoInputDto"
            },
            "type": "array"
          }
        },
        "required": [
          "traslados",
          "retenciones"
        ],
        "type": "object"
      },
      "CfdiConceptoInputDto": {
        "properties": {
          "cantidad": {
            "minimum": 1e-06,
            "type": "number"
          },
          "claveProdServ": {
            "maxLength": 16,
            "type": "string"
          },
          "claveUnidad": {
            "maxLength": 16,
            "type": "string"
          },
          "descripcion": {
            "maxLength": 1000,
            "type": "string"
          },
          "descuentoMinor": {
            "minimum": 0,
            "type": "number"
          },
          "impuestos": {
            "$ref": "#/components/schemas/CfdiConceptoImpuestosInputDto"
          },
          "objetoImp": {
            "enum": [
              "01",
              "02",
              "03",
              "04"
            ],
            "type": "string"
          },
          "valorUnitarioMinor": {
            "minimum": 0,
            "type": "number"
          }
        },
        "required": [
          "claveProdServ",
          "cantidad",
          "descripcion",
          "valorUnitarioMinor",
          "objetoImp",
          "impuestos"
        ],
        "type": "object"
      },
      "CfdiReceptorInputDto": {
        "properties": {
          "cpFiscal": {
            "maxLength": 5,
            "type": "string"
          },
          "email": {
            "maxLength": 254,
            "type": "string"
          },
          "razonSocial": {
            "maxLength": 254,
            "type": "string"
          },
          "regimenFiscal": {
            "maxLength": 4,
            "type": "string"
          },
          "rfc": {
            "maxLength": 13,
            "type": "string"
          },
          "usoCFDI": {
            "maxLength": 4,
            "type": "string"
          }
        },
        "required": [
          "rfc",
          "razonSocial",
          "cpFiscal",
          "regimenFiscal",
          "usoCFDI"
        ],
        "type": "object"
      },
      "CfdiRetencionInputDto": {
        "properties": {
          "impuesto": {
            "enum": [
              "001",
              "002"
            ],
            "type": "string"
          },
          "tasaOCuota": {
            "maxLength": 12,
            "type": "string"
          },
          "tipoFactor": {
            "enum": [
              "Tasa"
            ],
            "type": "string"
          }
        },
        "required": [
          "impuesto",
          "tipoFactor",
          "tasaOCuota"
        ],
        "type": "object"
      },
      "CfdiTrasladoInputDto": {
        "properties": {
          "impuesto": {
            "enum": [
              "002",
              "003"
            ],
            "type": "string"
          },
          "tasaOCuota": {
            "maxLength": 12,
            "type": "string"
          },
          "tipoFactor": {
            "enum": [
              "Tasa",
              "Cuota",
              "Exento"
            ],
            "type": "string"
          }
        },
        "required": [
          "impuesto",
          "tipoFactor"
        ],
        "type": "object"
      },
      "ChargeAppointmentDto": {
        "properties": {
          "provider": {
            "description": "Pasarela; omitir = cuenta default del tenant.",
            "enum": [
              "MERCADOPAGO",
              "OPENPAY",
              "STRIPE"
            ],
            "type": "string"
          },
          "sendMessage": {
            "description": "Enviar la liga por WhatsApp ya (default true) o solo generarla.",
            "type": "boolean"
          },
          "tipMinor": {
            "description": "Propina en unidad menor (centavos) que se SUMA al precio del tipo (Citas Ola 3 · CA3.2). Solo si el tipo admite propina (`allowTip`); 0/omitido = sin propina. Tope de sanidad $2,000.00 MXN.",
            "example": 5000,
            "maximum": 200000,
            "minimum": 0,
            "type": "number"
          }
        },
        "type": "object"
      },
      "CloseReasonResponseDto": {
        "properties": {
          "id": {
            "type": "string"
          },
          "isActive": {
            "description": "false = archivado: ya no sale en el selector, pero las conversaciones cerradas con él lo conservan.",
            "type": "boolean"
          },
          "isDefault": {
            "description": "true = viene de fábrica (se puede renombrar o archivar igual).",
            "type": "boolean"
          },
          "label": {
            "example": "Venta cerrada",
            "type": "string"
          },
          "position": {
            "description": "Orden en el selector (menor primero).",
            "type": "number"
          }
        },
        "required": [
          "id",
          "label",
          "position",
          "isActive",
          "isDefault"
        ],
        "type": "object"
      },
      "CloseReasonsListResponseDto": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/CloseReasonResponseDto"
            },
            "type": "array"
          }
        },
        "required": [
          "items"
        ],
        "type": "object"
      },
      "ConnectReputationDto": {
        "properties": {
          "code": {
            "maxLength": 4096,
            "minLength": 1,
            "type": "string"
          },
          "state": {
            "maxLength": 512,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "code",
          "state"
        ],
        "type": "object"
      },
      "ConsentEvidenceDto": {
        "properties": {
          "kind": {
            "description": "`message` = mensaje ENTRANTE de esta conversación · `note` = nota + fecha + medio.",
            "enum": [
              "message",
              "note"
            ],
            "type": "string"
          },
          "medium": {
            "description": "Por dónde lo dio (kind=note).",
            "enum": [
              "whatsapp",
              "llamada",
              "presencial",
              "formulario",
              "correo",
              "otro"
            ],
            "type": "string"
          },
          "messageId": {
            "description": "Id del mensaje ENTRANTE de esta conversación donde el cliente lo pidió o aceptó (kind=message).",
            "maxLength": 64,
            "type": "string"
          },
          "note": {
            "description": "Qué dijo el cliente y cómo (kind=note).",
            "maxLength": 500,
            "minLength": 10,
            "type": "string"
          },
          "occurredAt": {
            "description": "Cuándo lo dio (ISO 8601; no futuro) (kind=note).",
            "type": "string"
          }
        },
        "required": [
          "kind"
        ],
        "type": "object"
      },
      "ContactImportPreviewDto": {
        "properties": {
          "contentBase64": {
            "description": "Contenido del archivo (.csv o .xlsx) en base64. Máximo 5 MB.",
            "maxLength": 7200000,
            "minLength": 1,
            "type": "string"
          },
          "fileName": {
            "description": "Nombre del archivo (solo para el historial).",
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "fileName",
          "contentBase64"
        ],
        "type": "object"
      },
      "ConversationCountsDto": {
        "properties": {
          "all": {
            "description": "Total del alcance = `unanswered + following + resolved`, siempre.",
            "example": 103,
            "type": "number"
          },
          "asOf": {
            "description": "Instante del corte (ISO); descarta respuestas fuera de orden.",
            "example": "2026-09-16T04:37:27.073Z",
            "type": "string"
          },
          "following": {
            "description": "Abiertas a las que ya se contestó (siguen vivas).",
            "example": 34,
            "type": "number"
          },
          "resolved": {
            "description": "Cerradas.",
            "example": 57,
            "type": "number"
          },
          "unanswered": {
            "description": "Abiertas que esperan respuesta: espera clásica o traspaso bot→humano pendiente.",
            "example": 12,
            "type": "number"
          },
          "unread": {
            "description": "Mensajes sin leer en las ABIERTAS del alcance (suma de `unreadCount`). Es el badge de la app: cuánto falta por leer, no cuántos hilos.",
            "example": 7,
            "type": "number"
          }
        },
        "required": [
          "unanswered",
          "following",
          "resolved",
          "all",
          "unread",
          "asOf"
        ],
        "type": "object"
      },
      "ConvertWaitlistEntryDto": {
        "properties": {
          "contactEmail": {
            "description": "Correo del contacto (invitación de calendario).",
            "maxLength": 320,
            "type": "string"
          },
          "modality": {
            "description": "Default = del tipo.",
            "enum": [
              "IN_PERSON",
              "ONLINE"
            ],
            "type": "string"
          },
          "notes": {
            "description": "Notas internas de la cita.",
            "maxLength": 2000,
            "type": "string"
          },
          "resourceId": {
            "description": "Recurso (staff/sala) específico.",
            "type": "string"
          },
          "startAt": {
            "description": "Inicio de la cita (ISO instant). Debe coincidir con un slot.",
            "type": "string"
          },
          "tz": {
            "deprecated": true,
            "description": "Solo display: el servidor la IGNORA — la zona es SIEMPRE la del negocio. Se acepta por compatibilidad.",
            "type": "string"
          }
        },
        "required": [
          "startAt"
        ],
        "type": "object"
      },
      "CreateAppointmentDto": {
        "properties": {
          "appointmentTypeId": {
            "description": "AppointmentType (NATIVO) a reservar.",
            "type": "string"
          },
          "contact": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WalkInContactDto"
              }
            ],
            "description": "Walk-in nuevo (excluyente con conversationId)."
          },
          "contactEmail": {
            "description": "Correo del contacto (invitación de calendario).",
            "maxLength": 320,
            "type": "string"
          },
          "conversationId": {
            "description": "Agendar sobre un hilo existente (excluyente con contact).",
            "type": "string"
          },
          "modality": {
            "description": "Default = del tipo.",
            "enum": [
              "IN_PERSON",
              "ONLINE"
            ],
            "type": "string"
          },
          "notes": {
            "description": "Notas internas de la cita.",
            "maxLength": 2000,
            "type": "string"
          },
          "resourceId": {
            "description": "Recurso (staff/sala) específico.",
            "type": "string"
          },
          "startAt": {
            "description": "Inicio de la cita (ISO instant). Debe coincidir con un slot.",
            "type": "string"
          },
          "tz": {
            "deprecated": true,
            "description": "Solo display: el servidor la IGNORA — la zona es SIEMPRE la del negocio. Se acepta por compatibilidad.",
            "type": "string"
          }
        },
        "required": [
          "appointmentTypeId",
          "startAt"
        ],
        "type": "object"
      },
      "CreateAppointmentResourceDto": {
        "properties": {
          "active": {
            "description": "Activo. Default true.",
            "type": "boolean"
          },
          "capacity": {
            "description": "Capacidad simultánea. Default 1 (v1 = 1).",
            "maximum": 1000,
            "minimum": 1,
            "type": "number"
          },
          "kind": {
            "description": "Default STAFF.",
            "enum": [
              "STAFF",
              "ROOM"
            ],
            "type": "string"
          },
          "name": {
            "description": "Nombre del recurso (persona o sala).",
            "maxLength": 120,
            "minLength": 1,
            "type": "string"
          },
          "userId": {
            "description": "User Kontesta vinculado (STAFF) — debe ser del tenant.",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CreateAppointmentTypeDto": {
        "properties": {
          "active": {
            "description": "Activo (reservable). Default true.",
            "type": "boolean"
          },
          "allowTip": {
            "description": "Citas Ola 3 (CA3.2): ¿el servicio admite propina al cobrar el total? Solo tiene efecto con chargePolicy=TOTAL. Default false.",
            "type": "boolean"
          },
          "bufferAfterMin": {
            "description": "Buffer después de la cita (min). Default 0.",
            "maximum": 1440,
            "minimum": 0,
            "type": "number"
          },
          "bufferBeforeMin": {
            "description": "Buffer antes de la cita (min). Default 0.",
            "maximum": 1440,
            "minimum": 0,
            "type": "number"
          },
          "chargePolicy": {
            "description": "Política de cobro. NINGUNO=sin cobro obligatorio; ANTICIPO=anticipo anti-no-show (requiere depositAmountMinor>0); TOTAL=precio completo cobrable (requiere priceMinor>0). Default NINGUNO.",
            "enum": [
              "NINGUNO",
              "ANTICIPO",
              "TOTAL"
            ],
            "type": "string"
          },
          "color": {
            "description": "Color hex (#RRGGBB) para la UI.",
            "type": "string"
          },
          "currency": {
            "description": "Moneda del precio informativo.",
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "defaultModality": {
            "description": "Modalidad default. Default IN_PERSON.",
            "enum": [
              "IN_PERSON",
              "ONLINE"
            ],
            "type": "string"
          },
          "depositAmountMinor": {
            "description": "Monto del anticipo en unidad menor (centavos). Requerido si chargePolicy=ANTICIPO.",
            "minimum": 1,
            "type": "number"
          },
          "depositCurrency": {
            "description": "Moneda del anticipo (default = currency/tenant).",
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "durationMin": {
            "description": "Duración en minutos (1..1440).",
            "maximum": 1440,
            "minimum": 1,
            "type": "number"
          },
          "locationText": {
            "description": "Dirección/sala (IN_PERSON).",
            "maxLength": 500,
            "type": "string"
          },
          "maxHorizonDays": {
            "description": "Horizonte futuro reservable (días, 1..365). Default 30.",
            "maximum": 365,
            "minimum": 1,
            "type": "number"
          },
          "minNoticeMin": {
            "description": "Antelación mínima para reservar (min). Default 60.",
            "minimum": 0,
            "type": "number"
          },
          "name": {
            "description": "Nombre visible del tipo de cita.",
            "maxLength": 120,
            "minLength": 1,
            "type": "string"
          },
          "priceMinor": {
            "description": "Precio informativo v1 en unidad menor (centavos).",
            "minimum": 0,
            "type": "number"
          },
          "publicBookable": {
            "description": "Reserva pública: visible/reservable en el mini-site público. Default true.",
            "type": "boolean"
          },
          "requiresConfirmation": {
            "description": "La reserva nace PENDING (requiere confirmación) vs CONFIRMED.",
            "type": "boolean"
          },
          "slotStepMin": {
            "description": "Paso de la rejilla de horarios (min, 5..1440). null/omitido = igual a la duración (comportamiento previo).",
            "maximum": 1440,
            "minimum": 5,
            "nullable": true,
            "type": "number"
          }
        },
        "required": [
          "name",
          "durationMin"
        ],
        "type": "object"
      },
      "CreateAvailabilityExceptionDto": {
        "properties": {
          "appointmentTypeId": {
            "description": "Tipo de cita dueño de la excepción (debe ser del tenant).",
            "type": "string"
          },
          "blocked": {
            "description": "true=cierra el día (default); false=override que ABRE con start/end.",
            "type": "boolean"
          },
          "date": {
            "description": "Fecha específica en formato YYYY-MM-DD.",
            "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
            "type": "string"
          },
          "endMinute": {
            "description": "Fin del override en minutos (1..1440). Requerido si blocked=false.",
            "maximum": 1440,
            "minimum": 1,
            "type": "number"
          },
          "startMinute": {
            "description": "Inicio del override en minutos (0..1439). Requerido si blocked=false.",
            "maximum": 1439,
            "minimum": 0,
            "type": "number"
          }
        },
        "required": [
          "appointmentTypeId",
          "date"
        ],
        "type": "object"
      },
      "CreateAvailabilityRuleDto": {
        "properties": {
          "appointmentTypeId": {
            "description": "Tipo de cita dueño de la regla (debe ser del tenant).",
            "type": "string"
          },
          "endMinute": {
            "description": "Fin en minutos desde 00:00 local (1..1440). Debe ser > startMinute.",
            "maximum": 1440,
            "minimum": 1,
            "type": "number"
          },
          "resourceId": {
            "description": "Recurso específico (staff/sala) — debe ser del tenant. null = cualquiera.",
            "type": "string"
          },
          "startMinute": {
            "description": "Inicio en minutos desde 00:00 local (0..1439).",
            "maximum": 1439,
            "minimum": 0,
            "type": "number"
          },
          "tz": {
            "deprecated": true,
            "description": "Ignorado: los minutos son hora del negocio y la zona la resuelve el servidor. Se acepta por compatibilidad.",
            "type": "string"
          },
          "weekday": {
            "description": "Día de la semana: 0=domingo … 6=sábado.",
            "maximum": 6,
            "minimum": 0,
            "type": "number"
          }
        },
        "required": [
          "appointmentTypeId",
          "weekday",
          "startMinute",
          "endMinute"
        ],
        "type": "object"
      },
      "CreateCloseReasonDto": {
        "properties": {
          "label": {
            "maxLength": 60,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "label"
        ],
        "type": "object"
      },
      "CreateCommissionRuleDto": {
        "properties": {
          "active": {
            "description": "Activa. Default true.",
            "type": "boolean"
          },
          "amountMinor": {
            "description": "FIXED: monto en centavos (0..100_000_000 = $1M).",
            "maximum": 100000000,
            "minimum": 0,
            "type": "number"
          },
          "appointmentTypeId": {
            "description": "Servicio (AppointmentType); null/omitido = cualquiera.",
            "maxLength": 64,
            "type": "string"
          },
          "basis": {
            "description": "PERCENT = puntos base; FIXED = monto en centavos.",
            "enum": [
              "PERCENT",
              "FIXED"
            ],
            "type": "string"
          },
          "currency": {
            "description": "FIXED: moneda del monto fijo.",
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "rateBps": {
            "description": "PERCENT: puntos base (4000 = 40 %). 0..10000.",
            "maximum": 10000,
            "minimum": 0,
            "type": "number"
          },
          "resourceId": {
            "description": "Profesional (recurso STAFF); null/omitido = cualquiera.",
            "maxLength": 64,
            "type": "string"
          },
          "sourceType": {
            "description": "Default APPOINTMENT.",
            "enum": [
              "APPOINTMENT",
              "PACKAGE_SALE"
            ],
            "type": "string"
          }
        },
        "required": [
          "basis"
        ],
        "type": "object"
      },
      "CreateContactFieldDto": {
        "properties": {
          "key": {
            "description": "Clave estable. Si se omite se deriva de la etiqueta. NO cambia después: es lo que usan las automatizaciones.",
            "maxLength": 40,
            "type": "string"
          },
          "kind": {
            "default": "TEXT",
            "enum": [
              "TEXT",
              "NUMBER",
              "DATE",
              "BOOLEAN",
              "SELECT"
            ],
            "type": "string"
          },
          "label": {
            "maxLength": 60,
            "type": "string"
          },
          "options": {
            "description": "Opciones de un campo de lista (obligatorias si kind=SELECT).",
            "items": {
              "maxLength": 60,
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "label"
        ],
        "type": "object"
      },
      "CreateContactImportDto": {
        "properties": {
          "consent": {
            "additionalProperties": true,
            "description": "Consentimiento EXPLÍCITO (obligatorio). `{ mode: \"none\" }` importa sin permiso de marketing; `{ mode: \"granted\", medium, note }` declara que el negocio tiene el permiso (asiento inmutable por persona).",
            "type": "object"
          },
          "contentBase64": {
            "description": "Contenido del archivo (.csv o .xlsx) en base64. Máximo 5 MB.",
            "maxLength": 7200000,
            "minLength": 1,
            "type": "string"
          },
          "fileName": {
            "description": "Nombre del archivo (solo para el historial).",
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "mapping": {
            "additionalProperties": true,
            "description": "Mapeo de columnas (índice base 0): `{ phone, name?, email?, company?, fields?: { <fieldId>: <columna> } }`.",
            "type": "object"
          },
          "onExisting": {
            "description": "Qué hacer si el teléfono ya existe en el directorio.",
            "enum": [
              "skip",
              "fill",
              "overwrite"
            ],
            "type": "string"
          }
        },
        "required": [
          "fileName",
          "contentBase64",
          "mapping",
          "consent",
          "onExisting"
        ],
        "type": "object"
      },
      "CreateContactNoteDto": {
        "properties": {
          "body": {
            "description": "Texto libre de la nota (trim + cap server-side).",
            "maxLength": 2000,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "body"
        ],
        "type": "object"
      },
      "CreateContactSegmentDto": {
        "properties": {
          "filters": {
            "$ref": "#/components/schemas/SegmentFiltersDto"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "name",
          "filters"
        ],
        "type": "object"
      },
      "CreateConversationDto": {
        "properties": {
          "assignToMe": {
            "description": "Asignarme la conversación si está sin asignar (default true; se ignora con API key).",
            "type": "boolean"
          },
          "channelAccountId": {
            "description": "ChannelAccount.id de WhatsApp (alternativa a phoneNumberId).",
            "maxLength": 64,
            "type": "string"
          },
          "contactId": {
            "description": "Contacto de WhatsApp del directorio (alternativa a `phone`).",
            "maxLength": 64,
            "type": "string"
          },
          "name": {
            "description": "Nombre del cliente; queda como nombre del contacto.",
            "maxLength": 120,
            "pattern": "/^[^\\u0000-\\u001F\\u007F-\\u009F]*$/",
            "type": "string"
          },
          "optIn": {
            "allOf": [
              {
                "$ref": "#/components/schemas/NewConversationOptInDto"
              }
            ],
            "description": "Permiso del cliente. OBLIGATORIO para plantillas MARKETING si el contacto aún no tiene consentimiento de marketing (se registra como evidencia). Opcional para UTILITY/AUTHENTICATION (queda el medio en la auditoría)."
          },
          "phone": {
            "description": "Teléfono del cliente. México: 10 dígitos o con +52.",
            "example": "+52 81 1234 5678",
            "maxLength": 32,
            "minLength": 1,
            "type": "string"
          },
          "phoneNumberId": {
            "description": "PhoneNumber.id desde el que sale.",
            "maxLength": 64,
            "type": "string"
          },
          "template": {
            "$ref": "#/components/schemas/NewConversationTemplateDto"
          }
        },
        "required": [
          "template"
        ],
        "type": "object"
      },
      "CreateCrmTaskDto": {
        "properties": {
          "assignedToId": {
            "description": "Agente responsable (userId). Omitir = sin asignar.",
            "maxLength": 64,
            "type": "string"
          },
          "dueAt": {
            "description": "Cuándo vence (ISO-8601). Dispara el aviso interno al vencer (CRM-2).",
            "type": "string"
          },
          "kind": {
            "default": "FOLLOW_UP",
            "enum": [
              "FOLLOW_UP",
              "CALL",
              "OTHER"
            ],
            "type": "string"
          },
          "leadId": {
            "description": "Lead de contexto (kanban) si la tarea nace desde una tarjeta.",
            "maxLength": 64,
            "type": "string"
          },
          "title": {
            "description": "Qué recordar/hacer (trim + cap server-side).",
            "maxLength": 280,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "title",
          "dueAt"
        ],
        "type": "object"
      },
      "CreateEntryPointDto": {
        "properties": {
          "bookingSlug": {
            "maxLength": 40,
            "pattern": "^[a-z0-9-]{3,40}$",
            "type": "string"
          },
          "campaignRef": {
            "$ref": "#/components/schemas/CampaignRefDto"
          },
          "channelAccountId": {
            "minLength": 1,
            "type": "string"
          },
          "isActive": {
            "type": "boolean"
          },
          "kind": {
            "enum": [
              "WALINK",
              "QR",
              "WIDGET",
              "BOOKING",
              "IG_LINK",
              "MSGR_LINK"
            ],
            "type": "string"
          },
          "label": {
            "maxLength": 80,
            "minLength": 1,
            "type": "string"
          },
          "prefillText": {
            "maxLength": 1024,
            "type": "string"
          },
          "ref": {
            "maxLength": 250,
            "pattern": "^[A-Za-z0-9_-]{1,250}$",
            "type": "string"
          }
        },
        "required": [
          "kind",
          "label",
          "channelAccountId"
        ],
        "type": "object"
      },
      "CreateHostedLinkDto": {
        "properties": {
          "featureType": {
            "description": "`whatsapp_business_app_onboarding` = Coexistence (el número conserva la app WhatsApp Business del teléfono).",
            "enum": [
              "whatsapp_business_app_onboarding"
            ],
            "type": "string"
          },
          "flow": {
            "description": "Flujo a completar. Hoy: `whatsapp_onboarding`.",
            "enum": [
              "whatsapp_onboarding"
            ],
            "type": "string"
          },
          "metadata": {
            "additionalProperties": true,
            "description": "Objeto libre (≤ 2 KB en JSON) que se devuelve tal cual en la consulta y en los webhooks.",
            "type": "object"
          },
          "returnOrigin": {
            "description": "Origen https (esquema + host [+ puerto], SIN ruta ni barra final) de la ventana que abrió la liga. La página hace `window.opener.postMessage(..., returnOrigin)` SOLO a este origen.",
            "maxLength": 255,
            "type": "string"
          },
          "returnUrl": {
            "description": "URL https a la que la página redirige al terminar cuando NO se abrió como popup: `${returnUrl}?kontesta_link=<id>&status=<completed|failed|cancelled>`. Debe tener el MISMO origen que `returnOrigin`; si se omite `returnOrigin`, se deriva de esta URL.",
            "maxLength": 2048,
            "type": "string"
          },
          "ttlSeconds": {
            "default": 900,
            "description": "Vigencia de la liga en segundos.",
            "maximum": 3600,
            "minimum": 300,
            "type": "number"
          }
        },
        "required": [
          "flow"
        ],
        "type": "object"
      },
      "CreateIntegrationDto": {
        "properties": {
          "config": {
            "$ref": "#/components/schemas/OdooConfigDto"
          },
          "credentials": {
            "$ref": "#/components/schemas/OdooCredentialsDto"
          },
          "kind": {
            "enum": [
              "ODOO",
              "SALESFORCE"
            ],
            "type": "string"
          },
          "label": {
            "maxLength": 120,
            "type": "string"
          },
          "purpose": {
            "enum": [
              "CRM"
            ],
            "type": "string"
          }
        },
        "required": [
          "kind",
          "config",
          "credentials"
        ],
        "type": "object"
      },
      "CreateInvoiceConnectionDto": {
        "properties": {
          "config": {
            "$ref": "#/components/schemas/InvoiceConnectionConfigDto"
          },
          "credentials": {
            "type": "object"
          },
          "kind": {
            "enum": [
              "FACTURA_COM",
              "FACTURAMA",
              "ODOO",
              "FINKOK"
            ],
            "type": "string"
          },
          "label": {
            "maxLength": 120,
            "type": "string"
          }
        },
        "required": [
          "kind",
          "config",
          "credentials"
        ],
        "type": "object"
      },
      "CreateInvoiceDto": {
        "properties": {
          "conceptos": {
            "items": {
              "$ref": "#/components/schemas/CfdiConceptoInputDto"
            },
            "type": "array"
          },
          "currency": {
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "formaPago": {
            "maxLength": 2,
            "type": "string"
          },
          "idempotencyKey": {
            "maxLength": 80,
            "type": "string"
          },
          "metodoPago": {
            "enum": [
              "PUE",
              "PPD"
            ],
            "type": "string"
          },
          "receptor": {
            "$ref": "#/components/schemas/CfdiReceptorInputDto"
          },
          "sendMessage": {
            "type": "boolean"
          },
          "serieRef": {
            "maxLength": 64,
            "type": "string"
          },
          "tipoCambio": {
            "minimum": 1e-06,
            "type": "number"
          },
          "usoCFDI": {
            "maxLength": 4,
            "type": "string"
          }
        },
        "required": [
          "receptor",
          "conceptos",
          "usoCFDI",
          "formaPago",
          "metodoPago",
          "currency"
        ],
        "type": "object"
      },
      "CreateMacroDto": {
        "properties": {
          "active": {
            "type": "boolean"
          },
          "body": {
            "maxLength": 4000,
            "minLength": 1,
            "type": "string"
          },
          "category": {
            "maxLength": 40,
            "type": "string"
          },
          "shortcut": {
            "maxLength": 60,
            "minLength": 1,
            "type": "string"
          },
          "title": {
            "maxLength": 80,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "title",
          "shortcut",
          "body"
        ],
        "type": "object"
      },
      "CreateNoteDto": {
        "properties": {
          "body": {
            "description": "Texto de la nota. Lo ve el equipo, jamás el cliente.",
            "maxLength": 4000,
            "minLength": 1,
            "type": "string"
          },
          "mentionedUserIds": {
            "description": "Compañeros elegidos en el autocompletado \"@\" (ids de GET :id/mentionable). Se suman a los \"@handle\" escritos en el texto. Un id que no es de un compañero activo que ve la conversación se descarta en silencio; la respuesta trae las menciones finales. Máximo 20.",
            "items": {
              "maxLength": 64,
              "type": "string"
            },
            "maxItems": 20,
            "type": "array"
          }
        },
        "required": [
          "body"
        ],
        "type": "object"
      },
      "CreatePackageTypeDto": {
        "properties": {
          "appointmentTypeId": {
            "minLength": 1,
            "type": "string"
          },
          "currency": {
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "type": "string"
          },
          "priceMinor": {
            "minimum": 1,
            "type": "number"
          },
          "sessionCount": {
            "maximum": 1000,
            "minimum": 1,
            "type": "number"
          },
          "validityDays": {
            "maximum": 3650,
            "minimum": 1,
            "type": "number"
          }
        },
        "required": [
          "name",
          "appointmentTypeId",
          "sessionCount",
          "priceMinor"
        ],
        "type": "object"
      },
      "CreatePaymentAccountDto": {
        "properties": {
          "apiKey": {
            "description": "Access token (MP) / private key (OpenPay) / restricted key (Stripe). Se guarda cifrado.",
            "maxLength": 500,
            "type": "string"
          },
          "config": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PaymentAccountConfigDto"
              }
            ],
            "description": "Config NO-secreta por-pasarela. OpenPay: { sandbox }."
          },
          "displayName": {
            "description": "Nombre para mostrar (\"MP - Tacos El Güero\").",
            "maxLength": 120,
            "type": "string"
          },
          "isDefault": {
            "description": "Marca esta cuenta como default de su pasarela.",
            "type": "boolean"
          },
          "merchantId": {
            "description": "OpenPay merchant id (API_KEY).",
            "maxLength": 200,
            "type": "string"
          },
          "provider": {
            "enum": [
              "MERCADOPAGO",
              "OPENPAY",
              "STRIPE"
            ],
            "type": "string"
          },
          "webhookBasicPass": {
            "description": "OpenPay: password Basic-auth del webhook. Se guarda cifrado.",
            "maxLength": 200,
            "type": "string"
          },
          "webhookBasicUser": {
            "description": "OpenPay: usuario Basic-auth del webhook (SIN HMAC) — lo elige el tenant. Se guarda cifrado.",
            "maxLength": 200,
            "type": "string"
          },
          "webhookSecret": {
            "description": "Secreto de verificación de webhook por-cuenta (MP x-signature). Se guarda cifrado.",
            "maxLength": 500,
            "type": "string"
          }
        },
        "required": [
          "provider"
        ],
        "type": "object"
      },
      "CreatePaymentLinkDto": {
        "properties": {
          "amountMinor": {
            "description": "Monto en centavos (> 0). 15000 = $150.00.",
            "minimum": 1,
            "type": "number"
          },
          "concept": {
            "description": "Concepto visible al cliente (1..140).",
            "maxLength": 140,
            "type": "string"
          },
          "currency": {
            "description": "Default = cuenta / tenant.defaultCurrency.",
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "expiresAt": {
            "description": "Expiración ISO; default +48h.",
            "type": "string"
          },
          "provider": {
            "description": "Pasarela; omitir = cuenta default activa del tenant.",
            "enum": [
              "MERCADOPAGO",
              "OPENPAY",
              "STRIPE"
            ],
            "type": "string"
          },
          "sendMessage": {
            "description": "Default true (envía ya por WhatsApp); false = solo genera.",
            "type": "boolean"
          }
        },
        "required": [
          "amountMinor",
          "concept"
        ],
        "type": "object"
      },
      "CreateProductDto": {
        "properties": {
          "category": {
            "maxLength": 80,
            "type": "string"
          },
          "currency": {
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "description": {
            "maxLength": 2000,
            "type": "string"
          },
          "images": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "initialOnHand": {
            "maximum": 1000000,
            "minimum": 0,
            "type": "number"
          },
          "lowStockThreshold": {
            "maximum": 1000000,
            "minimum": 0,
            "type": "number"
          },
          "name": {
            "maxLength": 140,
            "minLength": 1,
            "type": "string"
          },
          "priceMinor": {
            "maximum": 100000000,
            "minimum": 0,
            "type": "number"
          },
          "sku": {
            "maxLength": 64,
            "minLength": 1,
            "type": "string"
          },
          "status": {
            "enum": [
              "ACTIVE",
              "HIDDEN",
              "ARCHIVED"
            ],
            "type": "string"
          }
        },
        "required": [
          "sku",
          "name",
          "priceMinor"
        ],
        "type": "object"
      },
      "CreateProgramDto": {
        "properties": {
          "autoEnroll": {
            "type": "boolean"
          },
          "brandColor": {
            "pattern": "/^#[0-9a-fA-F]{6}$/",
            "type": "string"
          },
          "brandLogoUrl": {
            "maxLength": 500,
            "type": "string"
          },
          "cardDesign": {
            "type": "object"
          },
          "currency": {
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "earnOnAppointmentCompleted": {
            "type": "boolean"
          },
          "earnOnPayment": {
            "type": "boolean"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "type": "string"
          },
          "pointsExpiryDays": {
            "minimum": 1,
            "type": "number"
          },
          "redeemOptions": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "rules": {
            "type": "object"
          },
          "type": {
            "enum": [
              "POINTS",
              "STAMPS"
            ],
            "type": "string"
          }
        },
        "required": [
          "name",
          "type"
        ],
        "type": "object"
      },
      "CreateRecommendedTemplateDto": {
        "properties": {
          "wabaAccountId": {
            "description": "id interno de la fila WabaAccount. Obligatorio solo si el tenant tiene más de una conectada.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "CreateSavedViewDto": {
        "properties": {
          "filters": {
            "$ref": "#/components/schemas/SavedViewFiltersDto"
          },
          "name": {
            "maxLength": 40,
            "minLength": 1,
            "type": "string"
          },
          "shared": {
            "description": "La ve todo el equipo. Sólo OWNER/ADMIN: una vista compartida aparece en la barra de todos.",
            "type": "boolean"
          }
        },
        "required": [
          "name",
          "filters"
        ],
        "type": "object"
      },
      "CreateScheduledMessageDto": {
        "properties": {
          "kind": {
            "enum": [
              "text",
              "template"
            ],
            "type": "string"
          },
          "scheduledAt": {
            "description": "Cuándo sale, en ISO-8601 con zona. Entre 1 minuto y 60 días.",
            "type": "string"
          },
          "template": {
            "additionalProperties": true,
            "type": "object"
          },
          "text": {
            "maxLength": 4096,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "kind",
          "scheduledAt"
        ],
        "type": "object"
      },
      "CreateSerieDto": {
        "properties": {
          "folio": {
            "minimum": 1,
            "type": "number"
          },
          "letra": {
            "maxLength": 8,
            "type": "string"
          },
          "tipoDocumento": {
            "maxLength": 32,
            "type": "string"
          }
        },
        "required": [
          "letra",
          "tipoDocumento"
        ],
        "type": "object"
      },
      "CreateTeamDto": {
        "properties": {
          "color": {
            "description": "Hex de la paleta, p. ej. #17a3de.",
            "nullable": true,
            "pattern": "/^#[0-9a-fA-F]{6}$/",
            "type": "string"
          },
          "memberIds": {
            "description": "User.id de los miembros.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "name": {
            "maxLength": 40,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CreateTemplateDto": {
        "properties": {
          "category": {
            "enum": [
              "UTILITY",
              "MARKETING",
              "AUTHENTICATION"
            ],
            "type": "string"
          },
          "components": {
            "items": {
              "$ref": "#/components/schemas/TemplateComponentDto"
            },
            "type": "array"
          },
          "language": {
            "maxLength": 15,
            "minLength": 2,
            "type": "string"
          },
          "name": {
            "maxLength": 512,
            "minLength": 1,
            "pattern": "/^[a-z0-9_]+$/",
            "type": "string"
          },
          "wabaAccountId": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "language",
          "category",
          "components"
        ],
        "type": "object"
      },
      "CreateWebhookDto": {
        "properties": {
          "description": {
            "maxLength": 200,
            "type": "string"
          },
          "events": {
            "enum": [
              "message.received",
              "message.status_changed",
              "conversation.assigned",
              "conversation.closed",
              "lead.created",
              "lead.stage_changed",
              "review.obtained",
              "appointment.created",
              "appointment.updated",
              "payment.updated",
              "handoff.requested",
              "call.ended",
              "call.recording_ready",
              "hosted_link.completed",
              "hosted_link.failed"
            ],
            "type": "string"
          },
          "url": {
            "maxLength": 2048,
            "pattern": "/^https:\\/\\//i",
            "type": "string"
          }
        },
        "required": [
          "url"
        ],
        "type": "object"
      },
      "DismissPersonSuggestionDto": {
        "properties": {
          "contactId": {
            "description": "Una de las dos fichas del par sugerido.",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_-]{1,64}$",
            "type": "string"
          },
          "otherContactId": {
            "description": "La otra ficha del par sugerido.",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_-]{1,64}$",
            "type": "string"
          }
        },
        "required": [
          "contactId",
          "otherContactId"
        ],
        "type": "object"
      },
      "EarnManualDto": {
        "properties": {
          "amountMinor": {
            "minimum": 1,
            "type": "number"
          },
          "idempotencyKey": {
            "maxLength": 80,
            "type": "string"
          },
          "notify": {
            "type": "boolean"
          },
          "points": {
            "minimum": 1,
            "type": "number"
          },
          "programId": {
            "minLength": 1,
            "type": "string"
          },
          "reason": {
            "maxLength": 280,
            "type": "string"
          }
        },
        "required": [
          "programId"
        ],
        "type": "object"
      },
      "EmisorDomicilioDto": {
        "properties": {
          "calle": {
            "maxLength": 120,
            "type": "string"
          },
          "colonia": {
            "maxLength": 120,
            "type": "string"
          },
          "estado": {
            "maxLength": 120,
            "type": "string"
          },
          "municipio": {
            "maxLength": 120,
            "type": "string"
          },
          "numeroExterior": {
            "maxLength": 20,
            "type": "string"
          },
          "numeroInterior": {
            "maxLength": 20,
            "type": "string"
          },
          "pais": {
            "maxLength": 3,
            "type": "string"
          }
        },
        "required": [
          "calle",
          "numeroExterior",
          "colonia",
          "municipio",
          "estado"
        ],
        "type": "object"
      },
      "EnrollDto": {
        "properties": {
          "programId": {
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "programId"
        ],
        "type": "object"
      },
      "ErasureByPhoneDto": {
        "properties": {
          "phone": {
            "description": "Teléfono de WhatsApp de la persona que pidió borrar sus datos, como lo escribe la gente. México: 10 dígitos o con +52.",
            "example": "+52 81 1234 5678",
            "maxLength": 32,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "phone"
        ],
        "type": "object"
      },
      "GrantPackageDto": {
        "properties": {
          "idempotencyKey": {
            "maxLength": 80,
            "type": "string"
          },
          "packageTypeId": {
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "packageTypeId"
        ],
        "type": "object"
      },
      "InvoiceConnectionConfigDto": {
        "properties": {
          "defaultClaveProdServ": {
            "maxLength": 16,
            "type": "string"
          },
          "defaultSerieRef": {
            "maxLength": 64,
            "type": "string"
          },
          "fPlugin": {
            "maxLength": 128,
            "type": "string"
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "sandbox"
        ],
        "type": "object"
      },
      "IssueWalletPassDto": {
        "properties": {
          "subjectId": {
            "minLength": 1,
            "type": "string"
          },
          "subjectType": {
            "enum": [
              "LOYALTY_MEMBER",
              "LOYALTY_PACKAGE"
            ],
            "type": "string"
          }
        },
        "required": [
          "subjectType",
          "subjectId"
        ],
        "type": "object"
      },
      "JoinWaitlistDto": {
        "properties": {
          "appointmentTypeId": {
            "description": "AppointmentType (servicio) deseado.",
            "type": "string"
          },
          "earliestAt": {
            "description": "Inicio de la ventana aceptable (ISO instant); omitido = desde ya.",
            "type": "string"
          },
          "latestAt": {
            "description": "Fin de la ventana aceptable (ISO instant); omitido = sin tope.",
            "type": "string"
          },
          "notes": {
            "description": "Notas internas de la entrada.",
            "maxLength": 2000,
            "type": "string"
          },
          "priority": {
            "description": "Prioridad (mayor primero); FIFO desempata por antigüedad.",
            "maximum": 1000,
            "minimum": 0,
            "type": "number"
          },
          "resourceId": {
            "description": "Recurso (staff/box) preferido; omitido = cualquiera.",
            "type": "string"
          }
        },
        "required": [
          "appointmentTypeId"
        ],
        "type": "object"
      },
      "LinkPersonDto": {
        "properties": {
          "keep": {
            "description": "`self` = la ficha de esta ruta queda como principal; `other` = la otra. Ninguna se borra ni pierde sus hilos.",
            "enum": [
              "self",
              "other"
            ],
            "type": "string"
          },
          "otherContactId": {
            "description": "Id (opaco) de la otra ficha del directorio, tal como lo devuelven las sugerencias.",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_-]{1,64}$",
            "type": "string"
          }
        },
        "required": [
          "otherContactId",
          "keep"
        ],
        "type": "object"
      },
      "MergeContactDto": {
        "properties": {
          "keep": {
            "description": "`self` = gana la ficha de esta conversación; `other` = gana la del duplicado. La que pierde queda como histórico.",
            "enum": [
              "self",
              "other"
            ],
            "type": "string"
          },
          "otherConversationId": {
            "description": "Conversación (opaca) del contacto duplicado, tal como la devuelve `merge-candidates`.",
            "maxLength": 64,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "otherConversationId",
          "keep"
        ],
        "type": "object"
      },
      "MessageAtDateHitDto": {
        "properties": {
          "id": {
            "description": "Id del mensaje ancla.",
            "type": "string"
          },
          "sentAt": {
            "description": "Instante del mensaje (ISO 8601, UTC).",
            "type": "string"
          }
        },
        "required": [
          "id",
          "sentAt"
        ],
        "type": "object"
      },
      "MessageAtDateResultDto": {
        "properties": {
          "date": {
            "description": "El día pedido, tal cual.",
            "example": "2026-09-03",
            "type": "string"
          },
          "exact": {
            "description": "true = `message` es el PRIMER mensaje de ese día. false = ese día no hubo mensajes y `message` es el primero POSTERIOR (o null si tampoco hay ninguno después).",
            "type": "boolean"
          },
          "message": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MessageAtDateHitDto"
              }
            ],
            "nullable": true
          },
          "tz": {
            "description": "Zona con la que se resolvió el día.",
            "example": "America/Mexico_City",
            "type": "string"
          }
        },
        "required": [
          "date",
          "tz",
          "exact",
          "message"
        ],
        "type": "object"
      },
      "ModerateReviewDto": {
        "properties": {
          "isPublic": {
            "type": "boolean"
          }
        },
        "required": [
          "isPublic"
        ],
        "type": "object"
      },
      "MoneyOut": {
        "properties": {
          "amount": {
            "type": "string"
          },
          "currency": {
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          }
        },
        "required": [
          "amount",
          "currency"
        ],
        "type": "object"
      },
      "NewConversationOptInDto": {
        "properties": {
          "medium": {
            "description": "Por dónde lo aceptó.",
            "enum": [
              "whatsapp",
              "llamada",
              "presencial",
              "formulario",
              "correo",
              "otro"
            ],
            "type": "string"
          },
          "note": {
            "description": "Qué dijo el cliente y cómo (texto declarado).",
            "maxLength": 500,
            "minLength": 10,
            "type": "string"
          },
          "occurredAt": {
            "description": "Cuándo lo aceptó (ISO 8601, no futuro). Default: ahora.",
            "type": "string"
          }
        },
        "required": [
          "medium",
          "note"
        ],
        "type": "object"
      },
      "NewConversationTemplateDto": {
        "properties": {
          "components": {
            "description": "Componentes en formato de Meta (header/body/buttons con parámetros). Avanzado: si viene, se envía tal cual y no se valida el número de variables. Necesario en plantillas con encabezado de imagen/video/documento o botones con URL variable.",
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "language": {
            "description": "Código de idioma de la plantilla.",
            "example": "es_MX",
            "pattern": "/^[A-Za-z]{2,3}(_[A-Za-z]{2,4})?$/",
            "type": "string"
          },
          "name": {
            "description": "Nombre de la plantilla en Meta.",
            "example": "seguimiento_solicitud",
            "pattern": "/^[a-z0-9_]{1,512}$/",
            "type": "string"
          },
          "variables": {
            "description": "Valores de {{1}}…{{n}} del cuerpo, en orden. Deben ser tantos como variables tenga el cuerpo.",
            "example": [
              "Ana",
              "la cotización del lunes"
            ],
            "items": {
              "maxLength": 1024,
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "name",
          "language"
        ],
        "type": "object"
      },
      "OdooConfigDto": {
        "properties": {
          "db": {
            "maxLength": 128,
            "type": "string"
          },
          "url": {
            "maxLength": 2048,
            "pattern": "/^https:\\/\\//i",
            "type": "string"
          }
        },
        "required": [
          "url",
          "db"
        ],
        "type": "object"
      },
      "OdooCredentialsDto": {
        "properties": {
          "apiKey": {
            "maxLength": 512,
            "type": "string"
          },
          "login": {
            "maxLength": 256,
            "type": "string"
          }
        },
        "required": [
          "login",
          "apiKey"
        ],
        "type": "object"
      },
      "OutboundMediaDto": {
        "properties": {
          "caption": {
            "description": "Texto que acompaña a la imagen o el documento.",
            "maxLength": 1024,
            "type": "string"
          },
          "filename": {
            "description": "Nombre con el que el cliente verá el documento. Sólo para kind=document — sin él, WhatsApp muestra un nombre opaco.",
            "maxLength": 240,
            "type": "string"
          },
          "id": {
            "description": "mediaId devuelto por POST /v1/media/upload. Es el camino normal: el archivo ya vive en Meta.",
            "maxLength": 256,
            "type": "string"
          },
          "link": {
            "description": "URL pública del archivo, alternativa a `id`. Meta la descarga al enviar; debe ser accesible sin auth. Sólo https con dominio público (sin IP literal, host local ni credenciales).",
            "maxLength": 2048,
            "type": "string"
          },
          "voice": {
            "default": true,
            "description": "Sólo kind=audio. `true` (por omisión) lo manda como NOTA DE VOZ: burbuja de micrófono y forma de onda en el teléfono del cliente. `false` lo manda como archivo de audio (ícono de música), p. ej. una canción o un podcast. Todo audio subido por /media/upload ya es OGG/Opus.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "PatchCrmTaskDto": {
        "properties": {
          "assignedToId": {
            "description": "Reasignar (userId) o `null` para quitar asignación.",
            "maxLength": 64,
            "nullable": true,
            "type": "string"
          },
          "dueAt": {
            "description": "Reprogramar el vencimiento (ISO-8601).",
            "type": "string"
          },
          "kind": {
            "enum": [
              "FOLLOW_UP",
              "CALL",
              "OTHER"
            ],
            "type": "string"
          },
          "status": {
            "description": "Completar o cancelar (no re-abre).",
            "enum": [
              "DONE",
              "CANCELLED"
            ],
            "type": "string"
          },
          "title": {
            "maxLength": 280,
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "PaymentAccountConfigDto": {
        "properties": {
          "sandbox": {
            "description": "OpenPay: true/ausente = sandbox-api.openpay.mx; false = api.openpay.mx (prod).",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "PresenceHeartbeatDto": {
        "properties": {
          "leave": {
            "default": false,
            "description": "El agente SALIÓ del hilo (cambió de conversación o cerró). Lo quita de \"viendo\".",
            "type": "boolean"
          },
          "typing": {
            "default": false,
            "description": "El agente está tecleando ahora mismo.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "QuickReplyDto": {
        "properties": {
          "payload": {
            "description": "Dato que vuelve en el webhook cuando el cliente lo toca. Por omisión = `title`. Meta: hasta 1000 caracteres.",
            "maxLength": 4000,
            "type": "string"
          },
          "title": {
            "description": "Texto del botón (Meta: hasta 20 caracteres).",
            "example": "Agendar",
            "maxLength": 200,
            "type": "string"
          }
        },
        "required": [
          "title"
        ],
        "type": "object"
      },
      "RagEngineStatusDto": {
        "properties": {
          "engine": {
            "description": "Motor de conocimiento efectivo de la cuenta.",
            "enum": [
              "native",
              "ariarag"
            ],
            "type": "string"
          },
          "engineSource": {
            "description": "`tenant` = fijado para la cuenta; `default` = sigue el default del servidor.",
            "enum": [
              "tenant",
              "default"
            ],
            "type": "string"
          },
          "provisioned": {
            "description": "Con el motor externo: si la cuenta ya está provisionada en el motor. null con native.",
            "nullable": true,
            "type": "boolean"
          },
          "usage": {
            "description": "Con el motor externo: plan, cuotas y consumo en el motor.",
            "nullable": true,
            "type": "object"
          }
        },
        "required": [
          "engine",
          "engineSource",
          "provisioned",
          "usage"
        ],
        "type": "object"
      },
      "ReactionDto": {
        "properties": {
          "emoji": {
            "description": "Emoji a poner: UN solo emoji Unicode (con modificadores/ZWJ si los tiene). Cadena vacía = QUITAR la reacción que se había puesto. Texto o varios emojis ⇒ 400.",
            "example": "👍",
            "maxLength": 32,
            "type": "string"
          },
          "messageId": {
            "description": "Id INTERNO (Message.id, el que devuelve GET /messages) del mensaje del cliente al que se reacciona. Debe ser entrante, de esta conversación y de ≤30 días (WhatsApp no entrega reacciones más viejas).",
            "maxLength": 64,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "messageId",
          "emoji"
        ],
        "type": "object"
      },
      "ReconcileTemplatesDto": {
        "properties": {
          "wabaAccountId": {
            "type": "string"
          }
        },
        "required": [
          "wabaAccountId"
        ],
        "type": "object"
      },
      "RecordConsentDto": {
        "properties": {
          "evidence": {
            "$ref": "#/components/schemas/ConsentEvidenceDto"
          },
          "intent": {
            "description": "`opt_in` = registrar · `opt_out` = retirar.",
            "enum": [
              "opt_in",
              "opt_out"
            ],
            "type": "string"
          }
        },
        "required": [
          "intent"
        ],
        "type": "object"
      },
      "RedeemDto": {
        "properties": {
          "costPoints": {
            "minimum": 1,
            "type": "number"
          },
          "costStamps": {
            "minimum": 1,
            "type": "number"
          },
          "idempotencyKey": {
            "maxLength": 80,
            "type": "string"
          },
          "notify": {
            "type": "boolean"
          },
          "programId": {
            "minLength": 1,
            "type": "string"
          },
          "reason": {
            "maxLength": 280,
            "type": "string"
          },
          "redeemOptionIndex": {
            "type": "number"
          }
        },
        "required": [
          "programId"
        ],
        "type": "object"
      },
      "ReminderSlotDto": {
        "properties": {
          "enabled": {
            "description": "Enviar este recordatorio.",
            "type": "boolean"
          },
          "leadMinutes": {
            "description": "Minutos de anticipación (5 … 20160 = 14 días).",
            "maximum": 20160,
            "minimum": 5,
            "type": "number"
          }
        },
        "type": "object"
      },
      "ReplyReviewDto": {
        "properties": {
          "text": {
            "maxLength": 4096,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "text"
        ],
        "type": "object"
      },
      "RequestCallPermissionDto": {
        "properties": {
          "body": {
            "maxLength": 1024,
            "type": "string"
          }
        },
        "type": "object"
      },
      "RequestCallPermissionResponseOut": {
        "properties": {
          "blockedReason": {
            "enum": [
              "rate_limited",
              "already_granted",
              "cap_reached"
            ],
            "nullable": true,
            "type": "string"
          },
          "canCall": {
            "type": "boolean"
          },
          "canRequest": {
            "type": "boolean"
          },
          "channelAccountId": {
            "type": "string"
          },
          "conversationId": {
            "type": "string"
          },
          "expiresAt": {
            "nullable": true,
            "type": "string"
          },
          "messageId": {
            "type": "string"
          },
          "missedCallsBeforeRevoke": {
            "type": "number"
          },
          "nextRequestAt": {
            "nullable": true,
            "type": "string"
          },
          "pendingSince": {
            "nullable": true,
            "type": "string"
          },
          "preview": {
            "type": "string"
          },
          "reason": {
            "enum": [
              "rate_limited",
              "already_granted",
              "cap_reached"
            ],
            "nullable": true,
            "type": "string"
          },
          "requestsLast7d": {
            "type": "number"
          },
          "retryAt": {
            "nullable": true,
            "type": "string"
          },
          "retryReason": {
            "enum": [
              "per24h",
              "per7d"
            ],
            "nullable": true,
            "type": "string"
          },
          "sentAs": {
            "enum": [
              "interactive",
              "template"
            ],
            "type": "string"
          },
          "status": {
            "enum": [
              "NONE",
              "TEMPORARY",
              "PERMANENT",
              "REJECTED",
              "REVOKED"
            ],
            "type": "string"
          },
          "templateAvailable": {
            "type": "boolean"
          },
          "templateCost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MoneyOut"
              }
            ],
            "nullable": true
          },
          "windowOpen": {
            "type": "boolean"
          }
        },
        "required": [
          "conversationId",
          "channelAccountId",
          "status",
          "canRequest",
          "requestsLast7d",
          "missedCallsBeforeRevoke",
          "windowOpen",
          "templateAvailable",
          "preview",
          "canCall",
          "messageId",
          "sentAs"
        ],
        "type": "object"
      },
      "RequestContactInfoDto": {
        "properties": {
          "text": {
            "description": "Texto que acompaña al botón «Compartir número» de WhatsApp (opcional, hasta 1024). El botón no se personaliza. Sin texto se manda uno por omisión en español.",
            "maxLength": 1024,
            "type": "string"
          }
        },
        "type": "object"
      },
      "RequestHandoffDto": {
        "properties": {
          "assignToUserId": {
            "description": "Enrutar DIRECTO a un agente (\"pásaselo a Karla\"): User.id activo del tenant (400 ASSIGNEE_INVALID si no). Si se omite, el sistema reparte por round-robin (o dentro del equipo de la política).",
            "maxLength": 64,
            "type": "string"
          },
          "note": {
            "description": "Contexto para el equipo (va a la nota interna del traspaso, jamás al cliente). Ej.: \"quiere cotización de 40 sillas para el viernes\".",
            "maxLength": 1000,
            "minLength": 1,
            "type": "string"
          },
          "notifyCustomer": {
            "description": "Avisar al cliente con el mensaje determinista de la política del número (\"Te paso con una persona…\"). Solo aplica a la API key (default true); una sesión humana nunca manda ack (el humano ya está ahí).",
            "type": "boolean"
          },
          "reason": {
            "description": "Motivo (solo API key; una sesión humana siempre queda como MANUAL). Default OTHER. Los motivos del bot (NO_KB_MATCH, MAX_TURNS, PROVIDER_ERROR…) los fija el sistema, no se aceptan aquí.",
            "enum": [
              "CUSTOMER_REQUEST",
              "PAYMENT_OR_BOOKING",
              "MANUAL",
              "OTHER"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "RescheduleAppointmentDto": {
        "properties": {
          "resourceId": {
            "description": "Recurso (staff/sala) específico.",
            "type": "string"
          },
          "startAt": {
            "description": "Nuevo inicio (ISO instant). Debe coincidir con un slot.",
            "type": "string"
          },
          "tz": {
            "deprecated": true,
            "description": "Solo display: el servidor la IGNORA — la zona es SIEMPRE la del negocio. Se acepta por compatibilidad.",
            "type": "string"
          }
        },
        "required": [
          "startAt"
        ],
        "type": "object"
      },
      "ReviewRequestDto": {
        "properties": {
          "platform": {
            "enum": [
              "GBP",
              "FACEBOOK"
            ],
            "type": "string"
          }
        },
        "required": [
          "platform"
        ],
        "type": "object"
      },
      "SavedViewFiltersDto": {
        "properties": {
          "assignedTo": {
            "description": "`me` | `unassigned` | `<userId>`",
            "maxLength": 64,
            "type": "string"
          },
          "channel": {
            "enum": [
              "whatsapp",
              "instagram",
              "messenger"
            ],
            "type": "string"
          },
          "origin": {
            "maxLength": 32,
            "type": "string"
          },
          "phoneNumberId": {
            "description": "Número de WhatsApp (el id del número de WhatsApp en Kontesta), igual que en el listado.",
            "maxLength": 64,
            "type": "string"
          },
          "q": {
            "maxLength": 120,
            "type": "string"
          },
          "snoozed": {
            "enum": [
              "hide",
              "only",
              "all"
            ],
            "type": "string"
          },
          "status": {
            "enum": [
              "open",
              "closed",
              "all"
            ],
            "type": "string"
          },
          "tagId": {
            "maxLength": 64,
            "type": "string"
          },
          "waitingOverMin": {
            "description": "Llevan más de N minutos esperando respuesta.",
            "minimum": 1,
            "type": "number"
          }
        },
        "type": "object"
      },
      "SegmentFiltersDto": {
        "properties": {
          "field": {
            "description": "Filtro por campo personalizado, `fieldId:valor`; se combinan con Y.",
            "items": {
              "maxLength": 600,
              "type": "string"
            },
            "type": "array"
          },
          "includeArchived": {
            "description": "Incluye los archivados.",
            "type": "boolean"
          },
          "phoneNumberId": {
            "description": "Sólo contactos con al menos una conversación en este número (`PhoneNumber.id`).",
            "maxLength": 64,
            "type": "string"
          },
          "q": {
            "description": "Busca en nombre, empresa, correo y teléfono.",
            "maxLength": 120,
            "type": "string"
          },
          "tagId": {
            "description": "Sólo contactos con esta etiqueta.",
            "maxLength": 64,
            "type": "string"
          }
        },
        "type": "object"
      },
      "SellPackageDto": {
        "properties": {
          "packageTypeId": {
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "packageTypeId"
        ],
        "type": "object"
      },
      "SendBalanceDto": {
        "properties": {
          "programId": {
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "SendFlowDto": {
        "properties": {
          "bodyText": {
            "description": "Texto que acompaña el botón (default por propósito).",
            "maxLength": 1024,
            "minLength": 1,
            "type": "string"
          },
          "ctaLabel": {
            "description": "Etiqueta del botón (default \"Abrir formulario\").",
            "maxLength": 30,
            "minLength": 1,
            "type": "string"
          },
          "flowPurpose": {
            "description": "Propósito del Flow a enviar.",
            "enum": [
              "LEAD_CAPTURE",
              "APPOINTMENT_REQUEST",
              "CONTACT_CAPTURE"
            ],
            "type": "string"
          }
        },
        "required": [
          "flowPurpose"
        ],
        "type": "object"
      },
      "SendInConversationDto": {
        "properties": {
          "interactive": {
            "additionalProperties": true,
            "description": "Para kind=interactive (C2 · «Botones o lista», solo WhatsApp y con la ventana de 24 h abierta): `{ type:\"button\", body, buttons:[{title}] }` (1–3, títulos ≤ 20 y distintos, cuerpo ≤ 1024) o `{ type:\"list\", body, button, rows:[{title, description?}] }` / `sections:[{title?, rows}]` (1–10 secciones, 1–10 filas EN TOTAL, título ≤ 24, descripción ≤ 72, header solo de texto). Los IDs de las opciones los asigna el API. Errores: 422 INTERACTIVE_INVALID `{ field, rule }` · 409 WINDOW_CLOSED · 422 CHANNEL_NOT_SUPPORTED.",
            "type": "object"
          },
          "kind": {
            "enum": [
              "text",
              "template",
              "image",
              "document",
              "audio",
              "video",
              "reaction",
              "interactive"
            ],
            "type": "string"
          },
          "media": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OutboundMediaDto"
              }
            ],
            "description": "Para kind=image|document|audio|video: el medio a enviar (`id` de Meta o `link` público)."
          },
          "quickReplies": {
            "description": "Solo kind=text en Instagram/Messenger con la bandera `channels.meta.interactions`: botones que el cliente toca para contestar (Meta `quick_replies`). Hasta 13, de 20 caracteres. 422 QUICK_REPLIES_INVALID (límites) · QUICK_REPLIES_UNSUPPORTED (WhatsApp, otro kind o bandera apagada).",
            "items": {
              "$ref": "#/components/schemas/QuickReplyDto"
            },
            "type": "array"
          },
          "reaction": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ReactionDto"
              }
            ],
            "description": "Para kind=reaction: a qué mensaje y con qué emoji. Se persiste como Message type=reaction colgado del mensaje reaccionado; en el hilo se ve como el emoji bajo la burbuja."
          },
          "replyTo": {
            "description": "Cita: `externalMessageId` del mensaje al que se responde (el id que devuelve GET /messages). WhatsApp pinta la cita arriba de la burbuja. No aplica a plantillas: Cloud API las rechaza con contexto.",
            "maxLength": 256,
            "type": "string"
          },
          "template": {
            "additionalProperties": true,
            "description": "Para kind=template: payload de envío (nombre, idioma, componentes).",
            "type": "object"
          },
          "text": {
            "description": "Para kind=text.",
            "maxLength": 4096,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "kind"
        ],
        "type": "object"
      },
      "SendMessageDto": {
        "properties": {
          "content": {
            "type": "object"
          },
          "kind": {
            "enum": [
              "text",
              "template",
              "image",
              "document",
              "video",
              "audio",
              "interactive"
            ],
            "type": "string"
          },
          "phoneNumberId": {
            "type": "string"
          },
          "replyTo": {
            "maxLength": 256,
            "nullable": true,
            "type": "string"
          },
          "template": {
            "type": "object"
          },
          "text": {
            "maxLength": 4096,
            "type": "string"
          },
          "to": {
            "maxLength": 140,
            "minLength": 8,
            "type": "string"
          }
        },
        "required": [
          "phoneNumberId",
          "to",
          "kind"
        ],
        "type": "object"
      },
      "SendProductDto": {
        "properties": {
          "note": {
            "maxLength": 500,
            "type": "string"
          },
          "productId": {
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "productId"
        ],
        "type": "object"
      },
      "SendWalletPassDto": {
        "properties": {
          "subjectId": {
            "minLength": 1,
            "type": "string"
          },
          "subjectType": {
            "enum": [
              "LOYALTY_MEMBER",
              "LOYALTY_PACKAGE"
            ],
            "type": "string"
          }
        },
        "required": [
          "subjectType",
          "subjectId"
        ],
        "type": "object"
      },
      "SetFieldValueDto": {
        "properties": {
          "value": {
            "description": "Valor. Vacío borra el dato capturado.",
            "maxLength": 500,
            "type": "string"
          }
        },
        "type": "object"
      },
      "SnoozeConversationDto": {
        "properties": {
          "note": {
            "description": "Por qué se pospuso (lo ve el equipo, no el cliente).",
            "maxLength": 280,
            "type": "string"
          },
          "until": {
            "description": "Hasta cuándo posponer (ISO 8601). Puede ADELANTARSE: si cae después de que expire la ventana de 24 h de WhatsApp, el servidor despierta la conversación antes, mientras todavía se puede contestar con texto libre.",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "until"
        ],
        "type": "object"
      },
      "SpeedToLeadAgentBucketDto": {
        "properties": {
          "answered": {
            "description": "Respondidas (firstResponseAt IS NOT NULL).",
            "type": "number"
          },
          "name": {
            "nullable": true,
            "type": "string"
          },
          "p50RawSec": {
            "description": "Mediana del TTFR BRUTO (reloj de pared, firstResponseAt − firstInboundAt, sin descontar fuera de horario), segundos.",
            "nullable": true,
            "type": "number"
          },
          "p50Sec": {
            "description": "Mediana del tiempo a la primera respuesta EN HORARIO HÁBIL (segundos): descuenta los tramos fuera del horario del tenant; un inbound fuera de horario empieza a contar en la siguiente apertura. Sin horario configurado = p50RawSec. Es el KPI que pinta el dashboard.",
            "nullable": true,
            "type": "number"
          },
          "p90RawSec": {
            "description": "p90 del TTFR BRUTO (reloj de pared), segundos.",
            "nullable": true,
            "type": "number"
          },
          "p90Sec": {
            "description": "p90 del TTFR en horario hábil (segundos). Sin horario = p90RawSec.",
            "nullable": true,
            "type": "number"
          },
          "pctUnder5m": {
            "description": "Respondidas con TTFR hábil < 5 min ÷ TOTAL (las no respondidas cuentan en contra). Sin horario = pctUnder5mRaw.",
            "type": "number"
          },
          "pctUnder5mRaw": {
            "description": "Respondidas con TTFR bruto < 5 min ÷ TOTAL.",
            "type": "number"
          },
          "total": {
            "description": "Conversaciones de la población (firstInboundAt ∈ [from, to)).",
            "type": "number"
          },
          "userId": {
            "description": "User.id del agente (solo primeras respuestas HUMAN).",
            "type": "string"
          }
        },
        "required": [
          "total",
          "answered",
          "p50Sec",
          "p90Sec",
          "pctUnder5m",
          "p50RawSec",
          "p90RawSec",
          "pctUnder5mRaw",
          "userId",
          "name"
        ],
        "type": "object"
      },
      "SpeedToLeadAgentRankRowDto": {
        "properties": {
          "answered": {
            "description": "Conversaciones de la población que respondió ÉL (HUMAN).",
            "type": "number"
          },
          "missed": {
            "description": "«Vencidas»: esperas que dejó pasar y se reasignaron antes de la 1.ª respuesta.",
            "type": "number"
          },
          "name": {
            "nullable": true,
            "type": "string"
          },
          "p50RawSec": {
            "description": "Las mismas tres en BRUTO (24/7), al lado (D4).",
            "nullable": true,
            "type": "number"
          },
          "p50Sec": {
            "description": "Reloj del AGENTE, hábil.",
            "nullable": true,
            "type": "number"
          },
          "p90RawSec": {
            "nullable": true,
            "type": "number"
          },
          "p90Sec": {
            "nullable": true,
            "type": "number"
          },
          "pctUnderGoal": {
            "description": "Proporción bajo `goalMin`, sobre `answered`.",
            "nullable": true,
            "type": "number"
          },
          "pctUnderGoalRaw": {
            "nullable": true,
            "type": "number"
          },
          "userId": {
            "description": "User.id del agente.",
            "type": "string"
          }
        },
        "required": [
          "userId",
          "name",
          "answered",
          "p50Sec",
          "p90Sec",
          "pctUnderGoal",
          "p50RawSec",
          "p90RawSec",
          "pctUnderGoalRaw",
          "missed"
        ],
        "type": "object"
      },
      "SpeedToLeadBucketDto": {
        "properties": {
          "answered": {
            "description": "Respondidas (firstResponseAt IS NOT NULL).",
            "type": "number"
          },
          "p50RawSec": {
            "description": "Mediana del TTFR BRUTO (reloj de pared, firstResponseAt − firstInboundAt, sin descontar fuera de horario), segundos.",
            "nullable": true,
            "type": "number"
          },
          "p50Sec": {
            "description": "Mediana del tiempo a la primera respuesta EN HORARIO HÁBIL (segundos): descuenta los tramos fuera del horario del tenant; un inbound fuera de horario empieza a contar en la siguiente apertura. Sin horario configurado = p50RawSec. Es el KPI que pinta el dashboard.",
            "nullable": true,
            "type": "number"
          },
          "p90RawSec": {
            "description": "p90 del TTFR BRUTO (reloj de pared), segundos.",
            "nullable": true,
            "type": "number"
          },
          "p90Sec": {
            "description": "p90 del TTFR en horario hábil (segundos). Sin horario = p90RawSec.",
            "nullable": true,
            "type": "number"
          },
          "pctUnder5m": {
            "description": "Respondidas con TTFR hábil < 5 min ÷ TOTAL (las no respondidas cuentan en contra). Sin horario = pctUnder5mRaw.",
            "type": "number"
          },
          "pctUnder5mRaw": {
            "description": "Respondidas con TTFR bruto < 5 min ÷ TOTAL.",
            "type": "number"
          },
          "total": {
            "description": "Conversaciones de la población (firstInboundAt ∈ [from, to)).",
            "type": "number"
          }
        },
        "required": [
          "total",
          "answered",
          "p50Sec",
          "p90Sec",
          "pctUnder5m",
          "p50RawSec",
          "p90RawSec",
          "pctUnder5mRaw"
        ],
        "type": "object"
      },
      "SpeedToLeadBusinessHoursInfoDto": {
        "properties": {
          "configured": {
            "description": "true si el tenant tiene horario de atención (BusinessHours) con al menos un día con rangos: los campos p50Sec/p90Sec/pctUnder5m son HÁBILES y pueden diferir de los *Raw*. false ⇒ hábil = bruto en todos los buckets.",
            "type": "boolean"
          },
          "timezone": {
            "description": "Zona horaria del horario del tenant; null sin horario.",
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "configured",
          "timezone"
        ],
        "type": "object"
      },
      "SpeedToLeadByChannelDto": {
        "properties": {
          "instagram": {
            "$ref": "#/components/schemas/SpeedToLeadBucketDto"
          },
          "messenger": {
            "$ref": "#/components/schemas/SpeedToLeadBucketDto"
          },
          "whatsapp": {
            "$ref": "#/components/schemas/SpeedToLeadBucketDto"
          }
        },
        "required": [
          "whatsapp",
          "instagram",
          "messenger"
        ],
        "type": "object"
      },
      "SpeedToLeadDayBucketDto": {
        "properties": {
          "answered": {
            "description": "Respondidas (firstResponseAt IS NOT NULL).",
            "type": "number"
          },
          "day": {
            "description": "Día local (tz del reporte) del firstInboundAt.",
            "example": "2026-09-14",
            "type": "string"
          },
          "p50RawSec": {
            "description": "Mediana del TTFR BRUTO (reloj de pared, firstResponseAt − firstInboundAt, sin descontar fuera de horario), segundos.",
            "nullable": true,
            "type": "number"
          },
          "p50Sec": {
            "description": "Mediana del tiempo a la primera respuesta EN HORARIO HÁBIL (segundos): descuenta los tramos fuera del horario del tenant; un inbound fuera de horario empieza a contar en la siguiente apertura. Sin horario configurado = p50RawSec. Es el KPI que pinta el dashboard.",
            "nullable": true,
            "type": "number"
          },
          "p90RawSec": {
            "description": "p90 del TTFR BRUTO (reloj de pared), segundos.",
            "nullable": true,
            "type": "number"
          },
          "p90Sec": {
            "description": "p90 del TTFR en horario hábil (segundos). Sin horario = p90RawSec.",
            "nullable": true,
            "type": "number"
          },
          "pctUnder5m": {
            "description": "Respondidas con TTFR hábil < 5 min ÷ TOTAL (las no respondidas cuentan en contra). Sin horario = pctUnder5mRaw.",
            "type": "number"
          },
          "pctUnder5mRaw": {
            "description": "Respondidas con TTFR bruto < 5 min ÷ TOTAL.",
            "type": "number"
          },
          "total": {
            "description": "Conversaciones de la población (firstInboundAt ∈ [from, to)).",
            "type": "number"
          }
        },
        "required": [
          "total",
          "answered",
          "p50Sec",
          "p90Sec",
          "pctUnder5m",
          "p50RawSec",
          "p90RawSec",
          "pctUnder5mRaw",
          "day"
        ],
        "type": "object"
      },
      "SpeedToLeadRangeDto": {
        "properties": {
          "channel": {
            "description": "Presente solo si se pidió ?channel=.",
            "enum": [
              "whatsapp",
              "instagram",
              "messenger"
            ],
            "type": "string"
          },
          "from": {
            "example": "2026-09-14",
            "type": "string"
          },
          "to": {
            "description": "EXCLUSIVO.",
            "example": "2026-09-15",
            "type": "string"
          },
          "tz": {
            "example": "America/Mexico_City",
            "type": "string"
          }
        },
        "required": [
          "from",
          "to",
          "tz"
        ],
        "type": "object"
      },
      "SpeedToLeadRawRowDto": {
        "properties": {
          "conversationId": {
            "type": "string"
          },
          "firstInboundAt": {
            "description": "ISO UTC.",
            "type": "string"
          },
          "firstResponderId": {
            "nullable": true,
            "type": "string"
          },
          "firstResponderType": {
            "enum": [
              "BOT",
              "HUMAN"
            ],
            "nullable": true,
            "type": "string"
          },
          "firstResponseAt": {
            "description": "ISO UTC; null si no respondida.",
            "nullable": true,
            "type": "string"
          },
          "ttfrBusinessSec": {
            "description": "TTFR en horario hábil en segundos (= ttfrRawSec sin horario configurado); null si no respondida.",
            "nullable": true,
            "type": "number"
          },
          "ttfrRawSec": {
            "description": "TTFR bruto en segundos (firstResponseAt − firstInboundAt); null si no respondida.",
            "nullable": true,
            "type": "number"
          }
        },
        "required": [
          "conversationId",
          "firstInboundAt",
          "firstResponseAt",
          "firstResponderType",
          "firstResponderId",
          "ttfrRawSec",
          "ttfrBusinessSec"
        ],
        "type": "object"
      },
      "SpeedToLeadReportDto": {
        "properties": {
          "agentRanking": {
            "description": "Ranking JUSTO por agente: a quien contestó se le mide desde que la conversación LE LLEGÓ, y a quien la dejó pasar le cuenta una «vencida». `byAgent` NO cambia: sigue con el reloj del negocio, que es el que cuadra con la cifra principal. Orden: p50Sec asc → answered desc → nombre; quien solo tiene vencidas va al final. Se RECORTA por rol (ver `rankingScope`).",
            "items": {
              "$ref": "#/components/schemas/SpeedToLeadAgentRankRowDto"
            },
            "type": "array"
          },
          "businessHours": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SpeedToLeadBusinessHoursInfoDto"
              }
            ],
            "description": "Cómo se descontó el fuera de horario en este reporte."
          },
          "byAgent": {
            "description": "Solo primeras respuestas HUMAN.",
            "items": {
              "$ref": "#/components/schemas/SpeedToLeadAgentBucketDto"
            },
            "type": "array"
          },
          "byChannel": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SpeedToLeadByChannelDto"
              }
            ],
            "description": "Siempre las tres claves (bucket vacío si el canal no tuvo hilos)."
          },
          "byDay": {
            "items": {
              "$ref": "#/components/schemas/SpeedToLeadDayBucketDto"
            },
            "type": "array"
          },
          "byResponder": {
            "items": {
              "$ref": "#/components/schemas/SpeedToLeadResponderBucketDto"
            },
            "type": "array"
          },
          "goalMin": {
            "description": "Meta de 1.ª respuesta del negocio (`Tenant.firstResponseGoalMin`) usada para `pctUnderGoal`.",
            "type": "number"
          },
          "range": {
            "$ref": "#/components/schemas/SpeedToLeadRangeDto"
          },
          "rankingScope": {
            "description": "N4: hasta dónde alcanzó a ver quien preguntó. `full` = OWNER/ADMIN (y las llaves de API, que pasan por el mismo portón con su rol ADMIN-equivalente). `self` = AGENT/VIEWER: `byAgent`, `agentRanking` y `raw` traen SOLO lo propio, y NO viaja ninguna referencia de equipo. Lo decide el servidor con el rol de la sesión; NO es un parámetro.",
            "enum": [
              "full",
              "self"
            ],
            "type": "string"
          },
          "raw": {
            "description": "Solo con include=raw (cap 10k).",
            "items": {
              "$ref": "#/components/schemas/SpeedToLeadRawRowDto"
            },
            "type": "array"
          },
          "summary": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SpeedToLeadBucketDto"
              }
            ],
            "description": "Global del rango."
          }
        },
        "required": [
          "range",
          "businessHours",
          "summary",
          "byDay",
          "byAgent",
          "byResponder",
          "byChannel"
        ],
        "type": "object"
      },
      "SpeedToLeadResponderBucketDto": {
        "properties": {
          "answered": {
            "description": "Respondidas (firstResponseAt IS NOT NULL).",
            "type": "number"
          },
          "p50RawSec": {
            "description": "Mediana del TTFR BRUTO (reloj de pared, firstResponseAt − firstInboundAt, sin descontar fuera de horario), segundos.",
            "nullable": true,
            "type": "number"
          },
          "p50Sec": {
            "description": "Mediana del tiempo a la primera respuesta EN HORARIO HÁBIL (segundos): descuenta los tramos fuera del horario del tenant; un inbound fuera de horario empieza a contar en la siguiente apertura. Sin horario configurado = p50RawSec. Es el KPI que pinta el dashboard.",
            "nullable": true,
            "type": "number"
          },
          "p90RawSec": {
            "description": "p90 del TTFR BRUTO (reloj de pared), segundos.",
            "nullable": true,
            "type": "number"
          },
          "p90Sec": {
            "description": "p90 del TTFR en horario hábil (segundos). Sin horario = p90RawSec.",
            "nullable": true,
            "type": "number"
          },
          "pctUnder5m": {
            "description": "Respondidas con TTFR hábil < 5 min ÷ TOTAL (las no respondidas cuentan en contra). Sin horario = pctUnder5mRaw.",
            "type": "number"
          },
          "pctUnder5mRaw": {
            "description": "Respondidas con TTFR bruto < 5 min ÷ TOTAL.",
            "type": "number"
          },
          "responderType": {
            "enum": [
              "BOT",
              "HUMAN"
            ],
            "type": "string"
          },
          "total": {
            "description": "Conversaciones de la población (firstInboundAt ∈ [from, to)).",
            "type": "number"
          }
        },
        "required": [
          "total",
          "answered",
          "p50Sec",
          "p90Sec",
          "pctUnder5m",
          "p50RawSec",
          "p90RawSec",
          "pctUnder5mRaw",
          "responderType"
        ],
        "type": "object"
      },
      "TemplateComponentDto": {
        "properties": {
          "type": {
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "type": "object"
      },
      "UpdateAppointmentResourceDto": {
        "properties": {
          "active": {
            "type": "boolean"
          },
          "capacity": {
            "maximum": 1000,
            "minimum": 1,
            "type": "number"
          },
          "name": {
            "maxLength": 120,
            "minLength": 1,
            "type": "string"
          },
          "userId": {
            "description": "User Kontesta vinculado (STAFF) — debe ser del tenant. null desvincula.",
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateAppointmentTypeDto": {
        "properties": {
          "active": {
            "type": "boolean"
          },
          "allowTip": {
            "description": "Citas Ola 3 (CA3.2): ¿el servicio admite propina al cobrar el total? Solo tiene efecto con chargePolicy=TOTAL. Default false.",
            "type": "boolean"
          },
          "bufferAfterMin": {
            "maximum": 1440,
            "minimum": 0,
            "type": "number"
          },
          "bufferBeforeMin": {
            "maximum": 1440,
            "minimum": 0,
            "type": "number"
          },
          "chargePolicy": {
            "description": "Política de cobro: NINGUNO | ANTICIPO (exige depositAmountMinor>0) | TOTAL (exige priceMinor>0).",
            "enum": [
              "NINGUNO",
              "ANTICIPO",
              "TOTAL"
            ],
            "type": "string"
          },
          "color": {
            "description": "Color hex (#RRGGBB).",
            "type": "string"
          },
          "currency": {
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "defaultModality": {
            "enum": [
              "IN_PERSON",
              "ONLINE"
            ],
            "type": "string"
          },
          "depositAmountMinor": {
            "description": "Monto del anticipo (centavos). Requerido si chargePolicy queda en ANTICIPO.",
            "minimum": 1,
            "type": "number"
          },
          "depositCurrency": {
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "durationMin": {
            "maximum": 1440,
            "minimum": 1,
            "type": "number"
          },
          "locationText": {
            "maxLength": 500,
            "type": "string"
          },
          "maxHorizonDays": {
            "maximum": 365,
            "minimum": 1,
            "type": "number"
          },
          "minNoticeMin": {
            "minimum": 0,
            "type": "number"
          },
          "name": {
            "maxLength": 120,
            "minLength": 1,
            "type": "string"
          },
          "priceMinor": {
            "minimum": 0,
            "type": "number"
          },
          "publicBookable": {
            "description": "Reserva pública: visible/reservable en el mini-site público. Default true.",
            "type": "boolean"
          },
          "requiresConfirmation": {
            "type": "boolean"
          },
          "slotStepMin": {
            "description": "Paso de la rejilla de horarios (min, 5..1440). null = igual a la duración.",
            "maximum": 1440,
            "minimum": 5,
            "nullable": true,
            "type": "number"
          }
        },
        "type": "object"
      },
      "UpdateAppointmentTypeRemindersDto": {
        "properties": {
          "first": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ReminderSlotDto"
              }
            ],
            "description": "Primer recordatorio (por defecto 24 h antes)."
          },
          "last": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ReminderSlotDto"
              }
            ],
            "description": "Último recordatorio (por defecto 1 h antes)."
          },
          "notifyOnBook": {
            "description": "Avisar por WhatsApp al agendar (o al confirmarla el negocio).",
            "type": "boolean"
          },
          "notifyOnCancel": {
            "description": "Avisar por WhatsApp al cancelar.",
            "type": "boolean"
          },
          "notifyOnReschedule": {
            "description": "Avisar por WhatsApp al reprogramar.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "UpdateAppointmentTypeWaitlistDto": {
        "properties": {
          "batchSize": {
            "description": "Personas que reciben la oferta por tanda (el primero que contesta se la queda).",
            "maximum": 20,
            "minimum": 1,
            "type": "number"
          },
          "enabled": {
            "description": "Ofrecer a la lista de espera los huecos que se liberen de este tipo.",
            "type": "boolean"
          },
          "maxOffersPerEntry": {
            "description": "Tope de ofertas por persona (anti-spam); al llegar, su entrada expira.",
            "maximum": 20,
            "minimum": 1,
            "type": "number"
          },
          "minLeadMinutes": {
            "description": "No se ofrece un hueco que empiece antes de ahora + estos minutos (un no-show de hace 5 min no dispara).",
            "maximum": 20160,
            "minimum": 0,
            "type": "number"
          },
          "offerWindowMinutes": {
            "description": "Minutos que vive la oferta antes de pasar a la siguiente tanda.",
            "maximum": 1440,
            "minimum": 2,
            "type": "number"
          },
          "ordering": {
            "description": "FIFO = por llegada · PRIORITY = por prioridad (desempate por llegada).",
            "enum": [
              "FIFO",
              "PRIORITY"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateAvailabilityExceptionDto": {
        "properties": {
          "blocked": {
            "description": "true=cierra el día; false=override.",
            "type": "boolean"
          },
          "date": {
            "description": "Fecha YYYY-MM-DD.",
            "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
            "type": "string"
          },
          "endMinute": {
            "description": "Fin del override (1..1440). null lo limpia.",
            "maximum": 1440,
            "minimum": 1,
            "nullable": true,
            "type": "number"
          },
          "startMinute": {
            "description": "Inicio del override (0..1439). null lo limpia.",
            "maximum": 1439,
            "minimum": 0,
            "nullable": true,
            "type": "number"
          }
        },
        "type": "object"
      },
      "UpdateAvailabilityRuleDto": {
        "properties": {
          "endMinute": {
            "description": "Minutos desde 00:00 local (1..1440). Debe ser > startMinute.",
            "maximum": 1440,
            "minimum": 1,
            "type": "number"
          },
          "resourceId": {
            "description": "Recurso específico — debe ser del tenant. null = cualquiera.",
            "nullable": true,
            "type": "string"
          },
          "startMinute": {
            "description": "Minutos desde 00:00 local (0..1439).",
            "maximum": 1439,
            "minimum": 0,
            "type": "number"
          },
          "tz": {
            "deprecated": true,
            "description": "Ignorado: los minutos son hora del negocio y la zona la resuelve el servidor. Se acepta por compatibilidad.",
            "type": "string"
          },
          "weekday": {
            "description": "0=domingo … 6=sábado.",
            "maximum": 6,
            "minimum": 0,
            "type": "number"
          }
        },
        "type": "object"
      },
      "UpdateCloseReasonDto": {
        "properties": {
          "isActive": {
            "description": "false = archivar (nunca se borra).",
            "type": "boolean"
          },
          "label": {
            "maxLength": 60,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "maximum": 1000,
            "minimum": 0,
            "type": "number"
          }
        },
        "type": "object"
      },
      "UpdateCommissionRuleDto": {
        "properties": {
          "active": {
            "description": "Activa/inactiva.",
            "type": "boolean"
          },
          "amountMinor": {
            "description": "FIXED: monto en centavos (0..100_000_000 = $1M).",
            "maximum": 100000000,
            "minimum": 0,
            "type": "number"
          },
          "basis": {
            "enum": [
              "PERCENT",
              "FIXED"
            ],
            "type": "string"
          },
          "currency": {
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "rateBps": {
            "description": "PERCENT: puntos base (0..10000).",
            "maximum": 10000,
            "minimum": 0,
            "type": "number"
          }
        },
        "type": "object"
      },
      "UpdateContactDto": {
        "properties": {
          "archived": {
            "description": "Lo saca del directorio sin perder su historial.",
            "type": "boolean"
          },
          "company": {
            "maxLength": 160,
            "type": "string"
          },
          "displayName": {
            "description": "Nombre que escribe el negocio (mismo nivel que el que captura un bot). Gana sobre la agenda del teléfono y el perfil del canal en TODAS las conversaciones del contacto; vacío regresa al siguiente. Se guarda saneado (NFC, sin invisibles bidi/ancho cero, espacios colapsados, máximo 120 caracteres). Un carácter de control (NUL, salto de línea, tab…) ⇒ 400. Un AGENT restringido por equipo solo renombra a quien ve en al menos una conversación (si no, 404).",
            "maxLength": 160,
            "pattern": "/^[^\\u0000-\\u001F\\u007F-\\u009F]*$/",
            "type": "string"
          },
          "email": {
            "maxLength": 200,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateContactFieldDto": {
        "properties": {
          "archived": {
            "description": "Lo oculta del formulario SIN borrar los valores capturados.",
            "type": "boolean"
          },
          "label": {
            "maxLength": 60,
            "type": "string"
          },
          "options": {
            "items": {
              "maxLength": 60,
              "type": "string"
            },
            "type": "array"
          },
          "position": {
            "description": "Orden en el formulario.",
            "minimum": 0,
            "type": "number"
          }
        },
        "type": "object"
      },
      "UpdateContactSegmentDto": {
        "properties": {
          "filters": {
            "$ref": "#/components/schemas/SegmentFiltersDto"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateConversationDto": {
        "properties": {
          "assignedToId": {
            "description": "User.id del tenant con isActive=true; null = desasignar.",
            "maxLength": 64,
            "nullable": true,
            "type": "string"
          },
          "closeNote": {
            "description": "R10-10: comentario libre del cierre (hasta 280 caracteres). Mismas reglas que `closeReasonId`; vacío lo borra.",
            "maxLength": 280,
            "type": "string"
          },
          "closeReasonId": {
            "description": "R10-10: motivo del catálogo (`GET /v1/conversation-close-reasons`). Va en el mismo PATCH que cierra (`status: \"CLOSED\"`) o DESPUÉS, solo (sin `status`), mientras la conversación siga cerrada y dentro de 15 minutos desde `closedAt`: 409 `CONVERSATION_NOT_CLOSED` si está abierta o se reabrió, 409 `CLOSE_REASON_WINDOW_EXPIRED` si ya pasó la ventana. Nunca con `status: \"OPEN\"` (400 `CLOSE_REASON_ONLY_WHEN_CLOSING`). 400 `CLOSE_REASON_NOT_FOUND` si no existe, es de otro negocio o está archivado. Opcional: cerrar sin motivo siempre es válido. Repetir el mismo valor no cambia nada.",
            "maxLength": 64,
            "type": "string"
          },
          "status": {
            "enum": [
              "OPEN",
              "CLOSED"
            ],
            "type": "string"
          },
          "teamId": {
            "description": "Mover a un equipo; null = sacarla de su equipo (vuelve al reparto global).",
            "maxLength": 64,
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateEmisorCsdDto": {
        "properties": {
          "cerBase64": {
            "maxLength": 20000,
            "minLength": 1,
            "type": "string"
          },
          "keyBase64": {
            "maxLength": 20000,
            "minLength": 1,
            "type": "string"
          },
          "password": {
            "maxLength": 128,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "cerBase64",
          "keyBase64",
          "password"
        ],
        "type": "object"
      },
      "UpdateEmisorFiscalDto": {
        "properties": {
          "codigoPostal": {
            "maxLength": 5,
            "type": "string"
          },
          "domicilio": {
            "$ref": "#/components/schemas/EmisorDomicilioDto"
          },
          "email": {
            "maxLength": 254,
            "type": "string"
          },
          "razonSocial": {
            "maxLength": 254,
            "type": "string"
          },
          "regimenFiscal": {
            "maxLength": 4,
            "type": "string"
          },
          "rfc": {
            "maxLength": 13,
            "type": "string"
          }
        },
        "required": [
          "razonSocial",
          "rfc",
          "regimenFiscal",
          "codigoPostal"
        ],
        "type": "object"
      },
      "UpdateEmisorLogoDto": {
        "properties": {
          "imageBase64": {
            "maxLength": 950000,
            "minLength": 1,
            "type": "string"
          },
          "mimeType": {
            "enum": [
              "image/jpeg",
              "image/png"
            ],
            "type": "string"
          }
        },
        "required": [
          "imageBase64",
          "mimeType"
        ],
        "type": "object"
      },
      "UpdateEntryPointDto": {
        "properties": {
          "bookingSlug": {
            "maxLength": 40,
            "pattern": "^[a-z0-9-]{3,40}$",
            "type": "string"
          },
          "isActive": {
            "type": "boolean"
          },
          "label": {
            "maxLength": 80,
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateIntegrationDto": {
        "properties": {
          "config": {
            "$ref": "#/components/schemas/OdooConfigDto"
          },
          "credentials": {
            "$ref": "#/components/schemas/OdooCredentialsDto"
          },
          "isActive": {
            "type": "boolean"
          },
          "label": {
            "maxLength": 120,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateInvoiceConnectionDto": {
        "properties": {
          "config": {
            "$ref": "#/components/schemas/InvoiceConnectionConfigDto"
          },
          "credentials": {
            "type": "object"
          },
          "isActive": {
            "type": "boolean"
          },
          "label": {
            "maxLength": 120,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateMacroDto": {
        "properties": {
          "active": {
            "type": "boolean"
          },
          "body": {
            "maxLength": 4000,
            "minLength": 1,
            "type": "string"
          },
          "category": {
            "maxLength": 40,
            "type": "string"
          },
          "shortcut": {
            "maxLength": 60,
            "minLength": 1,
            "type": "string"
          },
          "title": {
            "maxLength": 80,
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdatePackageTypeDto": {
        "properties": {
          "active": {
            "type": "boolean"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "type": "string"
          },
          "priceMinor": {
            "minimum": 1,
            "type": "number"
          },
          "validityDays": {
            "maximum": 3650,
            "minimum": 1,
            "type": "number"
          }
        },
        "type": "object"
      },
      "UpdatePaymentAccountDto": {
        "properties": {
          "apiKey": {
            "description": "Rota el access token (MP) / private key (OpenPay) / restricted key (Stripe). Se guarda cifrado.",
            "maxLength": 500,
            "type": "string"
          },
          "config": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PaymentAccountConfigDto"
              }
            ],
            "description": "Config NO-secreta por-pasarela. OpenPay: { sandbox }."
          },
          "displayName": {
            "maxLength": 120,
            "type": "string"
          },
          "isActive": {
            "type": "boolean"
          },
          "isDefault": {
            "type": "boolean"
          },
          "merchantId": {
            "description": "Rota el OpenPay merchant id. Se guarda cifrado.",
            "maxLength": 200,
            "type": "string"
          },
          "webhookBasicPass": {
            "description": "Rota el password Basic-auth del webhook OpenPay. Se guarda cifrado.",
            "maxLength": 200,
            "type": "string"
          },
          "webhookBasicUser": {
            "description": "Rota el usuario Basic-auth del webhook OpenPay. Se guarda cifrado.",
            "maxLength": 200,
            "type": "string"
          },
          "webhookSecret": {
            "description": "Rota el secreto de verificación de webhook por-cuenta. Se guarda cifrado.",
            "maxLength": 500,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdatePaymentSettingsDto": {
        "properties": {
          "notifyOnPaid": {
            "description": "Avisar al cliente por WhatsApp cuando se recibe el pago (recibo en el hilo). Default true.",
            "type": "boolean"
          },
          "primaryPaymentProvider": {
            "description": "Pasarela PRINCIPAL cross-provider (PAY-6). Solo se acepta una pasarela CONNECTED del tenant (400 PROVIDER_NOT_CONNECTED si no lo está). `null` limpia la principal. Ausente = no se toca.",
            "enum": [
              "MERCADOPAGO",
              "OPENPAY",
              "STRIPE"
            ],
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateProductDto": {
        "properties": {
          "category": {
            "maxLength": 80,
            "type": "string"
          },
          "currency": {
            "enum": [
              "MXN",
              "USD"
            ],
            "type": "string"
          },
          "description": {
            "maxLength": 2000,
            "type": "string"
          },
          "name": {
            "maxLength": 140,
            "minLength": 1,
            "type": "string"
          },
          "priceMinor": {
            "maximum": 100000000,
            "minimum": 0,
            "type": "number"
          },
          "status": {
            "enum": [
              "ACTIVE",
              "HIDDEN",
              "ARCHIVED"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateProgramDto": {
        "properties": {
          "autoEnroll": {
            "type": "boolean"
          },
          "brandColor": {
            "pattern": "/^#[0-9a-fA-F]{6}$/",
            "type": "string"
          },
          "brandLogoUrl": {
            "maxLength": 500,
            "type": "string"
          },
          "cardDesign": {
            "type": "object"
          },
          "earnOnAppointmentCompleted": {
            "type": "boolean"
          },
          "earnOnPayment": {
            "type": "boolean"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "type": "string"
          },
          "pointsExpiryDays": {
            "minimum": 1,
            "type": "number"
          },
          "redeemOptions": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "rules": {
            "type": "object"
          },
          "status": {
            "enum": [
              "ACTIVE",
              "PAUSED",
              "ARCHIVED"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateSavedViewDto": {
        "properties": {
          "filters": {
            "$ref": "#/components/schemas/SavedViewFiltersDto"
          },
          "name": {
            "maxLength": 40,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "description": "Orden en la barra.",
            "minimum": 0,
            "type": "number"
          }
        },
        "type": "object"
      },
      "UpdateTeamDto": {
        "properties": {
          "color": {
            "nullable": true,
            "pattern": "/^#[0-9a-fA-F]{6}$/",
            "type": "string"
          },
          "memberIds": {
            "description": "Reemplaza la lista completa de miembros.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "name": {
            "maxLength": 40,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "minimum": 0,
            "type": "number"
          }
        },
        "type": "object"
      },
      "UpdateTeamVisibilityDto": {
        "properties": {
          "teamVisibility": {
            "description": "`ALL`: todo el personal ve todas las conversaciones (comportamiento de siempre). `TEAM_ONLY`: cada agente ve las de sus equipos, las asignadas a él y, si `unassignedVisibleToAll`, las que no están en ningún equipo. OWNER/ADMIN y API keys ven todo siempre.",
            "enum": [
              "ALL",
              "TEAM_ONLY"
            ],
            "type": "string"
          },
          "unassignedVisibleToAll": {
            "description": "Con `TEAM_ONLY`: las conversaciones sin equipo las ve todo el personal (true, omisión) o sólo OWNER/ADMIN y quien las tenga asignadas (false). Se ignora con `ALL`.",
            "type": "boolean"
          }
        },
        "required": [
          "teamVisibility"
        ],
        "type": "object"
      },
      "UpdateWaitlistEntryDto": {
        "properties": {
          "earliestAt": {
            "description": "Nuevo inicio de la ventana aceptable (ISO instant).",
            "type": "string"
          },
          "latestAt": {
            "description": "Nuevo fin de la ventana aceptable (ISO instant).",
            "type": "string"
          },
          "priority": {
            "description": "Nueva prioridad (mayor primero).",
            "maximum": 1000,
            "minimum": 0,
            "type": "number"
          }
        },
        "type": "object"
      },
      "UpdateWebhookDto": {
        "properties": {
          "description": {
            "maxLength": 200,
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "events": {
            "enum": [
              "message.received",
              "message.status_changed",
              "conversation.assigned",
              "conversation.closed",
              "lead.created",
              "lead.stage_changed",
              "review.obtained",
              "appointment.created",
              "appointment.updated",
              "payment.updated",
              "handoff.requested",
              "call.ended",
              "call.recording_ready",
              "hosted_link.completed",
              "hosted_link.failed"
            ],
            "type": "string"
          },
          "url": {
            "maxLength": 2048,
            "pattern": "/^https:\\/\\//i",
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpsertFiscalDto": {
        "properties": {
          "cpFiscal": {
            "description": "Código postal del domicilio fiscal (5 dígitos).",
            "example": "64000",
            "pattern": "/^[0-9]{5}$/",
            "type": "string"
          },
          "email": {
            "description": "Email para enviar el CFDI (opcional).",
            "maxLength": 254,
            "type": "string"
          },
          "razonSocial": {
            "description": "Razón social EXACTA de la constancia SAT (sin régimen societario).",
            "maxLength": 254,
            "minLength": 1,
            "type": "string"
          },
          "regimenFiscal": {
            "description": "Régimen fiscal (c_RegimenFiscal), p.ej. 601/612/626.",
            "maxLength": 4,
            "minLength": 3,
            "type": "string"
          },
          "rfc": {
            "description": "RFC del receptor (12 moral / 13 física). Se normaliza a mayúsculas.",
            "maxLength": 13,
            "minLength": 12,
            "type": "string"
          },
          "usoCfdiDefault": {
            "description": "Uso CFDI por defecto (c_UsoCFDI), p.ej. G03. Opcional.",
            "maxLength": 4,
            "minLength": 3,
            "type": "string"
          }
        },
        "required": [
          "rfc",
          "razonSocial",
          "cpFiscal",
          "regimenFiscal"
        ],
        "type": "object"
      },
      "UsePackageDto": {
        "properties": {
          "packageId": {
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "packageId"
        ],
        "type": "object"
      },
      "WalkInContactDto": {
        "properties": {
          "name": {
            "description": "Nombre del contacto (no pisa el de un hilo existente).",
            "maxLength": 120,
            "type": "string"
          },
          "phone": {
            "description": "Teléfono del contacto (se canoniza server-side a wa_id MX 52+10).",
            "maxLength": 30,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "phone"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "api-key": {
        "description": "API key pública con formato ktk_<prefix>.<secret>",
        "in": "header",
        "name": "X-Api-Key",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "description": "API pública /v1 de Kontesta: la superficie que se alcanza con una API key.\n\nEste documento trae solo las 392 operaciones que una llave puede llamar. Guía completa, ejemplos en PHP, C#, TypeScript y Python, y catálogo de eventos: https://kontesta.mx/integracion\n\n## Autenticación\nCabecera `X-Api-Key: ktk_<prefijo>.<secreto>` (también se acepta como `Authorization: Bearer ktk_...`). La llave se crea en la app, en Ajustes → Llaves de API.\n\n## Permisos\nCada operación exige los scopes de `x-required-scopes`. Todo lo que no se concede se niega: sin el scope la respuesta es `403`.\n\n## Límites\nVentana de un minuto por cuenta, con cubetas separadas para lectura y escritura. Cada respuesta trae `X-RateLimit-Limit/Remaining/Reset`; al pasarte, `429` + `Retry-After`.\n\n## Errores\nCuerpo uniforme `{ statusCode, error, message, requestId }`.\n\n## Versionado\n`/v1` solo cambia de forma aditiva; un cambio incompatible abre `/v2`.",
    "title": "Kontesta API",
    "version": "1.0"
  },
  "openapi": "3.0.0",
  "paths": {
    "/v1/ai/bot-config/{channelAccountId}": {
      "get": {
        "description": "Devuelve la config del número o los defaults (HUMAN_ONLY/KONTESTA + política de traspaso por defecto) si aún no se configuró. `handoffPolicy`: keywords que piden humano, turnos máximos del bot, sentimiento negativo, minutos hasta \"nadie tomó\", textos de aviso al cliente y equipo destino. Número ajeno/inexistente ⇒ 404 (RLS).",
        "operationId": "BotConfigController_get",
        "parameters": [
          {
            "in": "path",
            "name": "channelAccountId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Config de orquestación del número: motor del bot (engineMode), dueño de hilo por defecto y política de traspaso",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      },
      "put": {
        "description": "Upsert por número (clave channelAccountId). PATCH-like: solo toca las claves presentes; `handoffPolicy` también es parcial (se mezcla con la vigente y se re-valida completa). enum inválido ⇒ 400; `routeToTeamId` de un equipo inexistente ⇒ 400 TEAM_NOT_FOUND. KONTESTA_BOT/HYBRID es lo que habilita el auto-responder del bot A1 (gate 2 de AI-7).",
        "operationId": "BotConfigController_update",
        "parameters": [
          {
            "in": "path",
            "name": "channelAccountId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Fijar el motor del número (HUMAN_ONLY|KONTESTA_BOT|META_AGENT|HYBRID), defaultThreadOwner y handoffPolicy",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/channels": {
      "get": {
        "description": "Por número: `botMode`, `allowedModes` (fuente única de plan + canal + agente de Meta + bandera «Los dos»), `blockedModes` con el motivo de cada modo no ofrecido, los bots por decisión prendidos y si el asistente está listo (versión probada y conocimiento). Sin PII de contactos.",
        "operationId": "AiChannelsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Números y canales con su modo de bot (sin bot · bot por decisión · asistente · los dos)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/channels/{channelAccountId}/mode": {
      "put": {
        "description": "Body `{ mode: OFF|DECISION|ASSISTANT|BOTH, skillVersionId? }`. Errores: 422 `MODE_NOT_ALLOWED` (plan o canal), 422 `MODE_CONFLICT_META_AGENT`, 422 `MODE_BOTH_DISABLED` (bandera `bots.mode.both`), 409 `NO_DECISION_BOT`, 409 `ASSISTANT_NOT_READY` (`details.reason`: no_version | skill_untested | no_knowledge), 404 número ajeno. Publicar el asistente ya NO apaga los bots por decisión: esta es la elección explícita. AuditLog `ai.channel.mode_changed {channelAccountId, from, to}` y aviso en tiempo real del cambio de modo (sin PII).",
        "operationId": "AiChannelsController_setMode",
        "parameters": [
          {
            "in": "path",
            "name": "channelAccountId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`USER_SESSION_REQUIRED` (llave de API) · `IMPERSONATION_FORBIDDEN` (suplantación) · rol."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Elegir quién contesta en un número o canal",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/credentials": {
      "get": {
        "operationId": "AiController_listCredentials",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar credenciales BYOK (enmascaradas: keyHint + status; SIN la key)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      },
      "post": {
        "description": "La key EN CLARO entra por HTTPS y NUNCA se re-emite. Dedupe por fingerprint → 409. Solo la SESIÓN de una persona OWNER/ADMIN: una llave de API (aunque tenga `ai:write`) recibe 403 `USER_SESSION_REQUIRED` (cambiar la llave de IA cambia a qué proveedor y a cuenta de quién van las conversaciones) y soporte suplantando recibe 403 `IMPERSONATION_FORBIDDEN`.",
        "operationId": "AiController_createCredential",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`USER_SESSION_REQUIRED` (llave de API) · `IMPERSONATION_FORBIDDEN` (suplantación) · rol."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Alta de credencial BYOK: cifra la credencial, prueba conexión y guarda el estado real",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/credentials/{id}": {
      "delete": {
        "description": "Mismo candado que el alta: solo la sesión de una persona OWNER/ADMIN (llave de API ⇒ 403 `USER_SESSION_REQUIRED`; suplantación ⇒ `IMPERSONATION_FORBIDDEN`).",
        "operationId": "AiController_removeCredential",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`USER_SESSION_REQUIRED` (llave de API) · `IMPERSONATION_FORBIDDEN` (suplantación) · rol."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Baja de credencial (limpia los roles de ruteo que la usaban)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/credentials/{id}/test": {
      "post": {
        "operationId": "AiController_testCredential",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Probar la conexión de una credencial (ping barato; no gasta tokens)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/crm/conversations/{id}/reading": {
      "get": {
        "operationId": "AiCrmController_reading",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Lectura de la IA del hilo: calificación de su oportunidad + última sugerencia + estado (no gasta la llave)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/crm/conversations/{id}/suggestions": {
      "get": {
        "operationId": "AiCrmController_getSuggestion",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Última sugerencia de intención, etiqueta y motivo de cierre del hilo (404 si no hay)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      },
      "post": {
        "description": "Usa la llave BYOK del negocio y su presupuesto. Etiquetas y motivo salen SOLO del catálogo del negocio. El agente decide si aplica cada una con los endpoints de siempre.",
        "operationId": "AiCrmController_generateSuggestion",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Sugerir intención, etiqueta y motivo de cierre (sugiere; NO aplica nada)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/crm/csat": {
      "get": {
        "description": "Es un CSAT INFERIDO de la conversación (`inferred: true`), no una encuesta contestada por el cliente.",
        "operationId": "AiCrmController_csat",
        "parameters": [
          {
            "description": "Primer día (YYYY-MM-DD, UTC). Por defecto, hace 30 días.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "pattern": "DAY",
              "type": "string"
            }
          },
          {
            "description": "Último día incluido (YYYY-MM-DD, UTC). Por defecto, hoy.",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "pattern": "DAY",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "CSAT inferido por IA, agregado por agente y por día",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/crm/lead-scores": {
      "get": {
        "operationId": "AiCrmController_listLeadScores",
        "parameters": [
          {
            "description": "Ids de oportunidad separados por coma (hasta 100).",
            "in": "query",
            "name": "leadIds",
            "required": true,
            "schema": {
              "maxLength": 4000,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Calificación IA (caliente/tibia/fría + razón) de varias oportunidades; omite las que no ves o no están calificadas",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/crm/lead-scores/{leadId}": {
      "get": {
        "operationId": "AiCrmController_getLeadScore",
        "parameters": [
          {
            "in": "path",
            "name": "leadId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Calificación IA de una oportunidad (404 si aún no se califica)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/crm/lead-scores/{leadId}/rescore": {
      "post": {
        "description": "Encola la calificación. Sin llave BYOK responde el motivo (AI_INTELLIGENCE_NOT_CONFIGURED / COPILOT_DISABLED). Solo mueve de etapa si el embudo lo activó (`aiStageMoveEnabled`) y la oportunidad no cambió mientras la IA pensaba.",
        "operationId": "AiCrmController_rescoreLead",
        "parameters": [
          {
            "in": "path",
            "name": "leadId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Volver a calificar una oportunidad ahora",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/crm/quality-audits": {
      "get": {
        "operationId": "AiCrmController_qualityAudits",
        "parameters": [
          {
            "description": "Primer día (YYYY-MM-DD, UTC). Por defecto, hace 30 días.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "pattern": "DAY",
              "type": "string"
            }
          },
          {
            "description": "Último día incluido (YYYY-MM-DD, UTC). Por defecto, hoy.",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "pattern": "DAY",
              "type": "string"
            }
          },
          {
            "description": "Solo las de este agente (User.id).",
            "in": "query",
            "name": "agentId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Solo las que sacaron esta calificación o menos (1-5).",
            "in": "query",
            "name": "maxScore",
            "required": false,
            "schema": {
              "maximum": 5,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Hallazgos de la auditoría de calidad nocturna",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/crm/quality-audits/summary": {
      "get": {
        "operationId": "AiCrmController_qualitySummary",
        "parameters": [
          {
            "description": "Primer día (YYYY-MM-DD, UTC). Por defecto, hace 30 días.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "pattern": "DAY",
              "type": "string"
            }
          },
          {
            "description": "Último día incluido (YYYY-MM-DD, UTC). Por defecto, hoy.",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "pattern": "DAY",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Resumen de la auditoría de calidad: promedio, por agente y hallazgos más frecuentes",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/crm/status": {
      "get": {
        "operationId": "AiCrmController_status",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Estado de las funciones de IA del CRM: encendidas o apagadas y por qué",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/disclosure": {
      "get": {
        "description": "Por función: encendida o no, fuente (BYOK del negocio / IA incluida de Kontesta / apagada), proveedor y modelo. `transcribeEnabled` es la bandera del negocio (`ai.transcribe.enabled`, se cambia en PUT /v1/ai/settings). Sin llaves ni efectos. Cualquier rol con sesión (la app lo muestra antes de usar IA).",
        "operationId": "AiController_getDisclosure",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Proveedor(es) de IA que usa HOY el negocio para copiloto, transcripción y robot",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/messages/{id}/transcribe": {
      "post": {
        "description": "Valida bajo RLS que el mensaje sea type=audio y que exista credencial STT ruteada; encola AI_TRANSCRIBE.",
        "operationId": "AiController_transcribeMessage",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Re-transcribir la nota de voz de un mensaje (on-demand; re-encola idempotente por jobId)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/rewrite": {
      "post": {
        "description": "Cero RAG y cero historial: al modelo va SOLO el borrador (texto del agente) como dato delimitado. Ruta BYOK > IA incluida > apagado; con IA incluida cuenta como 1 ayuda (`assists`). Fail-closed antes de gastar: borrador > 2000 chars ⇒ 400 AI_REWRITE_TOO_LONG; sin ruta ⇒ 400 AI_CHAT_NOT_CONFIGURED / 403 COPILOT_DISABLED / 403 AI_PLATFORM_CAP (con `details.reason` estable). `source` = quién pagó. El agente SIEMPRE revisa y envía (sin autopilot). `Cache-Control: no-store`.",
        "operationId": "AiController_rewrite",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Mejorar tono: reescribe el BORRADOR del agente (formal | calido | corto | corregir | claro) — síncrono",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/settings": {
      "get": {
        "operationId": "AiController_getSettings",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ajustes de IA + ruteo por rol (chat/embed/stt) + flags + presupuesto",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      },
      "put": {
        "description": "`disclosureText` = aviso de robot del negocio (uno por tenant; se manda una vez por conversación en WhatsApp, Instagram y Messenger): hasta 300 caracteres y sin enlaces ⇒ 400 si se excede. Vacío/null ⇒ texto por defecto. `{negocio}` se sustituye por el nombre del negocio al enviar.",
        "operationId": "AiController_updateSettings",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Actualizar ajustes de IA (valida capacidad de ruteo → 400 AI_ROUTING_INVALID)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/skill": {
      "get": {
        "operationId": "AiSkillController_state",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Estado del generador: llave, presupuesto, base de conocimiento, borrador, versiones y números",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/skill/draft": {
      "put": {
        "operationId": "AiSkillController_draft",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Guardar el borrador del asistente de pasos (retomable desde otro equipo)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/skill/draft/compose": {
      "post": {
        "operationId": "AiSkillController_compose",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Armar las secciones con la plantilla determinista del giro (sin IA, sin costo)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/skill/polish": {
      "post": {
        "description": "Si el resultado trae cifras que no estaban en el texto original se descarta (409 `polish_added_data`).",
        "operationId": "AiSkillController_polish",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Pulir la redacción de «Tu negocio» y «Cómo hablas» con la llave del negocio (sin datos nuevos)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/skill/test": {
      "post": {
        "description": "Usa la misma ruta del bot real (reglas de la casa + secciones). Sin llave BYOK activa o con el presupuesto agotado responde 4xx con `details.reason` y no llama a ningún proveedor. Devuelve el costo real por caso.",
        "operationId": "AiSkillController_test",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Probar las instrucciones contra conversaciones de ejemplo con la llave BYOK del negocio",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/skill/unpublish/{channelAccountId}": {
      "post": {
        "operationId": "AiSkillController_unpublish",
        "parameters": [
          {
            "in": "path",
            "name": "channelAccountId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Apagar el asistente en un número (vuelve a «contesta tu equipo»)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/skill/versions": {
      "post": {
        "operationId": "AiSkillController_saveVersion",
        "parameters": [],
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Guardar una versión nueva (v{n+1}); no cambia lo que contesta hoy ningún número",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/skill/versions/{id}": {
      "get": {
        "operationId": "AiSkillController_version",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Versión con sus respuestas y secciones",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/ai/skill/versions/{id}/publish": {
      "post": {
        "operationId": "AiSkillController_publish",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Poner una versión probada en uno o más números (engineMode KONTESTA_BOT)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/skill/versions/{id}/restore": {
      "post": {
        "description": "Las versiones son inmutables: no se reescribe la vieja. No cambia lo que contesta hoy ningún número (eso es publicar). Queda en AuditLog `ai.skill.version.restored` con la versión de origen. 409 `CONFLICT` si dos guardados chocan tras reintentar.",
        "operationId": "AiSkillController_restore",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Volver a una versión anterior: guarda una versión NUEVA (v{n+1}) con el contenido de `:id`",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/suggest": {
      "get": {
        "description": "RAG best-effort (AI-3) + chat BYOK con prompt anti-alucinación en español. Devuelve sugerencias + citas del KB INLINE + `grounded` (false ⇒ sin base en tu KB, verifica). NO envía nada ni escala a humano: el agente revisa/edita. Gasta la key BYOK del tenant (VIEWER excluido). `Cache-Control: no-store`.",
        "operationId": "AiController_suggest",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Copiloto: propone respuestas para el agente a partir del hilo + KB del tenant (síncrono)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/ai/usage": {
      "get": {
        "description": "`rows` (por feature y `source` BYOK|PLATFORM), `bySource` (agregado), `assists` (\"usaste X de Y ayudas este mes\", del periodo actual) y `platform` (estado de la IA incluida para el tenant). Ningún evento de plataforma se factura: el medidor es informativo. `period` = YYYY-MM.",
        "operationId": "AiController_getUsage",
        "parameters": [
          {
            "in": "query",
            "name": "period",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Dashboard de costo: rollup por feature × fuente de la llave + agregado BYOK/PLATFORM + medidor de ayudas",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/appointment-resources": {
      "get": {
        "operationId": "AppointmentResourcesController_list",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar recursos (staff/salas)",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      },
      "post": {
        "description": "userId (STAFF) se valida contra un User del tenant.",
        "operationId": "AppointmentResourcesController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAppointmentResourceDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear un recurso",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointment-resources/{id}": {
      "delete": {
        "description": "Hard delete; las citas históricas se desvinculan (FK onDelete:SetNull).",
        "operationId": "AppointmentResourcesController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar un recurso",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      },
      "get": {
        "operationId": "AppointmentResourcesController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ficha de un recurso",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      },
      "patch": {
        "operationId": "AppointmentResourcesController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAppointmentResourceDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar un recurso",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointment-types": {
      "get": {
        "operationId": "AppointmentTypesController_list",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar tipos de cita (config, incluye inactivos)",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      },
      "post": {
        "operationId": "AppointmentTypesController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAppointmentTypeDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear un tipo de cita",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointment-types/{id}": {
      "delete": {
        "description": "SOFT (active=false) si tiene citas ligadas; HARD (cascada reglas/excepciones) si no.",
        "operationId": "AppointmentTypesController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ id, softDeleted }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar un tipo de cita",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      },
      "get": {
        "operationId": "AppointmentTypesController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ficha de un tipo de cita (config)",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      },
      "patch": {
        "operationId": "AppointmentTypesController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAppointmentTypeDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar un tipo de cita",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointment-types/{id}/reminders": {
      "get": {
        "description": "Qué recordatorios manda este tipo (primero/último) y con cuánta anticipación, y qué avisos inmediatos (agendar/reprogramar/cancelar).",
        "operationId": "AppointmentTypesController_getReminders",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Política de recordatorios de un tipo de cita",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      },
      "put": {
        "description": "Parcial. Anticipación entre 5 min y 14 días; el primero nunca más cerca que el último. Aplica a citas nuevas o reprogramadas.",
        "operationId": "AppointmentTypesController_updateReminders",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAppointmentTypeRemindersDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "AppointmentTypeRemindersDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar la política de recordatorios de un tipo de cita",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointment-types/{id}/waitlist": {
      "get": {
        "description": "Interruptor, tamaño de tanda, ventana de la oferta, orden (llegada|prioridad), anticipación mínima y tope de ofertas por persona.",
        "operationId": "AppointmentTypesController_getWaitlist",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Lista de espera de un tipo de cita (config)",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      },
      "put": {
        "description": "Parcial. Tanda 1–20, ventana 2 min–24 h, anticipación 0–14 días, tope 1–20. Aplica a las ofertas que se abran a partir de ahora.",
        "operationId": "AppointmentTypesController_updateWaitlist",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAppointmentTypeWaitlistDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "AppointmentTypeWaitlistDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar la lista de espera de un tipo de cita",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointments": {
      "get": {
        "description": "Filtros from/to/status(+PENDIENTE_ANTICIPO)/assignedTo/resourceId; orden startAt ASC.",
        "operationId": "AppointmentsController_list",
        "parameters": [
          {
            "description": "Desde (ISO instant): startAt >=.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Hasta (ISO instant): startAt <.",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "PENDING",
                "CONFIRMED",
                "RESCHEDULED",
                "CANCELLED",
                "COMPLETED",
                "NO_SHOW",
                "PENDIENTE_ANTICIPO"
              ],
              "type": "string"
            }
          },
          {
            "description": "me | unassigned | <userId>.",
            "in": "query",
            "name": "assignedTo",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filtra por recurso (staff/sala) — calendario por columna.",
            "in": "query",
            "name": "resourceId",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Agenda de citas",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      },
      "post": {
        "description": "Agenda sobre un hilo existente (conversationId) o un walk-in (contact:{phone,name}) — este último find-or-create una Conversation server-side. tenant/to SIEMPRE server-side. NATIVE-only.",
        "operationId": "AppointmentsController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAppointmentDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "AppointmentDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Alta de cita (sin conversación / walk-in)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointments/availability": {
      "get": {
        "description": "Rejilla local − citas − excepciones − buffers − antelación − horizonte (tz IANA).",
        "operationId": "AppointmentsController_availability",
        "parameters": [
          {
            "description": "AppointmentType a consultar.",
            "in": "query",
            "name": "typeId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Desde (ISO date/instant, inclusive).",
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Hasta (ISO date/instant, exclusive).",
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "deprecated": true,
            "description": "Solo display: el servidor la IGNORA — la zona es SIEMPRE la del negocio. Se acepta por compatibilidad.",
            "in": "query",
            "name": "tz",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Recurso (staff/sala) específico.",
            "in": "query",
            "name": "resourceId",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Disponibilidad (slots) de un tipo",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      }
    },
    "/v1/appointments/calendar": {
      "get": {
        "description": "Conteos por día y estado en [from, to) (YYYY-MM-DD en tz, to EXCLUSIVO) para pintar la rejilla del mes sin traer las citas completas (esquiva el cap 100 del list). total EXCLUYE CANCELLED (= Σ byStatus). Filtros resourceId/assignedTo = los del list. Cap de rango 62 días. El detalle del día se obtiene con GET /v1/appointments (list).",
        "operationId": "AppointmentsController_calendar",
        "parameters": [
          {
            "description": "Inicio (inclusive), YYYY-MM-DD en tz.",
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "example": "2026-07-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Fin (EXCLUSIVO), YYYY-MM-DD en tz.",
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "example": "2026-08-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Zona IANA de proyección.",
            "in": "query",
            "name": "tz",
            "required": false,
            "schema": {
              "default": "America/Mexico_City",
              "maxLength": 64,
              "pattern": "/^[A-Za-z0-9_+\\-/]+$/",
              "type": "string"
            }
          },
          {
            "description": "Filtra por recurso (staff/sala) — mismo filtro que el list.",
            "in": "query",
            "name": "resourceId",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "me | unassigned | <userId> — mismo filtro que el list.",
            "in": "query",
            "name": "assignedTo",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "CalendarSummaryDto (range + days[]).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Resumen del calendario mensual (conteos por día/estado)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      }
    },
    "/v1/appointments/calendar/day": {
      "get": {
        "description": "ResourceDayDto { date, tz, columns[] } del día `date` (YYYY-MM-DD) en la zona del NEGOCIO. Una columna por recurso activo ordenado por nombre (más los inactivos con cita ese día); «Sin asignar» (resource=null) al final solo si hay citas sin recurso y sin filtro. Cada columna: openIntervals (ISO, de AvailabilityRule) y appointments (AppointmentDetailDto: AppointmentDto + lastEvents, fromWaitlist, contactVerifiedAt, rebookedFromId, nextOffer). Excluye CANCELLED. `resourceIds` (coma) limita las columnas. Solo lectura.",
        "operationId": "AppointmentsController_day",
        "parameters": [
          {
            "description": "Día YYYY-MM-DD en la zona del negocio.",
            "in": "query",
            "name": "date",
            "required": true,
            "schema": {
              "example": "2026-09-29",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Recursos (personas/cabinas) separados por coma, máx. 50. Con filtro no se incluye «Sin asignar».",
            "in": "query",
            "name": "resourceIds",
            "required": false,
            "schema": {
              "example": "res_a,res_b",
              "maxLength": 3250,
              "pattern": "/^[A-Za-z0-9_-]{1,64}(,[A-Za-z0-9_-]{1,64})*$/",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ResourceDayDto (date + tz + columns[]).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Vista del día por recurso (columnas por persona/cabina)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      }
    },
    "/v1/appointments/templates": {
      "get": {
        "description": "Una por kind (confirmación, recordatorio 24 h, recordatorio 1 h, reprogramación, cancelación) con su estado real en Meta. `NOT_PROVISIONED` = aún no se activaron los recordatorios.",
        "operationId": "AppointmentTemplatesController_overview",
        "parameters": [],
        "responses": {
          "200": {
            "description": "AppointmentTemplatesOverviewDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Estado de las plantillas de citas de este negocio",
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      }
    },
    "/v1/appointments/templates/provision": {
      "post": {
        "description": "Clic explícito del dueño. Idempotente: lo ya solicitado no se duplica. Meta aprueba de forma asíncrona; los recordatorios salen cuando cada plantilla está APPROVED.",
        "operationId": "AppointmentTemplatesController_provision",
        "parameters": [],
        "responses": {
          "200": {
            "description": "AppointmentTemplatesProvisionDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "WABA_NOT_CONNECTED — conecta tu número de WhatsApp antes."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Activar recordatorios: crear las 5 plantillas de citas en la WABA del negocio",
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "appointments:write",
          "templates:manage"
        ]
      }
    },
    "/v1/appointments/templates/{kind}/resubmit": {
      "post": {
        "description": "Crea una nueva versión (`<nombre>_v<n>`) en la WABA y deja el kind apuntando a ella. Solo desde REJECTED.",
        "operationId": "AppointmentTemplatesController_resubmit",
        "parameters": [
          {
            "in": "path",
            "name": "kind",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "AppointmentTemplatesOverviewDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "TEMPLATE_NOT_RESUBMITTABLE — la plantilla no está rechazada."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Volver a solicitar una plantilla de citas rechazada",
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "appointments:write",
          "templates:manage"
        ]
      }
    },
    "/v1/appointments/types": {
      "get": {
        "description": "AppointmentTypeDto[] del proveedor activo.",
        "operationId": "AppointmentsController_types",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Tipos de cita disponibles",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      }
    },
    "/v1/appointments/waitlist": {
      "get": {
        "description": "Filtros typeId/resourceId/status; sin status = cola activa (WAITING+OFFERED). Orden PRIORITY-aware (priority desc, createdAt asc). Read-first: cero write/cola.",
        "operationId": "WaitlistController_list",
        "parameters": [
          {
            "description": "Filtra por AppointmentType.",
            "in": "query",
            "name": "typeId",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filtra por recurso (staff/box).",
            "in": "query",
            "name": "resourceId",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filtra por estado; omitido = cola activa (WAITING+OFFERED).",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "WAITING",
                "OFFERED",
                "CONVERTED",
                "CANCELLED",
                "EXPIRED"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "WaitlistListView.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Lista de espera (gestión)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      }
    },
    "/v1/appointments/waitlist/overview": {
      "get": {
        "description": "CA2.8 · Ola 2c: `filledSeats` = citas con inicio en el rango y no canceladas que nacieron de la lista por oferta tomada (`filledSeatsByOffer`, Appointment.originWaitlistOfferId) O por conversión manual desde la lista (`filledSeatsByStaff`, entrada CONVERTED sin oferta de origen) — sin doble conteo (`filledSeats = byOffer + byStaff`); + en lista ahora, con oferta abierta, ofertas abiertas/vencidas/anuladas y minutos promedio para rellenar. Sin `from/to` = mes en curso. Ruta literal registrada ANTES de `:id`.",
        "operationId": "WaitlistController_overview",
        "parameters": [
          {
            "description": "Inicio del rango (ISO-8601). Default: primer día del mes en curso.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Fin del rango, exclusivo (ISO-8601). Default: primer día del mes siguiente.",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "WaitlistOverviewDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cifras de la lista de espera (sillas rellenadas por la lista)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      }
    },
    "/v1/appointments/waitlist/{id}": {
      "delete": {
        "description": "status=CANCELLED (idempotente). 409 si ya se convirtió en cita (CONVERTED).",
        "operationId": "WaitlistController_cancel",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "WaitlistEntryView.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Quitar (cancelar) una entrada de la lista",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      },
      "patch": {
        "description": "Ajusta priority/earliestAt/latestAt. Solo WAITING (409 en otro estado).",
        "operationId": "WaitlistController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWaitlistEntryDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "WaitlistEntryView.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar (reordenar) una entrada en espera",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointments/waitlist/{id}/convert": {
      "post": {
        "description": "Ola 2c: en UNA transacción crea la cita (mismo book nativo que `POST /v1/appointments`, sobre el hilo y el servicio DE LA ENTRADA — body = startAt, tz, modality, resourceId, contactEmail, notes) y marca la entrada CONVERTED con appointmentId + convertedAt (+ AuditLog `waitlist.entry.converted`). Solo entradas aún en la lista (WAITING | OFFERED | EXPIRED). Si la cita falla (409 slot ocupado, 400 tz…) la entrada no cambia. Post-commit: recordatorios, confirmación por WhatsApp (gated) y SSE `waitlist.updated` reason=converted. Cuenta en `filledSeats` del overview.",
        "operationId": "WaitlistController_convert",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConvertWaitlistEntryDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "WaitlistConvertResult { entry: WaitlistEntryView, appointment: AppointmentDto }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "409": {
            "description": "WAITLIST_ENTRY_CONVERTED (ya es cita) · WAITLIST_ENTRY_CANCELLED (ya no está en la lista) · APPOINTMENT_SLOT_UNAVAILABLE (el hueco no es reservable)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Agendar desde la lista (convertir una entrada en cita)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointments/{id}": {
      "delete": {
        "description": "status=CANCELLED (idempotente); remueve recordatorios pendientes + SYSTEM message + SSE.",
        "operationId": "AppointmentsController_cancel",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelAppointmentDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cancelar una cita",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      },
      "get": {
        "operationId": "AppointmentsController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "AppointmentDetailDto (AppointmentDto + lastEvents, fromWaitlist, contactVerifiedAt, rebookedFromId, nextOffer).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ficha de una cita",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      },
      "patch": {
        "description": "Revalida el slot (409 APPOINTMENT_SLOT_UNAVAILABLE si ya no existe); re-agenda recordatorios + SYSTEM message + SSE.",
        "operationId": "AppointmentsController_reschedule",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RescheduleAppointmentDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reprogramar una cita",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointments/{id}/charge": {
      "post": {
        "description": "Genera una liga de Cobros por el precio del tipo (monto SERVER-SIDE, nunca del body). NO bloquea la reserva. NINGUNO/TOTAL; en tipos ANTICIPO el depósito es automático (409). Estado por SSE payment.updated.",
        "operationId": "AppointmentsController_charge",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChargeAppointmentDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "PaymentLinkDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cobrar (total/manual)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointments/{id}/complete": {
      "post": {
        "description": "CONFIRMED|RESCHEDULED → COMPLETED (idempotente; 409 desde CANCELLED/NO_SHOW). Dispara el earn-on-cita (Lealtad F9-P1) en la misma tx + SSE appointment.updated / loyalty.updated.",
        "operationId": "AppointmentsController_complete",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Completar una cita",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointments/{id}/confirm": {
      "post": {
        "description": "PENDING → CONFIRMED (idempotente; 409 desde CANCELLED/COMPLETED/NO_SHOW/PENDIENTE_ANTICIPO). SYSTEM msg + SSE appointment.updated + confirmación WhatsApp (gate CIT-J4). Mantiene recordatorios.",
        "operationId": "AppointmentsController_confirm",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Confirmar una cita",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write",
          "messages:send"
        ]
      }
    },
    "/v1/appointments/{id}/no-show": {
      "post": {
        "description": "PENDING|CONFIRMED|RESCHEDULED → NO_SHOW (idempotente; 409 desde CANCELLED/COMPLETED). NO dispara earn; remueve recordatorios pendientes + SYSTEM msg + SSE appointment.updated. Base de comisiones v2.",
        "operationId": "AppointmentsController_noShow",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Marcar no-show",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/appointments/{id}/use-package": {
      "delete": {
        "description": "Devuelve la sesión al bono (RESTORE +1) y limpia el vínculo. Solo antes de COMPLETED (409 si no).",
        "operationId": "AppointmentsController_unlinkPackage",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Desvincular bono (Lealtad-Paquetes)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      },
      "post": {
        "description": "Reserva 1 sesión del bono (REDEEM -1, server-side) y la vincula a la cita → CONFIRMED, reemplazando el cobro (override chargePolicy). 409 si el bono no tiene sesiones / no cubre el servicio / es de otro contacto / la cita ya tiene bono / el anticipo ya está pagado.",
        "operationId": "AppointmentsController_usePackage",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UsePackageDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Usar bono (Lealtad-Paquetes)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/automations": {
      "get": {
        "description": "Filtros: trigger, enabled.",
        "operationId": "AutomationsController_list",
        "parameters": [
          {
            "in": "query",
            "name": "trigger",
            "required": false,
            "schema": {
              "enum": [
                "CONVERSATION_OPENED",
                "MESSAGE_RECEIVED",
                "CONVERSATION_ASSIGNED",
                "CONVERSATION_CLOSED",
                "LEAD_CREATED",
                "LEAD_STAGE_CHANGED",
                "SLA_FIRST_RESPONSE_BREACH",
                "NO_REPLY_AFTER",
                "HANDOFF_UNATTENDED"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "enabled",
            "required": false,
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar reglas de automatización",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "automations:read"
        ]
      },
      "post": {
        "description": "Valida conditions/actions con Zod → 400 AUTOMATION_INVALID.",
        "operationId": "AutomationsController_create",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear regla",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "automations:write"
        ]
      }
    },
    "/v1/automations/business-hours": {
      "get": {
        "operationId": "AutomationsController_getBusinessHours",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Horario de atención del tenant (fuente del fact businessHours)",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "automations:read"
        ]
      },
      "put": {
        "operationId": "AutomationsController_putBusinessHours",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Definir horario de atención",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "automations:write"
        ]
      }
    },
    "/v1/automations/{id}": {
      "delete": {
        "operationId": "AutomationsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar regla",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "automations:write"
        ]
      },
      "get": {
        "operationId": "AutomationsController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ficha de una regla",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "automations:read"
        ]
      },
      "patch": {
        "operationId": "AutomationsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar regla (enable/disable, reordenar, condiciones, acciones)",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "automations:write"
        ]
      }
    },
    "/v1/automations/{id}/runs": {
      "get": {
        "description": "Paginated<AutomationRunDto> keyset (createdAt,id DESC). Sin PII.",
        "operationId": "AutomationsController_runs",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Timeline de ejecuciones de una regla",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "automations:read"
        ]
      }
    },
    "/v1/automations/{id}/test": {
      "post": {
        "description": "Devuelve { matched, actionsPreview }. NO ejecuta acciones.",
        "operationId": "AutomationsController_test",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Dry-run: evaluar un fact de ejemplo",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "automations:write"
        ]
      }
    },
    "/v1/availability-exceptions": {
      "get": {
        "operationId": "AvailabilityExceptionsController_list",
        "parameters": [
          {
            "description": "Filtra por tipo (valida ownership).",
            "in": "query",
            "name": "appointmentTypeId",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar excepciones de disponibilidad",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      },
      "post": {
        "description": "blocked=true cierra el día; blocked=false requiere start/endMinute (override).",
        "operationId": "AvailabilityExceptionsController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAvailabilityExceptionDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear una excepción de disponibilidad",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/availability-exceptions/{id}": {
      "delete": {
        "operationId": "AvailabilityExceptionsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar una excepción de disponibilidad",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      },
      "patch": {
        "operationId": "AvailabilityExceptionsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAvailabilityExceptionDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar una excepción de disponibilidad",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/availability-rules": {
      "get": {
        "operationId": "AvailabilityRulesController_list",
        "parameters": [
          {
            "description": "Filtra por tipo (valida ownership).",
            "in": "query",
            "name": "appointmentTypeId",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar reglas de disponibilidad",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      },
      "post": {
        "operationId": "AvailabilityRulesController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAvailabilityRuleDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear una regla de disponibilidad",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/availability-rules/{id}": {
      "delete": {
        "operationId": "AvailabilityRulesController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar una regla de disponibilidad",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      },
      "patch": {
        "operationId": "AvailabilityRulesController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAvailabilityRuleDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar una regla de disponibilidad",
        "tags": [
          "Configuración"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/bots": {
      "get": {
        "operationId": "BotsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar bots con estado y actividad de 24 h",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      },
      "post": {
        "operationId": "BotsController_create",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear bot (vacío, desde plantilla o con un grafo)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/capabilities": {
      "get": {
        "operationId": "BotsController_capabilities",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Qué requisitos cumple el negocio (catálogo, Flows, citas, cobros…) y por número",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      }
    },
    "/v1/bots/catalog": {
      "get": {
        "operationId": "BotsController_catalog",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Catálogo de piezas con límites de Meta y disponibilidad en México",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      }
    },
    "/v1/bots/connection-presets": {
      "get": {
        "operationId": "BotConnectionsController_presets",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Conectores preconfigurados (Google Sheets, WooCommerce, Shopify…)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      }
    },
    "/v1/bots/connections": {
      "get": {
        "operationId": "BotConnectionsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar conexiones (sin secretos)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      },
      "post": {
        "operationId": "BotConnectionsController_create",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear conexión (secreto cifrado)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/connections/{connectionId}": {
      "delete": {
        "operationId": "BotConnectionsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "connectionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Borrar conexión (409 si un bot la usa)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      },
      "patch": {
        "operationId": "BotConnectionsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "connectionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar conexión (omitir `secret` lo conserva)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/connections/{connectionId}/test": {
      "post": {
        "operationId": "BotConnectionsController_test",
        "parameters": [
          {
            "in": "path",
            "name": "connectionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Probar conexión",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/env": {
      "get": {
        "operationId": "BotConnectionsController_envList",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Variables de entorno del negocio",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      },
      "put": {
        "operationId": "BotConnectionsController_envPut",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reemplazar variables de entorno del negocio",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/templates": {
      "get": {
        "operationId": "BotsController_templates",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Plantillas de bot iniciales",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      }
    },
    "/v1/bots/{id}": {
      "delete": {
        "operationId": "BotsController_archive",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Archivar bot (termina sus sesiones vivas)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      },
      "get": {
        "operationId": "BotsController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ficha del bot con borrador y validación",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      },
      "patch": {
        "operationId": "BotsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar nombre, descripción o prioridad",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/{id}/channels": {
      "put": {
        "description": "409 BOT_NOT_PUBLISHED / BOT_TRIGGER_CONFLICT.",
        "operationId": "BotsController_setChannels",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Prender o apagar por número",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/{id}/draft": {
      "put": {
        "description": "409 BOT_DRAFT_CONFLICT si otra persona guardó antes.",
        "operationId": "BotsController_saveDraft",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Autoguardado del borrador (concurrencia por revisión)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/{id}/duplicate": {
      "post": {
        "operationId": "BotsController_duplicate",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Duplicar bot (borrador)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/{id}/http-test": {
      "post": {
        "operationId": "BotsController_httpTest",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Probar un nodo de API con valores de ejemplo (SSRF-seguro; no guarda nada)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/{id}/metrics": {
      "get": {
        "operationId": "BotsController_metrics",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "versionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Métricas por nodo, resultado y costo estimado",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      }
    },
    "/v1/bots/{id}/publish": {
      "post": {
        "operationId": "BotsController_publish",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Publicar el borrador como versión inmutable",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/{id}/runs": {
      "get": {
        "operationId": "BotsController_runs",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sessionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ejecuciones paso a paso (30 días, sin datos del cliente)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      }
    },
    "/v1/bots/{id}/sessions": {
      "get": {
        "operationId": "BotsController_sessions",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Sesiones del bot",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      }
    },
    "/v1/bots/{id}/simulate": {
      "post": {
        "operationId": "BotsController_simulate",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Simular una entrada del cliente (sin WhatsApp ni efectos)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/{id}/test-phones": {
      "put": {
        "operationId": "BotsController_setTestPhones",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "«Probar en mi WhatsApp»: teléfonos que corren el borrador",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/{id}/validate": {
      "post": {
        "operationId": "BotsController_validate",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Validar como para publicar (errores y avisos estructurados por nodo)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      }
    },
    "/v1/bots/{id}/versions": {
      "get": {
        "operationId": "BotsController_versions",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Historial de versiones",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      }
    },
    "/v1/bots/{id}/versions/restore": {
      "post": {
        "operationId": "BotsController_restore",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Volver a una versión (crea una versión nueva)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:write"
        ]
      }
    },
    "/v1/bots/{id}/versions/{versionId}": {
      "get": {
        "operationId": "BotsController_version",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "versionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Versión con su grafo",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "bots:read"
        ]
      }
    },
    "/v1/calls/{id}": {
      "get": {
        "operationId": "CallsController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallResponseDto"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Llamadas"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      }
    },
    "/v1/calls/{id}/recording": {
      "get": {
        "operationId": "CallMediaController_recording",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallMediaLinkResponse"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Llamadas"
        ],
        "x-required-scopes": [
          "calls:media:read"
        ]
      }
    },
    "/v1/calls/{id}/transcript": {
      "get": {
        "operationId": "CallMediaController_transcript",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallMediaLinkResponse"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Llamadas"
        ],
        "x-required-scopes": [
          "calls:media:read"
        ]
      }
    },
    "/v1/catalog/products": {
      "get": {
        "operationId": "CatalogController_listProducts",
        "parameters": [
          {
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "ACTIVE",
                "HIDDEN",
                "ARCHIVED"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "maxLength": 120,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "category",
            "required": false,
            "schema": {
              "maxLength": 80,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "lowStock",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 60,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar productos del catálogo (filtros: status, q, category, lowStock)",
        "tags": [
          "Catálogo y productos"
        ],
        "x-required-scopes": [
          "catalog:read"
        ]
      },
      "post": {
        "operationId": "CatalogController_createProduct",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProductDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Alta de producto NATIVE",
        "tags": [
          "Catálogo y productos"
        ],
        "x-required-scopes": [
          "catalog:write"
        ]
      }
    },
    "/v1/catalog/products/{id}": {
      "delete": {
        "operationId": "CatalogController_archiveProduct",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Archivar (borrado suave) del producto",
        "tags": [
          "Catálogo y productos"
        ],
        "x-required-scopes": [
          "catalog:write"
        ]
      },
      "get": {
        "operationId": "CatalogController_getProduct",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Detalle del producto (+ imágenes + inventario)",
        "tags": [
          "Catálogo y productos"
        ],
        "x-required-scopes": [
          "catalog:read"
        ]
      },
      "patch": {
        "operationId": "CatalogController_updateProduct",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProductDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar campos/estado del producto (NATIVE)",
        "tags": [
          "Catálogo y productos"
        ],
        "x-required-scopes": [
          "catalog:write"
        ]
      }
    },
    "/v1/catalog/products/{id}/images": {
      "post": {
        "operationId": "CatalogController_addImage",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddImageDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Agregar imagen (URL https) al producto",
        "tags": [
          "Catálogo y productos"
        ],
        "x-required-scopes": [
          "catalog:write"
        ]
      }
    },
    "/v1/catalog/products/{id}/inventory": {
      "get": {
        "operationId": "CatalogController_getInventory",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Inventario del producto (onHand + umbral + últimos movimientos)",
        "tags": [
          "Catálogo y productos"
        ],
        "x-required-scopes": [
          "catalog:read"
        ]
      }
    },
    "/v1/catalog/products/{id}/inventory/adjust": {
      "post": {
        "operationId": "CatalogController_adjustStock",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdjustStockDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ajustar existencias (IN/OUT/ADJUST) → StockMovement + recalcula onHand",
        "tags": [
          "Catálogo y productos"
        ],
        "x-required-scopes": [
          "catalog:write"
        ]
      }
    },
    "/v1/commission-rules": {
      "get": {
        "description": "Filtros opcionales sourceType/active.",
        "operationId": "CommissionsController_list",
        "parameters": [
          {
            "in": "query",
            "name": "sourceType",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "active",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar reglas de comisión",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "commissions:read"
        ]
      },
      "post": {
        "description": "PERCENT (rateBps) o FIXED (amountMinor+currency). Alcance por profesional/servicio/tipo de venta o default. 409 si ya existe una regla para ese alcance.",
        "operationId": "CommissionsController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCommissionRuleDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear una regla",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "commissions:write"
        ]
      }
    },
    "/v1/commission-rules/{id}": {
      "delete": {
        "description": "Hard delete de la config; el devengo histórico es inmutable.",
        "operationId": "CommissionsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar una regla",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "commissions:write"
        ]
      },
      "get": {
        "operationId": "CommissionsController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe o no es del tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ficha de una regla",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "commissions:read"
        ]
      },
      "patch": {
        "description": "Cálculo (basis/rate/monto) y activo; el alcance es inmutable.",
        "operationId": "CommissionsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCommissionRuleDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar una regla",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "commissions:write"
        ]
      }
    },
    "/v1/commissions/overview": {
      "get": {
        "description": "Ventana FIJA server-side (mes en curso en tz de producto). config (reglas activas) | null, period, stats, topEarners (top-3 por net) y recentActivity (7 días) | null. Nómina: scope commissions:read + OWNER/ADMIN.",
        "operationId": "CommissionsOverviewController_getOverview",
        "parameters": [],
        "responses": {
          "200": {
            "description": "CommissionsOverviewView (config | period | stats | topEarners | recentActivity).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Overview de comisiones (mes en curso · nómina)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "commissions:read"
        ]
      }
    },
    "/v1/contacts": {
      "get": {
        "operationId": "ContactDirectoryController_list",
        "parameters": [
          {
            "description": "Busca en nombre, empresa, correo y teléfono.",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "maxLength": 120,
              "type": "string"
            }
          },
          {
            "description": "Sólo contactos con esta etiqueta.",
            "in": "query",
            "name": "tagId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Filtro por campo personalizado, `fieldId:valor`. Repetible; se combinan con Y.",
            "in": "query",
            "name": "field",
            "required": false,
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "description": "Sólo contactos que tienen al menos una conversación en este número (`PhoneNumber.id`, el `id` del número en Ajustes › Números).",
            "in": "query",
            "name": "phoneNumberId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Incluye los archivados.",
            "in": "query",
            "name": "includeArchived",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor opaco de la página siguiente.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 200,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar contactos con búsqueda y filtros",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/contacts/blocked": {
      "get": {
        "operationId": "BlockedContactsController_list",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor opaco de la página anterior (`nextCursor`).",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 512,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Contactos bloqueados del negocio (recientes primero, keyset)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/contacts/bulk": {
      "post": {
        "operationId": "ContactDirectoryController_bulk",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkContactsDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Etiquetar, quitar etiqueta, archivar o desarchivar hasta 200 contactos; responde por id qué cambió",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/erasure-request": {
      "post": {
        "description": "Para quien no tiene conversación con el negocio (p. ej. solo está en la agenda del teléfono conectado por Coexistence). Registra el mismo borrado que la ficha del cliente: todas sus llaves de WhatsApp, sus datos derivados y el registro de auditoría. Responde 202 igual exista o no la persona: `status: processing` = el borrado corre en cuanto el worker la toma; `status: pending` = queda registrada y se completa sola cuando se habilite la ejecución. Exige el scope `contacts:erase`; prohibido bajo suplantación; cupo de 10 solicitudes por hora por negocio (429 + Retry-After). Errores: 422 INVALID_PHONE.",
        "operationId": "ContactErasureController_requestErasureByPhone",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ErasureByPhoneDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "queued": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    },
                    "status": {
                      "enum": [
                        "pending",
                        "processing"
                      ],
                      "type": "string"
                    }
                  },
                  "required": [
                    "queued",
                    "status"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Solicitud registrada. `status`: `processing` (el borrado corre en cuanto el worker la toma) o `pending` (queda registrada y se completa sola cuando se habilite la ejecución; no hay que volver a pedirla).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar los datos de una persona por su teléfono (irreversible, sin gracia, OWNER/ADMIN)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:erase"
        ]
      }
    },
    "/v1/contacts/export": {
      "get": {
        "operationId": "ContactImportController_export",
        "parameters": [
          {
            "description": "Busca en nombre, empresa, correo y teléfono.",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "maxLength": 120,
              "type": "string"
            }
          },
          {
            "description": "Sólo contactos con esta etiqueta.",
            "in": "query",
            "name": "tagId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Filtro por campo personalizado, `fieldId:valor`. Repetible; se combinan con Y.",
            "in": "query",
            "name": "field",
            "required": false,
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "description": "Sólo contactos que tienen al menos una conversación en este número (`PhoneNumber.id`, el `id` del número en Ajustes › Números).",
            "in": "query",
            "name": "phoneNumberId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Incluye los archivados.",
            "in": "query",
            "name": "includeArchived",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor opaco de la página siguiente.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 200,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Exportar contactos a CSV con los mismos filtros del directorio (solo OWNER/ADMIN; queda en la bitácora)",
        "tags": [
          "Importar y exportar contactos"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/contacts/fields": {
      "get": {
        "operationId": "ContactDirectoryController_listFields",
        "parameters": [
          {
            "description": "`true` incluye los campos archivados.",
            "in": "query",
            "name": "includeArchived",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Campos personalizados del negocio",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      },
      "post": {
        "operationId": "ContactDirectoryController_createField",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContactFieldDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear un campo personalizado",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/fields/{fieldId}": {
      "delete": {
        "operationId": "ContactDirectoryController_deleteField",
        "parameters": [
          {
            "in": "path",
            "name": "fieldId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Borrar un campo Y todos sus valores capturados (destructivo; normalmente se archiva)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      },
      "patch": {
        "operationId": "ContactDirectoryController_updateField",
        "parameters": [
          {
            "in": "path",
            "name": "fieldId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateContactFieldDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Renombrar, reordenar o archivar un campo",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/imports": {
      "get": {
        "operationId": "ContactImportController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Últimas importaciones del negocio",
        "tags": [
          "Importar y exportar contactos"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      },
      "post": {
        "description": "Deduplica por teléfono canónico (con o sin el 1 de México). Exige elegir el consentimiento de forma explícita. Responde 202 con la importación; consulta el avance en `GET /v1/contacts/imports/{id}`.",
        "operationId": "ContactImportController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContactImportDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Importar contactos desde un archivo (trabajo en cola)",
        "tags": [
          "Importar y exportar contactos"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/imports/preview": {
      "post": {
        "operationId": "ContactImportController_preview",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactImportPreviewDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Vista previa de un archivo .csv/.xlsx: columnas, primeras filas y mapeo sugerido (no guarda nada)",
        "tags": [
          "Importar y exportar contactos"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/imports/{id}": {
      "get": {
        "operationId": "ContactImportController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Avance de una importación",
        "tags": [
          "Importar y exportar contactos"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/imports/{id}/errors.csv": {
      "get": {
        "operationId": "ContactImportController_errors",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reporte CSV de errores por fila de una importación",
        "tags": [
          "Importar y exportar contactos"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/person-suggestions": {
      "get": {
        "description": "Pares de fichas de canales distintos con señales deterministas: mismo teléfono (WhatsApp contra un campo tipo teléfono de IG/FB), mismo correo; y como señales débiles mismo nombre completo o nombre + misma foto. Nunca se unen solas. No salen los pares descartados, los que ya son la misma persona ni los que no se pueden unir (los dos ya tienen ese canal).",
        "operationId": "ContactPersonController_suggestions",
        "parameters": [
          {
            "description": "1–50 (20 por omisión).",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 50,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Sugerencias de «misma persona» entre WhatsApp, Instagram y Messenger (OWNER/ADMIN)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/contacts/person-suggestions/dismiss": {
      "post": {
        "description": "Persistente: el par no vuelve a sugerirse. Deja AuditLog `contact.person_suggestion_dismissed`. Prohibido bajo suplantación.",
        "operationId": "ContactPersonController_dismiss",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DismissPersonSuggestionDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Descartar una sugerencia de «misma persona» (OWNER/ADMIN)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/segment/count": {
      "get": {
        "operationId": "ContactDirectoryController_countSegment",
        "parameters": [
          {
            "description": "Busca en nombre, empresa, correo y teléfono.",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "maxLength": 120,
              "type": "string"
            }
          },
          {
            "description": "Sólo contactos con esta etiqueta.",
            "in": "query",
            "name": "tagId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Filtro por campo personalizado, `fieldId:valor`. Repetible; se combinan con Y.",
            "in": "query",
            "name": "field",
            "required": false,
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "description": "Sólo contactos que tienen al menos una conversación en este número (`PhoneNumber.id`, el `id` del número en Ajustes › Números).",
            "in": "query",
            "name": "phoneNumberId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Incluye los archivados.",
            "in": "query",
            "name": "includeArchived",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor opaco de la página siguiente.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 200,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cuántos contactos caen en un segmento (antes de mandarles algo)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/contacts/segments": {
      "get": {
        "operationId": "ContactDirectoryController_listSegments",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Segmentos guardados, cada uno con su conteo en vivo",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      },
      "post": {
        "operationId": "ContactDirectoryController_createSegment",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContactSegmentDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Guardar los filtros actuales como segmento con nombre",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/segments/{segmentId}": {
      "delete": {
        "operationId": "ContactDirectoryController_deleteSegment",
        "parameters": [
          {
            "in": "path",
            "name": "segmentId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Borrar un segmento (los contactos no se tocan)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      },
      "get": {
        "operationId": "ContactDirectoryController_getSegment",
        "parameters": [
          {
            "in": "path",
            "name": "segmentId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Un segmento con su conteo en vivo",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      },
      "patch": {
        "operationId": "ContactDirectoryController_updateSegment",
        "parameters": [
          {
            "in": "path",
            "name": "segmentId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateContactSegmentDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Renombrar un segmento o cambiar sus filtros",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/{id}": {
      "get": {
        "operationId": "ContactDirectoryController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Un contacto con sus etiquetas y campos",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      },
      "patch": {
        "operationId": "ContactDirectoryController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateContactDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar nombre, correo, empresa o archivar",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/{id}/fields/{fieldId}": {
      "put": {
        "operationId": "ContactDirectoryController_setFieldValue",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "fieldId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetFieldValueDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Capturar el valor de un campo personalizado",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/{id}/leads": {
      "get": {
        "description": "Keyset por `updatedAt` descendente (`nextCursor` opaco). Visibilidad por equipo como el tablero. 404 si el contacto no existe.",
        "operationId": "ContactLeadsController_list",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "`nextCursor` de la página anterior.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {}
          },
          {
            "description": "1–100 (30 por omisión).",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {}
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Todas las oportunidades de un contacto",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "leads:read"
        ]
      }
    },
    "/v1/contacts/{id}/person": {
      "get": {
        "description": "404 si la ficha no existe. Un solo canal = la ficha no está unida a otra.",
        "operationId": "ContactPersonController_person",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "La persona detrás de una ficha: su ficha principal y sus canales con el hilo más reciente de cada uno",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/contacts/{id}/person-suggestions": {
      "get": {
        "operationId": "ContactPersonController_suggestionsFor",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "1–50 (5 por omisión).",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 50,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "«Puede ser la misma persona que…»: sugerencias de esta ficha en otros canales (OWNER/ADMIN)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/contacts/{id}/person/link": {
      "post": {
        "description": "No mueve hilos: cada canal conserva su ancla (wa_id, IGSID, PSID), su ventana de 24 h y sus entrantes. La ficha principal conserva su nombre, completa correo y empresa vacíos y recibe la unión de etiquetas y campos. Deja AuditLog `contact.person_linked`. Errores: 404 NOT_FOUND · 409 PERSON_ALREADY_LINKED · PERSON_CHANGED · MERGE_ALREADY_MERGED · 422 PERSON_SAME_CONTACT · PERSON_SAME_CHANNEL · PERSON_CHANNEL_TAKEN · PERSON_CHANNEL_UNSUPPORTED. Prohibido bajo suplantación.",
        "operationId": "ContactPersonController_link",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LinkPersonDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Unir dos fichas de canales distintos en una sola persona (OWNER/ADMIN)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/{id}/person/unlink": {
      "post": {
        "description": "Si es la principal, la ficha unida más antigua pasa a serlo. Lo copiado al unir se queda. Deja AuditLog `contact.person_unlinked`. Errores: 404 · 409 PERSON_CHANGED · 422 PERSON_NOT_LINKED. Prohibido bajo suplantación.",
        "operationId": "ContactPersonController_unlink",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Separar esta ficha de su persona (OWNER/ADMIN)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/{id}/photos": {
      "get": {
        "operationId": "ContactPhotosController_listPhotos",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 24,
              "maximum": 50,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor keyset opaco (de `nextCursor`).",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 512,
              "type": "string"
            }
          },
          {
            "description": "Filtra por clasificación de la foto.",
            "in": "query",
            "name": "kind",
            "required": false,
            "schema": {
              "enum": [
                "BEFORE",
                "AFTER",
                "GENERAL"
              ],
              "type": "string"
            }
          },
          {
            "description": "Filtra por cita ligada.",
            "in": "query",
            "name": "appointmentId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ContactPhotosPage: { items: ServicePhotoDto[]; nextCursor }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Galería de fotos del contacto (keyset; filtros kind/appointmentId)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/contacts/{id}/tags": {
      "post": {
        "operationId": "ContactDirectoryController_addTag",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddContactTagDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Etiquetar un contacto",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/contacts/{id}/tags/{tagId}": {
      "delete": {
        "operationId": "ContactDirectoryController_removeTag",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "tagId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Quitarle una etiqueta a un contacto",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/conversation-close-reasons": {
      "get": {
        "description": "Por omisión solo los activos, en orden. `includeInactive=true` (solo OWNER/ADMIN) agrega los archivados. La primera vez se siembran los de fábrica (Resuelto, Venta cerrada, Cita agendada, Sin respuesta del cliente, No le interesó, Duplicada, Spam).",
        "operationId": "CloseReasonsController_list",
        "parameters": [
          {
            "in": "query",
            "name": "includeInactive",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CloseReasonsListResponseDto"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Motivos de cierre del negocio",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      },
      "post": {
        "description": "409 `CLOSE_REASON_LABEL_TAKEN` · `CLOSE_REASON_LIMIT_REACHED` (30).",
        "operationId": "CloseReasonsController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCloseReasonDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CloseReasonResponseDto"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "CLOSE_REASON_LABEL_REQUIRED"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "CLOSE_REASON_LABEL_TAKEN · CLOSE_REASON_LIMIT_REACHED"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear un motivo de cierre (auditado)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversation-close-reasons/{id}": {
      "patch": {
        "description": "Nunca se borra: `isActive=false` lo archiva.",
        "operationId": "CloseReasonsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCloseReasonDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CloseReasonResponseDto"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "NOT_FOUND"
          },
          "409": {
            "description": "CLOSE_REASON_LABEL_TAKEN"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Renombrar, reordenar o archivar un motivo (auditado)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations": {
      "get": {
        "description": "Orden lastMessageAt DESC con paginación keyset (cursor opaco). Filtros: status (default open), assignedTo (me | unassigned | userId), channel, origin.",
        "operationId": "ConversationsController_list",
        "parameters": [
          {
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "default": "open",
              "enum": [
                "open",
                "closed",
                "all"
              ],
              "type": "string"
            }
          },
          {
            "description": "`me` | `unassigned` | `<userId>` (`me` = principal autenticado). Un `<userId>` que no es una persona de este negocio (o no existe) ⇒ 400 `ASSIGNED_TO_UNKNOWN`.",
            "in": "query",
            "name": "assignedTo",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Canal del hilo: `whatsapp` | `instagram` | `messenger`. Se convierte con `toChannelEnum()` (fail-closed) al enum de la base.",
            "in": "query",
            "name": "channel",
            "required": false,
            "schema": {
              "enum": [
                "whatsapp",
                "instagram",
                "messenger"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "origin",
            "required": false,
            "schema": {
              "enum": [
                "direct",
                "ctwa",
                "qr",
                "walink",
                "widget",
                "ig_direct",
                "ig_ad",
                "booking",
                "msgr_direct",
                "msgr_ad"
              ],
              "type": "string"
            }
          },
          {
            "description": "Búsqueda: nombre o teléfono del contacto, y texto dentro del hilo. Se resuelve en el SERVIDOR (antes se filtraba en el navegador sobre las primeras 100 conversaciones y dejaba de encontrar sin avisar).",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "maxLength": 120,
              "type": "string"
            }
          },
          {
            "description": "Pospuestas: `hide` (omisión) las saca de la bandeja hasta su hora, `only` muestra sólo ésas, `all` no filtra.",
            "in": "query",
            "name": "snoozed",
            "required": false,
            "schema": {
              "default": "hide",
              "enum": [
                "hide",
                "only",
                "all"
              ],
              "type": "string"
            }
          },
          {
            "description": "Sólo conversaciones con esta etiqueta. Las etiquetas existían y no se podía filtrar por ellas — etiquetar sin poder buscar después no sirve de nada.",
            "in": "query",
            "name": "tagId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Sólo las conversaciones que entraron por este número de WhatsApp: el id del número de WhatsApp en Kontesta (no el ID de Meta). Se resuelve en el SERVIDOR con el mismo cursor: antes Mesa filtraba en el navegador sobre las primeras 100 y dejaba fuera, sin avisar, las del número que no cabían. Un id de otro negocio (o inexistente) da una lista vacía, no un error.",
            "in": "query",
            "name": "phoneNumberId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "`<teamId>` para la bandeja de un equipo; `none` para las que no están en ninguno.",
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Sólo las que llevan MÁS de N minutos esperando respuesta (el cliente escribió y nadie contestó). Es el filtro que convierte la bandeja en una lista de pendientes reales.",
            "in": "query",
            "name": "waitingOverMin",
            "required": false,
            "schema": {
              "maximum": 10080,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "`pending` = solo las que tienen un traspaso bot→humano pendiente (el bot pidió una persona y nadie ha contestado). Usa el índice parcial de pendientes; se combina con los demás filtros.",
            "in": "query",
            "name": "handoff",
            "required": false,
            "schema": {
              "enum": [
                "pending"
              ],
              "type": "string"
            }
          },
          {
            "description": "R10-13: sólo las de esta prioridad del equipo (`high` = alta). Se resuelve en el SERVIDOR con el mismo cursor.",
            "in": "query",
            "name": "priority",
            "required": false,
            "schema": {
              "enum": [
                "high",
                "normal"
              ],
              "type": "string"
            }
          },
          {
            "description": "R10-13: orden del listado. `recent` (omisión) = `lastMessageAt DESC`. `priority` = alta primero y, dentro de cada tramo, `lastMessageAt DESC`. El cursor recuerda el orden: usarlo con otro `sort` ⇒ 400 `INVALID_CURSOR`. En los dos órdenes las FIJADAS de quien pregunta salen antes, solo en la primera página (`pinned: true`), y no se repiten en las siguientes; no cuentan contra `limit`.",
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "recent",
              "enum": [
                "recent",
                "priority"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 30,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor keyset opaco (de `nextCursor`).",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 512,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated<ConversationDto> — assignedTo resuelto (criterio 4). Las que siguen pospuestas traen `snoozedUntil`, `snoozeReason`, `snoozedAt` y `snoozedBy { id, name }`. Con el agente de Meta en juego (bandera `meta.agent.standby` y WhatsApp) cada hilo trae además `threadControl` { owner, since, canTake, canReturnToMetaAgent, takeHint } y, si NACIÓ con el agente, `firstInboundOwner: \"META_AGENT\"` (esa conversación no cuenta en la 1.ª respuesta del negocio).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar conversaciones del inbox",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      },
      "post": {
        "description": "Normaliza el teléfono (México 52+10), elige el número de envío (o el único conectado), valida plantilla APROBADA y sus variables, aplica el consentimiento (MARKETING exige opt-in; UTILITY/AUTHENTICATION no) y envía por la misma ruta que la bandeja. Crea o reutiliza contacto y conversación. Acepta Idempotency-Key. Errores: 422 INVALID_PHONE · PHONE_OR_CONTACT_REQUIRED · NUMBER_REQUIRED · TEMPLATE_NOT_FOUND · TEMPLATE_NOT_APPROVED · TEMPLATE_VARIABLES_MISMATCH · TEMPLATE_COMPONENTS_REQUIRED · TEMPLATE_COMPONENTS_INVALID · CONSENT_MARKETING_REQUIRED · CONSENT_SERVICE_OPTED_OUT; 404 CONTACT_NOT_FOUND · NUMBER_NOT_FOUND · NOT_FOUND; 409 NUMBER_DISCONNECTED · ACCOUNT_RESTRICTED · CONSENT_EVIDENCE_OUTDATED; 403 QUOTA_EXCEEDED; 429 NEW_CONVERSATION_DAILY_LIMIT (Retry-After). MARKETING sin consentimiento vigente exige `optIn` (se registra como evidencia).",
        "operationId": "ConversationsController_create",
        "parameters": [
          {
            "description": "Llave única por intento de envío (1-255 caracteres ASCII visibles, p. ej. un UUID). Reenviar la MISMA petición con la misma llave dentro de 24 h devuelve la respuesta original (mismo messageId) sin encolar otro envío, con la cabecera `Idempotent-Replayed: true`. La llave es por usuario/API key y tenant.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "maxLength": 255,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateConversationDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "{ conversationId, created, contactId, messageId, status: QUEUED, conversation }",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "`IDEMPOTENCY_KEY_REUSED`: la llave ya se usó con otro cuerpo · `IDEMPOTENCY_IN_PROGRESS`: la petición original sigue en curso (reintentar) · `IDEMPOTENCY_ORIGINAL_FAILED`: la original falló sin confirmar si el mensaje salió; no se reintenta con esa llave (revisar la conversación y, si hace falta, usar una nueva)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "`IDEMPOTENCY_UNAVAILABLE`: con Idempotency-Key, si no se puede garantizar que el envío no se duplique, no se manda nada y se responde 503 con `Retry-After`. Reintentar con la MISMA llave."
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Nueva conversación a un número (primer mensaje con plantilla aprobada)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:send"
        ]
      }
    },
    "/v1/conversations/bulk": {
      "post": {
        "description": "Cada conversación se procesa por separado (SSE, auditoría y automatizaciones incluidas). El resultado dice cuántas salieron y cuáles no, con motivo. `snooze` con `until` en el pasado es 400 para todo el lote.",
        "operationId": "ConversationsController_bulk",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkConversationsDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Acción masiva: cerrar, reabrir, asignar, marcar leído, etiquetar o posponer varias",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/counts": {
      "get": {
        "description": "Los conteos ENTEROS del alcance, contados en la base — no sobre la página cargada. Los clientes los derivan del dato vivo que ya tienen, y eso alcanza hasta que una vista guardada o especial filtra en SERVIDOR: ahí sólo tienen el subconjunto y los conteos salen cortos. Misma visibilidad por equipo/asignación que `GET /v1/conversations` (lo que no se ve, no cuenta) y el mismo alcance escrito igual (`assignedTo`, `teamId`, `snoozed`). NO acepta `status`, `handoff` ni `waitingOverMin`: eso es lo que segmenta. `unanswered + following + resolved === all`, siempre.",
        "operationId": "ConversationsController_counts",
        "parameters": [
          {
            "description": "`me` | `unassigned` | `<userId>` (`me` = principal autenticado). Es el alcance «Mías» del segmentado. Un `<userId>` que no es una persona de este negocio (o no existe) ⇒ 400 `ASSIGNED_TO_UNKNOWN`.",
            "in": "query",
            "name": "assignedTo",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "`<teamId>` para la bandeja de un equipo (alcance «Mi equipo»); `none` para las que no están en ninguno.",
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Número de WhatsApp (el id del número de WhatsApp en Kontesta): con el selector de número activo, la bandeja ES la de ese número y los conteos también. Mismo nombre y misma regla que en el listado; un id ajeno ⇒ todo en 0.",
            "in": "query",
            "name": "phoneNumberId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Pospuestas, con la MISMA omisión que el listado (`hide`): una pospuesta no está en la bandeja, así que tampoco cuenta. Si el cliente pide la lista con otro valor, que pida los conteos igual.",
            "in": "query",
            "name": "snoozed",
            "required": false,
            "schema": {
              "default": "hide",
              "enum": [
                "hide",
                "only",
                "all"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationCountsDto"
                }
              }
            },
            "description": "ConversationCountsDto — `{ unanswered, following, resolved, all, unread, asOf }`. `unanswered` = abiertas que esperan respuesta (espera clásica o traspaso bot→humano pendiente); `unread` = mensajes sin leer en las abiertas del alcance (badge de la app). `Cache-Control: private, max-age=5` + `Vary: Authorization`: un cliente que lo pida en ráfaga (SSE) reutiliza la respuesta 5 s.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Conteos del segmentado de la bandeja (sin contestar · en seguimiento · resueltas · todas)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      }
    },
    "/v1/conversations/{conversationId}/bot-session": {
      "get": {
        "operationId": "ConversationBotSessionController_current",
        "parameters": [
          {
            "in": "path",
            "name": "conversationId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Sesión de bot más reciente del hilo (o null)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      }
    },
    "/v1/conversations/{conversationId}/bot-session/exit": {
      "post": {
        "operationId": "ConversationBotSessionController_exit",
        "parameters": [
          {
            "in": "path",
            "name": "conversationId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Sacar del bot (idempotente)",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{conversationId}/bot-session/start": {
      "post": {
        "description": "409 BOT_SESSION_ACTIVE / BOT_WINDOW_CLOSED / BOT_NOT_PUBLISHED. La sesión llega por SSE.",
        "operationId": "ConversationBotSessionController_start",
        "parameters": [
          {
            "in": "path",
            "name": "conversationId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Correr un bot en esta conversación",
        "tags": [
          "Bots"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{id}": {
      "get": {
        "description": "Incluye atribución first-touch: origin y ctwaClid (criterio 6). Mismos campos del agente de Meta que el listado (`threadControl`, `firstInboundOwner`).",
        "operationId": "ConversationsController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No existe, no es del tenant (RLS) o queda fuera de la visibilidad del usuario."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ficha de una conversación",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      },
      "patch": {
        "description": "assignedToId debe ser User activo del tenant (400 si no); null desasigna. Cada cambio queda auditado (AuditLog: conversation.assigned|unassigned|closed|reopened|close_reason_set). El motivo de cierre va al cerrar o hasta 15 minutos después mientras siga cerrada (ver `closeReasonId`).",
        "operationId": "ConversationsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateConversationDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "`CONVERSATION_NOT_CLOSED` · `CLOSE_REASON_WINDOW_EXPIRED` — motivo de cierre agregado después de cerrar."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Asignar / desasignar / cerrar / reabrir",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{id}/appointments": {
      "post": {
        "description": "Deriva contacto/lead/origin de la conversación, reserva (NATIVE), deja un Message SYSTEM en el hilo, emite SSE appointment.created y (gate CIT-J4) manda confirmación por WhatsApp.",
        "operationId": "ConversationAppointmentsController_book",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookInConversationDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "AppointmentDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Agendar una cita EN la conversación",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/conversations/{id}/call-permission": {
      "get": {
        "operationId": "CallPermissionController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallPermissionResponse"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Llamadas"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      }
    },
    "/v1/conversations/{id}/call-permission/request": {
      "post": {
        "operationId": "CallPermissionController_request",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestCallPermissionDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestCallPermissionResponseOut"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Llamadas"
        ],
        "x-required-scopes": [
          "messages:send"
        ]
      }
    },
    "/v1/conversations/{id}/catalog/send-product": {
      "post": {
        "operationId": "ConversationCatalogController_sendProduct",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendProductDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Enviar un producto por WhatsApp en la conversación (imagen+caption o texto)",
        "tags": [
          "Catálogo y productos"
        ],
        "x-required-scopes": [
          "catalog:write",
          "messages:send"
        ]
      }
    },
    "/v1/conversations/{id}/customer": {
      "get": {
        "operationId": "ContactsController_getCard",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ficha del cliente: agregados + timeline (1ª pág) + bonos + notas + tags",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/conversations/{id}/customer/appointments": {
      "get": {
        "operationId": "ContactsController_timeline",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 50,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor keyset opaco (de `nextCursor`).",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 512,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Timeline de visitas del contacto (paginación keyset)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/conversations/{id}/customer/block": {
      "delete": {
        "description": "Idempotente. Errores: 404 NOT_FOUND · 409 NUMBER_DISCONNECTED · 422 BLOCK_WINDOW_CLOSED · 429 META_RATE_LIMITED · 502 META_REJECTED · 503 META_UNAVAILABLE.",
        "operationId": "ContactsController_unblock",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Desbloquear al cliente en WhatsApp (OWNER/ADMIN)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      },
      "get": {
        "description": "`canBlock` false ⇒ `unavailableReason`: CHANNEL_UNSUPPORTED · NUMBER_NOT_CONNECTED · WINDOW_CLOSED.",
        "operationId": "ContactsController_getBlock",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Bloqueo del cliente en WhatsApp: estado vigente en el número de esta conversación y si se puede bloquear ya",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      },
      "post": {
        "description": "Errores: 404 NOT_FOUND · 409 NUMBER_DISCONNECTED · BLOCKLIST_FULL · 422 BLOCK_WINDOW_CLOSED · BLOCK_CHANNEL_UNSUPPORTED · BLOCK_SELF · 429 META_RATE_LIMITED · 502 META_REJECTED · 503 META_UNAVAILABLE.",
        "operationId": "ContactsController_block",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlockContactDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Bloquear al cliente en WhatsApp (OWNER/ADMIN, con motivo)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/conversations/{id}/customer/consent": {
      "get": {
        "operationId": "ContactsController_getConsent",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Consentimiento de marketing del contacto: estado vigente + últimos asientos (evidencia)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      },
      "post": {
        "operationId": "ContactsController_recordConsent",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecordConsentDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Registrar o retirar el consentimiento de marketing del contacto (AGENT+, con evidencia)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/conversations/{id}/customer/erasure-request": {
      "post": {
        "description": "Registra la solicitud ARCO y responde 202. `status: processing` = el borrado corre en cuanto el worker la toma; `status: pending` = queda registrada y se completa sola cuando se habilite la ejecución, sin volver a pedirla. Exige el scope `contacts:erase` (no lo implica `contacts:write`); prohibido bajo suplantación. Cupo propio: 10 solicitudes por hora por negocio (429 + Retry-After).",
        "operationId": "ContactsController_requestErasure",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "queued": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    },
                    "status": {
                      "enum": [
                        "pending",
                        "processing"
                      ],
                      "type": "string"
                    }
                  },
                  "required": [
                    "queued",
                    "status"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Solicitud registrada. `status`: `processing` (el borrado corre en cuanto el worker la toma) o `pending` (queda registrada y se completa sola cuando se habilite la ejecución; no hay que volver a pedirla).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar datos de este contacto (irreversible, sin gracia, OWNER/ADMIN)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:erase"
        ]
      }
    },
    "/v1/conversations/{id}/customer/fiscal": {
      "get": {
        "operationId": "ContactsController_getFiscal",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Datos fiscales del contacto (precarga \"Facturar\"); null si no existen",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      },
      "put": {
        "operationId": "ContactsController_upsertFiscal",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertFiscalDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Guardar/actualizar los datos fiscales del contacto (AGENT+)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/conversations/{id}/customer/merge": {
      "post": {
        "description": "Mueve al ganador conversaciones y mensajes, etiquetas, campos, notas, citas, cobros, tareas, oportunidades y consentimiento (una baja prevalece); el otro queda como histórico apuntando al ganador. Deja AuditLog `contact.merged_manual`. Errores: 404 NOT_FOUND · 422 MERGE_SAME_CONTACT · MERGE_CHANNEL_MISMATCH · 409 MERGE_ALREADY_MERGED. Prohibido bajo suplantación.",
        "operationId": "ContactsController_merge",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MergeContactDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Unir dos contactos duplicados del mismo canal (OWNER/ADMIN, gana el que se elija)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/conversations/{id}/customer/merge-candidates": {
      "get": {
        "description": "Cada candidato trae su `conversationId` (opaco) para pasarlo a `merge`. Los que ya se unieron a otro no salen.",
        "operationId": "ContactsController_mergeCandidates",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Texto a buscar (nombre, @usuario, correo o teléfono). Vacío ⇒ solo duplicados probables.",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "maxLength": 80,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 10,
              "maximum": 25,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Duplicados probables del contacto (mismo canal) o búsqueda por nombre, teléfono, @usuario o correo (OWNER/ADMIN)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/conversations/{id}/customer/notes": {
      "get": {
        "operationId": "ContactsController_listNotes",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Notas del contacto (recientes primero)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      },
      "post": {
        "operationId": "ContactsController_createNote",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContactNoteDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear una nota libre del contacto (AGENT+)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/conversations/{id}/customer/notes/{noteId}": {
      "delete": {
        "operationId": "ContactsController_deleteNote",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "noteId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Borrar una nota (OWNER/ADMIN o autor)",
        "tags": [
          "Ficha de cliente"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/conversations/{id}/flows": {
      "post": {
        "description": "Deriva contacto/número de la conversación, resuelve el FlowDefinition PUBLISHED del propósito, valida la ventana 24h (409 OUTSIDE_24H_WINDOW fuera de ella), crea el FlowSubmission(SENT) y envía el mensaje interactivo por el mismo path que todo lo saliente.",
        "operationId": "ConversationFlowsController_send",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendFlowDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "SendFlowResult { messageId, conversationId, submissionId, status }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Enviar un formulario (WhatsApp Flow) EN la conversación",
        "tags": [
          "WhatsApp Flows"
        ],
        "x-required-scopes": [
          "flows:write",
          "messages:send"
        ]
      }
    },
    "/v1/conversations/{id}/handoff": {
      "post": {
        "description": "Crea un EPISODIO de traspaso: silencia el bot, deja una nota interna de contexto y un mensaje de sistema, y post-commit el worker reparte (round-robin o directo), avisa al cliente, empuja push y emite el webhook `handoff.requested`. Desde una sesión el motivo es siempre MANUAL; con API key eliges `reason` (caso \"mi bot externo devuelve el hilo\"). Idempotente: si ya hay un traspaso pendiente responde 200 con `alreadyPending:true` y el episodio vigente.",
        "operationId": "HandoffController_request",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestHandoffDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "HandoffDto + alreadyPending:true — ya había un episodio pendiente.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "201": {
            "description": "HandoffDto — episodio nuevo (PENDING).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Conversación inexistente o de otro tenant (RLS)."
          },
          "409": {
            "description": "HANDOFF_CONVERSATION_CLOSED (no está abierta) · THREAD_CONTROL_BLOCKED (otra app controla el hilo, IG/FB)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Pasar la conversación a una persona (traspaso bot→humano)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{id}/handoff/return": {
      "post": {
        "description": "Cierra el episodio vigente (RETURNED), reactiva el bot (`botState=ACTIVE`) y deja un mensaje de sistema. Si el número vive con el agente de Meta por defecto, además le devuelve el hilo. En un número sin bot (HUMAN_ONLY) solo cierra el episodio; sin episodio ni bot ⇒ 409 NO_BOT_ACTIVE.",
        "operationId": "HandoffController_returnToBot",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ conversationId, botState: ACTIVE, threadOwner, returnedHandoffId, changed }",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Conversación inexistente o de otro tenant (RLS)."
          },
          "409": {
            "description": "HANDOFF_CONVERSATION_CLOSED · NO_BOT_ACTIVE."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Devolver la conversación al bot",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{id}/handoffs": {
      "get": {
        "description": "Episodios (más reciente primero, máximo 50) con motivo, quién lo pidió, a quién se enrutó y tiempos.",
        "operationId": "HandoffController_list",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "HandoffDto[]",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Conversación inexistente o de otro tenant (RLS)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Historial de traspasos de la conversación",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      }
    },
    "/v1/conversations/{id}/insight": {
      "get": {
        "description": "Incluye purpose (live|close|handoff), source (BYOK|PLATFORM), updatedAt, messageCountAtSummary y stale=true cuando hay mensajes nuevos desde el último resumen (el resumen vivo se actualiza solo).",
        "operationId": "AiInsightController_getInsight",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Insight del hilo (resumen/sentimiento/CSAT inferido/temas) — 404 si aún no se generó",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:read"
        ]
      }
    },
    "/v1/conversations/{id}/insight/reanalyze": {
      "post": {
        "description": "Valida bajo RLS que la conversación exista y que haya credencial de chat ruteada; encola AI_ANALYZE. Respeta el piso de 2 min del resumen vivo: dentro del piso responde 429 AI_INSIGHT_TOO_SOON con retryAfter (segundos).",
        "operationId": "AiInsightController_reanalyze",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Re-analizar el hilo on-demand (regenera el insight; idempotente por jobId)",
        "tags": [
          "IA (BYOK)"
        ],
        "x-required-scopes": [
          "ai:write"
        ]
      }
    },
    "/v1/conversations/{id}/invoices": {
      "get": {
        "operationId": "ConversationInvoicesController_listForConversation",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ items: InvoiceDto[] }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Facturas de la conversación",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:read"
        ]
      },
      "post": {
        "operationId": "ConversationInvoicesController_create",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInvoiceDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "InvoiceDto (status DRAFT; el worker timbra y avisa por SSE).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Facturar EN la conversación (crea CFDI DRAFT + encola timbrado)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:write"
        ]
      }
    },
    "/v1/conversations/{id}/leads": {
      "get": {
        "description": "`leads` trae las oportunidades del hilo y del contacto (T33: varias por contacto), la PRINCIPAL en `[0]` (la decide el servidor: abiertas primero, las de este hilo, embudo predeterminado…) más `principalLeadId`, `contactId`, `counts` y `hasMore`. Cada lead trae `pipelineId` y `stageChangedAt`; `pipelines` viene con el default primero y las etapas en orden.",
        "operationId": "ConversationLeadsController_list",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ConversationLeadsDto: { leads: LeadFichaDto[], pipelines: PipelineDto[] }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Oportunidades de la conversación + embudos (ficha del hilo)",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "leads:read"
        ]
      }
    },
    "/v1/conversations/{id}/loyalty": {
      "get": {
        "operationId": "ConversationLoyaltyController_getForConversation",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Membresías + saldos + actividad reciente + bonos de sesiones de la conversación",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:read"
        ]
      }
    },
    "/v1/conversations/{id}/loyalty/adjust": {
      "post": {
        "operationId": "ConversationLoyaltyController_adjust",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdjustDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ajustar el saldo +/- (ADMIN, reason obligatorio)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/conversations/{id}/loyalty/earn": {
      "post": {
        "operationId": "ConversationLoyaltyController_earn",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EarnManualDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Acumular manualmente (agente): puntos/monto o sellos",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/conversations/{id}/loyalty/enroll": {
      "post": {
        "operationId": "ConversationLoyaltyController_enroll",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnrollDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Inscribir al contacto en un programa (idempotente)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/conversations/{id}/loyalty/packages": {
      "post": {
        "operationId": "ConversationLoyaltyController_grantPackage",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GrantPackageDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Otorgar un bono de sesiones manualmente (sin pago; piso gate-free)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/conversations/{id}/loyalty/packages/sell": {
      "post": {
        "operationId": "ConversationLoyaltyController_sellPackage",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SellPackageDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Vender un bono por Cobros (liga por el precio del type; GRANT al PAID)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/conversations/{id}/loyalty/redeem": {
      "post": {
        "operationId": "ConversationLoyaltyController_redeem",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RedeemDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Canjear una recompensa (409 si saldo insuficiente)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/conversations/{id}/loyalty/send-balance": {
      "post": {
        "operationId": "ConversationLoyaltyController_sendBalance",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendBalanceDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Enviar el saldo por WhatsApp",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write",
          "messages:send"
        ]
      }
    },
    "/v1/conversations/{id}/loyalty/send-wallet-pass": {
      "post": {
        "operationId": "ConversationLoyaltyController_sendWalletPass",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendWalletPassDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Enviar la tarjeta de Wallet del contacto por WhatsApp (1 liga: iOS/Android)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write",
          "messages:send"
        ]
      }
    },
    "/v1/conversations/{id}/messages": {
      "get": {
        "description": "Orden canónico sentAt ASC; la primera página son LOS MÁS RECIENTES y `nextCursor` pagina hacia atrás (scroll-up). Los SYSTEM de asignación no se sirven; los de traspaso sí. Con la bandera `meta.agent.standby` encendida y SOLO en WhatsApp, el hilo suma el rastro del agente de Meta: `thread_control_changed` (quién tomó o devolvió) y `auto_send_skipped` (el envío automático que no salió).",
        "operationId": "ConversationsController_messages",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor keyset opaco (de `nextCursor`).",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 512,
              "type": "string"
            }
          },
          {
            "description": "Devuelve el tramo del hilo ALREDEDOR de este mensaje (mitad antes, mitad después). Es cómo se salta a un resultado de búsqueda de hace meses sin cargar todo lo intermedio. Excluyente con `cursor`.",
            "in": "query",
            "name": "around",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Conversación inexistente o de otro tenant."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Hilo de mensajes (paginado hacia atrás)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:read"
        ]
      },
      "post": {
        "description": "Deriva destinatario y número emisor de la conversación y delega a la ruta única de envío (Message QUEUED + cola wa-send). Enviar a una CLOSED la reabre. kind=template: si la plantilla está guardada, `template.components` se valida contra ella ANTES de encolar. Hilos de Instagram/Messenger: exigen el pilar `multichannel` (403 FEATURE_NOT_IN_PLAN, JWT y API key) y el rollout de Canales (503 CHANNELS_DISABLED); 422 TEXT_TOO_LONG por `capabilities.maxText` (IG bytes, FB caracteres) y 422 CHANNEL_DISCONNECTED si la Página raíz no está conectada — todo ANTES de tocar Meta.",
        "operationId": "ConversationsController_send",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Llave única por intento de envío (1-255 caracteres ASCII visibles, p. ej. un UUID). Reenviar la MISMA petición con la misma llave dentro de 24 h devuelve la respuesta original (mismo messageId) sin encolar otro envío, con la cabecera `Idempotent-Replayed: true`. La llave es por usuario/API key y tenant.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "maxLength": 255,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendInConversationDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "{ messageId, conversationId, status: QUEUED }",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "`IDEMPOTENCY_KEY_REUSED`: la llave ya se usó con otro cuerpo · `IDEMPOTENCY_IN_PROGRESS`: la petición original sigue en curso (reintentar) · `IDEMPOTENCY_ORIGINAL_FAILED`: la original falló sin confirmar si el mensaje salió; no se reintenta con esa llave (revisar la conversación y, si hace falta, usar una nueva)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "`IDEMPOTENCY_UNAVAILABLE`: con Idempotency-Key, si no se puede garantizar que el envío no se duplique, no se manda nada y se responde 503 con `Retry-After`. Reintentar con la MISMA llave."
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Enviar mensaje EN la conversación",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:send"
        ]
      }
    },
    "/v1/conversations/{id}/messages/at-date": {
      "get": {
        "description": "Devuelve el PRIMER mensaje del día (`exact:true`) o, si ese día no hubo, el primero POSTERIOR (`exact:false`; `message` null si tampoco hay). El día se lee a medianoche de `tz` — sin `tz`, la zona del horario de atención del tenant. Para ver el contexto, GET /messages?around=<message.id>.",
        "operationId": "ConversationsController_messageAtDate",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Día calendario, YYYY-MM-DD, en `tz`.",
            "in": "query",
            "name": "date",
            "required": true,
            "schema": {
              "example": "2026-09-03",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Zona IANA para interpretar `date`. Sin ella, la zona del horario de atención del tenant (America/Mexico_City si no lo configuró).",
            "in": "query",
            "name": "tz",
            "required": false,
            "schema": {
              "example": "America/Mexico_City",
              "maxLength": 64,
              "pattern": "/^[A-Za-z0-9_+\\-/]+$/",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageAtDateResultDto"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Fecha que no es un día calendario válido o tz que no es IANA."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Conversación inexistente o de otro tenant."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Saltar a un día del hilo",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:read"
        ]
      }
    },
    "/v1/conversations/{id}/messages/search": {
      "get": {
        "description": "Coincidencias con recorte de texto. Para ver el contexto de una, GET /messages?around=<id>. `%`, `_` y `\\` son literales; un término sin acentos ignora los acentos del texto.",
        "operationId": "ConversationsController_searchInThread",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Texto a buscar dentro del hilo: mínimo 2 caracteres, o un solo emoji. `%`, `_` y `\\` son literales; sin acentos en el término se ignoran los acentos del texto (\"cotizacion\" encuentra \"Cotización\"), con acentos se respetan.",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "maxLength": 120,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Conversación inexistente o de otro tenant."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Buscar dentro del hilo",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:read"
        ]
      }
    },
    "/v1/conversations/{id}/notes": {
      "get": {
        "description": "Sólo las ve el equipo. Una nota JAMÁS sale hacia el cliente.",
        "operationId": "ConversationsController_listNotes",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Notas internas del hilo",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      },
      "post": {
        "description": "El contexto que hoy vive en un grupo de WhatsApp aparte: \"ya viene con cita, cóbrale anticipo\". No crea un Message y no encola nada hacia Meta. Menciones \"@\": avisa (push `note.mention` + SSE `note.created`) a los compañeros activos que ven el hilo.",
        "operationId": "ConversationsController_createNote",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateNoteDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Dejar una nota interna",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{id}/payment-links": {
      "get": {
        "operationId": "ConversationPaymentLinksController_listForConversation",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ items: PaymentLinkDto[] }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ligas de pago de la conversación",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:read"
        ]
      },
      "post": {
        "description": "Resuelve la cuenta de cobro del tenant, crea la liga en la pasarela (comisión 0), la envía por WhatsApp, deja un Message SYSTEM en el hilo y emite SSE payment.updated.",
        "operationId": "ConversationPaymentLinksController_create",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentLinkDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "PaymentLinkDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cobrar EN la conversación (genera + envía liga de pago)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:write"
        ]
      }
    },
    "/v1/conversations/{id}/photos": {
      "post": {
        "description": "Marca un Message type=image del contacto como foto Antes/Después/General, la liga opcionalmente a una cita (snapshot del servicio) y crea MediaBlob(PENDING)+ServicePhoto. Encola la ingesta de bytes POST-COMMIT (best-effort). Exige consentAck=true (422 CONSENT_REQUIRED). Anti-doble-captura por mensaje+kind (409).",
        "operationId": "ConversationPhotosController_capture",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CapturePhotoDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "ServicePhotoDto (status PENDING hasta que la ingesta materialice los bytes).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Archivar una imagen del hilo como foto de servicio de la ficha",
        "tags": [
          "Fotos de servicio"
        ],
        "x-required-scopes": [
          "contacts:write"
        ]
      }
    },
    "/v1/conversations/{id}/presence": {
      "post": {
        "description": "El front lo manda cada ~15 s con el hilo abierto y con `typing:true` al teclear (cada ≥5 s). Los demás agentes lo reciben por SSE `conversation.presence`. Evita que dos contesten lo mismo. Señal de UI: bucket propio por (usuario, conversación), fuera del cupo de escritura del plan; dos latidos más cerca de 2 s se ignoran en silencio. Sin cuerpo (204). El nombre desaparece para los demás con `leave` o a los 45 s sin latido (TTL).",
        "operationId": "ConversationsController_presence",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PresenceHeartbeatDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Latido de presencia: \"estoy viendo esta conversación\" (+ escribiendo)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{id}/read": {
      "post": {
        "description": "unreadCount=0 (contador por conversación, v0.5). La UI lo llama al abrir el hilo y por cada entrante. El aviso de \"leído\" a Meta se ENCOLA (wa-signal), no se hace aquí; ya NO manda \"escribiendo…\" (eso es POST :id/typing). Es una señal de UI: no consume el cupo de escritura del plan (bucket propio por conversación).",
        "operationId": "ConversationsController_markRead",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Marcar conversación como leída",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{id}/request-contact-info": {
      "post": {
        "description": "Para clientes que escriben con username y sin teléfono. Manda el mensaje interactivo `request_contact_info`; cuando el cliente toca «Compartir número», el ingreso aprende su teléfono (`origin: contact_request`) y `contactPhone` deja de ser null. Mensaje de servicio: exige ventana de 24 h abierta. Errores: 404 NOT_FOUND · 400 CHANNEL_KIND_UNSUPPORTED · 409 CONTACT_PHONE_KNOWN · WINDOW_CLOSED · NUMBER_NOT_CONNECTED · 422 TEXT_TOO_LONG · 403 QUOTA_EXCEEDED. Acepta Idempotency-Key.",
        "operationId": "WhatsappIdentityController_requestContactInfo",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Llave única por intento de envío (1-255 caracteres ASCII visibles, p. ej. un UUID). Reenviar la MISMA petición con la misma llave dentro de 24 h devuelve la respuesta original (mismo messageId) sin encolar otro envío, con la cabecera `Idempotent-Replayed: true`. La llave es por usuario/API key y tenant.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "maxLength": 255,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestContactInfoDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "{ messageId, conversationId, status: QUEUED }",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "`IDEMPOTENCY_KEY_REUSED`: la llave ya se usó con otro cuerpo · `IDEMPOTENCY_IN_PROGRESS`: la petición original sigue en curso (reintentar) · `IDEMPOTENCY_ORIGINAL_FAILED`: la original falló sin confirmar si el mensaje salió; no se reintenta con esa llave (revisar la conversación y, si hace falta, usar una nueva)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "`IDEMPOTENCY_UNAVAILABLE`: con Idempotency-Key, si no se puede garantizar que el envío no se duplique, no se manda nada y se responde 503 con `Retry-After`. Reintentar con la MISMA llave."
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Pedir el teléfono al cliente (botón REQUEST_CONTACT_INFO de WhatsApp)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:send"
        ]
      }
    },
    "/v1/conversations/{id}/review-request": {
      "post": {
        "operationId": "ReviewRequestController_requestReview",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Pedir reseña por WhatsApp (link directo al formulario público)",
        "tags": [
          "Reputación"
        ],
        "x-required-scopes": [
          "messages:send",
          "reputation:write"
        ]
      }
    },
    "/v1/conversations/{id}/scheduled": {
      "get": {
        "operationId": "ScheduledMessagesController_list",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Envíos programados pendientes de esta conversación",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:read"
        ]
      },
      "post": {
        "description": "La ventana de 24 h se revisa AL ENVIAR, no ahora: entre una cosa y otra pueden pasar horas y lo que era texto libre deja de serlo. Si al llegar la hora la ventana cerró, el envío se marca SKIPPED_WINDOW y se avisa — no se manda algo que WhatsApp va a rechazar.",
        "operationId": "ScheduledMessagesController_create",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateScheduledMessageDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Programar un envío",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:send"
        ]
      }
    },
    "/v1/conversations/{id}/scheduled/{scheduledId}": {
      "delete": {
        "description": "Queda auditado (AuditLog: scheduled_message.canceled) con quién lo canceló.",
        "operationId": "ScheduledMessagesController_cancel",
        "parameters": [
          {
            "in": "path",
            "name": "scheduledId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cancelar un envío programado",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:send"
        ]
      }
    },
    "/v1/conversations/{id}/snooze": {
      "delete": {
        "operationId": "ConversationsController_unsnooze",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Regresar a la bandeja una conversación pospuesta",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      },
      "post": {
        "description": "La saca de la bandeja hasta la hora indicada. El servidor puede ADELANTAR esa hora: si cae después de que expire la ventana de 24 h de WhatsApp, despierta antes, mientras todavía se puede contestar con texto libre en vez de pagar una plantilla.",
        "operationId": "ConversationsController_snooze",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SnoozeConversationDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "La conversación con `snoozedUntil` ya resuelto, `snoozeReason` (la nota), `snoozedAt` y `snoozedBy`. Al despertar (sola o con DELETE …/snooze) el motivo se conserva como nota de SISTEMA del hilo.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Posponer la conversación",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{id}/tags": {
      "post": {
        "operationId": "ConversationTagsController_add",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Etiquetar conversación",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "tags:write"
        ]
      }
    },
    "/v1/conversations/{id}/tags/{tagId}": {
      "delete": {
        "operationId": "ConversationTagsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "tagId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Quitar etiqueta de la conversación",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "tags:write"
        ]
      }
    },
    "/v1/conversations/{id}/tasks": {
      "get": {
        "operationId": "ConversationTasksController_listByContact",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filtra por estado (omitir = todos).",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "OPEN",
                "DONE",
                "CANCELLED"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor keyset opaco (de `nextCursor`).",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 512,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Tareas/seguimientos del contacto (keyset por vencimiento)",
        "tags": [
          "Tareas de seguimiento"
        ],
        "x-required-scopes": [
          "tasks:read"
        ]
      },
      "post": {
        "operationId": "ConversationTasksController_create",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCrmTaskDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear una tarea/seguimiento del contacto (AGENT+)",
        "tags": [
          "Tareas de seguimiento"
        ],
        "x-required-scopes": [
          "tasks:write"
        ]
      }
    },
    "/v1/conversations/{id}/thread/pass": {
      "post": {
        "description": "Deja el hilo con el agente de Meta (threadOwner→META_AGENT; bloquea envíos automáticos) y avisa a Meta con Thread Control `release`; si Meta falla, la conversación vuelve a Kontesta y queda el rastro. Cierra como devuelto el traspaso abierto. AGENT solo si la conversación es suya o no tiene dueño. Requiere que el número tenga al agente de Meta. Si ya es del agente ⇒ 200 `changed:false`. Errores: 403 `FORBIDDEN`, 409 `THREAD_CONTROL_NOT_AVAILABLE`, 409 `META_RELEASE_REJECTED`, 409 `RECIPIENT_PHONE_CHANGED`, 409 `THREAD_CONTROL_RECIPIENT_UNSUPPORTED`, 429 `THREAD_CONTROL_BUSY`, 502 `META_THREAD_CONTROL_UNAVAILABLE`. No se permite en suplantación. Cupo: 30 por minuto por negocio, compartido con `thread/take` (429).",
        "operationId": "ConversationsController_passThread",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ conversationId, threadOwner, threadOwnerSince, changed, via: \"meta\" | \"noop\" }",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Devolver la conversación al agente de Meta",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{id}/thread/take": {
      "post": {
        "description": "Pide a Meta el control del hilo (Thread Control `take`) y, solo con su acuse, lo deja en Kontesta (threadOwner→KONTESTA) y pausa el robot. Si ya es de Kontesta ⇒ 200 `changed:false`, `via:\"noop\"` (no llama a Meta). Errores: 409 `THREAD_CONTROL_NOT_AVAILABLE` (función apagada para el negocio, canal que no es WhatsApp), 409 `META_TAKE_REJECTED` con `hint:\"send_only\"` (Meta solo deja tomar al escalation partner: envía un mensaje y la conversación pasa a tu equipo), 409 `RECIPIENT_PHONE_CHANGED`, 409 `THREAD_CONTROL_RECIPIENT_UNSUPPORTED` (cliente solo con BSUID), 429 `THREAD_CONTROL_BUSY`, 502 `META_THREAD_CONTROL_UNAVAILABLE`. Si Meta ya lo rechazó en este número en las últimas 24 h, responde 409 `META_TAKE_REJECTED` sin volver a llamarla. No se permite en suplantación. Cupo: 30 por minuto por negocio, compartido con `thread/pass` (429).",
        "operationId": "ConversationsController_takeThread",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ conversationId, threadOwner, threadOwnerSince, changed, via: \"meta\" | \"noop\" }",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Tomar la conversación del agente de Meta",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/conversations/{id}/typing": {
      "post": {
        "description": "Muéstrale al cliente \"escribiendo…\" porque vas a responder. Mándalo al empezar a teclear, no al abrir la conversación. Solo WhatsApp con un mensaje del cliente de las últimas 24 h (en otro caso no hace nada). Meta lo quita al responder o a los 25 s; se manda como máximo una vez cada 20 s por conversación. Se encola (wa-signal) y responde sin esperar a Meta; la misma llamada a Meta marca como leído el último mensaje del cliente. Pide el scope messages:send (es una señal que ve el cliente) y no se permite en suplantación. Cuenta en el cupo de escritura del plan (el front lo manda como mucho una vez cada 20 s por conversación). Sin cuerpo (204).",
        "operationId": "ConversationsController_typing",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Avisar al cliente que el agente está escribiendo",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:send"
        ]
      }
    },
    "/v1/conversations/{id}/waitlist": {
      "post": {
        "description": "Deriva contacto/lead de la conversación, crea la entrada WAITING (idempotente si ya espera el mismo servicio) y manda la confirmación por SESIÓN.",
        "operationId": "ConversationWaitlistController_join",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JoinWaitlistDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "WaitlistJoinResult.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Anotar al contacto en la lista de espera de un servicio",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:write"
        ]
      }
    },
    "/v1/entry-points": {
      "get": {
        "operationId": "EntryPointsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar los puntos de entrada del tenant",
        "tags": [
          "Puntos de entrada"
        ],
        "x-required-scopes": [
          "entrypoints:read"
        ]
      },
      "post": {
        "description": "Genera el `code` (para /e/:code) y, en kind=WIDGET, el `widgetKey` + snippet embebible. En kind=BOOKING toma un `bookingSlug` humano (@unique) para la página de reservas (una por tenant). En kind=IG_LINK/MSGR_LINK (Multicanal Ola 1) devuelve `https://ig.me/m/<usuario>?ref=` / `https://m.me/<pageId>?ref=` a partir de la cuenta INSTAGRAM/MESSENGER indicada (su Página debe estar CONNECTED; si no ⇒ 409 CHANNEL_NOT_CONNECTED); `ref` opcional (default: el `code`), único por tenant. Las identidades públicas no son secretas. La cuenta (channelAccountId) se valida por ownership.",
        "operationId": "EntryPointsController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateEntryPointDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear un punto de entrada (WALINK/QR/WIDGET/BOOKING/IG_LINK/MSGR_LINK)",
        "tags": [
          "Puntos de entrada"
        ],
        "x-required-scopes": [
          "entrypoints:write"
        ]
      }
    },
    "/v1/entry-points/channels": {
      "get": {
        "operationId": "EntryPointsController_channels",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Canales del tenant para el selector del alta (números de WhatsApp + Instagram/Messenger conectados)",
        "tags": [
          "Puntos de entrada"
        ],
        "x-required-scopes": [
          "entrypoints:read"
        ]
      }
    },
    "/v1/entry-points/{id}": {
      "delete": {
        "operationId": "EntryPointsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Borrar un punto de entrada (para solo desactivar: PATCH isActive=false)",
        "tags": [
          "Puntos de entrada"
        ],
        "x-required-scopes": [
          "entrypoints:write"
        ]
      },
      "get": {
        "operationId": "EntryPointsController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Detalle de un punto de entrada",
        "tags": [
          "Puntos de entrada"
        ],
        "x-required-scopes": [
          "entrypoints:read"
        ]
      },
      "patch": {
        "operationId": "EntryPointsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateEntryPointDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar label / activar-desactivar un punto de entrada",
        "tags": [
          "Puntos de entrada"
        ],
        "x-required-scopes": [
          "entrypoints:write"
        ]
      }
    },
    "/v1/hosted-links": {
      "post": {
        "operationId": "HostedLinksController_create",
        "parameters": [
          {
            "description": "Llave única por intento de envío (1-255 caracteres ASCII visibles, p. ej. un UUID). Reenviar la MISMA petición con la misma llave dentro de 24 h devuelve la respuesta original (mismo messageId) sin encolar otro envío, con la cabecera `Idempotent-Replayed: true`. La llave es por usuario/API key y tenant.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "maxLength": 255,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateHostedLinkDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "`IDEMPOTENCY_KEY_REUSED`: la llave ya se usó con otro cuerpo · `IDEMPOTENCY_IN_PROGRESS`: la petición original sigue en curso (reintentar) · `IDEMPOTENCY_ORIGINAL_FAILED`: la original falló sin confirmar si el mensaje salió; no se reintenta con esa llave (revisar la conversación y, si hace falta, usar una nueva)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "`IDEMPOTENCY_UNAVAILABLE`: con Idempotency-Key, si no se puede garantizar que el envío no se duplique, no se manda nada y se responde 503 con `Retry-After`. Reintentar con la MISMA llave."
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Ligas hospedadas"
        ],
        "x-required-scopes": [
          "hostedlinks:write"
        ]
      }
    },
    "/v1/hosted-links/{id}": {
      "get": {
        "operationId": "HostedLinksController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Ligas hospedadas"
        ],
        "x-required-scopes": [
          "hostedlinks:read"
        ]
      }
    },
    "/v1/hosted-links/{id}/cancel": {
      "post": {
        "operationId": "HostedLinksController_cancel",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Ligas hospedadas"
        ],
        "x-required-scopes": [
          "hostedlinks:write"
        ]
      }
    },
    "/v1/inbox/stream": {
      "get": {
        "description": "Eventos nombrados message.new | message.status | conversation.updated | assignment.changed. Heartbeat `:hb` cada 25s; `retry: 5000` como primer frame. Sin replay: al reconectar, re-consultar el snapshot REST. 429 al exceder el límite de conexiones del tenant.",
        "operationId": "SseController_stream",
        "parameters": [
          {
            "description": "Filtra message.new/message.status a esta conversación.",
            "in": "query",
            "name": "conversationId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Stream SSE de eventos del inbox",
        "tags": [
          "Tiempo real"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      }
    },
    "/v1/inbox/views": {
      "get": {
        "operationId": "SavedViewsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Vistas del equipo y las propias",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:read"
        ]
      },
      "post": {
        "operationId": "SavedViewsController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSavedViewDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Guardar el filtro actual como vista",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/inbox/views/{id}": {
      "delete": {
        "operationId": "SavedViewsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Borrar una vista",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      },
      "patch": {
        "operationId": "SavedViewsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSavedViewDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Renombrar, cambiar filtros o reordenar",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "conversations:write"
        ]
      }
    },
    "/v1/integrations": {
      "get": {
        "operationId": "IntegrationsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar conexiones a CRM (sin credenciales)",
        "tags": [
          "Integraciones"
        ],
        "x-required-scopes": [
          "integrations:read"
        ]
      },
      "post": {
        "description": "La `config.url` se valida contra SSRF (https + puerto 443 + IP pública). Las `credentials` se cifran (cifrado en reposo) y JAMÁS vuelven en un DTO. Requiere el plan con CRM.",
        "operationId": "IntegrationsController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateIntegrationDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Conectar un CRM (Odoo)",
        "tags": [
          "Integraciones"
        ],
        "x-required-scopes": [
          "integrations:write"
        ]
      }
    },
    "/v1/integrations/{id}": {
      "delete": {
        "operationId": "IntegrationsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar conexión",
        "tags": [
          "Integraciones"
        ],
        "x-required-scopes": [
          "integrations:write"
        ]
      },
      "get": {
        "operationId": "IntegrationsController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ficha de una conexión (status/lastCheckAt/lastError)",
        "tags": [
          "Integraciones"
        ],
        "x-required-scopes": [
          "integrations:read"
        ]
      },
      "patch": {
        "operationId": "IntegrationsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIntegrationDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar conexión (config/credenciales/isActive)",
        "tags": [
          "Integraciones"
        ],
        "x-required-scopes": [
          "integrations:write"
        ]
      }
    },
    "/v1/integrations/{id}/test": {
      "post": {
        "description": "Autentica contra el Odoo y confirma acceso a crm.lead. Actualiza status/lastCheckAt.",
        "operationId": "IntegrationsController_test",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Probar la conexión al CRM en vivo",
        "tags": [
          "Integraciones"
        ],
        "x-required-scopes": [
          "integrations:write"
        ]
      }
    },
    "/v1/invoices": {
      "get": {
        "operationId": "InvoicesController_list",
        "parameters": [
          {
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "DRAFT",
                "ISSUING",
                "STAMPED",
                "CANCELED",
                "PENDING_CANCEL",
                "ERROR"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ items: InvoiceDto[] }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Panel de facturas (paginado por fecha)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:read"
        ]
      }
    },
    "/v1/invoices/{id}": {
      "get": {
        "operationId": "InvoicesController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Detalle de una factura",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:read"
        ]
      }
    },
    "/v1/invoices/{id}/cancel": {
      "post": {
        "operationId": "InvoicesController_cancel",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelInvoiceDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cancelar el CFDI ante el SAT (motivo 01-04)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:write"
        ]
      }
    },
    "/v1/invoices/{id}/pdf": {
      "get": {
        "operationId": "InvoicesController_pdf",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Descargar el PDF (representación impresa)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:read"
        ]
      }
    },
    "/v1/invoices/{id}/xml": {
      "get": {
        "operationId": "InvoicesController_xml",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Descargar el XML timbrado (fuente legal)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:read"
        ]
      }
    },
    "/v1/invoicing/connections": {
      "get": {
        "operationId": "InvoiceConnectionsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Conexiones de facturación (enmascaradas)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:read"
        ]
      },
      "post": {
        "operationId": "InvoiceConnectionsController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInvoiceConnectionDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Conectar un PAC (factura.com)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:write"
        ]
      }
    },
    "/v1/invoicing/connections/{id}": {
      "delete": {
        "operationId": "InvoiceConnectionsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar una conexión de facturación",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:write"
        ]
      },
      "get": {
        "operationId": "InvoiceConnectionsController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Detalle de una conexión de facturación",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:read"
        ]
      },
      "patch": {
        "operationId": "InvoiceConnectionsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateInvoiceConnectionDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar una conexión de facturación",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:write"
        ]
      }
    },
    "/v1/invoicing/connections/{id}/test": {
      "post": {
        "operationId": "InvoiceConnectionsController_test",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Probar conexión al PAC (lista series)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:write"
        ]
      }
    },
    "/v1/invoicing/emisor": {
      "get": {
        "operationId": "InvoiceConnectionsController_getEmisor",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Emisor/cuenta fiscal actual del PAC (enmascarado)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:read"
        ]
      }
    },
    "/v1/invoicing/emisor/csd": {
      "post": {
        "operationId": "InvoiceConnectionsController_updateEmisorCsd",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateEmisorCsdDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cargar el CSD (.cer/.key/contraseña) — valida contra el PAC en vivo",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:write"
        ]
      }
    },
    "/v1/invoicing/emisor/fiscal": {
      "put": {
        "operationId": "InvoiceConnectionsController_updateEmisorFiscal",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateEmisorFiscalDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Actualizar datos fiscales del emisor (RazónSocial/RFC/régimen/CP)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:write"
        ]
      }
    },
    "/v1/invoicing/emisor/logo": {
      "post": {
        "operationId": "InvoiceConnectionsController_updateEmisorLogo",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateEmisorLogoDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cargar el logo del emisor (jpg/png)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:write"
        ]
      }
    },
    "/v1/invoicing/series": {
      "get": {
        "operationId": "InvoiceConnectionsController_series",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Series del emisor (para el selector)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:read"
        ]
      },
      "post": {
        "operationId": "InvoiceConnectionsController_createSerie",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSerieDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear una serie nueva (self-serve si el selector viene vacío)",
        "tags": [
          "Facturación CFDI"
        ],
        "x-required-scopes": [
          "invoices:write"
        ]
      }
    },
    "/v1/lead-close-reasons": {
      "get": {
        "description": "Catálogo por negocio, ordenado por `position`. `kind`: LEAD_LOST | LEAD_WON. Los archivados solo con `includeArchived=1`.",
        "operationId": "LeadCloseReasonsController_list",
        "parameters": [
          {
            "in": "query",
            "name": "kind",
            "required": false,
            "schema": {
              "enum": [
                "LEAD_LOST",
                "LEAD_WON"
              ],
              "type": "string"
            }
          },
          {
            "description": "`1` incluye los archivados.",
            "in": "query",
            "name": "includeArchived",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Motivos de cierre del negocio",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "leads:read"
        ]
      },
      "post": {
        "description": "409 CLOSE_REASON_LABEL_TAKEN (mismo rótulo activo en el tipo) · 409 CLOSE_REASON_LIMIT (30 por tipo).",
        "operationId": "LeadCloseReasonsController_create",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear un motivo de cierre",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "pipelines:write"
        ]
      }
    },
    "/v1/lead-close-reasons/{id}": {
      "patch": {
        "description": "Nunca se borra: `archived: true` lo deja de ofrecer, las oportunidades que ya lo usan lo conservan.",
        "operationId": "LeadCloseReasonsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Renombrar, reordenar o archivar un motivo",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "pipelines:write"
        ]
      }
    },
    "/v1/leads": {
      "get": {
        "description": "Misma visibilidad por equipo que `GET /leads/board`: es su paginación.",
        "operationId": "LeadsController_list",
        "parameters": [
          {
            "description": "Filtra por etapa (columna del kanban).",
            "in": "query",
            "name": "stageId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "T33: solo las oportunidades de este embudo.",
            "in": "query",
            "name": "pipelineId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "T33: solo las oportunidades de este contacto.",
            "in": "query",
            "name": "contactId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "T33: estado — `open` (abierta), `won` (ganada) o `lost` (perdida).",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "open",
                "won",
                "lost"
              ],
              "type": "string"
            }
          },
          {
            "description": "`unassigned` | `<userId>`.",
            "in": "query",
            "name": "assignedTo",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Segmenta por tags (ConversationTag): ids separados por coma.",
            "in": "query",
            "name": "tags",
            "required": false,
            "schema": {
              "maxLength": 512,
              "type": "string"
            }
          },
          {
            "description": "Segmenta por actividad de conversación (Conversation.lastMessageAt, default 30d). `recientes` = activos dentro del umbral; `inactivos` = sin actividad más vieja que el umbral.",
            "in": "query",
            "name": "activity",
            "required": false,
            "schema": {
              "enum": [
                "recientes",
                "inactivos"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 30,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor keyset opaco (de `nextCursor`).",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 512,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Lista plana de leads (keyset) — \"cargar más\" de una columna",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "leads:read"
        ]
      },
      "post": {
        "description": "T33: la oportunidad se ancla al CONTACTO (se deduce del hilo con `conversationId`, o `contactId`). Como máximo UNA abierta por contacto y embudo: 409 LEAD_EXISTS_FOR_CONVERSATION (`details.leadId`) si ya hay otra, salvo `allowAdditional: true`. `pipelineId` sin `stageId` usa la 1.ª etapa de ese embudo (409 PIPELINE_ARCHIVED, 400 STAGE_INVALID / CONTACT_INVALID). Deja la línea «{persona} creó la oportunidad en {etapa}» en el hilo (P1-6a). Acepta Idempotency-Key.",
        "operationId": "LeadsController_create",
        "parameters": [
          {
            "description": "Llave única por intento de envío (1-255 caracteres ASCII visibles, p. ej. un UUID). Reenviar la MISMA petición con la misma llave dentro de 24 h devuelve la respuesta original (mismo messageId) sin encolar otro envío, con la cabecera `Idempotent-Replayed: true`. La llave es por usuario/API key y tenant.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "maxLength": 255,
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "`IDEMPOTENCY_KEY_REUSED`: la llave ya se usó con otro cuerpo · `IDEMPOTENCY_IN_PROGRESS`: la petición original sigue en curso (reintentar) · `IDEMPOTENCY_ORIGINAL_FAILED`: la original falló sin confirmar si el mensaje salió; no se reintenta con esa llave (revisar la conversación y, si hace falta, usar una nueva)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "`IDEMPOTENCY_UNAVAILABLE`: con Idempotency-Key, si no se puede garantizar que el envío no se duplique, no se manda nada y se responde 503 con `Retry-After`. Reintentar con la MISMA llave."
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Alta manual de lead",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "leads:write"
        ]
      }
    },
    "/v1/leads/board": {
      "get": {
        "description": "Leads por etapa; LIMIT N por columna + total; tarjetas con speed-to-lead + origen. Aplica la MISMA visibilidad por equipo que la bandeja: un AGENT/VIEWER acotado sólo ve las oportunidades cuyo hilo puede abrir — tarjetas y totales por columna. Las oportunidades sin conversación (alta manual) las ve todo el personal del negocio.",
        "operationId": "LeadsController_board",
        "parameters": [
          {
            "description": "Pipeline a proyectar; si se omite, el default del tenant.",
            "in": "query",
            "name": "pipelineId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Tarjetas por columna (LIMIT N + keyset \"cargar más\" vía GET /v1/leads).",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Segmenta por tags (ConversationTag): ids separados por coma.",
            "in": "query",
            "name": "tags",
            "required": false,
            "schema": {
              "maxLength": 512,
              "type": "string"
            }
          },
          {
            "description": "Segmenta por actividad de conversación (Conversation.lastMessageAt, default 30d).",
            "in": "query",
            "name": "activity",
            "required": false,
            "schema": {
              "enum": [
                "recientes",
                "inactivos"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Tablero kanban (BoardDto)",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "leads:read"
        ]
      }
    },
    "/v1/leads/{id}": {
      "patch": {
        "description": "Posición server-side desde before/afterLeadId (midpoint + rebalanceo inline). Precondición opcional `expectedStageId`/`expectedValueMinor` ⇒ 409 LEAD_CHANGED con `{ lead, lastChange }` si no coincide. La misma etapa sin vecinos no hace nada. Cambiar etapa o valor deja auditoría, línea en el hilo y sync CRM. T33: `closeReasonId` (catálogo `GET /v1/lead-close-reasons`) y `closeNote` (≤ 280) al cerrar; 422 CLOSE_REASON_REQUIRED si el embudo exige motivo de pérdida y falta; 400 CLOSE_REASON_INVALID (archivado, de otro tipo o lead abierto); 409 PIPELINE_ARCHIVED.",
        "operationId": "LeadsController_move",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Mover / asignar / valorar un lead",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "leads:write"
        ]
      }
    },
    "/v1/loyalty/members": {
      "get": {
        "operationId": "LoyaltyController_listMembers",
        "parameters": [
          {
            "in": "query",
            "name": "programId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "q",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Panel de miembros (filtro por programa + búsqueda)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:read"
        ]
      }
    },
    "/v1/loyalty/members/{id}": {
      "get": {
        "operationId": "LoyaltyController_getMember",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Detalle de una membresía + ledger paginado",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:read"
        ]
      }
    },
    "/v1/loyalty/overview": {
      "get": {
        "description": "READ-ONLY: programa/diseño ACTIVO + agregados del club (miembros, pases instalados, bonos, sesiones/puntos/sellos) + actividad de la ventana de 7 días + referencia del QR de alta. NO dispara acciones de ciclo. Gate de pilar `loyalty` (PRO) a nivel de clase.",
        "operationId": "LoyaltyController_getOverview",
        "parameters": [],
        "responses": {
          "200": {
            "description": "LoyaltyOverviewView — { program: {id,name,type,status,rules,redeemOptions,cardDesign} | null, stats: {membersCount, passesInstalled, activeBonos, sessionsOutstanding, pointsOutstanding, stampsOutstanding}, recentActivity: {windowDays, pointsEarned, stampsEarned, redeemsCount, passesInstalledDelta} | null (null si no hay programa ACTIVO; ceros si no hubo actividad), enrollQr: {joinToken, landingUrl, qrSvgDataUri} | null }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Vista agregada read-first de \"Lealtad\"",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:read"
        ]
      }
    },
    "/v1/loyalty/package-types": {
      "get": {
        "operationId": "PackageTypesController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar las plantillas de bono del tenant",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:read"
        ]
      },
      "post": {
        "operationId": "PackageTypesController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePackageTypeDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear una plantilla de bono (N sesiones prepagadas de un servicio)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/loyalty/package-types/{id}": {
      "patch": {
        "operationId": "PackageTypesController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePackageTypeDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar name/precio/vigencia/estado (sessionCount y servicio son inmutables)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/loyalty/programs": {
      "get": {
        "operationId": "LoyaltyController_listPrograms",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar los programas del tenant",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:read"
        ]
      },
      "post": {
        "operationId": "LoyaltyController_createProgram",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProgramDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear un programa de lealtad (rules validadas por Zod)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/loyalty/programs/{id}": {
      "get": {
        "operationId": "LoyaltyController_getProgram",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Detalle de un programa",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:read"
        ]
      },
      "patch": {
        "operationId": "LoyaltyController_updateProgram",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProgramDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar reglas / recompensas / triggers / estado de un programa",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/loyalty/wallet-passes": {
      "post": {
        "operationId": "WalletPassController_issue",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IssueWalletPassDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Emitir/re-emitir el pase de Wallet de un sujeto (member o package) — idempotente",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:write"
        ]
      }
    },
    "/v1/loyalty/wallet-passes/{id}": {
      "get": {
        "operationId": "WalletPassController_getPass",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Estado de un pase de Wallet (sin material cripto)",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:read"
        ]
      }
    },
    "/v1/macros": {
      "get": {
        "operationId": "MacrosController_list",
        "parameters": [
          {
            "in": "query",
            "name": "active",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar las macros del tenant (active=1 → solo activas, para el picker)",
        "tags": [
          "Respuestas guardadas"
        ],
        "x-required-scopes": [
          "macros:read"
        ]
      },
      "post": {
        "description": "El atajo se normaliza server-side (minúsculas + slug) y es único por tenant. El body es una plantilla con variables {{contacto.nombre}} / {{agente.nombre}} / {{negocio.nombre}}.",
        "operationId": "MacrosController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMacroDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear una macro",
        "tags": [
          "Respuestas guardadas"
        ],
        "x-required-scopes": [
          "macros:write"
        ]
      }
    },
    "/v1/macros/variables": {
      "get": {
        "operationId": "MacrosController_variables",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Catálogo de variables soportadas en el cuerpo de una macro",
        "tags": [
          "Respuestas guardadas"
        ],
        "x-required-scopes": [
          "macros:read"
        ]
      }
    },
    "/v1/macros/{id}": {
      "delete": {
        "operationId": "MacrosController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Borrar una macro (para solo pausar: PATCH active=false)",
        "tags": [
          "Respuestas guardadas"
        ],
        "x-required-scopes": [
          "macros:write"
        ]
      },
      "get": {
        "operationId": "MacrosController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Detalle de una macro",
        "tags": [
          "Respuestas guardadas"
        ],
        "x-required-scopes": [
          "macros:read"
        ]
      },
      "patch": {
        "operationId": "MacrosController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMacroDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar una macro (título/atajo/cuerpo/categoría/activar)",
        "tags": [
          "Respuestas guardadas"
        ],
        "x-required-scopes": [
          "macros:write"
        ]
      }
    },
    "/v1/media/upload": {
      "post": {
        "description": "Sube la imagen o el documento a Meta y devuelve su `mediaId`, que se manda luego en POST /v1/conversations/:id/messages con kind=image|document|audio|video. El archivo NO se guarda de nuestro lado: duplicar PII del cliente sin necesidad. Límites: imagen 5 MB, documento/video/audio 16 MB. El contenido se verifica por firma (magic bytes) contra el MIME declarado: un \".mp4\" que no es video es 400 MEDIA_CONTENT_MISMATCH. Nota de voz: máximo 2 min 30 s (el excedente se recorta). SIN conversación: manda `phoneNumberId` (id interno del número de envío) en lugar de `conversationId`; solo kind=image|video|document con los límites del encabezado de plantilla (JPEG/PNG 5 MB, MP4/3GPP 16 MB, PDF 16 MB). Exactamente uno de los dos. El `mediaId` solo sirve para enviar desde ese número y caduca en 30 días (`expiresAt`). Cuota: 20 por minuto por persona o llave (429 MEDIA_UPLOAD_RATE_LIMITED con `retryAfter`). INSTAGRAM/MESSENGER (P1-7, detrás de la bandera de plataforma `messenger.mediaOut.enabled`; apagada ⇒ 422 CHANNEL_MEDIA_DISABLED): el archivo se guarda en Kontesta (Meta lo descarga por una liga firmada de 15 min al enviar) y `mediaId` es una referencia firmada `kmo1.…` atada a ESA conversación y a ese `kind`, vigente 24 h (`expiresAt`), con `phoneNumberId: null` y `channel`. Límites: imagen JPG/PNG 8 MB; video MP4 16 MB; audio (cualquier grabación) 16 MB, se convierte a M4A; documento: Instagram solo PDF, Messenger PDF/Office/txt/csv, 16 MB. Instagram y Messenger no admiten pie de foto: el texto va en un mensaje aparte.",
        "operationId": "MediaController_upload",
        "parameters": [],
        "responses": {
          "200": {
            "description": "{ mediaId, filename, mimeType, size, phoneNumberId, expiresAt, channel? } (IG/FB: phoneNumberId null, channel)",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Subir un archivo para enviarlo por WhatsApp",
        "tags": [
          "Archivos"
        ],
        "x-required-scopes": [
          "messages:send"
        ]
      }
    },
    "/v1/media/{mediaBlobId}": {
      "get": {
        "description": "Stream tenant-scoped de los bytes de un MediaBlob STORED. 404 si el blob no es del tenant, no existe, o aún no tiene bytes (PENDING/FAILED). Cache-Control: private (PII; nunca URL pública).",
        "operationId": "MediaController_serve",
        "parameters": [
          {
            "in": "path",
            "name": "mediaBlobId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bytes de la imagen (tenant-scoped, Cache-Control: private).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Serve autenticado de una imagen durable (foto de servicio)",
        "tags": [
          "Archivos"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/messages/{id}/media": {
      "get": {
        "description": "WhatsApp: materializa el binario desde Meta si aún no está en el store y devuelve el mediaBlobId. Idempotente: reutiliza los bytes si ya existen. No archiva la foto en la ficha. Instagram/Messenger: los bytes se guardan AL RECIBIR el mensaje; aquí solo se lee su estado (STORED con mediaBlobId, PENDING mientras se descargan, FAILED si Meta ya no los entregó: mediaBlobId null).",
        "operationId": "MessageMediaController_resolve",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "mediaBlobId": {
                      "nullable": true,
                      "type": "string"
                    },
                    "status": {
                      "enum": [
                        "PENDING",
                        "STORED",
                        "FAILED"
                      ],
                      "type": "string"
                    }
                  }
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Resuelve los bytes de un mensaje con archivo para mostrarlo",
        "tags": [
          "Fotos de servicio"
        ],
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/messaging/send": {
      "post": {
        "operationId": "MessagingController_send",
        "parameters": [
          {
            "description": "Llave única por intento de envío (1-255 caracteres ASCII visibles, p. ej. un UUID). Reenviar la MISMA petición con la misma llave dentro de 24 h devuelve la respuesta original (mismo messageId) sin encolar otro envío, con la cabecera `Idempotent-Replayed: true`. La llave es por usuario/API key y tenant.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "maxLength": 255,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendMessageDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "`IDEMPOTENCY_KEY_REUSED`: la llave ya se usó con otro cuerpo · `IDEMPOTENCY_IN_PROGRESS`: la petición original sigue en curso (reintentar) · `IDEMPOTENCY_ORIGINAL_FAILED`: la original falló sin confirmar si el mensaje salió; no se reintenta con esa llave (revisar la conversación y, si hace falta, usar una nueva)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "`IDEMPOTENCY_UNAVAILABLE`: con Idempotency-Key, si no se puede garantizar que el envío no se duplique, no se manda nada y se responde 503 con `Retry-After`. Reintentar con la MISMA llave."
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Iniciar conversación"
        ],
        "x-required-scopes": [
          "messages:send"
        ]
      }
    },
    "/v1/metrics/speed-to-lead": {
      "get": {
        "description": "Población = firstInboundAt ∈ [from, to) en tz (to EXCLUSIVO). pctUnder5m usa el TOTAL como denominador (las no respondidas cuentan en contra). include=raw devuelve las filas base (cap 10k) para verificar el cálculo. `byChannel` desglosa por whatsapp | instagram | messenger (siempre las tres claves); `?channel=` acota la población. p50Sec/p90Sec/pctUnder5m son EN HORARIO HÁBIL (descuentan el fuera de horario del tenant según Ajustes › Horario; sin horario configurado = bruto); p50RawSec/p90RawSec/pctUnder5mRaw es el bruto de reloj de pared. `businessHours.configured` dice si ambos pueden diferir. Las conversaciones que NACIERON con el agente de Meta (`firstInboundOwner = META_AGENT`) quedan FUERA de la cifra principal y se reportan aparte en `metaAgent`; el bloque sale con la bandera encendida y también con ella apagada si hubo excluidas (ahí `live:false` = solo el conteo). N4: `byAgent`, `agentRanking` y `raw` se RECORTAN por rol EN EL SERVIDOR — OWNER/ADMIN (y las llaves de API, que pasan por el mismo portón con su rol) reciben todo; un AGENT o un VIEWER reciben SOLO lo propio, y NO viaja ninguna referencia de equipo: su referencia es `goalMin`, que no habla de nadie. `rankingScope` dice cuál de los dos llegó. El recorte no es un parámetro: sale del rol de la sesión.",
        "operationId": "MetricsController_speedToLead",
        "parameters": [
          {
            "description": "Inicio (inclusive), YYYY-MM-DD en tz.",
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "example": "2026-07-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Fin (EXCLUSIVO), YYYY-MM-DD en tz.",
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "example": "2026-07-09",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "tz",
            "required": false,
            "schema": {
              "default": "America/Mexico_City",
              "maxLength": 64,
              "pattern": "/^[A-Za-z0-9_+\\-/]+$/",
              "type": "string"
            }
          },
          {
            "description": "Include=raw ⇒ filas base (cap 10k) para verificar el cálculo.",
            "in": "query",
            "name": "include",
            "required": false,
            "schema": {
              "enum": [
                "raw"
              ],
              "type": "string"
            }
          },
          {
            "description": "Acota TODA la población al canal (`ChannelKey`: whatsapp | instagram | messenger). Sin él, el reporte mezcla canales y `byChannel` los desglosa.",
            "in": "query",
            "name": "channel",
            "required": false,
            "schema": {
              "enum": [
                "whatsapp",
                "instagram",
                "messenger"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SpeedToLeadReportDto"
                }
              }
            },
            "description": "SpeedToLeadReport (businessHours + summary + byDay + byAgent + byResponder + byChannel [+ raw] [+ metaAgent]).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reporte speed-to-lead (p50/p90/%<5min)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "metrics:read"
        ]
      }
    },
    "/v1/packages/overview": {
      "get": {
        "description": "READ-ONLY: cifra principal \"Vendido\" (Σ precio de los bonos vendidos/pagados en la ventana FIJA del mes en curso, tz) + estado del monedero de sesiones (bonos ACTIVE + sesiones por consumir) + señales de RECOMPRA (agotados / saldo bajo / caducan pronto) + actividad 7d + estado de configuración (plantillas de bono). NO dispara acciones de ciclo. Gate de pilar `session_packs` (PRO) a nivel de clase.",
        "operationId": "PackagesOverviewController_getOverview",
        "parameters": [],
        "responses": {
          "200": {
            "description": "PackagesOverviewView — { config: {activeTypesCount, totalTypesCount} | null (null si no hay plantillas ACTIVAS), period: {from, to, tz, label}, stats: {currency, mixedCurrency, soldMinor, soldCount, activePackages, sessionsOutstanding}, recompra: {exhausted, lowBalance, expiringSoon}, recentActivity: {windowDays, packagesSold, sessionsRedeemed} | null (null si config === null) }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Vista agregada read-first de \"Bonos\"",
        "tags": [
          "Lealtad"
        ],
        "x-required-scopes": [
          "loyalty:read"
        ]
      }
    },
    "/v1/payment-accounts": {
      "get": {
        "operationId": "PaymentAccountsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "description": "PaymentAccountDto[].",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cuentas de cobro conectadas (enmascaradas)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:read"
        ]
      },
      "post": {
        "operationId": "PaymentAccountsController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentAccountDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "PaymentAccountDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Conectar una cuenta de pasarela (modo API_KEY)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:write"
        ]
      }
    },
    "/v1/payment-accounts/{id}": {
      "delete": {
        "operationId": "PaymentAccountsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar una cuenta de cobro (409 si tiene ligas de pago asociadas)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:write"
        ]
      },
      "patch": {
        "operationId": "PaymentAccountsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePaymentAccountDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "PaymentAccountDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar cuenta (default/activa/nombre/rotar secretos)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:write"
        ]
      }
    },
    "/v1/payment-accounts/{id}/test": {
      "post": {
        "operationId": "PaymentAccountsController_test",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PaymentAccountTestResult — { status, ok, detail?, environment, liveMode, modeMismatch }. `modeMismatch` = la credencial parece de otro ambiente que el configurado (aviso, no bloqueo).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Probar la conexión con la pasarela (flip CONNECTED↔ERROR verificado + liveMode vs ambiente)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:write"
        ]
      }
    },
    "/v1/payment-accounts/{id}/test-charge": {
      "post": {
        "description": "Crea una liga/orden marcada `isTest` con ESTA cuenta y devuelve `checkoutUrl` + `paymentLinkId`. NO se envía a ningún WhatsApp ni contacto; reportes/overview/Odoo la excluyen. Al pagar con tarjeta de prueba, el webhook la lleva a PAID como cualquier otra liga. 409 `ACCOUNT_NOT_IN_TEST_MODE` si la cuenta está en producción; 400 `CONFIG_ERROR` si no está activa/CONNECTED; 429 al exceder 5/min.",
        "operationId": "PaymentAccountsController_testCharge",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "PaymentAccountTestChargeResult — { paymentLinkId, checkoutUrl, amountMinor, currency, status }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "ACCOUNT_NOT_IN_TEST_MODE — la cuenta está en producción."
          },
          "429": {
            "description": "RATE_LIMITED — 5 por minuto por usuario."
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear un «Cobro de prueba de Kontesta» de $10.00 MXN con esta cuenta (solo modo pruebas)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:write"
        ]
      }
    },
    "/v1/payment-accounts/{id}/webhook-url": {
      "get": {
        "operationId": "PaymentAccountsController_getWebhookUrl",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ url }",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "URL pública del webhook a registrar en la pasarela",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:read"
        ]
      }
    },
    "/v1/payment-accounts/{id}/webhook/test": {
      "post": {
        "description": "Kontesta firma un evento tema `order` con el secreto de webhook de ESTA cuenta y lo envía a su URL pública `…/webhooks/payment-links/mercadopago/<routingToken>` — misma ruta y verificador que un webhook real. Si responde 200 con firma válida, `webhookVerifiedAt` queda fijado (una sola vez). 409 `ACCOUNT_NOT_IN_TEST_MODE` si la cuenta está en producción; 400 `NOT_IMPLEMENTED` si no es Mercado Pago; 429 al exceder 5/min.",
        "operationId": "PaymentAccountsController_webhookTest",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PaymentAccountWebhookTestResult — { ok, httpStatus, webhookVerifiedAt, detail? }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "ACCOUNT_NOT_IN_TEST_MODE — la cuenta está en producción."
          },
          "429": {
            "description": "RATE_LIMITED — 5 por minuto por usuario."
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Disparar un webhook SINTÉTICO firmado contra la propia URL pública (solo modo pruebas, Mercado Pago)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:write"
        ]
      }
    },
    "/v1/payment-links": {
      "get": {
        "operationId": "PaymentLinksController_list",
        "parameters": [
          {
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "CREATED",
                "SENT",
                "PENDING",
                "PAID",
                "EXPIRED",
                "CANCELED",
                "FAILED",
                "REFUNDED"
              ],
              "type": "string"
            }
          },
          {
            "description": "Desde (ISO) por createdAt.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Hasta (ISO, exclusivo) por createdAt.",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Máximo de filas (default 50, tope 200).",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 200,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ items: PaymentLinkDto[] }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Panel de cobros (filtrable por estado/fecha)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:read"
        ]
      }
    },
    "/v1/payment-links/{id}": {
      "get": {
        "operationId": "PaymentLinksController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PaymentLinkDto.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Detalle + estado de una liga",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:read"
        ]
      }
    },
    "/v1/payment-links/{id}/cancel": {
      "post": {
        "operationId": "PaymentLinksController_cancel",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PaymentLinkDto (status CANCELED).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cancelar / expirar una liga",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:write"
        ]
      }
    },
    "/v1/payments/overview": {
      "get": {
        "description": "READ-ONLY: cifra principal \"Cobrado\" (Σ paidAmountMinor de PAID en 30d) + tasa de conversión de cobro (linksPaid/linksSent) + bloque anticipos (proxy GO/KILL ≥30%, appointmentId != null) + dinero \"en la calle\" + actividad 7d + estado de configuración de la pasarela. NO dispara acciones de ciclo.",
        "operationId": "PaymentsOverviewController_getOverview",
        "parameters": [],
        "responses": {
          "200": {
            "description": "PaymentsOverviewView.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Vista agregada read-first de \"Cobros\"",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:read"
        ]
      }
    },
    "/v1/payments/settings": {
      "get": {
        "operationId": "PaymentsOverviewController_getSettings",
        "parameters": [],
        "responses": {
          "200": {
            "description": "PaymentSettingsView — { notifyOnPaid, primaryPaymentProvider }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Config del plug de Cobros a nivel tenant (notifyOnPaid + primaryPaymentProvider)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:read"
        ]
      },
      "patch": {
        "description": "PATCH: `notifyOnPaid` (PAY-5) y/o `primaryPaymentProvider` (PAY-6). Fijar principal exige una pasarela CONNECTED del tenant (400 PROVIDER_NOT_CONNECTED); `null` la limpia. Campo ausente = no se toca.",
        "operationId": "PaymentsOverviewController_updateSettings",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePaymentSettingsDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "PaymentSettingsView — { notifyOnPaid, primaryPaymentProvider }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Actualizar la config del plug (avisar al cobrar + pasarela principal)",
        "tags": [
          "Cobros"
        ],
        "x-required-scopes": [
          "payments:write"
        ]
      }
    },
    "/v1/pipelines": {
      "get": {
        "operationId": "PipelinesController_list",
        "parameters": [
          {
            "description": "`1` o `true` incluye los embudos archivados.",
            "in": "query",
            "name": "includeArchived",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Pipelines + etapas del tenant (asegura el default)",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "pipelines:read"
        ]
      },
      "post": {
        "operationId": "PipelinesController_create",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear embudo (R10-1)",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "pipelines:write"
        ]
      }
    },
    "/v1/pipelines/{id}": {
      "patch": {
        "operationId": "PipelinesController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Renombrar / reordenar / marcar como predeterminado un embudo",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "pipelines:write"
        ]
      }
    },
    "/v1/pipelines/{id}/archive": {
      "post": {
        "description": "Nunca se borra. El predeterminado no se archiva (409 `PIPELINE_IS_DEFAULT`). Con oportunidades abiertas exige `moveOpenTo` (etapa de otro embudo activo); sin él, 409 `PIPELINE_HAS_OPEN_LEADS`.",
        "operationId": "PipelinesController_archive",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Archivar embudo",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "pipelines:write"
        ]
      }
    },
    "/v1/pipelines/{id}/stages": {
      "post": {
        "operationId": "PipelinesController_createStage",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Agregar columna a un pipeline",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "pipelines:write"
        ]
      }
    },
    "/v1/pipelines/{id}/unarchive": {
      "post": {
        "operationId": "PipelinesController_unarchive",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reactivar un embudo archivado",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "pipelines:write"
        ]
      }
    },
    "/v1/rag/catalog": {
      "get": {
        "operationId": "RagController_catalogState",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Catálogo como conocimiento: estado (prendido, productos activos, fuentes)",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:read"
        ]
      },
      "put": {
        "description": "Los precios y la existencia nunca entran al conocimiento: el asistente los consulta en vivo. Prender con el catálogo vacío ⇒ 422 CATALOG_EMPTY. Apagar borra las fuentes de catálogo. Solo sesión de persona.",
        "operationId": "RagController_setCatalog",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Prender o apagar el catálogo como conocimiento (una fuente por producto, SIN precios)",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:write"
        ]
      }
    },
    "/v1/rag/catalog/sync": {
      "post": {
        "operationId": "RagController_syncCatalog",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Sincronizar ahora el catálogo con el conocimiento (cupo 6 por hora)",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:write"
        ]
      }
    },
    "/v1/rag/engine": {
      "get": {
        "operationId": "RagController_engineStatus",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RagEngineStatusDto"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Motor de conocimiento de la cuenta y, con el motor externo, estado/uso/cuotas en el motor",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:read"
        ]
      }
    },
    "/v1/rag/engine/provision": {
      "post": {
        "operationId": "RagController_provisionEngine",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Provisiona (idempotente) la cuenta en el motor de la base de conocimiento — backfill de cuentas previas a la bandera",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:write"
        ]
      }
    },
    "/v1/rag/search": {
      "post": {
        "operationId": "RagController_search",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Búsqueda en el KB: híbrida (semántica + por palabras, fusión RRF), top-k — debug/preview + consumo interno",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:read"
        ]
      }
    },
    "/v1/rag/sources": {
      "get": {
        "operationId": "RagController_listSources",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar fuentes del KB (estado/chunkCount)",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:read"
        ]
      },
      "post": {
        "operationId": "RagController_createSource",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Alta de fuente del KB (texto FAQ/CATALOG/MANUAL o URL) → ingesta asíncrona",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:write"
        ]
      }
    },
    "/v1/rag/sources/file": {
      "post": {
        "description": "El tipo se valida por la firma del archivo, no por la extensión (415 FILE_TYPE_UNSUPPORTED); más de 10 MB ⇒ 413 FILE_TOO_LARGE. El archivo se guarda solo mientras se extrae su texto y luego se borra; queda el texto. Un PDF escaneado (sin texto) termina FAILED con el aviso. Campos multipart: `file` (obligatorio) y `title` (opcional, ≤ 200). Solo sesión de persona (API key ⇒ 403 USER_SESSION_REQUIRED). Cupo: 30 por hora por negocio.",
        "operationId": "RagController_createFileSource",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Subir un archivo al conocimiento (PDF, DOCX, XLSX o CSV, máx. 10 MB) → ingesta asíncrona",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:write"
        ]
      }
    },
    "/v1/rag/sources/reprocess-stale": {
      "post": {
        "description": "Vuelve a trocear y embeber las fuentes marcadas `chunkerStale` (el troceo anterior se saltaba renglones sin punto final). Usa la llave de IA del negocio; nunca corre solo. Devuelve `{ queued }`. Solo sesión de persona; cupo 3 por hora.",
        "operationId": "RagController_reprocessStale",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reprocesar de un clic las fuentes leídas con el troceo anterior",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:write"
        ]
      }
    },
    "/v1/rag/sources/site": {
      "post": {
        "description": "Lee robots.txt, el sitemap.xml o, si no hay, los enlaces de la portada; solo páginas del mismo dominio y por https. Cada página queda como fuente propia con su estado. 422 URL_NOT_PUBLIC · 409 SITE_ALREADY_ADDED (para releerlo usa reingest sobre la raíz). Solo sesión de persona. Cupo: 10 por hora por negocio.",
        "operationId": "RagController_createSiteSource",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Agregar el sitio web del negocio al conocimiento (hasta 30 páginas del mismo dominio)",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:write"
        ]
      }
    },
    "/v1/rag/sources/{id}": {
      "delete": {
        "operationId": "RagController_removeSource",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Baja de una fuente del KB (borra sus chunks en cascada)",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:write"
        ]
      },
      "get": {
        "operationId": "RagController_getSource",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Detalle de una fuente del KB",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:read"
        ]
      }
    },
    "/v1/rag/sources/{id}/reingest": {
      "post": {
        "operationId": "RagController_reingestSource",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reingerir una fuente (vuelve a PENDING y reencola)",
        "tags": [
          "Base de conocimiento"
        ],
        "x-required-scopes": [
          "rag:write"
        ]
      }
    },
    "/v1/reports/appointments": {
      "get": {
        "description": "Rango [from, to) en tz (to EXCLUSIVO, cap 92 días). Ocupación = horas reservadas (todo estado menos CANCELLED; NO_SHOW ocupa) / horas disponibles configuradas (null si no hay disponibilidad). noShowRate = NO_SHOW/(COMPLETED+NO_SHOW). Ingresos SIN doble conteo: cobrado (PaymentLink PAID), cubiertoPorBono APARTE, pendiente (TOTAL sin pagar). Filtros resourceId/appointmentTypeId acotan todo. Solo OWNER/ADMIN (lleva ingreso por profesional): una sesión AGENT/VIEWER recibe 403.",
        "operationId": "ReportsController_appointments",
        "parameters": [
          {
            "description": "Inicio (inclusive), YYYY-MM-DD en tz.",
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "example": "2026-07-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Fin (EXCLUSIVO), YYYY-MM-DD en tz.",
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "example": "2026-07-08",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "tz",
            "required": false,
            "schema": {
              "default": "America/Mexico_City",
              "type": "string"
            }
          },
          {
            "description": "Recurso (staff/sala) específico — acota todo el reporte.",
            "in": "query",
            "name": "resourceId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Tipo de servicio específico — acota todo el reporte.",
            "in": "query",
            "name": "appointmentTypeId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "AppointmentReportDto (summary + revenue + byResource + byType + byDay).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reporte de citas (ocupación · no-show · ingresos · rankings)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "appointments:read"
        ]
      }
    },
    "/v1/reports/commissions": {
      "get": {
        "description": "Rango [from, to) en tz (to EXCLUSIVO, cap 92 días), atribuido por earnedAt. summary (accrual/reversal/net + conciliación cobrado-vs-pendiente) + byUser + byResource + byType + byDay. Filtros resourceId/userId/sourceType. Nómina-por-persona: scope PROPIO commissions:read + OWNER/ADMIN.",
        "operationId": "ReportsController_commissions",
        "parameters": [
          {
            "description": "Inicio (inclusive), YYYY-MM-DD en tz.",
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "example": "2026-07-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Fin (EXCLUSIVO), YYYY-MM-DD en tz.",
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "example": "2026-07-08",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "tz",
            "required": false,
            "schema": {
              "default": "America/Mexico_City",
              "type": "string"
            }
          },
          {
            "description": "Recurso (profesional) específico — acota todo el reporte.",
            "in": "query",
            "name": "resourceId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Usuario (earner/profesional) específico — acota todo el reporte.",
            "in": "query",
            "name": "userId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Filtra por origen del devengo.",
            "in": "query",
            "name": "sourceType",
            "required": false,
            "schema": {
              "enum": [
                "APPOINTMENT",
                "PACKAGE_SALE"
              ],
              "type": "string"
            }
          },
          {
            "description": "Capturas tardías del personal (citas que se registraron cuando ya habían empezado): `include` (default) las cuenta, `exclude` las quita del reporte y `only` deja solo esas.",
            "in": "query",
            "name": "staffCapture",
            "required": false,
            "schema": {
              "default": "include",
              "enum": [
                "include",
                "exclude",
                "only"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "CommissionReportDto (summary + byUser + byResource + byType + byDay).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Reporte de comisiones devengadas (por profesional · nómina)",
        "tags": [
          "Citas y agenda"
        ],
        "x-required-scopes": [
          "commissions:read"
        ]
      }
    },
    "/v1/reports/pipeline-conversion": {
      "get": {
        "description": "Cohorte = leads del embudo creados en el rango. «Alcanzó la etapa S» = hubo un evento hacia S o hacia una etapa de mayor posición que no sea LOST (saltarse columnas cuenta como pasar). `outcome` trae ganadas, perdidas, abiertas, valor ganado por moneda, mediana de días a ganar y perdidas por motivo. `approximateBefore`/`approximateLeads` marcan la historia reconstruida de antes de la migración (aproximada). Recorte por rol EN EL SERVIDOR (sin parámetro): OWNER/ADMIN y llave de API reciben todo; un AGENT o VIEWER recibe SOLO sus oportunidades (asignadas o cerradas por él) dentro de su visibilidad por equipo, sin filas de compañeros ni mediana de equipo. `scope` dice cuál llegó. Rango [from, to) con `to` EXCLUSIVO, tope de 92 días, en la zona horaria del negocio.",
        "operationId": "PipelineReportsController_conversion",
        "parameters": [
          {
            "description": "Inicio (inclusive), YYYY-MM-DD en la zona del negocio.",
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "example": "2026-10-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Fin (EXCLUSIVO), YYYY-MM-DD en la zona del negocio.",
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "example": "2026-11-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Embudo (id del pipeline). Un id ajeno o inexistente da 404.",
            "in": "query",
            "name": "pipelineId",
            "required": true,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Conversión por etapa de un embudo",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "metrics:read"
        ]
      }
    },
    "/v1/reports/pipeline-stage-time": {
      "get": {
        "description": "Estancias TERMINADAS (el lead ya salió de la etapa) cuyo inicio cae en el rango: promedio y mediana en segundos, y cuántas son aproximadas (historia reconstruida). Las estancias abiertas no se promedian. Recorte por rol EN EL SERVIDOR (sin parámetro): OWNER/ADMIN y llave de API reciben todo; un AGENT o VIEWER recibe SOLO sus oportunidades (asignadas o cerradas por él) dentro de su visibilidad por equipo, sin filas de compañeros ni mediana de equipo. `scope` dice cuál llegó. Rango [from, to) con `to` EXCLUSIVO, tope de 92 días, en la zona horaria del negocio.",
        "operationId": "PipelineReportsController_stageTime",
        "parameters": [
          {
            "description": "Inicio (inclusive), YYYY-MM-DD en la zona del negocio.",
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "example": "2026-10-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Fin (EXCLUSIVO), YYYY-MM-DD en la zona del negocio.",
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "example": "2026-11-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Embudo (id del pipeline). Un id ajeno o inexistente da 404.",
            "in": "query",
            "name": "pipelineId",
            "required": true,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Tiempo promedio en cada etapa de un embudo",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "metrics:read"
        ]
      }
    },
    "/v1/reports/pipeline-won": {
      "get": {
        "description": "Atribución = `closedAt` en el rango con la oportunidad en Ganada; el agente es el responsable AL CERRAR (`closedAssignedToId`: reasignar después no reescribe la historia). `groupBy` = agent | pipeline | day | week | month. El valor es el `valueMinor` actual; las monedas no se mezclan. Recorte por rol EN EL SERVIDOR (sin parámetro): OWNER/ADMIN y llave de API reciben todo; un AGENT o VIEWER recibe SOLO sus oportunidades (asignadas o cerradas por él) dentro de su visibilidad por equipo, sin filas de compañeros ni mediana de equipo. `scope` dice cuál llegó. Rango [from, to) con `to` EXCLUSIVO, tope de 92 días, en la zona horaria del negocio.",
        "operationId": "PipelineReportsController_won",
        "parameters": [
          {
            "description": "Inicio (inclusive), YYYY-MM-DD en la zona del negocio.",
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "example": "2026-10-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Fin (EXCLUSIVO), YYYY-MM-DD en la zona del negocio.",
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "example": "2026-11-01",
              "pattern": "/^\\d{4}-\\d{2}-\\d{2}$/",
              "type": "string"
            }
          },
          {
            "description": "Acota a un embudo; sin él, todos los embudos.",
            "in": "query",
            "name": "pipelineId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "groupBy",
            "required": false,
            "schema": {
              "default": "agent",
              "enum": [
                "agent",
                "pipeline",
                "day",
                "week",
                "month"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Valor ganado por agente, embudo o periodo",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "metrics:read"
        ]
      }
    },
    "/v1/reputation/callback/{kind}": {
      "post": {
        "description": "El `state` se valida single-use + TTL ANTES de intercambiar el code. El egress al proveedor va por hosts FIJOS (host-pin). Devuelve la conexión SIN credenciales.",
        "operationId": "ReputationController_callback",
        "parameters": [
          {
            "in": "path",
            "name": "kind",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectReputationDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Callback OAuth (POST-back del front): intercambia code, cifra tokens y conecta",
        "tags": [
          "Reputación"
        ],
        "x-required-scopes": [
          "reputation:write"
        ]
      }
    },
    "/v1/reputation/connect/{kind}": {
      "get": {
        "description": "El front conduce el popup OAuth y hace POST-back de code+state al callback.",
        "operationId": "ReputationController_connect",
        "parameters": [
          {
            "in": "path",
            "name": "kind",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Iniciar conexión OAuth (GBP/FB): devuelve la URL de consentimiento + state",
        "tags": [
          "Reputación"
        ],
        "x-required-scopes": [
          "reputation:read"
        ]
      }
    },
    "/v1/reputation/connections": {
      "get": {
        "operationId": "ReputationController_listConnections",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar conexiones de reputación (GBP/FB) — sin credenciales",
        "tags": [
          "Reputación"
        ],
        "x-required-scopes": [
          "reputation:read"
        ]
      }
    },
    "/v1/reputation/connections/{id}": {
      "delete": {
        "operationId": "ReputationController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar una conexión de reputación",
        "tags": [
          "Reputación"
        ],
        "x-required-scopes": [
          "reputation:write"
        ]
      }
    },
    "/v1/reputation/overview": {
      "get": {
        "operationId": "ReputationController_overview",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cifra principal (avg global + conteo) + split por plataforma",
        "tags": [
          "Reputación"
        ],
        "x-required-scopes": [
          "reputation:read"
        ]
      }
    },
    "/v1/reputation/reviews": {
      "get": {
        "operationId": "ReputationController_listReviews",
        "parameters": [
          {
            "in": "query",
            "name": "platform",
            "required": false,
            "schema": {
              "enum": [
                "GBP",
                "FACEBOOK"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "rating",
            "required": false,
            "schema": {
              "maximum": 5,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 200,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar reseñas (keyset publishedAt,id; filtro plataforma/rating)",
        "tags": [
          "Reputación"
        ],
        "x-required-scopes": [
          "reputation:read"
        ]
      }
    },
    "/v1/reputation/reviews/{id}": {
      "patch": {
        "operationId": "ReputationController_moderate",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModerateReviewDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Moderar una reseña (ocultar/mostrar en el widget)",
        "tags": [
          "Reputación"
        ],
        "x-required-scopes": [
          "reputation:write"
        ]
      }
    },
    "/v1/reputation/reviews/{id}/reply": {
      "post": {
        "operationId": "ReputationController_reply",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReplyReviewDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Responder una reseña (GBP; FB no soportado en v1)",
        "tags": [
          "Reputación"
        ],
        "x-required-scopes": [
          "reputation:write"
        ]
      }
    },
    "/v1/reputation/sync": {
      "post": {
        "operationId": "ReputationController_sync",
        "parameters": [],
        "responses": {
          "202": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Forzar la ingesta de reseñas (REVIEW_SYNC) de las conexiones activas",
        "tags": [
          "Reputación"
        ],
        "x-required-scopes": [
          "reputation:write"
        ]
      }
    },
    "/v1/stages/{id}": {
      "delete": {
        "description": "Exige columna vacía o `?moveTo=<stageId>` (mueve los leads antes de borrar). Cada lead movido deja auditoría, una línea en su hilo, el webhook `lead.stage_changed` y el sync al CRM (en tandas). NO dispara las automatizaciones «cambió de etapa» (`LEAD_STAGE_CHANGED`): reorganizar el embudo no manda mensajes a los clientes. Cupo: 10 bajas cada 10 minutos por negocio (429 + Retry-After). Más de 5,000 oportunidades en la etapa ⇒ 409 `STAGE_TOO_LARGE` sin mover nada (muévelas primero a otra etapa).",
        "operationId": "StagesController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "moveTo",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar columna",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "pipelines:write"
        ]
      },
      "patch": {
        "operationId": "StagesController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar columna (renombrar / recategorizar / reordenar)",
        "tags": [
          "Leads y embudos"
        ],
        "x-required-scopes": [
          "pipelines:write"
        ]
      }
    },
    "/v1/tags": {
      "get": {
        "operationId": "TagsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar etiquetas del tenant",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "tags:read"
        ]
      },
      "post": {
        "description": "Color: hex validado server-side. Permiso `conversations.tags` (OWNER/ADMIN/AGENT): quien etiqueta en la bandeja puede crear la etiqueta que falta. Borrar sigue siendo de OWNER/ADMIN.",
        "operationId": "TagsController_create",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear etiqueta",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "tags:write"
        ]
      }
    },
    "/v1/tags/{id}": {
      "delete": {
        "operationId": "TagsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar etiqueta (se desetiqueta de sus conversaciones)",
        "tags": [
          "Automatizaciones"
        ],
        "x-required-scopes": [
          "tags:write"
        ]
      }
    },
    "/v1/tasks": {
      "get": {
        "description": "`assignedTo=me` lo resuelve el servidor con la sesión (API key ⇒ 400 ASSIGNED_TO_ME_REQUIRES_USER). Visibilidad por equipo: un AGENT/VIEWER de un negocio TEAM_ONLY solo recibe tareas de conversaciones que ve (o sin conversación y asignadas a él); una página puede volver corta con `nextCursor`. PATCH de una tarea no visible ⇒ 404.",
        "operationId": "TasksController_listMine",
        "parameters": [
          {
            "description": "Filtra por responsable: `me` (la sesión) | `unassigned` | `<userId>`. Un `<userId>` que no es una persona de este negocio (o no existe) ⇒ 400 `ASSIGNED_TO_UNKNOWN`.",
            "in": "query",
            "name": "assignedTo",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Solo tareas con `dueAt` < esta fecha (ISO-8601) — vencen antes de.",
            "in": "query",
            "name": "dueBefore",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filtra por estado (omitir = todos).",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "OPEN",
                "DONE",
                "CANCELLED"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 30,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Cursor keyset opaco (de `nextCursor`).",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "maxLength": 512,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Mis pendientes: tareas por responsable/estado/vencimiento (keyset)",
        "tags": [
          "Tareas de seguimiento"
        ],
        "x-required-scopes": [
          "tasks:read"
        ]
      }
    },
    "/v1/tasks/{id}": {
      "patch": {
        "operationId": "TasksController_patch",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchCrmTaskDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Completar / reasignar / cancelar / reprogramar una tarea (AGENT+)",
        "tags": [
          "Tareas de seguimiento"
        ],
        "x-required-scopes": [
          "tasks:write"
        ]
      }
    },
    "/v1/team/workload": {
      "get": {
        "description": "Abiertas = OPEN no pospuestas. Sin contestar = el último mensaje del hilo es del cliente. oldestWaitingSince = lastInboundAt más antiguo entre las sin contestar. Incluye a todos los activos (aunque traigan 0) y a los desactivados que aún tienen algo asignado; `unassigned` = lo que falta repartir.",
        "operationId": "TeamWorkloadController_get",
        "parameters": [],
        "responses": {
          "200": {
            "description": "TeamWorkloadDto { generatedAt, people[], unassigned }.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Carga de trabajo por persona (abiertas, sin contestar, espera más antigua, seguimientos)",
        "tags": [
          "Tareas de seguimiento"
        ],
        "x-required-scopes": [
          "metrics:read"
        ]
      }
    },
    "/v1/teams": {
      "get": {
        "description": "OWNER/ADMIN (y API keys con `teams:read`): cada miembro con `{ id, name, email }`. AGENT/VIEWER: el mismo arreglo con `email: \"\"` — sin correo.",
        "operationId": "TeamsController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Equipos del negocio con sus miembros",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "teams:read"
        ]
      },
      "post": {
        "operationId": "TeamsController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTeamDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear un equipo (auditado)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "teams:write"
        ]
      }
    },
    "/v1/teams/visibility": {
      "get": {
        "description": "`teamVisibility` ALL | TEAM_ONLY y `unassignedVisibleToAll`, más `restricted`/`myTeamIds` del usuario que pregunta (para que la bandeja sugiera sólo sus equipos). Con TEAM_ONLY un AGENT no ve —ni por id, ni en búsqueda, ni por SSE— las conversaciones de otros equipos: son 404 para él.",
        "operationId": "TeamsController_getVisibility",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "¿Quién ve qué? (visibilidad por equipo)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "teams:read"
        ]
      },
      "put": {
        "description": "Sólo OWNER/ADMIN (scope `teams:write`). Queda auditado (AuditLog team.visibility.changed). Aplica de inmediato a la API; los streams abiertos lo recogen en ≤25 s.",
        "operationId": "TeamsController_setVisibility",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTeamVisibilityDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Cambiar quién ve qué",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "teams:write"
        ]
      }
    },
    "/v1/teams/{id}": {
      "delete": {
        "operationId": "TeamsController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Borrar un equipo (sus conversaciones vuelven al reparto global; auditado)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "teams:write"
        ]
      },
      "patch": {
        "operationId": "TeamsController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTeamDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Renombrar, recolorear, reordenar o cambiar miembros (auditado)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "teams:write"
        ]
      }
    },
    "/v1/templates": {
      "get": {
        "operationId": "TemplatesController_list",
        "parameters": [
          {
            "description": "Solo las plantillas de la cuenta de WhatsApp de ESE número (su `id` en Ajustes › Números).",
            "in": "query",
            "name": "phoneNumberId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "Lo mismo, pero partiendo de la cuenta de canal de UNA conversación. Es lo que usa el selector del composer: se contesta con las plantillas del número POR EL QUE ENTRÓ el hilo, no por el que el agente tenga elegido.",
            "in": "query",
            "name": "channelAccountId",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "templates:read"
        ]
      },
      "post": {
        "operationId": "TemplatesController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTemplateDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "templates:manage"
        ]
      }
    },
    "/v1/templates/header-sample": {
      "post": {
        "description": "multipart: `file`, `format` (IMAGE|VIDEO|DOCUMENT) y `wabaAccountId` opcional (sin él, la única cuenta conectada). Límites: IMAGE JPEG/PNG 5 MB, VIDEO MP4 16 MB, DOCUMENT PDF 16 MB (la Resumable Upload API de Meta solo acepta pdf/jpeg/png/mp4). El contenido se verifica por firma. Devuelve `{ headerHandle, format, mimeType, size, filename }`; el archivo no se guarda.",
        "operationId": "TemplatesController_uploadHeaderSample",
        "parameters": [],
        "responses": {
          "200": {
            "description": "{ headerHandle, format, mimeType, size, filename }",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Subir el archivo de ejemplo del encabezado de una plantilla",
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "templates:manage"
        ]
      }
    },
    "/v1/templates/recommended": {
      "get": {
        "operationId": "TemplatesController_listRecommended",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "templates:read"
        ]
      }
    },
    "/v1/templates/recommended/{name}": {
      "post": {
        "operationId": "TemplatesController_createRecommended",
        "parameters": [
          {
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRecommendedTemplateDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "templates:manage"
        ]
      }
    },
    "/v1/templates/reconcile": {
      "post": {
        "operationId": "TemplatesController_reconcile",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReconcileTemplatesDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "templates:manage"
        ]
      }
    },
    "/v1/templates/{id}": {
      "delete": {
        "operationId": "TemplatesController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "templates:manage"
        ]
      },
      "get": {
        "operationId": "TemplatesController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "tags": [
          "Plantillas de WhatsApp"
        ],
        "x-required-scopes": [
          "templates:read"
        ]
      }
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "WebhooksOutController_list",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Listar suscripciones de webhook (sin secreto)",
        "tags": [
          "Webhooks"
        ],
        "x-required-scopes": [
          "webhooks:read"
        ]
      },
      "post": {
        "description": "La `url` se valida contra SSRF (https + IP pública resuelta). El campo `secret` de la respuesta (`whsec_...`) se muestra UNA sola vez; en DB solo persiste cifrado (cifrado en reposo).",
        "operationId": "WebhooksOutController_create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Crear suscripción de webhook",
        "tags": [
          "Webhooks"
        ],
        "x-required-scopes": [
          "webhooks:write"
        ]
      }
    },
    "/v1/webhooks/deliveries/{id}/redeliver": {
      "post": {
        "operationId": "WebhooksOutController_redeliver",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Re-entregar manualmente una entrega (tras arreglar el endpoint)",
        "tags": [
          "Webhooks"
        ],
        "x-required-scopes": [
          "webhooks:write"
        ]
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "operationId": "WebhooksOutController_remove",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Eliminar suscripción",
        "tags": [
          "Webhooks"
        ],
        "x-required-scopes": [
          "webhooks:write"
        ]
      },
      "get": {
        "operationId": "WebhooksOutController_get",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Ficha de una suscripción (métricas de salud)",
        "tags": [
          "Webhooks"
        ],
        "x-required-scopes": [
          "webhooks:read"
        ]
      },
      "patch": {
        "description": "`enabled:true` reactiva una suscripción auto-deshabilitada por el circuit-breaker.",
        "operationId": "WebhooksOutController_update",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Editar suscripción (url/events/enabled)",
        "tags": [
          "Webhooks"
        ],
        "x-required-scopes": [
          "webhooks:write"
        ]
      }
    },
    "/v1/webhooks/{id}/deliveries": {
      "get": {
        "description": "Paginated<WebhookDeliveryDto> keyset (createdAt,id DESC). `responseSnippet` saneado.",
        "operationId": "WebhooksOutController_deliveries",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Log de entregas de una suscripción",
        "tags": [
          "Webhooks"
        ],
        "x-required-scopes": [
          "webhooks:read"
        ]
      }
    },
    "/v1/webhooks/{id}/ping": {
      "post": {
        "operationId": "WebhooksOutController_ping",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Enviar un evento test.ping firmado (verificación de endpoint)",
        "tags": [
          "Webhooks"
        ],
        "x-required-scopes": [
          "webhooks:write"
        ]
      }
    },
    "/v1/webhooks/{id}/rotate-secret": {
      "post": {
        "description": "Genera un nuevo `whsec_...` (visible UNA vez). El anterior deja de firmar.",
        "operationId": "WebhooksOutController_rotateSecret",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Rotar el secreto de firma",
        "tags": [
          "Webhooks"
        ],
        "x-required-scopes": [
          "webhooks:write"
        ]
      }
    },
    "/v1/whatsapp/recipient-check": {
      "get": {
        "description": "`phoneChanged: true` ⇒ el número es el ANTERIOR de un cliente que ya cambió de número: la conversación nueva entra a SU hilo y sale a su número/BSUID vigente (no a quien tenga hoy ese número). La UI lo avisa antes de enviar. No revela nombres ni conversaciones y respeta la visibilidad por equipo (sin conversación visible de esa persona, `phoneChanged: false`). 422 INVALID_PHONE.",
        "operationId": "WhatsappIdentityController_recipientCheck",
        "parameters": [
          {
            "description": "Teléfono como lo escribe la persona (con o sin +, espacios o guiones).",
            "in": "query",
            "name": "phone",
            "required": true,
            "schema": {
              "maxLength": 32,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ phone, phoneChanged }",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Máximo de requests en la ventana (por tenant y clase de operación).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests restantes en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicia.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "¿Ese número sigue siendo del cliente? (antes de abrir conversación por teléfono)",
        "tags": [
          "Conversaciones y mensajes"
        ],
        "x-required-scopes": [
          "messages:read"
        ]
      }
    }
  },
  "security": [
    {
      "api-key": []
    }
  ],
  "servers": [
    {
      "description": "Producción",
      "url": "https://api.kontesta.app"
    }
  ],
  "tags": [
    {
      "description": "El inbox: listar hilos, leer y enviar mensajes, asignar, traspaso a una persona.",
      "name": "Conversaciones y mensajes"
    },
    {
      "description": "Primer contacto con un número que todavía no escribió (plantilla aprobada).",
      "name": "Iniciar conversación"
    },
    {
      "description": "Datos del contacto, notas, datos fiscales, fotos, segmentos guardados y unir duplicados.",
      "name": "Ficha de cliente"
    },
    {
      "description": "Importación desde CSV o Excel en cola (vista previa, avance y reporte de errores) y exportación del directorio a CSV.",
      "name": "Importar y exportar contactos"
    },
    {
      "description": "Tablero de oportunidades, etapas y pipelines.",
      "name": "Leads y embudos"
    },
    {
      "description": "Pendientes por contacto y “mis pendientes”.",
      "name": "Tareas de seguimiento"
    },
    {
      "description": "Plantillas aprobadas por Meta para iniciar conversación.",
      "name": "Plantillas de WhatsApp"
    },
    {
      "description": "Reglas, horario de atención y corridas.",
      "name": "Automatizaciones"
    },
    {
      "description": "Bots conversacionales: borrador, validación, simulación sin WhatsApp, publicación por versión, conexiones y métricas.",
      "name": "Bots"
    },
    {
      "description": "Macros del equipo para el compositor.",
      "name": "Respuestas guardadas"
    },
    {
      "description": "Tipos de servicio, disponibilidad, agendar y reprogramar.",
      "name": "Citas y agenda"
    },
    {
      "description": "Llamadas de WhatsApp: detalle de una llamada, grabación y transcripción.",
      "name": "Llamadas"
    },
    {
      "description": "Ligas de pago dentro de la conversación.",
      "name": "Cobros"
    },
    {
      "description": "Conexión con el PAC, series, timbrado y cancelación.",
      "name": "Facturación CFDI"
    },
    {
      "description": "Productos, inventario y envío de producto al chat.",
      "name": "Catálogo y productos"
    },
    {
      "description": "Programas de puntos y sellos, saldo y canjes.",
      "name": "Lealtad"
    },
    {
      "description": "Reseñas, conexiones y solicitudes de reseña.",
      "name": "Reputación"
    },
    {
      "description": "WALINK, QR y widget con su llave pública.",
      "name": "Puntos de entrada"
    },
    {
      "description": "Formularios de captura y reserva dentro del chat.",
      "name": "WhatsApp Flows"
    },
    {
      "description": "Conectores salientes configurados por el tenant.",
      "name": "Integraciones"
    },
    {
      "description": "Suscripciones, entregas, reintentos y rotación del secreto.",
      "name": "Webhooks"
    },
    {
      "description": "Liga de un solo uso para que tu cliente conecte su número de WhatsApp en Kontesta, al estilo 3-D Secure.",
      "name": "Ligas hospedadas"
    },
    {
      "description": "Credenciales propias, ruteo, presupuesto y uso.",
      "name": "IA (BYOK)"
    },
    {
      "description": "Fuentes del KB, ingesta y búsqueda.",
      "name": "Base de conocimiento"
    },
    {
      "description": "Ajustes del tenant expuestos a la API.",
      "name": "Configuración"
    },
    {
      "description": "Descarga de adjuntos.",
      "name": "Archivos"
    },
    {
      "description": "Evidencia antes/después.",
      "name": "Fotos de servicio"
    },
    {
      "description": "Flujo de eventos por Server-Sent Events.",
      "name": "Tiempo real"
    }
  ]
}
