tas-uell-sdk 0.2.0 → 0.3.0

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.
Files changed (27) hide show
  1. package/README.md +2 -0
  2. package/esm2020/lib/components/tas-btn/tas-btn.component.mjs +2 -13
  3. package/esm2020/lib/components/tas-feedback-modal/tas-feedback-modal.component.mjs +229 -0
  4. package/esm2020/lib/components/tas-floating-call/tas-floating-call.component.mjs +38 -3
  5. package/esm2020/lib/components/tas-incoming-appointment/tas-incoming-appointment.component.mjs +46 -8
  6. package/esm2020/lib/components/tas-videocall/tas-videocall.component.mjs +41 -6
  7. package/esm2020/lib/components/tas-waiting-room/tas-waiting-room.component.mjs +18 -3
  8. package/esm2020/lib/interfaces/tas.interfaces.mjs +6 -1
  9. package/esm2020/lib/services/tas.service.mjs +40 -1
  10. package/esm2020/lib/tas-uell-sdk.module.mjs +8 -3
  11. package/esm2020/public-api.mjs +2 -1
  12. package/fesm2015/tas-uell-sdk.mjs +421 -36
  13. package/fesm2015/tas-uell-sdk.mjs.map +1 -1
  14. package/fesm2020/tas-uell-sdk.mjs +416 -36
  15. package/fesm2020/tas-uell-sdk.mjs.map +1 -1
  16. package/lib/components/tas-btn/tas-btn.component.d.ts +1 -2
  17. package/lib/components/tas-feedback-modal/tas-feedback-modal.component.d.ts +101 -0
  18. package/lib/components/tas-floating-call/tas-floating-call.component.d.ts +5 -0
  19. package/lib/components/tas-incoming-appointment/tas-incoming-appointment.component.d.ts +12 -1
  20. package/lib/components/tas-videocall/tas-videocall.component.d.ts +8 -2
  21. package/lib/components/tas-waiting-room/tas-waiting-room.component.d.ts +9 -0
  22. package/lib/interfaces/tas.interfaces.d.ts +17 -0
  23. package/lib/services/tas.service.d.ts +15 -1
  24. package/lib/tas-uell-sdk.module.d.ts +5 -4
  25. package/package.json +1 -1
  26. package/public-api.d.ts +1 -0
  27. package/src/lib/styles/tas-global.scss +17 -0
@@ -1,15 +1,16 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, Inject, Component, ChangeDetectionStrategy, Input, ViewChild, EventEmitter, Output, NgModule } from '@angular/core';
2
+ import { InjectionToken, Injectable, Inject, Component, Input, ChangeDetectionStrategy, ViewChild, EventEmitter, Output, NgModule } from '@angular/core';
3
3
  import { BehaviorSubject, Observable, Subscription } from 'rxjs';
4
4
  import { map, catchError, finalize, shareReplay } from 'rxjs/operators';
5
5
  import * as OT from '@opentok/client';
6
- import interact from 'interactjs';
7
6
  import * as i1 from '@ng-bootstrap/ng-bootstrap';
8
7
  import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
9
- import * as i4 from '@angular/platform-browser';
10
- import * as i4$1 from '@angular/common';
8
+ import * as i4 from '@angular/common';
11
9
  import { CommonModule } from '@angular/common';
10
+ import * as i4$1 from '@angular/forms';
12
11
  import { FormsModule } from '@angular/forms';
12
+ import interact from 'interactjs';
13
+ import * as i4$2 from '@angular/platform-browser';
13
14
 
14
15
  /**
15
16
  * Injection token for TAS configuration
@@ -95,6 +96,11 @@ var ViewMode;
95
96
  ViewMode["FULLSCREEN"] = "FULLSCREEN";
96
97
  ViewMode["PIP"] = "PIP";
97
98
  })(ViewMode || (ViewMode = {}));
99
+ var FeedbackMotiveType;
100
+ (function (FeedbackMotiveType) {
101
+ FeedbackMotiveType["TECHNICAL"] = "TECHNICAL";
102
+ FeedbackMotiveType["BUSINESS"] = "BUSINESS";
103
+ })(FeedbackMotiveType || (FeedbackMotiveType = {}));
98
104
  // Appointment types
99
105
  var AppointmentStatus;
100
106
  (function (AppointmentStatus) {
@@ -578,6 +584,42 @@ class TasService {
578
584
  throw error;
579
585
  }));
580
586
  }
587
+ /**
588
+ * Save video call feedback.
589
+ * POST /v2/proxy/video/save/feedback
590
+ * @param payload Feedback data including videoCallId, motiveId, motiveType, observation, rating (1-5), and tenant
591
+ * @returns Observable that completes on success (HTTP 200 OK, no response body)
592
+ */
593
+ saveFeedback(payload) {
594
+ // Validate rating is between 1 and 5
595
+ if (payload.rating < 1 || payload.rating > 5) {
596
+ return new Observable(observer => {
597
+ observer.error(new Error('Rating must be between 1 and 5'));
598
+ });
599
+ }
600
+ return this.httpClient
601
+ .post('v2/proxy/video/save/feedback', {
602
+ body: payload,
603
+ headers: {},
604
+ })
605
+ .pipe(catchError((error) => {
606
+ console.error('TAS Service: saveFeedback failed', error);
607
+ throw error;
608
+ }));
609
+ }
610
+ /**
611
+ * Get available feedback motives.
612
+ * GET /v2/proxy/video/motives
613
+ * @returns Observable of feedback motives array
614
+ */
615
+ getMotives() {
616
+ return this.httpClient
617
+ .get('v2/proxy/video/motives', { headers: {} })
618
+ .pipe(catchError((error) => {
619
+ console.error('TAS Service: getMotives failed', error);
620
+ throw error;
621
+ }));
622
+ }
581
623
  /**
582
624
  * Start automatic status polling for the current session.
583
625
  * Status is polled every STATUS_POLL_INTERVAL_MS milliseconds.
@@ -676,6 +718,9 @@ class TasService {
676
718
  get videoCallId() {
677
719
  return this.currentVideoCallId;
678
720
  }
721
+ get tenant() {
722
+ return this.currentTenant;
723
+ }
679
724
  /**
680
725
  * Connects to a TokBox video session
681
726
  */
@@ -888,6 +933,227 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
888
933
  }]
889
934
  }] });
890
935
 
