evo360-types 1.3.570 → 1.3.578

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 (58) hide show
  1. package/dist/apps/evo-crm/lead/zod-schemas.d.ts +18 -9
  2. package/dist/apps/evo-csq/zod-schemas.d.ts +12 -12
  3. package/dist/apps/evo-finops/zod-schemas.d.ts +18 -18
  4. package/dist/apps/evo-med/calendar/zod-schemas.d.ts +535 -0
  5. package/dist/apps/evo-med/calendar/zod-schemas.js +52 -1
  6. package/dist/apps/evo-med/calendar/zod-schemas.ts +55 -0
  7. package/dist/apps/evo-med/dic/zod-schemas.d.ts +2 -2
  8. package/dist/apps/evo-med/insurance/zod-schemas.d.ts +9 -0
  9. package/dist/apps/evo-med/people/zod-schemas.d.ts +63 -45
  10. package/dist/apps/evo-notifications/quick-reply-action/zod-schemas.d.ts +3 -0
  11. package/dist/apps/evo-notifications/quick-reply-action/zod-schemas.js +3 -0
  12. package/dist/apps/evo-notifications/quick-reply-action/zod-schemas.ts +3 -0
  13. package/dist/apps/evo-notifications/zod-schemas.d.ts +69 -39
  14. package/dist/apps/evo-task/zod-schemas.d.ts +69 -69
  15. package/dist/apps/evo-task/zod-schemas.js +8 -0
  16. package/dist/apps/evo-task/zod-schemas.ts +8 -0
  17. package/dist/apps/evo-telemedicine/zod-schemas.d.ts +1567 -0
  18. package/dist/apps/evo-telemedicine/zod-schemas.js +709 -0
  19. package/dist/apps/evo-telemedicine/zod-schemas.ts +794 -0
  20. package/dist/apps/evo-tenant/zod-schemas.d.ts +94 -0
  21. package/dist/apps/evo-tenant/zod-schemas.js +29 -1
  22. package/dist/apps/evo-tenant/zod-schemas.ts +32 -1
  23. package/dist/apps/hub-automation/zod-schemas.d.ts +172 -172
  24. package/dist/apps/shared/zod-schemas.d.ts +3 -0
  25. package/dist/apps/shared/zod-schemas.js +2 -0
  26. package/dist/apps/shared/zod-schemas.ts +2 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +2 -0
  29. package/dist/index.ts +2 -0
  30. package/dist/types/evo-finops/common/contract.d.ts +8 -0
  31. package/dist/types/evo-finops/common/contract.js +7 -0
  32. package/dist/types/evo-finops/common/contract.ts +7 -0
  33. package/dist/types/evo-med/calendar/index.d.ts +104 -0
  34. package/dist/types/evo-med/calendar/index.js +66 -1
  35. package/dist/types/evo-med/calendar/index.ts +146 -0
  36. package/dist/types/evo-notifications/index.d.ts +13 -0
  37. package/dist/types/evo-notifications/index.js +6 -0
  38. package/dist/types/evo-notifications/index.ts +13 -0
  39. package/dist/types/evo-reports/index.d.ts +27 -0
  40. package/dist/types/evo-reports/index.ts +28 -0
  41. package/dist/types/evo-task/index.d.ts +1 -0
  42. package/dist/types/evo-task/index.js +8 -0
  43. package/dist/types/evo-task/index.ts +8 -0
  44. package/dist/types/evo-telemedicine/fb_collections.d.ts +16 -0
  45. package/dist/types/evo-telemedicine/fb_collections.js +38 -0
  46. package/dist/types/evo-telemedicine/fb_collections.ts +37 -0
  47. package/dist/types/evo-telemedicine/index.d.ts +1137 -0
  48. package/dist/types/evo-telemedicine/index.js +387 -0
  49. package/dist/types/evo-telemedicine/index.ts +1299 -0
  50. package/dist/types/evo-tenant/index.d.ts +45 -0
  51. package/dist/types/evo-tenant/index.js +20 -1
  52. package/dist/types/evo-tenant/index.ts +65 -0
  53. package/dist/types/shared/external-links.d.ts +6 -0
  54. package/dist/types/shared/external-links.js +6 -0
  55. package/dist/types/shared/external-links.ts +6 -0
  56. package/dist/types/shared/index.d.ts +1 -0
  57. package/dist/types/shared/index.ts +2 -0
  58. package/package.json +1 -1
