# Códigos de error: Canales: envío directo a Meta y SES

> Todo mensaje que sale a WhatsApp, Messenger, Instagram o correo pasa por `ChannelSendService` (`src/channels/send/`). Cada error es un `ChannelSendError` con `code`, `reason_code`, `retry_after_seconds` y `retry_at`.

**Cómo se detecta (toda la familia):** Respuesta del envío (`code` + `reason_code`). Mensaje fallido: `channel_messages.status = 'failed'` con `error_code` / `error_json`. Render: `channel_send_rejected`, `channel_send_unknown_delivery`, `channel_send_prepare_failed`.

**Qué hacer (toda la familia):** Guíate por el `reason_code` (ver la familia «Motivos de envío»): plantilla si la ventana está cerrada, esperar si es límite, reconectar si la cuenta no puede enviar, corregir el mensaje si está mal armado.

Lo que un código no dice lo hereda de la familia.

## `CHANNEL_SEND_ACCOUNT_NOT_FOUND`

- **Tipo:** HTTP · **HTTP:** 404 · **Estado:** vigente
- **Qué significa:** La cuenta de canal del envío (o del comentario) no existe en esta entidad.

## `CHANNEL_SEND_ACCOUNT_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Falta el número emisor (entity_channel_account_id).

## `CHANNEL_SEND_ACTOR_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Un envío de automatización necesita workflow_id y execution_id (o, desde el camino de hoy, su usuario y service_kind).

## `CHANNEL_SEND_ASSET_INVALID`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** asset_id no reconocido (se espera mlib:<id> u obj:<llave>).

## `CHANNEL_SEND_ASSET_NOT_FOUND`

- **Tipo:** HTTP · **HTTP:** 422 · **Estado:** vigente
- **Qué significa:** El archivo no existe en esta entidad.

## `CHANNEL_SEND_ASSET_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Falta asset_id.

## `CHANNEL_SEND_AUDIT_UNAVAILABLE`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** No se pudo registrar la decisión del envío; el mensaje no sale sin auditoría.

## `CHANNEL_SEND_AUTH_TEMPLATE_NEEDS_PHONE`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Las plantillas de autenticación exigen el teléfono del destinatario.

## `CHANNEL_SEND_AUTHZ_DECISION_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Falta la decisión de autorización (authz_decision_id) del envío.

## `CHANNEL_SEND_CHANNEL_UNSUPPORTED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** La salida directa no envía por el tipo de canal de esa cuenta.

## `CHANNEL_SEND_CLIENT_MESSAGE_ID_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** client_message_id es obligatorio (1–64 caracteres [A-Za-z0-9:_.-]).

## `CHANNEL_SEND_COMMAND_INVALID`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Comando de envío inválido.

## `CHANNEL_SEND_COMMENT_NOT_FOUND`

- **Tipo:** HTTP · **HTTP:** 404 · **Estado:** vigente
- **Qué significa:** El comentario no existe en esta entidad.

## `CHANNEL_SEND_CONTACT_RATE_LIMITED`

- **Tipo:** HTTP · **HTTP:** 429 · **Estado:** vigente
- **Qué significa:** Tope de envíos automáticos a un mismo contacto (429). Solo corta con `CHANNELS_SEND_CONTACT_CAP_MODE=enforce`; en modo observar el mensaje sale.

## `CHANNEL_SEND_CONTENT_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Falta el contenido.

## `CHANNEL_SEND_DETACHED_TEXT_ONLY`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Un envío sin chat (mensaje de prueba) solo manda texto.

## `CHANNEL_SEND_DETACHED_WHATSAPP_ONLY`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Un envío sin chat (mensaje de prueba) solo sale por WhatsApp.

## `CHANNEL_SEND_EGRESS_CHANGED`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** La salida de este número cambió mientras se enviaba; vuelva a intentarlo.

## `CHANNEL_SEND_EMAIL_TRANSPORT_UNAVAILABLE`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** El envío de correo desde Boost todavía no está disponible.

## `CHANNEL_SEND_ENTITY_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Falta la entidad del envío.

## `CHANNEL_SEND_FAILED`

- **Tipo:** detalle, HTTP · **HTTP:** 400, 429, 502 · **Estado:** vigente
- **Qué significa:** El envío falló. El `reason_code` y el `title` dicen por qué (límite de Meta, rechazo, mensaje mal armado); también lo devuelve un reintento con un `client_message_id` que ya había fallado.
- **Cómo detectarlo:** Base: `channel_messages.status = 'failed'` con `error_code` y `error_json`. Render: `channel_send_rejected … meta=<n>`.

## `CHANNEL_SEND_FALLBACK_TEMPLATE_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** outside_window = fallback_template exige fallback_template.