936
+ class TasFeedbackModalComponent {
937
+ constructor(activeModal, tasService) {
938
+ this.activeModal = activeModal;
939
+ this.tasService = tasService;
940
+ this.businessRole = TasBusinessRole.USER;
941
+ this.motives = [];
942
+ this.filteredMotives = [];
943
+ this.selectedMotive = null;
944
+ this.rating = 0;
945
+ this.hoverRating = 0;
946
+ this.observation = '';
947
+ this.isSubmitting = false;
948
+ this.showToast = false;
949
+ this.isDropdownOpen = false;
950
+ this.subscriptions = new Subscription();
951
+ this.toastTimeout = null;
952
+ }
953
+ ngOnInit() {
954
+ this.loadMotives();
955
+ }
956
+ ngOnDestroy() {
957
+ this.subscriptions.unsubscribe();
958
+ if (this.toastTimeout) {
959
+ clearTimeout(this.toastTimeout);
960
+ }
961
+ }
962
+ /**
963
+ * Check if current user can see all motives (BUSINESS + TECHNICAL)
964
+ * Only BACKOFFICE, ADMIN_MANAGER, MANAGER roles see BUSINESS motives
965
+ */
966
+ get canSeeBusinessMotives() {
967
+ return (this.businessRole === TasBusinessRole.BACKOFFICE ||
968
+ this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
969
+ this.businessRole === TasBusinessRole.MANAGER);
970
+ }
971
+ /**
972
+ * Observation is required when "Otro problema tecnico" is selected
973
+ */
974
+ get isObservationRequired() {
975
+ return this.selectedMotive?.description === 'Otro problema tecnico';
976
+ }
977
+ /**
978
+ * Can submit if rating > 0 OR motive is selected
979
+ * If observation is required, it must not be empty
980
+ */
981
+ get canSubmit() {
982
+ const hasRatingOrMotive = this.rating > 0 || this.selectedMotive !== null;
983
+ if (!hasRatingOrMotive)
984
+ return false;
985
+ if (this.isObservationRequired && !this.observation.trim())
986
+ return false;
987
+ return true;
988
+ }
989
+ /**
990
+ * Progress percentage for the divider line
991
+ * 0 = nothing selected, 50 = one selected, 100 = both selected
992
+ */
993
+ get feedbackProgress() {
994
+ const hasRating = this.rating > 0;
995
+ const hasMotive = this.selectedMotive !== null;
996
+ if (hasRating && hasMotive)
997
+ return 100;
998
+ if (hasRating || hasMotive)
999
+ return 50;
1000
+ return 0;
1001
+ }
1002
+ /**
1003
+ * Set star rating
1004
+ */
1005
+ setRating(value) {
1006
+ // Toggle off if clicking the same rating
1007
+ if (this.rating === value) {
1008
+ this.rating = 0;
1009
+ }
1010
+ else {
1011
+ this.rating = value;
1012
+ }
1013
+ }
1014
+ /**
1015
+ * Set hover preview rating
1016
+ */
1017
+ setHoverRating(value) {
1018
+ this.hoverRating = value;
1019
+ }
1020
+ /**
1021
+ * Clear hover preview
1022
+ */
1023
+ clearHoverRating() {
1024
+ this.hoverRating = 0;
1025
+ }
1026
+ /**
1027
+ * Get the display rating (hover preview or actual)
1028
+ */
1029
+ getDisplayRating() {
1030
+ return this.hoverRating > 0 ? this.hoverRating : this.rating;
1031
+ }
1032
+ /**
1033
+ * Toggle dropdown open/close
1034
+ */
1035
+ toggleDropdown() {
1036
+ this.isDropdownOpen = !this.isDropdownOpen;
1037
+ }
1038
+ /**
1039
+ * Close dropdown
1040
+ */
1041
+ closeDropdown() {
1042
+ this.isDropdownOpen = false;
1043
+ }
1044
+ /**
1045
+ * Select a motive from dropdown
1046
+ */
1047
+ selectMotive(motive) {
1048
+ this.selectedMotive = motive;
1049
+ this.closeDropdown();
1050
+ }
1051
+ /**
1052
+ * Clear selected motive
1053
+ */
1054
+ clearMotive() {
1055
+ this.selectedMotive = null;
1056
+ }
1057
+ /**
1058
+ * Submit feedback
1059
+ */
1060
+ submit() {
1061
+ if (!this.canSubmit || this.isSubmitting)
1062
+ return;
1063
+ this.isSubmitting = true;
1064
+ const payload = {
1065
+ videoCallId: this.videoCallId,
1066
+ motiveId: this.selectedMotive?.id ?? 0,
1067
+ observation: this.observation.trim(),
1068
+ rating: this.rating || 1,
1069
+ tenant: this.tenant,
1070
+ };
1071
+ if (this.selectedMotive) {
1072
+ payload.motiveType = this.selectedMotive.motiveType;
1073
+ }
1074
+ this.subscriptions.add(this.tasService.saveFeedback(payload).subscribe({
1075
+ next: () => {
1076
+ this.isSubmitting = false;
1077
+ this.showToastNotification();
1078
+ },
1079
+ error: (err) => {
1080
+ console.error('Error saving feedback:', err);
1081
+ this.isSubmitting = false;
1082
+ // Still close modal on error, just don't show toast
1083
+ this.activeModal.close('submitted');
1084
+ },
1085
+ }));
1086
+ }
1087
+ /**
1088
+ * Close modal without saving
1089
+ */
1090
+ dismiss() {
1091
+ this.activeModal.dismiss('dismissed');
1092
+ }
1093
+ /**
1094
+ * Load motives from API
1095
+ */
1096
+ loadMotives() {
1097
+ this.subscriptions.add(this.tasService.getMotives().subscribe({
1098
+ next: (motives) => {
1099
+ this.motives = motives;
1100
+ this.filterMotives();
1101
+ },
1102
+ error: (err) => {
1103
+ console.error('Error loading motives:', err);
1104
+ // Fallback mock motives for development/testing
1105
+ this.motives = [
1106
+ { id: 1, description: 'Problemas de audio', motiveType: FeedbackMotiveType.TECHNICAL },
1107
+ { id: 2, description: 'Problemas de video', motiveType: FeedbackMotiveType.TECHNICAL },
1108
+ { id: 3, description: 'Conexión inestable', motiveType: FeedbackMotiveType.TECHNICAL },
1109
+ { id: 4, description: 'Otro problema tecnico', motiveType: FeedbackMotiveType.TECHNICAL },
1110
+ { id: 5, description: 'Problema de negocio', motiveType: FeedbackMotiveType.BUSINESS },
1111
+ ];
1112
+ this.filterMotives();
1113
+ },
1114
+ }));
1115
+ }
1116
+ /**
1117
+ * Filter motives based on user role
1118
+ * USERs see only TECHNICAL motives
1119
+ * Owners (BACKOFFICE, ADMIN_MANAGER, MANAGER) see all motives
1120
+ */
1121
+ filterMotives() {
1122
+ if (this.canSeeBusinessMotives) {
1123
+ this.filteredMotives = this.motives;
1124
+ }
1125
+ else {
1126
+ this.filteredMotives = this.motives.filter((m) => !m.motiveType || m.motiveType === FeedbackMotiveType.TECHNICAL);
1127
+ }
1128
+ // If still empty after filtering, and we have motives, just show all as fallback
1129
+ if (this.filteredMotives.length === 0 && this.motives.length > 0) {
1130
+ this.filteredMotives = this.motives;
1131
+ }
1132
+ }
1133
+ /**
1134
+ * Show toast notification and auto-dismiss after 3s
1135
+ */
1136
+ showToastNotification() {
1137
+ this.showToast = true;
1138
+ this.toastTimeout = setTimeout(() => {
1139
+ this.showToast = false;
1140
+ this.activeModal.close('submitted');
1141
+ }, 3000);
1142
+ }
1143
+ }
1144
+ TasFeedbackModalComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasFeedbackModalComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }], target: i0.ɵɵFactoryTarget.Component });
1145
+ TasFeedbackModalComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasFeedbackModalComponent, selector: "tas-feedback-modal", inputs: { videoCallId: "videoCallId", tenant: "tenant", businessRole: "businessRole" }, ngImport: i0, template: "<div class=\"tas-feedback-modal\">\n <!-- Header with subtitle -->\n <div class=\"feedback-header\">\n <div class=\"header-text\">\n <span class=\"subtitle\">Cierre de la consulta</span>\n <h2>Calidad de la videollamada</h2>\n </div>\n <button\n type=\"button\"\n class=\"close-btn\"\n (click)=\"dismiss()\"\n aria-label=\"Cerrar\"\n >\n <i class=\"fa fa-times\"></i>\n </button>\n </div>\n\n <!-- Progress Divider -->\n <div class=\"progress-divider\">\n <div class=\"progress-fill\" [style.width.%]=\"feedbackProgress\"></div>\n </div>\n <div class=\"accent-line\"></div>\n\n <div class=\"feedback-content\">\n <!-- Star Rating with question -->\n <div class=\"rating-section\">\n <p class=\"rating-question\">\u00BFC\u00F3mo fue la calidad de la llamada?</p>\n <div\n class=\"stars-container\"\n (mouseleave)=\"clearHoverRating()\"\n >\n <button\n *ngFor=\"let star of [1, 2, 3, 4, 5]\"\n type=\"button\"\n class=\"star-btn\"\n [class.filled]=\"star <= getDisplayRating()\"\n (click)=\"setRating(star)\"\n (mouseenter)=\"setHoverRating(star)\"\n [attr.aria-label]=\"'Calificar ' + star + ' estrellas'\"\n >\n <i class=\"fa\" [class.fa-star]=\"star <= getDisplayRating()\" [class.fa-star-o]=\"star > getDisplayRating()\"></i>\n </button>\n </div>\n </div>\n\n <!-- Motive Dropdown -->\n <div class=\"motive-section\">\n <label class=\"motive-label\">Motivo (opcional)</label>\n <div class=\"custom-dropdown\" [class.open]=\"isDropdownOpen\">\n <button\n type=\"button\"\n class=\"dropdown-trigger\"\n (click)=\"toggleDropdown()\"\n [attr.aria-expanded]=\"isDropdownOpen\"\n aria-haspopup=\"listbox\"\n >\n <span class=\"dropdown-text\">\n {{ selectedMotive?.description || 'Seleccionar motivo' }}\n </span>\n <i class=\"fa fa-chevron-down dropdown-arrow\"></i>\n </button>\n <div\n class=\"dropdown-menu\"\n *ngIf=\"isDropdownOpen\"\n role=\"listbox\"\n >\n <button\n *ngFor=\"let motive of filteredMotives\"\n type=\"button\"\n class=\"dropdown-item\"\n [class.selected]=\"selectedMotive?.id === motive.id\"\n (click)=\"selectMotive(motive)\"\n role=\"option\"\n [attr.aria-selected]=\"selectedMotive?.id === motive.id\"\n >\n {{ motive.description }}\n </button>\n </div>\n </div>\n </div>\n\n <!-- Observation Textarea -->\n <div class=\"observation-section\">\n <label class=\"observation-label\" [class.required]=\"isObservationRequired\">\n {{ isObservationRequired ? 'Observacion (requerida)' : 'Observacion (opcional)' }}\n </label>\n <textarea\n class=\"observation-textarea\"\n [(ngModel)]=\"observation\"\n [placeholder]=\"isObservationRequired ? 'Por favor, describe el problema...' : 'Escribe tu comentario...'\"\n rows=\"3\"\n [attr.aria-required]=\"isObservationRequired\"\n ></textarea>\n </div>\n </div>\n\n <div class=\"feedback-footer\">\n <button\n type=\"button\"\n class=\"submit-btn\"\n [disabled]=\"!canSubmit || isSubmitting\"\n (click)=\"submit()\"\n >\n <span *ngIf=\"!isSubmitting\">Enviar</span>\n <span *ngIf=\"isSubmitting\">\n <i class=\"fa fa-spinner fa-spin\"></i>\n Enviando...\n </span>\n </button>\n </div>\n\n <!-- Toast Notification -->\n <div class=\"toast-notification\" *ngIf=\"showToast\" role=\"alert\" aria-live=\"polite\">\n <i class=\"fa fa-check-circle\"></i>\n <span>Gracias por tu feedback</span>\n </div>\n</div>\n\n<!-- Backdrop to close dropdown when clicking outside -->\n<div\n class=\"dropdown-backdrop\"\n *ngIf=\"isDropdownOpen\"\n (click)=\"closeDropdown()\"\n></div>\n", styles: [":host{display:block;width:100%}::ng-deep .tas-feedback-modal-wrapper .modal-content{overflow:visible!important;border:none;background:transparent}::ng-deep .tas-feedback-modal-wrapper .modal-dialog{max-width:450px}.tas-feedback-modal{width:100%;background:#fff;border-radius:16px;overflow:visible;position:relative;padding:.5rem;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.feedback-header{display:flex;justify-content:space-between;align-items:flex-start;padding:1.25rem 1.5rem 1rem;background:#fff;border-radius:16px 16px 0 0}.feedback-header .header-text{display:flex;flex-direction:column;gap:.25rem}.feedback-header .header-text .subtitle{font-size:.8125rem;color:#6b7280;font-weight:400}.feedback-header .header-text h2{margin:0;font-size:1.25rem;font-weight:600;color:#1f2937;letter-spacing:-.01em}.feedback-header .close-btn{background:transparent;border:none;padding:8px;cursor:pointer;color:#9ca3af;font-size:1.25rem;line-height:1;transition:all .2s ease;margin-top:-4px}.feedback-header .close-btn:hover{color:#4b5563}.feedback-header .close-btn:focus{outline:none;color:#4b5563}.progress-divider{height:4px;background:#e5e7eb;margin:0;position:relative;overflow:hidden}.progress-divider .progress-fill{height:100%;background:linear-gradient(90deg,var(--Primary-Uell, #1da4b1) 0%,#4fd1c5 100%);transition:width .4s cubic-bezier(.4,0,.2,1);border-radius:0 2px 2px 0}.feedback-content{padding:1.5rem;display:flex;flex-direction:column;gap:1.5rem}.rating-section{display:flex;flex-direction:column;align-items:flex-start;gap:1rem}.rating-section .rating-question{margin:0;font-size:.9375rem;color:#4b5563;font-weight:400;align-self:center}.rating-section .stars-container{display:flex;gap:8px;width:100%;justify-content:center}.rating-section .star-btn{background:transparent;border:none;padding:4px;cursor:pointer;font-size:2rem;color:#d1d5db;transition:all .15s ease;line-height:1}.rating-section .star-btn:hover{transform:scale(1.15)}.rating-section .star-btn.filled{color:#f5a623}.rating-section .star-btn.filled i.fa-star{text-shadow:0 2px 4px rgba(245,166,35,.3)}.rating-section .star-btn i.fa-star-o{color:#d1d5db}.rating-section .star-btn:focus{outline:none}.motive-section{display:flex;flex-direction:column;gap:.5rem;position:relative;overflow:visible}.motive-section .motive-label{font-size:.875rem;font-weight:500;color:#4b5563}.motive-section .custom-dropdown{position:relative;overflow:visible}.motive-section .custom-dropdown .dropdown-trigger{width:100%;display:flex;justify-content:space-between;align-items:center;padding:1rem;background:#fff;border:1px solid #d1d5db;border-radius:8px;cursor:pointer;font-size:.9375rem;color:#4b5563;transition:all .2s ease;min-height:52px}.motive-section .custom-dropdown .dropdown-trigger:hover{border-color:#9ca3af}.motive-section .custom-dropdown .dropdown-trigger:focus{outline:none;border-color:var(--Primary-Uell, #1da4b1)}.motive-section .custom-dropdown .dropdown-trigger .dropdown-text{flex:1;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.motive-section .custom-dropdown .dropdown-trigger .dropdown-arrow{font-size:.875rem;color:#6b7280;transition:transform .2s ease;margin-left:.5rem}.motive-section .custom-dropdown.open .dropdown-trigger{border-color:var(--Primary-Uell, #1da4b1)}.motive-section .custom-dropdown.open .dropdown-trigger .dropdown-arrow{transform:rotate(180deg)}.motive-section .custom-dropdown .dropdown-menu{position:absolute;top:calc(100% + 4px);left:0;right:0;display:block;min-height:40px;min-width:100%;background:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 4px 16px #0000001f;max-height:250px;overflow:hidden;overflow-y:auto;z-index:1050;visibility:visible;opacity:1;padding:0}.motive-section .custom-dropdown .dropdown-menu .dropdown-item{width:100%;padding:1rem 1.25rem;background:transparent;border:none;border-bottom:1px dashed #e5e7eb;text-align:left;font-size:.9375rem;color:#4b5563;cursor:pointer;transition:all .15s ease;position:relative}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:last-child{border-bottom:none}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:hover{background:rgba(29,164,177,.05);color:#374151}.motive-section .custom-dropdown .dropdown-menu .dropdown-item.selected{background:rgba(29,164,177,.08);color:var(--Primary-Uell, #1da4b1);border-left:3px solid var(--Primary-Uell, #1da4b1);padding-left:calc(1.25rem - 3px)}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:focus{outline:none;background:rgba(29,164,177,.05)}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:first-child{border-radius:8px 8px 0 0}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:last-child{border-radius:0 0 8px 8px}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:only-child{border-radius:8px}.motive-section .clear-motive-btn{position:absolute;right:40px;top:32px;background:#f3f4f6;border:none;padding:4px 6px;border-radius:4px;cursor:pointer;color:#6b7280;font-size:.625rem;transition:all .2s ease}.motive-section .clear-motive-btn:hover{background:#e5e7eb;color:#374151}.motive-section .clear-motive-btn:focus{outline:none}.observation-section{display:flex;flex-direction:column;gap:.5rem}.observation-section .observation-label{font-size:.875rem;font-weight:500;color:#4b5563}.observation-section .observation-label.required{color:#dc2626}.observation-section .observation-label.required:after{content:\" *\";font-weight:600}.observation-section .observation-textarea{width:100%;padding:.75rem 1rem;border:1px solid #d1d5db;border-radius:12px;font-size:.875rem;font-family:inherit;resize:vertical;min-height:80px;transition:all .2s ease;background:#fff}.observation-section .observation-textarea::placeholder{color:#9ca3af}.observation-section .observation-textarea:hover{border-color:#9ca3af}.observation-section .observation-textarea:focus{outline:none;border-color:var(--Primary-Uell, #1da4b1);box-shadow:0 0 0 3px #1da4b126}.feedback-footer{padding:1rem 1.5rem 1.5rem;display:flex;justify-content:flex-end}.feedback-footer .submit-btn{padding:.75rem 2rem;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:24px;font-size:.9375rem;font-weight:600;cursor:pointer;transition:all .2s ease;min-width:120px}.feedback-footer .submit-btn:hover:not(:disabled){background:#178e99}.feedback-footer .submit-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.feedback-footer .submit-btn:focus{outline:none;box-shadow:0 0 0 3px #1da4b126}.feedback-footer .submit-btn i.fa-spinner{margin-right:6px}.toast-notification{position:fixed;bottom:24px;left:24px;display:flex;align-items:center;gap:10px;padding:14px 20px;background:#383e52;color:#fff;border-radius:8px;box-shadow:0 4px 12px #00000040;font-size:.875rem;font-weight:500;z-index:1060;animation:slideIn .3s ease}.toast-notification i.fa-check-circle{color:#4ade80;font-size:1.125rem}@keyframes slideIn{0%{transform:translate(-100%);opacity:0}to{transform:translate(0);opacity:1}}.dropdown-backdrop{position:fixed;inset:0;z-index:99}\n"], directives: [{ type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
1146
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasFeedbackModalComponent, decorators: [{
1147
+ type: Component,
1148
+ args: [{ selector: 'tas-feedback-modal', template: "<div class=\"tas-feedback-modal\">\n <!-- Header with subtitle -->\n <div class=\"feedback-header\">\n <div class=\"header-text\">\n <span class=\"subtitle\">Cierre de la consulta</span>\n <h2>Calidad de la videollamada</h2>\n </div>\n <button\n type=\"button\"\n class=\"close-btn\"\n (click)=\"dismiss()\"\n aria-label=\"Cerrar\"\n >\n <i class=\"fa fa-times\"></i>\n </button>\n </div>\n\n <!-- Progress Divider -->\n <div class=\"progress-divider\">\n <div class=\"progress-fill\" [style.width.%]=\"feedbackProgress\"></div>\n </div>\n <div class=\"accent-line\"></div>\n\n <div class=\"feedback-content\">\n <!-- Star Rating with question -->\n <div class=\"rating-section\">\n <p class=\"rating-question\">\u00BFC\u00F3mo fue la calidad de la llamada?</p>\n <div\n class=\"stars-container\"\n (mouseleave)=\"clearHoverRating()\"\n >\n <button\n *ngFor=\"let star of [1, 2, 3, 4, 5]\"\n type=\"button\"\n class=\"star-btn\"\n [class.filled]=\"star <= getDisplayRating()\"\n (click)=\"setRating(star)\"\n (mouseenter)=\"setHoverRating(star)\"\n [attr.aria-label]=\"'Calificar ' + star + ' estrellas'\"\n >\n <i class=\"fa\" [class.fa-star]=\"star <= getDisplayRating()\" [class.fa-star-o]=\"star > getDisplayRating()\"></i>\n </button>\n </div>\n </div>\n\n <!-- Motive Dropdown -->\n <div class=\"motive-section\">\n <label class=\"motive-label\">Motivo (opcional)</label>\n <div class=\"custom-dropdown\" [class.open]=\"isDropdownOpen\">\n <button\n type=\"button\"\n class=\"dropdown-trigger\"\n (click)=\"toggleDropdown()\"\n [attr.aria-expanded]=\"isDropdownOpen\"\n aria-haspopup=\"listbox\"\n >\n <span class=\"dropdown-text\">\n {{ selectedMotive?.description || 'Seleccionar motivo' }}\n </span>\n <i class=\"fa fa-chevron-down dropdown-arrow\"></i>\n </button>\n <div\n class=\"dropdown-menu\"\n *ngIf=\"isDropdownOpen\"\n role=\"listbox\"\n >\n <button\n *ngFor=\"let motive of filteredMotives\"\n type=\"button\"\n class=\"dropdown-item\"\n [class.selected]=\"selectedMotive?.id === motive.id\"\n (click)=\"selectMotive(motive)\"\n role=\"option\"\n [attr.aria-selected]=\"selectedMotive?.id === motive.id\"\n >\n {{ motive.description }}\n </button>\n </div>\n </div>\n </div>\n\n <!-- Observation Textarea -->\n <div class=\"observation-section\">\n <label class=\"observation-label\" [class.required]=\"isObservationRequired\">\n {{ isObservationRequired ? 'Observacion (requerida)' : 'Observacion (opcional)' }}\n </label>\n <textarea\n class=\"observation-textarea\"\n [(ngModel)]=\"observation\"\n [placeholder]=\"isObservationRequired ? 'Por favor, describe el problema...' : 'Escribe tu comentario...'\"\n rows=\"3\"\n [attr.aria-required]=\"isObservationRequired\"\n ></textarea>\n </div>\n </div>\n\n <div class=\"feedback-footer\">\n <button\n type=\"button\"\n class=\"submit-btn\"\n [disabled]=\"!canSubmit || isSubmitting\"\n (click)=\"submit()\"\n >\n <span *ngIf=\"!isSubmitting\">Enviar</span>\n <span *ngIf=\"isSubmitting\">\n <i class=\"fa fa-spinner fa-spin\"></i>\n Enviando...\n </span>\n </button>\n </div>\n\n <!-- Toast Notification -->\n <div class=\"toast-notification\" *ngIf=\"showToast\" role=\"alert\" aria-live=\"polite\">\n <i class=\"fa fa-check-circle\"></i>\n <span>Gracias por tu feedback</span>\n </div>\n</div>\n\n<!-- Backdrop to close dropdown when clicking outside -->\n<div\n class=\"dropdown-backdrop\"\n *ngIf=\"isDropdownOpen\"\n (click)=\"closeDropdown()\"\n></div>\n", styles: [":host{display:block;width:100%}::ng-deep .tas-feedback-modal-wrapper .modal-content{overflow:visible!important;border:none;background:transparent}::ng-deep .tas-feedback-modal-wrapper .modal-dialog{max-width:450px}.tas-feedback-modal{width:100%;background:#fff;border-radius:16px;overflow:visible;position:relative;padding:.5rem;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.feedback-header{display:flex;justify-content:space-between;align-items:flex-start;padding:1.25rem 1.5rem 1rem;background:#fff;border-radius:16px 16px 0 0}.feedback-header .header-text{display:flex;flex-direction:column;gap:.25rem}.feedback-header .header-text .subtitle{font-size:.8125rem;color:#6b7280;font-weight:400}.feedback-header .header-text h2{margin:0;font-size:1.25rem;font-weight:600;color:#1f2937;letter-spacing:-.01em}.feedback-header .close-btn{background:transparent;border:none;padding:8px;cursor:pointer;color:#9ca3af;font-size:1.25rem;line-height:1;transition:all .2s ease;margin-top:-4px}.feedback-header .close-btn:hover{color:#4b5563}.feedback-header .close-btn:focus{outline:none;color:#4b5563}.progress-divider{height:4px;background:#e5e7eb;margin:0;position:relative;overflow:hidden}.progress-divider .progress-fill{height:100%;background:linear-gradient(90deg,var(--Primary-Uell, #1da4b1) 0%,#4fd1c5 100%);transition:width .4s cubic-bezier(.4,0,.2,1);border-radius:0 2px 2px 0}.feedback-content{padding:1.5rem;display:flex;flex-direction:column;gap:1.5rem}.rating-section{display:flex;flex-direction:column;align-items:flex-start;gap:1rem}.rating-section .rating-question{margin:0;font-size:.9375rem;color:#4b5563;font-weight:400;align-self:center}.rating-section .stars-container{display:flex;gap:8px;width:100%;justify-content:center}.rating-section .star-btn{background:transparent;border:none;padding:4px;cursor:pointer;font-size:2rem;color:#d1d5db;transition:all .15s ease;line-height:1}.rating-section .star-btn:hover{transform:scale(1.15)}.rating-section .star-btn.filled{color:#f5a623}.rating-section .star-btn.filled i.fa-star{text-shadow:0 2px 4px rgba(245,166,35,.3)}.rating-section .star-btn i.fa-star-o{color:#d1d5db}.rating-section .star-btn:focus{outline:none}.motive-section{display:flex;flex-direction:column;gap:.5rem;position:relative;overflow:visible}.motive-section .motive-label{font-size:.875rem;font-weight:500;color:#4b5563}.motive-section .custom-dropdown{position:relative;overflow:visible}.motive-section .custom-dropdown .dropdown-trigger{width:100%;display:flex;justify-content:space-between;align-items:center;padding:1rem;background:#fff;border:1px solid #d1d5db;border-radius:8px;cursor:pointer;font-size:.9375rem;color:#4b5563;transition:all .2s ease;min-height:52px}.motive-section .custom-dropdown .dropdown-trigger:hover{border-color:#9ca3af}.motive-section .custom-dropdown .dropdown-trigger:focus{outline:none;border-color:var(--Primary-Uell, #1da4b1)}.motive-section .custom-dropdown .dropdown-trigger .dropdown-text{flex:1;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.motive-section .custom-dropdown .dropdown-trigger .dropdown-arrow{font-size:.875rem;color:#6b7280;transition:transform .2s ease;margin-left:.5rem}.motive-section .custom-dropdown.open .dropdown-trigger{border-color:var(--Primary-Uell, #1da4b1)}.motive-section .custom-dropdown.open .dropdown-trigger .dropdown-arrow{transform:rotate(180deg)}.motive-section .custom-dropdown .dropdown-menu{position:absolute;top:calc(100% + 4px);left:0;right:0;display:block;min-height:40px;min-width:100%;background:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 4px 16px #0000001f;max-height:250px;overflow:hidden;overflow-y:auto;z-index:1050;visibility:visible;opacity:1;padding:0}.motive-section .custom-dropdown .dropdown-menu .dropdown-item{width:100%;padding:1rem 1.25rem;background:transparent;border:none;border-bottom:1px dashed #e5e7eb;text-align:left;font-size:.9375rem;color:#4b5563;cursor:pointer;transition:all .15s ease;position:relative}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:last-child{border-bottom:none}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:hover{background:rgba(29,164,177,.05);color:#374151}.motive-section .custom-dropdown .dropdown-menu .dropdown-item.selected{background:rgba(29,164,177,.08);color:var(--Primary-Uell, #1da4b1);border-left:3px solid var(--Primary-Uell, #1da4b1);padding-left:calc(1.25rem - 3px)}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:focus{outline:none;background:rgba(29,164,177,.05)}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:first-child{border-radius:8px 8px 0 0}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:last-child{border-radius:0 0 8px 8px}.motive-section .custom-dropdown .dropdown-menu .dropdown-item:only-child{border-radius:8px}.motive-section .clear-motive-btn{position:absolute;right:40px;top:32px;background:#f3f4f6;border:none;padding:4px 6px;border-radius:4px;cursor:pointer;color:#6b7280;font-size:.625rem;transition:all .2s ease}.motive-section .clear-motive-btn:hover{background:#e5e7eb;color:#374151}.motive-section .clear-motive-btn:focus{outline:none}.observation-section{display:flex;flex-direction:column;gap:.5rem}.observation-section .observation-label{font-size:.875rem;font-weight:500;color:#4b5563}.observation-section .observation-label.required{color:#dc2626}.observation-section .observation-label.required:after{content:\" *\";font-weight:600}.observation-section .observation-textarea{width:100%;padding:.75rem 1rem;border:1px solid #d1d5db;border-radius:12px;font-size:.875rem;font-family:inherit;resize:vertical;min-height:80px;transition:all .2s ease;background:#fff}.observation-section .observation-textarea::placeholder{color:#9ca3af}.observation-section .observation-textarea:hover{border-color:#9ca3af}.observation-section .observation-textarea:focus{outline:none;border-color:var(--Primary-Uell, #1da4b1);box-shadow:0 0 0 3px #1da4b126}.feedback-footer{padding:1rem 1.5rem 1.5rem;display:flex;justify-content:flex-end}.feedback-footer .submit-btn{padding:.75rem 2rem;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:24px;font-size:.9375rem;font-weight:600;cursor:pointer;transition:all .2s ease;min-width:120px}.feedback-footer .submit-btn:hover:not(:disabled){background:#178e99}.feedback-footer .submit-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.feedback-footer .submit-btn:focus{outline:none;box-shadow:0 0 0 3px #1da4b126}.feedback-footer .submit-btn i.fa-spinner{margin-right:6px}.toast-notification{position:fixed;bottom:24px;left:24px;display:flex;align-items:center;gap:10px;padding:14px 20px;background:#383e52;color:#fff;border-radius:8px;box-shadow:0 4px 12px #00000040;font-size:.875rem;font-weight:500;z-index:1060;animation:slideIn .3s ease}.toast-notification i.fa-check-circle{color:#4ade80;font-size:1.125rem}@keyframes slideIn{0%{transform:translate(-100%);opacity:0}to{transform:translate(0);opacity:1}}.dropdown-backdrop{position:fixed;inset:0;z-index:99}\n"] }]
1149
+ }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }]; }, propDecorators: { videoCallId: [{
1150
+ type: Input
1151
+ }], tenant: [{
1152
+ type: Input
1153
+ }], businessRole: [{
1154
+ type: Input
1155
+ }] } });
1156
+
891
1157
  /**
892
1158
  * SVG icons used internally by TAS SDK components.
893
1159
  * Icons are stored as strings to avoid asset bundling complexity.
@@ -988,11 +1254,12 @@ var UserGeoViewState;
988
1254
  UserGeoViewState["DENIED"] = "DENIED";
989
1255
  })(UserGeoViewState || (UserGeoViewState = {}));
990
1256
  class TasVideocallComponent {
991
- constructor(activeModal, tasService, geolocationService, sanitizer) {
1257
+ constructor(activeModal, tasService, geolocationService, sanitizer, modalService) {
992
1258
  this.activeModal = activeModal;
993
1259
  this.tasService = tasService;
994
1260
  this.geolocationService = geolocationService;
995
1261
  this.sanitizer = sanitizer;
1262
+ this.modalService = modalService;
996
1263
  this.participantName = '';
997
1264
  this.tenant = '';
998
1265
  this.businessRole = TasBusinessRole.USER;
@@ -1016,6 +1283,7 @@ class TasVideocallComponent {
1016
1283
  this.UserGeoViewState = UserGeoViewState; // Expose enum to template
1017
1284
  this.devModeEnabled = false; // Enable dev controls for testing
1018
1285
  this.geoPanelDismissed = false; // Track if owner manually closed the panel
1286
+ this.feedbackShown = false; // Track if feedback modal has been shown
1019
1287
  this.subscriptions = new Subscription();
1020
1288
  this.homeIcon = this.sanitizer.bypassSecurityTrustHtml(TAS_ICONS.home);
1021
1289
  }
@@ -1043,6 +1311,37 @@ class TasVideocallComponent {
1043
1311
  hangUp() {
1044
1312
  this.tasService.disconnectSession();
1045
1313
  }
1314
+ /**
1315
+ * Open feedback modal when call ends
1316
+ */
1317
+ openFeedbackModal() {
1318
+ // Prevent opening feedback modal multiple times
1319
+ if (this.feedbackShown) {
1320
+ this.activeModal.close('hangup');
1321
+ return;
1322
+ }
1323
+ // Get videoCallId from input or service
1324
+ const videoCallId = this.videoCallId ?? this.tasService.videoCallId;
1325
+ // If no videoCallId, skip feedback and close directly
1326
+ if (!videoCallId) {
1327
+ this.activeModal.close('hangup');
1328
+ return;
1329
+ }
1330
+ this.feedbackShown = true;
1331
+ const modalRef = this.modalService.open(TasFeedbackModalComponent, {
1332
+ centered: true,
1333
+ backdrop: true,
1334
+ keyboard: true,
1335
+ windowClass: 'tas-feedback-modal-wrapper',
1336
+ });
1337
+ modalRef.componentInstance.videoCallId = videoCallId;
1338
+ modalRef.componentInstance.tenant = this.tenant;
1339
+ modalRef.componentInstance.businessRole = this.businessRole;
1340
+ // Close videocall modal after feedback modal is closed/dismissed
1341
+ modalRef.result.finally(() => {
1342
+ this.activeModal.close('hangup');
1343
+ });
1344
+ }
1046
1345
  toggleMute() {
1047
1346
  this.tasService.toggleMute();
1048
1347
  }
