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)
- La suscripción a
messageAckChanged$(filtrada por el ticket abierto) llama aapplyAck(messageIds, ack, failureReason). applyAckactualiza los mensajes cuyo_idestá enmessageIds: asignaacky, siack === 'failed', asignafailureReason(con'unknown'como valor por defecto si no llega).- La plantilla, solo para mensajes propios (
isOwnMessage), muestra:
message.ack | Icono |
|---|---|
failed | fa-exclamation-circle con clase msg-failed y title = messages.failure.<failureReason ?? 'unknown'> traducido |
sent o vacío | un check (fa-check) |
delivered / read | doble 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.subscribeToMessagesReaddespachaTicketActions.ticketLastMessageAckChanged({ ticketId, messageIds, ack })por cadamessage_ack_changed, además deticketClearUnreadcuandoack === 'read'.- El reducer actualiza
lastMessage.ackdel ticket solo si el_iddel último mensaje está entremessageIds. - 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-circlecon la clasechat-card__ack--failedcuandolastMessageAck === 'failed'. Esta variante no lleva tooltip.
Motivos y textos (messages.failure.*)
failureReason | Texto (en) |
|---|---|
out_of_window | The 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_error | The connection lost its channel authorization. Ask an administrator to link it again. |
rate_limited | The channel rejected the message because too many were sent. Try again in a few minutes. |
unsupported_media | The channel does not accept this file type. |
recipient_unavailable | The contact is not available right now: they may have blocked the account or deactivated their profile. |
unknown | The 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 mensajesfailedde canal. - La tarjeta de la lista no explica el motivo; el motivo solo está en el tooltip del hilo.