tychat-contracts 1.6.74 → 1.6.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/analytics/event-analytic.enum.d.ts +1 -1
  2. package/dist/analytics/event-analytic.enum.d.ts.map +1 -1
  3. package/dist/analytics/event-analytic.enum.js +4 -0
  4. package/dist/anamneses/normalize-anamnesis-access-token.d.ts +9 -0
  5. package/dist/anamneses/normalize-anamnesis-access-token.d.ts.map +1 -0
  6. package/dist/anamneses/normalize-anamnesis-access-token.js +21 -0
  7. package/dist/anamneses/normalize-anamnesis-access-token.spec.d.ts +2 -0
  8. package/dist/anamneses/normalize-anamnesis-access-token.spec.d.ts.map +1 -0
  9. package/dist/anamneses/normalize-anamnesis-access-token.spec.js +18 -0
  10. package/dist/appointments/appointment-status.dto.d.ts +1 -1
  11. package/dist/appointments/appointment-status.dto.d.ts.map +1 -1
  12. package/dist/appointments/appointment-status.dto.js +1 -0
  13. package/dist/appointments/index.d.ts +2 -0
  14. package/dist/appointments/index.d.ts.map +1 -1
  15. package/dist/appointments/index.js +2 -0
  16. package/dist/appointments/schedule-block-rmq-patterns.d.ts +7 -0
  17. package/dist/appointments/schedule-block-rmq-patterns.d.ts.map +1 -0
  18. package/dist/appointments/schedule-block-rmq-patterns.js +9 -0
  19. package/dist/appointments/schedule-block.dto.d.ts +54 -0
  20. package/dist/appointments/schedule-block.dto.d.ts.map +1 -0
  21. package/dist/appointments/schedule-block.dto.js +223 -0
  22. package/dist/fiscal/enqueue-nfe.dto.d.ts +0 -3
  23. package/dist/fiscal/enqueue-nfe.dto.d.ts.map +1 -1
  24. package/dist/fiscal/enqueue-nfe.dto.js +0 -14
  25. package/dist/tenants/index.d.ts +0 -1
  26. package/dist/tenants/index.d.ts.map +1 -1
  27. package/dist/tenants/index.js +0 -1
  28. package/package.json +1 -1
  29. package/src/analytics/event-analytic.enum.ts +5 -0
  30. package/src/appointments/appointment-status.dto.ts +1 -0
  31. package/src/appointments/index.ts +3 -1
  32. package/src/appointments/schedule-block-rmq-patterns.ts +6 -0
  33. package/src/appointments/schedule-block.dto.ts +155 -0
  34. package/src/campaigns/campaign-template-header-media.dto.ts +25 -25
  35. package/src/campaigns/campaign-template.dto.ts +45 -45
  36. package/src/campaigns/create-campaign.dto.ts +80 -80
  37. package/src/fiscal/enqueue-nfe.dto.ts +1 -22
  38. package/src/patients/index.ts +10 -10
  39. package/src/tenants/anamnesis-public-frontend-url.spec.ts +45 -45
  40. package/src/tenants/anamnesis-public-frontend-url.ts +61 -61
  41. package/src/tenants/index.ts +0 -1
  42. package/dist/connections/connections.dto.d.ts +0 -53
  43. package/dist/connections/connections.dto.d.ts.map +0 -1
  44. package/dist/connections/connections.dto.js +0 -376
  45. package/dist/tenants/tenant-fiscal-profile.dto.d.ts +0 -27
  46. package/dist/tenants/tenant-fiscal-profile.dto.d.ts.map +0 -1
  47. package/dist/tenants/tenant-fiscal-profile.dto.js +0 -128
  48. package/src/tenants/tenant-fiscal-profile.dto.ts +0 -85