@@ -1168,7 +1467,7 @@ class TasVideocallComponent {
1168
1467
  this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
1169
1468
  this.callState = state;
1170
1469
  if (state === CallState.DISCONNECTED) {
1171
- this.activeModal.close('hangup');
1470
+ this.openFeedbackModal();
1172
1471
  }
1173
1472
  // Track if we have an active video stream
1174
1473
  this.hasVideoStream = state === CallState.CONNECTED;
@@ -1195,8 +1494,9 @@ class TasVideocallComponent {
1195
1494
  // Owner left subscription - disconnect non-owners
1196
1495
  this.subscriptions.add(this.tasService.ownerHasLeft$.subscribe((hasLeft) => {
1197
1496
  if (hasLeft && !this.canAdmitUsers) { // Non-owner user
1497
+ // hangUp() triggers DISCONNECTED state, which opens feedback modal
1498
+ // then closes videocall modal after feedback is done
1198
1499
  this.hangUp();
1199
- this.activeModal.close('owner_left');
1200
1500
  }
1201
1501
  }));
1202
1502
  // ActivateGeo subscription - only for non-owners (users)
@@ -1382,12 +1682,12 @@ class TasVideocallComponent {
1382
1682
  });
1383
1683
  }
1384
1684
  }
1385
- TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: GeolocationService }, { token: i4.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
1386
- TasVideocallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasVideocallComponent, selector: "tas-videocall", inputs: { sessionId: "sessionId", token: "token", appointmentId: "appointmentId", videoCallId: "videoCallId", userId: "userId", participantName: "participantName", tenant: "tenant", businessRole: "businessRole", isReturningFromPip: "isReturningFromPip" }, viewQueries: [{ propertyName: "publisherContainer", first: true, predicate: ["publisherContainer"], descendants: true }, { propertyName: "subscriberContainer", first: true, predicate: ["subscriberContainer"], descendants: true }], ngImport: i0, template: "<div class=\"tas-videocall-wrapper\">\n <div class=\"tas-videocall-container\">\n <!-- Subscriber video (large, background) -->\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Publisher video (small, overlay) -->\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Centered avatar (shown when no video stream) -->\n <div class=\"avatar-container\" *ngIf=\"!hasVideoStream\">\n <tas-avatar [name]=\"participantName\" [size]=\"80\"></tas-avatar>\n </div>\n\n <!-- Controls -->\n <div class=\"controls-container\">\n <button\n class=\"btn control-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n [attr.aria-label]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"btn control-btn swap-btn\"\n (click)=\"toggleSwap()\"\n title=\"Intercambiar vista\"\n aria-label=\"Intercambiar vista\"\n >\n <i class=\"fa fa-refresh\"></i>\n </button>\n <button\n class=\"btn control-btn pip-btn\"\n (click)=\"minimize()\"\n title=\"Minimizar (Picture in Picture)\"\n aria-label=\"Minimizar videollamada\"\n >\n <i class=\"fa fa-compress\"></i>\n </button>\n <button\n class=\"btn control-btn hangup-btn\"\n (click)=\"hangUp()\"\n title=\"Finalizar llamada\"\n aria-label=\"Finalizar llamada\"\n >\n <i class=\"fa fa-phone\"></i>\n </button>\n </div>\n\n <!-- Waiting room notification (shown for OWNER/BACKOFFICE only) -->\n <div\n class=\"waiting-notification\"\n *ngIf=\"waitingRoomUsers.length > 0 && canAdmitUsers\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <span class=\"waiting-text\">\n {{ waitingRoomUsers[0].name }} est\u00E1 en la sala de espera.\n </span>\n <button\n class=\"admit-btn\"\n (click)=\"admitUser(waitingRoomUsers[0].userId)\"\n aria-label=\"Admitir usuario\"\n >\n Admitir\n </button>\n <button\n class=\"dismiss-btn\"\n (click)=\"dismissWaitingNotification(waitingRoomUsers[0].userId)\"\n aria-label=\"Cerrar notificaci\u00F3n\"\n >\n \u00D7\n </button>\n </div>\n </div>\n\n <!-- Owner geolocation panel (for owners to request user location) -->\n <div class=\"user-geo-panel\" *ngIf=\"shouldShowUserGeoPanel || devModeEnabled\">\n <div class=\"user-geo-header\">\n <div class=\"header-title-row\">\n <i class=\"fa fa-map-marker header-icon\" *ngIf=\"userGeoViewState !== UserGeoViewState.INITIAL\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n </div>\n <button class=\"close-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.HIDDEN)\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n\n <div class=\"user-geo-description\" *ngIf=\"userGeoViewState !== UserGeoViewState.VERIFIED && userGeoViewState !== UserGeoViewState.DENIED\">\n <p>El colaborador tiene la ubicaci\u00F3n desactivada, solicita que la active.</p>\n <p>Esta acci\u00F3n nos permitir\u00E1 disponibilizar algunas alertas.</p>\n </div>\n\n <div class=\"user-geo-content\">\n <!-- INITIAL state: just the button -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.INITIAL\">\n <!-- spacer -->\n </ng-container>\n\n <!-- VERIFYING state: spinner + loading message -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.VERIFYING\">\n <div class=\"user-geo-verifying\">\n <div class=\"geo-spinner-large\"></div>\n <p class=\"verifying-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"verifying-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n </ng-container>\n\n <!-- VERIFIED state: home icon with sparkles -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.VERIFIED\">\n <div class=\"user-geo-verified\">\n <div class=\"verified-icon-container\">\n <span class=\"home-icon\" [innerHTML]=\"homeIcon\"></span>\n </div>\n <p class=\"verified-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n\n <!-- DENIED state: error icon and message -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.DENIED\">\n <div class=\"user-geo-denied\">\n <div class=\"denied-icon-container\">\n <i class=\"fa fa-times-circle\"></i>\n </div>\n <p class=\"denied-title\">La ubicaci\u00F3n fue rechazada</p>\n <p class=\"denied-subtitle\">El colaborador no permiti\u00F3 el acceso a su ubicaci\u00F3n.</p>\n </div>\n </ng-container>\n </div>\n\n <!-- Button (hidden in verified and denied states) -->\n <button \n class=\"user-geo-btn\" \n *ngIf=\"userGeoViewState !== UserGeoViewState.VERIFIED && userGeoViewState !== UserGeoViewState.DENIED\"\n [class.disabled]=\"userGeoViewState === UserGeoViewState.VERIFYING\"\n [disabled]=\"userGeoViewState === UserGeoViewState.VERIFYING\"\n (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n\n <!-- Dev controls -->\n <div class=\"dev-controls\" *ngIf=\"devModeEnabled\">\n <span class=\"dev-label\">Dev:</span>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.INITIAL)\">Initial</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.VERIFYING)\">Verifying</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.VERIFIED)\">Verified</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.DENIED)\">Denied</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.HIDDEN)\">Hide</button>\n </div>\n </div>\n</div>\n\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100vw;height:100vh;box-sizing:border-box;padding:2rem;background:linear-gradient(281deg,rgba(29,164,177,.2) 6.96%,rgba(0,0,0,0) 70.44%),#212532}.tas-videocall-wrapper{display:flex;flex:1;gap:1rem;height:100%}.tas-videocall-container{position:relative;flex:1;height:100%;overflow:hidden;border-radius:8px;border:1px solid var(--Neutral-GreyLight, #dadfe9);background:linear-gradient(180deg,#e5f1f7 0%,#0072ac 100%)}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#0000004d;overflow:hidden}.tas-videocall-container .avatar-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1;display:flex;align-items:center;justify-content:center}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:12px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#33475bb3;padding:12px 20px;border-radius:30px;backdrop-filter:blur(8px)}.tas-videocall-container .controls-container .control-btn{width:44px;height:44px;border-radius:20px;display:flex;align-items:center;justify-content:center;font-size:18px;border:none;background:transparent;cursor:pointer;transition:all .2s ease}.tas-videocall-container .controls-container .control-btn i{color:#fff}.tas-videocall-container .controls-container .control-btn:hover{transform:scale(1.05);filter:brightness(1.1)}.tas-videocall-container .controls-container .control-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .controls-container .hangup-btn{background:#f44336}.tas-videocall-container .controls-container .hangup-btn i{transform:rotate(135deg)}.tas-videocall-container .controls-container .hangup-btn:hover{background:#d32f2f}.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{background:transparent}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover,.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.15)}.tas-videocall-container .waiting-notification{position:absolute;bottom:100px;left:16px;display:flex;align-items:center;gap:12px;background-color:#33475be6;padding:10px 16px;border-radius:8px;z-index:4;backdrop-filter:blur(4px);max-width:calc(100% - 32px)}.tas-videocall-container .waiting-notification .waiting-text{color:#fff;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tas-videocall-container .waiting-notification .admit-btn{background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:4px;padding:6px 16px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;white-space:nowrap}.tas-videocall-container .waiting-notification .admit-btn:hover{background:#178e99}.tas-videocall-container .waiting-notification .admit-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .waiting-notification .dismiss-btn{background:transparent;color:#fff;border:none;font-size:20px;line-height:1;cursor:pointer;padding:0 4px;opacity:.7;transition:opacity .2s ease}.tas-videocall-container .waiting-notification .dismiss-btn:hover{opacity:1}.tas-videocall-container .waiting-notification .dismiss-btn:focus{outline:2px solid #fff;outline-offset:2px}.location-panel{width:280px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.location-panel .location-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.location-panel .location-header h3{font-size:16px;font-weight:600;margin:0;color:#fff}.location-panel .location-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.location-panel .location-header .close-btn:hover{opacity:1}.location-panel .location-description{font-size:14px;color:#fffc;line-height:1.5;margin-bottom:.5rem}.location-panel .location-description p{margin:0 0 .5rem}.location-panel .location-user-list{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;max-height:200px;overflow-y:auto}.location-panel .location-user-list .user-geo-item{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:rgba(255,255,255,.05);border-radius:6px}.location-panel .location-user-list .user-geo-item .user-icon{color:#fff9;font-size:14px}.location-panel .location-user-list .user-geo-item .user-name{flex:1;font-size:14px;color:#fff}.location-panel .location-user-list .user-geo-item .geo-status{display:flex;align-items:center;gap:.35rem;font-size:12px;padding:2px 8px;border-radius:12px}.location-panel .location-user-list .user-geo-item .geo-status.pending{color:#ffc107;background:rgba(255,193,7,.15)}.location-panel .location-user-list .user-geo-item .geo-status.granted{color:#4caf50;background:rgba(76,175,80,.15)}.location-panel .location-user-list .user-geo-item .geo-status.denied{color:#f44336;background:rgba(244,67,54,.15)}.location-panel .location-user-list .user-geo-item .geo-status i{font-size:10px}.location-panel .geo-denied-warning{display:flex;align-items:center;gap:.5rem;padding:.75rem;background:rgba(244,67,54,.15);border-radius:6px;margin-bottom:1rem;color:#f44336;font-size:13px}.location-panel .geo-denied-warning i{font-size:14px}.location-panel .location-content{flex:1;display:flex;flex-direction:column;justify-content:flex-end}.location-panel .verify-location-btn{width:100%;padding:12px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;margin-bottom:1rem}.location-panel .verify-location-btn:hover{background:#178e99}.location-panel .verify-location-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.location-panel .location-footer{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid rgba(255,255,255,.1)}.location-panel .location-footer .footer-icon{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff;font-size:14px}.location-panel .location-footer .footer-icon.location-icon{background:var(--Primary-Uell, #1da4b1)}.location-panel .header-icon{color:#fff;font-size:16px}.location-panel .geo-loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-loading-container .geo-spinner{width:64px;height:64px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite}.location-panel .geo-loading-container .loading-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 .25rem}.location-panel .geo-loading-container .loading-subtitle{color:#ffffffb3;font-size:14px;margin:0}.location-panel .geo-success-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-success-container .success-icon{width:80px;height:80px;border-radius:50%;background:var(--Primary-Uell, #1da4b1);display:flex;align-items:center;justify-content:center;position:relative}.location-panel .geo-success-container .success-icon i{color:#fff;font-size:32px}.location-panel .geo-success-container .success-icon:before,.location-panel .geo-success-container .success-icon:after{content:\"\\2726\";position:absolute;color:#fff;font-size:10px}.location-panel .geo-success-container .success-icon:before{top:-8px;right:-4px}.location-panel .geo-success-container .success-icon:after{bottom:-4px;left:-8px}.location-panel .geo-success-container .success-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 0}.location-panel .verify-location-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}@keyframes geo-spin{to{transform:rotate(360deg)}}.user-geo-panel{width:360px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.user-geo-panel .user-geo-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:2.5rem;padding-top:2rem}.user-geo-panel .user-geo-header .header-title-row{display:flex;align-items:center;gap:.5rem}.user-geo-panel .user-geo-header .header-title-row .header-icon{font-size:16px;color:#fff}.user-geo-panel .user-geo-header .header-title-row h3{font-size:18px;font-weight:600;margin:0;color:#fff}.user-geo-panel .user-geo-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.user-geo-panel .user-geo-header .close-btn:hover{opacity:1}.user-geo-panel .user-geo-description{font-size:14px;color:#fffc;line-height:1.6;margin-bottom:1rem}.user-geo-panel .user-geo-description p{margin:0 0 .75rem}.user-geo-panel .user-geo-content{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center}.user-geo-panel .user-geo-verifying{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-verifying .geo-spinner-large{width:80px;height:80px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite;margin-bottom:1.5rem}.user-geo-panel .user-geo-verifying .verifying-title{color:#fff;font-size:16px;font-weight:600;margin:0 0 .25rem}.user-geo-panel .user-geo-verifying .verifying-subtitle{color:#fff9;font-size:14px;margin:0}.user-geo-panel .user-geo-verified{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-verified .verified-icon-container{margin-bottom:1rem}.user-geo-panel .user-geo-verified .verified-icon-container .home-icon{display:block}.user-geo-panel .user-geo-verified .verified-icon-container .home-icon svg{width:120px;height:100px}.user-geo-panel .user-geo-verified .verified-title{color:#fff;font-size:16px;font-weight:600;margin:0}.user-geo-panel .user-geo-denied{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-denied .denied-icon-container{width:100px;height:100px;border-radius:50%;background:rgba(244,67,54,.2);display:flex;align-items:center;justify-content:center;margin-bottom:1.5rem}.user-geo-panel .user-geo-denied .denied-icon-container i{font-size:48px;color:#f44336}.user-geo-panel .user-geo-denied .denied-title{color:#fff;font-size:16px;font-weight:600;margin:0 0 .5rem}.user-geo-panel .user-geo-denied .denied-subtitle{color:#fff9;font-size:14px;margin:0}.user-geo-panel .user-geo-btn{width:100%;padding:14px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:24px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s ease;margin-top:auto;margin-bottom:1rem}.user-geo-panel .user-geo-btn:hover:not(.disabled){background:#178e99}.user-geo-panel .user-geo-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.user-geo-panel .dev-controls{display:flex;align-items:center;gap:.5rem;padding:.75rem;background:rgba(0,0,0,.3);border-radius:6px;margin-bottom:1rem;flex-wrap:wrap}.user-geo-panel .dev-controls .dev-label{font-size:12px;color:#fff9;font-weight:600}.user-geo-panel .dev-controls .dev-btn{padding:4px 8px;font-size:11px;background:rgba(255,255,255,.1);color:#fff;border:1px solid rgba(255,255,255,.2);border-radius:4px;cursor:pointer;transition:all .2s ease}.user-geo-panel .dev-controls .dev-btn:hover{background:rgba(255,255,255,.2)}.user-geo-panel .user-geo-footer{display:flex;justify-content:center;gap:.75rem;padding-top:.5rem}.user-geo-panel .user-geo-footer .footer-icon{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff9;font-size:16px}.user-geo-panel .user-geo-footer .footer-icon.active{background:var(--Primary-Uell, #1da4b1);color:#fff}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }], directives: [{ type: i4$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1685
+ TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: GeolocationService }, { token: i4$2.DomSanitizer }, { token: i1.NgbModal }], target: i0.ɵɵFactoryTarget.Component });
1686
+ TasVideocallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasVideocallComponent, selector: "tas-videocall", inputs: { sessionId: "sessionId", token: "token", appointmentId: "appointmentId", videoCallId: "videoCallId", userId: "userId", participantName: "participantName", tenant: "tenant", businessRole: "businessRole", isReturningFromPip: "isReturningFromPip" }, viewQueries: [{ propertyName: "publisherContainer", first: true, predicate: ["publisherContainer"], descendants: true }, { propertyName: "subscriberContainer", first: true, predicate: ["subscriberContainer"], descendants: true }], ngImport: i0, template: "<div class=\"tas-videocall-wrapper\">\n <div class=\"tas-videocall-container\">\n <!-- Subscriber video (large, background) -->\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Publisher video (small, overlay) -->\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Centered avatar (shown when no video stream) -->\n <div class=\"avatar-container\" *ngIf=\"!hasVideoStream\">\n <tas-avatar [name]=\"participantName\" [size]=\"80\"></tas-avatar>\n </div>\n\n <!-- Controls -->\n <div class=\"controls-container\">\n <button\n class=\"btn control-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n [attr.aria-label]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"btn control-btn swap-btn\"\n (click)=\"toggleSwap()\"\n title=\"Intercambiar vista\"\n aria-label=\"Intercambiar vista\"\n >\n <i class=\"fa fa-refresh\"></i>\n </button>\n <button\n class=\"btn control-btn pip-btn\"\n (click)=\"minimize()\"\n title=\"Minimizar (Picture in Picture)\"\n aria-label=\"Minimizar videollamada\"\n >\n <i class=\"fa fa-compress\"></i>\n </button>\n <button\n class=\"btn control-btn hangup-btn\"\n (click)=\"hangUp()\"\n title=\"Finalizar llamada\"\n aria-label=\"Finalizar llamada\"\n >\n <i class=\"fa fa-phone\"></i>\n </button>\n </div>\n\n <!-- Waiting room notification (shown for OWNER/BACKOFFICE only) -->\n <div\n class=\"waiting-notification\"\n *ngIf=\"waitingRoomUsers.length > 0 && canAdmitUsers\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <span class=\"waiting-text\">\n {{ waitingRoomUsers[0].name }} est\u00E1 en la sala de espera.\n </span>\n <button\n class=\"admit-btn\"\n (click)=\"admitUser(waitingRoomUsers[0].userId)\"\n aria-label=\"Admitir usuario\"\n >\n Admitir\n </button>\n <button\n class=\"dismiss-btn\"\n (click)=\"dismissWaitingNotification(waitingRoomUsers[0].userId)\"\n aria-label=\"Cerrar notificaci\u00F3n\"\n >\n \u00D7\n </button>\n </div>\n </div>\n\n <!-- Owner geolocation panel (for owners to request user location) -->\n <div class=\"user-geo-panel\" *ngIf=\"shouldShowUserGeoPanel || devModeEnabled\">\n <div class=\"user-geo-header\">\n <div class=\"header-title-row\">\n <i class=\"fa fa-map-marker header-icon\" *ngIf=\"userGeoViewState !== UserGeoViewState.INITIAL\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n </div>\n <button class=\"close-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.HIDDEN)\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n\n <div class=\"user-geo-description\" *ngIf=\"userGeoViewState !== UserGeoViewState.VERIFIED && userGeoViewState !== UserGeoViewState.DENIED\">\n <p>El colaborador tiene la ubicaci\u00F3n desactivada, solicita que la active.</p>\n <p>Esta acci\u00F3n nos permitir\u00E1 disponibilizar algunas alertas.</p>\n </div>\n\n <div class=\"user-geo-content\">\n <!-- INITIAL state: just the button -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.INITIAL\">\n <!-- spacer -->\n </ng-container>\n\n <!-- VERIFYING state: spinner + loading message -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.VERIFYING\">\n <div class=\"user-geo-verifying\">\n <div class=\"geo-spinner-large\"></div>\n <p class=\"verifying-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"verifying-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n </ng-container>\n\n <!-- VERIFIED state: home icon with sparkles -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.VERIFIED\">\n <div class=\"user-geo-verified\">\n <div class=\"verified-icon-container\">\n <span class=\"home-icon\" [innerHTML]=\"homeIcon\"></span>\n </div>\n <p class=\"verified-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n\n <!-- DENIED state: error icon and message -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.DENIED\">\n <div class=\"user-geo-denied\">\n <div class=\"denied-icon-container\">\n <i class=\"fa fa-times-circle\"></i>\n </div>\n <p class=\"denied-title\">La ubicaci\u00F3n fue rechazada</p>\n <p class=\"denied-subtitle\">El colaborador no permiti\u00F3 el acceso a su ubicaci\u00F3n.</p>\n </div>\n </ng-container>\n </div>\n\n <!-- Button (hidden in verified and denied states) -->\n <button \n class=\"user-geo-btn\" \n *ngIf=\"userGeoViewState !== UserGeoViewState.VERIFIED && userGeoViewState !== UserGeoViewState.DENIED\"\n [class.disabled]=\"userGeoViewState === UserGeoViewState.VERIFYING\"\n [disabled]=\"userGeoViewState === UserGeoViewState.VERIFYING\"\n (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n\n <!-- Dev controls -->\n <div class=\"dev-controls\" *ngIf=\"devModeEnabled\">\n <span class=\"dev-label\">Dev:</span>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.INITIAL)\">Initial</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.VERIFYING)\">Verifying</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.VERIFIED)\">Verified</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.DENIED)\">Denied</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.HIDDEN)\">Hide</button>\n </div>\n </div>\n</div>\n\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100vw;height:100vh;box-sizing:border-box;padding:2rem;background:linear-gradient(281deg,rgba(29,164,177,.2) 6.96%,rgba(0,0,0,0) 70.44%),#212532}.tas-videocall-wrapper{display:flex;flex:1;gap:1rem;height:100%}.tas-videocall-container{position:relative;flex:1;height:100%;overflow:hidden;border-radius:8px;border:1px solid var(--Neutral-GreyLight, #dadfe9);background:linear-gradient(180deg,#e5f1f7 0%,#0072ac 100%)}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#0000004d;overflow:hidden}.tas-videocall-container .avatar-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1;display:flex;align-items:center;justify-content:center}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:12px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#33475bb3;padding:12px 20px;border-radius:30px;backdrop-filter:blur(8px)}.tas-videocall-container .controls-container .control-btn{width:44px;height:44px;border-radius:20px;display:flex;align-items:center;justify-content:center;font-size:18px;border:none;background:transparent;cursor:pointer;transition:all .2s ease}.tas-videocall-container .controls-container .control-btn i{color:#fff}.tas-videocall-container .controls-container .control-btn:hover{transform:scale(1.05);filter:brightness(1.1)}.tas-videocall-container .controls-container .control-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .controls-container .hangup-btn{background:#f44336}.tas-videocall-container .controls-container .hangup-btn i{transform:rotate(135deg)}.tas-videocall-container .controls-container .hangup-btn:hover{background:#d32f2f}.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{background:transparent}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover,.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.15)}.tas-videocall-container .waiting-notification{position:absolute;bottom:100px;left:16px;display:flex;align-items:center;gap:12px;background-color:#33475be6;padding:10px 16px;border-radius:8px;z-index:4;backdrop-filter:blur(4px);max-width:calc(100% - 32px)}.tas-videocall-container .waiting-notification .waiting-text{color:#fff;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tas-videocall-container .waiting-notification .admit-btn{background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:4px;padding:6px 16px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;white-space:nowrap}.tas-videocall-container .waiting-notification .admit-btn:hover{background:#178e99}.tas-videocall-container .waiting-notification .admit-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .waiting-notification .dismiss-btn{background:transparent;color:#fff;border:none;font-size:20px;line-height:1;cursor:pointer;padding:0 4px;opacity:.7;transition:opacity .2s ease}.tas-videocall-container .waiting-notification .dismiss-btn:hover{opacity:1}.tas-videocall-container .waiting-notification .dismiss-btn:focus{outline:2px solid #fff;outline-offset:2px}.location-panel{width:280px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.location-panel .location-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.location-panel .location-header h3{font-size:16px;font-weight:600;margin:0;color:#fff}.location-panel .location-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.location-panel .location-header .close-btn:hover{opacity:1}.location-panel .location-description{font-size:14px;color:#fffc;line-height:1.5;margin-bottom:.5rem}.location-panel .location-description p{margin:0 0 .5rem}.location-panel .location-user-list{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;max-height:200px;overflow-y:auto}.location-panel .location-user-list .user-geo-item{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:rgba(255,255,255,.05);border-radius:6px}.location-panel .location-user-list .user-geo-item .user-icon{color:#fff9;font-size:14px}.location-panel .location-user-list .user-geo-item .user-name{flex:1;font-size:14px;color:#fff}.location-panel .location-user-list .user-geo-item .geo-status{display:flex;align-items:center;gap:.35rem;font-size:12px;padding:2px 8px;border-radius:12px}.location-panel .location-user-list .user-geo-item .geo-status.pending{color:#ffc107;background:rgba(255,193,7,.15)}.location-panel .location-user-list .user-geo-item .geo-status.granted{color:#4caf50;background:rgba(76,175,80,.15)}.location-panel .location-user-list .user-geo-item .geo-status.denied{color:#f44336;background:rgba(244,67,54,.15)}.location-panel .location-user-list .user-geo-item .geo-status i{font-size:10px}.location-panel .geo-denied-warning{display:flex;align-items:center;gap:.5rem;padding:.75rem;background:rgba(244,67,54,.15);border-radius:6px;margin-bottom:1rem;color:#f44336;font-size:13px}.location-panel .geo-denied-warning i{font-size:14px}.location-panel .location-content{flex:1;display:flex;flex-direction:column;justify-content:flex-end}.location-panel .verify-location-btn{width:100%;padding:12px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;margin-bottom:1rem}.location-panel .verify-location-btn:hover{background:#178e99}.location-panel .verify-location-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.location-panel .location-footer{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid rgba(255,255,255,.1)}.location-panel .location-footer .footer-icon{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff;font-size:14px}.location-panel .location-footer .footer-icon.location-icon{background:var(--Primary-Uell, #1da4b1)}.location-panel .header-icon{color:#fff;font-size:16px}.location-panel .geo-loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-loading-container .geo-spinner{width:64px;height:64px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite}.location-panel .geo-loading-container .loading-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 .25rem}.location-panel .geo-loading-container .loading-subtitle{color:#ffffffb3;font-size:14px;margin:0}.location-panel .geo-success-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-success-container .success-icon{width:80px;height:80px;border-radius:50%;background:var(--Primary-Uell, #1da4b1);display:flex;align-items:center;justify-content:center;position:relative}.location-panel .geo-success-container .success-icon i{color:#fff;font-size:32px}.location-panel .geo-success-container .success-icon:before,.location-panel .geo-success-container .success-icon:after{content:\"\\2726\";position:absolute;color:#fff;font-size:10px}.location-panel .geo-success-container .success-icon:before{top:-8px;right:-4px}.location-panel .geo-success-container .success-icon:after{bottom:-4px;left:-8px}.location-panel .geo-success-container .success-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 0}.location-panel .verify-location-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}@keyframes geo-spin{to{transform:rotate(360deg)}}.user-geo-panel{width:360px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.user-geo-panel .user-geo-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:2.5rem;padding-top:2rem}.user-geo-panel .user-geo-header .header-title-row{display:flex;align-items:center;gap:.5rem}.user-geo-panel .user-geo-header .header-title-row .header-icon{font-size:16px;color:#fff}.user-geo-panel .user-geo-header .header-title-row h3{font-size:18px;font-weight:600;margin:0;color:#fff}.user-geo-panel .user-geo-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.user-geo-panel .user-geo-header .close-btn:hover{opacity:1}.user-geo-panel .user-geo-description{font-size:14px;color:#fffc;line-height:1.6;margin-bottom:1rem}.user-geo-panel .user-geo-description p{margin:0 0 .75rem}.user-geo-panel .user-geo-content{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center}.user-geo-panel .user-geo-verifying{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-verifying .geo-spinner-large{width:80px;height:80px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite;margin-bottom:1.5rem}.user-geo-panel .user-geo-verifying .verifying-title{color:#fff;font-size:16px;font-weight:600;margin:0 0 .25rem}.user-geo-panel .user-geo-verifying .verifying-subtitle{color:#fff9;font-size:14px;margin:0}.user-geo-panel .user-geo-verified{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-verified .verified-icon-container{margin-bottom:1rem}.user-geo-panel .user-geo-verified .verified-icon-container .home-icon{display:block}.user-geo-panel .user-geo-verified .verified-icon-container .home-icon svg{width:120px;height:100px}.user-geo-panel .user-geo-verified .verified-title{color:#fff;font-size:16px;font-weight:600;margin:0}.user-geo-panel .user-geo-denied{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-denied .denied-icon-container{width:100px;height:100px;border-radius:50%;background:rgba(244,67,54,.2);display:flex;align-items:center;justify-content:center;margin-bottom:1.5rem}.user-geo-panel .user-geo-denied .denied-icon-container i{font-size:48px;color:#f44336}.user-geo-panel .user-geo-denied .denied-title{color:#fff;font-size:16px;font-weight:600;margin:0 0 .5rem}.user-geo-panel .user-geo-denied .denied-subtitle{color:#fff9;font-size:14px;margin:0}.user-geo-panel .user-geo-btn{width:100%;padding:14px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:24px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s ease;margin-top:auto;margin-bottom:1rem}.user-geo-panel .user-geo-btn:hover:not(.disabled){background:#178e99}.user-geo-panel .user-geo-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.user-geo-panel .dev-controls{display:flex;align-items:center;gap:.5rem;padding:.75rem;background:rgba(0,0,0,.3);border-radius:6px;margin-bottom:1rem;flex-wrap:wrap}.user-geo-panel .dev-controls .dev-label{font-size:12px;color:#fff9;font-weight:600}.user-geo-panel .dev-controls .dev-btn{padding:4px 8px;font-size:11px;background:rgba(255,255,255,.1);color:#fff;border:1px solid rgba(255,255,255,.2);border-radius:4px;cursor:pointer;transition:all .2s ease}.user-geo-panel .dev-controls .dev-btn:hover{background:rgba(255,255,255,.2)}.user-geo-panel .user-geo-footer{display:flex;justify-content:center;gap:.75rem;padding-top:.5rem}.user-geo-panel .user-geo-footer .footer-icon{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff9;font-size:16px}.user-geo-panel .user-geo-footer .footer-icon.active{background:var(--Primary-Uell, #1da4b1);color:#fff}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1387
1687
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, decorators: [{
1388
1688
  type: Component,
1389
1689
  args: [{ selector: 'tas-videocall', template: "<div class=\"tas-videocall-wrapper\">\n <div class=\"tas-videocall-container\">\n <!-- Subscriber video (large, background) -->\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Publisher video (small, overlay) -->\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Centered avatar (shown when no video stream) -->\n <div class=\"avatar-container\" *ngIf=\"!hasVideoStream\">\n <tas-avatar [name]=\"participantName\" [size]=\"80\"></tas-avatar>\n </div>\n\n <!-- Controls -->\n <div class=\"controls-container\">\n <button\n class=\"btn control-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n [attr.aria-label]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"btn control-btn swap-btn\"\n (click)=\"toggleSwap()\"\n title=\"Intercambiar vista\"\n aria-label=\"Intercambiar vista\"\n >\n <i class=\"fa fa-refresh\"></i>\n </button>\n <button\n class=\"btn control-btn pip-btn\"\n (click)=\"minimize()\"\n title=\"Minimizar (Picture in Picture)\"\n aria-label=\"Minimizar videollamada\"\n >\n <i class=\"fa fa-compress\"></i>\n </button>\n <button\n class=\"btn control-btn hangup-btn\"\n (click)=\"hangUp()\"\n title=\"Finalizar llamada\"\n aria-label=\"Finalizar llamada\"\n >\n <i class=\"fa fa-phone\"></i>\n </button>\n </div>\n\n <!-- Waiting room notification (shown for OWNER/BACKOFFICE only) -->\n <div\n class=\"waiting-notification\"\n *ngIf=\"waitingRoomUsers.length > 0 && canAdmitUsers\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <span class=\"waiting-text\">\n {{ waitingRoomUsers[0].name }} est\u00E1 en la sala de espera.\n </span>\n <button\n class=\"admit-btn\"\n (click)=\"admitUser(waitingRoomUsers[0].userId)\"\n aria-label=\"Admitir usuario\"\n >\n Admitir\n </button>\n <button\n class=\"dismiss-btn\"\n (click)=\"dismissWaitingNotification(waitingRoomUsers[0].userId)\"\n aria-label=\"Cerrar notificaci\u00F3n\"\n >\n \u00D7\n </button>\n </div>\n </div>\n\n <!-- Owner geolocation panel (for owners to request user location) -->\n <div class=\"user-geo-panel\" *ngIf=\"shouldShowUserGeoPanel || devModeEnabled\">\n <div class=\"user-geo-header\">\n <div class=\"header-title-row\">\n <i class=\"fa fa-map-marker header-icon\" *ngIf=\"userGeoViewState !== UserGeoViewState.INITIAL\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n </div>\n <button class=\"close-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.HIDDEN)\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n\n <div class=\"user-geo-description\" *ngIf=\"userGeoViewState !== UserGeoViewState.VERIFIED && userGeoViewState !== UserGeoViewState.DENIED\">\n <p>El colaborador tiene la ubicaci\u00F3n desactivada, solicita que la active.</p>\n <p>Esta acci\u00F3n nos permitir\u00E1 disponibilizar algunas alertas.</p>\n </div>\n\n <div class=\"user-geo-content\">\n <!-- INITIAL state: just the button -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.INITIAL\">\n <!-- spacer -->\n </ng-container>\n\n <!-- VERIFYING state: spinner + loading message -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.VERIFYING\">\n <div class=\"user-geo-verifying\">\n <div class=\"geo-spinner-large\"></div>\n <p class=\"verifying-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"verifying-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n </ng-container>\n\n <!-- VERIFIED state: home icon with sparkles -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.VERIFIED\">\n <div class=\"user-geo-verified\">\n <div class=\"verified-icon-container\">\n <span class=\"home-icon\" [innerHTML]=\"homeIcon\"></span>\n </div>\n <p class=\"verified-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n\n <!-- DENIED state: error icon and message -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.DENIED\">\n <div class=\"user-geo-denied\">\n <div class=\"denied-icon-container\">\n <i class=\"fa fa-times-circle\"></i>\n </div>\n <p class=\"denied-title\">La ubicaci\u00F3n fue rechazada</p>\n <p class=\"denied-subtitle\">El colaborador no permiti\u00F3 el acceso a su ubicaci\u00F3n.</p>\n </div>\n </ng-container>\n </div>\n\n <!-- Button (hidden in verified and denied states) -->\n <button \n class=\"user-geo-btn\" \n *ngIf=\"userGeoViewState !== UserGeoViewState.VERIFIED && userGeoViewState !== UserGeoViewState.DENIED\"\n [class.disabled]=\"userGeoViewState === UserGeoViewState.VERIFYING\"\n [disabled]=\"userGeoViewState === UserGeoViewState.VERIFYING\"\n (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n\n <!-- Dev controls -->\n <div class=\"dev-controls\" *ngIf=\"devModeEnabled\">\n <span class=\"dev-label\">Dev:</span>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.INITIAL)\">Initial</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.VERIFYING)\">Verifying</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.VERIFIED)\">Verified</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.DENIED)\">Denied</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.HIDDEN)\">Hide</button>\n </div>\n </div>\n</div>\n\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100vw;height:100vh;box-sizing:border-box;padding:2rem;background:linear-gradient(281deg,rgba(29,164,177,.2) 6.96%,rgba(0,0,0,0) 70.44%),#212532}.tas-videocall-wrapper{display:flex;flex:1;gap:1rem;height:100%}.tas-videocall-container{position:relative;flex:1;height:100%;overflow:hidden;border-radius:8px;border:1px solid var(--Neutral-GreyLight, #dadfe9);background:linear-gradient(180deg,#e5f1f7 0%,#0072ac 100%)}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#0000004d;overflow:hidden}.tas-videocall-container .avatar-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1;display:flex;align-items:center;justify-content:center}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:12px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#33475bb3;padding:12px 20px;border-radius:30px;backdrop-filter:blur(8px)}.tas-videocall-container .controls-container .control-btn{width:44px;height:44px;border-radius:20px;display:flex;align-items:center;justify-content:center;font-size:18px;border:none;background:transparent;cursor:pointer;transition:all .2s ease}.tas-videocall-container .controls-container .control-btn i{color:#fff}.tas-videocall-container .controls-container .control-btn:hover{transform:scale(1.05);filter:brightness(1.1)}.tas-videocall-container .controls-container .control-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .controls-container .hangup-btn{background:#f44336}.tas-videocall-container .controls-container .hangup-btn i{transform:rotate(135deg)}.tas-videocall-container .controls-container .hangup-btn:hover{background:#d32f2f}.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{background:transparent}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover,.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.15)}.tas-videocall-container .waiting-notification{position:absolute;bottom:100px;left:16px;display:flex;align-items:center;gap:12px;background-color:#33475be6;padding:10px 16px;border-radius:8px;z-index:4;backdrop-filter:blur(4px);max-width:calc(100% - 32px)}.tas-videocall-container .waiting-notification .waiting-text{color:#fff;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tas-videocall-container .waiting-notification .admit-btn{background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:4px;padding:6px 16px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;white-space:nowrap}.tas-videocall-container .waiting-notification .admit-btn:hover{background:#178e99}.tas-videocall-container .waiting-notification .admit-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .waiting-notification .dismiss-btn{background:transparent;color:#fff;border:none;font-size:20px;line-height:1;cursor:pointer;padding:0 4px;opacity:.7;transition:opacity .2s ease}.tas-videocall-container .waiting-notification .dismiss-btn:hover{opacity:1}.tas-videocall-container .waiting-notification .dismiss-btn:focus{outline:2px solid #fff;outline-offset:2px}.location-panel{width:280px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.location-panel .location-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.location-panel .location-header h3{font-size:16px;font-weight:600;margin:0;color:#fff}.location-panel .location-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.location-panel .location-header .close-btn:hover{opacity:1}.location-panel .location-description{font-size:14px;color:#fffc;line-height:1.5;margin-bottom:.5rem}.location-panel .location-description p{margin:0 0 .5rem}.location-panel .location-user-list{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;max-height:200px;overflow-y:auto}.location-panel .location-user-list .user-geo-item{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:rgba(255,255,255,.05);border-radius:6px}.location-panel .location-user-list .user-geo-item .user-icon{color:#fff9;font-size:14px}.location-panel .location-user-list .user-geo-item .user-name{flex:1;font-size:14px;color:#fff}.location-panel .location-user-list .user-geo-item .geo-status{display:flex;align-items:center;gap:.35rem;font-size:12px;padding:2px 8px;border-radius:12px}.location-panel .location-user-list .user-geo-item .geo-status.pending{color:#ffc107;background:rgba(255,193,7,.15)}.location-panel .location-user-list .user-geo-item .geo-status.granted{color:#4caf50;background:rgba(76,175,80,.15)}.location-panel .location-user-list .user-geo-item .geo-status.denied{color:#f44336;background:rgba(244,67,54,.15)}.location-panel .location-user-list .user-geo-item .geo-status i{font-size:10px}.location-panel .geo-denied-warning{display:flex;align-items:center;gap:.5rem;padding:.75rem;background:rgba(244,67,54,.15);border-radius:6px;margin-bottom:1rem;color:#f44336;font-size:13px}.location-panel .geo-denied-warning i{font-size:14px}.location-panel .location-content{flex:1;display:flex;flex-direction:column;justify-content:flex-end}.location-panel .verify-location-btn{width:100%;padding:12px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;margin-bottom:1rem}.location-panel .verify-location-btn:hover{background:#178e99}.location-panel .verify-location-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.location-panel .location-footer{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid rgba(255,255,255,.1)}.location-panel .location-footer .footer-icon{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff;font-size:14px}.location-panel .location-footer .footer-icon.location-icon{background:var(--Primary-Uell, #1da4b1)}.location-panel .header-icon{color:#fff;font-size:16px}.location-panel .geo-loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-loading-container .geo-spinner{width:64px;height:64px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite}.location-panel .geo-loading-container .loading-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 .25rem}.location-panel .geo-loading-container .loading-subtitle{color:#ffffffb3;font-size:14px;margin:0}.location-panel .geo-success-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-success-container .success-icon{width:80px;height:80px;border-radius:50%;background:var(--Primary-Uell, #1da4b1);display:flex;align-items:center;justify-content:center;position:relative}.location-panel .geo-success-container .success-icon i{color:#fff;font-size:32px}.location-panel .geo-success-container .success-icon:before,.location-panel .geo-success-container .success-icon:after{content:\"\\2726\";position:absolute;color:#fff;font-size:10px}.location-panel .geo-success-container .success-icon:before{top:-8px;right:-4px}.location-panel .geo-success-container .success-icon:after{bottom:-4px;left:-8px}.location-panel .geo-success-container .success-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 0}.location-panel .verify-location-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}@keyframes geo-spin{to{transform:rotate(360deg)}}.user-geo-panel{width:360px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.user-geo-panel .user-geo-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:2.5rem;padding-top:2rem}.user-geo-panel .user-geo-header .header-title-row{display:flex;align-items:center;gap:.5rem}.user-geo-panel .user-geo-header .header-title-row .header-icon{font-size:16px;color:#fff}.user-geo-panel .user-geo-header .header-title-row h3{font-size:18px;font-weight:600;margin:0;color:#fff}.user-geo-panel .user-geo-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.user-geo-panel .user-geo-header .close-btn:hover{opacity:1}.user-geo-panel .user-geo-description{font-size:14px;color:#fffc;line-height:1.6;margin-bottom:1rem}.user-geo-panel .user-geo-description p{margin:0 0 .75rem}.user-geo-panel .user-geo-content{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center}.user-geo-panel .user-geo-verifying{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-verifying .geo-spinner-large{width:80px;height:80px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite;margin-bottom:1.5rem}.user-geo-panel .user-geo-verifying .verifying-title{color:#fff;font-size:16px;font-weight:600;margin:0 0 .25rem}.user-geo-panel .user-geo-verifying .verifying-subtitle{color:#fff9;font-size:14px;margin:0}.user-geo-panel .user-geo-verified{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-verified .verified-icon-container{margin-bottom:1rem}.user-geo-panel .user-geo-verified .verified-icon-container .home-icon{display:block}.user-geo-panel .user-geo-verified .verified-icon-container .home-icon svg{width:120px;height:100px}.user-geo-panel .user-geo-verified .verified-title{color:#fff;font-size:16px;font-weight:600;margin:0}.user-geo-panel .user-geo-denied{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-denied .denied-icon-container{width:100px;height:100px;border-radius:50%;background:rgba(244,67,54,.2);display:flex;align-items:center;justify-content:center;margin-bottom:1.5rem}.user-geo-panel .user-geo-denied .denied-icon-container i{font-size:48px;color:#f44336}.user-geo-panel .user-geo-denied .denied-title{color:#fff;font-size:16px;font-weight:600;margin:0 0 .5rem}.user-geo-panel .user-geo-denied .denied-subtitle{color:#fff9;font-size:14px;margin:0}.user-geo-panel .user-geo-btn{width:100%;padding:14px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:24px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s ease;margin-top:auto;margin-bottom:1rem}.user-geo-panel .user-geo-btn:hover:not(.disabled){background:#178e99}.user-geo-panel .user-geo-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.user-geo-panel .dev-controls{display:flex;align-items:center;gap:.5rem;padding:.75rem;background:rgba(0,0,0,.3);border-radius:6px;margin-bottom:1rem;flex-wrap:wrap}.user-geo-panel .dev-controls .dev-label{font-size:12px;color:#fff9;font-weight:600}.user-geo-panel .dev-controls .dev-btn{padding:4px 8px;font-size:11px;background:rgba(255,255,255,.1);color:#fff;border:1px solid rgba(255,255,255,.2);border-radius:4px;cursor:pointer;transition:all .2s ease}.user-geo-panel .dev-controls .dev-btn:hover{background:rgba(255,255,255,.2)}.user-geo-panel .user-geo-footer{display:flex;justify-content:center;gap:.75rem;padding-top:.5rem}.user-geo-panel .user-geo-footer .footer-icon{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff9;font-size:16px}.user-geo-panel .user-geo-footer .footer-icon.active{background:var(--Primary-Uell, #1da4b1);color:#fff}\n"] }]
1390
- }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }, { type: i4.DomSanitizer }]; }, propDecorators: { sessionId: [{
1690
+ }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }, { type: i4$2.DomSanitizer }, { type: i1.NgbModal }]; }, propDecorators: { sessionId: [{
1391
1691
  type: Input
1392
1692
  }], token: [{
1393
1693
  type: Input
@@ -1436,6 +1736,7 @@ class TasWaitingRoomComponent {
1436
1736
  this.state = WaitingRoomState.CHECKING_STATUS;
1437
1737
  this.WaitingRoomState = WaitingRoomState; // Expose enum to template
1438
1738
  this.errorMessage = '';
1739
+ this.showErrorDetails = false;
1439
1740
  this.isJoinable = false;
1440
1741
  // Session data from status response
1441
1742
  this.resolvedSessionId = '';
@@ -1773,10 +2074,24 @@ class TasWaitingRoomComponent {
1773
2074
  retry() {
1774
2075
  this.state = WaitingRoomState.CHECKING_STATUS;
1775
2076
  this.errorMessage = '';
2077
+ this.showErrorDetails = false;
1776
2078
  this.token = '';
1777
2079
  this.retryCount = 0; // Reset retry count for auto-retry
1778
2080
  this.checkStatus();
1779
2081
  }
2082
+ /**
2083
+ * Toggle the error details dropdown visibility.
2084
+ */
2085
+ toggleErrorDetails() {
2086
+ this.showErrorDetails = !this.showErrorDetails;
2087
+ }
2088
+ /**
2089
+ * Close the waiting room modal.
2090
+ */
2091
+ close() {
2092
+ this.tasService.stopStatusPolling();
2093
+ this.activeModal.dismiss('close');
2094
+ }
1780
2095
  openVideoCallModal() {
1781
2096
  this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
1782
2097
  size: 'xl',
@@ -1800,10 +2115,10 @@ class TasWaitingRoomComponent {
1800
2115
  }
1801
2116
  }
1802
2117
  TasWaitingRoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: GeolocationService }, { token: i1.NgbModal }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1803
- TasWaitingRoomComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasWaitingRoomComponent, selector: "tas-waiting-room", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser" }, ngImport: i0, template: "<div class=\"tas-waiting-room\">\n <div class=\"waiting-room-content\">\n <div class=\"state-container\">\n <!-- Loading states (show spinner) -->\n <ng-container *ngIf=\"state !== WaitingRoomState.ERROR\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n \n <!-- Requesting permissions -->\n <ng-container *ngIf=\"isRequestingPermissions\">\n <p class=\"state-message\">Solicitando permisos...</p>\n <p class=\"state-submessage\">Por favor, acept\u00E1 los permisos de c\u00E1mara y micr\u00F3fono.</p>\n </ng-container>\n \n <!-- Waiting for admission (permissions granted, not joinable yet) -->\n <ng-container *ngIf=\"isWaitingForAdmission\">\n <p class=\"state-message\">Medicina laboral va a admitirte</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </ng-container>\n \n <!-- Getting token / Joining -->\n <ng-container *ngIf=\"state === WaitingRoomState.GETTING_TOKEN || state === WaitingRoomState.JOINING\">\n <p class=\"state-message\">Conectando...</p>\n </ng-container>\n \n <!-- Checking status (when not requesting permissions) -->\n <ng-container *ngIf=\"state === WaitingRoomState.CHECKING_STATUS && !isRequestingPermissions\">\n <p class=\"state-message\">Verificando estado...</p>\n </ng-container>\n </ng-container>\n\n <!-- Error state (show error icon and message) -->\n <ng-container *ngIf=\"state === WaitingRoomState.ERROR\">\n <div class=\"state-icon error\">\n <i class=\"fa fa-exclamation-triangle\"></i>\n </div>\n <p class=\"state-message error\">Ocurri\u00F3 un error</p>\n <p class=\"error-details\" *ngIf=\"errorMessage\">{{ errorMessage }}</p>\n <button type=\"button\" class=\"btn action-btn retry-btn\" (click)=\"retry()\">\n <i class=\"fa fa-refresh\"></i>\n Reintentar\n </button>\n </ng-container>\n </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:300px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:400px;width:100%}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-details{font-size:13px;color:#ee316b;margin:0 0 24px;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2)}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@media (max-width: 576px){.tas-waiting-room{min-height:250px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.action-btn{padding:10px 24px;font-size:14px}}\n"], directives: [{ type: i4$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
2118
+ TasWaitingRoomComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasWaitingRoomComponent, selector: "tas-waiting-room", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser" }, ngImport: i0, template: "<div class=\"tas-waiting-room\">\n <div class=\"waiting-room-content\">\n <div class=\"state-container\">\n <!-- Loading states (show spinner) -->\n <ng-container *ngIf=\"state !== WaitingRoomState.ERROR\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n \n <!-- Requesting permissions -->\n <ng-container *ngIf=\"isRequestingPermissions\">\n <p class=\"state-message\">Solicitando permisos...</p>\n <p class=\"state-submessage\">Por favor, acept\u00E1 los permisos de c\u00E1mara y micr\u00F3fono.</p>\n </ng-container>\n \n <!-- Waiting for admission (permissions granted, not joinable yet) -->\n <ng-container *ngIf=\"isWaitingForAdmission\">\n <p class=\"state-message\">Medicina laboral va a admitirte</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </ng-container>\n \n <!-- Getting token / Joining -->\n <ng-container *ngIf=\"state === WaitingRoomState.GETTING_TOKEN || state === WaitingRoomState.JOINING\">\n <p class=\"state-message\">Conectando...</p>\n </ng-container>\n \n <!-- Checking status (when not requesting permissions) -->\n <ng-container *ngIf=\"state === WaitingRoomState.CHECKING_STATUS && !isRequestingPermissions\">\n <p class=\"state-message\">Verificando estado...</p>\n </ng-container>\n </ng-container>\n\n <!-- Error state (show error icon and message) -->\n <ng-container *ngIf=\"state === WaitingRoomState.ERROR\">\n <div class=\"state-icon error\">\n <i class=\"fa fa-exclamation-triangle\"></i>\n </div>\n <p class=\"state-message error\">Ocurri\u00F3 un error</p>\n \n <!-- Collapsible error details -->\n <div class=\"error-dropdown\" *ngIf=\"errorMessage\">\n <button \n type=\"button\" \n class=\"error-toggle-btn\"\n (click)=\"toggleErrorDetails()\"\n [attr.aria-expanded]=\"showErrorDetails\"\n aria-controls=\"error-details-content\"\n >\n <span>Ver detalles del error</span>\n <i class=\"fa\" [class.fa-chevron-down]=\"!showErrorDetails\" [class.fa-chevron-up]=\"showErrorDetails\"></i>\n </button>\n <div \n id=\"error-details-content\"\n class=\"error-details-content\"\n [class.expanded]=\"showErrorDetails\"\n >\n <p class=\"error-details-text\">{{ errorMessage }}</p>\n </div>\n </div>\n \n <div class=\"error-actions\">\n <button type=\"button\" class=\"btn action-btn retry-btn\" (click)=\"retry()\">\n <i class=\"fa fa-refresh\"></i>\n Reintentar\n </button>\n <button type=\"button\" class=\"btn action-btn close-btn\" (click)=\"close()\">\n Cerrar\n </button>\n </div>\n </ng-container>\n </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:300px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:400px;width:100%}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-dropdown{margin:16px 0;width:100%}.error-toggle-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:10px 16px;font-size:13px;font-weight:500;color:#6c757d;background:transparent;border:1px solid #e9ecef;border-radius:6px;cursor:pointer;transition:all .2s ease}.error-toggle-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.error-toggle-btn i{font-size:12px;transition:transform .2s ease}.error-details-content{max-height:0;overflow:hidden;transition:max-height .3s ease,padding .3s ease}.error-details-content.expanded{max-height:200px;padding-top:12px}.error-details-text{font-size:12px;color:#ee316b;margin:0;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2);text-align:left;word-break:break-word;max-height:150px;overflow-y:auto}.error-actions{display:flex;justify-content:center;gap:12px;margin-top:16px}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.action-btn.close-btn{background:#ee316b;color:#fff;border:none}.action-btn.close-btn:hover{background:#da124f}@keyframes spin{to{transform:rotate(360deg)}}@media (max-width: 576px){.tas-waiting-room{min-height:250px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.action-btn{padding:10px 24px;font-size:14px}}\n"], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1804
2119
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, decorators: [{
1805
2120
  type: Component,
1806
- args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n <div class=\"waiting-room-content\">\n <div class=\"state-container\">\n <!-- Loading states (show spinner) -->\n <ng-container *ngIf=\"state !== WaitingRoomState.ERROR\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n \n <!-- Requesting permissions -->\n <ng-container *ngIf=\"isRequestingPermissions\">\n <p class=\"state-message\">Solicitando permisos...</p>\n <p class=\"state-submessage\">Por favor, acept\u00E1 los permisos de c\u00E1mara y micr\u00F3fono.</p>\n </ng-container>\n \n <!-- Waiting for admission (permissions granted, not joinable yet) -->\n <ng-container *ngIf=\"isWaitingForAdmission\">\n <p class=\"state-message\">Medicina laboral va a admitirte</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </ng-container>\n \n <!-- Getting token / Joining -->\n <ng-container *ngIf=\"state === WaitingRoomState.GETTING_TOKEN || state === WaitingRoomState.JOINING\">\n <p class=\"state-message\">Conectando...</p>\n </ng-container>\n \n <!-- Checking status (when not requesting permissions) -->\n <ng-container *ngIf=\"state === WaitingRoomState.CHECKING_STATUS && !isRequestingPermissions\">\n <p class=\"state-message\">Verificando estado...</p>\n </ng-container>\n </ng-container>\n\n <!-- Error state (show error icon and message) -->\n <ng-container *ngIf=\"state === WaitingRoomState.ERROR\">\n <div class=\"state-icon error\">\n <i class=\"fa fa-exclamation-triangle\"></i>\n </div>\n <p class=\"state-message error\">Ocurri\u00F3 un error</p>\n <p class=\"error-details\" *ngIf=\"errorMessage\">{{ errorMessage }}</p>\n <button type=\"button\" class=\"btn action-btn retry-btn\" (click)=\"retry()\">\n <i class=\"fa fa-refresh\"></i>\n Reintentar\n </button>\n </ng-container>\n </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:300px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:400px;width:100%}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-details{font-size:13px;color:#ee316b;margin:0 0 24px;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2)}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@media (max-width: 576px){.tas-waiting-room{min-height:250px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.action-btn{padding:10px 24px;font-size:14px}}\n"] }]
2121
+ args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n <div class=\"waiting-room-content\">\n <div class=\"state-container\">\n <!-- Loading states (show spinner) -->\n <ng-container *ngIf=\"state !== WaitingRoomState.ERROR\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n \n <!-- Requesting permissions -->\n <ng-container *ngIf=\"isRequestingPermissions\">\n <p class=\"state-message\">Solicitando permisos...</p>\n <p class=\"state-submessage\">Por favor, acept\u00E1 los permisos de c\u00E1mara y micr\u00F3fono.</p>\n </ng-container>\n \n <!-- Waiting for admission (permissions granted, not joinable yet) -->\n <ng-container *ngIf=\"isWaitingForAdmission\">\n <p class=\"state-message\">Medicina laboral va a admitirte</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </ng-container>\n \n <!-- Getting token / Joining -->\n <ng-container *ngIf=\"state === WaitingRoomState.GETTING_TOKEN || state === WaitingRoomState.JOINING\">\n <p class=\"state-message\">Conectando...</p>\n </ng-container>\n \n <!-- Checking status (when not requesting permissions) -->\n <ng-container *ngIf=\"state === WaitingRoomState.CHECKING_STATUS && !isRequestingPermissions\">\n <p class=\"state-message\">Verificando estado...</p>\n </ng-container>\n </ng-container>\n\n <!-- Error state (show error icon and message) -->\n <ng-container *ngIf=\"state === WaitingRoomState.ERROR\">\n <div class=\"state-icon error\">\n <i class=\"fa fa-exclamation-triangle\"></i>\n </div>\n <p class=\"state-message error\">Ocurri\u00F3 un error</p>\n \n <!-- Collapsible error details -->\n <div class=\"error-dropdown\" *ngIf=\"errorMessage\">\n <button \n type=\"button\" \n class=\"error-toggle-btn\"\n (click)=\"toggleErrorDetails()\"\n [attr.aria-expanded]=\"showErrorDetails\"\n aria-controls=\"error-details-content\"\n >\n <span>Ver detalles del error</span>\n <i class=\"fa\" [class.fa-chevron-down]=\"!showErrorDetails\" [class.fa-chevron-up]=\"showErrorDetails\"></i>\n </button>\n <div \n id=\"error-details-content\"\n class=\"error-details-content\"\n [class.expanded]=\"showErrorDetails\"\n >\n <p class=\"error-details-text\">{{ errorMessage }}</p>\n </div>\n </div>\n \n <div class=\"error-actions\">\n <button type=\"button\" class=\"btn action-btn retry-btn\" (click)=\"retry()\">\n <i class=\"fa fa-refresh\"></i>\n Reintentar\n </button>\n <button type=\"button\" class=\"btn action-btn close-btn\" (click)=\"close()\">\n Cerrar\n </button>\n </div>\n </ng-container>\n </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:300px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:400px;width:100%}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-dropdown{margin:16px 0;width:100%}.error-toggle-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:10px 16px;font-size:13px;font-weight:500;color:#6c757d;background:transparent;border:1px solid #e9ecef;border-radius:6px;cursor:pointer;transition:all .2s ease}.error-toggle-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.error-toggle-btn i{font-size:12px;transition:transform .2s ease}.error-details-content{max-height:0;overflow:hidden;transition:max-height .3s ease,padding .3s ease}.error-details-content.expanded{max-height:200px;padding-top:12px}.error-details-text{font-size:12px;color:#ee316b;margin:0;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2);text-align:left;word-break:break-word;max-height:150px;overflow-y:auto}.error-actions{display:flex;justify-content:center;gap:12px;margin-top:16px}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.action-btn.close-btn{background:#ee316b;color:#fff;border:none}.action-btn.close-btn:hover{background:#da124f}@keyframes spin{to{transform:rotate(360deg)}}@media (max-width: 576px){.tas-waiting-room{min-height:250px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.action-btn{padding:10px 24px;font-size:14px}}\n"] }]
1807
2122
  }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }, { type: i1.NgbModal }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { roomType: [{
1808
2123
  type: Input
1809
2124
  }], entityId: [{
@@ -1827,9 +2142,6 @@ class TasButtonComponent {
1827
2142
  // Style customization
1828
2143
  this.variant = 'default';
1829
2144
  this.buttonLabel = 'Iniciar TAS';
1830
- // Skip status check - when true, button shows immediately without API call
1831
- // Useful when parent component already knows the appointment is valid
1832
- this.skipStatusCheck = false;
1833
2145
  this.isLoading = false;
1834
2146
  // Status check state
1835
2147
  this.isCheckingStatus = false;
@@ -1874,12 +2186,6 @@ class TasButtonComponent {
1874
2186
  this.videoCallModalRef = null;
1875
2187
  }
1876
2188
  }));
1877
- // If skipStatusCheck is true, show button immediately without polling
1878
- if (this.skipStatusCheck) {
1879
- this.shouldShowButton = true;
1880
- this.isJoinable = true;
1881
- return;
1882
- }
1883
2189
  // Start status checking
1884
2190
  this.startStatusPolling();
1885
2191
  }