## `CHANNEL_SEND_FIELD_INVALID`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Un campo del mensaje tiene un valor inválido para el canal (p. ej. un botón con URL que no es https). El `title` dice cuál.

## `CHANNEL_SEND_FIELD_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Falta un campo obligatorio del mensaje (p. ej. la reacción sin el mensaje al que responde). El `title` dice cuál.

## `CHANNEL_SEND_HUMAN_AGENT_NOT_ALLOWED`

- **Tipo:** HTTP · **HTTP:** 403 · **Estado:** vigente
- **Qué significa:** Solo una persona puede enviar con la etiqueta HUMAN_AGENT.

## `CHANNEL_SEND_IN_FLIGHT`

- **Tipo:** detalle · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Hay un envío en curso (o sin respuesta de Meta) con este client_message_id; no se reenvía.

## `CHANNEL_SEND_INSTAGRAM_FILE_UNSUPPORTED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Se intentó mandar por Instagram un archivo que no es PDF (Word, Excel, zip…). Instagram solo acepta PDF como archivo (además de imagen, video y audio); el envío se niega antes de registrar nada.
- **Causa típica:** Desde el chat: se adjuntó un documento con el compositor en Instagram. Desde un workflow: `comms.send_document` por Instagram con un archivo que no es PDF.
- **Qué hacer:** Mandar el documento como PDF, o por WhatsApp, Messenger o correo, que sí lo aceptan.

## `CHANNEL_SEND_LEGACY_ROUTE_MISMATCH`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** El chat no coincide con la ruta resuelta.

## `CHANNEL_SEND_LIMITS_EXCEEDED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** El mensaje pasa un límite del canal (largo del texto, cantidad de botones, filas o adjuntos). El `title` dice cuál.

## `CHANNEL_SEND_MEDIA_EMPTY`

- **Tipo:** HTTP · **HTTP:** 409, 422 · **Estado:** vigente
- **Qué significa:** El archivo vino vacío.

## `CHANNEL_SEND_MEDIA_LINK_INVALID`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** La media del encabezado no es una URL válida.

## `CHANNEL_SEND_MEDIA_LINK_NOT_ALLOWED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** La media del encabezado de la plantilla debe llegar como asset_id o id de Meta, no como URL libre.

## `CHANNEL_SEND_MEDIA_LINK_UNAVAILABLE`

- **Tipo:** HTTP · **HTTP:** 422 · **Estado:** vigente
- **Qué significa:** No se pudo bajar la media que venía como enlace (no responde, no existe o es demasiado grande).

## `CHANNEL_SEND_MEDIA_NOT_RESOLVED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Falta la URL firmada del adjunto.

## `CHANNEL_SEND_MEDIA_STORAGE_NOT_CONFIGURED`

- **Tipo:** HTTP · **HTTP:** 409, 422 · **Estado:** vigente
- **Qué significa:** El almacenamiento de media no está configurado.

## `CHANNEL_SEND_MEDIA_TOO_LARGE`

- **Tipo:** HTTP · **HTTP:** 409, 422 · **Estado:** vigente
- **Qué significa:** El archivo supera el tamaño que acepta WhatsApp.

## `CHANNEL_SEND_MEDIA_UPLOAD_NO_ID`

- **Tipo:** HTTP · **HTTP:** 422 · **Estado:** vigente
- **Qué significa:** Meta no devolvió el id de la media subida.

## `CHANNEL_SEND_MEDIA_UPLOAD_UNREACHABLE`

- **Tipo:** HTTP · **HTTP:** 422 · **Estado:** vigente
- **Qué significa:** No se pudo subir la media a Meta (red o Meta sin responder). No se envió nada al cliente: reintentar es seguro.

## `CHANNEL_SEND_MESSAGE_NOT_FAILED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Solo se reintenta un mensaje fallido.

## `CHANNEL_SEND_MESSAGE_NOT_FOUND`

- **Tipo:** HTTP · **HTTP:** 404 · **Estado:** vigente
- **Qué significa:** El mensaje no existe en esta entidad.

## `CHANNEL_SEND_MESSAGE_NOT_RETRYABLE`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Este mensaje no se puede reintentar desde el chat.

## `CHANNEL_SEND_PAGE_ID_MISSING`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** La cuenta de Messenger/Instagram no tiene página de Facebook guardada: no puede enviar.

## `CHANNEL_SEND_PHONE_NUMBER_ID_MISSING`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** La cuenta de WhatsApp no tiene `phoneNumberId` de Meta guardado: no puede enviar.

## `CHANNEL_SEND_PHONE_THROUGHPUT`

- **Tipo:** HTTP · **HTTP:** 429 · **Estado:** vigente
- **Qué significa:** Se alcanzó el tope de envíos por segundo de este número.

## `CHANNEL_SEND_PREPARE_FAILED`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** No se pudo preparar el envío.

