evo360-types 1.3.503 → 1.3.507

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.
@@ -0,0 +1,376 @@
1
+ // ======================================================
2
+ // evo-integrations — bateria de regressão de adapters (feat-123)
3
+ //
4
+ // Porte do script `_regressao-adapters.cjs` para ferramenta de produto no Nexus.
5
+ //
6
+ // A ferramenta é APARTADA: roda por fora, chamando as APIs (`integrations_actions`,
7
+ // `integrations_admin`, `integrations_sync_monitor`) exatamente como o script faz.
8
+ // Quem orquestra a sequência é o FE; quem persiste o resultado é o BE.
9
+ //
10
+ // Este módulo é o contrato COMPARTILHADO entre os dois lados:
11
+ // · o FE monta a seleção a partir do catálogo de passos;
12
+ // · o BE valida a seleção contra o MESMO catálogo e calcula o veredito.
13
+ //
14
+ // O catálogo de defeitos CONHECIDOS não mora aqui — mora no backend do Nexus,
15
+ // porque só ele calcula veredito (o FE apenas renderiza o que recebe).
16
+ // ======================================================
17
+
18
+ import type { IFireDoc } from "../shared";
19
+
20
+ // Path: /platform/evo-integrations/test-runs/{run_id}
21
+ export const TEST_RUNS_COLLECTION = "test-runs";
22
+ // Path: /platform/evo-integrations/test-runs/{run_id}/steps/{step_id}
23
+ export const TEST_RUN_STEPS_COLLECTION = "steps";
24
+
25
+ // ======================================================
26
+ // Suítes
27
+ // ======================================================
28
+
29
+ /**
30
+ * O agrupamento é derivado da DEPENDÊNCIA REAL DE ESTADO, não de estética:
31
+ * o `external_id` devolvido pelo create alimenta get/update/confirm/revert/cancel,
32
+ * e o do patient.create alimenta o round-trip e o delete.
33
+ */
34
+ export type TestSuiteId =
35
+ | "reading" // S1 — dicionários e leitura; independentes, zero efeito colateral
36
+ | "appointment" // S2 — ciclo de vida; ordem estrita
37
+ | "sync" // S3 — depende do cancel da S2
38
+ | "patient" // S4 — ordem estrita; opcional
39
+ | "cleanup"; // Z — limpeza; selecionável (D11)
40
+
41
+ export interface ITestSuiteMeta {
42
+ id: TestSuiteId;
43
+ label: string;
44
+ /** Ordem estrita = o passo N depende do estado produzido pelo N-1. */
45
+ ordered: boolean;
46
+ description: string;
47
+ }
48
+
49
+ export const TEST_SUITES: readonly ITestSuiteMeta[] = [
50
+ {
51
+ id: "reading",
52
+ label: "Dicionários e leitura",
53
+ ordered: false,
54
+ description:
55
+ "Capabilities de consulta. Nenhuma escreve — pode rodar sozinha com efeito colateral zero.",
56
+ },
57
+ {
58
+ id: "appointment",
59
+ label: "Ciclo de vida do agendamento",
60
+ ordered: true,
61
+ description:
62
+ "create → get → update → confirm → revert_confirm → cancel. Cada passo depende do external_id do create.",
63
+ },
64
+ {
65
+ id: "sync",
66
+ label: "Sync não ressuscita",
67
+ ordered: true,
68
+ description:
69
+ "Força o sync de 1 dia e confere que o agendamento cancelado NÃO volta. Exige o cancel da suíte anterior.",
70
+ },
71
+ {
72
+ id: "patient",
73
+ label: "Paciente",
74
+ ordered: true,
75
+ description:
76
+ "create → round-trip (get+update) → delete. O create nunca foi exercitado por nenhuma rodada do script.",
77
+ },
78
+ {
79
+ id: "cleanup",
80
+ label: "Varredura de resíduo",
81
+ ordered: false,
82
+ description:
83
+ "Lê a agenda EXTERNA e cancela o que tiver o marcador. Selecionável — quem roda é o dev de integrações, que responde pelo que escolhe.",
84
+ },
85
+ ] as const;
86
+
87
+ // ======================================================
88
+ // Passos
89
+ // ======================================================
90
+
91
+ export type TestStepId =
92
+ // S1
93
+ | "caps.list"
94
+ | "ui.config"
95
+ | "procedure.list"
96
+ | "status.list"
97
+ | "dicts.extra"
98
+ | "patient.list"
99
+ | "patient.get"
100
+ | "appointment.list"
101
+ // S2
102
+ | "appointment.create"
103
+ | "appointment.get"
104
+ | "appointment.update"
105
+ | "appointment.confirm"
106
+ | "appointment.revert_confirm"
107
+ | "appointment.cancel"
108
+ // S3
109
+ | "sync.force"
110
+ // S4
111
+ | "patient.create"
112
+ | "patient.roundtrip"
113
+ | "patient.delete"
114
+ // Z
115
+ | "residue.sweep";
116
+
117
+ export interface ITestStepMeta {
118
+ id: TestStepId;
119
+ suite: TestSuiteId;
120
+ label: string;
121
+ /**
122
+ * `true` = o passo ESCREVE no PMS de produção.
123
+ *
124
+ * Usado em dois lugares: para o modo somente-leitura e para classificar o custo
125
+ * de REPETIR um passo — repetir leitura é livre, repetir escrita duplica efeito.
126
+ */
127
+ writes: boolean;
128
+ /** Passos que precisam ter passado antes. Vazio = pode rodar sozinho. */
129
+ dependsOn: readonly TestStepId[];
130
+ /** Capability invocada, quando houver. Nem todo passo tem uma. */
131
+ capability?: string;
132
+ }
133
+
134
+ /**
135
+ * Ordem do array = ordem de execução. Não é 1:1 com capability:
136
+ * · `residue.sweep` não invoca capability nenhuma (varre a agenda externa);
137
+ * · `sync.force` chama `integrations_sync_monitor`, não uma capability;
138
+ * · `appointment.list` aparece em dois passos (leitura e varredura);
139
+ * · `patient.roundtrip` combina `patient.get` + `patient.update`.
140
+ */
141
+ export const TEST_STEPS: readonly ITestStepMeta[] = [
142
+ // ── S1 — leitura ────────────────────────────────────────────────────────────
143
+ { id: "caps.list", suite: "reading", label: "capabilities", writes: false, dependsOn: [] },
144
+ { id: "ui.config", suite: "reading", label: "ui-config", writes: false, dependsOn: [] },
145
+ { id: "procedure.list", suite: "reading", label: "procedure.list", writes: false, dependsOn: [], capability: "appointment.procedure.list" },
146
+ { id: "status.list", suite: "reading", label: "status.list", writes: false, dependsOn: [], capability: "appointment.status.list" },
147
+ { id: "dicts.extra", suite: "reading", label: "dicionários extra", writes: false, dependsOn: [] },
148
+ { id: "patient.list", suite: "reading", label: "patient.list", writes: false, dependsOn: [], capability: "patient.list" },
149
+ { id: "patient.get", suite: "reading", label: "patient.get", writes: false, dependsOn: [], capability: "patient.get" },
150
+ { id: "appointment.list", suite: "reading", label: "appointment.list", writes: false, dependsOn: [], capability: "appointment.list" },
151
+
152
+ // ── S2 — ciclo de vida ──────────────────────────────────────────────────────
153
+ { id: "appointment.create", suite: "appointment", label: "create", writes: true, dependsOn: [], capability: "appointment.create" },
154
+ { id: "appointment.get", suite: "appointment", label: "get", writes: false, dependsOn: ["appointment.create"], capability: "appointment.get" },
155
+ { id: "appointment.update", suite: "appointment", label: "update", writes: true, dependsOn: ["appointment.create"], capability: "appointment.update" },
156
+ { id: "appointment.confirm", suite: "appointment", label: "confirm", writes: true, dependsOn: ["appointment.create"], capability: "appointment.confirm" },
157
+ { id: "appointment.revert_confirm", suite: "appointment", label: "revert_confirm", writes: true, dependsOn: ["appointment.confirm"], capability: "appointment.revert_confirm" },
158
+ { id: "appointment.cancel", suite: "appointment", label: "cancel", writes: true, dependsOn: ["appointment.create"], capability: "appointment.cancel" },
159
+
160
+ // ── S3 — sync ───────────────────────────────────────────────────────────────
161
+ { id: "sync.force", suite: "sync", label: "sync (1 dia)", writes: true, dependsOn: ["appointment.cancel"] },
162
+
163
+ // ── S4 — paciente ───────────────────────────────────────────────────────────
164
+ { id: "patient.create", suite: "patient", label: "patient.create", writes: true, dependsOn: [], capability: "patient.create" },
165
+ { id: "patient.roundtrip", suite: "patient", label: "round-trip (get+update)", writes: true, dependsOn: [], capability: "patient.update" },
166
+ { id: "patient.delete", suite: "patient", label: "patient.delete", writes: true, dependsOn: ["patient.create"], capability: "patient.delete" },
167
+
168
+ // ── Z — limpeza ─────────────────────────────────────────────────────────────
169
+ { id: "residue.sweep", suite: "cleanup", label: "varredura de resíduo", writes: true, dependsOn: [] },
170
+ ] as const;
171
+
172
+ /**
173
+ * Suíte padrão (D10) — a LINHA DE BASE COMPARÁVEL: todos os passos.
174
+ *
175
+ * Só uma corrida com exatamente esta seleção é comparável com a anterior. Sem isso
176
+ * o output principal da ferramenta ("nenhum defeito novo") seria infalsificável:
177
+ * bastaria desmarcar o passo que falha.
178
+ *
179
+ * Atenção: a seleção não basta. `comparable` também exige `target.create_patient`
180
+ * ligado — senão `patient.create`/`patient.delete` são pulados e a cobertura real
181
+ * fica menor que a nominal. Quem decide isso é o backend, em `computeComparable`.
182
+ */
183
+ export const STANDARD_SUITE_STEP_IDS: readonly TestStepId[] = TEST_STEPS.map(
184
+ (s) => s.id,
185
+ );
186
+
187
+ export const READ_ONLY_STEP_IDS: readonly TestStepId[] = TEST_STEPS.filter(
188
+ (s) => !s.writes,
189
+ ).map((s) => s.id);
190
+
191
+ // ======================================================
192
+ // Checks
193
+ // ======================================================
194
+
195
+ export type TestCheckStatus = "pass" | "fail" | "skipped";
196
+
197
+ export interface ITestCheck {
198
+ /**
199
+ * Id ESTÁVEL no formato `<step_id>:<slug>` — ex. `appointment.update:sem_divergencia`.
200
+ *
201
+ * É a chave que o catálogo de conhecidos referencia. O script antigo casava por
202
+ * REGEX no texto do label, o que quebrava nos dois sentidos: mudar a redação
203
+ * descatalogava um defeito em silêncio, e um regex largo engolia defeito novo de
204
+ * outra causa. Por isso o id vem antes do texto — o label pode ser reescrito à
205
+ * vontade.
206
+ */
207
+ id: string;
208
+ label: string;
209
+ status: TestCheckStatus;
210
+ detail?: string;
211
+ }
212
+
213
+ // ======================================================
214
+ // Passo executado
215
+ // ======================================================
216
+
217
+ export type TestStepStatus =
218
+ | "pending"
219
+ | "running"
220
+ | "pass"
221
+ | "known" // falhou, mas casa com defeito catalogado — não reprova a corrida
222
+ | "fail" // defeito NOVO
223
+ | "skipped"
224
+ | "error";
225
+
226
+ export interface ITestSchemaValidation {
227
+ ok: boolean;
228
+ errors?: Array<{ path: (string | number)[]; message: string; code?: string }>;
229
+ }
230
+
231
+ /**
232
+ * O wire — o que o BACKEND mandou para o n8n, lido de
233
+ * `integrations_sync_monitor GET /calls?run_id=`.
234
+ *
235
+ * É a camada que interessa a quem mantém os workflows: a resposta da action mostra
236
+ * o que a UI mandou, não o que saiu na linha. `DocumentReference` vivo no payload
237
+ * (a classe de bug que derrubou produção no revert_confirm) só aparece aqui.
238
+ */
239
+ export interface ITestStepWire {
240
+ request_json?: string;
241
+ response_json?: string;
242
+ n8n_execution_id?: string | null;
243
+ n8n_workflow_id?: string | null;
244
+ workflow_url?: string;
245
+ http_status?: number | null;
246
+ timed_out?: boolean | null;
247
+ error_message?: string | null;
248
+ request_truncated?: boolean | null;
249
+ response_truncated?: boolean | null;
250
+ }
251
+
252
+ export interface ITestRunStep extends IFireDoc {
253
+ step_id: TestStepId;
254
+ suite: TestSuiteId;
255
+ order: number;
256
+ status: TestStepStatus;
257
+ started_at?: unknown;
258
+ finished_at?: unknown;
259
+ duration_ms?: number;
260
+
261
+ /** Camada 1 — o que a UI mandou para `integrations_actions` e o que voltou. */
262
+ layer1?: { request?: unknown; response?: unknown };
263
+
264
+ /** Correlação com o wire: `transport_metadata.run_id` da resposta da action. */
265
+ n8n_run_id?: string | null;
266
+
267
+ /** Camada 2 — o que o BE mandou para o n8n. */
268
+ wire?: ITestStepWire | null;
269
+
270
+ schema_validation?: {
271
+ request?: ITestSchemaValidation;
272
+ response?: ITestSchemaValidation;
273
+ } | null;
274
+
275
+ checks: ITestCheck[];
276
+
277
+ /** Preenchido quando `status === 'known'`: qual defeito catalogado casou. */
278
+ known_defects?: Array<{ check_id: string; description: string; ref: string }>;
279
+
280
+ error?: string | null;
281
+ }
282
+
283
+ // ======================================================
284
+ // Corrida
285
+ // ======================================================
286
+
287
+ export type TestRunStatus = "running" | "finished" | "abandoned";
288
+
289
+ /** Dados do paciente novo, quando o usuário liga a criação (D3). */
290
+ export interface ITestNewPatient {
291
+ first_name: string;
292
+ last_name?: string;
293
+ mobile_phone?: string;
294
+ email?: string;
295
+ social_id?: string;
296
+ birth_date?: string;
297
+ }
298
+
299
+ export interface ITestRunTarget {
300
+ /** Paciente de teste JÁ EXISTENTE, usado na leitura e no agendamento. */
301
+ patient_id: string;
302
+ /** Só quando `create_patient` está ligado. */
303
+ new_patient?: ITestNewPatient | null;
304
+ create_patient: boolean;
305
+ /** `YYYY-MM-DD`. Recomendado no passado — é aviso, não trava (D2). */
306
+ slot_date: string;
307
+ /** `HH:MM`. */
308
+ slot_time: string;
309
+ /** Dia povoado para o `appointment.list`; sem isso usa `slot_date`. */
310
+ list_date?: string | null;
311
+ }
312
+
313
+ /**
314
+ * Diagnóstico de segurança — AVISO, não trava (D2).
315
+ *
316
+ * Os dois avisos têm gravidade diferente e a UI precisa refletir isso: data futura
317
+ * só ativa as varreduras D-2/D-0, que dependem de janela; gatilho de evento ligado
318
+ * dispara na hora do create.
319
+ */
320
+ export interface ITestRunPreflight {
321
+ slot_in_past: boolean;
322
+ /** Quais dos gatilhos de evento estão ligados na agenda-alvo. */
323
+ triggers_enabled: string[];
324
+ /**
325
+ * `notification_config.configs` vazio.
326
+ *
327
+ * Distinguir de "desligado" importa: nada dispara nos dois casos, mas aqui é por
328
+ * AUSÊNCIA DE CONFIGURAÇÃO, não por decisão de alguém.
329
+ */
330
+ notification_config_empty: boolean;
331
+ evaluated_at?: unknown;
332
+ }
333
+
334
+ /** O que a corrida criou no PMS — permite enumerar resíduo quando o `Z` é desmarcado (D11). */
335
+ export interface ITestRunInventory {
336
+ appointment_external_ids: string[];
337
+ patient_external_id?: string | null;
338
+ }
339
+
340
+ export interface ITestRunVerdict {
341
+ ok: number;
342
+ known: number;
343
+ new_defects: number;
344
+ }
345
+
346
+ export interface ITestRun extends IFireDoc {
347
+ adapter_id: string;
348
+ tenant: string;
349
+ calendar_id: string;
350
+
351
+ target: ITestRunTarget;
352
+
353
+ selection: {
354
+ preset: "standard" | "read-only" | "custom";
355
+ step_ids: TestStepId[];
356
+ };
357
+
358
+ /** Quantos passos da suíte padrão esta corrida cobriu. */
359
+ coverage: { ran: number; total: number };
360
+ /** `true` só quando a seleção é exatamente a suíte padrão (D10). */
361
+ comparable: boolean;
362
+
363
+ preflight: ITestRunPreflight;
364
+ inventory: ITestRunInventory;
365
+ verdict: ITestRunVerdict;
366
+
367
+ status: TestRunStatus;
368
+
369
+ /**
370
+ * Dono da corrida. Quem orquestra é o FE, então duas abas conduzindo a mesma
371
+ * corrida escreveriam na mesma agenda de produção em paralelo — outro usuário
372
+ * abre em modo leitura até assumir explicitamente.
373
+ */
374
+ created_by: string;
375
+ finished_at?: unknown;
376
+ }
@@ -58,6 +58,7 @@ export declare const TaskAutoHandlerEnum: {
58
58
  readonly AppointmentConfirmation: "appointment_confirmation";
59
59
  readonly AppointmentPostConsultation: "appointment_post_consultation";
60
60
  readonly IntegrationsSyncRun: "evo-integrations.sync-run";
61
+ readonly LeadFormCapture: "lead_form_capture";
61
62
  readonly CampaignMaterialize: "campaign.materialize";
62
63
  readonly CampaignDispatchBatch: "campaign.dispatch_batch";
63
64
  };