@@ -2001,7 +2307,7 @@ class TasButtonComponent {
2001
2307
  }
2002
2308
  }
2003
2309
  TasButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, deps: [{ token: i1.NgbModal }, { token: TasService }, { token: TasUtilityService }], target: i0.ɵɵFactoryTarget.Component });
2004
- TasButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasButtonComponent, selector: "tas-btn", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser", variant: "variant", buttonLabel: "buttonLabel", skipStatusCheck: "skipStatusCheck" }, ngImport: i0, template: "<span\n *ngIf=\"shouldShowButton\"\n [ngbTooltip]=\"isDisabled && disabledReason ? disabledReason : null\"\n container=\"body\"\n placement=\"top\"\n tooltipClass=\"tas-btn-tooltip\"\n>\n <button\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n [class.tas-btn--teal]=\"variant === 'teal'\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n >\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">{{ buttonLabel }}</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n </button>\n</span>\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px;display:flex;padding:6px 14px;justify-content:center;align-items:center;gap:7px;flex-shrink:0;flex-grow:0}.tas-btn:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.tas-btn:hover:not(:disabled){background-color:#d62a5f!important;border-color:#d62a5f!important}.tas-btn i{margin-right:5px}.tas-btn--teal{background-color:#0097a7!important;border-color:#0097a7!important;border-radius:24px;font-weight:500;margin-right:0;padding:6px 14px}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"], directives: [{ type: i4$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "triggers", "container", "disableTooltip", "tooltipClass", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }] });
2310
+ TasButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasButtonComponent, selector: "tas-btn", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser", variant: "variant", buttonLabel: "buttonLabel" }, ngImport: i0, template: "<span\n *ngIf=\"shouldShowButton\"\n [ngbTooltip]=\"isDisabled && disabledReason ? disabledReason : null\"\n container=\"body\"\n placement=\"top\"\n tooltipClass=\"tas-btn-tooltip\"\n>\n <button\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n [class.tas-btn--teal]=\"variant === 'teal'\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n >\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">{{ buttonLabel }}</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n </button>\n</span>\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px;display:flex;padding:6px 14px;justify-content:center;align-items:center;gap:7px;flex-shrink:0;flex-grow:0}.tas-btn:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.tas-btn:hover:not(:disabled){background-color:#d62a5f!important;border-color:#d62a5f!important}.tas-btn i{margin-right:5px}.tas-btn--teal{background-color:#0097a7!important;border-color:#0097a7!important;border-radius:24px;font-weight:500;margin-right:0;padding:6px 14px}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "triggers", "container", "disableTooltip", "tooltipClass", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }] });
2005
2311
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, decorators: [{
2006
2312
  type: Component,
2007
2313
  args: [{ selector: 'tas-btn', template: "<span\n *ngIf=\"shouldShowButton\"\n [ngbTooltip]=\"isDisabled && disabledReason ? disabledReason : null\"\n container=\"body\"\n placement=\"top\"\n tooltipClass=\"tas-btn-tooltip\"\n>\n <button\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n [class.tas-btn--teal]=\"variant === 'teal'\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n >\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">{{ buttonLabel }}</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n </button>\n</span>\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px;display:flex;padding:6px 14px;justify-content:center;align-items:center;gap:7px;flex-shrink:0;flex-grow:0}.tas-btn:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.tas-btn:hover:not(:disabled){background-color:#d62a5f!important;border-color:#d62a5f!important}.tas-btn i{margin-right:5px}.tas-btn--teal{background-color:#0097a7!important;border-color:#0097a7!important;border-radius:24px;font-weight:500;margin-right:0;padding:6px 14px}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"] }]
@@ -2019,8 +2325,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
2019
2325
  type: Input
2020
2326
  }], buttonLabel: [{
2021
2327
  type: Input
2022
- }], skipStatusCheck: [{
2023
- type: Input
2024
2328
  }] } });