## `CHANNEL_SEND_PRINCIPAL_INVALID`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Tipo de actor desconocido.

## `CHANNEL_SEND_PRINCIPAL_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Falta el actor del envío.

## `CHANNEL_SEND_PROVIDER_ERROR`

- **Tipo:** detalle · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Meta rechazó el envío sin un código de error legible; el `title` lleva el texto de Meta.

## `CHANNEL_SEND_PROVIDER_RATE_LIMITED`

- **Tipo:** detalle, HTTP · **HTTP:** 429 · **Estado:** vigente
- **Qué significa:** Meta está limitando los envíos de esta cuenta (número, página o WABA). Reintentar antes del tiempo que dice Meta no adelanta nada.
- **Qué hacer:** Esperar `retry_after_seconds` (o `retry_at`); si viene en `null`, Meta no dijo cuánto y Boost no lo inventa.

## `CHANNEL_SEND_REACTION_TARGET_NOT_FOUND`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** No se encontró el mensaje al que se reacciona.

## `CHANNEL_SEND_RECIPIENT_INVALID`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** El contacto no tiene un id de Messenger/Instagram válido.

## `CHANNEL_SEND_RETRY_ROUTE_MISMATCH`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** El reintento no corresponde a este chat.

## `CHANNEL_SEND_TARGET_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Falta el chat destino.

## `CHANNEL_SEND_TARGET_UNSUPPORTED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Iniciar un chat nuevo todavía no sale por la salida directa.

## `CHANNEL_SEND_THREAD_ACCOUNT_MISMATCH`

- **Tipo:** HTTP · **HTTP:** 404 · **Estado:** vigente
- **Qué significa:** El hilo es de otra cuenta.

## `CHANNEL_SEND_THREAD_NOT_FOUND`

- **Tipo:** HTTP · **HTTP:** 404 · **Estado:** vigente
- **Qué significa:** No hay un hilo de esta cuenta para ese chat en Messenger/Instagram. La salida directa no crea hilos: solo responde en uno que ya existe. Si la conversación lleva el hilo de otra cuenta (el chat único del contacto suele llevar el de WhatsApp), antes de fallar busca el de esta página por la ruta activa del contacto y por la identidad (PSID/IGSID) del destinatario.
- **Causa típica:** El contacto no tiene ni ruta ni identidad con hilo en esta página: nunca le escribió a esta cuenta en Boost (la conversación empezó por otro canal o por otra página), o no tiene su id de Messenger/Instagram.
- **Qué hacer:** Esperar a que el cliente escriba primero por esa página; no se puede iniciar un chat de Messenger/Instagram desde Boost.

## `CHANNEL_SEND_TOKEN_UNAVAILABLE`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** No se pudo obtener el token de la cuenta desde la bóveda; el mensaje dice el `VAULT_*` que respondió.
- **Qué hacer:** Si es `VAULT_SECRET_NOT_FOUND` o `VAULT_SECRET_EXPIRED`, reconectar la cuenta del canal.

## `CHANNEL_SEND_TRANSPORT_DENIED`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** El worker de salida no tiene permitido enviar por esa cuenta (el principal de servicio lo negó).

## `CHANNEL_SEND_UNKNOWN_DELIVERY`

- **Tipo:** detalle, HTTP · **HTTP:** 504 · **Estado:** vigente
- **Qué significa:** Meta no respondió a tiempo y no se sabe si el mensaje salió (504). No se reintenta solo para no duplicar.

## `CHANNEL_SEND_UNSUPPORTED_CONTENT`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Contenido no soportado por WhatsApp.

## `CHANNEL_SEND_WABA_MISSING`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** La cuenta de WhatsApp no tiene su WABA guardada: no se pueden usar plantillas.

## `CHANNEL_SEND_WINDOW_CLOSED`

- **Tipo:** detalle · **HTTP:** — · **Estado:** vigente
- **Qué significa:** Ventana cerrada: el envío se omitió (outside_window = skip).

## `CHANNEL_TEMPLATES_DIRECT_UNAVAILABLE`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** vigente
- **Qué significa:** Las plantillas directas a Meta no están disponibles para esta cuenta (409).

## `CHANNEL_TEMPLATES_MIGRATION_INVALID_WABA`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** source_waba_id no es el id numérico de una WABA.

## `CHANNEL_TEMPLATES_MIGRATION_SAME_WABA`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** La WABA de origen es la misma de la cuenta: no hay plantillas que migrar.

## `CHANNEL_TEMPLATES_PROVIDER_REJECTED`

- **Tipo:** HTTP · **HTTP:** 400, 502 · **Estado:** vigente
- **Qué significa:** Meta rechazó el pedido de plantillas (400 si el pedido está mal, 502 si es de Meta).

