tas-uell-sdk 0.0.6 → 0.1.1

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 (28) hide show
  1. package/README.md +74 -3
  2. package/esm2020/lib/components/tas-btn/tas-btn.component.mjs +55 -61
  3. package/esm2020/lib/components/tas-incoming-appointment/tas-incoming-appointment.component.mjs +109 -0
  4. package/esm2020/lib/components/tas-videocall/tas-videocall.component.mjs +114 -8
  5. package/esm2020/lib/components/tas-waiting-room/tas-waiting-room.component.mjs +104 -46
  6. package/esm2020/lib/config/tas.config.mjs +1 -1
  7. package/esm2020/lib/interfaces/tas.interfaces.mjs +8 -1
  8. package/esm2020/lib/services/geolocation.service.mjs +56 -0
  9. package/esm2020/lib/services/tas-utility.service.mjs +60 -0
  10. package/esm2020/lib/services/tas.service.mjs +49 -12
  11. package/esm2020/lib/tas-uell-sdk.module.mjs +11 -5
  12. package/esm2020/public-api.mjs +4 -1
  13. package/fesm2015/tas-uell-sdk.mjs +553 -126
  14. package/fesm2015/tas-uell-sdk.mjs.map +1 -1
  15. package/fesm2020/tas-uell-sdk.mjs +540 -124
  16. package/fesm2020/tas-uell-sdk.mjs.map +1 -1
  17. package/lib/components/tas-btn/tas-btn.component.d.ts +10 -5
  18. package/lib/components/tas-incoming-appointment/tas-incoming-appointment.component.d.ts +33 -0
  19. package/lib/components/tas-videocall/tas-videocall.component.d.ts +27 -2
  20. package/lib/components/tas-waiting-room/tas-waiting-room.component.d.ts +29 -8
  21. package/lib/config/tas.config.d.ts +3 -0
  22. package/lib/interfaces/tas.interfaces.d.ts +30 -2
  23. package/lib/services/geolocation.service.d.ts +24 -0
  24. package/lib/services/tas-utility.service.d.ts +26 -0
  25. package/lib/services/tas.service.d.ts +16 -6
  26. package/lib/tas-uell-sdk.module.d.ts +5 -3
  27. package/package.json +1 -1
  28. package/public-api.d.ts +3 -0
@@ -1,11 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, Inject, Component, ChangeDetectionStrategy, Input, ViewChild, NgModule } from '@angular/core';
2
+ import { InjectionToken, Injectable, Inject, Component, ChangeDetectionStrategy, Input, ViewChild, EventEmitter, Output, NgModule } from '@angular/core';
3
3
  import { BehaviorSubject, Subscription } from 'rxjs';
4
4
  import { map, catchError } from 'rxjs/operators';
5
5
  import * as OT from '@opentok/client';
6
+ import { __awaiter } from 'tslib';
6
7
  import interact from 'interactjs';
7
8
  import * as i1 from '@ng-bootstrap/ng-bootstrap';
8
- import * as i3 from '@angular/common';
9
+ import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
10
+ import * as i4 from '@angular/common';
9
11
  import { CommonModule } from '@angular/common';
10
12
  import { FormsModule } from '@angular/forms';
11
13
 
@@ -86,11 +88,78 @@ var ViewMode;
86
88
  ViewMode["FULLSCREEN"] = "FULLSCREEN";
87
89
  ViewMode["PIP"] = "PIP";
88
90
  })(ViewMode || (ViewMode = {}));
91
+ // Appointment types
92
+ var AppointmentStatus;
93
+ (function (AppointmentStatus) {
94
+ AppointmentStatus["CONFIRMED"] = "CONFIRMED";
95
+ AppointmentStatus["CANCELLED"] = "CANCELLED";
96
+ AppointmentStatus["RESCHEDULED"] = "RESCHEDULED";
97
+ })(AppointmentStatus || (AppointmentStatus = {}));
98
+
99
+ class TasUtilityService {
100
+ /**
101
+ * Determines if the button should be shown based on the error message.
102
+ * Returns false if the error message matches any of the patterns that indicate
103
+ * the button should be hidden (but polling should continue).
104
+ *
105
+ * @param errorMessage The error message from the status call
106
+ * @returns true if button should be shown, false if it should be hidden
107
+ */
108
+ shouldShowButton(errorMessage) {
109
+ if (!errorMessage) {
110
+ return true;
111
+ }
112
+ const message = errorMessage.trim();
113
+ // List of error messages that should hide the button
114
+ const hideButtonMessages = [
115
+ 'Debe enviarse sessionId o roomType + entityId',
116
+ 'No existe videollamada con ese sessionId',
117
+ 'Usuario no pertenece a la llamada',
118
+ ];
119
+ // Check exact matches
120
+ if (hideButtonMessages.some((msg) => message === msg)) {
121
+ return false;
122
+ }
123
+ // Check pattern match for "No existe videocall vigente para roomType=x y entityId=x"
124
+ // Using startsWith as primary check to handle any variations
125
+ if (message.startsWith('No existe videocall vigente para roomType=')) {
126
+ return false;
127
+ }
128
+ // Also check regex pattern as backup
129
+ const vigentePattern = /^No existe videocall vigente para roomType=.+ y entityId=\s*\d+$/;
130
+ if (vigentePattern.test(message)) {
131
+ return false;
132
+ }
133
+ return true;
134
+ }
135
+ /**
136
+ * Check if at least one OWNER has joined the call.
137
+ */
138
+ checkIfOwnerJoined(users) {
139
+ return users.some((u) => u.rol === 'OWNER' && u.status === 'JOINED');
140
+ }
141
+ /**
142
+ * Extract error message from various error structures.
143
+ */
144
+ extractErrorMessage(err, fallback = 'Error desconocido') {
145
+ var _a;
146
+ return ((_a = err === null || err === void 0 ? void 0 : err.error) === null || _a === void 0 ? void 0 : _a.message) || (err === null || err === void 0 ? void 0 : err.message) || fallback;
147
+ }
148
+ }
149
+ TasUtilityService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUtilityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
150
+ TasUtilityService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUtilityService, providedIn: 'root' });
151
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUtilityService, decorators: [{
152
+ type: Injectable,
153
+ args: [{
154
+ providedIn: 'root',
155
+ }]
156
+ }] });
89
157
 
90
158
  class TasService {
91
- constructor(httpClient, config) {
159
+ constructor(httpClient, config, tasUtilityService) {
92
160
  this.httpClient = httpClient;
93
161
  this.config = config;
162
+ this.tasUtilityService = tasUtilityService;
94
163
  this.session = null;
95
164
  this.publisher = null;
96
165
  this.subscribers = [];
@@ -120,6 +189,15 @@ class TasService {
120
189
  this.ownerHasLeft$ = this.ownerHasLeftSubject.asObservable();
121
190
  this.joinableSubject = new BehaviorSubject(false);
122
191
  this.joinable$ = this.joinableSubject.asObservable();
192
+ // Observable for when backend requests geolocation activation
193
+ this.activateGeoSubject = new BehaviorSubject(false);
194
+ this.activateGeo$ = this.activateGeoSubject.asObservable();
195
+ // Observable for when geo request is active (owner waiting for user response)
196
+ this.geoRequestActiveSubject = new BehaviorSubject(false);
197
+ this.geoRequestActive$ = this.geoRequestActiveSubject.asObservable();
198
+ // Observable for when all geo has been granted
199
+ this.allGeoGrantedSubject = new BehaviorSubject(false);
200
+ this.allGeoGranted$ = this.allGeoGrantedSubject.asObservable();
123
201
  this.statusPollingInterval = null;
124
202
  this.DEFAULT_POLL_INTERVAL_MS = 30000; // Default 30s
125
203
  this.wasOwnerPresent = false;
@@ -412,6 +490,23 @@ class TasService {
412
490
  throw error;
413
491
  }));
414
492
  }
493
+ /**
494
+ * Get user appointments within a date range.
495
+ * @param params Date range for querying appointments
496
+ * @returns Observable of appointment array
497
+ */
498
+ getAppointments(params) {
499
+ let url = `v2/proxy/appointment/agendas/user/appointments?fromDate=${params.fromDate}&toDate=${params.toDate}&roomType=TAS`;
500
+ if (params.entityId !== undefined) {
501
+ url += `&entityId=${params.entityId}`;
502
+ }
503
+ return this.httpClient
504
+ .get(url, { headers: {} })
505
+ .pipe(catchError((error) => {
506
+ console.error('TAS Service: getAppointments failed', error);
507
+ throw error;
508
+ }));
509
+ }
415
510
  /**
416
511
  * Start automatic status polling for the current session.
417
512
  * Status is polled every STATUS_POLL_INTERVAL_MS milliseconds.
@@ -448,8 +543,23 @@ class TasService {
448
543
  }
449
544
  // Update joinable status
450
545
  this.joinableSubject.next(content.joinable);
546
+ // Update activateGeo status
547
+ if (content.activateGeo !== undefined) {
548
+ console.log('[TAS DEBUG] activateGeo received:', content.activateGeo);
549
+ this.activateGeoSubject.next(content.activateGeo);
550
+ }
551
+ // Update geoRequestActive status (owner waiting for user geo response)
552
+ if (content.geoRequestActive !== undefined) {
553
+ console.log('[TAS DEBUG] geoRequestActive received:', content.geoRequestActive);
554
+ this.geoRequestActiveSubject.next(content.geoRequestActive);
555
+ }
556
+ // Update allGeoGranted status (all users responded with geo)
557
+ if (content.allGeoGranted !== undefined) {
558
+ console.log('[TAS DEBUG] allGeoGranted received:', content.allGeoGranted);
559
+ this.allGeoGrantedSubject.next(content.allGeoGranted);
560
+ }
451
561
  // Check if owner has joined
452
- const ownerJoined = this.checkIfOwnerJoined(content.users);
562
+ const ownerJoined = this.tasUtilityService.checkIfOwnerJoined(content.users);
453
563
  this.ownerHasJoinedSubject.next(ownerJoined);
454
564
  // Detect if owner left: was present, now not present
455
565
  if (this.wasOwnerPresent && !ownerJoined) {
@@ -469,12 +579,6 @@ class TasService {
469
579
  }));
470
580
  this.waitingRoomUsersSubject.next(waitingUsers);
471
581
  }
472
- /**
473
- * Check if at least one OWNER has joined the call.
474
- */
475
- checkIfOwnerJoined(users) {
476
- return users.some((u) => u.rol === TasUserRole.OWNER && u.status === 'JOINED');
477
- }
478
582
  /**
479
583
  * Admit a user from the waiting room by changing their status.
480
584
  */
@@ -640,7 +744,7 @@ class TasService {
640
744
  this.movePublisherTo('publisher-container');
641
745
  }
642
746
  }