2025
2329
 
2026
2330
  class TasFloatingCallComponent {
@@ -2031,6 +2335,7 @@ class TasFloatingCallComponent {
2031
2335
  this.isMuted = false;
2032
2336
  this.subscriptions = new Subscription();
2033
2337
  this.videoCallModalRef = null;
2338
+ this.feedbackShown = false; // Track if feedback modal has been shown
2034
2339
  // Margin from screen edges (in pixels)
2035
2340
  this.PIP_MARGIN = 20;
2036
2341
  }
@@ -2052,12 +2357,45 @@ class TasFloatingCallComponent {
2052
2357
  toggleMute() {
2053
2358
  this.tasService.toggleMute();
2054
2359
  }
2360
+ /**
2361
+ * Open feedback modal when call ends from PiP mode
2362
+ */
2363
+ openFeedbackModal() {
2364
+ // Prevent opening feedback modal multiple times
2365
+ if (this.feedbackShown) {
2366
+ this.isVisible = false;
2367
+ return;
2368
+ }
2369
+ // Get videoCallId from service
2370
+ const videoCallId = this.tasService.videoCallId;
2371
+ // If no videoCallId, skip feedback and hide directly
2372
+ if (!videoCallId) {
2373
+ this.isVisible = false;
2374
+ return;
2375
+ }
2376
+ this.feedbackShown = true;
2377
+ const modalRef = this.modalService.open(TasFeedbackModalComponent, {
2378
+ centered: true,
2379
+ backdrop: true,
2380
+ keyboard: true,
2381
+ windowClass: 'tas-feedback-modal-wrapper',
2382
+ });
2383
+ modalRef.componentInstance.videoCallId = videoCallId;
2384
+ modalRef.componentInstance.tenant = this.tasService.tenant ?? '';
2385
+ modalRef.componentInstance.businessRole = this.tasService.businessRole;
2386
+ // Hide floating call after feedback modal is closed/dismissed
2387
+ modalRef.result.finally(() => {
2388
+ this.isVisible = false;
2389
+ this.feedbackShown = false; // Reset for potential future calls
2390
+ });
2391
+ }
2055
2392
  // Private Methods
2056
2393
  setupSubscriptions() {
2057
2394
  // Call state subscription
2058
2395
  this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
2059
- if (state === CallState.DISCONNECTED) {
2060
- this.isVisible = false;
2396
+ // Only handle disconnect feedback if we're in PiP mode (isVisible)
2397
+ if (state === CallState.DISCONNECTED && this.isVisible) {
2398
+ this.openFeedbackModal();
2061
2399
  }
2062
2400
  }));
2063
2401
  // View mode subscription
@@ -2179,10 +2517,13 @@ class TasIncomingAppointmentComponent {
2179
2517
  this.appointments = [];
2180
2518
  this.isLoading = true;
2181
2519
  this.hasError = false;
2520
+ // The appointmentId from status API - only this appointment shows tas-btn
2521
+ this.activeAppointmentId = null;
2182
2522
  this.subscriptions = new Subscription();
2183
2523
  }
2184
2524
  ngOnInit() {
2185
2525
  this.loadAppointments();
2526
+ this.checkStatus();
2186
2527
  }
2187
2528
  ngOnDestroy() {
2188
2529
  this.subscriptions.unsubscribe();
@@ -2200,8 +2541,9 @@ class TasIncomingAppointmentComponent {
2200
2541
  const appointments = Array.isArray(response)
2201
2542
  ? response
2202
2543
  : response?.content || [];
2203
- // Sort by date and startTime descending (most recent first)
2204
- this.appointments = appointments.sort((a, b) => {
2544
+ // Deduplicate by id and sort by date and startTime ascending (earliest first)
2545
+ const uniqueAppointments = appointments.filter((appt, index, self) => index === self.findIndex((a) => a.id === appt.id));
2546
+ this.appointments = uniqueAppointments.sort((a, b) => {
2205
2547
  const dateTimeA = `${a.date}T${a.startTime}`;
2206
2548
  const dateTimeB = `${b.date}T${b.startTime}`;
2207
2549
  return dateTimeB.localeCompare(dateTimeA);
@@ -2214,15 +2556,49 @@ class TasIncomingAppointmentComponent {
2214
2556
  },
2215
2557
  }));
2216
2558
  }
2559
+ /**
2560
+ * Check status endpoint to get the active appointmentId
2561
+ */
2562
+ checkStatus() {
2563
+ if (!this.tenant || !this.entityId) {
2564
+ return;
2565
+ }
2566
+ this.subscriptions.add(this.tasService
2567
+ .getProxyVideoStatus({
2568
+ roomType: this.roomType,
2569
+ entityId: this.entityId,
2570
+ tenant: this.tenant,
2571
+ businessRole: this.businessRole,
2572
+ })
2573
+ .subscribe({
2574
+ next: (response) => {
2575
+ // Store the appointmentId from status - tas-btn only shows for this one
2576
+ this.activeAppointmentId = response?.content?.appointmentId ?? null;
2577
+ },
2578
+ error: () => {
2579
+ this.activeAppointmentId = null;
2580
+ },
2581
+ }));
2582
+ }
2217
2583
  onEnterCall(appointment) {
2218
2584
  this.enterCall.emit(appointment);
2219
2585
  }
2220
2586
  /**
2221
- * Check if tas-btn should be shown for an appointment (CONFIRMED or ACTIVE status)
2587
+ * Check if tas-btn should be shown for an appointment.
2588
+ * Only shows when appointment.id matches the activeAppointmentId from status API.
2589
+ * tas-btn handles its own polling for joinable state.
2222
2590
  */
2223
2591
  shouldShowTasBtn(appointment) {
2224
- return appointment.status === AppointmentStatus.CONFIRMED ||
2592
+ const hasValidStatus = appointment.status === AppointmentStatus.CONFIRMED ||
2225
2593
  appointment.status === AppointmentStatus.ACTIVE;
2594
+ // Only show for the appointment that matches status API response
2595
+ return hasValidStatus && appointment.id === this.activeAppointmentId;
2596
+ }
2597
+ /**
2598
+ * TrackBy function for ngFor
2599
+ */
2600
+ trackByAppointmentId(index, appointment) {
2601
+ return appointment.id;
2226
2602
  }
2227
2603
  /**
2228
2604
  * Format date to Spanish format: "Lunes 8 de diciembre"
@@ -2254,17 +2630,17 @@ class TasIncomingAppointmentComponent {
2254
2630
  */
2255
2631
  getOtherParticipant(appointment) {
2256
2632
  if (!appointment.participants || appointment.participants.length === 0) {
2257
- return appointment.title; // Fallback to title if no participants
2633
+ return appointment.title;
2258
2634
  }
2259
2635
  const otherParticipant = appointment.participants.find(p => p.userId !== this.currentUser?.id);
2260
2636
  return otherParticipant?.name || appointment.title;
2261
2637
  }
2262
2638
  }
2263
2639
  TasIncomingAppointmentComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, deps: [{ token: TasService }], target: i0.ɵɵFactoryTarget.Component });
