{
  "openapi": "3.1.0",
  "info": {
    "title": "Deliver API",
    "version": "1.0.0",
    "description": "**SMS, WhatsApp, Email y Push** integrados en una sola API.\n\nTu aplicación indica el canal, el destinatario, la plantilla y los datos; Deliver compone el mensaje, lo entrega por el canal pedido, reintenta si falla y registra cada cambio de estado.\n\n## Autenticación\nCada aplicación recibe **dos API keys independientes**, una por ambiente. Envíala en la cabecera `Authorization: Bearer <clave>`. La clave define el ambiente, el emisor y los canales habilitados.\n\n| Ambiente | Prefijo | Comportamiento |\n|---|---|---|\n| UAT | `dlv_test_` | Envío real **solo a destinatarios autorizados** por Piensa IT. Cualquier otro destino se registra con estado `blocked` y no sale. |\n| PRD | `dlv_live_` | Envío real sin restricción. |\n\nUsa la clave de UAT en tus ambientes de desarrollo y pruebas y la de PRD solo en producción. `GET /v1/me` te dice qué ambiente tiene la clave configurada.\n\n## Canales\n| Canal | Destinatario (`to`) |\n|---|---|\n| `email` | Dirección de correo |\n| `sms` | Teléfono en formato E.164 (+57…) |\n| `whatsapp` | Teléfono en formato E.164 (+57…) |\n| `push` | Usuario, token de dispositivo o topic |\n\n## Plantillas de email\nEnvía `template: { slug, design }`, `brand` y `data`: Deliver arma asunto, HTML y texto plano con el logo, colores y datos legales de la marca. `content` puede sobrescribir `subject`, `fromName` y `replyTo`, y aportar `attachments`. Míralas en [deliver.piensait.com/plantillas](https://deliver.piensait.com/plantillas).\n\n| Plantilla (`slug`) | Descripción |\n|---|---|\n| `factura-electronica` | Factura, nota crédito o nota débito con total, datos DIAN y aviso del ZIP adjunto. |\n| `codigo-otp` | Código grande y legible, vigencia, datos del intento y aviso de seguridad. |\n\nDiseños: `clasico` (Clásico), `moderno` (Moderno), `minimal` (Minimal). Por defecto `moderno`.\n\nLa marca (`brand`) la registra Piensa IT para tu empresa y define qué aplicaciones pueden usarla.\n\n## Errores\nTodos los errores responden `{ \"error\", \"codigo\", \"detalle\"? }`. Programa contra `codigo`, que es estable.\n\n| Código | HTTP | Significado |\n|---|---|---|\n| `CLAVE_AUSENTE` | 401 | Falta la cabecera authorization: Bearer <clave>. |\n| `CLAVE_INVALIDA` | 401 | La clave no existe o fue revocada. |\n| `CLAVE_NO_VERIFICABLE` | 503 | No se pudo verificar la clave. Reintenta en unos segundos. |\n| `CANAL_NO_PERMITIDO` | 403 | La clave no tiene habilitado este canal. |\n| `RUTA_NO_ENCONTRADA` | 404 | La ruta no existe. |\n| `METODO_NO_PERMITIDO` | 405 | Método no permitido en esta ruta. |\n| `PETICION_INVALIDA` | 400 | El cuerpo de la petición no cumple el contrato. |\n| `CONTENIDO_LIBRE_NO_PERMITIDO` | 403 | La clave no permite enviar contenido libre; usa una plantilla. |\n| `CANAL_NO_DISPONIBLE` | 501 | Este canal todavía no está disponible para envío. |\n| `ADJUNTOS_DEMASIADO_GRANDES` | 413 | Los adjuntos superan el tamaño máximo permitido (10 MB en total). |\n| `MENSAJE_NO_ENCONTRADO` | 404 | No existe un mensaje con ese id para esta clave. |\n| `MARCA_NO_ENCONTRADA` | 404 | La marca no existe o tu aplicación no está autorizada para usarla. |\n| `PLANTILLA_NO_ENCONTRADA` | 404 | No existe esa plantilla o ese diseño. |\n| `VARIABLES_FALTANTES` | 422 | Faltan datos que la plantilla necesita. |\n| `DESTINATARIO_SUPRIMIDO` | 422 | El destinatario se dio de baja o rebotó en este canal. |\n| `LIMITE_EXCEDIDO` | 429 | Se superó el límite de envíos de la clave. |\n| `SESION_INVALIDA` | 401 | La sesión no es válida o expiró. Vuelve a ingresar. |\n| `SIN_ACCESO` | 403 | Tu cuenta no tiene acceso a Deliver. Pídele a un administrador que te habilite. |\n| `PERMISO_INSUFICIENTE` | 403 | Tu rol no permite esta acción. |\n| `CONFLICTO` | 409 | La operación no se puede aplicar en el estado actual. |\n| `ERROR_INTERNO` | 500 | Error inesperado. Ya quedó registrado. |\n\n## Ciclo de vida de un mensaje\n`queued` → `sending` → `sent` → `delivered` → `read` (WhatsApp)  ·  en error: `failed`  ·  destinatario dado de baja: `suppressed`  ·  programado: `scheduled`  ·  destino no autorizado en UAT: `blocked`",
    "contact": {
      "name": "Piensa IT",
      "url": "https://piensait.com"
    }
  },
  "servers": [
    {
      "url": "https://deliver.piensait.com/api",
      "description": "Producción"
    },
    {
      "url": "http://localhost:9999",
      "description": "Local (npm run api:local)"
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Mensajes",
      "description": "Enviar y consultar comunicaciones."
    },
    {
      "name": "Plantillas",
      "description": "Plantillas publicadas disponibles para tu clave."
    },
    {
      "name": "Dispositivos",
      "description": "Tokens de push de los usuarios de tu aplicación."
    },
    {
      "name": "Servicio",
      "description": "Salud, catálogo y datos de la clave."
    }
  ],
  "paths": {
    "/v1/messages": {
      "post": {
        "tags": [
          "Mensajes"
        ],
        "operationId": "enviarMensaje",
        "summary": "Enviar un mensaje",
        "x-estado": "disponible-email",
        "description": "> ✅ **Disponible para `email`**, con plantilla (`template` + `brand` + `data`) o contenido libre. SMS, WhatsApp y Push llegan en las siguientes versiones.\n\nRegistra un mensaje, lo envía de inmediato en segundo plano y responde con `202`. Si el envío falla de forma temporal, Deliver reintenta a los 1, 5, 15 y 60 minutos. Consulta el resultado con `GET /v1/messages/{id}`.\n\nEncola un mensaje y responde de inmediato con `202`. Usa `template` para enviar con una plantilla publicada o `content` para contenido libre (si tu clave lo permite).\n\nEnvía la cabecera `Idempotency-Key` (o el campo `idempotencyKey`) para que un reintento de tu lado no genere un segundo envío.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Identificador único del envío en tu sistema."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NuevoMensaje"
              },
              "examples": {
                "facturaConPlantilla": {
                  "summary": "Factura con plantilla y marca (recomendado)",
                  "value": {
                    "channel": "email",
                    "to": [
                      "compras@cliente.com"
                    ],
                    "template": {
                      "slug": "factura-electronica",
                      "design": "moderno"
                    },
                    "brand": "sudivisa",
                    "data": {
                      "tipoDocumento": "factura",
                      "numero": "SETP990000123",
                      "fechaEmision": "14 de septiembre de 2026",
                      "fechaVencimiento": "14 de octubre de 2026",
                      "cliente": {
                        "nombre": "Laura Gómez"
                      },
                      "total": "$ 1.250.000",
                      "resumen": [
                        {
                          "etiqueta": "Subtotal",
                          "valor": "$ 1.050.420"
                        },
                        {
                          "etiqueta": "IVA 19 %",
                          "valor": "$ 199.580"
                        },
                        {
                          "etiqueta": "Forma de pago",
                          "valor": "Crédito 30 días"
                        }
                      ],
                      "cufe": "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f6071829",
                      "nombreAdjunto": "z09001234560002600000123.zip"
                    },
                    "content": {
                      "attachments": [
                        {
                          "filename": "z09001234560002600000123.zip",
                          "content": "UEsDBBQAAAAIA…(base64)"
                        }
                      ]
                    },
                    "idempotencyKey": "midivisa:factura:SETP990000123"
                  }
                },
                "otpConPlantilla": {
                  "summary": "Código OTP con plantilla",
                  "value": {
                    "channel": "email",
                    "to": "laura@example.com",
                    "template": {
                      "slug": "codigo-otp",
                      "design": "minimal"
                    },
                    "brand": "piensa-it",
                    "data": {
                      "codigo": "481205",
                      "proposito": "iniciar sesión",
                      "vigenciaMinutos": 10,
                      "nombre": "Laura",
                      "dispositivo": "Chrome en macOS",
                      "ubicacion": "Medellín, Colombia"
                    }
                  }
                },
                "facturaElectronica": {
                  "summary": "Email con factura electrónica (ZIP DIAN adjunto)",
                  "value": {
                    "channel": "email",
                    "to": [
                      "compras@cliente.com"
                    ],
                    "cc": [
                      "contabilidad@cliente.com"
                    ],
                    "idempotencyKey": "midivisa:factura:SETP990000123",
                    "content": {
                      "subject": "900123456;SUDIVISA SAS;SETP990000123;01;Sudivisa;",
                      "fromName": "Sudivisa",
                      "replyTo": "facturacion@sudivisa.com",
                      "html": "<p>Estimado cliente, adjuntamos la factura electrónica SETP990000123.</p>",
                      "attachments": [
                        {
                          "filename": "z09001234560002600000123.zip",
                          "content": "UEsDBBQAAAAIA…(base64)"
                        }
                      ]
                    },
                    "metadata": {
                      "factura": "SETP990000123",
                      "empresa": "sudivisa"
                    }
                  }
                },
                "whatsappPlantilla": {
                  "summary": "WhatsApp con plantilla",
                  "value": {
                    "channel": "whatsapp",
                    "to": "+573001234567",
                    "template": {
                      "slug": "recordatorio-pago"
                    },
                    "data": {
                      "cliente": {
                        "nombre": "Laura"
                      },
                      "valor": "$ 250.000",
                      "vence": "30 de septiembre"
                    }
                  }
                },
                "emailPlantilla": {
                  "summary": "Email con plantilla e idioma",
                  "value": {
                    "channel": "email",
                    "to": "laura@example.com",
                    "template": {
                      "slug": "bienvenida",
                      "locale": "es"
                    },
                    "data": {
                      "cliente": {
                        "nombre": "Laura"
                      }
                    },
                    "metadata": {
                      "usuarioId": "u_123"
                    }
                  }
                },
                "smsOtp": {
                  "summary": "SMS de código OTP",
                  "value": {
                    "channel": "sms",
                    "to": "+573001234567",
                    "template": {
                      "slug": "codigo-otp"
                    },
                    "data": {
                      "codigo": "481 205"
                    }
                  }
                },
                "pushLibre": {
                  "summary": "Push con contenido libre",
                  "value": {
                    "channel": "push",
                    "to": {
                      "userId": "u_123"
                    },
                    "content": {
                      "title": "Tu pedido va en camino",
                      "body": "Llega hoy entre 2 y 4 p. m.",
                      "deepLink": "app://pedidos/987"
                    }
                  }
                },
                "programado": {
                  "summary": "Envío programado",
                  "value": {
                    "channel": "sms",
                    "to": "+573001234567",
                    "template": {
                      "slug": "recordatorio-cita"
                    },
                    "data": {
                      "hora": "10:00 a. m."
                    },
                    "scheduleAt": "2026-09-30T13:00:00Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ya existía un mensaje con esa `Idempotency-Key`: se devuelve el mismo, sin reenviar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MensajeAceptado"
                }
              }
            }
          },
          "202": {
            "description": "Mensaje aceptado. `status` es `queued`, o `blocked` si la clave es de UAT y algún destino no está autorizado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MensajeAceptado"
                }
              }
            }
          },
          "400": {
            "description": "El cuerpo no cumple el contrato.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "PETICION_INVALIDA": {
                    "value": {
                      "error": "El cuerpo de la petición no cumple el contrato.",
                      "codigo": "PETICION_INVALIDA"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clave ausente o inválida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "CLAVE_AUSENTE": {
                    "value": {
                      "error": "Falta la cabecera authorization: Bearer <clave>.",
                      "codigo": "CLAVE_AUSENTE"
                    }
                  },
                  "CLAVE_INVALIDA": {
                    "value": {
                      "error": "La clave no existe o fue revocada.",
                      "codigo": "CLAVE_INVALIDA"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Canal no habilitado o contenido libre no permitido para la clave.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "CANAL_NO_PERMITIDO": {
                    "value": {
                      "error": "La clave no tiene habilitado este canal.",
                      "codigo": "CANAL_NO_PERMITIDO"
                    }
                  },
                  "CONTENIDO_LIBRE_NO_PERMITIDO": {
                    "value": {
                      "error": "La clave no permite enviar contenido libre; usa una plantilla.",
                      "codigo": "CONTENIDO_LIBRE_NO_PERMITIDO"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Plantilla, diseño o marca inexistentes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "PLANTILLA_NO_ENCONTRADA": {
                    "value": {
                      "error": "No existe esa plantilla o ese diseño.",
                      "codigo": "PLANTILLA_NO_ENCONTRADA"
                    }
                  },
                  "MARCA_NO_ENCONTRADA": {
                    "value": {
                      "error": "La marca no existe o tu aplicación no está autorizada para usarla.",
                      "codigo": "MARCA_NO_ENCONTRADA"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "Adjuntos demasiado grandes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ADJUNTOS_DEMASIADO_GRANDES": {
                    "value": {
                      "error": "Los adjuntos superan el tamaño máximo permitido (10 MB en total).",
                      "codigo": "ADJUNTOS_DEMASIADO_GRANDES"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Datos incompletos o destinatario suprimido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "VARIABLES_FALTANTES": {
                    "value": {
                      "error": "Faltan datos que la plantilla necesita.",
                      "codigo": "VARIABLES_FALTANTES"
                    }
                  },
                  "DESTINATARIO_SUPRIMIDO": {
                    "value": {
                      "error": "El destinatario se dio de baja o rebotó en este canal.",
                      "codigo": "DESTINATARIO_SUPRIMIDO"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Límite de envíos superado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "LIMITE_EXCEDIDO": {
                    "value": {
                      "error": "Se superó el límite de envíos de la clave.",
                      "codigo": "LIMITE_EXCEDIDO"
                    }
                  }
                }
              }
            }
          },
          "501": {
            "description": "Canal aún no disponible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "CANAL_NO_DISPONIBLE": {
                    "value": {
                      "error": "Este canal todavía no está disponible para envío.",
                      "codigo": "CANAL_NO_DISPONIBLE"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "msg_01J9ZK4Q8R"
        }
      ],
      "get": {
        "tags": [
          "Mensajes"
        ],
        "operationId": "consultarMensaje",
        "summary": "Consultar estado",
        "description": "Estado actual del mensaje y su historial de eventos. Solo ves mensajes de tu misma aplicación y ambiente.",
        "responses": {
          "200": {
            "description": "Mensaje.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Mensaje"
                }
              }
            }
          },
          "401": {
            "description": "Clave ausente o inválida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "CLAVE_AUSENTE": {
                    "value": {
                      "error": "Falta la cabecera authorization: Bearer <clave>.",
                      "codigo": "CLAVE_AUSENTE"
                    }
                  },
                  "CLAVE_INVALIDA": {
                    "value": {
                      "error": "La clave no existe o fue revocada.",
                      "codigo": "CLAVE_INVALIDA"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No existe o pertenece a otra aplicación o ambiente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "MENSAJE_NO_ENCONTRADO": {
                    "value": {
                      "error": "No existe un mensaje con ese id para esta clave.",
                      "codigo": "MENSAJE_NO_ENCONTRADO"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Mensajes"
        ],
        "operationId": "cancelarMensaje",
        "summary": "Cancelar envío programado",
        "x-estado": "proximamente",
        "description": "> 🚧 **Próximamente.** El contrato es definitivo; la implementación está en curso.\n\nCancela un mensaje en estado `scheduled` o `queued` que aún no se ha enviado.",
        "responses": {
          "204": {
            "description": "Cancelado."
          },
          "404": {
            "description": "No existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RUTA_NO_ENCONTRADA": {
                    "value": {
                      "error": "La ruta no existe.",
                      "codigo": "RUTA_NO_ENCONTRADA"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/templates": {
      "get": {
        "tags": [
          "Plantillas"
        ],
        "operationId": "listarPlantillas",
        "summary": "Listar plantillas",
        "description": "Plantillas disponibles con sus diseños y un ejemplo de `data`.",
        "parameters": [
          {
            "name": "channel",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Canal"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Plantillas.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plantillas": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Plantilla"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/devices": {
      "post": {
        "tags": [
          "Dispositivos"
        ],
        "operationId": "registrarDispositivo",
        "summary": "Registrar dispositivo",
        "x-estado": "proximamente",
        "description": "> 🚧 **Próximamente.** El contrato es definitivo; la implementación está en curso.\n\nAsocia un token de FCM a un usuario de tu aplicación para enviarle push por `userId`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "userId",
                  "token",
                  "platform"
                ],
                "properties": {
                  "userId": {
                    "type": "string",
                    "example": "u_123"
                  },
                  "token": {
                    "type": "string"
                  },
                  "platform": {
                    "type": "string",
                    "enum": [
                      "android",
                      "ios",
                      "web"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registrado."
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": [
          "Servicio"
        ],
        "operationId": "salud",
        "summary": "Salud del servicio",
        "security": [],
        "responses": {
          "200": {
            "description": "Servicio en línea.",
            "content": {
              "application/json": {
                "example": {
                  "servicio": "deliver",
                  "estado": "ok"
                }
              }
            }
          }
        }
      }
    },
    "/v1/channels": {
      "get": {
        "tags": [
          "Servicio"
        ],
        "operationId": "listarCanales",
        "summary": "Catálogo de canales",
        "security": [],
        "responses": {
          "200": {
            "description": "Canales disponibles.",
            "content": {
              "application/json": {
                "example": {
                  "canales": [
                    {
                      "id": "email",
                      "nombre": "Email",
                      "proveedor": "Resend",
                      "descripcion": "Correos transaccionales con HTML y texto plano, adjuntos y seguimiento de rebotes.",
                      "destinatario": "Dirección de correo",
                      "disponibilidad": "mvp"
                    },
                    {
                      "id": "sms",
                      "nombre": "SMS",
                      "proveedor": "Twilio",
                      "descripcion": "Mensajes cortos para códigos OTP, recordatorios y alertas, con estado de entrega.",
                      "destinatario": "Teléfono en formato E.164 (+57…)",
                      "disponibilidad": "mvp"
                    },
                    {
                      "id": "whatsapp",
                      "nombre": "WhatsApp",
                      "proveedor": "Meta Cloud API",
                      "descripcion": "Plantillas aprobadas de WhatsApp con botones y variables, y confirmación de lectura.",
                      "destinatario": "Teléfono en formato E.164 (+57…)",
                      "disponibilidad": "siguiente"
                    },
                    {
                      "id": "push",
                      "nombre": "Push",
                      "proveedor": "Firebase Cloud Messaging",
                      "descripcion": "Notificaciones a apps Android, iOS y web por usuario, dispositivo o tema.",
                      "destinatario": "Usuario, token de dispositivo o topic",
                      "disponibilidad": "siguiente"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "tags": [
          "Servicio"
        ],
        "operationId": "miClave",
        "summary": "Datos de mi clave",
        "description": "Útil para comprobar que la integración está bien configurada y en qué ambiente (UAT o PRD) está la clave.",
        "responses": {
          "200": {
            "description": "Emisor y permisos de la clave.",
            "content": {
              "application/json": {
                "example": {
                  "aplicacion": "app-lynx",
                  "emisor": "piensa-it",
                  "ambiente": "uat",
                  "canales": [
                    "email",
                    "sms"
                  ],
                  "permite_contenido_libre": false
                }
              }
            }
          },
          "401": {
            "description": "Clave ausente o inválida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "CLAVE_AUSENTE": {
                    "value": {
                      "error": "Falta la cabecera authorization: Bearer <clave>.",
                      "codigo": "CLAVE_AUSENTE"
                    }
                  },
                  "CLAVE_INVALIDA": {
                    "value": {
                      "error": "La clave no existe o fue revocada.",
                      "codigo": "CLAVE_INVALIDA"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key de tu aplicación."
      }
    },
    "schemas": {
      "Canal": {
        "type": "string",
        "enum": [
          "email",
          "sms",
          "whatsapp",
          "push"
        ]
      },
      "Estado": {
        "type": "string",
        "enum": [
          "scheduled",
          "queued",
          "sending",
          "sent",
          "delivered",
          "read",
          "failed",
          "suppressed",
          "blocked",
          "cancelled"
        ]
      },
      "NuevoMensaje": {
        "type": "object",
        "required": [
          "to"
        ],
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/Canal",
            "description": "Opcional si la plantilla define un solo canal."
          },
          "to": {
            "description": "Teléfono E.164, email, o `{ userId | token | topic }` para push.",
            "oneOf": [
              {
                "type": "string",
                "examples": [
                  "+573001234567",
                  "laura@example.com"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "userId": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  },
                  "topic": {
                    "type": "string"
                  }
                }
              }
            ]
          },
          "cc": {
            "description": "Solo email. Correo o lista de correos.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "bcc": {
            "description": "Solo email. Correo o lista de correos.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "brand": {
            "type": "string",
            "example": "sudivisa",
            "description": "Marca registrada. Por defecto `piensa-it`."
          },
          "template": {
            "type": "object",
            "required": [
              "slug"
            ],
            "properties": {
              "slug": {
                "type": "string",
                "enum": [
                  "factura-electronica",
                  "codigo-otp"
                ]
              },
              "design": {
                "type": "string",
                "enum": [
                  "clasico",
                  "moderno",
                  "minimal"
                ],
                "default": "moderno"
              },
              "version": {
                "type": "integer",
                "description": "Por defecto, la publicada."
              },
              "locale": {
                "type": "string",
                "example": "es",
                "description": "Por defecto, `es`."
              }
            }
          },
          "content": {
            "description": "Contenido libre (requiere permiso en la clave). Excluyente con `template`. Para email usa `ContenidoEmail`.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/ContenidoEmail"
              },
              {
                "type": "object",
                "additionalProperties": true
              }
            ]
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Variables de la plantilla."
          },
          "idempotencyKey": {
            "type": "string",
            "maxLength": 128
          },
          "scheduleAt": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Datos tuyos que vuelven en la consulta y en los webhooks."
          }
        }
      },
      "ContenidoEmail": {
        "type": "object",
        "required": [
          "subject"
        ],
        "description": "Incluye `html`, `text` o ambos. La dirección del remitente la fija Deliver; tú eliges el nombre visible.",
        "properties": {
          "subject": {
            "type": "string",
            "maxLength": 998
          },
          "html": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "fromName": {
            "type": "string",
            "maxLength": 120,
            "example": "Sudivisa",
            "description": "Nombre visible del remitente."
          },
          "replyTo": {
            "type": "string",
            "format": "email",
            "description": "A dónde llegan las respuestas del destinatario."
          },
          "attachments": {
            "type": "array",
            "maxItems": 10,
            "description": "Máximo 10 MB en total (ya decodificados).",
            "items": {
              "type": "object",
              "required": [
                "filename",
                "content"
              ],
              "properties": {
                "filename": {
                  "type": "string",
                  "example": "z09001234560002600000123.zip"
                },
                "content": {
                  "type": "string",
                  "contentEncoding": "base64",
                  "description": "Archivo en base64, sin prefijo data:."
                },
                "contentType": {
                  "type": "string",
                  "example": "application/zip",
                  "description": "Por defecto se infiere de la extensión."
                }
              }
            }
          }
        }
      },
      "MensajeAceptado": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "msg_01J9ZK4Q8R"
          },
          "status": {
            "$ref": "#/components/schemas/Estado"
          },
          "statusDetail": {
            "type": "string",
            "example": "UAT: destinos no autorizados: alguien@empresa.com"
          }
        }
      },
      "Mensaje": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "channel": {
            "$ref": "#/components/schemas/Canal"
          },
          "to": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/Estado"
          },
          "template": {
            "type": "object",
            "properties": {
              "slug": {
                "type": "string"
              },
              "version": {
                "type": "integer"
              }
            }
          },
          "provider": {
            "type": "string",
            "example": "twilio"
          },
          "providerMessageId": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "status": {
                  "$ref": "#/components/schemas/Estado"
                },
                "at": {
                  "type": "string",
                  "format": "date-time"
                },
                "detail": {
                  "type": "string"
                }
              }
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "Plantilla": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "nombre": {
            "type": "string"
          },
          "channel": {
            "$ref": "#/components/schemas/Canal"
          },
          "version": {
            "type": "integer"
          },
          "locales": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "variables": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "cliente.nombre",
              "valor",
              "vence"
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "codigo"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "codigo": {
            "type": "string",
            "enum": [
              "CLAVE_AUSENTE",
              "CLAVE_INVALIDA",
              "CLAVE_NO_VERIFICABLE",
              "CANAL_NO_PERMITIDO",
              "RUTA_NO_ENCONTRADA",
              "METODO_NO_PERMITIDO",
              "PETICION_INVALIDA",
              "CONTENIDO_LIBRE_NO_PERMITIDO",
              "CANAL_NO_DISPONIBLE",
              "ADJUNTOS_DEMASIADO_GRANDES",
              "MENSAJE_NO_ENCONTRADO",
              "MARCA_NO_ENCONTRADA",
              "PLANTILLA_NO_ENCONTRADA",
              "VARIABLES_FALTANTES",
              "DESTINATARIO_SUPRIMIDO",
              "LIMITE_EXCEDIDO",
              "SESION_INVALIDA",
              "SIN_ACCESO",
              "PERMISO_INSUFICIENTE",
              "CONFLICTO",
              "ERROR_INTERNO"
            ]
          },
          "detalle": {}
        }
      }
    }
  }
}