643
- TasService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, deps: [{ token: TAS_HTTP_CLIENT }, { token: TAS_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
747
+ TasService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, deps: [{ token: TAS_HTTP_CLIENT }, { token: TAS_CONFIG }, { token: TasUtilityService }], target: i0.ɵɵFactoryTarget.Injectable });
644
748
  TasService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, providedIn: 'root' });
645
749
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, decorators: [{
646
750
  type: Injectable,
@@ -654,9 +758,65 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
654
758
  }] }, { type: undefined, decorators: [{
655
759
  type: Inject,
656
760
  args: [TAS_CONFIG]
657
- }] }];
761
+ }] }, { type: TasUtilityService }];
658
762
  } });
659
763
 
764
+ class GeolocationService {
765
+ constructor() {
766
+ this.cachedPosition = null;
767
+ }
768
+ /**
769
+ * Request current geolocation position using Web Geolocation API.
770
+ * Works in both browser and Capacitor environments.
771
+ * @returns Promise with {latitude, longitude} or null if denied/unavailable
772
+ */
773
+ getCurrentPosition() {
774
+ return __awaiter(this, void 0, void 0, function* () {
775
+ if (!navigator.geolocation) {
776
+ console.warn('[GeolocationService] Geolocation not supported');
777
+ return null;
778
+ }
779
+ return new Promise((resolve) => {
780
+ navigator.geolocation.getCurrentPosition((position) => {
781
+ const geoPosition = {
782
+ latitude: position.coords.latitude,
783
+ longitude: position.coords.longitude,
784
+ };
785
+ this.cachedPosition = geoPosition;
786
+ resolve(geoPosition);
787
+ }, (error) => {
788
+ console.warn('[GeolocationService] Geolocation error:', error.message);
789
+ resolve(null);
790
+ }, {
791
+ enableHighAccuracy: false,
792
+ timeout: 30000,
793
+ maximumAge: 60000,
794
+ });
795
+ });
796
+ });
797
+ }
798
+ /**
799
+ * Get the cached position from the last successful request.
800
+ */
801
+ getCachedPosition() {
802
+ return this.cachedPosition;
803
+ }
804
+ /**
805
+ * Clear the cached position.
806
+ */
807
+ clearCache() {
808
+ this.cachedPosition = null;
809
+ }
810
+ }
811
+ GeolocationService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
812
+ GeolocationService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, providedIn: 'root' });
813
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, decorators: [{
814
+ type: Injectable,
815
+ args: [{
816
+ providedIn: 'root',
817
+ }]
818
+ }] });
819
+
660
820
  class TasAvatarComponent {
661
821
  constructor() {
662
822
  this.name = '';
@@ -731,9 +891,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
731
891
  }] } });
732
892
 
733
893
  class TasVideocallComponent {
734
- constructor(activeModal, tasService) {
894
+ constructor(activeModal, tasService, geolocationService) {
735
895
  this.activeModal = activeModal;
736
896
  this.tasService = tasService;
897
+ this.geolocationService = geolocationService;
737
898
  this.participantName = '';
738
899
  this.tenant = '';
739
900
  this.businessRole = TasBusinessRole.USER;
@@ -745,6 +906,12 @@ class TasVideocallComponent {
745
906
  this.ownerHasJoined = false;
746
907
  this.hasVideoStream = false;
747
908
  this.dismissedUsers = [];
909
+ this.showLocationPanel = true; // Show by default for owners, will be updated based on user's location status
910
+ this.userHasLocation = false; // Tracks if the user has shared their location
911
+ this.geoLocationStatus = 'unknown';
912
+ // Geo panel states for owner
913
+ this.geoRequestActive = false; // Owner sent request, waiting for user
914
+ this.allGeoGranted = false; // All users responded with geo
748
915
  this.subscriptions = new Subscription();
749
916
  }
750
917
  ngOnInit() {
@@ -822,6 +989,30 @@ class TasVideocallComponent {
822
989
  this.dismissedUsers.push(userId);
823
990
  this.waitingRoomUsers = this.waitingRoomUsers.filter((u) => u.userId !== userId);
824
991
  }
992
+ /**
993
+ * Close the location panel
994
+ */
995
+ closeLocationPanel() {
996
+ this.showLocationPanel = false;
997
+ }
998
+ /**
999
+ * Request the user to share their location
1000
+ */
1001
+ requestUserLocation() {
1002
+ if (!this.videoCallId) {
1003
+ console.error('Cannot request location: videoCallId not set');
1004
+ return;
1005
+ }
1006
+ const body = {
1007
+ videoCallId: this.videoCallId,
1008
+ action: UserCallAction.REQUEST_GEOLOCALIZATION,
1009
+ };
1010
+ // TODO: Send location request action to backend when endpoint is ready
1011
+ this.tasService.modifyProxyVideoUser(body).subscribe({
1012
+ next: () => console.log('Location request sent'),
1013
+ error: (err) => console.error('Error requesting location:', err),
1014
+ });
1015
+ }
825
1016
  // Private Methods
826
1017
  setupSubscriptions() {
827
1018
  // Call state subscription
@@ -860,6 +1051,80 @@ class TasVideocallComponent {
860
1051
  this.activeModal.close('owner_left');
861
1052
  }
862
1053
  }));
1054
+ // ActivateGeo subscription - only for non-owners (users)
1055
+ this.subscriptions.add(this.tasService.activateGeo$.subscribe((activateGeo) => {
1056
+ if (activateGeo && !this.canAdmitUsers) {
1057
+ console.log('[TAS DEBUG] activateGeo=true, checking geo status for user...');
1058
+ this.handleActivateGeo();
1059
+ }
1060
+ }));
1061
+ // GeoRequestActive subscription - for owners
1062
+ this.subscriptions.add(this.tasService.geoRequestActive$.subscribe((active) => {
1063
+ console.log('[TAS DEBUG] geoRequestActive changed:', active);
1064
+ this.geoRequestActive = active;
1065
+ }));
1066
+ // AllGeoGranted subscription - for owners
1067
+ this.subscriptions.add(this.tasService.allGeoGranted$.subscribe((granted) => {
1068
+ console.log('[TAS DEBUG] allGeoGranted changed:', granted);
1069
+ this.allGeoGranted = granted;
1070
+ }));
1071
+ }
1072
+ /**
1073
+ * Handle activateGeo request from backend (for non-owner users).
1074
+ * If geo is already active, report it. If not, prompt user.
1075
+ */
1076
+ handleActivateGeo() {
1077
+ return __awaiter(this, void 0, void 0, function* () {
1078
+ console.log('[TAS DEBUG] handleActivateGeo - current status:', this.geoLocationStatus);
1079
+ // Check if we already have a cached position
1080
+ const cachedPosition = this.geolocationService.getCachedPosition();
1081
+ if (cachedPosition) {
1082
+ console.log('[TAS DEBUG] Geolocation already active, reporting to backend:', cachedPosition);
1083
+ this.geoLocationStatus = 'active';
1084
+ this.reportGeoStatus(cachedPosition.latitude, cachedPosition.longitude);
1085
+ return;
1086
+ }
1087
+ // Try to get position
1088
+ console.log('[TAS DEBUG] Requesting geolocation from user...');
1089
+ const position = yield this.geolocationService.getCurrentPosition();
1090
+ if (position) {
1091
+ console.log('[TAS DEBUG] Geolocation obtained:', position);
1092
+ this.geoLocationStatus = 'active';
1093
+ this.reportGeoStatus(position.latitude, position.longitude);
1094
+ }
1095
+ else {
1096
+ console.log('[TAS DEBUG] Geolocation denied or unavailable');
1097
+ this.geoLocationStatus = 'denied';
1098
+ // Report that geo is not available (with no coordinates)
1099
+ this.reportGeoStatus();
1100
+ }
1101
+ });
1102
+ }
1103
+ /**
1104
+ * Report geolocation status to backend.
1105
+ */
1106
+ reportGeoStatus(latitude, longitude) {
1107
+ if (!this.videoCallId) {
1108
+ console.error('[TAS DEBUG] Cannot report geo status: videoCallId not set');
1109
+ return;
1110
+ }
1111
+ // TODO: If the permission for geo location is not granted, we should not report it to the backend
1112
+ if (!this.geoLocationStatus) {
1113
+ console.error('[TAS DEBUG] Cannot report geo status: geoLocationStatus not set');
1114
+ return;
1115
+ }
1116
+ const body = {
1117
+ userId: this.userId,
1118
+ videoCallId: this.videoCallId,
1119
+ action: UserCallAction.ACTIVATE_GEOLOCATION,
1120
+ latitude,
1121
+ longitude,
1122
+ };
1123
+ console.log('[TAS DEBUG] Reporting geo status to backend:', body);
1124
+ this.tasService.modifyProxyVideoUser(body).subscribe({
1125
+ next: () => console.log('[TAS DEBUG] Geo status reported successfully'),
1126
+ error: (err) => console.error('[TAS DEBUG] Error reporting geo status:', err),
1127
+ });
863
1128
  }
864
1129
  initializeCall() {
865
1130
  if (this.isReturningFromPip) {
@@ -949,12 +1214,12 @@ class TasVideocallComponent {
949
1214
  });
950
1215
  }
951
1216
  }
