evo360-types 1.3.391 → 1.3.394

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export declare const zNotificationChannelTypeSchema: z.ZodEnum<["whatsapp", "email", "sms", "push"]>;
3
- export declare const zNotificationMessageStatusSchema: z.ZodEnum<["queued", "processing", "sent", "delivered", "failed", "cancelled", "suppressed"]>;
3
+ export declare const zNotificationMessageStatusSchema: z.ZodEnum<["queued", "processing", "sent", "delivered", "read", "failed", "cancelled", "suppressed"]>;
4
4
  export declare const zNotificationAttemptStatusSchema: z.ZodEnum<["success", "retry", "error"]>;
5
5
  export declare const zNotificationRecipientKindSchema: z.ZodEnum<["patient", "professional", "preconfigured", "raw"]>;
6
6
  export declare const zNotificationLogLevelSchema: z.ZodEnum<["debug", "info", "warn", "error"]>;
@@ -139,11 +139,12 @@ export declare const zNotificationMessageSchema: z.ZodObject<{
139
139
  display_name: z.ZodOptional<z.ZodString>;
140
140
  }, z.ZodTypeAny, "passthrough">>;
141
141
  variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
142
- status: z.ZodEnum<["queued", "processing", "sent", "delivered", "failed", "cancelled", "suppressed"]>;
142
+ status: z.ZodEnum<["queued", "processing", "sent", "delivered", "read", "failed", "cancelled", "suppressed"]>;
143
143
  queued_at: z.ZodDate;
144
144
  processing_started_at: z.ZodOptional<z.ZodDate>;
145
145
  sent_at: z.ZodOptional<z.ZodDate>;
146
146
  delivered_at: z.ZodOptional<z.ZodDate>;
147
+ read_at: z.ZodOptional<z.ZodDate>;
147
148
  completed_at: z.ZodOptional<z.ZodDate>;
