tas-uell-sdk 0.0.6 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,6 +88,12 @@ 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 || (AppointmentStatus = {}));
89
97
 
90
98
  class TasService {
91
99
  constructor(httpClient, config) {
@@ -120,6 +128,15 @@ class TasService {
120
128
  this.ownerHasLeft$ = this.ownerHasLeftSubject.asObservable();
121
129
  this.joinableSubject = new BehaviorSubject(false);
122
130
  this.joinable$ = this.joinableSubject.asObservable();
131
+ // Observable for when backend requests geolocation activation
132
+ this.activateGeoSubject = new BehaviorSubject(false);
133
+ this.activateGeo$ = this.activateGeoSubject.asObservable();
134
+ // Observable for when geo request is active (owner waiting for user response)
135
+ this.geoRequestActiveSubject = new BehaviorSubject(false);
136
+ this.geoRequestActive$ = this.geoRequestActiveSubject.asObservable();
137
+ // Observable for when all geo has been granted
138
+ this.allGeoGrantedSubject = new BehaviorSubject(false);
139
+ this.allGeoGranted$ = this.allGeoGrantedSubject.asObservable();
123
140
  this.statusPollingInterval = null;
124
141
  this.DEFAULT_POLL_INTERVAL_MS = 30000; // Default 30s
125
142
  this.wasOwnerPresent = false;
@@ -412,6 +429,19 @@ class TasService {
412
429
  throw error;
413
430
  }));
414
431
  }
432
+ /**
433
+ * Get user appointments within a date range.
434
+ * @param params Date range for querying appointments
435
+ * @returns Observable of appointment array
436
+ */
437
+ getAppointments(params) {
438
+ return this.httpClient
439
+ .get(`v2/proxy/appointment/agendas/user/appointments?fromDate=${params.fromDate}&toDate=${params.toDate}`, { headers: {} })
440
+ .pipe(catchError((error) => {
441
+ console.error('TAS Service: getAppointments failed', error);
442
+ throw error;
443
+ }));
444
+ }
415
445
  /**
416
446
  * Start automatic status polling for the current session.
417
447
  * Status is polled every STATUS_POLL_INTERVAL_MS milliseconds.
@@ -448,6 +478,21 @@ class TasService {
448
478
  }
449
479
  // Update joinable status
450
480
  this.joinableSubject.next(content.joinable);
481
+ // Update activateGeo status
482
+ if (content.activateGeo !== undefined) {
483
+ console.log('[TAS DEBUG] activateGeo received:', content.activateGeo);
484
+ this.activateGeoSubject.next(content.activateGeo);
485
+ }
486
+ // Update geoRequestActive status (owner waiting for user geo response)
487
+ if (content.geoRequestActive !== undefined) {
488
+ console.log('[TAS DEBUG] geoRequestActive received:', content.geoRequestActive);
489
+ this.geoRequestActiveSubject.next(content.geoRequestActive);
490
+ }
491
+ // Update allGeoGranted status (all users responded with geo)
492
+ if (content.allGeoGranted !== undefined) {
493
+ console.log('[TAS DEBUG] allGeoGranted received:', content.allGeoGranted);
494
+ this.allGeoGrantedSubject.next(content.allGeoGranted);
495
+ }
451
496
  // Check if owner has joined
452
497
  const ownerJoined = this.checkIfOwnerJoined(content.users);
453
498
  this.ownerHasJoinedSubject.next(ownerJoined);
@@ -657,6 +702,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
657
702
  }] }];
658
703
  } });
659
704
 
705
+ class GeolocationService {
706
+ constructor() {
707
+ this.cachedPosition = null;
708
+ }
709
+ /**
710
+ * Request current geolocation position using Web Geolocation API.
711
+ * Works in both browser and Capacitor environments.
712
+ * @returns Promise with {latitude, longitude} or null if denied/unavailable
713
+ */
714
+ getCurrentPosition() {
715
+ return __awaiter(this, void 0, void 0, function* () {
716
+ if (!navigator.geolocation) {
717
+ console.warn('[GeolocationService] Geolocation not supported');
718
+ return null;
719
+ }
720
+ return new Promise((resolve) => {
721
+ navigator.geolocation.getCurrentPosition((position) => {
722
+ const geoPosition = {
723
+ latitude: position.coords.latitude,
724
+ longitude: position.coords.longitude,
725
+ };
726
+ this.cachedPosition = geoPosition;
727
+ resolve(geoPosition);
728
+ }, (error) => {
729
+ console.warn('[GeolocationService] Geolocation error:', error.message);
730
+ resolve(null);
731
+ }, {
732
+ enableHighAccuracy: false,
733
+ timeout: 30000,
734
+ maximumAge: 60000,
735
+ });
736
+ });
737
+ });
738
+ }
739
+ /**
740
+ * Get the cached position from the last successful request.
741
+ */
742
+ getCachedPosition() {
743
+ return this.cachedPosition;
744
+ }
745
+ /**
746
+ * Clear the cached position.
747
+ */
748
+ clearCache() {
749
+ this.cachedPosition = null;
750
+ }
751
+ }
752
+ GeolocationService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
753
+ GeolocationService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, providedIn: 'root' });
754
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, decorators: [{
755
+ type: Injectable,
756
+ args: [{
757
+ providedIn: 'root',
758
+ }]
759
+ }] });
760
+
660
761
  class TasAvatarComponent {
661
762
  constructor() {
662
763
  this.name = '';
@@ -731,9 +832,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
731
832
  }] } });