952
- TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }], target: i0.ɵɵFactoryTarget.Component });
953
- 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", 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-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-video-camera\"></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 <button\n class=\"btn control-btn more-btn\"\n title=\"M\u00E1s opciones\"\n aria-label=\"M\u00E1s opciones\"\n >\n <i class=\"fa fa-ellipsis-v\"></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", styles: [".tas-videocall-container{position:relative;width:100vw;height:100vh;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:var(--Primary-Uell, #1da4b1);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 .control-btn.muted{background:#f39c12}.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{background:var(--Primary-Uell, #1da4b1)}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover{background:#178e99}.tas-videocall-container .controls-container .more-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .more-btn:hover{background:rgba(255,255,255,.35)}.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);box-shadow:0 4px 12px #0003}.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}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1217
+ TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: GeolocationService }], target: i0.ɵɵFactoryTarget.Component });
1218
+ 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-video-camera\"></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 <button\n class=\"btn control-btn more-btn\"\n title=\"M\u00E1s opciones\"\n aria-label=\"M\u00E1s opciones\"\n >\n <i class=\"fa fa-ellipsis-v\"></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 <!-- Location panel (shown for owners when user hasn't allowed location) -->\n <div class=\"location-panel\" *ngIf=\"showLocationPanel && canAdmitUsers\">\n <div class=\"location-header\">\n <i class=\"fa fa-map-marker header-icon\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n <button class=\"close-btn\" (click)=\"closeLocationPanel()\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n <div class=\"location-description\">\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 <div class=\"location-content\">\n <!-- Initial state: Show verify button -->\n <ng-container *ngIf=\"!geoRequestActive && !allGeoGranted\">\n <button class=\"verify-location-btn\" (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Loading state: Spinner while waiting for user response -->\n <ng-container *ngIf=\"geoRequestActive && !allGeoGranted\">\n <div class=\"geo-loading-container\">\n <div class=\"geo-spinner\"></div>\n <p class=\"loading-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"loading-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n <button class=\"verify-location-btn disabled\" disabled>\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Success state: Location verified -->\n <ng-container *ngIf=\"allGeoGranted\">\n <div class=\"geo-success-container\">\n <div class=\"success-icon\">\n <i class=\"fa fa-check\"></i>\n </div>\n <p class=\"success-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n </div>\n <div class=\"location-footer\">\n <span class=\"footer-icon\"><i class=\"fa fa-clock-o\"></i></span>\n <span class=\"footer-icon location-icon\"><i class=\"fa fa-map-marker\"></i></span>\n </div>\n </div>\n</div>\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:var(--Primary-Uell, #1da4b1);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 .control-btn.muted{background:#f39c12}.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{background:var(--Primary-Uell, #1da4b1)}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover{background:#178e99}.tas-videocall-container .controls-container .more-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .more-btn:hover{background:rgba(255,255,255,.35)}.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);box-shadow:0 4px 12px #0003}.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-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)}}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
954
1219
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, decorators: [{
955
1220
  type: Component,
956
- args: [{ selector: 'tas-videocall', template: "<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-video-camera\"></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 <button\n class=\"btn control-btn more-btn\"\n title=\"M\u00E1s opciones\"\n aria-label=\"M\u00E1s opciones\"\n >\n <i class=\"fa fa-ellipsis-v\"></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", styles: [".tas-videocall-container{position:relative;width:100vw;height:100vh;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:var(--Primary-Uell, #1da4b1);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 .control-btn.muted{background:#f39c12}.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{background:var(--Primary-Uell, #1da4b1)}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover{background:#178e99}.tas-videocall-container .controls-container .more-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .more-btn:hover{background:rgba(255,255,255,.35)}.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);box-shadow:0 4px 12px #0003}.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}\n"] }]
957
- }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }]; }, propDecorators: { sessionId: [{
1221
+ 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-video-camera\"></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 <button\n class=\"btn control-btn more-btn\"\n title=\"M\u00E1s opciones\"\n aria-label=\"M\u00E1s opciones\"\n >\n <i class=\"fa fa-ellipsis-v\"></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 <!-- Location panel (shown for owners when user hasn't allowed location) -->\n <div class=\"location-panel\" *ngIf=\"showLocationPanel && canAdmitUsers\">\n <div class=\"location-header\">\n <i class=\"fa fa-map-marker header-icon\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n <button class=\"close-btn\" (click)=\"closeLocationPanel()\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n <div class=\"location-description\">\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 <div class=\"location-content\">\n <!-- Initial state: Show verify button -->\n <ng-container *ngIf=\"!geoRequestActive && !allGeoGranted\">\n <button class=\"verify-location-btn\" (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Loading state: Spinner while waiting for user response -->\n <ng-container *ngIf=\"geoRequestActive && !allGeoGranted\">\n <div class=\"geo-loading-container\">\n <div class=\"geo-spinner\"></div>\n <p class=\"loading-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"loading-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n <button class=\"verify-location-btn disabled\" disabled>\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Success state: Location verified -->\n <ng-container *ngIf=\"allGeoGranted\">\n <div class=\"geo-success-container\">\n <div class=\"success-icon\">\n <i class=\"fa fa-check\"></i>\n </div>\n <p class=\"success-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n </div>\n <div class=\"location-footer\">\n <span class=\"footer-icon\"><i class=\"fa fa-clock-o\"></i></span>\n <span class=\"footer-icon location-icon\"><i class=\"fa fa-map-marker\"></i></span>\n </div>\n </div>\n</div>\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:var(--Primary-Uell, #1da4b1);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 .control-btn.muted{background:#f39c12}.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{background:var(--Primary-Uell, #1da4b1)}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover{background:#178e99}.tas-videocall-container .controls-container .more-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .more-btn:hover{background:rgba(255,255,255,.35)}.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);box-shadow:0 4px 12px #0003}.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-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)}}\n"] }]
1222
+ }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }]; }, propDecorators: { sessionId: [{
958
1223
  type: Input
959
1224
  }], token: [{
960
1225
  type: Input
@@ -962,6 +1227,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
962
1227
  type: Input
963
1228
  }], videoCallId: [{
964
1229
  type: Input
1230
+ }], userId: [{
1231
+ type: Input
965
1232
  }], participantName: [{
966
1233
  type: Input
967
1234
  }], tenant: [{
@@ -981,15 +1248,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
981
1248
  var WaitingRoomState;
982
1249
  (function (WaitingRoomState) {
983
1250
  WaitingRoomState["CHECKING_STATUS"] = "CHECKING_STATUS";
984
- WaitingRoomState["WAITING_FOR_OWNER"] = "WAITING_FOR_OWNER";
1251
+ WaitingRoomState["WAITING_FOR_JOINABLE"] = "WAITING_FOR_JOINABLE";
985
1252
  WaitingRoomState["READY"] = "READY";
986
1253
  WaitingRoomState["GETTING_TOKEN"] = "GETTING_TOKEN";
1254
+ WaitingRoomState["JOINING"] = "JOINING";
987
1255
  WaitingRoomState["ERROR"] = "ERROR";
988
1256
  })(WaitingRoomState || (WaitingRoomState = {}));
989
1257
  class TasWaitingRoomComponent {
990
- constructor(activeModal, tasService, modalService, cdr) {
1258
+ constructor(activeModal, tasService, geolocationService, modalService, cdr) {
991
1259
  this.activeModal = activeModal;
992
1260
  this.tasService = tasService;
1261
+ this.geolocationService = geolocationService;
993
1262
  this.modalService = modalService;
994
1263
  this.cdr = cdr;
995
1264
  // Status endpoint params
@@ -999,6 +1268,7 @@ class TasWaitingRoomComponent {
999
1268
  this.state = WaitingRoomState.CHECKING_STATUS;
1000
1269
  this.WaitingRoomState = WaitingRoomState; // Expose enum to template
1001
1270
  this.errorMessage = '';
1271
+ this.isJoinable = false;
1002
1272
  // Session data from status response
1003
1273
  this.resolvedSessionId = '';
1004
1274
  this.resolvedAppointmentId = null;
@@ -1007,6 +1277,8 @@ class TasWaitingRoomComponent {
1007
1277
  // Subscriptions
1008
1278
  this.subscriptions = new Subscription();
1009
1279
  this.videoCallModalRef = null;
1280
+ // Geolocation
1281
+ this.geoPosition = null;
1010
1282
  }
1011
1283
  /** Whether current user is an owner */
1012
1284
  get isOwner() {
@@ -1015,8 +1287,74 @@ class TasWaitingRoomComponent {
1015
1287
  }
1016
1288
  ngOnInit() {
1017
1289
  console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnInit');
1290
+ this.requestMediaPermissions();
1291
+ this.requestGeolocation();
1018
1292
  this.checkStatus();
1019
1293
  }
1294
+ /**
1295
+ * Request camera and microphone permissions.
1296
+ */
1297
+ requestMediaPermissions() {
1298
+ return __awaiter(this, void 0, void 0, function* () {
1299
+ console.log('[TAS DEBUG] Requesting media permissions...');
1300
+ try {
1301
+ const stream = yield navigator.mediaDevices.getUserMedia({ audio: true, video: true });
1302
+ // Stop tracks immediately - we just needed the permission
1303
+ stream.getTracks().forEach(track => track.stop());
1304
+ console.log('[TAS DEBUG] Media permissions granted');
1305
+ }
1306
+ catch (error) {
1307
+ console.warn('[TAS DEBUG] Media permissions denied or unavailable:', error);
1308
+ }
1309
+ });
1310
+ }
1311
+ /**
1312
+ * Request geolocation immediately on init.
1313
+ * Only for regular users (not owners/backoffice).
1314
+ * If user allows, store position and send to backend.
1315
+ */
1316
+ requestGeolocation() {
1317
+ return __awaiter(this, void 0, void 0, function* () {
1318
+ // Only request geolocation for regular users, not owners/backoffice
1319
+ if (this.isOwner || this.isBackoffice) {
1320
+ console.log('[TAS DEBUG] Skipping geolocation for owner/backoffice');
1321
+ return;
1322
+ }
1323
+ console.log('[TAS DEBUG] Requesting geolocation...');
1324
+ const position = yield this.geolocationService.getCurrentPosition();
1325
+ if (position) {
1326
+ console.log('[TAS DEBUG] Geolocation obtained:', position);
1327
+ this.geoPosition = position;
1328
+ // Send to backend when videoCallId is available
1329
+ this.sendGeolocationToBackend();
1330
+ }
1331
+ else {
1332
+ console.log('[TAS DEBUG] Geolocation denied or unavailable');
1333
+ }
1334
+ });
1335
+ }
1336
+ /**
1337
+ * Send geolocation to backend via modify user endpoint.
1338
+ * NOTE: Endpoint call is prepared but may not be active yet.
1339
+ */
1340
+ sendGeolocationToBackend() {
1341
+ var _a;
1342
+ if (!this.geoPosition || !this.videoCallId) {
1343
+ return;
1344
+ }
1345
+ console.log('[TAS DEBUG] Sending geolocation to backend...');
1346
+ const body = {
1347
+ userId: (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.id,
1348
+ videoCallId: this.videoCallId,
1349
+ action: UserCallAction.ACTIVATE_GEOLOCATION,
1350
+ latitude: this.geoPosition.latitude,
1351
+ longitude: this.geoPosition.longitude,
1352
+ };
1353
+ this.tasService.modifyProxyVideoUser(body).subscribe({
1354
+ next: () => console.log('[TAS DEBUG] Geolocation sent successfully'),
1355
+ error: (err) => console.error('[TAS DEBUG] Failed to send geolocation:', err),
1356
+ });
1357
+ }
1020
1358
  ngOnDestroy() {
1021
1359
  console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnDestroy');
1022
1360
  this.subscriptions.unsubscribe();
@@ -1050,6 +1388,8 @@ class TasWaitingRoomComponent {
1050
1388
  this.resolvedAppointmentId = content.appointmentId;
1051
1389
  this.videoCallId = content.videoCallId;
1052
1390
  console.log('[TAS DEBUG] Status response:', content);
1391
+ // Try to send geolocation now that we have videoCallId
1392
+ this.sendGeolocationToBackend();
1053
1393
  // Start polling for status updates
1054
1394
  this.tasService.startStatusPolling(statusParams);
1055
1395
  // Subscribe to joinable status
@@ -1071,36 +1411,33 @@ class TasWaitingRoomComponent {
1071
1411
  console.log('[TAS DEBUG] handleJoinableChange called', {
1072
1412
  joinable,
1073
1413
  currentState: this.state,
1074
- isOwner: this.isOwner,
1075
- isBackoffice: this.isBackoffice,
1076
1414
  resolvedSessionId: this.resolvedSessionId,
1077
1415
  });
1078
- // Don't update state if already getting token, ready, or in error
1416
+ this.isJoinable = joinable;
1417
+ // Don't update state if already getting token, joining, or in error
1079
1418
  if (this.state === WaitingRoomState.GETTING_TOKEN ||
1080
- this.state === WaitingRoomState.READY ||
1419
+ this.state === WaitingRoomState.JOINING ||
1081
1420
  this.state === WaitingRoomState.ERROR) {
1082
1421
  console.log('[TAS DEBUG] Skipping state update - already in:', this.state);
1083
1422
  return;
1084
1423
  }
1085
- if (this.isOwner || this.isBackoffice) {
1086
- console.log('[TAS DEBUG] User is owner/backoffice, calling getTokenForOwner');
1087
- // Owner/Backoffice: call /start to get token first, then show join button
1088
- if (this.state === WaitingRoomState.CHECKING_STATUS) {
1089
- this.getTokenForOwner();
1090
- }
1424
+ // Both users and owners: show join button based on joinable
1425
+ if (joinable) {
1426
+ console.log('[TAS DEBUG] Joinable is true, showing join button');
1427
+ this.state = WaitingRoomState.READY;
1091
1428
  }
1092
1429
  else {
1093
- // Non-owner: wait until joinable is true
1094
- if (joinable) {
1095
- console.log('[TAS DEBUG] Non-owner: joinable is true, getting token');
1096
- this.getTokenForOwner(); // Also get token when joinable
1097
- }
1098
- else {
1099
- console.log('[TAS DEBUG] Non-owner: waiting for owner');
1100
- this.state = WaitingRoomState.WAITING_FOR_OWNER;
1101
- this.cdr.detectChanges();
1102
- }
1430
+ console.log('[TAS DEBUG] Waiting for joinable...');
1431
+ this.state = WaitingRoomState.WAITING_FOR_JOINABLE;
1103
1432
  }
1433
+ this.cdr.detectChanges();
1434
+ }
1435
+ /**
1436
+ * Called when user clicks the join button.
1437
+ * Calls /start to get token then auto-joins.
1438
+ */
1439
+ joinSession() {
1440
+ this.startSessionAndJoin();
1104
1441
  }
1105
1442
  /**
1106
1443
  * Check if user has owner/backoffice role
@@ -1111,9 +1448,9 @@ class TasWaitingRoomComponent {
1111
1448
  this.businessRole === TasBusinessRole.MANAGER);
1112
1449
  }
1113
1450
  /**
1114
- * Get token for owner/backoffice - call /start endpoint
1451
+ * Start session and join - called when user clicks join button
1115
1452
  */
1116
- getTokenForOwner() {
1453
+ startSessionAndJoin() {
1117
1454
  if (!this.resolvedSessionId) {
1118
1455
  this.state = WaitingRoomState.ERROR;
1119
1456
  this.errorMessage = 'Session ID not available';
@@ -1143,10 +1480,14 @@ class TasWaitingRoomComponent {
1143
1480
  this.cdr.detectChanges();
1144
1481
  return;
1145
1482
  }
1146
- console.log('[TAS DEBUG] Token obtained successfully');
1483
+ console.log('[TAS DEBUG] Token obtained successfully, joining session...');
1147
1484
  this.token = tokenResponse.content.token;
1148
- this.state = WaitingRoomState.READY;
1485
+ this.state = WaitingRoomState.JOINING;
1149
1486
  this.cdr.detectChanges();
1487
+ // Auto-join immediately
1488
+ this.tasService.stopStatusPolling();
1489
+ this.activeModal.close('joining');
1490
+ this.openVideoCallModal();
1150
1491
  },
1151
1492
  error: (err) => {
1152
1493
  var _a;
@@ -1159,19 +1500,6 @@ class TasWaitingRoomComponent {
1159
1500
  },
1160
1501
  }));
1161
1502
  }
1162
- /**
1163
- * Join the session - token already obtained
1164
- */
1165
- joinSession() {
1166
- if (!this.resolvedSessionId || !this.token) {
1167
- this.errorMessage = 'Session not ready';
1168
- return;
1169
- }
1170
- // Close waiting room and open video call
1171
- this.tasService.stopStatusPolling();
1172
- this.activeModal.close('joining');
1173
- this.openVideoCallModal();
1174
- }
1175
1503
  /**
1176
1504
  * Closes the waiting room
1177
1505
  */
@@ -1189,6 +1517,7 @@ class TasWaitingRoomComponent {
1189
1517
  this.checkStatus();
1190
1518
  }
1191
1519
  openVideoCallModal() {
1520
+ var _a;
1192
1521
  this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
1193
1522
  size: 'xl',
1194
1523
  windowClass: 'tas-video-modal',
@@ -1199,6 +1528,7 @@ class TasWaitingRoomComponent {
1199
1528
  this.videoCallModalRef.componentInstance.token = this.token;
1200
1529
  this.videoCallModalRef.componentInstance.appointmentId = this.resolvedAppointmentId;
1201
1530
  this.videoCallModalRef.componentInstance.videoCallId = this.videoCallId;
1531
+ this.videoCallModalRef.componentInstance.userId = (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.id;
1202
1532
  this.videoCallModalRef.componentInstance.tenant = this.tenant;
1203
1533
  this.videoCallModalRef.componentInstance.businessRole = this.businessRole;
1204
1534
  this.videoCallModalRef.componentInstance.isReturningFromPip = false;
@@ -1209,12 +1539,12 @@ class TasWaitingRoomComponent {
1209
1539
  });
1210
1540
  }
1211
1541
  }
1212
- TasWaitingRoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: i1.NgbModal }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1213
- 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 <!-- Header -->\n <div class=\"waiting-room-header\">\n <h2 class=\"header-title\">Iniciar turno</h2>\n <button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"waiting-room-content\">\n <!-- CHECKING_STATUS State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CHECKING_STATUS\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Verificando estado de la sesi\u00F3n...</p>\n </div>\n\n <!-- WAITING_FOR_OWNER State (Non-owner waiting) -->\n <div class=\"state-container waiting-for-owner\" *ngIf=\"state === WaitingRoomState.WAITING_FOR_OWNER\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message waiting-title\">Medicina laboral te va a admitir en unos instantes...</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla hasta que inicie la consulta.</p>\n </div>\n\n <!-- READY State (Owner can join) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY\">\n <div class=\"state-icon ready\">\n <i class=\"fa fa-check-circle\"></i>\n </div>\n <p class=\"state-message success\">\u00A1La sala est\u00E1 lista!</p>\n <p class=\"state-submessage\">Pod\u00E9s unirte a la videollamada cuando quieras</p>\n <button type=\"button\" class=\"btn action-btn join-btn\" (click)=\"joinSession()\">\n <i class=\"fa fa-sign-in\"></i>\n Unirse a la llamada\n </button>\n </div>\n\n <!-- GETTING_TOKEN State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Conectando...</p>\n </div>\n\n <!-- ERROR State -->\n <div class=\"state-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\">\n {{ errorMessage }}\n </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 </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:20px 40px;border-bottom:1px solid #e9ecef;display:flex;align-items:center;justify-content:space-between}.waiting-room-header .header-title{margin:0;font-size:18px;font-weight:600;line-height:24px;color:#212529}.waiting-room-header .close-btn{width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px;display:flex;align-items:center;justify-content:center}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-title{font-size:16px;font-weight:600;color:#212529;margin-bottom:8px}.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%}.mode-tabs{display:flex;justify-content:center;gap:8px;margin-bottom:24px;padding:4px;background:#f8f9fa;border-radius:8px}.mode-tab{flex:1;padding:10px 16px;font-size:13px;font-weight:600;border:none;border-radius:6px;background:transparent;color:#6c757d;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:8px}.mode-tab i{font-size:14px}.mode-tab:hover{color:#212529}.mode-tab.active{background:#ffffff;color:#1da4b1;box-shadow:0 2px 8px #00000014}.mode-content{animation:fadeIn .3s ease}.session-input-container{display:flex;flex-direction:column;gap:16px;margin-top:8px}.session-input{width:100%;padding:14px 16px;font-size:14px;border:2px solid #e9ecef;border-radius:8px;background:#ffffff;color:#212529;transition:all .2s ease;text-align:center;font-family:Monaco,Consolas,monospace;letter-spacing:.5px}.session-input::placeholder{color:#6c757d;font-family:inherit;letter-spacing:normal}.session-input:focus{outline:none;border-color:#1da4b1;box-shadow:0 0 0 3px #1da4b126}.session-input:hover:not(:focus){border-color:#6c757d}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.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.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.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.success{color:#1da4b1}.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)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.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.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1542
+ 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 });
1543
+ 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 <!-- Header -->\n <div class=\"waiting-room-header\">\n <h2 class=\"header-title\">Iniciar turno</h2>\n <button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"waiting-room-content\">\n <!-- CHECKING_STATUS State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CHECKING_STATUS\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Verificando estado de la sesi\u00F3n...</p>\n </div>\n\n <!-- WAITING_FOR_JOINABLE State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.WAITING_FOR_JOINABLE\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Esperando que la sesi\u00F3n est\u00E9 disponible...</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </div>\n\n <!-- READY State (Join button enabled) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY\">\n <div class=\"state-icon ready\">\n <i class=\"fa fa-check-circle\"></i>\n </div>\n <p class=\"state-message success\">\u00A1La sala est\u00E1 lista!</p>\n <button type=\"button\" class=\"btn action-btn join-btn\" (click)=\"joinSession()\">\n <i class=\"fa fa-sign-in\"></i>\n Unirse a la llamada\n </button>\n </div>\n\n <!-- JOINING State (Auto-joining) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.JOINING\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Ingresando a la llamada...</p>\n </div>\n\n <!-- GETTING_TOKEN State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Conectando...</p>\n </div>\n\n <!-- ERROR State -->\n <div class=\"state-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\">\n {{ errorMessage }}\n </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 </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:20px 40px;border-bottom:1px solid #e9ecef;display:flex;align-items:center;justify-content:space-between}.waiting-room-header .header-title{margin:0;font-size:18px;font-weight:600;line-height:24px;color:#212529}.waiting-room-header .close-btn{width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px;display:flex;align-items:center;justify-content:center}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-title{font-size:16px;font-weight:600;color:#212529;margin-bottom:8px}.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%}.mode-tabs{display:flex;justify-content:center;gap:8px;margin-bottom:24px;padding:4px;background:#f8f9fa;border-radius:8px}.mode-tab{flex:1;padding:10px 16px;font-size:13px;font-weight:600;border:none;border-radius:6px;background:transparent;color:#6c757d;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:8px}.mode-tab i{font-size:14px}.mode-tab:hover{color:#212529}.mode-tab.active{background:#ffffff;color:#1da4b1;box-shadow:0 2px 8px #00000014}.mode-content{animation:fadeIn .3s ease}.session-input-container{display:flex;flex-direction:column;gap:16px;margin-top:8px}.session-input{width:100%;padding:14px 16px;font-size:14px;border:2px solid #e9ecef;border-radius:8px;background:#ffffff;color:#212529;transition:all .2s ease;text-align:center;font-family:Monaco,Consolas,monospace;letter-spacing:.5px}.session-input::placeholder{color:#6c757d;font-family:inherit;letter-spacing:normal}.session-input:focus{outline:none;border-color:#1da4b1;box-shadow:0 0 0 3px #1da4b126}.session-input:hover:not(:focus){border-color:#6c757d}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.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.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.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.success{color:#1da4b1}.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)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.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.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1214
1544
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, decorators: [{
1215
1545
  type: Component,
1216
- args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n <!-- Header -->\n <div class=\"waiting-room-header\">\n <h2 class=\"header-title\">Iniciar turno</h2>\n <button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"waiting-room-content\">\n <!-- CHECKING_STATUS State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CHECKING_STATUS\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Verificando estado de la sesi\u00F3n...</p>\n </div>\n\n <!-- WAITING_FOR_OWNER State (Non-owner waiting) -->\n <div class=\"state-container waiting-for-owner\" *ngIf=\"state === WaitingRoomState.WAITING_FOR_OWNER\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message waiting-title\">Medicina laboral te va a admitir en unos instantes...</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla hasta que inicie la consulta.</p>\n </div>\n\n <!-- READY State (Owner can join) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY\">\n <div class=\"state-icon ready\">\n <i class=\"fa fa-check-circle\"></i>\n </div>\n <p class=\"state-message success\">\u00A1La sala est\u00E1 lista!</p>\n <p class=\"state-submessage\">Pod\u00E9s unirte a la videollamada cuando quieras</p>\n <button type=\"button\" class=\"btn action-btn join-btn\" (click)=\"joinSession()\">\n <i class=\"fa fa-sign-in\"></i>\n Unirse a la llamada\n </button>\n </div>\n\n <!-- GETTING_TOKEN State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Conectando...</p>\n </div>\n\n <!-- ERROR State -->\n <div class=\"state-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\">\n {{ errorMessage }}\n </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 </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:20px 40px;border-bottom:1px solid #e9ecef;display:flex;align-items:center;justify-content:space-between}.waiting-room-header .header-title{margin:0;font-size:18px;font-weight:600;line-height:24px;color:#212529}.waiting-room-header .close-btn{width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px;display:flex;align-items:center;justify-content:center}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-title{font-size:16px;font-weight:600;color:#212529;margin-bottom:8px}.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%}.mode-tabs{display:flex;justify-content:center;gap:8px;margin-bottom:24px;padding:4px;background:#f8f9fa;border-radius:8px}.mode-tab{flex:1;padding:10px 16px;font-size:13px;font-weight:600;border:none;border-radius:6px;background:transparent;color:#6c757d;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:8px}.mode-tab i{font-size:14px}.mode-tab:hover{color:#212529}.mode-tab.active{background:#ffffff;color:#1da4b1;box-shadow:0 2px 8px #00000014}.mode-content{animation:fadeIn .3s ease}.session-input-container{display:flex;flex-direction:column;gap:16px;margin-top:8px}.session-input{width:100%;padding:14px 16px;font-size:14px;border:2px solid #e9ecef;border-radius:8px;background:#ffffff;color:#212529;transition:all .2s ease;text-align:center;font-family:Monaco,Consolas,monospace;letter-spacing:.5px}.session-input::placeholder{color:#6c757d;font-family:inherit;letter-spacing:normal}.session-input:focus{outline:none;border-color:#1da4b1;box-shadow:0 0 0 3px #1da4b126}.session-input:hover:not(:focus){border-color:#6c757d}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.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.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.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.success{color:#1da4b1}.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)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.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.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"] }]
1217
- }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: i1.NgbModal }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { roomType: [{
1546
+ args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n <!-- Header -->\n <div class=\"waiting-room-header\">\n <h2 class=\"header-title\">Iniciar turno</h2>\n <button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"waiting-room-content\">\n <!-- CHECKING_STATUS State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CHECKING_STATUS\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Verificando estado de la sesi\u00F3n...</p>\n </div>\n\n <!-- WAITING_FOR_JOINABLE State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.WAITING_FOR_JOINABLE\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Esperando que la sesi\u00F3n est\u00E9 disponible...</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </div>\n\n <!-- READY State (Join button enabled) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY\">\n <div class=\"state-icon ready\">\n <i class=\"fa fa-check-circle\"></i>\n </div>\n <p class=\"state-message success\">\u00A1La sala est\u00E1 lista!</p>\n <button type=\"button\" class=\"btn action-btn join-btn\" (click)=\"joinSession()\">\n <i class=\"fa fa-sign-in\"></i>\n Unirse a la llamada\n </button>\n </div>\n\n <!-- JOINING State (Auto-joining) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.JOINING\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Ingresando a la llamada...</p>\n </div>\n\n <!-- GETTING_TOKEN State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Conectando...</p>\n </div>\n\n <!-- ERROR State -->\n <div class=\"state-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\">\n {{ errorMessage }}\n </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 </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:20px 40px;border-bottom:1px solid #e9ecef;display:flex;align-items:center;justify-content:space-between}.waiting-room-header .header-title{margin:0;font-size:18px;font-weight:600;line-height:24px;color:#212529}.waiting-room-header .close-btn{width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px;display:flex;align-items:center;justify-content:center}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-title{font-size:16px;font-weight:600;color:#212529;margin-bottom:8px}.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%}.mode-tabs{display:flex;justify-content:center;gap:8px;margin-bottom:24px;padding:4px;background:#f8f9fa;border-radius:8px}.mode-tab{flex:1;padding:10px 16px;font-size:13px;font-weight:600;border:none;border-radius:6px;background:transparent;color:#6c757d;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:8px}.mode-tab i{font-size:14px}.mode-tab:hover{color:#212529}.mode-tab.active{background:#ffffff;color:#1da4b1;box-shadow:0 2px 8px #00000014}.mode-content{animation:fadeIn .3s ease}.session-input-container{display:flex;flex-direction:column;gap:16px;margin-top:8px}.session-input{width:100%;padding:14px 16px;font-size:14px;border:2px solid #e9ecef;border-radius:8px;background:#ffffff;color:#212529;transition:all .2s ease;text-align:center;font-family:Monaco,Consolas,monospace;letter-spacing:.5px}.session-input::placeholder{color:#6c757d;font-family:inherit;letter-spacing:normal}.session-input:focus{outline:none;border-color:#1da4b1;box-shadow:0 0 0 3px #1da4b126}.session-input:hover:not(:focus){border-color:#6c757d}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.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.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.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.success{color:#1da4b1}.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)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.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.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"] }]
1547
+ }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }, { type: i1.NgbModal }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { roomType: [{
1218
1548
  type: Input
1219
1549
  }], entityId: [{
1220
1550
  type: Input
@@ -1227,18 +1557,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1227
1557
  }] } });
1228
1558
 
1229
1559
  class TasButtonComponent {
1230
- constructor(modalService, tasService) {
1560
+ constructor(modalService, tasService, tasUtilityService) {
1231
1561
  this.modalService = modalService;
1232
1562
  this.tasService = tasService;
1563
+ this.tasUtilityService = tasUtilityService;
1233
1564
  // Status endpoint params
1234
1565
  this.roomType = TasRoomType.TAS;
1235
1566
  this.businessRole = TasBusinessRole.USER;
1567
+ // Style customization
1568
+ this.variant = 'default';
1569
+ this.buttonLabel = 'Iniciar TAS';
1236
1570
  this.isLoading = false;
1237
- this.buttonText = 'Iniciar TAS';
1238
1571
  // Status check state
1239
1572
  this.isCheckingStatus = false;
1240
1573
  this.isStatusError = false;
1241
1574
  this.statusErrorMessage = '';
1575
+ this.isJoinable = false; // Tracks joinable field from status response
1576
+ this.shouldShowButton = true; // Controls button visibility
1242
1577
  this.subscriptions = new Subscription();
1243
1578
  this.currentModalRef = null;
1244
1579
  this.videoCallModalRef = null;
@@ -1251,18 +1586,22 @@ class TasButtonComponent {
1251
1586
  this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
1252
1587
  this.businessRole === TasBusinessRole.MANAGER);
1253
1588
  }
1254
- /** Whether the button should be visible */
1255
- get isVisible() {
1256
- // Backoffice: always show (disabled if error)
1257
- // Other roles: hide if status error
1258
- if (this.isBackoffice) {
1259
- return true;
1260
- }
1261
- return !this.isStatusError;
1262
- }
1263
1589
  /** Whether the button should be disabled */
1264
1590
  get isDisabled() {
1265
- return this.isLoading || this.isCheckingStatus || this.isStatusError;
1591
+ return this.isLoading || this.isStatusError || !this.isJoinable;
1592
+ }
1593
+ /** Reason why the button is disabled (for tooltip) */
1594
+ get disabledReason() {
1595
+ if (this.isLoading) {
1596
+ return '';
1597
+ }
1598
+ if (this.isStatusError) {
1599
+ return this.statusErrorMessage || 'Error al verificar el estado';
1600
+ }
1601
+ if (!this.isJoinable) {
1602
+ return 'Todavía no es el horario de la llamada';
1603
+ }
1604
+ return '';
1266
1605
  }
1267
1606
  ngOnInit() {
1268
1607
  // Subscribe to viewMode to handle PiP return
@@ -1305,74 +1644,53 @@ class TasButtonComponent {
1305
1644
  checkStatus() {
1306
1645
  // Skip if required inputs are not available
1307
1646
  if (!this.tenant || !this.entityId) {
1308
- console.log('[TAS DEBUG] checkStatus skipped - missing required inputs');
1309
1647
  return;
1310
1648
  }
1311
1649
  this.isCheckingStatus = true;
1312
1650
  this.statusErrorMessage = '';
1313
- console.log('[TAS DEBUG] checkStatus called with:', {
1314
- roomType: this.roomType,
1315
- entityId: this.entityId,
1316
- tenant: this.tenant,
1317
- businessRole: this.businessRole,
1318
- });
1319
- this.subscriptions.add(this.tasService.getProxyVideoStatus({
1651
+ this.subscriptions.add(this.tasService
1652
+ .getProxyVideoStatus({
1320
1653
  roomType: this.roomType,
1321
1654
  entityId: this.entityId,
1322
1655
  tenant: this.tenant,
1323
1656
  businessRole: this.businessRole,
1324
- }).subscribe({
1657
+ })
1658
+ .subscribe({
1325
1659
  next: (response) => {
1326
- var _a;
1327
- // Check if response is actually an error (some HTTP adapters return errors in next)
1328
- // Also check for undefined/null or missing content
1329
- const isErrorResponse = !response ||
1330
- !response.content ||
1331
- (response === null || response === void 0 ? void 0 : response.ok) === false ||
1332
- (response === null || response === void 0 ? void 0 : response.status) >= 400 ||
1333
- (response === null || response === void 0 ? void 0 : response.error) ||
1334
- (response === null || response === void 0 ? void 0 : response.name) === 'HttpErrorResponse';
1335
- if (isErrorResponse) {
1336
- console.error('[TAS DEBUG] Status check returned error in response:', response);
1337
- this.isCheckingStatus = false;
1338
- this.isStatusError = true;
1339
- this.statusErrorMessage = ((_a = response === null || response === void 0 ? void 0 : response.error) === null || _a === void 0 ? void 0 : _a.message) || (response === null || response === void 0 ? void 0 : response.message) || 'Error checking status';
1340
- }
1341
- else {
1342
- console.log('[TAS DEBUG] Status check successful:', response);
1343
- this.isCheckingStatus = false;
1344
- this.isStatusError = false;
1345
- this.statusErrorMessage = '';
1346
- }
1660
+ var _a, _b;
1661
+ this.isCheckingStatus = false;
1662
+ this.isStatusError = false;
1663
+ this.statusErrorMessage = '';
1664
+ this.shouldShowButton = true;
1665
+ // Update joinable state from response
1666
+ this.isJoinable = (_b = (_a = response.content) === null || _a === void 0 ? void 0 : _a.joinable) !== null && _b !== void 0 ? _b : false;
1347
1667
  },
1348
1668
  error: (err) => {
1349
- var _a;
1350
- console.error('[TAS DEBUG] Status check failed:', err);
1351
1669
  this.isCheckingStatus = false;
1352
- this.isStatusError = true;
1353
- this.statusErrorMessage = ((_a = err === null || err === void 0 ? void 0 : err.error) === null || _a === void 0 ? void 0 : _a.message) || (err === null || err === void 0 ? void 0 : err.message) || 'Error checking status';
1670
+ const errorMessage = this.tasUtilityService.extractErrorMessage(err, 'Error checking status');
1671
+ this.statusErrorMessage = errorMessage;
1672
+ // Use utility service to determine if button should be shown
1673
+ this.shouldShowButton = this.tasUtilityService.shouldShowButton(errorMessage);
1674
+ // Stop polling on error
1675
+ this.stopStatusPolling();
1676
+ // If button should be hidden, don't treat as error
1677
+ if (!this.shouldShowButton) {
1678
+ this.isStatusError = false;
1679
+ }
1680
+ else {
1681
+ this.isStatusError = true;
1682
+ }
1354
1683
  },
1355
1684
  }));
1356
1685
  }
1357
1686
  onClick() {
1358
1687
  var _a;
1359
- console.log('[TAS DEBUG] onClick called');
1360
- console.log('[TAS DEBUG] Inputs:', {
1361
- tenant: this.tenant,
1362
- entityId: this.entityId,
1363
- roomType: this.roomType,
1364
- businessRole: this.businessRole,
1365
- currentUser: this.currentUser,
1366
- });
1367
1688
  if (!this.tenant || !((_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.name)) {
1368
- console.error('[TAS DEBUG] Tenant or current user not available');
1369
1689
  return;
1370
1690
  }
1371
1691
  if (!this.entityId) {
1372
- console.error('[TAS DEBUG] entityId is required');
1373
1692
  return;
1374
1693
  }
1375
- console.log('[TAS DEBUG] Validation passed, opening waiting room modal');
1376
1694
  this.openWaitingRoomModal();
1377
1695
  }
1378
1696
  openWaitingRoomModal() {
@@ -1413,12 +1731,12 @@ class TasButtonComponent {
1413
1731
  });
1414
1732
  }
1415
1733
  }
1416
- TasButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, deps: [{ token: i1.NgbModal }, { token: TasService }], target: i0.ɵɵFactoryTarget.Component });
1417
- 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" }, ngImport: i0, template: "<button\n *ngIf=\"isVisible\"\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n>\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">Iniciar TAS</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n</button>\n\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px}.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}\n"], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1734
+ 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 });
1735
+ 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"] }] });
1418
1736
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, decorators: [{
1419
1737
  type: Component,
1420
- args: [{ selector: 'tas-btn', template: "<button\n *ngIf=\"isVisible\"\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n>\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">Iniciar TAS</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n</button>\n\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px}.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}\n"] }]
1421
- }], ctorParameters: function () { return [{ type: i1.NgbModal }, { type: TasService }]; }, propDecorators: { roomType: [{
1738
+ 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"] }]
1739
+ }], ctorParameters: function () { return [{ type: i1.NgbModal }, { type: TasService }, { type: TasUtilityService }]; }, propDecorators: { roomType: [{
1422
1740
  type: Input
1423
1741
  }], entityId: [{
1424
1742
  type: Input
@@ -1428,6 +1746,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1428
1746
  type: Input
1429
1747
  }], currentUser: [{
1430
1748
  type: Input
1749
+ }], variant: [{
1750
+ type: Input
1751
+ }], buttonLabel: [{
1752
+ type: Input
1431
1753
  }] } });