@@ -0,0 +1,155 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+ import { Type } from 'class-transformer';
3
+ import {
4
+ IsArray,
5
+ IsDateString,
6
+ IsEnum,
7
+ IsNotEmpty,
8
+ IsOptional,
9
+ IsString,
10
+ IsUUID,
11
+ MaxLength,
12
+ ValidateNested,
13
+ } from 'class-validator';
14
+
15
+ export class ScheduleBlockIntervalDto {
16
+ @ApiProperty({ description: 'Início do bloqueio (ISO 8601)', example: '2026-06-15T14:00:00.000Z' })
17
+ @IsDateString()
18
+ startAt: string;
19
+
20
+ @ApiProperty({ description: 'Fim do bloqueio (ISO 8601)', example: '2026-06-15T16:00:00.000Z' })
21
+ @IsDateString()
22
+ endAt: string;
23
+ }
24
+
25
+ export const SCHEDULE_BLOCK_CONFLICT_ACTIONS = ['cancel', 'ai_reschedule'] as const;
26
+ export type ScheduleBlockConflictActionDto = (typeof SCHEDULE_BLOCK_CONFLICT_ACTIONS)[number];
27
+
28
+ export class ScheduleBlockConflictActionDtoClass {
29
+ @ApiProperty({ description: 'ID do agendamento em conflito' })
30
+ @IsUUID('4')
31
+ appointmentId: string;
32
+
33
+ @ApiProperty({ enum: SCHEDULE_BLOCK_CONFLICT_ACTIONS })
34
+ @IsEnum(SCHEDULE_BLOCK_CONFLICT_ACTIONS)
35
+ action: ScheduleBlockConflictActionDto;
36
+ }
37
+
38
+ export class PreviewScheduleBlockDto {
39
+ @ApiProperty({ description: 'ID do profissional (userId)' })
40
+ @IsUUID('4')
41
+ userId: string;
42
+
43
+ @ApiProperty({ type: [ScheduleBlockIntervalDto] })
44
+ @IsArray()
45
+ @ValidateNested({ each: true })
46
+ @Type(() => ScheduleBlockIntervalDto)
47
+ intervals: ScheduleBlockIntervalDto[];
48
+ }
49
+
50
+ export class ScheduleBlockConflictDto {
51
+ @ApiProperty()
52
+ appointmentId: string;
53
+
54
+ @ApiProperty()
55
+ patientId: string;
56
+
57
+ @ApiProperty()
58
+ patientName: string;
59
+
60
+ @ApiProperty({ type: [String] })
61
+ procedureNames: string[];
62
+
63
+ @ApiProperty()
64
+ start: string;
65
+
66
+ @ApiProperty()
67
+ end: string;
68
+
69
+ @ApiProperty()
70
+ status: string;
71
+ }
72
+
73
+ export class PreviewScheduleBlockResponseDto {
74
+ @ApiProperty({ type: [ScheduleBlockIntervalDto] })
75
+ intervals: ScheduleBlockIntervalDto[];
76
+
77
+ @ApiProperty({ type: [ScheduleBlockConflictDto] })
78
+ conflicts: ScheduleBlockConflictDto[];
79
+ }
80
+
81
+ export class ApplyScheduleBlockDto extends PreviewScheduleBlockDto {
82
+ @ApiPropertyOptional({ description: 'Motivo opcional do bloqueio', maxLength: 500 })
83
+ @IsOptional()
84
+ @IsString()
85
+ @MaxLength(500)
86
+ reason?: string;
87
+
88
+ @ApiProperty({ description: 'ID do usuário que criou o bloqueio' })
89
+ @IsUUID('4')
90
+ createdByUserId: string;
91
+
92
+ @ApiPropertyOptional({ type: [ScheduleBlockConflictActionDtoClass] })
93
+ @IsOptional()
94
+ @IsArray()
95
+ @ValidateNested({ each: true })
96
+ @Type(() => ScheduleBlockConflictActionDtoClass)
97
+ conflictActions?: ScheduleBlockConflictActionDtoClass[];
98
+ }
99
+
100
+ export class ScheduleBlockDto {
101
+ @ApiProperty()
102
+ id: string;
103
+
104
+ @ApiProperty()
105
+ userId: string;
106
+
107
+ @ApiProperty()
108
+ startAt: string;
109
+
110
+ @ApiProperty()
111
+ endAt: string;
112
+
113
+ @ApiPropertyOptional()
114
+ reason?: string | null;
115
+
116
+ @ApiProperty()
117
+ createdByUserId: string;
118
+
119
+ @ApiProperty()
120
+ createdAt: string;
121
+ }
122
+
123
+ export class ApplyScheduleBlockResponseDto {
124
+ @ApiProperty({ type: [ScheduleBlockDto] })
125
+ blocks: ScheduleBlockDto[];
126
+
127
+ @ApiProperty({ description: 'IDs de agendamentos cancelados' })
128
+ cancelledAppointmentIds: string[];
129
+
130
+ @ApiProperty({ description: 'IDs de agendamentos com pedido de reagendamento IA iniciado' })
131
+ aiRescheduleAppointmentIds: string[];
132
+ }
133
+
134
+ export class FindScheduleBlocksInRangeQueryDto {
135
+ @ApiProperty()
136
+ @IsUUID('4')
137
+ userId: string;
138
+
139
+ @ApiProperty({ example: '2026-06-01' })
140
+ @IsDateString()
141
+ startDate: string;
142
+
143
+ @ApiProperty({ example: '2026-06-30' })
144
+ @IsDateString()
145
+ endDate: string;
146
+ }
147
+
148
+ export const SCHEDULE_BLOCK_RESCHEDULE_STATUSES = [
149
+ 'pending_patient',
150
+ 'confirmed',
151
+ 'expired',
152
+ 'cancelled',
153
+ ] as const;
154
+ export type ScheduleBlockRescheduleStatusDto =
155
+ (typeof SCHEDULE_BLOCK_RESCHEDULE_STATUSES)[number];
@@ -1,25 +1,25 @@
1
- /** Formato do header de template oficial (Meta Cloud API). */
2
- export type TemplateHeaderFormatDto = 'TEXT' | 'IMAGE' | 'VIDEO' | 'DOCUMENT';
3
-
4
- /** Resposta do upload de mídia para header de template (Meta + storage opcional). */
5
- export interface UploadTemplateHeaderMediaResponseDto {
6
- headerHandle: string;
7
- messagingMediaId: string;
8
- mimeType: string;
9
- sizeBytes: number;
10
- /** URL pública opcional persistida no storage da plataforma. */
11
- previewUrl?: string;
12
- }
13
-
14
- /** URL temporária da Meta (GET /{media-id}) para pré-visualização. */
15
- export interface TemplateHeaderMediaPreviewUrlResponseDto {
16
- mediaId: string;
17
- url: string;
18
- mimeType: string;
19
- }
20
-
21
- /** Bytes da mídia de header para proxy de pré-visualização. */
22
- export interface TemplateHeaderMediaPreviewBinaryResponseDto {
23
- mimeType: string;
24
- fileBase64: string;
25
- }
1
+ /** Formato do header de template oficial (Meta Cloud API). */
2
+ export type TemplateHeaderFormatDto = 'TEXT' | 'IMAGE' | 'VIDEO' | 'DOCUMENT';
3
+
4
+ /** Resposta do upload de mídia para header de template (Meta + storage opcional). */
5
+ export interface UploadTemplateHeaderMediaResponseDto {
6
+ headerHandle: string;
7
+ messagingMediaId: string;
8
+ mimeType: string;
9
+ sizeBytes: number;
10
+ /** URL pública opcional persistida no storage da plataforma. */
11
+ previewUrl?: string;
12
+ }
13
+
14
+ /** URL temporária da Meta (GET /{media-id}) para pré-visualização. */
15
+ export interface TemplateHeaderMediaPreviewUrlResponseDto {
16
+ mediaId: string;
17
+ url: string;
18
+ mimeType: string;
19
+ }
20
+
21
+ /** Bytes da mídia de header para proxy de pré-visualização. */
22
+ export interface TemplateHeaderMediaPreviewBinaryResponseDto {
23
+ mimeType: string;
24
+ fileBase64: string;
25
+ }
@@ -1,45 +1,45 @@
1
- import type { TemplateCategoryDto, TemplateMetaStatusDto } from './campaign-status.dto';
2
- import type { TemplateHeaderFormatDto } from './campaign-template-header-media.dto';
3
-
4
- export type { TemplateHeaderFormatDto };
5
-
6
- export interface CampaignTemplateDto {
7
- id: string;
8
- tenantId: string;
9
- name: string;
10
- languageCode: string;
11
- category: TemplateCategoryDto | null;
12
- metaTemplateName: string | null;
13
- metaTemplateId: string | null;
14
- metaStatus: TemplateMetaStatusDto | null;
15
- headerText: string | null;
16
- headerFormat?: TemplateHeaderFormatDto | null;
17
- headerMediaHandle?: string | null;
18
- headerMediaId?: string | null;
19
- headerMediaPreviewUrl?: string | null;
20
- bodyText: string;
21
- footerText: string | null;
22
- buttons: Record<string, unknown>[] | null;
23
- isOfficial: boolean;
24
- metaSyncedAt: string | null;
25
- createdAt: string;
26
- updatedAt: string;
27
- }
28
-
29
- export interface CampaignTemplateListResponseDto {
30
- items: CampaignTemplateDto[];
31
- }
32
-
33
- export interface TemplateVariablePreviewDto {
34
- variableName: string;
35
- sampleValue: string;
36
- }
37
-
38
- export interface TemplatePreviewRequestDto {
39
- bodyText: string;
40
- variables: TemplateVariablePreviewDto[];
41
- }
42
-
43
- export interface TemplatePreviewResponseDto {
44
- previewText: string;
45
- }
1
+ import type { TemplateCategoryDto, TemplateMetaStatusDto } from './campaign-status.dto';
2
+ import type { TemplateHeaderFormatDto } from './campaign-template-header-media.dto';
3
+
4
+ export type { TemplateHeaderFormatDto };
5
+
6
+ export interface CampaignTemplateDto {
7
+ id: string;
8
+ tenantId: string;
9
+ name: string;
10
+ languageCode: string;
11
+ category: TemplateCategoryDto | null;
12
+ metaTemplateName: string | null;
13
+ metaTemplateId: string | null;
14
+ metaStatus: TemplateMetaStatusDto | null;
15
+ headerText: string | null;
16
+ headerFormat?: TemplateHeaderFormatDto | null;
17
+ headerMediaHandle?: string | null;
18
+ headerMediaId?: string | null;
19
+ headerMediaPreviewUrl?: string | null;
20
+ bodyText: string;
21
+ footerText: string | null;
22
+ buttons: Record<string, unknown>[] | null;
23
+ isOfficial: boolean;
24
+ metaSyncedAt: string | null;
25
+ createdAt: string;
26
+ updatedAt: string;
27
+ }
28
+
29
+ export interface CampaignTemplateListResponseDto {
30
+ items: CampaignTemplateDto[];
31
+ }
32
+
33
+ export interface TemplateVariablePreviewDto {
34
+ variableName: string;
35
+ sampleValue: string;
36
+ }
37
+
38
+ export interface TemplatePreviewRequestDto {
39
+ bodyText: string;
40
+ variables: TemplateVariablePreviewDto[];
41
+ }
42
+
43
+ export interface TemplatePreviewResponseDto {
44
+ previewText: string;
45
+ }
@@ -1,80 +1,80 @@
1
- import type { TemplateCategoryDto } from './campaign-status.dto';
2
- import type { TemplateHeaderFormatDto } from './campaign-template-header-media.dto';
3
-
4
- export type { TemplateHeaderFormatDto };
5
-
6
- export interface CreateCampaignRequestDto {
7
- name: string;
8
- templateId?: string;
9
- isOfficial?: boolean;
10
- audienceFilters?: Record<string, unknown>[];
11
- delayBetweenSendsMs?: number;
12
- scheduledAt?: string;
13
- }
14
-
15
- export interface UpdateCampaignRequestDto {
16
- name?: string;
17
- templateId?: string;
18
- audienceFilters?: Record<string, unknown>[];
19
- delayBetweenSendsMs?: number;
20
- scheduledAt?: string;
21
- }
22
-
23
- export interface CreateCampaignTemplateRequestDto {
24
- name: string;
25
- languageCode?: string;
26
- category?: TemplateCategoryDto;
27
- bodyText: string;
28
- headerText?: string;
29
- headerFormat?: TemplateHeaderFormatDto;
30
- headerMediaHandle?: string;
31
- /** Numeric Meta messaging media id from header upload (required to send media headers). */
32
- headerMediaId?: string;
33
- headerMediaPreviewUrl?: string;
34
- headerMediaMimeType?: string;
35
- headerMediaSizeBytes?: number;
36
- footerText?: string;
37
- isOfficial?: boolean;
38
- buttons?: Record<string, unknown>[];
39
- }
40
-
41
- export interface StartCampaignRequestDto {
42
- campaignId: string;
43
- }
44
-
45
- export interface PauseCampaignRequestDto {
46
- campaignId: string;
47
- }
48
-
49
- export interface ResumeCampaignRequestDto {
50
- campaignId: string;
51
- }
52
-
53
- export interface CancelCampaignRequestDto {
54
- campaignId: string;
55
- reason?: string;
56
- }
57
-
58
- export interface ConfirmCampaignPaymentRequestDto {
59
- campaignId: string;
60
- billingId: string;
61
- }
62
-
63
- export interface PopulateRecipientsRequestDto {
64
- campaignId: string;
65
- }
66
-
67
- export interface PopulateRecipientsResponseDto {
68
- count: number;
69
- }
70
-
71
- export interface CampaignCostEstimateRequestDto {
72
- campaignId: string;
73
- }
74
-
75
- export interface CampaignCostEstimateResponseDto {
76
- totalRecipients: number;
77
- costPerMessageCents: number;
78
- estimatedTotalCents: number;
79
- category: TemplateCategoryDto;
80
- }
1
+ import type { TemplateCategoryDto } from './campaign-status.dto';
2
+ import type { TemplateHeaderFormatDto } from './campaign-template-header-media.dto';
3
+
4
+ export type { TemplateHeaderFormatDto };
5
+
6
+ export interface CreateCampaignRequestDto {
7
+ name: string;
8
+ templateId?: string;
9
+ isOfficial?: boolean;
10
+ audienceFilters?: Record<string, unknown>[];
11
+ delayBetweenSendsMs?: number;
12
+ scheduledAt?: string;
13
+ }
14
+
15
+ export interface UpdateCampaignRequestDto {
16
+ name?: string;
17
+ templateId?: string;
18
+ audienceFilters?: Record<string, unknown>[];
19
+ delayBetweenSendsMs?: number;
20
+ scheduledAt?: string;
21
+ }
22
+
23
+ export interface CreateCampaignTemplateRequestDto {
24
+ name: string;
25
+ languageCode?: string;
26
+ category?: TemplateCategoryDto;
27
+ bodyText: string;
28
+ headerText?: string;
29
+ headerFormat?: TemplateHeaderFormatDto;
30
+ headerMediaHandle?: string;
31
+ /** Numeric Meta messaging media id from header upload (required to send media headers). */
32
+ headerMediaId?: string;
33
+ headerMediaPreviewUrl?: string;
34
+ headerMediaMimeType?: string;
35
+ headerMediaSizeBytes?: number;
36
+ footerText?: string;
37
+ isOfficial?: boolean;
38
+ buttons?: Record<string, unknown>[];
39
+ }
40
+
41
+ export interface StartCampaignRequestDto {
42
+ campaignId: string;
43
+ }
44
+
45
+ export interface PauseCampaignRequestDto {
46
+ campaignId: string;
47
+ }
48
+
49
+ export interface ResumeCampaignRequestDto {
50
+ campaignId: string;
51
+ }
52
+
53
+ export interface CancelCampaignRequestDto {
54
+ campaignId: string;
55
+ reason?: string;
56
+ }
57
+
58
+ export interface ConfirmCampaignPaymentRequestDto {
59
+ campaignId: string;
60
+ billingId: string;
61
+ }
62
+
63
+ export interface PopulateRecipientsRequestDto {
64
+ campaignId: string;
65
+ }
66
+
67
+ export interface PopulateRecipientsResponseDto {
68
+ count: number;
69
+ }
70
+
71
+ export interface CampaignCostEstimateRequestDto {
72
+ campaignId: string;
73
+ }
74
+
75
+ export interface CampaignCostEstimateResponseDto {
76
+ totalRecipients: number;
77
+ costPerMessageCents: number;
78
+ estimatedTotalCents: number;
79
+ category: TemplateCategoryDto;
80
+ }
@@ -1,15 +1,5 @@
1
1
  import { ApiPropertyOptional } from '@nestjs/swagger';