2264
- TasIncomingAppointmentComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasIncomingAppointmentComponent, selector: "tas-incoming-appointment", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser", fromDate: "fromDate", toDate: "toDate" }, outputs: { enterCall: "enterCall" }, ngImport: i0, template: "<div class=\"incoming-appointment-card\">\n <h3 class=\"card-title\">Pr\u00F3ximo turno</h3>\n\n <!-- Loading state -->\n <div class=\"card-content\" *ngIf=\"isLoading\">\n <div class=\"loading-spinner\"></div>\n </div>\n\n <!-- Empty state -->\n <div class=\"card-content empty-state\" *ngIf=\"!isLoading && appointments.length === 0\">\n <div class=\"icon-container\">\n <div class=\"icon-circle\">\n <i class=\"fa fa-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"sparkle sparkle-1\">\u2726</span>\n <span class=\"sparkle sparkle-2\">\u2726</span>\n <span class=\"sparkle sparkle-3\">\u2726</span>\n <span class=\"sparkle sparkle-4\">\u2726</span>\n </div>\n <h4 class=\"empty-title\">Todav\u00EDa no ten\u00E9s turnos agendados</h4>\n <p class=\"empty-subtitle\">\n En caso de que Medicina Laboral requiera una consulta, lo ver\u00E1s en esta secci\u00F3n.\n </p>\n </div>\n\n <!-- Appointments list -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointments.length > 0\">\n <div class=\"appointment-card\" *ngFor=\"let appt of appointments\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"getOtherParticipant(appt)\" [size]=\"48\"></tas-avatar>\n <span class=\"doctor-name\">{{ getOtherParticipant(appt) }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formatAppointmentDate(appt) }}</span>\n <span class=\"time\">{{ formatTimeRange(appt) }}</span>\n </div>\n <tas-btn\n *ngIf=\"shouldShowTasBtn(appt)\"\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"appt.roomType\"\n [entityId]=\"appt.entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\n\n", styles: [":host{display:block}.incoming-appointment-card{background:#ffffff;border-radius:12px;box-shadow:0 2px 8px #00000014;padding:24px;min-width:360px}.card-title{font-size:16px;font-weight:400;color:#6b7280;margin:0 0 24px}.card-content{display:flex;flex-direction:column;align-items:center}.loading-spinner{width:40px;height:40px;border:3px solid #e0f7fa;border-top-color:#0097a7;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:20px 0}.icon-container{position:relative;width:120px;height:120px;margin-bottom:24px}.icon-circle{width:100%;height:100%;background:#e0f7fa;border-radius:50%;display:flex;align-items:center;justify-content:center}.icon-circle i{font-size:40px;color:#0097a7}.sparkle{position:absolute;color:#0097a7;font-size:12px}.sparkle-1{top:10px;right:5px}.sparkle-2{top:0;right:30px}.sparkle-3{top:25px;left:0}.sparkle-4{bottom:20px;right:0}.empty-title{font-size:18px;font-weight:600;color:#1f2937;margin:0 0 12px}.empty-subtitle{font-size:14px;color:#6b7280;margin:0;max-width:320px;line-height:1.5}.appointment-state{align-items:stretch;gap:16px}.appointment-card{border-radius:12px;border:1px solid var(--Primary-White-Uell50, #8ED1D8);background:#F1FAFA;padding:1.5rem}.appointment-header{display:flex;align-items:center;gap:12px;margin-bottom:16px}.doctor-name{overflow:hidden;color:var(--Neutral-GreyDark, #383E52);text-overflow:ellipsis;font-size:16px;font-style:normal;font-weight:600;line-height:24px;letter-spacing:.016px}.appointment-details{display:flex;justify-content:space-between;align-items:flex-end}.date-time{display:flex;flex-direction:column;gap:4px}.date{color:var(--Neutral-GreyDark, #383E52);font-size:12px;font-style:normal;font-weight:600;line-height:16px;letter-spacing:.06px}.time{font-size:14px;color:var(--Neutral-GreyDark, #383E52);font-family:Inter;font-size:10px;font-style:normal;font-weight:400;line-height:14px;letter-spacing:.04px}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }, { type: TasButtonComponent, selector: "tas-btn", inputs: ["roomType", "entityId", "tenant", "businessRole", "currentUser", "variant", "buttonLabel", "skipStatusCheck"] }], directives: [{ type: i4$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i4$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
2640
+ TasIncomingAppointmentComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasIncomingAppointmentComponent, selector: "tas-incoming-appointment", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser", fromDate: "fromDate", toDate: "toDate" }, outputs: { enterCall: "enterCall" }, ngImport: i0, template: "<div class=\"incoming-appointment-card\">\n <h3 class=\"card-title\">Pr\u00F3ximo turno</h3>\n\n <!-- Loading state -->\n <div class=\"card-content\" *ngIf=\"isLoading\">\n <div class=\"loading-spinner\"></div>\n </div>\n\n <!-- Empty state -->\n <div class=\"card-content empty-state\" *ngIf=\"!isLoading && appointments.length === 0\">\n <div class=\"icon-container\">\n <div class=\"icon-circle\">\n <i class=\"fa fa-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"sparkle sparkle-1\">\u2726</span>\n <span class=\"sparkle sparkle-2\">\u2726</span>\n <span class=\"sparkle sparkle-3\">\u2726</span>\n <span class=\"sparkle sparkle-4\">\u2726</span>\n </div>\n <h4 class=\"empty-title\">Todav\u00EDa no ten\u00E9s turnos agendados</h4>\n <p class=\"empty-subtitle\">\n En caso de que Medicina Laboral requiera una consulta, lo ver\u00E1s en esta secci\u00F3n.\n </p>\n </div>\n\n <!-- Appointments list -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointments.length > 0\">\n <div class=\"appointment-card\" *ngFor=\"let appt of appointments; trackBy: trackByAppointmentId\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"getOtherParticipant(appt)\" [size]=\"48\"></tas-avatar>\n <span class=\"doctor-name\">{{ getOtherParticipant(appt) }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formatAppointmentDate(appt) }}</span>\n <span class=\"time\">{{ formatTimeRange(appt) }}</span>\n </div>\n <tas-btn\n *ngIf=\"shouldShowTasBtn(appt)\"\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"appt.roomType\"\n [entityId]=\"appt.entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\n\n", styles: [":host{display:block}.incoming-appointment-card{background:#ffffff;border-radius:12px;box-shadow:0 2px 8px #00000014;padding:24px;min-width:360px}.card-title{font-size:16px;font-weight:400;color:#6b7280;margin:0 0 24px}.card-content{display:flex;flex-direction:column;align-items:center}.loading-spinner{width:40px;height:40px;border:3px solid #e0f7fa;border-top-color:#0097a7;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:20px 0}.icon-container{position:relative;width:120px;height:120px;margin-bottom:24px}.icon-circle{width:100%;height:100%;background:#e0f7fa;border-radius:50%;display:flex;align-items:center;justify-content:center}.icon-circle i{font-size:40px;color:#0097a7}.sparkle{position:absolute;color:#0097a7;font-size:12px}.sparkle-1{top:10px;right:5px}.sparkle-2{top:0;right:30px}.sparkle-3{top:25px;left:0}.sparkle-4{bottom:20px;right:0}.empty-title{font-size:18px;font-weight:600;color:#1f2937;margin:0 0 12px}.empty-subtitle{font-size:14px;color:#6b7280;margin:0;max-width:320px;line-height:1.5}.appointment-state{align-items:stretch;gap:16px}.appointment-card{border-radius:12px;border:1px solid var(--Primary-White-Uell50, #8ED1D8);background:#F1FAFA;padding:1.5rem}.appointment-header{display:flex;align-items:center;gap:12px;margin-bottom:16px}.doctor-name{overflow:hidden;color:var(--Neutral-GreyDark, #383E52);text-overflow:ellipsis;font-size:16px;font-style:normal;font-weight:600;line-height:24px;letter-spacing:.016px}.appointment-details{display:flex;justify-content:space-between;align-items:flex-end}.date-time{display:flex;flex-direction:column;gap:4px}.date{color:var(--Neutral-GreyDark, #383E52);font-size:12px;font-style:normal;font-weight:600;line-height:16px;letter-spacing:.06px}.time{font-size:14px;color:var(--Neutral-GreyDark, #383E52);font-family:Inter;font-size:10px;font-style:normal;font-weight:400;line-height:14px;letter-spacing:.04px}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }, { type: TasButtonComponent, selector: "tas-btn", inputs: ["roomType", "entityId", "tenant", "businessRole", "currentUser", "variant", "buttonLabel"] }], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
2265
2641
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, decorators: [{
2266
2642
  type: Component,
2267
- args: [{ selector: 'tas-incoming-appointment', template: "<div class=\"incoming-appointment-card\">\n <h3 class=\"card-title\">Pr\u00F3ximo turno</h3>\n\n <!-- Loading state -->\n <div class=\"card-content\" *ngIf=\"isLoading\">\n <div class=\"loading-spinner\"></div>\n </div>\n\n <!-- Empty state -->\n <div class=\"card-content empty-state\" *ngIf=\"!isLoading && appointments.length === 0\">\n <div class=\"icon-container\">\n <div class=\"icon-circle\">\n <i class=\"fa fa-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"sparkle sparkle-1\">\u2726</span>\n <span class=\"sparkle sparkle-2\">\u2726</span>\n <span class=\"sparkle sparkle-3\">\u2726</span>\n <span class=\"sparkle sparkle-4\">\u2726</span>\n </div>\n <h4 class=\"empty-title\">Todav\u00EDa no ten\u00E9s turnos agendados</h4>\n <p class=\"empty-subtitle\">\n En caso de que Medicina Laboral requiera una consulta, lo ver\u00E1s en esta secci\u00F3n.\n </p>\n </div>\n\n <!-- Appointments list -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointments.length > 0\">\n <div class=\"appointment-card\" *ngFor=\"let appt of appointments\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"getOtherParticipant(appt)\" [size]=\"48\"></tas-avatar>\n <span class=\"doctor-name\">{{ getOtherParticipant(appt) }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formatAppointmentDate(appt) }}</span>\n <span class=\"time\">{{ formatTimeRange(appt) }}</span>\n </div>\n <tas-btn\n *ngIf=\"shouldShowTasBtn(appt)\"\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"appt.roomType\"\n [entityId]=\"appt.entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\n\n", styles: [":host{display:block}.incoming-appointment-card{background:#ffffff;border-radius:12px;box-shadow:0 2px 8px #00000014;padding:24px;min-width:360px}.card-title{font-size:16px;font-weight:400;color:#6b7280;margin:0 0 24px}.card-content{display:flex;flex-direction:column;align-items:center}.loading-spinner{width:40px;height:40px;border:3px solid #e0f7fa;border-top-color:#0097a7;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:20px 0}.icon-container{position:relative;width:120px;height:120px;margin-bottom:24px}.icon-circle{width:100%;height:100%;background:#e0f7fa;border-radius:50%;display:flex;align-items:center;justify-content:center}.icon-circle i{font-size:40px;color:#0097a7}.sparkle{position:absolute;color:#0097a7;font-size:12px}.sparkle-1{top:10px;right:5px}.sparkle-2{top:0;right:30px}.sparkle-3{top:25px;left:0}.sparkle-4{bottom:20px;right:0}.empty-title{font-size:18px;font-weight:600;color:#1f2937;margin:0 0 12px}.empty-subtitle{font-size:14px;color:#6b7280;margin:0;max-width:320px;line-height:1.5}.appointment-state{align-items:stretch;gap:16px}.appointment-card{border-radius:12px;border:1px solid var(--Primary-White-Uell50, #8ED1D8);background:#F1FAFA;padding:1.5rem}.appointment-header{display:flex;align-items:center;gap:12px;margin-bottom:16px}.doctor-name{overflow:hidden;color:var(--Neutral-GreyDark, #383E52);text-overflow:ellipsis;font-size:16px;font-style:normal;font-weight:600;line-height:24px;letter-spacing:.016px}.appointment-details{display:flex;justify-content:space-between;align-items:flex-end}.date-time{display:flex;flex-direction:column;gap:4px}.date{color:var(--Neutral-GreyDark, #383E52);font-size:12px;font-style:normal;font-weight:600;line-height:16px;letter-spacing:.06px}.time{font-size:14px;color:var(--Neutral-GreyDark, #383E52);font-family:Inter;font-size:10px;font-style:normal;font-weight:400;line-height:14px;letter-spacing:.04px}\n"] }]
2643
+ args: [{ selector: 'tas-incoming-appointment', template: "<div class=\"incoming-appointment-card\">\n <h3 class=\"card-title\">Pr\u00F3ximo turno</h3>\n\n <!-- Loading state -->\n <div class=\"card-content\" *ngIf=\"isLoading\">\n <div class=\"loading-spinner\"></div>\n </div>\n\n <!-- Empty state -->\n <div class=\"card-content empty-state\" *ngIf=\"!isLoading && appointments.length === 0\">\n <div class=\"icon-container\">\n <div class=\"icon-circle\">\n <i class=\"fa fa-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"sparkle sparkle-1\">\u2726</span>\n <span class=\"sparkle sparkle-2\">\u2726</span>\n <span class=\"sparkle sparkle-3\">\u2726</span>\n <span class=\"sparkle sparkle-4\">\u2726</span>\n </div>\n <h4 class=\"empty-title\">Todav\u00EDa no ten\u00E9s turnos agendados</h4>\n <p class=\"empty-subtitle\">\n En caso de que Medicina Laboral requiera una consulta, lo ver\u00E1s en esta secci\u00F3n.\n </p>\n </div>\n\n <!-- Appointments list -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointments.length > 0\">\n <div class=\"appointment-card\" *ngFor=\"let appt of appointments; trackBy: trackByAppointmentId\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"getOtherParticipant(appt)\" [size]=\"48\"></tas-avatar>\n <span class=\"doctor-name\">{{ getOtherParticipant(appt) }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formatAppointmentDate(appt) }}</span>\n <span class=\"time\">{{ formatTimeRange(appt) }}</span>\n </div>\n <tas-btn\n *ngIf=\"shouldShowTasBtn(appt)\"\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"appt.roomType\"\n [entityId]=\"appt.entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\n\n", styles: [":host{display:block}.incoming-appointment-card{background:#ffffff;border-radius:12px;box-shadow:0 2px 8px #00000014;padding:24px;min-width:360px}.card-title{font-size:16px;font-weight:400;color:#6b7280;margin:0 0 24px}.card-content{display:flex;flex-direction:column;align-items:center}.loading-spinner{width:40px;height:40px;border:3px solid #e0f7fa;border-top-color:#0097a7;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:20px 0}.icon-container{position:relative;width:120px;height:120px;margin-bottom:24px}.icon-circle{width:100%;height:100%;background:#e0f7fa;border-radius:50%;display:flex;align-items:center;justify-content:center}.icon-circle i{font-size:40px;color:#0097a7}.sparkle{position:absolute;color:#0097a7;font-size:12px}.sparkle-1{top:10px;right:5px}.sparkle-2{top:0;right:30px}.sparkle-3{top:25px;left:0}.sparkle-4{bottom:20px;right:0}.empty-title{font-size:18px;font-weight:600;color:#1f2937;margin:0 0 12px}.empty-subtitle{font-size:14px;color:#6b7280;margin:0;max-width:320px;line-height:1.5}.appointment-state{align-items:stretch;gap:16px}.appointment-card{border-radius:12px;border:1px solid var(--Primary-White-Uell50, #8ED1D8);background:#F1FAFA;padding:1.5rem}.appointment-header{display:flex;align-items:center;gap:12px;margin-bottom:16px}.doctor-name{overflow:hidden;color:var(--Neutral-GreyDark, #383E52);text-overflow:ellipsis;font-size:16px;font-style:normal;font-weight:600;line-height:24px;letter-spacing:.016px}.appointment-details{display:flex;justify-content:space-between;align-items:flex-end}.date-time{display:flex;flex-direction:column;gap:4px}.date{color:var(--Neutral-GreyDark, #383E52);font-size:12px;font-style:normal;font-weight:600;line-height:16px;letter-spacing:.06px}.time{font-size:14px;color:var(--Neutral-GreyDark, #383E52);font-family:Inter;font-size:10px;font-style:normal;font-weight:400;line-height:14px;letter-spacing:.04px}\n"] }]
2268
2644
  }], ctorParameters: function () { return [{ type: TasService }]; }, propDecorators: { roomType: [{
2269
2645
  type: Input
2270
2646
  }], entityId: [{
@@ -2330,12 +2706,14 @@ TasUellSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", versio
2330
2706
  TasFloatingCallComponent,
2331
2707
  TasWaitingRoomComponent,
2332
2708
  TasAvatarComponent,
2333
- TasIncomingAppointmentComponent], imports: [CommonModule, FormsModule, NgbTooltipModule], exports: [TasButtonComponent,
2709
+ TasIncomingAppointmentComponent,
2710
+ TasFeedbackModalComponent], imports: [CommonModule, FormsModule, NgbTooltipModule], exports: [TasButtonComponent,
2334
2711
  TasVideocallComponent,
2335
2712
  TasFloatingCallComponent,
2336
2713
  TasWaitingRoomComponent,
2337
2714
  TasAvatarComponent,
2338
- TasIncomingAppointmentComponent] });
2715
+ TasIncomingAppointmentComponent,
2716
+ TasFeedbackModalComponent] });
2339
2717
  TasUellSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, imports: [[CommonModule, FormsModule, NgbTooltipModule]] });