1432
1754
 
1433
1755
  class TasFloatingCallComponent {
@@ -1576,6 +1898,107 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1576
1898
  args: [{ selector: 'tas-floating-call', template: "<div class=\"tas-floating-container\" [class.visible]=\"isVisible\">\n <!-- Video content area - shows main video only -->\n <div class=\"floating-content\">\n <!-- Main video container (subscriber if available, otherwise publisher) -->\n <div id=\"pip-main-video\" class=\"pip-main-video\"></div>\n\n <!-- Bottom controls -->\n <div class=\"floating-controls\">\n <button\n class=\"action-btn expand-btn\"\n (click)=\"onExpand()\"\n title=\"Expand to fullscreen\"\n >\n <i class=\"fa fa-expand\"></i>\n </button>\n <button\n class=\"action-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Unmute microphone' : 'Mute microphone'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button class=\"action-btn hangup-btn\" (click)=\"onHangUp()\" title=\"Hang up call\">\n <i class=\"fa fa-phone\" style=\"transform: rotate(135deg)\"></i>\n </button>\n </div>\n </div>\n</div>\n", styles: [".tas-floating-container{position:fixed;bottom:20px;right:20px;width:280px;height:180px;background:#000;border-radius:12px;box-shadow:0 8px 32px #00000080;z-index:9999;overflow:hidden;touch-action:none;-webkit-user-select:none;user-select:none;transition:opacity .3s ease,visibility .3s ease,box-shadow .2s ease;opacity:0;visibility:hidden;pointer-events:none}.tas-floating-container.visible{opacity:1;visibility:visible;pointer-events:auto}.tas-floating-container:hover{box-shadow:0 8px 32px #00000080,0 0 0 2px #ffffff4d}.floating-content{position:relative;width:100%;height:100%;overflow:hidden}.pip-main-video{position:absolute;top:0;left:0;width:100%;height:100%;background:#000}.pip-main-video ::ng-deep video{width:100%;height:100%;object-fit:cover}.pip-main-video ::ng-deep .OT_subscriber,.pip-main-video ::ng-deep .OT_publisher{width:100%!important;height:100%!important}.pip-main-video ::ng-deep .OT_edge-bar-item,.pip-main-video ::ng-deep .OT_mute,.pip-main-video ::ng-deep .OT_audio-level-meter,.pip-main-video ::ng-deep .OT_bar,.pip-main-video ::ng-deep .OT_name{display:none!important}.floating-controls{position:absolute;bottom:10px;left:50%;transform:translate(-50%);display:flex;gap:12px;padding:6px 14px;background:rgba(0,0,0,.7);border-radius:24px;backdrop-filter:blur(8px);opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease}.tas-floating-container:hover .floating-controls{opacity:1;visibility:visible}.action-btn{width:32px;height:32px;border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .2s ease}.action-btn.expand-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.expand-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.mute-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn.muted{background:#f39c12;color:#fff}.action-btn.mute-btn.muted:hover{background:#e67e22}.action-btn.hangup-btn{background:#dc3545;color:#fff}.action-btn.hangup-btn:hover{background:#c82333;transform:scale(1.1)}\n"] }]
1577
1899
  }], ctorParameters: function () { return [{ type: TasService }, { type: i1.NgbModal }]; } });