2
- import {
3
- IsIn,
4
- IsInt,
5
- IsNotEmpty,
6
- IsObject,
7
- IsOptional,
8
- IsString,
9
- IsUUID,
10
- Max,
11
- Min,
12
- } from 'class-validator';
2
+ import { IsIn, IsInt, IsNotEmpty, IsObject, IsOptional, IsString, Max, Min } from 'class-validator';
13
3
 
14
4
  export const FISCAL_DOCUMENT_TYPES = ['nfe', 'nfce', 'nfse'] as const;
15
5
  export type FiscalDocumentType = (typeof FISCAL_DOCUMENT_TYPES)[number];
@@ -43,11 +33,6 @@ export class EnqueueNfeDto {
43
33
  @IsString()
44
34
  @IsOptional()
45
35
  referenceId?: string;
46
-
47
- @ApiPropertyOptional({ description: 'Paciente vinculado (UUID)' })
48
- @IsOptional()
49
- @IsUUID()
50
- patientId?: string;
51
36
  }
52
37
 
53
38
  export class ListFiscalDocumentsQueryDto {
@@ -66,11 +51,6 @@ export class ListFiscalDocumentsQueryDto {
66
51
  @IsString()
67
52
  referenceId?: string;
68
53
 
69
- @ApiPropertyOptional({ description: 'Busca por paciente (UUID)' })
70
- @IsOptional()
71
- @IsUUID()
72
- patientId?: string;
73
-
74
54
  @ApiPropertyOptional({ description: 'Busca por ACBR ID (parcial ou completo)' })
75
55
  @IsOptional()
76
56
  @IsString()
@@ -105,7 +85,6 @@ export type FiscalDocumentListItemDto = {
105
85
  documentType: FiscalDocumentType;
106
86
  companyCpfCnpj: string;
107
87
  referenceId: string | null;
108
- patientId: string | null;
109
88
  acbrId: string | null;
110
89
  status: FiscalDocumentStatusDto;
111
90
  lastError: string | null;
@@ -1,10 +1,10 @@
1
- export * from './create-patient.dto';
2
- export * from './update-patient.dto';
3
- export * from './patient-history-entry.dto';
4
- export * from './list-patients-query.dto';
5
- export * from './list-patients-filters.dto';
6
- export * from './upsert-patient-by-phone.dto';
7
- export * from './patient-status.dto';
8
- export * from './patient-document-response.dto';
9
- export * from './update-patient-document.dto';
10
-
1
+ export * from './create-patient.dto';
2
+ export * from './update-patient.dto';
3
+ export * from './patient-history-entry.dto';
4
+ export * from './list-patients-query.dto';
5
+ export * from './list-patients-filters.dto';
6
+ export * from './upsert-patient-by-phone.dto';
7
+ export * from './patient-status.dto';
8
+ export * from './patient-document-response.dto';
9
+ export * from './update-patient-document.dto';
10
+
@@ -1,45 +1,45 @@
1
- import {
2
- hostnameMatchesTenantSlug,
3
- resolveTenantScopedFrontendBaseUrl,
4
- } from './anamnesis-public-frontend-url';
5
-
6
- describe('resolveTenantScopedFrontendBaseUrl', () => {
7
- it('keeps tenant-specific tychat.app host', () => {
8
- expect(
9
- resolveTenantScopedFrontendBaseUrl(
10
- 'clinica_sampaio',
11
- 'https://clinica-sampaio.tychat.app',
12
- ),
13
- ).toBe('https://clinica-sampaio.tychat.app');
14
- });
15
-
16
- it('scopes shared QA host with tenant subdomain', () => {
17
- expect(
18
- resolveTenantScopedFrontendBaseUrl('clinica_sampaio', 'https://qa.tychat.app'),
19
- ).toBe('https://clinica-sampaio.qa.tychat.app');
20
- });
21
-
22
- it('keeps custom domain unchanged', () => {
23
- expect(
24
- resolveTenantScopedFrontendBaseUrl(
25
- 'clinica_sampaio',
26
- 'https://app.minhaclinica.com.br',
27
- ),
28
- ).toBe('https://app.minhaclinica.com.br');
29
- });
30
-
31
- it('uses slug fallback when host is empty', () => {
32
- expect(resolveTenantScopedFrontendBaseUrl('homolog', '')).toBe(
33
- 'https://homolog.tychat.app',
34
- );
35
- });
36
- });
37
-
38
- describe('hostnameMatchesTenantSlug', () => {
39
- it('matches first DNS label to slug segment', () => {
40
- expect(hostnameMatchesTenantSlug('clinica-sampaio.qa.tychat.app', 'clinica_sampaio')).toBe(
41
- true,
42
- );
43
- expect(hostnameMatchesTenantSlug('qa.tychat.app', 'clinica_sampaio')).toBe(false);
44
- });
45
- });
1
+ import {
2
+ hostnameMatchesTenantSlug,
3
+ resolveTenantScopedFrontendBaseUrl,
4
+ } from './anamnesis-public-frontend-url';
5
+
6
+ describe('resolveTenantScopedFrontendBaseUrl', () => {
7
+ it('keeps tenant-specific tychat.app host', () => {
8
+ expect(
9
+ resolveTenantScopedFrontendBaseUrl(
10
+ 'clinica_sampaio',
11
+ 'https://clinica-sampaio.tychat.app',
12
+ ),
13
+ ).toBe('https://clinica-sampaio.tychat.app');
14
+ });
15
+
16
+ it('scopes shared QA host with tenant subdomain', () => {
17
+ expect(
18
+ resolveTenantScopedFrontendBaseUrl('clinica_sampaio', 'https://qa.tychat.app'),
19
+ ).toBe('https://clinica-sampaio.qa.tychat.app');
20
+ });
21
+
22
+ it('keeps custom domain unchanged', () => {
23
+ expect(
24
+ resolveTenantScopedFrontendBaseUrl(
25
+ 'clinica_sampaio',
26
+ 'https://app.minhaclinica.com.br',
27
+ ),
28
+ ).toBe('https://app.minhaclinica.com.br');
29
+ });
30
+
31
+ it('uses slug fallback when host is empty', () => {
32
+ expect(resolveTenantScopedFrontendBaseUrl('homolog', '')).toBe(
33
+ 'https://homolog.tychat.app',
34
+ );
35
+ });
36
+ });
37
+
38
+ describe('hostnameMatchesTenantSlug', () => {
39
+ it('matches first DNS label to slug segment', () => {
40
+ expect(hostnameMatchesTenantSlug('clinica-sampaio.qa.tychat.app', 'clinica_sampaio')).toBe(
41
+ true,
42
+ );
43
+ expect(hostnameMatchesTenantSlug('qa.tychat.app', 'clinica_sampaio')).toBe(false);
44
+ });
45
+ });