tychat-contracts 1.0.99 → 1.0.101

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.
@@ -22,4 +22,51 @@ export declare class AnalyticsDashboardQueryDto {
22
22
  startDate: string;
23
23
  endDate: string;
24
24
  }
25
+ /** Resumo financeiro agregado a partir de eventos de analytics (pagamentos + faturamento). */
26
+ export declare class AnalyticsDashboardFinancialDto {
27
+ revenue: number;
28
+ averageTicket: number;
29
+ pending: number;
30
+ }
31
+ export declare class AnalyticsDashboardTopProcedureDto {
32
+ name: string;
33
+ count: number;
34
+ }
35
+ /** Média de `message.sent` por hora do relógio durante o expediente configurado. */
36
+ export declare class AnalyticsDashboardMessageHourDto {
37
+ hour: number;
38
+ averageSent: number;
39
+ totalSent: number;
40
+ openOccurrencesInPeriod: number;
41
+ }
42
+ export declare class AnalyticsDashboardMessagePeakDto {
43
+ hour: number;
44
+ totalSent: number;
45
+ averageSent: number;
46
+ }
47
+ /**
48
+ * Resposta enriquecida de `GET /analytics/dashboard` (mantém campos legados `total`, `byEventType`, `byDomain`).
49
+ */
50
+ export declare class AnalyticsDashboardResponseDto {
51
+ appointmentsCreated: number;
52
+ appointmentsFinished: number;
53
+ proceduresScheduled: number;
54
+ proceduresFinished: number;
55
+ satisfactionAverage: number | null;
56
+ noShowRatePercent: number;
57
+ financialSummary: AnalyticsDashboardFinancialDto;
58
+ topProcedures: AnalyticsDashboardTopProcedureDto[];
59
+ messagesByClinicHour: AnalyticsDashboardMessageHourDto[];
60
+ messagesPeakHour: AnalyticsDashboardMessagePeakDto | null;
61
+ clinicTimezone: string;
62
+ total: number;
63
+ byEventType: Array<{
64
+ eventType: string;
65
+ count: number;
66
+ }>;
67
+ byDomain: Array<{
68
+ domain: string;
69
+ count: number;
70
+ }>;
71
+ }
25
72
  //# sourceMappingURL=analytics-query.dto.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analytics-query.dto.d.ts","sourceRoot":"","sources":["../../src/analytics/analytics-query.dto.ts"],"names":[],"mappings":"AAYA,OAAO,EAAwB,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAEhF;;GAEG;AACH,eAAO,MAAM,kBAAkB,2CAA4C,CAAC;AAC5E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE;;GAEG;AACH,qBAAa,uBAAuB;IAMlC,SAAS,EAAE,MAAM,CAAC;IAOlB,OAAO,EAAE,MAAM,CAAC;IAShB,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAS9B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAO3B,IAAI,CAAC,EAAE,MAAM,CAAC;IAOd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,qBAAa,0BAA0B;IAMrC,SAAS,EAAE,MAAM,CAAC;IAOlB,OAAO,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"analytics-query.dto.d.ts","sourceRoot":"","sources":["../../src/analytics/analytics-query.dto.ts"],"names":[],"mappings":"AAYA,OAAO,EAAwB,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAEhF;;GAEG;AACH,eAAO,MAAM,kBAAkB,2CAA4C,CAAC;AAC5E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE;;GAEG;AACH,qBAAa,uBAAuB;IAMlC,SAAS,EAAE,MAAM,CAAC;IAOlB,OAAO,EAAE,MAAM,CAAC;IAShB,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAS9B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAO3B,IAAI,CAAC,EAAE,MAAM,CAAC;IAOd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,qBAAa,0BAA0B;IAMrC,SAAS,EAAE,MAAM,CAAC;IAOlB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,8FAA8F;AAC9F,qBAAa,8BAA8B;IAKzC,OAAO,EAAE,MAAM,CAAC;IAMhB,aAAa,EAAE,MAAM,CAAC;IAMtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,iCAAiC;IAE5C,IAAI,EAAE,MAAM,CAAC;IAGb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oFAAoF;AACpF,qBAAa,gCAAgC;IAE3C,IAAI,EAAE,MAAM,CAAC;IAMb,WAAW,EAAE,MAAM,CAAC;IAGpB,SAAS,EAAE,MAAM,CAAC;IAMlB,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED,qBAAa,gCAAgC;IAE3C,IAAI,EAAE,MAAM,CAAC;IAEE,SAAS,EAAE,MAAM,CAAC;IAElB,WAAW,EAAE,MAAM,CAAC;CACpC;AAED;;GAEG;AACH,qBAAa,6BAA6B;IAExC,mBAAmB,EAAE,MAAM,CAAC;IAG5B,oBAAoB,EAAE,MAAM,CAAC;IAM7B,mBAAmB,EAAE,MAAM,CAAC;IAK5B,kBAAkB,EAAE,MAAM,CAAC;IAO3B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IAMnC,iBAAiB,EAAE,MAAM,CAAC;IAG1B,gBAAgB,EAAE,8BAA8B,CAAC;IAGjD,aAAa,EAAE,iCAAiC,EAAE,CAAC;IAOnD,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IAOzD,gBAAgB,EAAE,gCAAgC,GAAG,IAAI,CAAC;IAM1D,cAAc,EAAE,MAAM,CAAC;IAGvB,KAAK,EAAE,MAAM,CAAC;IAKd,WAAW,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAKzD,QAAQ,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD"}
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.AnalyticsDashboardQueryDto = exports.AnalyticsReportQueryDto = exports.ANALYTICS_GROUP_BY = void 0;
12
+ exports.AnalyticsDashboardResponseDto = exports.AnalyticsDashboardMessagePeakDto = exports.AnalyticsDashboardMessageHourDto = exports.AnalyticsDashboardTopProcedureDto = exports.AnalyticsDashboardFinancialDto = exports.AnalyticsDashboardQueryDto = exports.AnalyticsReportQueryDto = exports.ANALYTICS_GROUP_BY = void 0;
13
13
  const swagger_1 = require("@nestjs/swagger");
