Compositor, adjuntos y envío programado
Parte inferior de ChatConversationComponent (ver Hilo de mensajes). Solo se pinta cuando el ticket no es de solo lectura (resuelto, cerrado, cancelado o vista forzada). Todo el envío del agente sale por socket (ChatSocketService), no por REST: ver Tiempo real y estado para el ciclo de vida del socket y Chat para el contexto.
Archivos
| Archivo | Rol |
|---|---|
chat-conversation/chat-conversation.component.* | Todo el compositor: texto, emoji, respuestas rápidas, firma, nota, adjuntos, audio |
schedule-send-button/schedule-send-button.component.* | Botón "Programar envío" (standalone) |
schedule-message-dialog/schedule-message-dialog.component.* y .service.ts | Diálogo de fecha, hora y texto (standalone) |
scheduled-message-strip/scheduled-message-strip.component.* | Fila de envíos programados pendientes del ticket (standalone) |
core/services/api/file-api.service.ts | Subida y validación de archivos |
core/services/api/quick-response.service.ts | Búsqueda de respuestas rápidas |
core/states/scheduled-message/* | Slice de mensajes programados |
Barra de herramientas
Fila de botones a la izquierda del cuadro de texto, en este orden:
| Botón | Estado | Efecto |
|---|---|---|
| Emoji | showEmojiPicker | Carga bajo demanda emoji-mart (em-emoji-picker, tema oscuro) y añade el emoji al final del texto |
Adjuntar (+) | showAttachMenu | Menú con Imagen, Video y Documento; muestra un spinner mientras sube (isUploadingFile) |
| Nota interna | isNoteMode | El siguiente envío es una nota interna (evento send-note); se desactiva al enviar |
| Firma | signatureEnabled (activa por defecto) | Indica si el mensaje se firma con el nombre del agente |
| Respuestas rápidas | quickResponsesManualOpen | Abre el selector sin escribir / |
| Programar envío | Ver más abajo | Abre el diálogo de programación |
resetConversation restaura nota, firma, selector y respuesta citada al cambiar de ticket.
Envío de texto
sendMessage ignora el texto vacío y no hace nada si el socket no está autenticado (isConnectionLive), sin borrar lo escrito. Según el modo:
| Modo | Emisión | Payload |
|---|---|---|
| Mensaje | send-message | { ticketId, content, signature, parentId? } |
| Nota | send-note | { ticketId, content } |
Enter envía; Shift+Enter inserta salto de línea. El textarea crece hasta 120 px. signature viaja siempre; en el backend, un valor ausente se interpreta como "firmar" (SendMessageDto.signature) y false evita la firma. El hilo recibe el mensaje de vuelta por el evento new_message (no hay eco local).
Responder (cita)
startReply(message) guarda replyingTo; el compositor muestra una barra con el remitente y el texto citado y un botón para cancelar. El parentId se incluye en el mensaje de texto, en los adjuntos y en el envío programado. Se limpia tras enviar.
Respuestas rápidas
- Escribir
/al inicio del texto dispara una búsqueda con debounce de 200 ms:GET /quick-responses/get-allconsearch(lo escrito tras/),ticketId, página 1 y límite 10. El backend filtra por el departamento del ticket y solo devuelve las activas; ver Respuestas rápidas y Quick response. - El selector muestra
/atajoy el mensaje con formato;ArrowUpyArrowDownnavegan,Enterelige,Escapecierra. Elegir una respuesta reemplaza el texto del compositor por el mensaje de la respuesta y devuelve el foco altextarea. - Abierto manualmente muestra "sin resultados" si no hay coincidencias.
Adjuntos
Flujo común de _uploadAndSend(file, filename?):
- Validación en el cliente con
FileApiService(si falla, toast de error y no se sube). POST /files/upload(multipart, campofile) que devuelve el archivo con su_id.- Emisión
send-filecon{ ticketId, fileId, parentId?, caption? }. Elcaptiones el texto que hubiera en el compositor en ese momento; después se limpia el texto y se cancela la cita.
| Tipo | Tipos MIME permitidos | Límite |
|---|---|---|
| Imagen | image/jpeg, image/png, image/gif, image/webp | 20 MB |
| Video | video/mp4, video/webm, video/quicktime, video/x-msvideo, video/x-matroska, video/mpeg, video/3gpp | 100 MB |
| Documento | application/pdf, application/msword, .docx (application/vnd.openxmlformats-officedocument.wordprocessingml.document) | 10 MB |
| Audio | Sin validación (validateAudio siempre acepta) | Sin límite |
El mensaje de error de video menciona "MP4, WebM, MOV, AVI o MKV" aunque ALLOWED.video también acepta video/mpeg y video/3gpp. Si la subida falla se muestra "Error al subir el archivo." (texto fijo). El backend decide cómo entregar el archivo según el canal; ver Message y File.
Notas de voz
Usa getUserMedia y MediaRecorder con estados audioState: idle, recording, paused y preview.
startRecordingpide el micrófono; si falla, toast "No se pudo acceder al micrófono.".- Mientras graba muestra un cronómetro (
formatRecordingTime,mm:ss) y permite pausar y reanudar. stopRecordinggenera unBlobaudio/webmy pasa apreviewcon un<audio>para escucharlo.sendAudiolo sube comoaudio_<marca de tiempo>.webmpor el mismo flujo de adjuntos;cancelAudiolibera el micrófono y revoca la URL del blob.ngOnDestroytambién cancela.
Envío programado
Superficie principal de la función descrita en Mensajes programados (la pantalla de "mis envíos" es la secundaria).
- Botón:
app-schedule-send-buttonsolo se pinta sischeduledMessagesEnabled$es verdadero y el usuario tieneMESSAGE_CREATE. El indicador sale deselectScheduledMessagesEnabled, que leefeatureFlags.scheduledMessagesEnableddel perfil del usuario (auth.selectors.ts), no de la configuración del tenant. El botón se deshabilita con el texto vacío y solo emiteschedule. - Diálogo (
ScheduleMessageDialogService.open, modalsm, estático y sin cierre por teclado): campos de texto y fecha/hora conDateTimePickerComponent; confirmar exige texto no vacío y fecha futura. Resuelve{ body, scheduledAt }onullsi se cancela. - Crear:
onScheduleSendabre el diálogo con el texto actual y, al confirmar, despachaScheduledMessageActions.createconticketId,body,scheduledAt(ISO),messageType(noteochat),signature(solo si la firma está activa y no es nota) yparentId?. Limpia el texto, sale del modo nota y cancela la cita. Endpoint:POST /scheduled-messages/create. - Pendientes del ticket:
app-scheduled-message-stripmuestra los pendientes del ticket (selectPendingByTicket), un bloque hermano de la lista de mensajes para no confundirlos con mensajes enviados. Editar reabre el diálogo con el texto y la fecha actuales y despachaupdateincluyendoscheduledAtsolo si cambió; cancelar despachacancel. Se rehidratan al abrir el hilo (loadByTicket) y la lista de tickets carga los pendientes propios (loadMinecon estadopending) para el contador de las tarjetas.
Pruebas
| Spec | Cubre |
|---|---|
chat-conversation.component.spec.ts | sendMessage con y sin parentId, texto vacío, conservación del texto con el socket caído, onScheduleSend (diálogo, create, nota, parentId, cancelación), visibilidad del botón, adjuntos con leyenda (manda lo escrito, limpia el compositor, no manda leyenda si está vacío) |
schedule-send-button.component.spec.ts | Emite al hacer clic y no emite deshabilitado |
schedule-message-dialog.component.spec.ts | Precarga de texto y fecha, rechazo de fecha pasada o texto vacío, confirmación y cancelación |
scheduled-message-strip.component.spec.ts | Pendientes por ticket, cancelar, editar (con y sin cambio de fecha) |
postpone-dialog.component.spec.ts | Ver Acciones del ticket |
No hay specs para grabación de audio, respuestas rápidas del compositor, selector de emoji ni subida de archivos.
Gotchas
- La firma, las notas y los adjuntos no tienen directivas de permiso en el cliente; los controla el backend (
MESSAGE_*y el gateway). Solo el envío programado exigeMESSAGE_CREATEen la UI. - Elegir una respuesta rápida sobrescribe el texto en lugar de insertarlo en la posición del cursor.
- Un adjunto usa como leyenda cualquier texto pendiente del compositor, incluso si el agente lo escribía para otro mensaje.
- Los textos de varios toasts y tooltips (adjuntos, firma, nota) están en español fijo en el componente.