1578
1900
 
1901
+ class TasIncomingAppointmentComponent {
1902
+ constructor(tasService) {
1903
+ this.tasService = tasService;
1904
+ // Passthrough inputs for tas-btn
1905
+ this.roomType = TasRoomType.TAS;
1906
+ this.businessRole = TasBusinessRole.USER;
1907
+ this.enterCall = new EventEmitter();
1908
+ this.appointment = null;
1909
+ this.isLoading = true;
1910
+ this.hasError = false;
1911
+ this.subscriptions = new Subscription();
1912
+ }
1913
+ ngOnInit() {
1914
+ this.loadAppointments();
1915
+ }
1916
+ ngOnDestroy() {
1917
+ this.subscriptions.unsubscribe();
1918
+ }
1919
+ loadAppointments() {
1920
+ const today = new Date();
1921
+ const in7Days = new Date(today);
1922
+ in7Days.setDate(today.getDate() + 7);
1923
+ this.subscriptions.add(this.tasService
1924
+ .getAppointments({ fromDate: this.formatDate(today), toDate: this.formatDate(in7Days), entityId: this.entityId })
1925
+ .subscribe({
1926
+ next: (response) => {
1927
+ // Handle both array response and wrapped response (e.g., { content: [...] })
1928
+ const appointments = Array.isArray(response)
1929
+ ? response
1930
+ : (response === null || response === void 0 ? void 0 : response.content) || [];
1931
+ // Filter for confirmed appointments and get the first one
1932
+ const confirmed = appointments.filter((a) => a.status === AppointmentStatus.CONFIRMED);
1933
+ this.appointment = confirmed.length > 0 ? confirmed[0] : null;
1934
+ this.isLoading = false;
1935
+ },
1936
+ error: () => {
1937
+ this.hasError = true;
1938
+ this.isLoading = false;
1939
+ },
1940
+ }));
1941
+ }
1942
+ onEnterCall() {
1943
+ if (this.appointment) {
1944
+ this.enterCall.emit(this.appointment);
1945
+ }
1946
+ }
1947
+ /**
1948
+ * Format date to Spanish format: "Lunes 8 de diciembre"
1949
+ */
1950
+ get formattedDate() {
1951
+ if (!this.appointment)
1952
+ return '';
1953
+ const [year, month, day] = this.appointment.date.split('-').map(Number);
1954
+ const date = new Date(year, month - 1, day);
1955
+ const dayNames = [
1956
+ 'Domingo', 'Lunes', 'Martes', 'Miércoles',
1957
+ 'Jueves', 'Viernes', 'Sábado'
1958
+ ];
1959
+ const monthNames = [
1960
+ 'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio',
1961
+ 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'
1962
+ ];
1963
+ const dayName = dayNames[date.getDay()];
1964
+ const dayNum = date.getDate();
1965
+ const monthName = monthNames[date.getMonth()];
1966
+ return `${dayName} ${dayNum} de ${monthName}`;
1967
+ }
1968
+ /**
1969
+ * Format time range: "9:00 - 9:30"
1970
+ */
1971
+ get formattedTimeRange() {
1972
+ if (!this.appointment)
1973
+ return '';
1974
+ return `${this.appointment.startTime} - ${this.appointment.endTime}`;
1975
+ }
1976
+ formatDate(date) {
1977
+ const year = date.getFullYear();
1978
+ const month = String(date.getMonth() + 1).padStart(2, '0');
1979
+ const day = String(date.getDate()).padStart(2, '0');
1980
+ return `${year}-${month}-${day}`;
1981
+ }
1982
+ }
1983
+ TasIncomingAppointmentComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, deps: [{ token: TasService }], target: i0.ɵɵFactoryTarget.Component });
1984
+ 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" }, 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 && !appointment\">\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 <!-- Appointment state -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointment\">\n \n <div class=\"appointment-card\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"appointment.title\" [size]=\"48\"></tas-avatar>\n \n <span class=\"doctor-name\">{{ appointment.title }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formattedDate }}</span>\n <span class=\"time\">{{ formattedTimeRange }}</span>\n </div>\n <tas-btn\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"appointment.roomType\"\n [entityId]=\"appointment.entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\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}.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"] }] });
1985
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, decorators: [{
1986
+ type: Component,
1987
+ 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 && !appointment\">\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 <!-- Appointment state -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointment\">\n \n <div class=\"appointment-card\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"appointment.title\" [size]=\"48\"></tas-avatar>\n \n <span class=\"doctor-name\">{{ appointment.title }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formattedDate }}</span>\n <span class=\"time\">{{ formattedTimeRange }}</span>\n </div>\n <tas-btn\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"appointment.roomType\"\n [entityId]=\"appointment.entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\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}.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"] }]
1988
+ }], ctorParameters: function () { return [{ type: TasService }]; }, propDecorators: { roomType: [{
1989
+ type: Input
1990
+ }], entityId: [{
1991
+ type: Input
1992
+ }], tenant: [{
1993
+ type: Input
1994
+ }], businessRole: [{
1995
+ type: Input
1996
+ }], currentUser: [{
1997
+ type: Input
1998
+ }], enterCall: [{
1999
+ type: Output
2000
+ }] } });
2001
+
1579
2002
  class TasUellSdkModule {
1580
2003
  /**
1581
2004
  * Use forRoot() to configure the TAS SDK module with required dependencies.
@@ -1622,12 +2045,14 @@ TasUellSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", versio
1622
2045
  TasVideocallComponent,
1623
2046
  TasFloatingCallComponent,
1624
2047
  TasWaitingRoomComponent,
1625
- TasAvatarComponent], imports: [CommonModule, FormsModule], exports: [TasButtonComponent,
2048
+ TasAvatarComponent,
2049
+ TasIncomingAppointmentComponent], imports: [CommonModule, FormsModule, NgbTooltipModule], exports: [TasButtonComponent,
1626
2050
  TasVideocallComponent,
1627
2051
  TasFloatingCallComponent,
1628
2052
  TasWaitingRoomComponent,
1629
- TasAvatarComponent] });
1630
- TasUellSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, imports: [[CommonModule, FormsModule]] });
2053
+ TasAvatarComponent,
2054
+ TasIncomingAppointmentComponent] });
2055
+ TasUellSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, imports: [[CommonModule, FormsModule, NgbTooltipModule]] });
1631
2056
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, decorators: [{
1632
2057
  type: NgModule,
1633
2058
  args: [{
@@ -1637,14 +2062,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1637
2062
  TasFloatingCallComponent,
1638
2063
  TasWaitingRoomComponent,
1639
2064
  TasAvatarComponent,
2065
+ TasIncomingAppointmentComponent,
1640
2066
  ],
1641
- imports: [CommonModule, FormsModule],
2067
+ imports: [CommonModule, FormsModule, NgbTooltipModule],
1642
2068
  exports: [
1643
2069
  TasButtonComponent,
1644
2070
  TasVideocallComponent,
1645
2071
  TasFloatingCallComponent,
1646
2072
  TasWaitingRoomComponent,
1647
2073
  TasAvatarComponent,
2074
+ TasIncomingAppointmentComponent,
1648
2075
  ],
1649
2076
  }]
1650
2077
  }] });
@@ -1657,5 +2084,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1657
2084
  * Generated bundle index. Do not edit.
1658
2085
  */
1659
2086
 
1660
- export { CallState, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
2087
+ export { AppointmentStatus, CallState, GeolocationService, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasIncomingAppointmentComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasUtilityService, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
1661
2088
  //# sourceMappingURL=tas-uell-sdk.mjs.map