14
14
  const class_validator_1 = require("class-validator");
15
15
  const class_transformer_1 = require("class-transformer");
@@ -106,3 +106,186 @@ __decorate([
106
106
  (0, class_validator_1.IsISO8601)(),
107
107
  __metadata("design:type", String)
108
108
  ], AnalyticsDashboardQueryDto.prototype, "endDate", void 0);
109
+ /** Resumo financeiro agregado a partir de eventos de analytics (pagamentos + faturamento). */
110
+ class AnalyticsDashboardFinancialDto {
111
+ revenue;
112
+ averageTicket;
113
+ pending;
114
+ }
115
+ exports.AnalyticsDashboardFinancialDto = AnalyticsDashboardFinancialDto;
116
+ __decorate([
117
+ (0, swagger_1.ApiProperty)({
118
+ description: 'Receita no período: soma de `payment.payed` e `billing.invoice_paid` (valores em metadata.amount)',
119
+ }),
120
+ __metadata("design:type", Number)
121
+ ], AnalyticsDashboardFinancialDto.prototype, "revenue", void 0);
122
+ __decorate([
123
+ (0, swagger_1.ApiProperty)({
124
+ description: 'Ticket médio: revenue / número de eventos pagos (payment.payed + billing.invoice_paid); 0 se não houver pagamentos',
125
+ }),
126
+ __metadata("design:type", Number)
127
+ ], AnalyticsDashboardFinancialDto.prototype, "averageTicket", void 0);
128
+ __decorate([
129
+ (0, swagger_1.ApiProperty)({
130
+ description: 'Valor ainda em aberto no fim do período (exclusive endDate): soma de `payment.created` no intervalo sem `payment.payed` correspondente (mesmo paymentId) com occurredAt anterior a endDate; mais faturas `billing.invoice_created` PENDING/OVERDUE sem `billing.invoice_paid` (mesmo invoiceId). Baseado em eventos de analytics.',
131
+ }),
132
+ __metadata("design:type", Number)
133
+ ], AnalyticsDashboardFinancialDto.prototype, "pending", void 0);
134
+ class AnalyticsDashboardTopProcedureDto {
135
+ name;
136
+ count;
137
+ }
138
+ exports.AnalyticsDashboardTopProcedureDto = AnalyticsDashboardTopProcedureDto;
139
+ __decorate([
140
+ (0, swagger_1.ApiProperty)({ description: 'Nome do procedimento (agendamento)' }),
141
+ __metadata("design:type", String)
142
+ ], AnalyticsDashboardTopProcedureDto.prototype, "name", void 0);
143
+ __decorate([
144
+ (0, swagger_1.ApiProperty)({ description: 'Quantidade de ocorrências no período' }),
145
+ __metadata("design:type", Number)
146
+ ], AnalyticsDashboardTopProcedureDto.prototype, "count", void 0);
147
+ /** Média de `message.sent` por hora do relógio durante o expediente configurado. */
148
+ class AnalyticsDashboardMessageHourDto {
149
+ hour;
150
+ averageSent;
151
+ totalSent;
152
+ openOccurrencesInPeriod;
153
+ }
154
+ exports.AnalyticsDashboardMessageHourDto = AnalyticsDashboardMessageHourDto;
155
+ __decorate([
156
+ (0, swagger_1.ApiProperty)({ description: 'Hora local (0–23) no fuso clinicTimezone' }),
157
+ __metadata("design:type", Number)
158
+ ], AnalyticsDashboardMessageHourDto.prototype, "hour", void 0);
159
+ __decorate([
160
+ (0, swagger_1.ApiProperty)({
161
+ description: 'Média de mensagens enviadas nessa hora: totalSent / openOccurrencesInPeriod (0 se não houve expediente nessa hora no período)',
162
+ }),
163
+ __metadata("design:type", Number)
164
+ ], AnalyticsDashboardMessageHourDto.prototype, "averageSent", void 0);
165
+ __decorate([
166
+ (0, swagger_1.ApiProperty)({ description: 'Total de eventos message.sent nessa hora local, só dentro do horário da clínica' }),
167
+ __metadata("design:type", Number)
168
+ ], AnalyticsDashboardMessageHourDto.prototype, "totalSent", void 0);
169
+ __decorate([
170
+ (0, swagger_1.ApiProperty)({
171
+ description: 'Quantas vezes essa hora calendário integrou o expediente (dias locais no intervalo × slots de opening_hours)',
172
+ }),
173
+ __metadata("design:type", Number)
174
+ ], AnalyticsDashboardMessageHourDto.prototype, "openOccurrencesInPeriod", void 0);
175
+ class AnalyticsDashboardMessagePeakDto {
176
+ hour;
177
+ totalSent;
178
+ averageSent;
179
+ }
180
+ exports.AnalyticsDashboardMessagePeakDto = AnalyticsDashboardMessagePeakDto;
181
+ __decorate([
182
+ (0, swagger_1.ApiProperty)({ description: 'Hora local (0–23) com maior totalSent entre as horas de expediente' }),
183
+ __metadata("design:type", Number)
184
+ ], AnalyticsDashboardMessagePeakDto.prototype, "hour", void 0);
185
+ __decorate([
186
+ (0, swagger_1.ApiProperty)(),
187
+ __metadata("design:type", Number)
188
+ ], AnalyticsDashboardMessagePeakDto.prototype, "totalSent", void 0);
189
+ __decorate([
190
+ (0, swagger_1.ApiProperty)(),
191
+ __metadata("design:type", Number)
192
+ ], AnalyticsDashboardMessagePeakDto.prototype, "averageSent", void 0);
193
+ /**
194
+ * Resposta enriquecida de `GET /analytics/dashboard` (mantém campos legados `total`, `byEventType`, `byDomain`).
195
+ */
196
+ class AnalyticsDashboardResponseDto {
197
+ appointmentsCreated;
198
+ appointmentsFinished;
199
+ proceduresScheduled;
200
+ proceduresFinished;
201
+ satisfactionAverage;
202
+ noShowRatePercent;
203
+ financialSummary;
204
+ topProcedures;
205
+ messagesByClinicHour;
206
+ messagesPeakHour;
207
+ clinicTimezone;
208
+ total;
209
+ byEventType;
210
+ byDomain;
211
+ }
212
+ exports.AnalyticsDashboardResponseDto = AnalyticsDashboardResponseDto;
213
+ __decorate([
214
+ (0, swagger_1.ApiProperty)({ description: 'Agendamentos criados no período (`appointment.created`)' }),
215
+ __metadata("design:type", Number)
216
+ ], AnalyticsDashboardResponseDto.prototype, "appointmentsCreated", void 0);
217
+ __decorate([
218
+ (0, swagger_1.ApiProperty)({ description: 'Agendamentos finalizados no período (`appointment.finished`)' }),
219
+ __metadata("design:type", Number)
220
+ ], AnalyticsDashboardResponseDto.prototype, "appointmentsFinished", void 0);
221
+ __decorate([
222
+ (0, swagger_1.ApiProperty)({
223
+ description: 'Procedimentos agendados: soma dos itens em metadata.procedures em `appointment.created` + vínculos posteriores (`appointment.updated` com reason procedure_added)',
224
+ }),
225
+ __metadata("design:type", Number)
226
+ ], AnalyticsDashboardResponseDto.prototype, "proceduresScheduled", void 0);
227
+ __decorate([
228
+ (0, swagger_1.ApiProperty)({
229
+ description: 'Procedimentos marcados como concluídos no período (`appointment.procedure_finished`)',
230
+ }),
231
+ __metadata("design:type", Number)
232
+ ], AnalyticsDashboardResponseDto.prototype, "proceduresFinished", void 0);
233
+ __decorate([
234
+ (0, swagger_1.ApiProperty)({
235
+ description: 'Média das notas 1–5 em respostas de satisfação (followup booking + pós-atendimento); null se não houver respostas',
236
+ nullable: true,
237
+ }),
238
+ __metadata("design:type", Object)
239
+ ], AnalyticsDashboardResponseDto.prototype, "satisfactionAverage", void 0);
240
+ __decorate([
241
+ (0, swagger_1.ApiProperty)({
242
+ description: 'Taxa de no-show (%): appointment.no_show / appointment.created no período (0 se não houver criações)',
243
+ }),
244
+ __metadata("design:type", Number)
245
+ ], AnalyticsDashboardResponseDto.prototype, "noShowRatePercent", void 0);
246
+ __decorate([
247
+ (0, swagger_1.ApiProperty)({ type: AnalyticsDashboardFinancialDto }),
248
+ __metadata("design:type", AnalyticsDashboardFinancialDto)
249
+ ], AnalyticsDashboardResponseDto.prototype, "financialSummary", void 0);
250
+ __decorate([
251
+ (0, swagger_1.ApiProperty)({ type: [AnalyticsDashboardTopProcedureDto], maxItems: 5 }),
252
+ __metadata("design:type", Array)
253
+ ], AnalyticsDashboardResponseDto.prototype, "topProcedures", void 0);
254
+ __decorate([
255
+ (0, swagger_1.ApiProperty)({
256
+ description: 'Pico / distribuição de mensagens no horário da clínica: médias por hora local (só `message.sent` dentro de opening_hours). Ordenado por hora crescente. Vazio se não houver opening_hours.',
257
+ type: [AnalyticsDashboardMessageHourDto],
258
+ }),
259
+ __metadata("design:type", Array)
260
+ ], AnalyticsDashboardResponseDto.prototype, "messagesByClinicHour", void 0);
261
+ __decorate([
262
+ (0, swagger_1.ApiProperty)({
263
+ description: 'Hora com maior volume de mensagens no expediente; null se não houver dados ou horário configurado',
264
+ nullable: true,
265
+ type: AnalyticsDashboardMessagePeakDto,
266
+ }),
267
+ __metadata("design:type", Object)
268
+ ], AnalyticsDashboardResponseDto.prototype, "messagesPeakHour", void 0);
269
+ __decorate([
270
+ (0, swagger_1.ApiProperty)({
271
+ description: 'Fuso IANA usado para hora local e opening_hours (env ANALYTICS_CLINIC_TIMEZONE ou America/Sao_Paulo)',
272
+ example: 'America/Sao_Paulo',
273
+ }),
274
+ __metadata("design:type", String)
275
+ ], AnalyticsDashboardResponseDto.prototype, "clinicTimezone", void 0);
276
+ __decorate([
277
+ (0, swagger_1.ApiProperty)({ description: 'Total de eventos de analytics no período (legado)' }),
278
+ __metadata("design:type", Number)
279
+ ], AnalyticsDashboardResponseDto.prototype, "total", void 0);
280
+ __decorate([
281
+ (0, swagger_1.ApiProperty)({
282
+ description: 'Contagens por eventType (legado)',
283
+ }),
284
+ __metadata("design:type", Array)
285
+ ], AnalyticsDashboardResponseDto.prototype, "byEventType", void 0);
286
+ __decorate([
287
+ (0, swagger_1.ApiProperty)({
288
+ description: 'Contagens por domínio (prefixo antes do ponto no eventType) (legado)',
289
+ }),
290
+ __metadata("design:type", Array)
291
+ ], AnalyticsDashboardResponseDto.prototype, "byDomain", void 0);
@@ -2,6 +2,6 @@
2
2
  * Enum of all analytic event types emitted by microservices.