@@ -117,6 +117,12 @@ exports.TaskAutoHandlerEnum = {
117
117
  // types/evo-integrations). Permite que o `/force` roteie pelo task-runner e
118
118
  // herde retry/backoff/DLQ, em vez de montar a mensagem PubSub na mão.
119
119
  IntegrationsSyncRun: "evo-integrations.sync-run",
120
+ // feat-121: captação de lead por FORMULÁRIO (site/landing). Resolve ou cria o lead
121
+ // pelo telefone, aplica o pipe que vier no payload (origem/qualificação/tags/campanha)
122
+ // e inicia a conversa no WhatsApp conforme o `mode` (`contato` | `agenda_demo`).
123
+ // Tópico do executor: `lead_form_capture.execute_requests` (functions-notifications).
124
+ // Espelhado à mão em `zTaskAutoHandlerSchema` — sem lá, o POST /tasks estoura ZodError.
125
+ LeadFormCapture: "lead_form_capture",
120
126
  // feat-081 F2: handlers do modulo de campanhas (padrao pontilhado da feat-078 —
121
127
  // `resolveHandlerTopic` monta `campaign.materialize.execute_requests`, topico que
122
128
  // a CF `campaign_materialize` ja escuta desde a F1, e
@@ -128,6 +128,12 @@ export const TaskAutoHandlerEnum = {
128
128
  // types/evo-integrations). Permite que o `/force` roteie pelo task-runner e
129
129
  // herde retry/backoff/DLQ, em vez de montar a mensagem PubSub na mão.
130
130
  IntegrationsSyncRun: "evo-integrations.sync-run",
131
+ // feat-121: captação de lead por FORMULÁRIO (site/landing). Resolve ou cria o lead
132
+ // pelo telefone, aplica o pipe que vier no payload (origem/qualificação/tags/campanha)
133
+ // e inicia a conversa no WhatsApp conforme o `mode` (`contato` | `agenda_demo`).
134
+ // Tópico do executor: `lead_form_capture.execute_requests` (functions-notifications).
135
+ // Espelhado à mão em `zTaskAutoHandlerSchema` — sem lá, o POST /tasks estoura ZodError.
136
+ LeadFormCapture: "lead_form_capture",
131
137
  // feat-081 F2: handlers do modulo de campanhas (padrao pontilhado da feat-078 —
132
138
  // `resolveHandlerTopic` monta `campaign.materialize.execute_requests`, topico que
133
139
  // a CF `campaign_materialize` ja escuta desde a F1, e
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evo360-types",
3
- "version": "1.3.503",
3
+ "version": "1.3.507",
4
4
  "description": "HREVO360 Shared Types",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",