148
149
  provider: z.ZodOptional<z.ZodObject<{
149
150
  name: z.ZodOptional<z.ZodString>;
@@ -255,11 +256,12 @@ export declare const zNotificationMessageSchema: z.ZodObject<{
255
256
  display_name: z.ZodOptional<z.ZodString>;
256
257
  }, z.ZodTypeAny, "passthrough">>;
257
258
  variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
258
- status: z.ZodEnum<["queued", "processing", "sent", "delivered", "failed", "cancelled", "suppressed"]>;
259
+ status: z.ZodEnum<["queued", "processing", "sent", "delivered", "read", "failed", "cancelled", "suppressed"]>;
259
260
  queued_at: z.ZodDate;
260
261
  processing_started_at: z.ZodOptional<z.ZodDate>;
261
262
  sent_at: z.ZodOptional<z.ZodDate>;
262
263
  delivered_at: z.ZodOptional<z.ZodDate>;
264
+ read_at: z.ZodOptional<z.ZodDate>;
263
265
  completed_at: z.ZodOptional<z.ZodDate>;
264
266
  provider: z.ZodOptional<z.ZodObject<{
265
267
  name: z.ZodOptional<z.ZodString>;
@@ -371,11 +373,12 @@ export declare const zNotificationMessageSchema: z.ZodObject<{
371
373
  display_name: z.ZodOptional<z.ZodString>;
372
374
  }, z.ZodTypeAny, "passthrough">>;
373
375
  variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
374
- status: z.ZodEnum<["queued", "processing", "sent", "delivered", "failed", "cancelled", "suppressed"]>;
376
+ status: z.ZodEnum<["queued", "processing", "sent", "delivered", "read", "failed", "cancelled", "suppressed"]>;
375
377
  queued_at: z.ZodDate;
376
378
  processing_started_at: z.ZodOptional<z.ZodDate>;
377
379
  sent_at: z.ZodOptional<z.ZodDate>;
378
380
  delivered_at: z.ZodOptional<z.ZodDate>;
381
+ read_at: z.ZodOptional<z.ZodDate>;
379
382
  completed_at: z.ZodOptional<z.ZodDate>;
380
383
  provider: z.ZodOptional<z.ZodObject<{
381
384
  name: z.ZodOptional<z.ZodString>;
@@ -16,6 +16,7 @@ exports.zNotificationMessageStatusSchema = zod_1.z.enum([
16
16
  "processing",
17
17
  "sent",
18
18
  "delivered",
19
+ "read",
19
20
  "failed",
20
21
  "cancelled",
21
22
  "suppressed",
@@ -95,6 +96,7 @@ exports.zNotificationMessageSchema = zod_schemas_1.zFireDocSchema
95
96
  processing_started_at: zod_1.z.coerce.date().optional(),
96
97
  sent_at: zod_1.z.coerce.date().optional(),
97
98
  delivered_at: zod_1.z.coerce.date().optional(),
99
+ read_at: zod_1.z.coerce.date().optional(),
98
100
  completed_at: zod_1.z.coerce.date().optional(),
99
101
  provider: exports.zNotificationProviderMetaSchema.optional(),
100
102
  error: exports.zNotificationErrorSchema.optional(),
@@ -16,6 +16,7 @@ export const zNotificationMessageStatusSchema = z.enum([
16
16
  "processing",
17
17
  "sent",
18
18
  "delivered",
19
+ "read",
19
20
  "failed",
20
21
  "cancelled",
21
22
  "suppressed",
@@ -106,6 +107,7 @@ export const zNotificationMessageSchema = zFireDocSchema
106
107
  processing_started_at: z.coerce.date().optional(),
107
108
  sent_at: z.coerce.date().optional(),
108
109
  delivered_at: z.coerce.date().optional(),
110
+ read_at: z.coerce.date().optional(),
109
111
  completed_at: z.coerce.date().optional(),
110
112
  provider: zNotificationProviderMetaSchema.optional(),
111
113
  error: zNotificationErrorSchema.optional(),
@@ -33,6 +33,16 @@ export interface IDistChannel extends IFireDoc {
33
33
  type_name?: string;
34
34
  typeRef?: FirestoreDocumentReference;
35
35
  tags?: ITag[] | null;
36
+ rules?: {
37
+ regex: string;
38
+ flags?: string;
39
+ priority?: number;
40
+ description?: string | null;
41
+ examples?: string[] | null;
42
+ } | null;
43
+ /** Tipo FINO de aquisição (feat-039) — override explícito da derivação por `code`.
44
+ * String crua p/ evitar ciclo de import com ../lead; o core estreita p/ LeadReferralType. */
45
+ acq_type?: string | null;
36
46
  [key: string]: unknown;
37
47
  }
38
48
  export type QualificationFunnelLevel = "invalid" | "new" | "trying_contact" | "contacted" | "qualifying" | "qualified" | "disqualified" | "presented" | "proposal_sent" | "negotiating" | "won" | "lost" | "waiting_reply" | "follow_up_later";
@@ -54,6 +54,18 @@ export interface IDistChannel extends IFireDoc {
54
54
  type_name?: string;
55
55
  typeRef?: FirestoreDocumentReference;
56
56
  tags?: ITag[] | null;
57
+ // Regras de parsing da 1ª mensagem (feat-039) — regex casada contra o texto
58
+ // p/ atribuir este canal; `priority` ordena entre múltiplos matches.
59
+ rules?: {
60
+ regex: string;
61
+ flags?: string;
62
+ priority?: number;
63
+ description?: string | null;
64
+ examples?: string[] | null;
65
+ } | null;
66
+ /** Tipo FINO de aquisição (feat-039) — override explícito da derivação por `code`.
67
+ * String crua p/ evitar ciclo de import com ../lead; o core estreita p/ LeadReferralType. */
68
+ acq_type?: string | null;
57
69
  [key: string]: unknown; // index signature
58
70
  }
59
71
 
@@ -32,13 +32,15 @@ export interface ILeadDistChannel {
32
32
  /** Origem de aquisição do lead (feat-038) — base expansível p/ múltiplas
33
33
  * plataformas. v1 implementa só `type:'ctwa'` (Click-to-WhatsApp da Meta);
34
34
  * Google Ads / Instagram Direct / TikTok / site ficam para fast-follow. */
35
- export type LeadReferralType = "ctwa" | "instagram_direct" | "google_ads" | "tiktok" | "site" | "other";
35
+ export type LeadReferralType = "ctwa" | "instagram_direct" | "google_ads" | "tiktok" | "site" | "meta_organic" | "referral" | "other";
36
36
  export interface ILeadReferralBase {
37
37
  type: LeadReferralType;
38
38
  captured_at?: Date | string | null;
39
39
  source_url?: string | null;
40
40
  label?: string | null;
41
41
  raw?: Record<string, unknown> | null;
42
+ dist_channel_code?: string | null;
43
+ matched_rule?: string | null;
42
44
  }
43
45
  export interface ILeadReferralCTWA extends ILeadReferralBase {
44
46
  type: "ctwa";
@@ -49,7 +51,14 @@ export interface ILeadReferralCTWA extends ILeadReferralBase {
49
51
  media_type?: string | null;
50
52
  ctwa_clid?: string | null;
51
53
  }
52
- export type ILeadReferral = ILeadReferralCTWA;
54
+ /** Origem genérica derivada da 1ª mensagem (feat-039) — Google/TikTok/site/
55
+ * FB-IG orgânico/indicação. O `type` FINO vem do `acq_type` do dict ou do mapa
56
+ * por `code`; `dist_channel_code`/`matched_rule` ficam na base p/ auditoria. */
57
+ export interface ILeadReferralGeneric extends ILeadReferralBase {
58
+ type: "google_ads" | "tiktok" | "site" | "instagram_direct" | "meta_organic" | "referral" | "other";
59
+ headline?: string | null;
60
+ }
61
+ export type ILeadReferral = ILeadReferralCTWA | ILeadReferralGeneric;
53
62
  /** Estado da régua de reativação de lead (feat-037).
54
63
  * Mantido pelo subscriber `ticket_closed` (streak) e pela task
55
64
  * `lead_reactivation` (último disparo). Semântica do streak alinhada ao
@@ -53,6 +53,8 @@ export type LeadReferralType =
53
53
  | "google_ads" // gclid / token em ?text= ← futuro
54
54
  | "tiktok"
55
55
  | "site"
56
+ | "meta_organic" // menção/URL orgânica de FB/IG (não-pago, sem referral) ← feat-039
57
+ | "referral" // indicação ("me indicaram", "amigo falou") ← feat-039
56
58
  | "other"; // ← futuro
57
59
 
58
60
  export interface ILeadReferralBase {
@@ -61,6 +63,8 @@ export interface ILeadReferralBase {
61
63
  source_url?: string | null; // link clicável
62
64
  label?: string | null; // rótulo de exibição (headline || ad_id || domínio)
63
65
  raw?: Record<string, unknown> | null; // payload original lossless → re-parse futuro sem backfill
66
+ dist_channel_code?: string | null; // code do entry do dict que casou (ex.: 'GOOGLE') ← feat-039
67
+ matched_rule?: string | null; // regex/priority que casou (debug; opcional) ← feat-039
64
68
  }
65
69
 
66
70
  export interface ILeadReferralCTWA extends ILeadReferralBase {
@@ -73,7 +77,22 @@ export interface ILeadReferralCTWA extends ILeadReferralBase {
73
77
  ctwa_clid?: string | null; // espelho; o de topo (CAPI/feat-034) permanece na raiz
74
78
  }
75
79
 
76
- export type ILeadReferral = ILeadReferralCTWA; // | ILeadReferralGoogleAds | ...
80
+ /** Origem genérica derivada da mensagem (feat-039) Google/TikTok/site/
81
+ * FB-IG orgânico/indicação. O `type` FINO vem do `acq_type` do dict ou do mapa
82
+ * por `code`; `dist_channel_code`/`matched_rule` ficam na base p/ auditoria. */
83
+ export interface ILeadReferralGeneric extends ILeadReferralBase {
84
+ type:
85
+ | "google_ads"
86
+ | "tiktok"
87
+ | "site"
88
+ | "instagram_direct"
89
+ | "meta_organic"
90
+ | "referral"
91
+ | "other";
92
+ headline?: string | null; // paridade com CTWA p/ o FE existente
93
+ }
94
+
95
+ export type ILeadReferral = ILeadReferralCTWA | ILeadReferralGeneric;
77
96
 
78
97
  /** Estado da régua de reativação de lead (feat-037).
79
98
  * Mantido pelo subscriber `ticket_closed` (streak) e pela task
@@ -37,6 +37,7 @@ export declare const NotificationMessageStatusEnum: {
37
37
  readonly Processing: "processing";
38
38
  readonly Sent: "sent";
39
39
  readonly Delivered: "delivered";
40
+ readonly Read: "read";
40
41
  readonly Failed: "failed";
41
42
  readonly Cancelled: "cancelled";
42
43
  readonly Suppressed: "suppressed";
@@ -130,6 +131,7 @@ export interface INotificationMessage extends IFireDoc {
130
131
  processing_started_at?: Date;
131
132
  sent_at?: Date;
132
133
  delivered_at?: Date;
134
+ read_at?: Date;
133
135
  completed_at?: Date;
134
136
  provider?: INotificationProviderMeta;
135
137
  error?: INotificationError;
@@ -73,6 +73,7 @@ exports.NotificationMessageStatusEnum = {
73
73
  Processing: "processing", // being handled by notifications service
74
74
  Sent: "sent", // accepted by provider
75
75
  Delivered: "delivered", // delivery confirmed (when supported)
76
+ Read: "read", // read receipt confirmed (when supported, e.g. WhatsApp/WABA)
76
77
  Failed: "failed", // permanent failure
77
78
  Cancelled: "cancelled",
78
79
  Suppressed: "suppressed", // dedup/skip decision
@@ -80,6 +80,7 @@ export const NotificationMessageStatusEnum = {
80
80
  Processing: "processing", // being handled by notifications service
81
81
  Sent: "sent", // accepted by provider
82
82
  Delivered: "delivered", // delivery confirmed (when supported)
83
+ Read: "read", // read receipt confirmed (when supported, e.g. WhatsApp/WABA)
83
84
  Failed: "failed", // permanent failure
84
85
  Cancelled: "cancelled",
85
86
  Suppressed: "suppressed", // dedup/skip decision
@@ -242,6 +243,7 @@ export interface INotificationMessage extends IFireDoc {
242
243
  processing_started_at?: Date;
243
244
  sent_at?: Date;
244
245
  delivered_at?: Date;
246
+ read_at?: Date; // read receipt time (WABA-first; channel-neutral field)
245
247
  completed_at?: Date;
246
248
 
247
249
  // Final outcome
@@ -3,8 +3,20 @@ import type { IFireGlobalDoc } from "../shared";
3
3
  export declare const CHATBEE_IMPORT_SCHEMA_VERSION: 1;
4
4
  export declare const CHATBEE_IMPORT_FIRESTORE_PATH: "core/nexus/apps/nex-tenants/chatbee_imports";
5
5
  export declare const CHATBEE_IMPORT_PUBSUB_TOPIC: "app_nexus_chatbee-import_request";
6
+ /**
7
+ * Per-(tenant, channel) department mapping doc, keyed `{tenant}__{channelId}`.
8
+ * Holds the operator's resolution of unresolved Chatbee departments before a
9
+ * real import is allowed to run.
10
+ */
11
+ export declare const CHATBEE_IMPORT_DEPT_MAP_PATH: "core/nexus/apps/nex-tenants/chatbee_import_dept_maps";
12
+ /** Mapping sentinel: route this Chatbee department to the channel's default. */
13
+ export declare const CHANNEL_DEFAULT_DEPT_SENTINEL: "__channel_default__";
6
14
  export type ChatbeeImportStatus = "pending" | "running" | "done" | "failed";
7
- export type ChatbeeImportAuditAction = "import_requested" | "import_started" | "import_page" | "import_completed" | "import_failed";
15
+ /** A job runs either as a read-only analysis pass or the real import. */
16
+ export type ChatbeeImportMode = "analyze" | "import";
17
+ export type ChatbeeImportAuditAction = "import_requested" | "import_started" | "import_page" | "import_completed" | "import_failed" | "analyze_requested" | "analyze_completed" | "dept_map_saved";
18
+ /** Resolution status of one Chatbee department against the platform. */
19
+ export type ChatbeeDeptResolutionStatus = "resolved" | "resolved_no_inbox" | "missing" | "no_external_id" | "mapped";
8
20
  /**
9
21
  * Keyset cursor over `chatbee.msgs_raw` ordered by
10
22
  * (contact.id, history.id, timestamp, message.id). A page resumes strictly
@@ -32,6 +44,12 @@ export interface IChatbeeImportCounters {
32
44
  contacts_reconciled: number;
33
45
  reconcile_conflicts: number;
34
46
  pages_processed: number;
47
+ /** Distinct Chatbee departments seen across the run. */
48
+ departments_found: number;
49
+ /** Departments still blocking (not resolved nor mapped). */
50
+ departments_unresolved: number;
51
+ /** Lead links dropped because the referenced lead was missing/deleted. */
52
+ lead_links_missing: number;
35
53
  }
36
54
  export interface IChatbeeImportError {
37
55
  code: string;
@@ -42,12 +60,67 @@ export interface IChatbeeImportRequestedBy {
42
60
  email: string;
43
61
  loginId: string;
44
62
  }
63
+ /** Resolution of one DISTINCT Chatbee department against the platform. */
64
+ export interface IChatbeeDepartmentResolution {
65
+ /** Chatbee department numeric id (stable) — the mapping key. */
66
+ chatbee_id: string;
67
+ /** Chatbee department external_id (may be null → no platform correlation). */
68
+ external_id: string | null;
69
+ /** Chatbee department display name. */
70
+ name: string;
71
+ /** Distinct attendances seen under this department in the window. */
72
+ event_count: number;
73
+ tickets_open: number;
74
+ tickets_closed: number;
75
+ status: ChatbeeDeptResolutionStatus;
76
+ /**
77
+ * The platform department this resolves to: the validated `external_id` for
78
+ * `resolved`/`resolved_no_inbox`, or the operator mapping target for `mapped`
79
+ * (a platform dept id, or CHANNEL_DEFAULT_DEPT_SENTINEL). Null while blocking.
80
+ */
81
+ mapped_to?: string | null;
82
+ }
83
+ /** Read-only pre-import analysis report, stored on the job-doc. */
84
+ export interface IChatbeeImportAnalysis {
85
+ departments: IChatbeeDepartmentResolution[];
86
+ channel_default: {
87
+ department_id: string | null;
88
+ exists: boolean;
89
+ chat_enabled: boolean;
90
+ has_inbox: boolean;
91
+ /** True when the default is usable (exists + chat_enabled + has_inbox). */
92
+ valid: boolean;
93
+ };
94
+ lead_links: {
95
+ contacts_total: number;
96
+ /** Contacts whose contact.lead_id points at a missing/deleted lead. */
97
+ lead_missing_count: number;
98
+ };
99
+ contacts: {
100
+ total: number;
101
+ /** wa-index already points at a different contact (kept Chatbee canonical). */
102
+ index_conflicts: number;
103
+ };
104
+ /** True if any department is unresolved/unmapped or the channel default is invalid. */
105
+ blocking: boolean;
106
+ analyzed_at?: Date | null;
107
+ }
108
+ /**
109
+ * Operator's department mapping: Chatbee department id → platform department id
110
+ * (or CHANNEL_DEFAULT_DEPT_SENTINEL). Stored per (tenant, channel) at
111
+ * CHATBEE_IMPORT_DEPT_MAP_PATH/{tenant}__{channelId}.
112
+ */
113
+ export type IChatbeeImportDeptMapping = Record<string, string>;
45
114
  export interface IChatbeeImportDoc extends IFireGlobalDoc {
46
115
  tenant: string;
47
116
  /** Destination hub-waba channel id — threads/tickets/messages carry this channel_id. */
48
117
  destination_channel_id: string;
118
+ /** 'analyze' = read-only pre-import pass; 'import' = the real import. */
119
+ mode?: ChatbeeImportMode;
49
120
  /** Count-only preview: zero Firestore/PubSub/BQ writes, projects counters + totals. */
50
121
  dry_run: boolean;
122
+ /** Read-only analysis report (set when mode === 'analyze'). */
123
+ analysis?: IChatbeeImportAnalysis | null;
51
124
  requested_by: IChatbeeImportRequestedBy;
52
125
  requested_at: Date | null;
53
126
  started_at?: Date | null;
@@ -77,10 +150,38 @@ export declare const ChatbeeImportRequestSchema: z.ZodObject<{
77
150
  dry_run?: boolean | undefined;
78
151
  }>;
79
152
  export type IChatbeeImportRequestPayload = z.infer<typeof ChatbeeImportRequestSchema>;
153
+ export declare const ChatbeeImportAnalyzeSchema: z.ZodObject<{
154
+ tenant: z.ZodString;
155
+ destination_channel_id: z.ZodString;
156
+ }, "strip", z.ZodTypeAny, {
157
+ tenant: string;
158
+ destination_channel_id: string;
159
+ }, {
160
+ tenant: string;
161
+ destination_channel_id: string;
162
+ }>;
163
+ export type IChatbeeImportAnalyzePayload = z.infer<typeof ChatbeeImportAnalyzeSchema>;
164
+ export declare const ChatbeeImportSaveDeptMapSchema: z.ZodObject<{
165
+ tenant: z.ZodString;
166
+ channel_id: z.ZodString;
167
+ /** chatbee_id → platform dept id (or CHANNEL_DEFAULT_DEPT_SENTINEL). */
168
+ map: z.ZodRecord<z.ZodString, z.ZodString>;
169
+ }, "strip", z.ZodTypeAny, {
170
+ map: Record<string, string>;
171
+ tenant: string;
172
+ channel_id: string;
173
+ }, {
174
+ map: Record<string, string>;
175
+ tenant: string;
176
+ channel_id: string;
177
+ }>;
178
+ export type IChatbeeImportSaveDeptMapPayload = z.infer<typeof ChatbeeImportSaveDeptMapSchema>;
80
179
  export interface IChatbeeImportPubSubEvent {
81
180
  importId: string;
82
181
  tenant: string;
83
182
  destination_channel_id: string;
183
+ /** 'analyze' = read-only pass; 'import' = real import. */
184
+ mode: ChatbeeImportMode;
84
185
  dry_run: boolean;
85
186
  /** Cursor to resume from on a self-continuation; null/absent for the first run. */
86
187
  cursor?: IChatbeeImportCursor | null;
@@ -1,14 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ChatbeeImportRequestSchema = exports.CHATBEE_IMPORT_PUBSUB_TOPIC = exports.CHATBEE_IMPORT_FIRESTORE_PATH = exports.CHATBEE_IMPORT_SCHEMA_VERSION = void 0;
3
+ exports.ChatbeeImportSaveDeptMapSchema = exports.ChatbeeImportAnalyzeSchema = exports.ChatbeeImportRequestSchema = exports.CHANNEL_DEFAULT_DEPT_SENTINEL = exports.CHATBEE_IMPORT_DEPT_MAP_PATH = exports.CHATBEE_IMPORT_PUBSUB_TOPIC = exports.CHATBEE_IMPORT_FIRESTORE_PATH = exports.CHATBEE_IMPORT_SCHEMA_VERSION = void 0;
4
4
  const zod_1 = require("zod");
5
5
  // ── Constants ──
6
6
  exports.CHATBEE_IMPORT_SCHEMA_VERSION = 1;
7
7
  exports.CHATBEE_IMPORT_FIRESTORE_PATH = "core/nexus/apps/nex-tenants/chatbee_imports";
8
8
  exports.CHATBEE_IMPORT_PUBSUB_TOPIC = "app_nexus_chatbee-import_request";
9
+ /**
10
+ * Per-(tenant, channel) department mapping doc, keyed `{tenant}__{channelId}`.
11
+ * Holds the operator's resolution of unresolved Chatbee departments before a
12
+ * real import is allowed to run.
13
+ */
14
+ exports.CHATBEE_IMPORT_DEPT_MAP_PATH = "core/nexus/apps/nex-tenants/chatbee_import_dept_maps";
15
+ /** Mapping sentinel: route this Chatbee department to the channel's default. */
16
+ exports.CHANNEL_DEFAULT_DEPT_SENTINEL = "__channel_default__";
9
17
  // ── Zod Schemas (callable payloads) ──
10
18
  exports.ChatbeeImportRequestSchema = zod_1.z.object({
11
19
  tenant: zod_1.z.string().min(1),
12
20
  destination_channel_id: zod_1.z.string().min(1),
13
21
  dry_run: zod_1.z.boolean().optional(),
14
22
  });
23
+ exports.ChatbeeImportAnalyzeSchema = zod_1.z.object({
24
+ tenant: zod_1.z.string().min(1),
25
+ destination_channel_id: zod_1.z.string().min(1),
26
+ });
27
+ exports.ChatbeeImportSaveDeptMapSchema = zod_1.z.object({
28
+ tenant: zod_1.z.string().min(1),
29
+ channel_id: zod_1.z.string().min(1),
30
+ /** chatbee_id → platform dept id (or CHANNEL_DEFAULT_DEPT_SENTINEL). */
31
+ map: zod_1.z.record(zod_1.z.string(), zod_1.z.string().min(1)),
32
+ });
@@ -11,6 +11,17 @@ export const CHATBEE_IMPORT_FIRESTORE_PATH =
11
11
  export const CHATBEE_IMPORT_PUBSUB_TOPIC =
12
12
  "app_nexus_chatbee-import_request" as const;
13
13
 
14
+ /**
15
+ * Per-(tenant, channel) department mapping doc, keyed `{tenant}__{channelId}`.
16
+ * Holds the operator's resolution of unresolved Chatbee departments before a
17
+ * real import is allowed to run.
18
+ */
19
+ export const CHATBEE_IMPORT_DEPT_MAP_PATH =
20
+ "core/nexus/apps/nex-tenants/chatbee_import_dept_maps" as const;
21
+
22
+ /** Mapping sentinel: route this Chatbee department to the channel's default. */
23
+ export const CHANNEL_DEFAULT_DEPT_SENTINEL = "__channel_default__" as const;
24
+
14
25
  // ── Enums / Literals ──
15
26
 
16
27
  export type ChatbeeImportStatus =
@@ -19,12 +30,26 @@ export type ChatbeeImportStatus =
19
30
  | "done"
20
31
  | "failed";
21
32
 
33
+ /** A job runs either as a read-only analysis pass or the real import. */
34
+ export type ChatbeeImportMode = "analyze" | "import";
35
+
22
36
  export type ChatbeeImportAuditAction =
23
37
  | "import_requested"
24
38
  | "import_started"
25
39
  | "import_page"
26
40
  | "import_completed"
27
- | "import_failed";
41
+ | "import_failed"
42
+ | "analyze_requested"
43
+ | "analyze_completed"
44
+ | "dept_map_saved";
45
+
46
+ /** Resolution status of one Chatbee department against the platform. */
47
+ export type ChatbeeDeptResolutionStatus =
48
+ | "resolved" // external_id → dept exists, chat_enabled, has dept inbox sub
49
+ | "resolved_no_inbox" // dept exists but no dept inbox subscription (ticket invisible)
50
+ | "missing" // external_id set but dept doc does not exist
51
+ | "no_external_id" // external_id NULL (no correlation possible)
52
+ | "mapped"; // operator mapped chatbee_id → platform dept (or channel default)
28
53
 
29
54
  // ── Sub-interfaces ──
30
55
 
@@ -56,6 +81,12 @@ export interface IChatbeeImportCounters {
56
81
  contacts_reconciled: number;
57
82
  reconcile_conflicts: number;
58
83
  pages_processed: number;
84
+ /** Distinct Chatbee departments seen across the run. */
85
+ departments_found: number;
86
+ /** Departments still blocking (not resolved nor mapped). */
87
+ departments_unresolved: number;
88
+ /** Lead links dropped because the referenced lead was missing/deleted. */
89
+ lead_links_missing: number;
59
90
  }
60
91
 
61
92
  export interface IChatbeeImportError {
@@ -69,14 +100,74 @@ export interface IChatbeeImportRequestedBy {
69
100
  loginId: string;
70
101
  }
71
102
 
103
+ // ── Analysis sub-interfaces ──
104
+
105
+ /** Resolution of one DISTINCT Chatbee department against the platform. */
106
+ export interface IChatbeeDepartmentResolution {
107
+ /** Chatbee department numeric id (stable) — the mapping key. */
108
+ chatbee_id: string;
109
+ /** Chatbee department external_id (may be null → no platform correlation). */
110
+ external_id: string | null;
111
+ /** Chatbee department display name. */
112
+ name: string;
113
+ /** Distinct attendances seen under this department in the window. */
114
+ event_count: number;
115
+ tickets_open: number;
116
+ tickets_closed: number;
117
+ status: ChatbeeDeptResolutionStatus;
118
+ /**
119
+ * The platform department this resolves to: the validated `external_id` for
120
+ * `resolved`/`resolved_no_inbox`, or the operator mapping target for `mapped`
121
+ * (a platform dept id, or CHANNEL_DEFAULT_DEPT_SENTINEL). Null while blocking.
122
+ */
123
+ mapped_to?: string | null;
124
+ }
125
+
126
+ /** Read-only pre-import analysis report, stored on the job-doc. */
127
+ export interface IChatbeeImportAnalysis {
128
+ departments: IChatbeeDepartmentResolution[];
129
+ channel_default: {
130
+ department_id: string | null;
131
+ exists: boolean;
132
+ chat_enabled: boolean;
133
+ has_inbox: boolean;
134
+ /** True when the default is usable (exists + chat_enabled + has_inbox). */
135
+ valid: boolean;
136
+ };
137
+ lead_links: {
138
+ contacts_total: number;
139
+ /** Contacts whose contact.lead_id points at a missing/deleted lead. */
140
+ lead_missing_count: number;
141
+ };
142
+ contacts: {
143
+ total: number;
144
+ /** wa-index already points at a different contact (kept Chatbee canonical). */
145
+ index_conflicts: number;
146
+ };
147
+ /** True if any department is unresolved/unmapped or the channel default is invalid. */
148
+ blocking: boolean;
149
+ analyzed_at?: Date | null;
150
+ }
151
+
152
+ /**
153
+ * Operator's department mapping: Chatbee department id → platform department id
154
+ * (or CHANNEL_DEFAULT_DEPT_SENTINEL). Stored per (tenant, channel) at
155
+ * CHATBEE_IMPORT_DEPT_MAP_PATH/{tenant}__{channelId}.
156
+ */
157
+ export type IChatbeeImportDeptMapping = Record<string, string>;
158
+
72
159
  // ── Main Document ──
73
160
 
74
161
  export interface IChatbeeImportDoc extends IFireGlobalDoc {
75
162
  tenant: string;
76
163
  /** Destination hub-waba channel id — threads/tickets/messages carry this channel_id. */
77
164
  destination_channel_id: string;
165
+ /** 'analyze' = read-only pre-import pass; 'import' = the real import. */
166
+ mode?: ChatbeeImportMode;
78
167
  /** Count-only preview: zero Firestore/PubSub/BQ writes, projects counters + totals. */
79
168
  dry_run: boolean;
169
+ /** Read-only analysis report (set when mode === 'analyze'). */
170
+ analysis?: IChatbeeImportAnalysis | null;
80
171
  requested_by: IChatbeeImportRequestedBy;
81
172
  requested_at: Date | null;
82
173
  started_at?: Date | null;
@@ -105,12 +196,34 @@ export type IChatbeeImportRequestPayload = z.infer<
105
196
  typeof ChatbeeImportRequestSchema
106
197
  >;
107
198
 
199
+ export const ChatbeeImportAnalyzeSchema = z.object({
200
+ tenant: z.string().min(1),
201
+ destination_channel_id: z.string().min(1),
202
+ });
203
+
204
+ export type IChatbeeImportAnalyzePayload = z.infer<
205
+ typeof ChatbeeImportAnalyzeSchema
206
+ >;
207
+
208
+ export const ChatbeeImportSaveDeptMapSchema = z.object({
209
+ tenant: z.string().min(1),
210
+ channel_id: z.string().min(1),
211
+ /** chatbee_id → platform dept id (or CHANNEL_DEFAULT_DEPT_SENTINEL). */
212
+ map: z.record(z.string(), z.string().min(1)),
213
+ });
214
+
215
+ export type IChatbeeImportSaveDeptMapPayload = z.infer<
216
+ typeof ChatbeeImportSaveDeptMapSchema
217
+ >;
218
+
108
219
  // ── PubSub Event ──
109
220
 
110
221
  export interface IChatbeeImportPubSubEvent {
111
222
  importId: string;
112
223
  tenant: string;
113
224
  destination_channel_id: string;
225
+ /** 'analyze' = read-only pass; 'import' = real import. */
226
+ mode: ChatbeeImportMode;
114
227
  dry_run: boolean;
115
228
  /** Cursor to resume from on a self-continuation; null/absent for the first run. */
116
229
  cursor?: IChatbeeImportCursor | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evo360-types",
3
- "version": "1.3.391",
3
+ "version": "1.3.394",
4
4
  "description": "HREVO360 Shared Types",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",