## `CHANNEL_TEMPLATES_PROVIDER_UNAVAILABLE`

- **Tipo:** HTTP · **HTTP:** 504 · **Estado:** vigente
- **Qué significa:** Meta no respondió al pedir las plantillas.

## `CHANNEL_TEMPLATES_RATE_LIMITED`

- **Tipo:** HTTP · **HTTP:** 429 · **Estado:** vigente
- **Qué significa:** Meta está limitando las consultas de plantillas de esta cuenta de WhatsApp. Reintentar antes no adelanta la recuperación.

## `EMAIL_ATTACHMENT_NOT_AVAILABLE`

- **Tipo:** HTTP · **HTTP:** 502 · **Estado:** vigente
- **Qué significa:** No se pudo leer un adjunto del almacenamiento de Boost.

## `EMAIL_ATTACHMENTS_TOO_LARGE`

- **Tipo:** HTTP · **HTTP:** 413 · **Estado:** vigente
- **Qué significa:** Los adjuntos superan 20 MB.

## `EMAIL_AUTOMATED_THREAD_LIMIT`

- **Tipo:** HTTP · **HTTP:** 429 · **Estado:** vigente
- **Qué significa:** Tope de correos automáticos por hilo: una automatización (workflow o copiloto) ya mandó `CHANNELS_EMAIL_AUTOMATED_PER_THREAD_24H` correos (3 por defecto) a ese hilo en 24 h y el siguiente no sale (429, `reason_code` `CONTACT_RATE_LIMITED`, `retry_after_seconds` = cuándo vence el más viejo). No se persiste nada ni se llama a SES. Una persona nunca cuenta ni se frena.
- **Cómo detectarlo:** Respuesta (`code`). La negación queda en `authz_decisions` (`allowed = 0`, `reason_code = 'CONTACT_RATE_LIMITED'`, `detail.limit_key = 'email_thread_24h'`).
- **Qué hacer:** No reintentes antes de `retry_after_seconds`: es el candado contra bucles de correo, no una falla. Si el tope es corto para un caso legítimo, se ajusta con la variable, no con código.

## `EMAIL_PROVIDER_REJECTED`

- **Tipo:** HTTP · **HTTP:** 400, 502 · **Estado:** vigente
- **Qué significa:** SES rechazó el correo.
- **Causa típica:** Dirección de destino inválida, contenido rechazado, o el buzón o el dominio no pueden enviar en SES.
- **Cómo detectarlo:** Respuesta del envío (400 si es del mensaje, 502 si es de SES). Render: líneas del adaptador de SES.
- **Qué hacer:** Revisar la dirección y el contenido. Si pasa con todos los correos del buzón, revisar el dominio y el buzón en Configuración → Canales.

## `EMAIL_PROVIDER_THROTTLED`

- **Tipo:** HTTP · **HTTP:** 429 · **Estado:** vigente
- **Qué significa:** SES está limitando los envíos; intente más tarde.

## `EMAIL_TEMPLATE_UNSUPPORTED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Las plantillas de correo todavía no salen por Boost; mande el cuerpo.

## `WHATSAPP_SESSION_EXPIRED`

- **Tipo:** detalle, HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** La ventana de 24 h de WhatsApp está cerrada (o nunca se abrió): solo se puede enviar una plantilla aprobada.
- **Causa típica:** El cliente no escribió en las últimas 24 h, o ese número todavía no tiene conversación con el contacto.
- **Cómo detectarlo:** Respuesta 400 con `reason_code: WINDOW_CLOSED`. Con `outside_window = skip` no es error: el envío se omite y viene como `error` del resultado.
- **Qué hacer:** Mandar una plantilla preaprobada; cuando el cliente responda, se abre la ventana.

## `WHATSAPP_SESSION_TEMPLATE_COOLDOWN`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** vigente
- **Qué significa:** Se mandó una plantilla a este contacto hace muy poco: hay que esperar antes de otra (tope de plantillas).

## `CHANNEL_SEND_EGRESS_NOT_BOOST`

- **Tipo:** HTTP · **HTTP:** 409 · **Estado:** por retirar
- **Qué significa:** La cuenta no sale por Boost (`egress_owner` distinto de `boost`): la salida directa no la toca y no tiene otro camino. Hay que pasarla a Boost.
- **por retirar:** Muere con los interruptores por cuenta.

## `CHANNEL_SEND_LEGACY_ROUTE_REQUIRED`

- **Tipo:** HTTP · **HTTP:** 400 · **Estado:** por retirar
- **Qué significa:** La cuenta no está en ingreso `boost`: el chat, el destinatario y la ventana tienen que venir del camino viejo y no vinieron.
- **por retirar:** Todavía existe, pero va a desaparecer junto con el camino que lo produce. No lo uses en código nuevo.
