Skip to main content

Estado failed de mensajes

Los canales de Meta (Instagram, y Facebook en el backend) pueden rechazar un envío. Cuando eso ocurre el backend marca el mensaje con ack: failed y un failureReason, y avisa por socket. El frontend lo muestra en el hilo y en la lista de chats. Contraparte: Message y Meta.

Tipos (core/interfaces/api/message.interfaces.ts)​

export type MessageAck = 'sent' | 'delivered' | 'read' | 'failed';

export type MessageFailureReason =
| 'out_of_window' | 'auth_error' | 'rate_limited'
| 'unsupported_media' | 'recipient_unavailable' | 'unknown';

interface TicketMessage {
ack: MessageAck;
failureReason?: MessageFailureReason;
failureDetail?: string; // detalle crudo del proveedor
// ...
}

MessageAck vive en message.interfaces.ts; ticket.interface.ts ya no tiene su propia copia y lo reexporta (export type { MessageAck }) para que no diverjan. ChatMessage (message-chat.interface.ts) añade failureReason?. El comentario del código aclara que la copy se resuelve con i18n y que el detalle crudo del proveedor no se muestra (failureDetail existe en el tipo pero ninguna plantilla lo usa).

Evento de socket​

message_ack_changed (MessageAckChangedPayload) incorpora failureReason?:

{ ticketId, messageIds: string[], ack, at, failureReason? }

ticket_updated (TicketUpdatedPayload) incorpora messageId? y ack?, de modo que si el último mensaje de un ticket nace fallido o cambia, la lista lo sabe.

Dónde se refleja​

Hilo (ChatConversationComponent)​

  1. La suscripción a messageAckChanged$ (filtrada por el ticket abierto) llama a applyAck(messageIds, ack, failureReason).
  2. applyAck actualiza los mensajes cuyo _id está en messageIds: asigna ack y, si ack === 'failed', asigna failureReason (con 'unknown' como valor por defecto si no llega).
  3. La plantilla, solo para mensajes propios (isOwnMessage), muestra:
message.ackIcono
failedfa-exclamation-circle con clase msg-failed y title = messages.failure.<failureReason ?? 'unknown'> traducido
sent o vacíoun check (fa-check)
delivered / readdoble check; el de read en azul (--zg-chat-ack-read)

Al construir los mensajes desde la API, failureReason se copia del mensaje (failureReason: msg.failureReason), de modo que un mensaje fallido sigue marcado tras recargar el hilo.

Lista (ChatListItemComponent y NgRx)​

  • ChatListComponent.subscribeToMessagesRead despacha TicketActions.ticketLastMessageAckChanged({ ticketId, messageIds, ack }) por cada message_ack_changed, además de ticketClearUnread cuando ack === 'read'.
  • El reducer actualiza lastMessage.ack del ticket solo si el _id del último mensaje está entre messageIds.
  • El ticket se transforma a conversación con lastMessageAck: ticket.lastMessage?.ack.
  • En la tarjeta, la insignia de acuse (visible cuando no hay no leídos, el último mensaje es del agente al contacto y el usuario es agente) muestra fa-exclamation-circle con la clase chat-card__ack--failed cuando lastMessageAck === 'failed'. Esta variante no lleva tooltip.

Motivos y textos (messages.failure.*)​

failureReasonTexto (en)
out_of_windowThe channel does not allow replies more than 24 hours after the contact's last message. You will be able to reply once they write again.
auth_errorThe connection lost its channel authorization. Ask an administrator to link it again.
rate_limitedThe channel rejected the message because too many were sent. Try again in a few minutes.
unsupported_mediaThe channel does not accept this file type.
recipient_unavailableThe contact is not available right now: they may have blocked the account or deactivated their profile.
unknownThe channel rejected the message. Tell an administrator if it happens again.

es.json tiene las traducciones equivalentes. En el backend, out_of_window corresponde al código 10 de la Graph API y auth_error al 190 (que además marca la conexión como disconnected, lo que en el frontend lleva a la acción "Reautorizar" de la tarjeta; ver OAuth de Instagram).

Estilos​

src/styles.scss define --zg-chat-ack-failed: #e5484d en tema claro y #ff6b60 en body.dark-only. El comentario explica que es un rojo propio y no el color de acento del tema: un fallo debe leerse como fallo con cualquier marca del tenant. .msg-failed (en chat-conversation.component.scss) usa font-size: 10px y cursor: help; .chat-card__ack--failed (en chat-list-item.component.scss) usa el mismo color.

Limitaciones observadas​

  • El texto "tocar para reintentar" (sendFailedRetry) existe en i18n para el chat interno, no para este estado: no hay botón de reintento para mensajes failed de canal.
  • La tarjeta de la lista no explica el motivo; el motivo solo está en el tooltip del hilo.