valtech-components 4.0.226 → 4.0.227

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
56
56
  * Current version of valtech-components.
57
57
  * This is automatically updated during the publish process.
58
58
  */
59
- const VERSION = '4.0.226';
59
+ const VERSION = '4.0.227';
60
60
 
61
61
  // Control de estado de refresco (singleton a nivel de módulo)
62
62
  let isRefreshing = false;
@@ -72171,10 +72171,21 @@ class ConversationService {
72171
72171
  return this.http.delete(`${this.baseUrl}/conversations/${convId}/participants/${userId}`);
72172
72172
  }
72173
72173
  /**
72174
- * Envia un mensaje a una conversacion.
72174
+ * Envia un mensaje a una conversacion, opcionalmente con adjuntos.
72175
+ * El backend espera `attachments: { url, type, name, size }[]` (key `type` = MIME),
72176
+ * mientras que el front usa `mimeType` — se mapea aqui.
72175
72177
  */
72176
- sendMessage(convId, body) {
72177
- return this.http.post(`${this.baseUrl}/conversations/${convId}/messages`, { body });
72178
+ sendMessage(convId, body, attachments) {
72179
+ const payload = { body };
72180
+ if (attachments?.length) {
72181
+ payload['attachments'] = attachments.map(a => ({
72182
+ url: a.url,
72183
+ type: a.mimeType,
72184
+ name: a.name,
72185
+ size: a.size ?? 0,
72186
+ }));
72187
+ }
72188
+ return this.http.post(`${this.baseUrl}/conversations/${convId}/messages`, payload);
72178
72189
  }
72179
72190
  /**
72180
72191
  * Elimina (marca como eliminado) un mensaje.
@@ -72401,7 +72412,8 @@ class MessageBubbleComponent {
72401
72412
  return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
72402
72413
  });
72403
72414
  this.imageAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('image/')));
72404
- this.fileAttachments = computed(() => (this.msg().attachments ?? []).filter(a => !a.mimeType?.startsWith('image/')));
72415
+ this.audioAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('audio/')));
72416
+ this.fileAttachments = computed(() => (this.msg().attachments ?? []).filter(a => !a.mimeType?.startsWith('image/') && !a.mimeType?.startsWith('audio/')));
72405
72417
  this.statusIcon = computed(() => {
72406
72418
  switch (this.msg().status) {
72407
72419
  case 'sending':
@@ -72472,6 +72484,13 @@ class MessageBubbleComponent {
72472
72484
  }
72473
72485
  </div>
72474
72486
  }
72487
+ @if (audioAttachments().length > 0) {
72488
+ <div class="audios">
72489
+ @for (att of audioAttachments(); track att.url) {
72490
+ <audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
72491
+ }
72492
+ </div>
72493
+ }
72475
72494
  @if (fileAttachments().length > 0) {
72476
72495
  <div class="files">
72477
72496
  @for (att of fileAttachments(); track att.url) {
@@ -72535,7 +72554,7 @@ class MessageBubbleComponent {
72535
72554
  }
72536
72555
  </div>
72537
72556
  </div>
72538
- `, isInline: true, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
72557
+ `, isInline: true, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.audios{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-audio{width:240px;max-width:100%;height:40px}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
72539
72558
  }
72540
72559
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, decorators: [{
72541
72560
  type: Component,
@@ -72580,6 +72599,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
72580
72599
  }
72581
72600
  </div>
72582
72601
  }
72602
+ @if (audioAttachments().length > 0) {
72603
+ <div class="audios">
72604
+ @for (att of audioAttachments(); track att.url) {
72605
+ <audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
72606
+ }
72607
+ </div>
72608
+ }
72583
72609
  @if (fileAttachments().length > 0) {
72584
72610
  <div class="files">
72585
72611
  @for (att of fileAttachments(); track att.url) {
@@ -72643,7 +72669,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
72643
72669
  }
72644
72670
  </div>
72645
72671
  </div>
72646
- `, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"] }]
72672
+ `, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.audios{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-audio{width:240px;max-width:100%;height:40px}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"] }]
72647
72673
  }], ctorParameters: () => [] });
72648
72674
 
72649
72675
  addIcons({ sendOutline });
@@ -72796,7 +72822,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
72796
72822
  type: Output
72797
72823
  }] } });
72798
72824
 
72799
- addIcons({ addOutline, closeOutline, happyOutline, micOutline, arrowUpOutline });
72825
+ addIcons({ addOutline, closeOutline, happyOutline, micOutline, arrowUpOutline, trashOutline });
72800
72826
  const CHAT_COMPOSER_I18N = {
72801
72827
  es: {
72802
72828
  placeholder: 'Escribe un mensaje',
@@ -72844,6 +72870,16 @@ class ChatComposerComponent {
72844
72870
  this.body = signal('');
72845
72871
  this.emojiOpen = signal(false);
72846
72872
  this.quickEmojis = QUICK_EMOJIS;
72873
+ // Grabación de audio (MediaRecorder).
72874
+ this.recording = signal(false);
72875
+ this.recSeconds = signal(0);
72876
+ this.recChunks = [];
72877
+ this.recTimeLabel = computed(() => {
72878
+ const s = this.recSeconds();
72879
+ const mm = String(Math.floor(s / 60)).padStart(2, '0');
72880
+ const ss = String(s % 60).padStart(2, '0');
72881
+ return `${mm}:${ss}`;
72882
+ });
72847
72883
  this.lastTypingEmit = 0;
72848
72884
  this.canSend = computed(() => {
72849
72885
  const t = this.body().trim();
@@ -72887,6 +72923,22 @@ class ChatComposerComponent {
72887
72923
  this.attach.emit(file);
72888
72924
  input.value = '';
72889
72925
  }
72926
+ /** Pegar imagen desde el portapapeles (captura de pantalla, copiar imagen). */
72927
+ onPaste(event) {
72928
+ const items = event.clipboardData?.items;
72929
+ if (!items)
72930
+ return;
72931
+ for (const item of Array.from(items)) {
72932
+ if (item.type.startsWith('image/')) {
72933
+ const file = item.getAsFile();
72934
+ if (file) {
72935
+ event.preventDefault();
72936
+ this.attach.emit(file);
72937
+ }
72938
+ return;
72939
+ }
72940
+ }
72941
+ }
72890
72942
  /**
72891
72943
  * Emite typing como máximo 1 vez cada 2s. No usa Date.now() directo en plantillas
72892
72944
  * de workflow; aquí estamos en runtime de navegador, performance.now es seguro.
@@ -72898,6 +72950,85 @@ class ChatComposerComponent {
72898
72950
  this.typing.emit();
72899
72951
  }
72900
72952
  }
72953
+ // ─── Grabación de audio ───────────────────────────────────────────────────
72954
+ /** Selecciona un mimeType de audio soportado por el navegador. */
72955
+ pickAudioMime() {
72956
+ const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'];
72957
+ const MR = window.MediaRecorder;
72958
+ if (!MR?.isTypeSupported)
72959
+ return '';
72960
+ return candidates.find(c => MR.isTypeSupported(c)) ?? '';
72961
+ }
72962
+ async startRecording() {
72963
+ if (this.recording())
72964
+ return;
72965
+ if (!navigator.mediaDevices?.getUserMedia || !('MediaRecorder' in window)) {
72966
+ this.voice.emit(); // sin soporte: deja que el consumer decida (fallback)
72967
+ return;
72968
+ }
72969
+ try {
72970
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
72971
+ this.recStream = stream;
72972
+ const mimeType = this.pickAudioMime();
72973
+ this.recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
72974
+ this.recChunks = [];
72975
+ this.recorder.ondataavailable = e => {
72976
+ if (e.data.size > 0)
72977
+ this.recChunks.push(e.data);
72978
+ };
72979
+ this.recorder.start();
72980
+ this.recording.set(true);
72981
+ this.recSeconds.set(0);
72982
+ this.recTimer = setInterval(() => this.recSeconds.update(s => s + 1), 1000);
72983
+ }
72984
+ catch {
72985
+ this.cleanupRecording();
72986
+ }
72987
+ }
72988
+ stopAndSend() {
72989
+ const rec = this.recorder;
72990
+ if (!rec) {
72991
+ this.cleanupRecording();
72992
+ return;
72993
+ }
72994
+ rec.onstop = () => {
72995
+ const type = rec.mimeType || 'audio/webm';
72996
+ const blob = new Blob(this.recChunks, { type });
72997
+ const ext = type.includes('mp4') ? 'm4a' : 'webm';
72998
+ const file = new File([blob], `audio-${this.recSeconds()}s.${ext}`, { type });
72999
+ if (blob.size > 0)
73000
+ this.attach.emit(file);
73001
+ this.cleanupRecording();
73002
+ };
73003
+ rec.stop();
73004
+ }
73005
+ cancelRecording() {
73006
+ const rec = this.recorder;
73007
+ if (rec)
73008
+ rec.onstop = () => this.cleanupRecording();
73009
+ try {
73010
+ rec?.stop();
73011
+ }
73012
+ catch {
73013
+ this.cleanupRecording();
73014
+ }
73015
+ if (!rec)
73016
+ this.cleanupRecording();
73017
+ }
73018
+ cleanupRecording() {
73019
+ if (this.recTimer)
73020
+ clearInterval(this.recTimer);
73021
+ this.recTimer = undefined;
73022
+ this.recStream?.getTracks().forEach(t => t.stop());
73023
+ this.recStream = undefined;
73024
+ this.recorder = undefined;
73025
+ this.recChunks = [];
73026
+ this.recording.set(false);
73027
+ this.recSeconds.set(0);
73028
+ }
73029
+ ngOnDestroy() {
73030
+ this.cleanupRecording();
73031
+ }
72901
73032
  t(key) {
72902
73033
  return this.i18n.t(key, 'ChatComposer');
72903
73034
  }
@@ -72924,44 +73055,68 @@ class ChatComposerComponent {
72924
73055
  </div>
72925
73056
  }
72926
73057
 
72927
- <div class="pill">
72928
- @if (showAttach()) {
72929
- <label class="icon-btn" [attr.aria-label]="t('attach')">
72930
- <ion-icon name="add-outline" aria-hidden="true" />
72931
- <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
72932
- </label>
72933
- }
72934
-
72935
- <ion-textarea
72936
- class="field"
72937
- [placeholder]="placeholderText()"
72938
- [disabled]="disabled()"
72939
- [maxlength]="maxLength()"
72940
- [autoGrow]="true"
72941
- [rows]="1"
72942
- [value]="body()"
72943
- (ionInput)="onInput($event)"
72944
- (keydown)="onKeydown($event)"
72945
- ></ion-textarea>
72946
-
72947
- @if (showEmoji()) {
72948
- <button class="icon-btn" [class.active]="emojiOpen()" [attr.aria-label]="t('emoji')" (click)="toggleEmoji()">
72949
- <ion-icon name="happy-outline" aria-hidden="true" />
73058
+ @if (recording()) {
73059
+ <div class="rec-bar">
73060
+ <button class="icon-btn danger" [attr.aria-label]="t('cancel')" (click)="cancelRecording()">
73061
+ <ion-icon name="trash-outline" aria-hidden="true" />
72950
73062
  </button>
72951
- }
72952
-
72953
- @if (canSend()) {
72954
- <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73063
+ <span class="rec-dot"></span>
73064
+ <span class="rec-time">{{ recTimeLabel() }}</span>
73065
+ <span class="rec-spacer"></span>
73066
+ <button class="send-btn" [attr.aria-label]="t('send')" (click)="stopAndSend()">
72955
73067
  <ion-icon name="arrow-up-outline" aria-hidden="true" />
72956
73068
  </button>
72957
- } @else if (showMic()) {
72958
- <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="voice.emit()">
72959
- <ion-icon name="mic-outline" aria-hidden="true" />
72960
- </button>
72961
- }
72962
- </div>
73069
+ </div>
73070
+ } @else {
73071
+ <div class="pill">
73072
+ @if (showAttach()) {
73073
+ <label class="icon-btn" [attr.aria-label]="t('attach')">
73074
+ <ion-icon name="add-outline" aria-hidden="true" />
73075
+ <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73076
+ </label>
73077
+ }
73078
+
73079
+ <ion-textarea
73080
+ class="field"
73081
+ [placeholder]="placeholderText()"
73082
+ [disabled]="disabled()"
73083
+ [maxlength]="maxLength()"
73084
+ [autoGrow]="true"
73085
+ [rows]="1"
73086
+ [value]="body()"
73087
+ [spellcheck]="false"
73088
+ autocapitalize="sentences"
73089
+ autocorrect="off"
73090
+ enterkeyhint="enter"
73091
+ (ionInput)="onInput($event)"
73092
+ (keydown)="onKeydown($event)"
73093
+ (paste)="onPaste($event)"
73094
+ ></ion-textarea>
73095
+
73096
+ @if (showEmoji()) {
73097
+ <button
73098
+ class="icon-btn"
73099
+ [class.active]="emojiOpen()"
73100
+ [attr.aria-label]="t('emoji')"
73101
+ (click)="toggleEmoji()"
73102
+ >
73103
+ <ion-icon name="happy-outline" aria-hidden="true" />
73104
+ </button>
73105
+ }
73106
+
73107
+ @if (canSend()) {
73108
+ <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73109
+ <ion-icon name="arrow-up-outline" aria-hidden="true" />
73110
+ </button>
73111
+ } @else if (showMic()) {
73112
+ <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73113
+ <ion-icon name="mic-outline" aria-hidden="true" />
73114
+ </button>
73115
+ }
73116
+ </div>
73117
+ }
72963
73118
  </div>
72964
- `, isInline: true, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active,.icon-btn.mic:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
73119
+ `, isInline: true, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active,.icon-btn.mic:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}.icon-btn.danger{color:var(--ion-color-danger, #eb445a)}.rec-bar{display:flex;align-items:center;gap:10px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--ion-color-danger, #eb445a);animation:rec-pulse 1.2s ease-in-out infinite}.rec-time{font-variant-numeric:tabular-nums;font-size:.9375rem;color:var(--ion-text-color, #000)}.rec-spacer{flex:1}@keyframes rec-pulse{0%,to{opacity:1}50%{opacity:.3}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
72965
73120
  }
72966
73121
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, decorators: [{
72967
73122
  type: Component,
@@ -72987,44 +73142,68 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
72987
73142
  </div>
72988
73143
  }
72989
73144
 
72990
- <div class="pill">
72991
- @if (showAttach()) {
72992
- <label class="icon-btn" [attr.aria-label]="t('attach')">
72993
- <ion-icon name="add-outline" aria-hidden="true" />
72994
- <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
72995
- </label>
72996
- }
72997
-
72998
- <ion-textarea
72999
- class="field"
73000
- [placeholder]="placeholderText()"
73001
- [disabled]="disabled()"
73002
- [maxlength]="maxLength()"
73003
- [autoGrow]="true"
73004
- [rows]="1"
73005
- [value]="body()"
73006
- (ionInput)="onInput($event)"
73007
- (keydown)="onKeydown($event)"
73008
- ></ion-textarea>
73009
-
73010
- @if (showEmoji()) {
73011
- <button class="icon-btn" [class.active]="emojiOpen()" [attr.aria-label]="t('emoji')" (click)="toggleEmoji()">
73012
- <ion-icon name="happy-outline" aria-hidden="true" />
73145
+ @if (recording()) {
73146
+ <div class="rec-bar">
73147
+ <button class="icon-btn danger" [attr.aria-label]="t('cancel')" (click)="cancelRecording()">
73148
+ <ion-icon name="trash-outline" aria-hidden="true" />
73013
73149
  </button>
73014
- }
73015
-
73016
- @if (canSend()) {
73017
- <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73150
+ <span class="rec-dot"></span>
73151
+ <span class="rec-time">{{ recTimeLabel() }}</span>
73152
+ <span class="rec-spacer"></span>
73153
+ <button class="send-btn" [attr.aria-label]="t('send')" (click)="stopAndSend()">
73018
73154
  <ion-icon name="arrow-up-outline" aria-hidden="true" />
73019
73155
  </button>
73020
- } @else if (showMic()) {
73021
- <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="voice.emit()">
73022
- <ion-icon name="mic-outline" aria-hidden="true" />
73023
- </button>
73024
- }
73025
- </div>
73156
+ </div>
73157
+ } @else {
73158
+ <div class="pill">
73159
+ @if (showAttach()) {
73160
+ <label class="icon-btn" [attr.aria-label]="t('attach')">
73161
+ <ion-icon name="add-outline" aria-hidden="true" />
73162
+ <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73163
+ </label>
73164
+ }
73165
+
73166
+ <ion-textarea
73167
+ class="field"
73168
+ [placeholder]="placeholderText()"
73169
+ [disabled]="disabled()"
73170
+ [maxlength]="maxLength()"
73171
+ [autoGrow]="true"
73172
+ [rows]="1"
73173
+ [value]="body()"
73174
+ [spellcheck]="false"
73175
+ autocapitalize="sentences"
73176
+ autocorrect="off"
73177
+ enterkeyhint="enter"
73178
+ (ionInput)="onInput($event)"
73179
+ (keydown)="onKeydown($event)"
73180
+ (paste)="onPaste($event)"
73181
+ ></ion-textarea>
73182
+
73183
+ @if (showEmoji()) {
73184
+ <button
73185
+ class="icon-btn"
73186
+ [class.active]="emojiOpen()"
73187
+ [attr.aria-label]="t('emoji')"
73188
+ (click)="toggleEmoji()"
73189
+ >
73190
+ <ion-icon name="happy-outline" aria-hidden="true" />
73191
+ </button>
73192
+ }
73193
+
73194
+ @if (canSend()) {
73195
+ <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73196
+ <ion-icon name="arrow-up-outline" aria-hidden="true" />
73197
+ </button>
73198
+ } @else if (showMic()) {
73199
+ <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73200
+ <ion-icon name="mic-outline" aria-hidden="true" />
73201
+ </button>
73202
+ }
73203
+ </div>
73204
+ }
73026
73205
  </div>
73027
- `, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active,.icon-btn.mic:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}\n"] }]
73206
+ `, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active,.icon-btn.mic:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}.icon-btn.danger{color:var(--ion-color-danger, #eb445a)}.rec-bar{display:flex;align-items:center;gap:10px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--ion-color-danger, #eb445a);animation:rec-pulse 1.2s ease-in-out infinite}.rec-time{font-variant-numeric:tabular-nums;font-size:.9375rem;color:var(--ion-text-color, #000)}.rec-spacer{flex:1}@keyframes rec-pulse{0%,to{opacity:1}50%{opacity:.3}}\n"] }]
73028
73207
  }], ctorParameters: () => [] });
73029
73208
 
73030
73209
  /**