openapi: 3.1.0
info:
  title: KUZ CRM API
  version: "1.0.0"
  summary: API REST pública de KUZ CRM (v1)
  description: |
    API para integrar KUZ CRM con tu ERP, tu sitio web, Zapier o Make.

    **Autenticación**: cabecera `Authorization: Bearer kuz_live_…`. Los tokens se crean en
    *Cuenta › API y webhooks* y llevan permisos (scopes) y expiración opcional.

    **Límites**: 600 solicitudes cada 15 minutos por token (100 por día durante la prueba gratis).
    Cabeceras `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`.

    **Paginación**: por cursor. Pide `?limit=50` y sigue `next_cursor` mientras `has_more` sea `true`.

    **Idempotencia**: en los `POST` puedes enviar `Idempotency-Key` (hasta 128 caracteres). Si repites la
    misma clave dentro de 24 h recibes la misma respuesta con la cabecera `Idempotent-Replayed: true`.

    **Errores**: siempre `{ "error": "texto legible", "code": "CODIGO_ESTABLE" }`.
  contact:
    name: Soporte KUZ CRM
    url: https://kuzcrm.com
    email: soporte@kuzcrm.com
  termsOfService: https://kuzcrm.com/terminos
servers:
  - url: https://kuzcrm.com/api/v1
    description: Producción
security:
  - tokenApi: []
tags:
  - name: Cuenta
  - name: Leads
  - name: Clientes
  - name: Tareas
  - name: Eventos
  - name: Webhooks