3
3
  * Each value follows the pattern: `domain.action`.
4
4
  */
5
- export declare const EVENT_ANALYTIC_TYPES: readonly ["patient.created", "patient.updated", "patient.deleted", "patient.status_changed", "appointment.created", "appointment.updated", "appointment.canceled", "appointment.rescheduled", "appointment.no_show", "appointment.finished", "checkin.effectued", "checkout.effectued", "message.sent", "message.received", "conversation.session_started", "conversation.session_ended", "conversation.transferred_to_human", "followup.abandonment.sent", "followup.appointment_confirmation.sent", "followup.satisfaction_booking.sent", "followup.satisfaction_finished.sent", "followup.no_show_reschedule.sent", "followup.wellness_check.sent", "followup.return_suggestion.sent", "followup.satisfaction_booking.response_received", "followup.satisfaction_finished.response_received", "followup.config_updated", "payment.created", "payment.payed", "payment.refunded", "payment.canceled", "payment.overdue", "payment_gateway.payment_created", "payment_gateway.payment_confirmed", "payment_gateway.payment_failed", "payment_gateway.payment_refunded", "payment_gateway.payment_canceled", "payment_gateway.payment_expired", "payment_gateway.webhook_received", "billing.invoice_created", "billing.invoice_paid", "ai.interaction", "ai.fallback", "auth.login", "auth.logout", "auth.password_reset", "user.created", "user.updated", "user.deleted", "notification.sent", "notification.failed", "whatsapp.connection_established", "whatsapp.connection_lost", "procedure.created", "procedure.updated", "procedure.deleted", "specialty.created", "specialty.updated", "specialty.deleted", "professional.created", "professional.updated", "professional.deleted", "configuration.updated", "storage.file_uploaded", "storage.file_deleted", "tenant.created", "tenant.updated"];
5
+ export declare const EVENT_ANALYTIC_TYPES: readonly ["patient.created", "patient.updated", "patient.deleted", "patient.status_changed", "appointment.created", "appointment.updated", "appointment.canceled", "appointment.rescheduled", "appointment.no_show", "appointment.finished", "appointment.procedure_finished", "checkin.effectued", "checkout.effectued", "message.sent", "message.received", "conversation.session_started", "conversation.session_ended", "conversation.transferred_to_human", "followup.abandonment.sent", "followup.appointment_confirmation.sent", "followup.satisfaction_booking.sent", "followup.satisfaction_finished.sent", "followup.no_show_reschedule.sent", "followup.wellness_check.sent", "followup.return_suggestion.sent", "followup.satisfaction_booking.response_received", "followup.satisfaction_finished.response_received", "followup.config_updated", "payment.created", "payment.payed", "payment.refunded", "payment.canceled", "payment.overdue", "payment_gateway.payment_created", "payment_gateway.payment_confirmed", "payment_gateway.payment_failed", "payment_gateway.payment_refunded", "payment_gateway.payment_canceled", "payment_gateway.payment_expired", "payment_gateway.webhook_received", "billing.invoice_created", "billing.invoice_paid", "ai.interaction", "ai.fallback", "auth.login", "auth.logout", "auth.password_reset", "user.created", "user.updated", "user.deleted", "notification.sent", "notification.failed", "whatsapp.connection_established", "whatsapp.connection_lost", "procedure.created", "procedure.updated", "procedure.deleted", "specialty.created", "specialty.updated", "specialty.deleted", "professional.created", "professional.updated", "professional.deleted", "configuration.updated", "storage.file_uploaded", "storage.file_deleted", "tenant.created", "tenant.updated"];
6
6
  export type EventAnalyticType = (typeof EVENT_ANALYTIC_TYPES)[number];