732
833
 
733
834
  class TasVideocallComponent {
734
- constructor(activeModal, tasService) {
835
+ constructor(activeModal, tasService, geolocationService) {
735
836
  this.activeModal = activeModal;
736
837
  this.tasService = tasService;
838
+ this.geolocationService = geolocationService;
737
839
  this.participantName = '';
738
840
  this.tenant = '';
739
841
  this.businessRole = TasBusinessRole.USER;
@@ -745,6 +847,12 @@ class TasVideocallComponent {
745
847
  this.ownerHasJoined = false;
746
848
  this.hasVideoStream = false;
747
849
  this.dismissedUsers = [];
850
+ this.showLocationPanel = true; // Show by default for owners, will be updated based on user's location status
851
+ this.userHasLocation = false; // Tracks if the user has shared their location
852
+ this.geoLocationStatus = 'unknown';
853
+ // Geo panel states for owner
854
+ this.geoRequestActive = false; // Owner sent request, waiting for user
855
+ this.allGeoGranted = false; // All users responded with geo
748
856
  this.subscriptions = new Subscription();
749
857
  }
750
858
  ngOnInit() {
@@ -822,6 +930,30 @@ class TasVideocallComponent {
822
930
  this.dismissedUsers.push(userId);
823
931
  this.waitingRoomUsers = this.waitingRoomUsers.filter((u) => u.userId !== userId);
824
932
  }
933
+ /**
934
+ * Close the location panel
935
+ */
936
+ closeLocationPanel() {
937
+ this.showLocationPanel = false;
938
+ }
939
+ /**
940
+ * Request the user to share their location
941
+ */
942
+ requestUserLocation() {
943
+ if (!this.videoCallId) {
944
+ console.error('Cannot request location: videoCallId not set');
945
+ return;
946
+ }
947
+ const body = {
948
+ videoCallId: this.videoCallId,
949
+ action: UserCallAction.REQUEST_GEOLOCALIZATION,
950
+ };
951
+ // TODO: Send location request action to backend when endpoint is ready
952
+ this.tasService.modifyProxyVideoUser(body).subscribe({
953
+ next: () => console.log('Location request sent'),
954
+ error: (err) => console.error('Error requesting location:', err),
955
+ });
956
+ }
825
957
  // Private Methods
826
958
  setupSubscriptions() {
827
959
  // Call state subscription
@@ -860,6 +992,80 @@ class TasVideocallComponent {
860
992
  this.activeModal.close('owner_left');
861
993
  }
862
994
  }));
995
+ // ActivateGeo subscription - only for non-owners (users)
996
+ this.subscriptions.add(this.tasService.activateGeo$.subscribe((activateGeo) => {
997
+ if (activateGeo && !this.canAdmitUsers) {
998
+ console.log('[TAS DEBUG] activateGeo=true, checking geo status for user...');
999
+ this.handleActivateGeo();
1000
+ }
1001
+ }));
1002
+ // GeoRequestActive subscription - for owners
1003
+ this.subscriptions.add(this.tasService.geoRequestActive$.subscribe((active) => {
1004
+ console.log('[TAS DEBUG] geoRequestActive changed:', active);
1005
+ this.geoRequestActive = active;
1006
+ }));
1007
+ // AllGeoGranted subscription - for owners
1008
+ this.subscriptions.add(this.tasService.allGeoGranted$.subscribe((granted) => {
1009
+ console.log('[TAS DEBUG] allGeoGranted changed:', granted);
1010
+ this.allGeoGranted = granted;
1011
+ }));
1012
+ }
1013
+ /**
1014
+ * Handle activateGeo request from backend (for non-owner users).
1015
+ * If geo is already active, report it. If not, prompt user.
1016
+ */
1017
+ handleActivateGeo() {
1018
+ return __awaiter(this, void 0, void 0, function* () {
1019
+ console.log('[TAS DEBUG] handleActivateGeo - current status:', this.geoLocationStatus);
1020
+ // Check if we already have a cached position
1021
+ const cachedPosition = this.geolocationService.getCachedPosition();
1022
+ if (cachedPosition) {
1023
+ console.log('[TAS DEBUG] Geolocation already active, reporting to backend:', cachedPosition);
1024
+ this.geoLocationStatus = 'active';
1025
+ this.reportGeoStatus(cachedPosition.latitude, cachedPosition.longitude);
1026
+ return;
1027
+ }
1028
+ // Try to get position
1029
+ console.log('[TAS DEBUG] Requesting geolocation from user...');
1030
+ const position = yield this.geolocationService.getCurrentPosition();
1031
+ if (position) {
1032
+ console.log('[TAS DEBUG] Geolocation obtained:', position);
1033
+ this.geoLocationStatus = 'active';
1034
+ this.reportGeoStatus(position.latitude, position.longitude);
1035
+ }
1036
+ else {
1037
+ console.log('[TAS DEBUG] Geolocation denied or unavailable');
1038
+ this.geoLocationStatus = 'denied';
1039
+ // Report that geo is not available (with no coordinates)
1040
+ this.reportGeoStatus();
1041
+ }
1042
+ });
1043
+ }
1044
+ /**
1045
+ * Report geolocation status to backend.
1046
+ */
1047
+ reportGeoStatus(latitude, longitude) {
1048
+ if (!this.videoCallId) {
1049
+ console.error('[TAS DEBUG] Cannot report geo status: videoCallId not set');
1050
+ return;
1051
+ }
1052
+ // TODO: If the permission for geo location is not granted, we should not report it to the backend
1053
+ if (!this.geoLocationStatus) {
1054
+ console.error('[TAS DEBUG] Cannot report geo status: geoLocationStatus not set');
1055
+ return;
1056
+ }
1057
+ const body = {
1058
+ userId: this.userId,
1059
+ videoCallId: this.videoCallId,
1060
+ action: UserCallAction.ACTIVATE_GEOLOCATION,
1061
+ latitude,
1062
+ longitude,
1063
+ };
1064
+ console.log('[TAS DEBUG] Reporting geo status to backend:', body);
1065
+ this.tasService.modifyProxyVideoUser(body).subscribe({
1066
+ next: () => console.log('[TAS DEBUG] Geo status reported successfully'),
1067
+ error: (err) => console.error('[TAS DEBUG] Error reporting geo status:', err),
1068
+ });
863
1069
  }
864
1070
  initializeCall() {
865
1071
  if (this.isReturningFromPip) {
@@ -949,12 +1155,12 @@ class TasVideocallComponent {
949
1155
  });
950
1156
  }
951
1157
  }
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"] }] });
1158
+ 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 });
1159
+ 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
1160
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, decorators: [{
955
1161
  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: [{
1162
+ 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"] }]
1163
+ }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }]; }, propDecorators: { sessionId: [{
958
1164
  type: Input
959
1165
  }], token: [{
960
1166
  type: Input
@@ -962,6 +1168,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
962
1168
  type: Input
963
1169
  }], videoCallId: [{
964
1170
  type: Input
1171
+ }], userId: [{
1172
+ type: Input
965
1173
  }], participantName: [{
966
1174
  type: Input
967
1175
  }], tenant: [{
@@ -981,15 +1189,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
981
1189
  var WaitingRoomState;
982
1190
  (function (WaitingRoomState) {
983
1191
  WaitingRoomState["CHECKING_STATUS"] = "CHECKING_STATUS";
984
- WaitingRoomState["WAITING_FOR_OWNER"] = "WAITING_FOR_OWNER";
1192
+ WaitingRoomState["WAITING_FOR_JOINABLE"] = "WAITING_FOR_JOINABLE";
985
1193
  WaitingRoomState["READY"] = "READY";
986
1194
  WaitingRoomState["GETTING_TOKEN"] = "GETTING_TOKEN";
1195
+ WaitingRoomState["JOINING"] = "JOINING";
987
1196
  WaitingRoomState["ERROR"] = "ERROR";
988
1197
  })(WaitingRoomState || (WaitingRoomState = {}));
989
1198
  class TasWaitingRoomComponent {
990
- constructor(activeModal, tasService, modalService, cdr) {
1199
+ constructor(activeModal, tasService, geolocationService, modalService, cdr) {
991
1200
  this.activeModal = activeModal;
992
1201
  this.tasService = tasService;
1202
+ this.geolocationService = geolocationService;
993
1203
  this.modalService = modalService;
994
1204
  this.cdr = cdr;
995
1205
  // Status endpoint params
@@ -999,6 +1209,7 @@ class TasWaitingRoomComponent {
999
1209
  this.state = WaitingRoomState.CHECKING_STATUS;
1000
1210
  this.WaitingRoomState = WaitingRoomState; // Expose enum to template
1001
1211
  this.errorMessage = '';
1212
+ this.isJoinable = false;
1002
1213
  // Session data from status response
1003
1214
  this.resolvedSessionId = '';
1004
1215
  this.resolvedAppointmentId = null;
@@ -1007,6 +1218,8 @@ class TasWaitingRoomComponent {
1007
1218
  // Subscriptions
1008
1219
  this.subscriptions = new Subscription();
1009
1220
  this.videoCallModalRef = null;
1221
+ // Geolocation
1222
+ this.geoPosition = null;
1010
1223
  }
1011
1224
  /** Whether current user is an owner */
1012
1225
  get isOwner() {
@@ -1015,8 +1228,74 @@ class TasWaitingRoomComponent {
1015
1228
  }
1016
1229
  ngOnInit() {
1017
1230
  console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnInit');
1231
+ this.requestMediaPermissions();
1232
+ this.requestGeolocation();
1018
1233
  this.checkStatus();
1019
1234
  }
1235
+ /**
1236
+ * Request camera and microphone permissions.
1237
+ */
1238
+ requestMediaPermissions() {
1239
+ return __awaiter(this, void 0, void 0, function* () {
1240
+ console.log('[TAS DEBUG] Requesting media permissions...');
1241
+ try {
1242
+ const stream = yield navigator.mediaDevices.getUserMedia({ audio: true, video: true });
1243
+ // Stop tracks immediately - we just needed the permission
1244
+ stream.getTracks().forEach(track => track.stop());
1245
+ console.log('[TAS DEBUG] Media permissions granted');
1246
+ }
1247
+ catch (error) {
1248
+ console.warn('[TAS DEBUG] Media permissions denied or unavailable:', error);
1249
+ }
1250
+ });
1251
+ }
1252
+ /**
1253
+ * Request geolocation immediately on init.
1254
+ * Only for regular users (not owners/backoffice).
1255
+ * If user allows, store position and send to backend.
1256
+ */
1257
+ requestGeolocation() {
1258
+ return __awaiter(this, void 0, void 0, function* () {
1259
+ // Only request geolocation for regular users, not owners/backoffice
1260
+ if (this.isOwner || this.isBackoffice) {
1261
+ console.log('[TAS DEBUG] Skipping geolocation for owner/backoffice');
1262
+ return;
1263
+ }
1264
+ console.log('[TAS DEBUG] Requesting geolocation...');
1265
+ const position = yield this.geolocationService.getCurrentPosition();
1266
+ if (position) {
1267
+ console.log('[TAS DEBUG] Geolocation obtained:', position);
1268
+ this.geoPosition = position;
1269
+ // Send to backend when videoCallId is available
1270
+ this.sendGeolocationToBackend();
1271
+ }
1272
+ else {
1273
+ console.log('[TAS DEBUG] Geolocation denied or unavailable');
1274
+ }
1275
+ });
1276
+ }
1277
+ /**
1278
+ * Send geolocation to backend via modify user endpoint.
1279
+ * NOTE: Endpoint call is prepared but may not be active yet.
1280
+ */
1281
+ sendGeolocationToBackend() {
1282
+ var _a;
1283
+ if (!this.geoPosition || !this.videoCallId) {
1284
+ return;
1285
+ }
1286
+ console.log('[TAS DEBUG] Sending geolocation to backend...');
1287
+ const body = {
1288
+ userId: (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.id,
1289
+ videoCallId: this.videoCallId,
1290
+ action: UserCallAction.ACTIVATE_GEOLOCATION,
1291
+ latitude: this.geoPosition.latitude,
1292
+ longitude: this.geoPosition.longitude,
1293
+ };
1294
+ this.tasService.modifyProxyVideoUser(body).subscribe({
1295
+ next: () => console.log('[TAS DEBUG] Geolocation sent successfully'),
1296
+ error: (err) => console.error('[TAS DEBUG] Failed to send geolocation:', err),
1297
+ });
1298
+ }
1020
1299
  ngOnDestroy() {
1021
1300
  console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnDestroy');
1022
1301
  this.subscriptions.unsubscribe();
@@ -1050,6 +1329,8 @@ class TasWaitingRoomComponent {
1050
1329
  this.resolvedAppointmentId = content.appointmentId;
1051
1330
  this.videoCallId = content.videoCallId;
1052
1331
  console.log('[TAS DEBUG] Status response:', content);
1332
+ // Try to send geolocation now that we have videoCallId
1333
+ this.sendGeolocationToBackend();
1053
1334
  // Start polling for status updates
1054
1335
  this.tasService.startStatusPolling(statusParams);
1055
1336
  // Subscribe to joinable status
@@ -1071,36 +1352,33 @@ class TasWaitingRoomComponent {
1071
1352
  console.log('[TAS DEBUG] handleJoinableChange called', {
1072
1353
  joinable,
1073
1354
  currentState: this.state,
1074
- isOwner: this.isOwner,
1075
- isBackoffice: this.isBackoffice,
1076
1355
  resolvedSessionId: this.resolvedSessionId,
1077
1356
  });
1078
- // Don't update state if already getting token, ready, or in error
1357
+ this.isJoinable = joinable;
1358
+ // Don't update state if already getting token, joining, or in error
1079
1359
  if (this.state === WaitingRoomState.GETTING_TOKEN ||
1080
- this.state === WaitingRoomState.READY ||
1360
+ this.state === WaitingRoomState.JOINING ||
1081
1361
  this.state === WaitingRoomState.ERROR) {
1082
1362
  console.log('[TAS DEBUG] Skipping state update - already in:', this.state);
1083
1363
  return;
1084
1364
  }
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
- }
1365
+ // Both users and owners: show join button based on joinable
1366
+ if (joinable) {
1367
+ console.log('[TAS DEBUG] Joinable is true, showing join button');
1368
+ this.state = WaitingRoomState.READY;
1091
1369
  }
1092
1370
  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
- }
1371
+ console.log('[TAS DEBUG] Waiting for joinable...');
1372
+ this.state = WaitingRoomState.WAITING_FOR_JOINABLE;
1103
1373
  }
1374
+ this.cdr.detectChanges();
1375
+ }
1376
+ /**
1377
+ * Called when user clicks the join button.
1378
+ * Calls /start to get token then auto-joins.
1379
+ */
1380
+ joinSession() {
1381
+ this.startSessionAndJoin();
1104
1382
  }
1105
1383
  /**
1106
1384
  * Check if user has owner/backoffice role
@@ -1111,9 +1389,9 @@ class TasWaitingRoomComponent {
1111
1389
  this.businessRole === TasBusinessRole.MANAGER);
1112
1390
  }
1113
1391
  /**
1114
- * Get token for owner/backoffice - call /start endpoint
1392
+ * Start session and join - called when user clicks join button
1115
1393
  */
1116
- getTokenForOwner() {
1394
+ startSessionAndJoin() {
1117
1395
  if (!this.resolvedSessionId) {
1118
1396
  this.state = WaitingRoomState.ERROR;
1119
1397
  this.errorMessage = 'Session ID not available';
@@ -1143,10 +1421,14 @@ class TasWaitingRoomComponent {
1143
1421
  this.cdr.detectChanges();
1144
1422
  return;
1145
1423
  }
1146
- console.log('[TAS DEBUG] Token obtained successfully');
1424
+ console.log('[TAS DEBUG] Token obtained successfully, joining session...');
1147
1425
  this.token = tokenResponse.content.token;
1148
- this.state = WaitingRoomState.READY;
1426
+ this.state = WaitingRoomState.JOINING;
1149
1427
  this.cdr.detectChanges();
1428
+ // Auto-join immediately
1429
+ this.tasService.stopStatusPolling();
1430
+ this.activeModal.close('joining');
1431
+ this.openVideoCallModal();
1150
1432
  },
1151
1433
  error: (err) => {
1152
1434
  var _a;
@@ -1159,19 +1441,6 @@ class TasWaitingRoomComponent {
1159
1441
  },
1160
1442
  }));
1161
1443
  }
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
1444
  /**
1176
1445
  * Closes the waiting room
1177
1446
  */
@@ -1189,6 +1458,7 @@ class TasWaitingRoomComponent {
1189
1458
  this.checkStatus();
1190
1459
  }
1191
1460
  openVideoCallModal() {
1461
+ var _a;
1192
1462
  this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
1193
1463
  size: 'xl',
1194
1464
  windowClass: 'tas-video-modal',
@@ -1199,6 +1469,7 @@ class TasWaitingRoomComponent {
1199
1469
  this.videoCallModalRef.componentInstance.token = this.token;
1200
1470
  this.videoCallModalRef.componentInstance.appointmentId = this.resolvedAppointmentId;
1201
1471
  this.videoCallModalRef.componentInstance.videoCallId = this.videoCallId;
1472
+ this.videoCallModalRef.componentInstance.userId = (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.id;
1202
1473
  this.videoCallModalRef.componentInstance.tenant = this.tenant;
1203
1474
  this.videoCallModalRef.componentInstance.businessRole = this.businessRole;
1204
1475
  this.videoCallModalRef.componentInstance.isReturningFromPip = false;
@@ -1209,12 +1480,12 @@ class TasWaitingRoomComponent {
1209
1480
  });
1210
1481
  }
1211
1482
  }
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"] }] });
1483
+ 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 });
1484
+ 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
1485
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, decorators: [{
1215
1486
  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: [{
1487
+ 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"] }]
1488
+ }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }, { type: i1.NgbModal }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { roomType: [{
1218
1489
  type: Input
1219
1490
  }], entityId: [{
1220
1491
  type: Input
@@ -1233,12 +1504,15 @@ class TasButtonComponent {
1233
1504
  // Status endpoint params
1234
1505
  this.roomType = TasRoomType.TAS;
1235
1506
  this.businessRole = TasBusinessRole.USER;
1507
+ // Style customization
1508
+ this.variant = 'default';
1509
+ this.buttonLabel = 'Iniciar TAS';
1236
1510
  this.isLoading = false;
1237
- this.buttonText = 'Iniciar TAS';
1238
1511
  // Status check state
1239
1512
  this.isCheckingStatus = false;
1240
1513
  this.isStatusError = false;
1241
1514
  this.statusErrorMessage = '';
1515
+ this.isJoinable = false; // Tracks joinable field from status response
1242
1516
  this.subscriptions = new Subscription();
1243
1517
  this.currentModalRef = null;
1244
1518
  this.videoCallModalRef = null;
@@ -1251,18 +1525,22 @@ class TasButtonComponent {
1251
1525
  this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
1252
1526
  this.businessRole === TasBusinessRole.MANAGER);
1253
1527
  }
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
1528
  /** Whether the button should be disabled */
1264
1529
  get isDisabled() {
1265
- return this.isLoading || this.isCheckingStatus || this.isStatusError;
1530
+ return this.isLoading || this.isStatusError || !this.isJoinable;
1531
+ }
1532
+ /** Reason why the button is disabled (for tooltip) */
1533
+ get disabledReason() {
1534
+ if (this.isLoading) {
1535
+ return '';
1536
+ }
1537
+ if (this.isStatusError) {
1538
+ return this.statusErrorMessage || 'Error al verificar el estado';
1539
+ }
1540
+ if (!this.isJoinable) {
1541
+ return 'Todavía no es el horario de la llamada';
1542
+ }
1543
+ return '';
1266
1544
  }
1267
1545
  ngOnInit() {
1268
1546
  // Subscribe to viewMode to handle PiP return
@@ -1305,17 +1583,10 @@ class TasButtonComponent {
1305
1583
  checkStatus() {
1306
1584
  // Skip if required inputs are not available
1307
1585
  if (!this.tenant || !this.entityId) {
1308
- console.log('[TAS DEBUG] checkStatus skipped - missing required inputs');
1309
1586
  return;
1310
1587
  }
1311
1588
  this.isCheckingStatus = true;
1312
1589
  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
1590
  this.subscriptions.add(this.tasService.getProxyVideoStatus({
1320
1591
  roomType: this.roomType,
1321
1592
  entityId: this.entityId,
@@ -1323,7 +1594,7 @@ class TasButtonComponent {
1323
1594
  businessRole: this.businessRole,
1324
1595
  }).subscribe({
1325
1596
  next: (response) => {
1326
- var _a;
1597
+ var _a, _b, _c;
1327
1598
  // Check if response is actually an error (some HTTP adapters return errors in next)
1328
1599
  // Also check for undefined/null or missing content
1329
1600
  const isErrorResponse = !response ||
@@ -1333,21 +1604,20 @@ class TasButtonComponent {
1333
1604
  (response === null || response === void 0 ? void 0 : response.error) ||
1334
1605
  (response === null || response === void 0 ? void 0 : response.name) === 'HttpErrorResponse';
1335
1606
  if (isErrorResponse) {
1336
- console.error('[TAS DEBUG] Status check returned error in response:', response);
1337
1607
  this.isCheckingStatus = false;
1338
1608
  this.isStatusError = true;
1339
1609
  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
1610
  }
1341
1611
  else {
1342
- console.log('[TAS DEBUG] Status check successful:', response);
1343
1612
  this.isCheckingStatus = false;
1344
1613
  this.isStatusError = false;
1345
1614
  this.statusErrorMessage = '';
1615
+ // Update joinable state from response
1616
+ this.isJoinable = (_c = (_b = response.content) === null || _b === void 0 ? void 0 : _b.joinable) !== null && _c !== void 0 ? _c : false;
1346
1617
  }
1347
1618
  },
1348
1619
  error: (err) => {
1349
1620
  var _a;
1350
- console.error('[TAS DEBUG] Status check failed:', err);
1351
1621
  this.isCheckingStatus = false;
1352
1622
  this.isStatusError = true;
1353
1623
  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';
@@ -1356,23 +1626,12 @@ class TasButtonComponent {
1356
1626
  }
1357
1627
  onClick() {
1358
1628
  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
1629
  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
1630
  return;
1370
1631
  }
1371
1632
  if (!this.entityId) {
1372
- console.error('[TAS DEBUG] entityId is required');
1373
1633
  return;
1374
1634
  }
1375
- console.log('[TAS DEBUG] Validation passed, opening waiting room modal');
1376
1635
  this.openWaitingRoomModal();
1377
1636
  }
1378
1637
  openWaitingRoomModal() {
@@ -1414,10 +1673,10 @@ class TasButtonComponent {
1414
1673
  }
1415
1674
  }
1416
1675
  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"] }] });
1676
+ 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 [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;padding:10px 24px;font-weight:500;margin-right:0}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"], directives: [{ type: i1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "triggers", "container", "disableTooltip", "tooltipClass", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1418
1677
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, decorators: [{
1419
1678
  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"] }]
1679
+ args: [{ selector: 'tas-btn', template: "<span\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;padding:10px 24px;font-weight:500;margin-right:0}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"] }]
1421
1680
  }], ctorParameters: function () { return [{ type: i1.NgbModal }, { type: TasService }]; }, propDecorators: { roomType: [{
1422
1681
  type: Input
1423
1682
  }], entityId: [{
@@ -1428,6 +1687,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1428
1687
  type: Input
1429
1688
  }], currentUser: [{
1430
1689
  type: Input
1690
+ }], variant: [{
1691
+ type: Input
1692
+ }], buttonLabel: [{
1693
+ type: Input
1431
1694
  }] } });
1432
1695
 
1433
1696
  class TasFloatingCallComponent {
@@ -1576,6 +1839,107 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1576
1839
  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
1840
  }], ctorParameters: function () { return [{ type: TasService }, { type: i1.NgbModal }]; } });
1578
1841
 
1842
+ class TasIncomingAppointmentComponent {
1843
+ constructor(tasService) {
1844
+ this.tasService = tasService;
1845
+ // Passthrough inputs for tas-btn
1846
+ this.roomType = TasRoomType.TAS;
1847
+ this.businessRole = TasBusinessRole.USER;
1848
+ this.enterCall = new EventEmitter();
1849
+ this.appointment = null;
1850
+ this.isLoading = true;
1851
+ this.hasError = false;
1852
+ this.subscriptions = new Subscription();
1853
+ }
1854
+ ngOnInit() {
1855
+ this.loadAppointments();
1856
+ }
1857
+ ngOnDestroy() {
1858
+ this.subscriptions.unsubscribe();
1859
+ }
1860
+ loadAppointments() {
1861
+ const today = new Date();
1862
+ const in7Days = new Date(today);
1863
+ in7Days.setDate(today.getDate() + 7);
1864
+ this.subscriptions.add(this.tasService
1865
+ .getAppointments({ fromDate: this.formatDate(today), toDate: this.formatDate(in7Days) })
1866
+ .subscribe({
1867
+ next: (response) => {
1868
+ // Handle both array response and wrapped response (e.g., { content: [...] })
1869
+ const appointments = Array.isArray(response)
1870
+ ? response
1871
+ : (response === null || response === void 0 ? void 0 : response.content) || [];
1872
+ // Filter for confirmed appointments and get the first one
1873
+ const confirmed = appointments.filter((a) => a.status === AppointmentStatus.CONFIRMED);
1874
+ this.appointment = confirmed.length > 0 ? confirmed[0] : null;
1875
+ this.isLoading = false;
1876
+ },
1877
+ error: () => {
1878
+ this.hasError = true;
1879
+ this.isLoading = false;
1880
+ },
1881
+ }));
1882
+ }
1883
+ onEnterCall() {
1884
+ if (this.appointment) {
1885
+ this.enterCall.emit(this.appointment);
1886
+ }
1887
+ }
1888
+ /**
1889
+ * Format date to Spanish format: "Lunes 8 de diciembre"
1890
+ */
1891
+ get formattedDate() {
1892
+ if (!this.appointment)
1893
+ return '';
1894
+ const [year, month, day] = this.appointment.date.split('-').map(Number);
1895
+ const date = new Date(year, month - 1, day);
1896
+ const dayNames = [
1897
+ 'Domingo', 'Lunes', 'Martes', 'Miércoles',
1898
+ 'Jueves', 'Viernes', 'Sábado'
1899
+ ];
1900
+ const monthNames = [
1901
+ 'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio',
1902
+ 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'
1903
+ ];
1904
+ const dayName = dayNames[date.getDay()];
1905
+ const dayNum = date.getDate();
1906
+ const monthName = monthNames[date.getMonth()];
1907
+ return `${dayName} ${dayNum} de ${monthName}`;
1908
+ }
1909
+ /**
1910
+ * Format time range: "9:00 - 9:30"
1911
+ */
1912
+ get formattedTimeRange() {
1913
+ if (!this.appointment)
1914
+ return '';
1915
+ return `${this.appointment.startTime} - ${this.appointment.endTime}`;
1916
+ }
1917
+ formatDate(date) {
1918
+ const year = date.getFullYear();
1919
+ const month = String(date.getMonth() + 1).padStart(2, '0');
1920
+ const day = String(date.getDate()).padStart(2, '0');
1921
+ return `${year}-${month}-${day}`;
1922
+ }
1923
+ }
1924
+ TasIncomingAppointmentComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, deps: [{ token: TasService }], target: i0.ɵɵFactoryTarget.Component });
1925
+ 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]=\"roomType\"\n [entityId]=\"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"] }] });
1926
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, decorators: [{
1927
+ type: Component,
1928
+ 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]=\"roomType\"\n [entityId]=\"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"] }]
1929
+ }], ctorParameters: function () { return [{ type: TasService }]; }, propDecorators: { roomType: [{
1930
+ type: Input
1931
+ }], entityId: [{
1932
+ type: Input
1933
+ }], tenant: [{
1934
+ type: Input
1935
+ }], businessRole: [{
1936
+ type: Input
1937
+ }], currentUser: [{
1938
+ type: Input
1939
+ }], enterCall: [{
1940
+ type: Output
1941
+ }] } });
1942
+
1579
1943
  class TasUellSdkModule {
1580
1944
  /**
1581
1945
  * Use forRoot() to configure the TAS SDK module with required dependencies.
@@ -1622,12 +1986,14 @@ TasUellSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", versio
1622
1986
  TasVideocallComponent,
1623
1987
  TasFloatingCallComponent,
1624
1988
  TasWaitingRoomComponent,
1625
- TasAvatarComponent], imports: [CommonModule, FormsModule], exports: [TasButtonComponent,
1989
+ TasAvatarComponent,
1990
+ TasIncomingAppointmentComponent], imports: [CommonModule, FormsModule, NgbTooltipModule], exports: [TasButtonComponent,
1626
1991
  TasVideocallComponent,
1627
1992
  TasFloatingCallComponent,
1628
1993
  TasWaitingRoomComponent,
1629
- TasAvatarComponent] });
1630
- TasUellSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, imports: [[CommonModule, FormsModule]] });
1994
+ TasAvatarComponent,
1995
+ TasIncomingAppointmentComponent] });
1996
+ TasUellSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, imports: [[CommonModule, FormsModule, NgbTooltipModule]] });
1631
1997
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, decorators: [{
1632
1998
  type: NgModule,
1633
1999
  args: [{
@@ -1637,14 +2003,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1637
2003
  TasFloatingCallComponent,
1638
2004
  TasWaitingRoomComponent,
1639
2005
  TasAvatarComponent,
2006
+ TasIncomingAppointmentComponent,
1640
2007
  ],
1641
- imports: [CommonModule, FormsModule],
2008
+ imports: [CommonModule, FormsModule, NgbTooltipModule],
1642
2009
  exports: [
1643
2010
  TasButtonComponent,
1644
2011
  TasVideocallComponent,
1645
2012
  TasFloatingCallComponent,
1646
2013
  TasWaitingRoomComponent,
1647
2014
  TasAvatarComponent,
2015
+ TasIncomingAppointmentComponent,
1648
2016
  ],
1649
2017
  }]
1650
2018
  }] });
@@ -1657,5 +2025,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1657
2025
  * Generated bundle index. Do not edit.
1658
2026
  */
1659
2027
 
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 };
2028
+ export { AppointmentStatus, CallState, GeolocationService, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasIncomingAppointmentComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
1661
2029
  //# sourceMappingURL=tas-uell-sdk.mjs.map