paths:
  /me:
    get:
      tags: [Cuenta]
      summary: Organización y permisos del token
      operationId: obtenerMe
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  organizacion:
                    type: object
                    properties:
                      id: { type: integer }
                      nombre: { type: string }
                      slug: { type: string }
                      plan: { type: string, enum: [emprende, pyme, corporativo] }
                      estado: { type: string, enum: [trial, activa, suspendida, cancelada] }
                  token:
                    type: object
                    properties:
                      nombre: { type: string }
                      prefijo: { type: string, example: kuz_live_a1B2c3D4 }
                      scopes: { type: array, items: { $ref: "#/components/schemas/Scope" } }
                      expiraEn: { type: [string, "null"], format: date-time }
                  limites:
                    type: object
                    properties:
                      solicitudes: { type: string }
                  documentacion: { type: string, format: uri }
        "401": { $ref: "#/components/responses/NoAutorizado" }
        "429": { $ref: "#/components/responses/RateLimit" }
  /pipeline/etapas:
    get:
      tags: [Leads]
      summary: Etapas del pipeline de ventas
      operationId: listarEtapas
      security: [{ tokenApi: ["leads:read"] }]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Etapa" }
        "401": { $ref: "#/components/responses/NoAutorizado" }
        "403": { $ref: "#/components/responses/SinPermiso" }
  /leads:
    get:
      tags: [Leads]
      summary: Listar leads
      operationId: listarLeads
      security: [{ tokenApi: ["leads:read"] }]
      parameters:
        - $ref: "#/components/parameters/limit"
        - $ref: "#/components/parameters/cursor"
        - { name: etapa, in: query, schema: { type: string }, description: Clave de la etapa (ver /pipeline/etapas) }
        - { name: fuente, in: query, schema: { type: string }, example: whatsapp }
        - { name: email, in: query, schema: { type: string, format: email } }
        - { name: telefono, in: query, schema: { type: string } }
        - { name: q, in: query, schema: { type: string, maxLength: 80 }, description: Busca en nombre, empresa, email y teléfono }
        - { name: desde, in: query, schema: { type: string, format: date-time }, description: Creados desde esta fecha (ISO 8601 o YYYY-MM-DD) }
        - { name: actualizado_desde, in: query, schema: { type: string, format: date-time } }
        - { name: cliente_id, in: query, schema: { type: integer } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Pagina"
                  - type: object
                    properties:
                      data: { type: array, items: { $ref: "#/components/schemas/Lead" } }
        "401": { $ref: "#/components/responses/NoAutorizado" }
        "403": { $ref: "#/components/responses/SinPermiso" }
    post:
      tags: [Leads]
      summary: Crear lead
      operationId: crearLead
      security: [{ tokenApi: ["leads:write"] }]
      parameters: [{ $ref: "#/components/parameters/idempotencyKey" }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/LeadEntrada" }
            example: { nombre: "Juan Pérez", empresa: "Ejemplo Ltda", email: "juan@ejemplo.cl", telefono: "+56912345678", valor: 150000, fuente: "formulario", externalId: "typeform-8812" }
      responses:
        "201":
          description: Creado
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Lead" }
        "400": { $ref: "#/components/responses/Validacion" }
        "401": { $ref: "#/components/responses/NoAutorizado" }
        "402": { $ref: "#/components/responses/LimitePlan" }
        "403": { $ref: "#/components/responses/SinPermiso" }
  /leads/{id}:
    parameters: [{ $ref: "#/components/parameters/id" }]
    get:
      tags: [Leads]
      summary: Detalle de un lead (con actividades y tareas)
      operationId: obtenerLead
      security: [{ tokenApi: ["leads:read"] }]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Lead" }
        "404": { $ref: "#/components/responses/NoEncontrado" }
    patch:
      tags: [Leads]
      summary: Modificar lead (incluye cambiar de etapa)
      description: Cambiar `etapa` a una etapa de tipo `ganada` o `perdida` dispara los webhooks `lead.ganado` / `lead.perdido`.
      operationId: modificarLead
      security: [{ tokenApi: ["leads:write"] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/LeadEntradaParcial" }
            example: { etapa: "ganado", valor: 180000 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Lead" }
        "400": { $ref: "#/components/responses/Validacion" }
        "404": { $ref: "#/components/responses/NoEncontrado" }
  /clientes:
    get:
      tags: [Clientes]
      summary: Listar clientes
      operationId: listarClientes
      security: [{ tokenApi: ["clientes:read"] }]
      parameters:
        - $ref: "#/components/parameters/limit"
        - $ref: "#/components/parameters/cursor"
        - { name: q, in: query, schema: { type: string, maxLength: 80 } }
        - { name: rut, in: query, schema: { type: string }, example: "76.086.428-5" }
        - { name: email, in: query, schema: { type: string, format: email } }
        - { name: segmento, in: query, schema: { type: string, enum: [vip, frecuente, nuevo, en_riesgo, dormido, sin_compras] } }
        - { name: etiqueta, in: query, schema: { type: string } }
        - { name: con_deuda, in: query, schema: { type: string, enum: ["1", "0"] } }
        - { name: desde, in: query, schema: { type: string, format: date-time } }
        - { name: actualizado_desde, in: query, schema: { type: string, format: date-time } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Pagina"
                  - type: object
                    properties:
                      data: { type: array, items: { $ref: "#/components/schemas/Cliente" } }
    post:
      tags: [Clientes]
      summary: Crear cliente
      operationId: crearCliente
      security: [{ tokenApi: ["clientes:write"] }]
      parameters: [{ $ref: "#/components/parameters/idempotencyKey" }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ClienteEntrada" }
            example: { razonSocial: "Comercial Andes SpA", rut: "76.086.428-5", email: "pagos@andes.cl", telefono: "+56223456789", comuna: "Providencia", condicionPago: "30 dias", etiquetas: ["mayorista"] }
      responses:
        "201":
          description: Creado
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Cliente" }
        "400": { $ref: "#/components/responses/Validacion" }
        "402": { $ref: "#/components/responses/LimitePlan" }
        "409":
          description: Ya existe un cliente con ese RUT
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Error"
                  - type: object
                    properties: { clienteId: { type: integer } }
  /clientes/{id}:
    parameters: [{ $ref: "#/components/parameters/id" }]
    get:
      tags: [Clientes]
      summary: Ficha del cliente con contactos, direcciones, calificación de pagador y semáforo de riesgo
      operationId: obtenerCliente
      security: [{ tokenApi: ["clientes:read"] }]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ClienteDetalle" }
        "404": { $ref: "#/components/responses/NoEncontrado" }
    patch:
      tags: [Clientes]
      summary: Modificar cliente
      operationId: modificarCliente
      security: [{ tokenApi: ["clientes:write"] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ClienteEntradaParcial" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Cliente" }
        "404": { $ref: "#/components/responses/NoEncontrado" }
        "409": { description: Otro cliente ya tiene ese RUT }
  /tareas:
    get:
      tags: [Tareas]
      summary: Listar tareas
      operationId: listarTareas
      security: [{ tokenApi: ["tareas:read"] }, { tokenApi: ["tareas:write"] }]
      parameters:
        - $ref: "#/components/parameters/limit"
        - $ref: "#/components/parameters/cursor"
        - { name: estado, in: query, schema: { type: string, enum: [pendiente, en_curso, hecha] } }
        - { name: asignado_id, in: query, schema: { type: integer } }
        - { name: lead_id, in: query, schema: { type: integer } }
        - { name: cliente_id, in: query, schema: { type: integer } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Pagina"
                  - type: object
                    properties:
                      data: { type: array, items: { $ref: "#/components/schemas/Tarea" } }
    post:
      tags: [Tareas]
      summary: Crear tarea
      operationId: crearTarea
      security: [{ tokenApi: ["tareas:write"] }]
      parameters: [{ $ref: "#/components/parameters/idempotencyKey" }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TareaEntrada" }
            example: { titulo: "Llamar para confirmar pedido", prioridad: "alta", vence: "2026-09-10T15:00:00Z", clienteId: 3 }
      responses:
        "201":
          description: Creada
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Tarea" }
        "400": { $ref: "#/components/responses/Validacion" }
  /eventos:
    post:
      tags: [Eventos]
      summary: Registrar una actividad sobre un cliente o un lead
      description: Deja una entrada en la línea de tiempo de la ficha (llamada, correo, reunión, visita, nota o evento externo de tu sistema).
      operationId: crearEvento
      security: [{ tokenApi: ["eventos:write"] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [detalle]
              properties:
                clienteId: { type: integer }
                leadId: { type: integer }
                tipo: { type: string, enum: [llamada, email, reunion, whatsapp, visita, nota, externo], default: externo }
                detalle: { type: string, maxLength: 4000 }
                fecha: { type: string, format: date-time }
            example: { clienteId: 3, tipo: "externo", detalle: "Factura 10233 emitida en el ERP por $1.190.000" }
      responses:
        "201": { description: Registrado }
        "400": { $ref: "#/components/responses/Validacion" }
        "404": { $ref: "#/components/responses/NoEncontrado" }
  /webhooks:
    get:
      tags: [Webhooks]
      summary: Listar webhooks de la organización
      operationId: listarWebhooks
      security: [{ tokenApi: ["webhooks:manage"] }]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Webhook" } }
                  eventos_disponibles: { type: array, items: { $ref: "#/components/schemas/Evento" } }
    post:
      tags: [Webhooks]
      summary: Crear webhook (suscripción REST hook)
      description: |
        Devuelve el `secreto` de firma **una sola vez**. La URL debe ser `https://` pública: se rechazan
        direcciones privadas, loopback, link-local y nombres internos, y no se siguen redirecciones.
      operationId: crearWebhook
      security: [{ tokenApi: ["webhooks:manage"] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, eventos]
              properties:
                url: { type: string, format: uri, maxLength: 500 }
                eventos:
                  type: array
                  minItems: 1
                  items:
                    oneOf:
                      - $ref: "#/components/schemas/Evento"
                      - { type: string, const: "*" }
                descripcion: { type: string, maxLength: 120 }
            example: { url: "https://hooks.zapier.com/hooks/catch/123/abc", eventos: ["lead.creado", "lead.ganado"] }
      responses:
        "201":
          description: Creado
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: integer }
                  url: { type: string }
                  eventos: { type: array, items: { type: string } }
                  activo: { type: boolean }
                  secreto: { type: string, example: whsec_… }
                  aviso: { type: string }
        "400":
          description: URL rechazada o datos inválidos (`URL_RECHAZADA`, `VALIDACION`, `LIMITE_WEBHOOKS`)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
  /webhooks/{id}:
    parameters: [{ $ref: "#/components/parameters/id" }]
    delete:
      tags: [Webhooks]
      summary: Eliminar webhook (baja de la suscripción)
      operationId: eliminarWebhook
      security: [{ tokenApi: ["webhooks:manage"] }]
      responses:
        "200": { description: Eliminado }
        "404": { $ref: "#/components/responses/NoEncontrado" }
webhooks:
  evento:
    post:
      summary: Entrega de un evento a tu URL
      description: |
        Cada entrega es un `POST` con JSON y estas cabeceras:

        - `X-Kuz-Event`: nombre del evento (p. ej. `lead.creado`).
        - `X-Kuz-Delivery`: id único de la entrega (`whd_123`). Úsalo para descartar duplicados.
        - `X-Kuz-Attempt`: número de intento (1..6).
        - `X-Kuz-Signature`: `t=<unix>,v1=<hex>` donde `v1 = HMAC_SHA256(secreto, t + "." + cuerpo_crudo)`.
          Rechaza firmas con más de 5 minutos de antigüedad.

        Responde `2xx` en menos de 10 segundos. Si no, reintentamos a 1 min, 5 min, 30 min, 2 h y 12 h.
        Tras 20 fallos seguidos el webhook se desactiva y avisamos por correo.
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/EntregaWebhook" }
      responses:
        "200": { description: Recibido }
components:
  securitySchemes:
    tokenApi:
      type: http
      scheme: bearer
      bearerFormat: kuz_live_<id><secreto>
      description: Token de organización creado en Cuenta › API y webhooks. Scopes disponibles en `#/components/schemas/Scope`.
  parameters:
    id: { name: id, in: path, required: true, schema: { type: integer, minimum: 1 } }
    limit: { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 50 } }
    cursor: { name: cursor, in: query, schema: { type: integer }, description: Valor de `next_cursor` de la página anterior }
    idempotencyKey: { name: Idempotency-Key, in: header, required: false, schema: { type: string, maxLength: 128 }, description: Clave única por operación; repetirla dentro de 24 h devuelve la misma respuesta }
  responses:
    NoAutorizado:
      description: Token ausente, inválido, revocado o expirado (`SIN_TOKEN`, `TOKEN_INVALIDO`, `TOKEN_REVOCADO`, `TOKEN_EXPIRADO`)
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    SinPermiso:
      description: El token no tiene el scope requerido (`SCOPE_INSUFICIENTE`)
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    NoEncontrado:
      description: El recurso no existe o pertenece a otra organización (`NO_ENCONTRADO`)
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Validacion:
      description: Datos inválidos (`VALIDACION`, `RUT_INVALIDO`, `ETAPA_INEXISTENTE`, `CLIENTE_INEXISTENTE`, `USUARIO_INEXISTENTE`)
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    LimitePlan:
      description: Límite del plan alcanzado o cuenta en solo lectura (`LIMITE_PLAN`, `PRUEBA_VENCIDA`, `SUSPENDIDA`)
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    RateLimit:
      description: Demasiadas solicitudes (`RATE_LIMIT`); revisa `Retry-After`
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
  schemas:
    Error:
      type: object
      required: [error, code]
      properties:
        error: { type: string, description: Mensaje legible en español }
        code: { type: string, description: Código estable para programar contra él }
    Scope:
      type: string
      enum: ["leads:read", "leads:write", "clientes:read", "clientes:write", "tareas:read", "tareas:write", "eventos:write", "webhooks:manage"]
    Evento:
      type: string
      enum: [lead.creado, lead.etapa_cambiada, lead.ganado, lead.perdido, cliente.creado, cliente.actualizado, tarea.creada, tarea.completada, conversacion.mensaje_entrante, ticket.creado, evaluacion.completada]
    Pagina:
      type: object
      properties:
        next_cursor: { type: [integer, "null"] }
        has_more: { type: boolean }
    Etapa:
      type: object
      properties:
        clave: { type: string, example: calificado }
        nombre: { type: string }
        orden: { type: integer }
        tipo: { type: string, enum: [abierta, ganada, perdida] }
        color: { type: string }
    LeadEntrada:
      type: object
      required: [nombre]
      properties:
        nombre: { type: string, maxLength: 120 }
        empresa: { type: [string, "null"], maxLength: 120 }
        email: { type: [string, "null"], format: email }
        telefono: { type: [string, "null"], maxLength: 30 }
        mensaje: { type: [string, "null"], maxLength: 4000 }
        fuente: { type: string, maxLength: 30, default: api, description: "manual | web | webchat | whatsapp | messenger | instagram | landing | api | formulario…" }
        fuenteDetalle: { type: [string, "null"], maxLength: 200 }
        externalId: { type: [string, "null"], maxLength: 80, description: Id en tu sistema; se guarda en fuenteDetalle }
        etapa: { type: string, description: Clave de una etapa existente }
        valor: { type: integer, minimum: 0, description: Monto estimado en pesos }
        probabilidad: { type: integer, minimum: 0, maximum: 100 }
        propietarioId: { type: [integer, "null"], description: Usuario de la organización }
        clienteId: { type: [integer, "null"] }
        cierreEstimado: { type: [string, "null"], format: date-time }
        motivoPerdida: { type: [string, "null"], maxLength: 200 }
    LeadEntradaParcial:
      allOf: [{ $ref: "#/components/schemas/LeadEntrada" }]
      required: []
    Lead:
      allOf:
        - $ref: "#/components/schemas/LeadEntrada"
        - type: object
          properties:
            id: { type: integer }
            orgId: { type: integer }
            score: { type: integer, description: Puntaje 0-100 calculado por KUZ }
            utm: { type: object }
            ultimaActividad: { type: string, format: date-time }
            creadoEn: { type: string, format: date-time }
            actualizadoEn: { type: string, format: date-time }
    ClienteEntrada:
      type: object
      required: [razonSocial]
      properties:
        tipo: { type: string, enum: [empresa, persona] }
        razonSocial: { type: string, maxLength: 160 }
        rut: { type: [string, "null"], description: Se valida el dígito verificador y se normaliza a 12.345.678-9 }
        email: { type: [string, "null"], format: email }
        telefono: { type: [string, "null"] }
        direccion: { type: [string, "null"] }
        comuna: { type: [string, "null"] }
        ciudad: { type: [string, "null"] }
        region: { type: [string, "null"] }
        sitioWeb: { type: [string, "null"] }
        giro: { type: [string, "null"] }
        etiquetas: { type: array, items: { type: string, maxLength: 30 }, maxItems: 20 }
        propietarioId: { type: [integer, "null"] }
        origen: { type: [string, "null"], default: api }
        condicionPago: { type: [string, "null"], example: "30 dias" }
        limiteCredito: { type: integer, minimum: 0 }
        externalId: { type: [string, "null"], maxLength: 80 }
    ClienteEntradaParcial:
      allOf: [{ $ref: "#/components/schemas/ClienteEntrada" }]
      required: []
    Cliente:
      allOf:
        - $ref: "#/components/schemas/ClienteEntrada"
        - type: object
          properties:
            id: { type: integer }
            orgId: { type: integer }
            totalCompras: { type: integer }
            numCompras: { type: integer }
            ultimaCompra: { type: [string, "null"], format: date-time }
            salud: { type: integer, minimum: 0, maximum: 100 }
            segmento: { type: string, enum: [vip, frecuente, nuevo, en_riesgo, dormido, sin_compras] }
            saldoPendiente: { type: integer }
            ticketPromedio: { type: integer }
            creadoEn: { type: string, format: date-time }
            actualizadoEn: { type: string, format: date-time }
    ClienteDetalle:
      allOf:
        - $ref: "#/components/schemas/Cliente"
        - type: object
          properties:
            contactos: { type: array, items: { type: object, properties: { id: { type: integer }, nombre: { type: string }, email: { type: [string, "null"] }, telefono: { type: [string, "null"] }, cargo: { type: [string, "null"] }, principal: { type: boolean } } } }
            direcciones: { type: array, items: { type: object } }
            pagador:
              type: [object, "null"]
              description: Comportamiento de pago calculado con los documentos de venta y pagos registrados (ERP o manual)
              properties:
                calificacion: { type: string, enum: [excelente, bueno, regular, malo, sin_datos] }
                etiqueta: { type: string, example: Buen pagador }
                confianza: { type: string, enum: [alta, media, baja] }
                diasPromedioPago: { type: [number, "null"] }
                diasPromedioAtraso: { type: [number, "null"] }
                pctATiempo: { type: [number, "null"] }
                saldoPendiente: { type: integer }
                dso: { type: [number, "null"] }
                motivos: { type: array, items: { type: string } }
            riesgoComercial:
              type: [object, "null"]
              description: Última evaluación comercial de la organización sobre este RUT (solo resultado)
              properties:
                semaforo: { type: string, enum: [verde, amarillo, rojo, gris] }
                score: { type: [integer, "null"] }
                etiqueta: { type: [string, "null"] }
                evaluadoEn: { type: string, format: date-time }
    TareaEntrada:
      type: object
      required: [titulo]
      properties:
        titulo: { type: string, maxLength: 200 }
        descripcion: { type: [string, "null"], maxLength: 4000 }
        estado: { type: string, enum: [pendiente, en_curso, hecha] }
        prioridad: { type: string, enum: [baja, media, alta] }
        vence: { type: [string, "null"], format: date-time }
        asignadoId: { type: [integer, "null"], description: Si se omite se asigna al propietario de la cuenta }
        leadId: { type: [integer, "null"] }
        clienteId: { type: [integer, "null"] }
    Tarea:
      allOf:
        - $ref: "#/components/schemas/TareaEntrada"
        - type: object
          properties:
            id: { type: integer }
            orgId: { type: integer }
            completadaEn: { type: [string, "null"], format: date-time }
            creadaEn: { type: string, format: date-time }
    Webhook:
      type: object
      properties:
        id: { type: integer }
        url: { type: string }
        descripcion: { type: [string, "null"] }
        eventos: { type: array, items: { type: string } }
        activo: { type: boolean }
        ultimoEstado: { type: [string, "null"], enum: [ok, error, desactivado, null] }
        ultimoStatus: { type: [integer, "null"] }
        ultimaEntrega: { type: [string, "null"], format: date-time }
        fallosSeguidos: { type: integer }
        creadoEn: { type: string, format: date-time }
    EntregaWebhook:
      type: object
      properties:
        id: { type: string, example: evt_1042 }
        evento: { $ref: "#/components/schemas/Evento" }
        creadoEn: { type: string, format: date-time }
        orgId: { type: integer }
        data:
          type: object
          description: El recurso afectado (Lead, Cliente, Tarea…). En `lead.etapa_cambiada` incluye `etapaAnterior`; en `evaluacion.completada` solo `rut, razonSocial, semaforo, score, etiqueta`.
      example:
        id: evt_1042
        evento: lead.ganado
        creadoEn: "2026-09-04T13:05:00.000Z"
        orgId: 12
        data: { id: 88, nombre: "Juan Pérez", empresa: "Ejemplo Ltda", etapa: "ganado", valor: 150000, probabilidad: 100 }