7
7
  //# sourceMappingURL=event-analytic.enum.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"event-analytic.enum.d.ts","sourceRoot":"","sources":["../../src/analytics/event-analytic.enum.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,4sDA2GvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"event-analytic.enum.d.ts","sourceRoot":"","sources":["../../src/analytics/event-analytic.enum.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,8uDA6GvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC"}
@@ -18,6 +18,8 @@ exports.EVENT_ANALYTIC_TYPES = [
18
18
  'appointment.rescheduled',
19
19
  'appointment.no_show',
20
20
  'appointment.finished',
21
+ /** Procedimento do agendamento marcado como concluído (add/update com finished). */
22
+ 'appointment.procedure_finished',
21
23
  // ── Check-in / Check-out events ─────────────────────────────────
22
24
  'checkin.effectued',
23
25
  'checkout.effectued',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tychat-contracts",
3
- "version": "1.0.99",
3
+ "version": "1.0.101",
4
4
  "description": "DTOs compartilhados com class-validator (API e microserviços)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -87,3 +87,136 @@ export class AnalyticsDashboardQueryDto {
87
87
  @IsISO8601()
88
88
  endDate: string;
89
89
  }
90
+
91
+ /** Resumo financeiro agregado a partir de eventos de analytics (pagamentos + faturamento). */
92
+ export class AnalyticsDashboardFinancialDto {
93
+ @ApiProperty({
94
+ description:
95
+ 'Receita no período: soma de `payment.payed` e `billing.invoice_paid` (valores em metadata.amount)',
96
+ })
97
+ revenue: number;
98
+
99
+ @ApiProperty({
100
+ description:
101
+ 'Ticket médio: revenue / número de eventos pagos (payment.payed + billing.invoice_paid); 0 se não houver pagamentos',
102
+ })
103
+ averageTicket: number;
104
+
105
+ @ApiProperty({
106
+ description:
107
+ 'Valor ainda em aberto no fim do período (exclusive endDate): soma de `payment.created` no intervalo sem `payment.payed` correspondente (mesmo paymentId) com occurredAt anterior a endDate; mais faturas `billing.invoice_created` PENDING/OVERDUE sem `billing.invoice_paid` (mesmo invoiceId). Baseado em eventos de analytics.',
108
+ })
109
+ pending: number;
110
+ }
111
+
112
+ export class AnalyticsDashboardTopProcedureDto {
113
+ @ApiProperty({ description: 'Nome do procedimento (agendamento)' })
114
+ name: string;
115
+
116
+ @ApiProperty({ description: 'Quantidade de ocorrências no período' })
117
+ count: number;
118
+ }
119
+
120
+ /** Média de `message.sent` por hora do relógio durante o expediente configurado. */
121
+ export class AnalyticsDashboardMessageHourDto {
122
+ @ApiProperty({ description: 'Hora local (0–23) no fuso clinicTimezone' })
123
+ hour: number;
124
+
125
+ @ApiProperty({
126
+ description:
127
+ 'Média de mensagens enviadas nessa hora: totalSent / openOccurrencesInPeriod (0 se não houve expediente nessa hora no período)',
128
+ })
129
+ averageSent: number;
130
+
131
+ @ApiProperty({ description: 'Total de eventos message.sent nessa hora local, só dentro do horário da clínica' })
132
+ totalSent: number;
133
+
134
+ @ApiProperty({
135
+ description:
136
+ 'Quantas vezes essa hora calendário integrou o expediente (dias locais no intervalo × slots de opening_hours)',
137
+ })
138
+ openOccurrencesInPeriod: number;
139
+ }
140
+
141
+ export class AnalyticsDashboardMessagePeakDto {
142
+ @ApiProperty({ description: 'Hora local (0–23) com maior totalSent entre as horas de expediente' })
143
+ hour: number;
144
+
145
+ @ApiProperty() totalSent: number;
146
+
147
+ @ApiProperty() averageSent: number;
148
+ }
149
+
150
+ /**
151
+ * Resposta enriquecida de `GET /analytics/dashboard` (mantém campos legados `total`, `byEventType`, `byDomain`).
152
+ */
153
+ export class AnalyticsDashboardResponseDto {
154
+ @ApiProperty({ description: 'Agendamentos criados no período (`appointment.created`)' })
155
+ appointmentsCreated: number;
156
+
157
+ @ApiProperty({ description: 'Agendamentos finalizados no período (`appointment.finished`)' })
158
+ appointmentsFinished: number;
159
+
160
+ @ApiProperty({
161
+ description:
162
+ 'Procedimentos agendados: soma dos itens em metadata.procedures em `appointment.created` + vínculos posteriores (`appointment.updated` com reason procedure_added)',
163
+ })
164
+ proceduresScheduled: number;
165
+
166
+ @ApiProperty({
167
+ description: 'Procedimentos marcados como concluídos no período (`appointment.procedure_finished`)',
168
+ })
169
+ proceduresFinished: number;
170
+
171
+ @ApiProperty({
172
+ description:
173
+ 'Média das notas 1–5 em respostas de satisfação (followup booking + pós-atendimento); null se não houver respostas',
174
+ nullable: true,
175
+ })
176
+ satisfactionAverage: number | null;
177
+
178
+ @ApiProperty({
179
+ description:
180
+ 'Taxa de no-show (%): appointment.no_show / appointment.created no período (0 se não houver criações)',
181
+ })
182
+ noShowRatePercent: number;
183
+
184
+ @ApiProperty({ type: AnalyticsDashboardFinancialDto })
185
+ financialSummary: AnalyticsDashboardFinancialDto;
186
+
187
+ @ApiProperty({ type: [AnalyticsDashboardTopProcedureDto], maxItems: 5 })
188
+ topProcedures: AnalyticsDashboardTopProcedureDto[];
189
+
190
+ @ApiProperty({
191
+ description:
192
+ 'Pico / distribuição de mensagens no horário da clínica: médias por hora local (só `message.sent` dentro de opening_hours). Ordenado por hora crescente. Vazio se não houver opening_hours.',
193
+ type: [AnalyticsDashboardMessageHourDto],
194
+ })
195
+ messagesByClinicHour: AnalyticsDashboardMessageHourDto[];
196
+
197
+ @ApiProperty({
198
+ description: 'Hora com maior volume de mensagens no expediente; null se não houver dados ou horário configurado',
199
+ nullable: true,
200
+ type: AnalyticsDashboardMessagePeakDto,
201
+ })
202
+ messagesPeakHour: AnalyticsDashboardMessagePeakDto | null;
203
+
204
+ @ApiProperty({
205
+ description: 'Fuso IANA usado para hora local e opening_hours (env ANALYTICS_CLINIC_TIMEZONE ou America/Sao_Paulo)',
206
+ example: 'America/Sao_Paulo',
207
+ })
208
+ clinicTimezone: string;
209
+
210
+ @ApiProperty({ description: 'Total de eventos de analytics no período (legado)' })
211
+ total: number;
212
+
213
+ @ApiProperty({
214
+ description: 'Contagens por eventType (legado)',
215
+ })
216
+ byEventType: Array<{ eventType: string; count: number }>;
217
+
218
+ @ApiProperty({
219
+ description: 'Contagens por domínio (prefixo antes do ponto no eventType) (legado)',
220
+ })
221
+ byDomain: Array<{ domain: string; count: number }>;
222
+ }
@@ -16,6 +16,8 @@ export const EVENT_ANALYTIC_TYPES = [
16
16
  'appointment.rescheduled',
17
17
  'appointment.no_show',
18
18
  'appointment.finished',
19
+ /** Procedimento do agendamento marcado como concluído (add/update com finished). */
20
+ 'appointment.procedure_finished',
19
21
 
20
22
  // ── Check-in / Check-out events ─────────────────────────────────
21
23
  'checkin.effectued',