2340
2718
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, decorators: [{
2341
2719
  type: NgModule,
@@ -2347,6 +2725,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
2347
2725
  TasWaitingRoomComponent,
2348
2726
  TasAvatarComponent,
2349
2727
  TasIncomingAppointmentComponent,
2728
+ TasFeedbackModalComponent,
2350
2729
  ],
2351
2730
  imports: [CommonModule, FormsModule, NgbTooltipModule],
2352
2731
  exports: [
@@ -2356,6 +2735,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
2356
2735
  TasWaitingRoomComponent,
2357
2736
  TasAvatarComponent,
2358
2737
  TasIncomingAppointmentComponent,
2738
+ TasFeedbackModalComponent,
2359
2739
  ],
2360
2740
  }]
2361
2741
  }] });
@@ -2368,5 +2748,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
2368
2748
  * Generated bundle index. Do not edit.
2369
2749
  */
2370
2750
 
2371
- export { AppointmentStatus, CallState, GeoStatus, GeolocationService, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasIncomingAppointmentComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasUtilityService, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserGeoViewState, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
2751
+ export { AppointmentStatus, CallState, FeedbackMotiveType, GeoStatus, GeolocationService, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFeedbackModalComponent, TasFloatingCallComponent, TasIncomingAppointmentComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasUtilityService, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserGeoViewState, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
2372
2752
  //# sourceMappingURL=tas-uell-sdk.mjs.map