@@ -0,0 +1,1299 @@
1
+ export * from "./fb_collections";
2
+ import type { IFireDoc, IFireGlobalDoc } from "../shared";
3
+ import type { ITenantModuleEntitlement } from "../evo-tenant";
4
+
5
+ // ======================================================
6
+ // evo-telemedicine (feat-160 — Telemedicina integrada)
7
+ //
8
+ // Contratos compartilhados entre `functions-telemedicine`, `packages/model`,
9
+ // o web-app (painel) e o SPA `web-virtual-care`. Shapes seguem
10
+ // `hub-medica-docs/features/feat-160-telemedicine-platform-mvp/data-model.md`.
11
+ //
12
+ // Regras que os tipos carregam:
13
+ // - nada de PII do paciente (nome/telefone/e-mail/DOB), token do provider,
14
+ // capability `/qr`, cookie, URL assinada ou texto de transcrição nos docs;
15
+ // - segredos de grant existem só como hash/HMAC (`secret_hash`);
16
+ // - eventos são append-only e a `metadata` é allowlist sem PII/conteúdo médico;
17
+ // - o acesso exige entitlement do tenant AND rollout operacional AND permissão.
18
+ //
19
+ // Convenção de datas: docs Firestore usam `Date` (como o resto do repo — o zod
20
+ // converte Timestamp via `zFirestoreDateSchema`); DTOs HTTP usam ISO 8601 em
21
+ // `string` (JSON não transporta Date).
22
+ // ======================================================
23
+
24
+ // ----- Permissões (spec §9.3)
25
+ export const EvoTelemedicinePermissions = {
26
+ /** Ver estado e dados operacionais básicos. */
27
+ Read: "evo_telemedicine_read",
28
+ /** Preparar, admitir, entrar como médico e encerrar. */
29
+ Host: "evo_telemedicine_host",
30
+ /** Solicitar/iniciar/parar gravação. */
31
+ Record: "evo_telemedicine_record",
32
+ /** Listar e baixar gravações/transcrições. */
33
+ ArtifactsRead: "evo_telemedicine_artifacts_read",
34
+ /** Alterar defaults permitidos do tenant; reatribuir host. */
35
+ Configure: "evo_telemedicine_configure",
36
+ /** Consultar trilha detalhada (events). */
37
+ AuditRead: "evo_telemedicine_audit_read",
38
+ } as const;
39
+
40
+ export type EvoTelemedicinePermissions =
41
+ (typeof EvoTelemedicinePermissions)[keyof typeof EvoTelemedicinePermissions];
42
+
43
+ // ----- Constantes de integração
44
+ /** `action_type` do quick-reply `/qr` que redireciona para `/api/public/v1/enter/{cap}` (redirect puro, reexecutável). */
45
+ export const TELEMEDICINE_JOIN_ACTION_TYPE = "telemedicine_join";
46
+
47
+ /** Handler do lembrete/CTA de entrada — igual a `TaskAutoHandlerEnum.TelemedicineJoinReminder`. */
48
+ export const TELEMEDICINE_JOIN_REMINDER_HANDLER = "telemedicine_join_reminder";
49
+
50
+ /**
51
+ * Defaults de janela do MVP (spec §8.2/§8.3/D23) — configuráveis só em nível de
52
+ * plataforma. Em minutos, salvo indicação.
53
+ */
54
+ export const TELEMEDICINE_WINDOW_DEFAULTS = {
55
+ /** Médico prepara/entra desde N minutos antes do início previsto. */
56
+ doctor_access_before_minutes: 30,
57
+ /** Paciente abre o fluxo desde N minutos antes do início previsto. */
58
+ patient_access_before_minutes: 15,
59
+ /** Se nunca iniciou: expira N minutos após o fim previsto. */
60
+ unused_expires_after_end_minutes: 120,
61
+ /** Reconexão após última presença do médico. */
62
+ reconnect_grace_minutes: 10,
63
+ /** Code one-time de exchange (`launch_code`). */
64
+ launch_code_ttl_minutes: 5,
65
+ /** Access da sessão pública do browser (`browser_session`), renovável de forma rotativa. */
66
+ browser_session_ttl_minutes: 15,
67
+ /** Verificação de identidade: N falhas na janela bloqueiam o grant. */
68
+ identity_max_failures: 5,
69
+ identity_failure_window_minutes: 15,
70
+ identity_block_minutes: 30,
71
+ /** URL assinada de artefato. */
72
+ signed_url_max_minutes: 5,
73
+ /** Bits aleatórios mínimos de capability/code. */
74
+ secret_min_bits: 128,
75
+ } as const;
76
+
77
+ // ----- Enums (espelhados à mão em apps/evo-telemedicine/zod-schemas.ts)
78
+
79
+ /** Estado clínico da teleconsulta (spec §10). Presença é separada (`ParticipantState`). */
80
+ export const TeleconsultationStatusEnum = {
81
+ /** Vínculo criado, janela fechada. */
82
+ Scheduled: "scheduled",
83
+ /** Janela aberta, nenhum encontro ativo. */
84
+ Open: "open",
85
+ /** Ao menos uma sessão física ativa ou presença clínica em curso. */
86
+ Live: "live",
87
+ /** Consulta iniciada, médico sem presença dentro da tolerância. */
88
+ Reconnecting: "reconnecting",
89
+ /** Encerramento em andamento; novos joins proibidos. */
90
+ Ending: "ending",
91
+ Ended: "ended",
92
+ Cancelled: "cancelled",
93
+ Expired: "expired",
94
+ } as const;
95
+ export type TeleconsultationStatus =
96
+ (typeof TeleconsultationStatusEnum)[keyof typeof TeleconsultationStatusEnum];
97
+
98
+ /** Terminais: nunca ressuscitam (webhook atrasado, reschedule, join). */
99
+ export const TELECONSULTATION_TERMINAL_STATUSES: readonly TeleconsultationStatus[] = [
100
+ "ended",
101
+ "cancelled",
102
+ "expired",
103
+ ] as const;
104
+
105
+ export const WaitingPolicyEnum = {
106
+ /** Default. Médico admite manualmente. */
107
+ ManualAdmit: "manual_admit",
108
+ /** Entrada automática só com médico `connected` e `identity_level = strong`; nunca em sala vazia. */
109
+ AutoWhenDoctorPresent: "auto_when_doctor_present",
110
+ } as const;
111
+ export type WaitingPolicy = (typeof WaitingPolicyEnum)[keyof typeof WaitingPolicyEnum];
112
+
113
+ export const TeleconsultationModeEnum = {
114
+ AudioVideo: "audio_video",
115
+ AudioOnlyAllowed: "audio_only_allowed",
116
+ } as const;
117
+ export type TeleconsultationMode =
118
+ (typeof TeleconsultationModeEnum)[keyof typeof TeleconsultationModeEnum];
119
+
120
+ export const TeleconsultationProviderEnum = {
121
+ RealtimeKit: "realtimekit",
122
+ } as const;
123
+ export type TeleconsultationProvider =
124
+ (typeof TeleconsultationProviderEnum)[keyof typeof TeleconsultationProviderEnum];
125
+
126
+ export const ParticipantRoleEnum = {
127
+ Doctor: "doctor",
128
+ Patient: "patient",
129
+ } as const;
130
+ export type ParticipantRole = (typeof ParticipantRoleEnum)[keyof typeof ParticipantRoleEnum];
131
+
132
+ /** Presença — separada do estado clínico da teleconsulta. */
133
+ export const ParticipantStateEnum = {
134
+ Invited: "invited",
135
+ Verified: "verified",
136
+ Waiting: "waiting",
137
+ Connected: "connected",
138
+ Disconnected: "disconnected",
139
+ Left: "left",
140
+ } as const;
141
+ export type ParticipantState =
142
+ (typeof ParticipantStateEnum)[keyof typeof ParticipantStateEnum];
143
+
144
+ /** Subconjunto de `ParticipantState` que o cliente pode reportar em `POST /presence`. */
145
+ export const PresenceStateEnum = {
146
+ Waiting: "waiting",
147
+ Connected: "connected",
148
+ Disconnected: "disconnected",
149
+ Left: "left",
150
+ } as const;
151
+ export type PresenceState = (typeof PresenceStateEnum)[keyof typeof PresenceStateEnum];
152
+
153
+ /**
154
+ * Nível de verificação da identidade do paciente (spec §8.3): `strong` = nome +
155
+ * DOB conferidos; `weak` = só nome (paciente sem DOB) — proíbe `auto_admit`.
156
+ */
157
+ export const IdentityLevelEnum = {
158
+ None: "none",
159
+ Weak: "weak",
160
+ Strong: "strong",
161
+ } as const;
162
+ export type IdentityLevel = (typeof IdentityLevelEnum)[keyof typeof IdentityLevelEnum];
163
+
164
+ export const RecordingStatusEnum = {
165
+ Idle: "idle",
166
+ ConsentPending: "consent_pending",
167
+ Recording: "recording",
168
+ Stopping: "stopping",
169
+ Failed: "failed",
170
+ } as const;
171
+ export type RecordingStatus = (typeof RecordingStatusEnum)[keyof typeof RecordingStatusEnum];
172
+
173
+ export const TranscriptionStatusEnum = {
174
+ NotRequested: "not_requested",
175
+ Queued: "queued",
176
+ Processing: "processing",
177
+ Ready: "ready",
178
+ Failed: "failed",
179
+ Deleted: "deleted",
180
+ } as const;
181
+ export type TranscriptionStatus =
182
+ (typeof TranscriptionStatusEnum)[keyof typeof TranscriptionStatusEnum];
183
+
184
+ export const ConsentSubjectEnum = {
185
+ Terms: "terms",
186
+ Privacy: "privacy",
187
+ Recording: "recording",
188
+ Transcription: "transcription",
189
+ } as const;
190
+ export type ConsentSubject = (typeof ConsentSubjectEnum)[keyof typeof ConsentSubjectEnum];
191
+
192
+ export const ConsentDecisionEnum = {
193
+ Accepted: "accepted",
194
+ Declined: "declined",
195
+ Withdrawn: "withdrawn",
196
+ } as const;
197
+ export type ConsentDecision = (typeof ConsentDecisionEnum)[keyof typeof ConsentDecisionEnum];
198
+
199
+ export const ConsentActorTypeEnum = {
200
+ Patient: "patient",
201
+ Doctor: "doctor",
202
+ } as const;
203
+ export type ConsentActorType =
204
+ (typeof ConsentActorTypeEnum)[keyof typeof ConsentActorTypeEnum];
205
+
206
+ export const ArtifactTypeEnum = {
207
+ RecordingAudioVideo: "recording_audio_video",
208
+ RecordingAudio: "recording_audio",
209
+ Transcript: "transcript",
210
+ } as const;
211
+ export type ArtifactType = (typeof ArtifactTypeEnum)[keyof typeof ArtifactTypeEnum];
212
+
213
+ export const ArtifactStatusEnum = {
214
+ Pending: "pending",
215
+ Processing: "processing",
216
+ Ready: "ready",
217
+ Failed: "failed",
218
+ Deleting: "deleting",
219
+ Deleted: "deleted",
220
+ } as const;
221
+ export type ArtifactStatus = (typeof ArtifactStatusEnum)[keyof typeof ArtifactStatusEnum];
222
+
223
+ export const AccessGrantKindEnum = {
224
+ /** Capability de 128 bits no link `/qr`; reexecutável na janela da teleconsulta. */
225
+ QrCapability: "qr_capability",
226
+ /** Code one-time de exchange (5 min) — médico (`/launch`) ou filho do `/enter`. */
227
+ LaunchCode: "launch_code",
228
+ /** Sessão do browser (cookie `__session`), 15 min, rotativa. */
229
+ BrowserSession: "browser_session",
230
+ /**
231
+ * feat-160 F7 — `kind` de uma ENTRADA A MAIS no `grant-index`, apontando
232
+ * para o MESMO grant `qr_capability`, para o link do calendário (ICS)
233
+ * resolver o caminho da teleconsulta (T56/P1).
234
+ *
235
+ * ⚠️ NENHUM grant tem este kind: `ITeleconsultationAccessGrant.kind` nunca
236
+ * vale `ics_capability`. O poder de entrar continua amarrado ao
237
+ * `secret_hash` do grant, que é o HMAC do `cap` e não do `ics_cap`. Está no
238
+ * mesmo enum porque é o `kind` do índice e o índice é endereçado pelo mesmo
239
+ * espaço de nomes — não para virar um grant.
240
+ */
241
+ IcsCapability: "ics_capability",
242
+ } as const;
243
+ export type AccessGrantKind = (typeof AccessGrantKindEnum)[keyof typeof AccessGrantKindEnum];
244
+
245
+ export const AccessGrantStateEnum = {
246
+ Active: "active",
247
+ Consumed: "consumed",
248
+ Blocked: "blocked",
249
+ Revoked: "revoked",
250
+ Expired: "expired",
251
+ } as const;
252
+ export type AccessGrantState =
253
+ (typeof AccessGrantStateEnum)[keyof typeof AccessGrantStateEnum];
254
+
255
+ /** Canal pelo qual o CTA de acesso do paciente é (re)enviado. `manual` só devolve a URL mascarada (smoke). */
256
+ export const AccessSendChannelKindEnum = {
257
+ WhatsApp: "whatsapp",
258
+ Email: "email",
259
+ Manual: "manual",
260
+ } as const;
261
+ export type AccessSendChannelKind =
262
+ (typeof AccessSendChannelKindEnum)[keyof typeof AccessSendChannelKindEnum];
263
+
264
+ export const EndedByTypeEnum = {
265
+ User: "user",
266
+ System: "system",
267
+ } as const;
268
+ export type EndedByType = (typeof EndedByTypeEnum)[keyof typeof EndedByTypeEnum];
269
+
270
+ export const TeleconsultationEventActorTypeEnum = {
271
+ User: "user",
272
+ Patient: "patient",
273
+ System: "system",
274
+ Provider: "provider",
275
+ } as const;
276
+ export type TeleconsultationEventActorType =
277
+ (typeof TeleconsultationEventActorTypeEnum)[keyof typeof TeleconsultationEventActorTypeEnum];
278
+
279
+ export const TeleconsultationEventSourceEnum = {
280
+ Api: "api",
281
+ Webhook: "webhook",
282
+ AppointmentReconciler: "appointment_reconciler",
283
+ RetentionJob: "retention_job",
284
+ /** `telemed_sweep`: expiração de não usadas, timeout de reconexão, outbox órfã. */
285
+ Sweep: "sweep",
286
+ } as const;
287
+ export type TeleconsultationEventSource =
288
+ (typeof TeleconsultationEventSourceEnum)[keyof typeof TeleconsultationEventSourceEnum];
289
+
290
+ /** Tipos de evento append-only (data-model §5 + acréscimos do plan). */
291
+ export const TeleconsultationEventTypeEnum = {
292
+ TeleconsultationCreated: "teleconsultation.created",
293
+ TeleconsultationRescheduled: "teleconsultation.rescheduled",
294
+ TeleconsultationCancelled: "teleconsultation.cancelled",
295
+ TeleconsultationExpired: "teleconsultation.expired",
296
+ /** Alteração tardia na agenda com consulta já iniciada — tratamento operacional, não apaga o encontro. */
297
+ TeleconsultationDiverged: "teleconsultation.diverged",
298
+ AccessCreated: "access.created",
299
+ /**
300
+ * feat-160 F13/D31 — alguém REVELOU/copiou o link do paciente no painel. Um
301
+ * evento por revelação (é o ato do usuário que fica registrado, não a
302
+ * abertura da tela). Distinto de `access.created`, que é a emissão do grant.
303
+ */
304
+ AccessLinkRevealed: "access.link_revealed",
305
+ AccessRevoked: "access.revoked",
306
+ AccessExchanged: "access.exchanged",
307
+ AccessVerificationFailed: "access.verification_failed",
308
+ AccessBlocked: "access.blocked",
309
+ HostClaimed: "host.claimed",
310
+ HostReassigned: "host.reassigned",
311
+ WaitingPolicyChanged: "waiting_policy.changed",
312
+ DoctorPrepared: "doctor.prepared",
313
+ DoctorJoined: "doctor.joined",
314
+ DoctorDisconnected: "doctor.disconnected",
315
+ DoctorRejoined: "doctor.rejoined",
316
+ DoctorLeft: "doctor.left",
317
+ PatientVerified: "patient.verified",
318
+ PatientWaiting: "patient.waiting",
319
+ PatientAdmitted: "patient.admitted",
320
+ PatientRejected: "patient.rejected",
321
+ PatientJoined: "patient.joined",
322
+ PatientDisconnected: "patient.disconnected",
323
+ PatientRejoined: "patient.rejoined",
324
+ PatientLeft: "patient.left",
325
+ RecordingConsentRequested: "recording.consent_requested",
326
+ RecordingConsentAccepted: "recording.consent_accepted",
327
+ RecordingConsentDeclined: "recording.consent_declined",
328
+ RecordingConsentWithdrawn: "recording.consent_withdrawn",
329
+ RecordingStarted: "recording.started",
330
+ RecordingStopped: "recording.stopped",
331
+ RecordingFailed: "recording.failed",
332
+ TranscriptionQueued: "transcription.queued",
333
+ TranscriptionReady: "transcription.ready",
334
+ TranscriptionFailed: "transcription.failed",
335
+ TranscriptionDeleted: "transcription.deleted",
336
+ ConsultationStarted: "consultation.started",
337
+ ConsultationReconnecting: "consultation.reconnecting",
338
+ ConsultationEnded: "consultation.ended",
339
+ ArtifactAccessed: "artifact.accessed",
340
+ ArtifactDeleted: "artifact.deleted",
341
+ EntitlementDenied: "entitlement.denied",
342
+ RolloutDenied: "rollout.denied",
343
+ PermissionDenied: "permission.denied",
344
+ /** Evento do provider desconhecido/ignorado — registrado, não processado. */
345
+ WebhookObserved: "webhook.observed",
346
+ } as const;
347
+ export type TeleconsultationEventType =
348
+ (typeof TeleconsultationEventTypeEnum)[keyof typeof TeleconsultationEventTypeEnum];
349
+
350
+ /** Outbox durável do webhook (data-model §6): claim/lease como o task-runner. */
351
+ export const WebhookEventStatusEnum = {
352
+ Pending: "pending",
353
+ Claimed: "claimed",
354
+ Completed: "completed",
355
+ Failed: "failed",
356
+ } as const;
357
+ export type WebhookEventStatus =
358
+ (typeof WebhookEventStatusEnum)[keyof typeof WebhookEventStatusEnum];
359
+
360
+ export const LegalTextStatusEnum = {
361
+ Draft: "draft",
362
+ Published: "published",
363
+ Retired: "retired",
364
+ } as const;
365
+ export type LegalTextStatus = (typeof LegalTextStatusEnum)[keyof typeof LegalTextStatusEnum];
366
+
367
+ export const TranscriptionProviderEnum = {
368
+ None: "none",
369
+ RealtimeKit: "realtimekit",
370
+ } as const;
371
+ export type TranscriptionProvider =
372
+ (typeof TranscriptionProviderEnum)[keyof typeof TranscriptionProviderEnum];
373
+
374
+ // ----- Value objects
375
+
376
+ export interface ITeleconsultationEndedBy {
377
+ type: EndedByType;
378
+ /** userId quando `type = user`. */
379
+ id?: string;
380
+ /** Ex.: `doctor_end`, `reconnect_timeout`, `appointment_cancelled`, `unused_expired`, `emergency_kill`. */
381
+ reason: string;
382
+ }
383
+
384
+ /** Snapshot do entitlement no momento da criação — o gate vivo continua sendo `tenant.modules`. */
385
+ export interface ITeleconsultationEntitlementSnapshot {
386
+ version: number;
387
+ capabilities: Record<string, boolean | number | string>;
388
+ }
389
+
390
+ export interface ITeleconsultationEventActor {
391
+ type: TeleconsultationEventActorType;
392
+ /** userId, participantId ou id opaco do provider — nunca nome/telefone. */
393
+ id?: string;
394
+ }
395
+
396
+ /** Metadata allowlisted — sem PII e sem conteúdo médico. */
397
+ export type TeleconsultationEventMetadata = Record<string, string | number | boolean>;
398
+
399
+ // ======================================================
400
+ // Documentos (por tenant)
401
+ // ======================================================
402
+
403
+ /** `.../teleconsultations/{teleconsultationId}` (data-model §1). */
404
+ export interface ITeleconsultation extends IFireDoc {
405
+ calendar_id: string;
406
+ appointment_id: string;
407
+ patient_id: string;
408
+ scheduled_professional_id?: string;
409
+ /** Claim transacional (D22): primeiro elegível vence; reassignment exige `configure` + motivo + evento. */
410
+ claimed_host_user_id?: string;
411
+ claimed_host_at?: Date | null;
412
+ mode: TeleconsultationMode;
413
+ status: TeleconsultationStatus;
414
+ waiting_policy: WaitingPolicy;
415
+ scheduled_start_at: Date;
416
+ scheduled_end_at: Date;
417
+ doctor_access_from: Date;
418
+ patient_access_from: Date;
419
+ unused_expires_at: Date;
420
+ reconnect_until?: Date | null;
421
+ started_at?: Date | null;
422
+ ended_at?: Date | null;
423
+ ended_by?: ITeleconsultationEndedBy | null;
424
+ provider: TeleconsultationProvider;
425
+ /** Id opaco do meeting no provider — nunca vai em external link nem no SPA. */
426
+ provider_meeting_id?: string;
427
+ active_provider_session_id?: string;
428
+ recording_status: RecordingStatus;
429
+ /**
430
+ * Gravação — campos de OPERAÇÃO escritos por F8 e lidos pelo `GET /session`
431
+ * (feat-160). Nenhum id do provedor entra aqui: o id da gravação no
432
+ * provedor vive em `private/provider.active_provider_recording_id` (T52).
433
+ */
434
+ recording_request_id?: string;
435
+ /** Versão do texto legal `recording` referenciada na solicitação (D21). */
436
+ recording_terms_version?: string;
437
+ recording_requested_at?: Date | null;
438
+ /** `userId` do médico que solicitou. */
439
+ recording_requested_by?: string;
440
+ recording_started_at?: Date | null;
441
+ recording_stopped_at?: Date | null;
442
+ /** Código estável, sem PII, quando `recording_status === 'failed'`. */
443
+ recording_failure_code?: string | null;
444
+ /** Segmento corrente (1..n) — mais de uma sessão física = vários segmentos. */
445
+ recording_segment?: number;
446
+ transcription_status: TranscriptionStatus;
447
+ /**
448
+ * feat-160 F10 — desmonte do meeting no provedor ainda pendente; o sweep lê e
449
+ * limpa. Escrito FORA do comando transacional de propósito: não é estado
450
+ * clínico e um `version + 1` por housekeeping faria o `expected_version` que
451
+ * o painel acabou de ler virar 409.
452
+ */
453
+ provider_teardown_pending?: boolean;
454
+ /** feat-160 F10 — até quando o metadado sensível sobrevive à retenção. */
455
+ metadata_retention_until?: Date | null;
456
+ entitlement_snapshot: ITeleconsultationEntitlementSnapshot;
457
+ /**
458
+ * Geração no `appointment-index`: incrementa quando o appointment volta a ser
459
+ * telemedicina depois de um estado terminal (nova teleconsulta, novo id).
460
+ */
461
+ generation: number;
462
+ /** Contagem de reagendamentos (SEQUENCE do ICS). */
463
+ reschedule_count?: number;
464
+ /** Última versão/data do appointment aplicada — descarta evento de reconciliação antigo. */
465
+ last_reconciled_appointment_version?: number | string | null;
466
+ last_reconciled_at?: Date | null;
467
+ /**
468
+ * feat-160 F18 — quando a passada de bilhetagem do sweep pode contar esta
469
+ * consulta. Carimbado por `endForAll` (`ended_at + 15 min`): o `ready` das
470
+ * gravações só chega pelo webhook DEPOIS do fim, então contar no `end`
471
+ * classificaria toda consulta gravada como "sem gravação".
472
+ */
473
+ usage_metering_due_at?: Date | null;
474
+ /**
475
+ * Quando o evento de uso foi emitido. Presente = já contada; é o que torna a
476
+ * bilhetagem idempotente no doc (a dedup em BigQuery, por `target.entityId`,
477
+ * é a segunda camada) e a trilha para reconciliar contra o BigQuery — o
478
+ * publish é best-effort e pode se perder.
479
+ */
480
+ usage_metered_at?: Date | null;
481
+ /** Concorrência otimista: todo comando de estado é transação + `version`. */
482
+ version: number;
483
+ created_at: Date;
484
+ updated_at: Date;
485
+ }
486
+
487
+ /**
488
+ * `.../teleconsultations/{teleconsultationId}/private/provider` (data-model §7;
489
+ * threat model P1/T52).
490
+ *
491
+ * Tudo o que identifica o encontro NO PROVEDOR mora aqui, e só aqui: o subdoc
492
+ * `private/**` é negado a todo mundo nas rules, inclusive super admin, enquanto
493
+ * `teleconsultations/{id}` e `participants/{id}` são legíveis pelo FE com
494
+ * `evo_telemedicine_read`. Um id de provedor na mão do browser é exatamente o
495
+ * que T52 proíbe — e `runTeleconsultationCommand` LANÇA se um patch do doc
496
+ * principal carregar `provider_meeting_id`/`active_provider_session_id`, para
497
+ * que o invariante não dependa de alguém lembrar em cada PR.
498
+ */
499
+ export interface ITeleconsultationPrivateProvider {
500
+ readonly id: string;
501
+ tenant: string;
502
+ teleconsultation_id: string;
503
+ provider: TeleconsultationProvider;
504
+ /** Id do meeting no provider — nunca sai deste doc para o FE nem para o SPA. */
505
+ provider_meeting_id?: string;
506
+ active_provider_session_id?: string;
507
+ /** Sessões físicas já vistas (mais de uma = segmentos do mesmo encontro). */
508
+ provider_session_ids?: string[];
509
+ /** Id do participante no provider, por id LÓGICO (`doctor`/`patient`). */
510
+ provider_participant_ids?: Record<string, string>;
511
+ /**
512
+ * Gravação ativa no provedor. O artefato guarda o mesmo valor em
513
+ * `provider_artifact_id` (é por ele que o webhook casa), mas o `stop` precisa
514
+ * do id sem depender de qual segmento é o corrente.
515
+ */
516
+ active_provider_recording_id?: string | null;
517
+ created_at?: Date | null;
518
+ updated_at?: Date | null;
519
+ }
520
+
521
+ /**
522
+ * Campos que o doc PRINCIPAL da teleconsulta jamais carrega — a asserção do
523
+ * `runTeleconsultationCommand` e do teste de sanidade itera esta lista.
524
+ */
525
+ export const TELECONSULTATION_PROVIDER_ONLY_FIELDS = [
526
+ "provider_meeting_id",
527
+ "active_provider_session_id",
528
+ ] as const;
529
+
530
+ /** `.../participants/{participantId}` (data-model §2). Id lógico estável; reconexão reutiliza o mesmo participante. */
531
+ export interface ITeleconsultationParticipant extends IFireDoc {
532
+ teleconsultation_id: string;
533
+ role: ParticipantRole;
534
+ /** userId quando `role = doctor`. */
535
+ actor_user_id?: string;
536
+ /** patientId quando `role = patient`. */
537
+ patient_id?: string;
538
+ /** Id do participante no provider (pode ser renovado; o id lógico é o doc). */
539
+ provider_participant_id?: string;
540
+ /** `custom_participant_id` opaco enviado ao provider — nunca o patientId. */
541
+ opaque_custom_participant_id: string;
542
+ state: ParticipantState;
543
+ identity_level?: IdentityLevel;
544
+ admitted_at?: Date | null;
545
+ connected_at?: Date | null;
546
+ last_seen_at?: Date | null;
547
+ disconnected_at?: Date | null;
548
+ reconnect_count: number;
549
+ created_at: Date;
550
+ updated_at: Date;
551
+ }
552
+
553
+ export interface ITeleconsultationConsentEvidence {
554
+ ui_locale: string;
555
+ /** Hash da sessão pública — nunca o cookie/sid bruto. */
556
+ session_id_hash: string;
557
+ }
558
+
559
+ /** `.../consents/{consentId}` (data-model §3). Guarda versão/hash do texto, não o texto. */
560
+ export interface ITeleconsultationConsent extends IFireDoc {
561
+ teleconsultation_id: string;
562
+ subject: ConsentSubject;
563
+ decision: ConsentDecision;
564
+ terms_version: string;
565
+ /** `content_hash` do legal text vigente na solicitação. */
566
+ terms_content_hash?: string;
567
+ participant_id: string;
568
+ actor_type: ConsentActorType;
569
+ occurred_at: Date;
570
+ correlation_id: string;
571
+ /** Vincula aceite/recusa/retirada de gravação à solicitação específica (data-model §11). */
572
+ recording_request_id?: string;
573
+ evidence: ITeleconsultationConsentEvidence;
574
+ }
575
+
576
+ /** `.../artifacts/{artifactId}` (data-model §4). Download só por endpoint `artifacts_read` + URL assinada ≤ 5 min. */
577
+ export interface ITeleconsultationArtifact extends IFireDoc {
578
+ teleconsultation_id: string;
579
+ type: ArtifactType;
580
+ /** Segmento (1..n) — mais de uma sessão física do provider = vários segmentos da mesma teleconsulta. */
581
+ segment: number;
582
+ status: ArtifactStatus;
583
+ provider_artifact_id?: string;
584
+ provider_session_id?: string;
585
+ recording_request_id?: string;
586
+ storage_bucket?: string;
587
+ storage_object?: string;
588
+ content_type?: string;
589
+ size_bytes?: number;
590
+ checksum?: string;
591
+ started_at?: Date | null;
592
+ ended_at?: Date | null;
593
+ /** Retenção: o sweep deleta o objeto e só então marca `deleted` (auditoria preservada). */
594
+ expires_at?: Date | null;
595
+ /**
596
+ * feat-160 F10 — quando o CONTEÚDO foi apagado. O doc sobrevive (status
597
+ * `deleted`) porque a auditoria da deleção é preservada (D13).
598
+ */
599
+ deleted_at?: Date | null;
600
+ error_code?: string;
601
+ created_at: Date;
602
+ updated_at: Date;
603
+ }
604
+
605
+ /** `.../events/{eventId}` (data-model §5) — append-only. */
606
+ export interface ITeleconsultationEvent extends IFireDoc {
607
+ teleconsultation_id: string;
608
+ type: TeleconsultationEventType;
609
+ occurred_at: Date;
610
+ actor: ITeleconsultationEventActor;
611
+ source: TeleconsultationEventSource;
612
+ correlation_id: string;
613
+ causation_id?: string;
614
+ idempotency_key?: string;
615
+ from_status?: TeleconsultationStatus | string;
616
+ to_status?: TeleconsultationStatus | string;
617
+ metadata?: TeleconsultationEventMetadata;
618
+ }
619
+
620
+ /**
621
+ * `.../webhook-events/{providerEventId}` (data-model §6). Dedup + outbox durável:
622
+ * a recepção grava `pending` ANTES do 2xx; o worker faz claim, processa
623
+ * idempotentemente e marca `completed`. Sem payload bruto — só campos allowlisted.
624
+ */
625
+ export interface ITeleconsultationWebhookEvent extends IFireDoc {
626
+ teleconsultation_id?: string;
627
+ provider: TeleconsultationProvider;
628
+ /** Id do evento no provider (= doc id; ex.: `rtk-uuid`). */
629
+ provider_event_id: string;
630
+ event_type: string;
631
+ /** Hash do corpo bruto (integridade/dedup) — nunca o corpo. */
632
+ payload_hash?: string;
633
+ /** Campos allowlisted do payload (ids opacos, status) — sem PII. */
634
+ sanitized_payload?: Record<string, string | number | boolean | null>;
635
+ status: WebhookEventStatus;
636
+ received_at: Date;
637
+ claimed_at?: Date | null;
638
+ claimed_by?: string;
639
+ lease_until?: Date | null;
640
+ processed_at?: Date | null;
641
+ attempt: number;
642
+ next_attempt_at?: Date | null;
643
+ result?: string;
644
+ error_code?: string;
645
+ correlation_id: string;
646
+ /** TTL nativo do Firestore. */
647
+ expires_at: Date;
648
+ }
649
+
650
+ /** `.../access-grants/{grantId}` (data-model §7). Segredo nunca persistido bruto. */
651
+ export interface ITeleconsultationAccessGrant extends IFireDoc {
652
+ teleconsultation_id: string;
653
+ role: ParticipantRole;
654
+ kind: AccessGrantKind;
655
+ /** HMAC (chave rotacionável) do segredo; comparação constant-time. */
656
+ secret_hash: string;
657
+ /** Hash do destinatário (telefone/e-mail) — evita reenvio para contato diferente do agendamento. */
658
+ recipient_ref_hash?: string;
659
+ actor_user_id?: string;
660
+ participant_id?: string;
661
+ state: AccessGrantState;
662
+ expires_at: Date;
663
+ consumed_at?: Date | null;
664
+ revoked_at?: Date | null;
665
+ revoked_by?: string;
666
+ last_used_at?: Date | null;
667
+ failed_attempts: number;
668
+ failed_window_started_at?: Date | null;
669
+ blocked_until?: Date | null;
670
+ /** `launch_code` filho de `qr_capability`; `browser_session` filho do code/capability. */
671
+ parent_grant_id?: string;
672
+ /** Canal do envio que originou o grant (`qr_capability`). */
673
+ channel_kind?: AccessSendChannelKind;
674
+ /** Doc `quick-reply-actions/{actionId}` ligado ao grant — cancelado no revoke. */
675
+ quick_reply_action_id?: string;
676
+ version: number;
677
+ created_at: Date;
678
+ updated_at: Date;
679
+ }
680
+
681
+ /**
682
+ * `.../appointment-index/{calendarId}__{appointmentId}` (plan §2). Unicidade da
683
+ * teleconsulta ativa por appointment; o reconciliador transaciona sobre este doc.
684
+ */
685
+ export interface ITelemedicineAppointmentIndex extends IFireDoc {
686
+ calendar_id: string;
687
+ appointment_id: string;
688
+ active_teleconsultation_id: string | null;
689
+ generation: number;
690
+ last_reconciled_appointment_version?: number | string | null;
691
+ /**
692
+ * feat-160 F3 — instante do write do appointment já aplicado. A marca d'água
693
+ * mora AQUI, e não só na teleconsulta, porque o índice sobrevive ao que ela
694
+ * precisa cobrir: um `denied` não cria doc nenhum e um terminal deixa de ser
695
+ * a teleconsulta ativa. Sem isto, um evento antigo redelivered depois de um
696
+ * `denied` seria reavaliado como novo.
697
+ */
698
+ last_reconciled_at?: Date | null;
699
+ /**
700
+ * Última teleconsulta deste appointment, ativa ou não. Gravada quando o
701
+ * índice é liberado (terminal) para o painel continuar mostrando a
702
+ * teleconsulta expirada/encerrada em vez de "não tem teleconsulta". NÃO é
703
+ * fonte de verdade de ativo — isso é `active_teleconsultation_id`.
704
+ */
705
+ last_teleconsultation_id?: string | null;
706
+ updated_at: Date;
707
+ }
708
+
709
+ // ======================================================
710
+ // Documentos de plataforma (`platform/evo-telemedicine/**`, só backend)
711
+ // ======================================================
712
+
713
+ /**
714
+ * `platform/evo-telemedicine/legal-texts/{subject}-{locale}-{version}` (data-model §9).
715
+ * Publish explícito, nunca overwrite. NÃO estende `IFireGlobalDoc`: aqui `version`
716
+ * é a versão do TEXTO (string, ex.: `v1`), não o contador de concorrência.
717
+ */
718
+ export interface ITelemedicineLegalText {
719
+ readonly id: string;
720
+ subject: ConsentSubject;
721
+ locale: string;
722
+ version: string;
723
+ title: string;
724
+ /** HTML sanitizado; placeholders allowlisted (`{{clinic_name}}`, `{{clinic_contact}}`). */
725
+ content_html: string;
726
+ content_hash: string;
727
+ published_at?: Date | null;
728
+ effective_at?: Date | null;
729
+ status: LegalTextStatus;
730
+ placeholders?: string[];
731
+ created_at?: Date | null;
732
+ updated_at?: Date | null;
733
+ }
734
+
735
+ /** `platform/evo-telemedicine/config/rollout` (plan §2). Deny se ausente; cache 30 s. */
736
+ export interface ITelemedicineRollout extends IFireGlobalDoc {
737
+ create_enabled: boolean;
738
+ join_enabled: boolean;
739
+ recording_enabled: boolean;
740
+ /** Só pode ser `true` em produção após o gate de D12. */
741
+ transcription_enabled: boolean;
742
+ /** Bloqueia tudo, inclusive consultas ao vivo (D19). */
743
+ emergency_kill: boolean;
744
+ updated_at: Date;
745
+ updated_by: string;
746
+ }
747
+
748
+ /** `platform/evo-telemedicine/config/transcription` (plan F9). */
749
+ export interface ITelemedicineTranscriptionConfig extends IFireGlobalDoc {
750
+ provider: TranscriptionProvider;
751
+ /** `realtimekit` com `false` só funciona em staging. */
752
+ production_gate_approved: boolean;
753
+ approved_by?: string;
754
+ approved_at?: Date | null;
755
+ }
756
+
757
+ /** `platform/evo-telemedicine/grant-index/{hmac(cap)}` (plan §2). Só resolve o path; o grant tenant-scoped é a verdade. */
758
+ export interface ITelemedicineGrantIndex extends IFireGlobalDoc {
759
+ tenant: string;
760
+ teleconsultation_id: string;
761
+ grant_id: string;
762
+ /**
763
+ * Qual segredo esta entrada resolve. Ausente = a capability do `/qr`
764
+ * (comportamento original); `ics_capability` = o segredo derivado do link do
765
+ * calendário, que aponta para o MESMO grant `qr_capability` (F7).
766
+ */
767
+ kind?: AccessGrantKind;
768
+ /** TTL nativo. */
769
+ expires_at: Date;
770
+ }
771
+
772
+ // ======================================================
773
+ // Auditoria de entitlement (`.../entitlement-audit/{id}`, data-model §8)
774
+ // ======================================================
775
+
776
+ export const EntitlementAuditActionEnum = {
777
+ Set: "set",
778
+ Override: "override",
779
+ Revoke: "revoke",
780
+ } as const;
781
+ export type EntitlementAuditAction =
782
+ (typeof EntitlementAuditActionEnum)[keyof typeof EntitlementAuditActionEnum];
783
+
784
+ export interface IEntitlementAuditActor {
785
+ type: "user" | "system" | "script";
786
+ id?: string;
787
+ }
788
+
789
+ /** Fora do doc do tenant, como pede o data-model. `before/after` são a projeção sanitizada (sem preço/contrato). */
790
+ export interface ITelemedicineEntitlementAudit extends IFireDoc {
791
+ module: string;
792
+ action: EntitlementAuditAction;
793
+ before?: ITenantModuleEntitlement | null;
794
+ after: ITenantModuleEntitlement;
795
+ actor: IEntitlementAuditActor;
796
+ reason: string;
797
+ occurred_at: Date;
798
+ correlation_id?: string;
799
+ }
800
+
801
+ // ======================================================
802
+ // Códigos de erro (plan §4 — `{ ok: false, error: '<CODE>', message? }` com status estável)
803
+ // ======================================================
804
+
805
+ export const TelemedicineErrorCode = {
806
+ /** 403 — entitlement ausente/inativo (sem bypass de super admin). */
807
+ TenantModuleDisabled: "TENANT_MODULE_DISABLED",
808
+ /** 403 — kill switch operacional (`create|join|recording|transcription`). */
809
+ RolloutDisabled: "ROLLOUT_DISABLED",
810
+ /** 403 — `emergency_kill` ligado. */
811
+ EmergencyKill: "EMERGENCY_KILL",
812
+ /** 403 — RBAC. */
813
+ PermissionDenied: "PERMISSION_DENIED",
814
+ /** 403 — capability do entitlement ausente/false (`recording`, `transcription`, `auto_admit`). */
815
+ CapabilityDisabled: "CAPABILITY_DISABLED",
816
+ /** 404 — teleconsulta/artefato/grant inexistente (ou genérico, em rota pública). */
817
+ NotFound: "NOT_FOUND",
818
+ /** 409 — fora da janela (antes de `*_access_from` ou após `unused_expires_at`). */
819
+ AccessWindowClosed: "ACCESS_WINDOW_CLOSED",
820
+ /** 409 — outro médico já fez o claim de host. */
821
+ HostClaimedByOther: "HOST_CLAIMED_BY_OTHER",
822
+ /** 409 — comando exige médico claimant presente (admit/end/recording). */
823
+ HostRequired: "HOST_REQUIRED",
824
+ /** 409 — termos/privacidade (ou gravação) não aceitos na versão vigente. */
825
+ ConsentRequired: "CONSENT_REQUIRED",
826
+ /** 409 — paciente ainda não verificou identidade. */
827
+ IdentityRequired: "IDENTITY_REQUIRED",
828
+ /** 401 — verificação de identidade falhou (erro neutro: não diz qual campo). */
829
+ IdentityVerificationFailed: "IDENTITY_VERIFICATION_FAILED",
830
+ /** 429 — grant bloqueado por excesso de falhas. */
831
+ GrantBlocked: "GRANT_BLOCKED",
832
+ /** 401 — grant/code inválido, consumido, expirado ou revogado (genérico por design). */
833
+ GrantInvalid: "GRANT_INVALID",
834
+ /** 401 — cookie `__session` ausente/inválido/expirado. */
835
+ SessionInvalid: "SESSION_INVALID",
836
+ /** 410 — estado terminal (`ended|cancelled|expired`). */
837
+ TeleconsultationTerminal: "TELECONSULTATION_TERMINAL",
838
+ /** 409 — transição de estado não permitida (spec §10). */
839
+ InvalidTransition: "INVALID_TRANSITION",
840
+ /** 409 — `expected_version` divergente. */
841
+ VersionConflict: "VERSION_CONFLICT",
842
+ /** 409 — política de espera só muda até o paciente entrar. */
843
+ WaitingPolicyLocked: "WAITING_POLICY_LOCKED",
844
+ /** 409 — já existe gravação ativa/pendente. */
845
+ RecordingActive: "RECORDING_ACTIVE",
846
+ /** 502 — provider falhou ao gravar (não derruba a consulta). */
847
+ RecordingFailed: "RECORDING_FAILED",
848
+ /** 403 — transcrição em produção sem gate aprovado (D12). */
849
+ TranscriptionGateClosed: "TRANSCRIPTION_GATE_CLOSED",
850
+ /** 409 — artefato não está `ready`. */
851
+ ArtifactNotReady: "ARTIFACT_NOT_READY",
852
+ /** 502 — erro do provider de mídia. */
853
+ ProviderError: "PROVIDER_ERROR",
854
+ /** 429 — rate limit por IP-hash/grant. */
855
+ RateLimited: "RATE_LIMITED",
856
+ /** 400 — body/params inválidos. */
857
+ ValidationError: "VALIDATION_ERROR",
858
+ } as const;
859
+
860
+ export type TelemedicineErrorCode =
861
+ (typeof TelemedicineErrorCode)[keyof typeof TelemedicineErrorCode];
862
+
863
+ export interface ITelemedicineErrorResponse {
864
+ ok: false;
865
+ error: TelemedicineErrorCode;
866
+ message?: string;
867
+ error_id?: string;
868
+ }
869
+
870
+ // ======================================================
871
+ // DTOs públicos — HTTP (plan §4). Datas em ISO 8601 (`string`).
872
+ // ======================================================
873
+
874
+ export interface ITeleconsultationParticipantDto {
875
+ participant_id: string;
876
+ role: ParticipantRole;
877
+ state: ParticipantState;
878
+ identity_level?: IdentityLevel;
879
+ admitted_at?: string | null;
880
+ connected_at?: string | null;
881
+ last_seen_at?: string | null;
882
+ disconnected_at?: string | null;
883
+ reconnect_count: number;
884
+ }
885
+
886
+ /** Estado do CTA de acesso do paciente, para a recepção — destinatário só mascarado. */
887
+ export interface ITeleconsultationAccessSummaryDto {
888
+ grant_id?: string;
889
+ grant_state?: AccessGrantState;
890
+ channel_kind?: AccessSendChannelKind;
891
+ /** Ex.: `+55 11 9****-1234`, `l***@dominio.com`. */
892
+ recipient_masked?: string;
893
+ last_sent_at?: string | null;
894
+ expires_at?: string | null;
895
+ }
896
+
897
+ /** `GET /v1/appointments/:calendarId/:appointmentId/teleconsultation` (telemed_admin). */
898
+ export interface ITeleconsultationSummaryDto {
899
+ id: string;
900
+ calendar_id: string;
901
+ appointment_id: string;
902
+ patient_id: string;
903
+ scheduled_professional_id?: string;
904
+ claimed_host_user_id?: string;
905
+ claimed_host_at?: string | null;
906
+ mode: TeleconsultationMode;
907
+ status: TeleconsultationStatus;
908
+ waiting_policy: WaitingPolicy;
909
+ scheduled_start_at: string;
910
+ scheduled_end_at: string;
911
+ doctor_access_from: string;
912
+ patient_access_from: string;
913
+ unused_expires_at: string;
914
+ reconnect_until?: string | null;
915
+ started_at?: string | null;
916
+ ended_at?: string | null;
917
+ ended_by?: ITeleconsultationEndedBy | null;
918
+ recording_status: RecordingStatus;
919
+ transcription_status: TranscriptionStatus;
920
+ entitlement_snapshot: ITeleconsultationEntitlementSnapshot;
921
+ /** Capabilities EFETIVAS agora (entitlement vivo AND rollout): `recording`, `transcription`, `auto_admit`. */
922
+ capabilities_now: Record<string, boolean>;
923
+ generation: number;
924
+ version: number;
925
+ participants?: ITeleconsultationParticipantDto[];
926
+ access?: ITeleconsultationAccessSummaryDto;
927
+ }
928
+
929
+ /** `POST /v1/teleconsultations/:id/launch` → `{ launch_url }` (fragment `#code=`; SPA faz o exchange). */
930
+ export interface ILaunchResponseDto {
931
+ ok: true;
932
+ launch_url: string;
933
+ /** Expiração do code one-time (5 min). */
934
+ expires_at: string;
935
+ claimed_host_user_id: string;
936
+ }
937
+
938
+ export interface IRoomSessionWindowDto {
939
+ scheduled_start_at: string;
940
+ scheduled_end_at: string;
941
+ doctor_access_from: string;
942
+ patient_access_from: string;
943
+ unused_expires_at: string;
944
+ reconnect_until?: string | null;
945
+ }
946
+
947
+ export interface IRoomConsentRefDto {
948
+ subject: ConsentSubject;
949
+ terms_version: string;
950
+ }
951
+
952
+ /**
953
+ * Projeção SANITIZADA do estado de gravação no `GET /session` (feat-160 F8).
954
+ * Nunca traz id do provider, path do objeto no GCS nem URL assinada — só o que
955
+ * a UI dos dois papéis precisa.
956
+ */
957
+ export interface IRoomRecordingStateDto {
958
+ /** Mesma origem do `recording_status` do DTO da sessão. */
959
+ status: RecordingStatus;
960
+ /** Início do segmento ativo — única origem do cronômetro; `null` fora de gravação. */
961
+ started_at: string | null;
962
+ /** Idempotência do comando e vínculo do consentimento (T19). */
963
+ recording_request_id?: string;
964
+ /** Versão do texto legal `recording` referenciada na solicitação (D21). */
965
+ terms_version?: string;
966
+ /** Código estável, sem PII, quando `status === 'failed'`. */
967
+ failure_code?: string;
968
+ }
969
+
970
+ /**
971
+ * `GET /api/room/v1/session` — estado SANITIZADO: sem tenant id, sem ids do
972
+ * provider; nome do profissional/clínica só depois da verificação.
973
+ */
974
+ export interface IRoomSessionDto {
975
+ ok: true;
976
+ role: ParticipantRole;
977
+ participant_id: string;
978
+ participant_state: ParticipantState;
979
+ teleconsultation_status: TeleconsultationStatus;
980
+ mode: TeleconsultationMode;
981
+ waiting_policy: WaitingPolicy;
982
+ window: IRoomSessionWindowDto;
983
+ identity_level: IdentityLevel;
984
+ /** Paciente precisa passar por `POST /identity/verify` antes do join. */
985
+ identity_required: boolean;
986
+ /**
987
+ * Quais campos o formulário de identidade deve pedir (spec §8.3): a data de
988
+ * nascimento só é pedida quando EXISTE no cadastro. Para o médico vem sempre
989
+ * `{ birth_date: false }`.
990
+ */
991
+ identity_required_fields: { birth_date: boolean };
992
+ /**
993
+ * Contador do lockout, lido do GRANT raiz — e a ÚNICA fonte dele: o erro do
994
+ * `POST /identity/verify` é neutro e não devolve tentativas nem prazo. Por
995
+ * isso a UI relê a sessão depois de cada falha.
996
+ */
997
+ identity_attempts: { remaining: number; blocked_until: string | null };
998
+ recording_status: RecordingStatus;
999
+ recording: IRoomRecordingStateDto;
1000
+ transcription_status: TranscriptionStatus;
1001
+ /** Textos vigentes que ainda precisam de aceite antes do join. */
1002
+ required_consents: IRoomConsentRefDto[];
1003
+ accepted_consents: IRoomConsentRefDto[];
1004
+ /** Capabilities efetivas para esta sessão (entitlement AND rollout AND papel). */
1005
+ capabilities: Record<string, boolean>;
1006
+ /** Presença do médico; `null` quando não há participante. Atalho de `participants`. */
1007
+ doctor_presence: ParticipantState | null;
1008
+ /** Presença do paciente. Atalho de `participants`. */
1009
+ patient_presence: ParticipantState | null;
1010
+ /** Os dois papéis, com `state`/`disconnected_at`/`reconnect_count`. */
1011
+ participants: ITeleconsultationParticipantDto[];
1012
+ /**
1013
+ * Pacientes em `state === 'waiting'`, e a ponte OBRIGATÓRIA do `POST /admit`:
1014
+ * o `participant_id` de domínio é o literal `'patient'`, enquanto o
1015
+ * `custom_participant_id` devolvido no `POST /join` é hash opaco que nunca
1016
+ * casaria com ele.
1017
+ */
1018
+ waiting_participants: ITeleconsultationParticipantDto[];
1019
+ /**
1020
+ * Máscara "Maria A. d. S." do titular do agendamento. SÓ na sessão do
1021
+ * MÉDICO — o backend nunca a manda ao paciente (T01), e é por isso que é o
1022
+ * único destes campos legitimamente opcional.
1023
+ */
1024
+ patient_display_masked?: string;
1025
+ professional_display_name?: string;
1026
+ clinic_display_name?: string;
1027
+ /** Expiração do access da sessão (15 min, rotativo via `/session/refresh`). */
1028
+ session_expires_at: string;
1029
+ server_time: string;
1030
+ }
1031
+
1032
+ /** `POST /api/room/v1/join` — token do provider fica em MEMÓRIA do SPA; nunca em storage. */
1033
+ export interface IRoomJoinDto {
1034
+ ok: true;
1035
+ provider_token: string;
1036
+ /** Id opaco do meeting (não é o `provider_meeting_id`). */
1037
+ meeting_id_opaque: string;
1038
+ preset: string;
1039
+ participant_id: string;
1040
+ custom_participant_id: string;
1041
+ /** true quando o paciente cai na waiting room (`manual_admit` ou médico ausente). */
1042
+ waiting: boolean;
1043
+ /** Expiração do token do provider, quando conhecida. */
1044
+ token_expires_at?: string;
1045
+ }
1046
+
1047
+ /** `POST /api/room/v1/admit` — o SPA só chama `acceptWaitingRoomRequest` DEPOIS de receber isto. */
1048
+ export interface IAdmissionGrantDto {
1049
+ ok: true;
1050
+ participant_id: string;
1051
+ provider_participant_id: string;
1052
+ admitted_at: string;
1053
+ teleconsultation_status: TeleconsultationStatus;
1054
+ }
1055
+
1056
+ export interface ITeleconsultationArtifactDto {
1057
+ id: string;
1058
+ type: ArtifactType;
1059
+ segment: number;
1060
+ status: ArtifactStatus;
1061
+ content_type?: string;
1062
+ size_bytes?: number;
1063
+ started_at?: string | null;
1064
+ ended_at?: string | null;
1065
+ expires_at?: string | null;
1066
+ error_code?: string;
1067
+ created_at: string;
1068
+ }
1069
+
1070
+ /** `POST /v1/teleconsultations/:id/artifacts/:artifactId/signed-url` — URL ≤ 5 min; evento `artifact.accessed`. */
1071
+ export interface ISignedUrlResponseDto {
1072
+ ok: true;
1073
+ url: string;
1074
+ expires_at: string;
1075
+ }
1076
+
1077
+ export interface ITeleconsultationEventDto {
1078
+ id: string;
1079
+ type: TeleconsultationEventType;
1080
+ occurred_at: string;
1081
+ actor: ITeleconsultationEventActor;
1082
+ source: TeleconsultationEventSource;
1083
+ correlation_id: string;
1084
+ causation_id?: string;
1085
+ from_status?: string;
1086
+ to_status?: string;
1087
+ metadata?: TeleconsultationEventMetadata;
1088
+ }
1089
+
1090
+ /** `GET /v1/teleconsultations/:id/events` — paginação por cursor. */
1091
+ export interface ITeleconsultationEventsPageDto {
1092
+ ok: true;
1093
+ events: ITeleconsultationEventDto[];
1094
+ next_cursor?: string;
1095
+ }
1096
+
1097
+ export interface ITelemedicineLegalTextDto {
1098
+ subject: ConsentSubject;
1099
+ locale: string;
1100
+ version: string;
1101
+ title: string;
1102
+ content_html: string;
1103
+ content_hash: string;
1104
+ effective_at?: string | null;
1105
+ /**
1106
+ * VALORES dos placeholders já resolvidos e filtrados pela allowlist do
1107
+ * próprio doc (`ITelemedicineLegalText.placeholders`, que guarda os NOMES
1108
+ * permitidos). ux-flows §3 exige "usada para {finalidade} e ficará
1109
+ * disponível por {prazo}", e D21 proíbe hardcode disso no bundle.
1110
+ */
1111
+ placeholders?: Record<string, string>;
1112
+ }
1113
+
1114
+ /** `GET /api/room/v1/legal-texts`. */
1115
+ export interface ILegalTextsResponseDto {
1116
+ ok: true;
1117
+ texts: ITelemedicineLegalTextDto[];
1118
+ }
1119
+
1120
+ /** `POST /api/room/v1/identity/verify` — sucesso; falha é `IDENTITY_VERIFICATION_FAILED` neutro. */
1121
+ export interface IIdentityVerifyResponseDto {
1122
+ ok: true;
1123
+ identity_level: IdentityLevel;
1124
+ }
1125
+
1126
+ /**
1127
+ * `POST /api/room/v1/exchange` e `POST /api/room/v1/session/refresh`.
1128
+ *
1129
+ * O corpo NÃO repete o `sid` (está no cookie `__session`, `HttpOnly`,
1130
+ * `Path=/api`) nem o tenant; traz o papel, que é o que permite ao SPA escolher
1131
+ * a tela sem um `GET /session` extra.
1132
+ */
1133
+ export interface IExchangeResponseDto {
1134
+ ok: true;
1135
+ role: ParticipantRole;
1136
+ session_expires_at: string;
1137
+ }
1138
+
1139
+ /** `POST /api/room/v1/participant-token/refresh` — token novo, MESMO participante lógico. */
1140
+ export interface IParticipantTokenRefreshDto {
1141
+ ok: true;
1142
+ provider_token: string;
1143
+ /** Nome do preset no provider (`hm_doctor`, `hm_patient_waiting`, ...). */
1144
+ preset: string;
1145
+ token_expires_at?: string;
1146
+ }
1147
+
1148
+ /**
1149
+ * `POST /v1/teleconsultations/:id/access/send` (feat-160 F7).
1150
+ *
1151
+ * É RECIBO DE TASK, não confirmação de envio: a rota publica a mesma task fina
1152
+ * que a rotina publicaria (`telemedicine_join_reminder`) e responde antes de
1153
+ * existir grant — o executor é quem resolve template, canal e elegibilidade e
1154
+ * emite a capability, uma por perna do fan-out. Por isso NÃO há `grant_id` nem
1155
+ * `expires_at` aqui: forjá-los era mentir para a recepção sobre um acesso que
1156
+ * ainda não existia.
1157
+ */
1158
+ export interface IAccessSendResponseDto {
1159
+ ok: true;
1160
+ task_id: string;
1161
+ /** `true` quando a chave de idempotência colapsou num envio já pedido. */
1162
+ reused: boolean;
1163
+ channel_kind: AccessSendChannelKind;
1164
+ /** Eco do pedido: revogar a capability anterior antes de emitir a nova. */
1165
+ resend: boolean;
1166
+ /** Destinatário mascarado do canal pedido; ausente em `manual`. */
1167
+ recipient_masked?: string;
1168
+ }
1169
+
1170
+ /**
1171
+ * `POST /v1/teleconsultations/:id/access/link` (feat-160 F13; D31).
1172
+ *
1173
+ * **A `url` é CREDENCIAL** (capability de 128 bits do `/qr`), não um
1174
+ * identificador: não tem lugar em store, log, analytics nem storage do
1175
+ * navegador (T01/T04). Nasce nesta resposta, vai para a área de transferência e
1176
+ * morre com o diálogo. Cada revelação é uma chamada nova, de propósito — o que
1177
+ * fica registrado em `access.link_revealed` é o ato do usuário.
1178
+ */
1179
+ export interface IAccessLinkResultDto {
1180
+ ok: true;
1181
+ url: string;
1182
+ grant_id: string;
1183
+ expires_at: string;
1184
+ }
1185
+
1186
+ // ======================================================
1187
+ // Comandos (request bodies). `tenant` sempre do contexto, nunca do body.
1188
+ // ======================================================
1189
+
1190
+ export interface IWaitingPolicyCommand {
1191
+ waiting_policy: WaitingPolicy;
1192
+ expected_version?: number;
1193
+ }
1194
+
1195
+ export interface IReassignHostCommand {
1196
+ to_user_id: string;
1197
+ reason: string;
1198
+ expected_version?: number;
1199
+ }
1200
+
1201
+ export interface IAccessSendCommand {
1202
+ channel_kind: AccessSendChannelKind;
1203
+ /** Revoga a capability anterior antes de emitir a nova. */
1204
+ resend?: boolean;
1205
+ }
1206
+
1207
+ export interface IAccessRevokeCommand {
1208
+ /** Ausente = todos os grants ativos do paciente na teleconsulta. */
1209
+ grant_id?: string;
1210
+ reason: string;
1211
+ }
1212
+
1213
+ export interface IEndCommand {
1214
+ reason?: string;
1215
+ expected_version?: number;
1216
+ }
1217
+
1218
+ /** `POST /api/room/v1/exchange` — code one-time do fragment `#code=`. */
1219
+ export interface IExchangeCommand {
1220
+ code: string;
1221
+ }
1222
+
1223
+ /** `POST /api/room/v1/identity/verify` — DOB normalizada `YYYY-MM-DD`; comparação exata após normalização. */
1224
+ export interface IIdentityVerifyCommand {
1225
+ display_name: string;
1226
+ birth_date?: string;
1227
+ }
1228
+
1229
+ /** `POST /api/room/v1/consents` — termos/privacidade (gravação usa `IRecordingConsentCommand`). */
1230
+ export interface IConsentCommand {
1231
+ subject: ConsentSubject;
1232
+ terms_version: string;
1233
+ decision: Extract<ConsentDecision, "accepted" | "declined">;
1234
+ }
1235
+
1236
+ /**
1237
+ * `POST /api/room/v1/join` — intenção de mídia do preflight ("Entrar somente
1238
+ * com áudio"). Os dois campos são opcionais porque o backend só os REGISTRA
1239
+ * (escolha de preset e `audio_only_allowed`) e ignora o que não vier.
1240
+ */
1241
+ export interface IJoinCommand {
1242
+ audio?: boolean;
1243
+ video?: boolean;
1244
+ }
1245
+
1246
+ /**
1247
+ * `POST /api/room/v1/leave` — saída TEMPORÁRIA (D10); nunca encerra a consulta.
1248
+ *
1249
+ * `reason` é string aberta e o backend só distingue UM valor: `'temporary'` é
1250
+ * saída explícita (`left`), qualquer outro é tratado como queda de rede
1251
+ * (`disconnected`). Ausente = `'temporary'`. Chega pelo `pagehide` com
1252
+ * `fetch(keepalive)`, então pode vir sem `Content-Type`.
1253
+ */
1254
+ export interface ILeaveCommand {
1255
+ reason?: string;
1256
+ }
1257
+
1258
+ /** `POST /api/room/v1/presence`. */
1259
+ export interface IPresenceCommand {
1260
+ state: PresenceState;
1261
+ provider_participant_id?: string;
1262
+ }
1263
+
1264
+ /** `POST /api/room/v1/admit` e `/reject`. */
1265
+ export interface IAdmitCommand {
1266
+ participant_id: string;
1267
+ }
1268
+
1269
+ export interface IRejectCommand {
1270
+ participant_id: string;
1271
+ reason?: string;
1272
+ }
1273
+
1274
+ /** `POST /api/room/v1/recording/request` (médico com `record`) — idempotente por `recording_request_id`. */
1275
+ export interface IRecordingRequestCommand {
1276
+ recording_request_id: string;
1277
+ }
1278
+
1279
+ /** `POST /api/room/v1/recording/accept|decline|withdraw` (paciente) — versão do texto = a da solicitação. */
1280
+ export interface IRecordingConsentCommand {
1281
+ recording_request_id: string;
1282
+ terms_version: string;
1283
+ }
1284
+
1285
+ /** `POST /api/room/v1/recording/stop` (médico) — repetível. */
1286
+ export interface IRecordingStopCommand {
1287
+ recording_request_id?: string;
1288
+ }
1289
+
1290
+ export interface ITelemetryEvent {
1291
+ name: string;
1292
+ at: string;
1293
+ attrs?: Record<string, string | number | boolean>;
1294
+ }
1295
+
1296
+ /** `POST /api/room/v1/telemetry` — nomes allowlisted no backend; sem PII. */
1297
+ export interface ITelemetryCommand {
1298
+ events: ITelemetryEvent[];
1299
+ }