evo360-types 1.3.656 → 1.3.659

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,153 @@
1
+ import { z } from "zod";
2
+ import { zFireDocSchema, zFirestoreDateSchema } from "../shared/zod-schemas";
3
+ import { getModuleDefinition } from "../../types/evo-modules/catalog";
4
+
5
+ // ======================================================
6
+ // evo-modules (feat-263) — zod schemas
7
+ //
8
+ // Espelham `ModuleAction`, `ModuleStepKind`, `ModuleStepStatus` e os shapes de
9
+ // `types/evo-modules/index.ts`. As listas são à mão: manter os dois lados
10
+ // sincronizados (o teste do catálogo confere).
11
+ // ======================================================
12
+
13
+ export const zModuleActionSchema = z.enum(["enable", "disable", "trial", "override"]);
14
+
15
+ export const zModuleStepKindSchema = z.enum([
16
+ "entitlement",
17
+ "rbac_group",
18
+ "rbac_recompute",
19
+ "contract_allowance",
20
+ "contract_check",
21
+ "rollout_check",
22
+ ]);
23
+
24
+ export const zModuleStepStatusSchema = z.enum([
25
+ "pending",
26
+ "noop",
27
+ "change",
28
+ "done",
29
+ "failed",
30
+ "blocked",
31
+ "skipped",
32
+ ]);
33
+
34
+ export const zModuleRunModeSchema = z.enum(["plan", "apply"]);
35
+
36
+ export const zModuleRunStatusSchema = z.enum([
37
+ "planned",
38
+ "running",
39
+ "done",
40
+ "failed",
41
+ "blocked",
42
+ "superseded",
43
+ ]);
44
+
45
+ export const zModuleChangeParamsSchema = z.object({
46
+ expires_at: zFirestoreDateSchema.nullable().optional(),
47
+ override_until: zFirestoreDateSchema.nullable().optional(),
48
+ allow_without_contract: z.boolean().optional(),
49
+ purge_rbac: z.boolean().optional(),
50
+ suspend: z.boolean().optional(),
51
+ });
52
+
53
+ const zModuleKeyInCatalog = z
54
+ .string()
55
+ .min(1)
56
+ .refine((k) => getModuleDefinition(k) !== null, { message: "module is not in PLATFORM_MODULE_CATALOG" });
57
+
58
+ /** Body de `nex_tenants-planModuleChange` (e entrada do runner). */
59
+ export const zModuleChangeRequestSchema = z
60
+ .object({
61
+ tenant: z.string().trim().min(1).max(255),
62
+ module: zModuleKeyInCatalog,
63
+ action: zModuleActionSchema,
64
+ params: zModuleChangeParamsSchema.optional().default({}),
65
+ reason: z.string().trim().min(3).max(1000),
66
+ })
67
+ .superRefine((req, ctx) => {
68
+ const now = Date.now();
69
+ if (req.action === "trial") {
70
+ const exp = req.params.expires_at;
71
+ if (!exp) {
72
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["params", "expires_at"], message: "trial requires params.expires_at" });
73
+ } else if (exp.getTime() <= now) {
74
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["params", "expires_at"], message: "params.expires_at must be in the future" });
75
+ }
76
+ }
77
+ if (req.action === "override") {
78
+ const until = req.params.override_until;
79
+ if (!until) {
80
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["params", "override_until"], message: "override requires params.override_until" });
81
+ } else if (until.getTime() <= now) {
82
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["params", "override_until"], message: "params.override_until must be in the future" });
83
+ }
84
+ }
85
+ });
86
+
87
+ /** Body de `nex_tenants-applyModuleChange`: exatamente um de `plan_run_id` / `resume_run_id`. */
88
+ export const zModuleApplyRequestSchema = z
89
+ .object({
90
+ tenant: z.string().trim().min(1).max(255),
91
+ plan_run_id: z.string().trim().min(1).optional(),
92
+ resume_run_id: z.string().trim().min(1).optional(),
93
+ params: z.object({ allow_without_contract: z.boolean().optional() }).optional(),
94
+ })
95
+ .superRefine((req, ctx) => {
96
+ const n = (req.plan_run_id ? 1 : 0) + (req.resume_run_id ? 1 : 0);
97
+ if (n !== 1) {
98
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["plan_run_id"], message: "exactly one of plan_run_id / resume_run_id is required" });
99
+ }
100
+ });
101
+
102
+ /** Body de `nex_tenants-getModuleOverview`. */
103
+ export const zModuleOverviewRequestSchema = z.object({
104
+ tenant: z.string().trim().min(1).max(255),
105
+ });
106
+
107
+ export const zModuleProvisioningStepSchema = z.object({
108
+ kind: zModuleStepKindSchema,
109
+ status: zModuleStepStatusSchema,
110
+ summary: z.string(),
111
+ before: z.unknown().optional(),
112
+ after: z.unknown().optional(),
113
+ error: z.string().nullable().optional(),
114
+ started_at: zFirestoreDateSchema.nullable().optional(),
115
+ finished_at: zFirestoreDateSchema.nullable().optional(),
116
+ });
117
+
118
+ export const zModuleRunActorSchema = z.object({
119
+ type: z.enum(["user", "system", "script"]),
120
+ id: z.string().optional(),
121
+ login_id: z.string().optional(),
122
+ });
123
+
124
+ export const zModuleProvisioningRunSchema = zFireDocSchema
125
+ .extend({
126
+ tenant: z.string().min(1),
127
+ module: z.string().min(1),
128
+ action: zModuleActionSchema,
129
+ mode: zModuleRunModeSchema,
130
+ status: zModuleRunStatusSchema,
131
+ params: zModuleChangeParamsSchema,
132
+ plan_hash: z.string(),
133
+ types_version: z.string(),
134
+ steps: z.array(zModuleProvisioningStepSchema),
135
+ actor: zModuleRunActorSchema,
136
+ reason: z.string(),
137
+ resumed_from: z.string().nullable().optional(),
138
+ plan_run_id: z.string().nullable().optional(),
139
+ error: z.string().nullable().optional(),
140
+ finished_at: zFirestoreDateSchema.nullable().optional(),
141
+ created_at: zFirestoreDateSchema.nullable().optional(),
142
+ updated_at: zFirestoreDateSchema.nullable().optional(),
143
+ })
144
+ .passthrough();
145
+
146
+ export const zModuleRolloutDocSchema = z
147
+ .object({
148
+ flags: z.record(z.string(), z.boolean()).default({}),
149
+ emergency_kill: z.boolean().default(false),
150
+ updated_at: zFirestoreDateSchema.nullable().optional(),
151
+ updated_by: z.string().optional(),
152
+ })
153
+ .passthrough();
package/dist/index.d.ts CHANGED
@@ -36,6 +36,7 @@ export * from "./types/evo-csq";
36
36
  export * from "./types/evo-mcp";
37
37
  export * from "./types/evo-reports";
38
38
  export * from "./types/evo-telemedicine";
39
+ export * from "./types/evo-modules";
39
40
  export * from "./apps/shared/zod-schemas";
40
41
  export * from "./apps/evo-core/zod-schemas";
41
42
  export * from "./apps/evo-tenant/zod-schemas";
@@ -78,3 +79,4 @@ export * from "./apps/evo-task-runner/zod-schemas";
78
79
  export * from "./apps/hub-automation/zod-schemas";
79
80
  export * from "./apps/evo-integrations/zod-schemas";
80
81
  export * from "./apps/evo-telemedicine/zod-schemas";
82
+ export * from "./apps/evo-modules/zod-schemas";
package/dist/index.js CHANGED
@@ -53,6 +53,7 @@ __exportStar(require("./types/evo-csq"), exports);
53
53
  __exportStar(require("./types/evo-mcp"), exports);
54
54
  __exportStar(require("./types/evo-reports"), exports);
55
55
  __exportStar(require("./types/evo-telemedicine"), exports);
56
+ __exportStar(require("./types/evo-modules"), exports);
56
57
  // zod schemas
57
58
  __exportStar(require("./apps/shared/zod-schemas"), exports);
58
59
  __exportStar(require("./apps/evo-core/zod-schemas"), exports);
@@ -96,3 +97,4 @@ __exportStar(require("./apps/evo-task-runner/zod-schemas"), exports);
96
97
  __exportStar(require("./apps/hub-automation/zod-schemas"), exports);
97
98
  __exportStar(require("./apps/evo-integrations/zod-schemas"), exports);
98
99
  __exportStar(require("./apps/evo-telemedicine/zod-schemas"), exports);
100
+ __exportStar(require("./apps/evo-modules/zod-schemas"), exports);
package/dist/index.ts CHANGED
@@ -37,6 +37,7 @@ export * from "./types/evo-csq";
37
37
  export * from "./types/evo-mcp";
38
38
  export * from "./types/evo-reports";
39
39
  export * from "./types/evo-telemedicine";
40
+ export * from "./types/evo-modules";
40
41
 
41
42
  // zod schemas
42
43
  export * from "./apps/shared/zod-schemas";
@@ -81,3 +82,4 @@ export * from "./apps/evo-task-runner/zod-schemas";
81
82
  export * from "./apps/hub-automation/zod-schemas";
82
83
  export * from "./apps/evo-integrations/zod-schemas";
83
84
  export * from "./apps/evo-telemedicine/zod-schemas";
85
+ export * from "./apps/evo-modules/zod-schemas";
@@ -0,0 +1,46 @@
1
+ import type { ITenantModuleEntitlement } from "../evo-tenant";
2
+ /**
3
+ * Normaliza qualquer forma de data que chega às superfícies: `Date`, Firestore
4
+ * `Timestamp` (`toDate()`), Timestamp serializado (`{_seconds}` / `{seconds}`),
5
+ * ISO string e epoch em ms. Inválido ⇒ `null` (equivale a "sem data").
6
+ */
7
+ export declare function moduleDateToMillis(value: unknown): number | null;
8
+ /**
9
+ * Decide se o entitlement autoriza um uso NOVO do módulo. Função pura e ÚNICA:
10
+ * backend (`Core.Tenant.isModuleActive`), web-app e Nexus usam esta; o Flutter
11
+ * espelha e prova pela fixture `fixtures/tenant-modules/is-module-active.parity.json`.
12
+ *
13
+ * Ordem das cláusulas (importa):
14
+ * 1. `override_until` futuro COM `override_by` libera (D6: exceção operacional
15
+ * temporária e auditada) — mesmo suspenso/cancelado/vencido;
16
+ * 2. `enabled !== true` bloqueia;
17
+ * 3. `suspended`/`cancelled` bloqueiam;
18
+ * 4. `effective_at` no futuro bloqueia;
19
+ * 5. `expires_at` no passado (ou agora) bloqueia, para QUALQUER status;
20
+ * 6. só `active` e `trial` liberam.
21
+ *
22
+ * Não tem parâmetro de papel: super admin não ignora entitlement (D6).
23
+ */
24
+ export declare function isModuleActive(entitlement: ITenantModuleEntitlement | null | undefined, now?: Date): boolean;
25
+ /**
26
+ * Capability ausente, não-booleana (`"true"`, `1`) ou `false` equivale a `false`.
27
+ * Não implica módulo ativo: o gate de UI equivalente ao backend é
28
+ * `isModuleActive(e) && hasModuleCapability(e, cap)`.
29
+ */
30
+ export declare function hasModuleCapability(entitlement: ITenantModuleEntitlement | null | undefined, capability: string): boolean;
31
+ export interface IModuleContractMismatch {
32
+ module: string;
33
+ metric: string;
34
+ kind: "active_without_allowance" | "allowance_without_module";
35
+ }
36
+ /**
37
+ * Divergências entre franquias do contrato (`allowances`) e módulos do tenant,
38
+ * pelas `allowance_metrics` do catálogo. Só informa (a tela de contrato mostra);
39
+ * nada sincroniza sozinho (Q1).
40
+ *
41
+ * - `active_without_allowance`: módulo ativo e nenhuma métrica dele no contrato
42
+ * (consumo invisível ao alerta e ao portal);
43
+ * - `allowance_without_module`: contrato tem franquia de uma métrica do módulo e
44
+ * o módulo não está ativo.
45
+ */
46
+ export declare function moduleContractMismatches(allowances: Record<string, unknown> | null | undefined, modules: Record<string, ITenantModuleEntitlement> | null | undefined, now?: Date): IModuleContractMismatch[];
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.moduleDateToMillis = moduleDateToMillis;
4
+ exports.isModuleActive = isModuleActive;
5
+ exports.hasModuleCapability = hasModuleCapability;
6
+ exports.moduleContractMismatches = moduleContractMismatches;
7
+ const catalog_1 = require("./catalog");
8
+ /**
9
+ * Normaliza qualquer forma de data que chega às superfícies: `Date`, Firestore
10
+ * `Timestamp` (`toDate()`), Timestamp serializado (`{_seconds}` / `{seconds}`),
11
+ * ISO string e epoch em ms. Inválido ⇒ `null` (equivale a "sem data").
12
+ */
13
+ function moduleDateToMillis(value) {
14
+ if (value === null || value === undefined || value === "")
15
+ return null;
16
+ if (value instanceof Date) {
17
+ const t = value.getTime();
18
+ return isNaN(t) ? null : t;
19
+ }
20
+ if (typeof value === "number")
21
+ return isFinite(value) ? value : null;
22
+ if (typeof value === "string") {
23
+ const t = new Date(value).getTime();
24
+ return isNaN(t) ? null : t;
25
+ }
26
+ if (typeof value === "object") {
27
+ const v = value;
28
+ if (typeof v.toDate === "function") {
29
+ try {
30
+ const d = v.toDate();
31
+ if (d instanceof Date) {
32
+ const t = d.getTime();
33
+ return isNaN(t) ? null : t;
34
+ }
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ return null;
40
+ }
41
+ const seconds = typeof v._seconds === "number" ? v._seconds : typeof v.seconds === "number" ? v.seconds : null;
42
+ if (seconds !== null && isFinite(seconds)) {
43
+ const nanos = typeof v._nanoseconds === "number" ? v._nanoseconds : typeof v.nanoseconds === "number" ? v.nanoseconds : 0;
44
+ return seconds * 1000 + Math.floor(nanos / 1e6);
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ /**
50
+ * Decide se o entitlement autoriza um uso NOVO do módulo. Função pura e ÚNICA:
51
+ * backend (`Core.Tenant.isModuleActive`), web-app e Nexus usam esta; o Flutter
52
+ * espelha e prova pela fixture `fixtures/tenant-modules/is-module-active.parity.json`.
53
+ *
54
+ * Ordem das cláusulas (importa):
55
+ * 1. `override_until` futuro COM `override_by` libera (D6: exceção operacional
56
+ * temporária e auditada) — mesmo suspenso/cancelado/vencido;
57
+ * 2. `enabled !== true` bloqueia;
58
+ * 3. `suspended`/`cancelled` bloqueiam;
59
+ * 4. `effective_at` no futuro bloqueia;
60
+ * 5. `expires_at` no passado (ou agora) bloqueia, para QUALQUER status;
61
+ * 6. só `active` e `trial` liberam.
62
+ *
63
+ * Não tem parâmetro de papel: super admin não ignora entitlement (D6).
64
+ */
65
+ function isModuleActive(entitlement, now = new Date()) {
66
+ if (!entitlement || typeof entitlement !== "object")
67
+ return false;
68
+ const t = now.getTime();
69
+ const overrideUntil = moduleDateToMillis(entitlement.override_until);
70
+ if (overrideUntil !== null && overrideUntil > t && !!entitlement.override_by)
71
+ return true;
72
+ if (entitlement.enabled !== true)
73
+ return false;
74
+ if (entitlement.status === "suspended" || entitlement.status === "cancelled")
75
+ return false;
76
+ const effectiveAt = moduleDateToMillis(entitlement.effective_at);
77
+ if (effectiveAt !== null && effectiveAt > t)
78
+ return false;
79
+ const expiresAt = moduleDateToMillis(entitlement.expires_at);
80
+ if (expiresAt !== null && expiresAt <= t)
81
+ return false;
82
+ return entitlement.status === "active" || entitlement.status === "trial";
83
+ }
84
+ /**
85
+ * Capability ausente, não-booleana (`"true"`, `1`) ou `false` equivale a `false`.
86
+ * Não implica módulo ativo: o gate de UI equivalente ao backend é
87
+ * `isModuleActive(e) && hasModuleCapability(e, cap)`.
88
+ */
89
+ function hasModuleCapability(entitlement, capability) {
90
+ if (!entitlement || !entitlement.capabilities)
91
+ return false;
92
+ return entitlement.capabilities[capability] === true;
93
+ }
94
+ /**
95
+ * Divergências entre franquias do contrato (`allowances`) e módulos do tenant,
96
+ * pelas `allowance_metrics` do catálogo. Só informa (a tela de contrato mostra);
97
+ * nada sincroniza sozinho (Q1).
98
+ *
99
+ * - `active_without_allowance`: módulo ativo e nenhuma métrica dele no contrato
100
+ * (consumo invisível ao alerta e ao portal);
101
+ * - `allowance_without_module`: contrato tem franquia de uma métrica do módulo e
102
+ * o módulo não está ativo.
103
+ */
104
+ function moduleContractMismatches(allowances, modules, now = new Date()) {
105
+ const out = [];
106
+ const allow = allowances && typeof allowances === "object" ? allowances : {};
107
+ const mods = modules && typeof modules === "object" ? modules : {};
108
+ for (const key in catalog_1.PLATFORM_MODULE_CATALOG) {
109
+ if (!Object.prototype.hasOwnProperty.call(catalog_1.PLATFORM_MODULE_CATALOG, key))
110
+ continue;
111
+ const def = catalog_1.PLATFORM_MODULE_CATALOG[key];
112
+ if (def.allowance_metrics.length === 0)
113
+ continue;
114
+ const active = isModuleActive(Object.prototype.hasOwnProperty.call(mods, key) ? mods[key] : null, now);
115
+ const present = [];
116
+ for (const m of def.allowance_metrics) {
117
+ if (Object.prototype.hasOwnProperty.call(allow, m.metric) && allow[m.metric] !== null && allow[m.metric] !== undefined) {
118
+ present.push(m.metric);
119
+ }
120
+ }
121
+ if (active && present.length === 0) {
122
+ out.push({ module: key, metric: def.allowance_metrics[0].metric, kind: "active_without_allowance" });
123
+ }
124
+ if (!active) {
125
+ for (const metric of present) {
126
+ out.push({ module: key, metric, kind: "allowance_without_module" });
127
+ }
128
+ }
129
+ }
130
+ return out;
131
+ }
@@ -0,0 +1,150 @@
1
+ import type { ITenantModuleEntitlement } from "../evo-tenant";
2
+ import { PLATFORM_MODULE_CATALOG } from "./catalog";
3
+
4
+ /**
5
+ * Normaliza qualquer forma de data que chega às superfícies: `Date`, Firestore
6
+ * `Timestamp` (`toDate()`), Timestamp serializado (`{_seconds}` / `{seconds}`),
7
+ * ISO string e epoch em ms. Inválido ⇒ `null` (equivale a "sem data").
8
+ */
9
+ export function moduleDateToMillis(value: unknown): number | null {
10
+ if (value === null || value === undefined || value === "") return null;
11
+ if (value instanceof Date) {
12
+ const t = value.getTime();
13
+ return isNaN(t) ? null : t;
14
+ }
15
+ if (typeof value === "number") return isFinite(value) ? value : null;
16
+ if (typeof value === "string") {
17
+ const t = new Date(value).getTime();
18
+ return isNaN(t) ? null : t;
19
+ }
20
+ if (typeof value === "object") {
21
+ const v = value as {
22
+ toDate?: unknown;
23
+ _seconds?: unknown;
24
+ seconds?: unknown;
25
+ _nanoseconds?: unknown;
26
+ nanoseconds?: unknown;
27
+ };
28
+ if (typeof v.toDate === "function") {
29
+ try {
30
+ const d = (v.toDate as () => unknown)();
31
+ if (d instanceof Date) {
32
+ const t = d.getTime();
33
+ return isNaN(t) ? null : t;
34
+ }
35
+ } catch {
36
+ return null;
37
+ }
38
+ return null;
39
+ }
40
+ const seconds = typeof v._seconds === "number" ? v._seconds : typeof v.seconds === "number" ? v.seconds : null;
41
+ if (seconds !== null && isFinite(seconds)) {
42
+ const nanos =
43
+ typeof v._nanoseconds === "number" ? v._nanoseconds : typeof v.nanoseconds === "number" ? v.nanoseconds : 0;
44
+ return seconds * 1000 + Math.floor(nanos / 1e6);
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+
50
+ /**
51
+ * Decide se o entitlement autoriza um uso NOVO do módulo. Função pura e ÚNICA:
52
+ * backend (`Core.Tenant.isModuleActive`), web-app e Nexus usam esta; o Flutter
53
+ * espelha e prova pela fixture `fixtures/tenant-modules/is-module-active.parity.json`.
54
+ *
55
+ * Ordem das cláusulas (importa):
56
+ * 1. `override_until` futuro COM `override_by` libera (D6: exceção operacional
57
+ * temporária e auditada) — mesmo suspenso/cancelado/vencido;
58
+ * 2. `enabled !== true` bloqueia;
59
+ * 3. `suspended`/`cancelled` bloqueiam;
60
+ * 4. `effective_at` no futuro bloqueia;
61
+ * 5. `expires_at` no passado (ou agora) bloqueia, para QUALQUER status;
62
+ * 6. só `active` e `trial` liberam.
63
+ *
64
+ * Não tem parâmetro de papel: super admin não ignora entitlement (D6).
65
+ */
66
+ export function isModuleActive(
67
+ entitlement: ITenantModuleEntitlement | null | undefined,
68
+ now: Date = new Date(),
69
+ ): boolean {
70
+ if (!entitlement || typeof entitlement !== "object") return false;
71
+
72
+ const t = now.getTime();
73
+
74
+ const overrideUntil = moduleDateToMillis(entitlement.override_until);
75
+ if (overrideUntil !== null && overrideUntil > t && !!entitlement.override_by) return true;
76
+
77
+ if (entitlement.enabled !== true) return false;
78
+ if (entitlement.status === "suspended" || entitlement.status === "cancelled") return false;
79
+
80
+ const effectiveAt = moduleDateToMillis(entitlement.effective_at);
81
+ if (effectiveAt !== null && effectiveAt > t) return false;
82
+
83
+ const expiresAt = moduleDateToMillis(entitlement.expires_at);
84
+ if (expiresAt !== null && expiresAt <= t) return false;
85
+
86
+ return entitlement.status === "active" || entitlement.status === "trial";
87
+ }
88
+
89
+ /**
90
+ * Capability ausente, não-booleana (`"true"`, `1`) ou `false` equivale a `false`.
91
+ * Não implica módulo ativo: o gate de UI equivalente ao backend é
92
+ * `isModuleActive(e) && hasModuleCapability(e, cap)`.
93
+ */
94
+ export function hasModuleCapability(
95
+ entitlement: ITenantModuleEntitlement | null | undefined,
96
+ capability: string,
97
+ ): boolean {
98
+ if (!entitlement || !entitlement.capabilities) return false;
99
+ return entitlement.capabilities[capability] === true;
100
+ }
101
+
102
+ export interface IModuleContractMismatch {
103
+ module: string;
104
+ metric: string;
105
+ kind: "active_without_allowance" | "allowance_without_module";
106
+ }
107
+
108
+ /**
109
+ * Divergências entre franquias do contrato (`allowances`) e módulos do tenant,
110
+ * pelas `allowance_metrics` do catálogo. Só informa (a tela de contrato mostra);
111
+ * nada sincroniza sozinho (Q1).
112
+ *
113
+ * - `active_without_allowance`: módulo ativo e nenhuma métrica dele no contrato
114
+ * (consumo invisível ao alerta e ao portal);
115
+ * - `allowance_without_module`: contrato tem franquia de uma métrica do módulo e
116
+ * o módulo não está ativo.
117
+ */
118
+ export function moduleContractMismatches(
119
+ allowances: Record<string, unknown> | null | undefined,
120
+ modules: Record<string, ITenantModuleEntitlement> | null | undefined,
121
+ now: Date = new Date(),
122
+ ): IModuleContractMismatch[] {
123
+ const out: IModuleContractMismatch[] = [];
124
+ const allow = allowances && typeof allowances === "object" ? allowances : {};
125
+ const mods = modules && typeof modules === "object" ? modules : {};
126
+
127
+ for (const key in PLATFORM_MODULE_CATALOG) {
128
+ if (!Object.prototype.hasOwnProperty.call(PLATFORM_MODULE_CATALOG, key)) continue;
129
+ const def = PLATFORM_MODULE_CATALOG[key];
130
+ if (def.allowance_metrics.length === 0) continue;
131
+
132
+ const active = isModuleActive(Object.prototype.hasOwnProperty.call(mods, key) ? mods[key] : null, now);
133
+ const present: string[] = [];
134
+ for (const m of def.allowance_metrics) {
135
+ if (Object.prototype.hasOwnProperty.call(allow, m.metric) && allow[m.metric] !== null && allow[m.metric] !== undefined) {
136
+ present.push(m.metric);
137
+ }
138
+ }
139
+
140
+ if (active && present.length === 0) {
141
+ out.push({ module: key, metric: def.allowance_metrics[0].metric, kind: "active_without_allowance" });
142
+ }
143
+ if (!active) {
144
+ for (const metric of present) {
145
+ out.push({ module: key, metric, kind: "allowance_without_module" });
146
+ }
147
+ }
148
+ }
149
+ return out;
150
+ }
@@ -0,0 +1,28 @@
1
+ import type { IPlatformModuleDefinition } from "./index";
2
+ /**
3
+ * Versão do catálogo, gravada em `IModuleProvisioningRun.types_version`.
4
+ * Mudou capability, passo, grupo ou métrica de algum módulo ⇒ incrementar: o
5
+ * run antigo precisa dizer com que catálogo foi planejado.
6
+ */
7
+ export declare const PLATFORM_MODULE_CATALOG_VERSION = "2026-10-06.1";
8
+ export declare const PLATFORM_MODULE_TELEMEDICINE = "telemedicine";
9
+ export declare const PLATFORM_MODULE_CSQ = "csq";
10
+ /**
11
+ * Catálogo de módulos da plataforma (feat-263). É código, não Firestore: carrega
12
+ * comportamento (passos, grupos, métricas) e precisa de versionamento e tipos.
13
+ *
14
+ * Módulo novo = uma entrada aqui + publish. O estado por tenant continua em
15
+ * `tenants/{t}.modules[key]`.
16
+ */
17
+ export declare const PLATFORM_MODULE_CATALOG: Readonly<Record<string, IPlatformModuleDefinition>>;
18
+ export declare function getModuleDefinition(key: string | null | undefined): IPlatformModuleDefinition | null;
19
+ /** Definições em ordem estável (ordem de declaração). */
20
+ export declare function listModuleDefinitions(): IPlatformModuleDefinition[];
21
+ /** Chaves do catálogo em ordem de declaração. */
22
+ export declare function listModuleKeys(): string[];
23
+ /** Capabilities default do módulo (mapa novo a cada chamada). */
24
+ export declare function buildModuleDefaultCapabilities(definition: IPlatformModuleDefinition): Record<string, boolean | number | string>;
25
+ /** Módulos que tenant novo recebe habilitados. */
26
+ export declare function listDefaultModulesForNewTenants(): IPlatformModuleDefinition[];
27
+ /** União, sem repetição e em ordem, dos grupos RBAC dos módulos default de tenant novo. */
28
+ export declare function listDefaultTenantRbacGroups(): string[];
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PLATFORM_MODULE_CATALOG = exports.PLATFORM_MODULE_CSQ = exports.PLATFORM_MODULE_TELEMEDICINE = exports.PLATFORM_MODULE_CATALOG_VERSION = void 0;
4
+ exports.getModuleDefinition = getModuleDefinition;
5
+ exports.listModuleDefinitions = listModuleDefinitions;
6
+ exports.listModuleKeys = listModuleKeys;
7
+ exports.buildModuleDefaultCapabilities = buildModuleDefaultCapabilities;
8
+ exports.listDefaultModulesForNewTenants = listDefaultModulesForNewTenants;
9
+ exports.listDefaultTenantRbacGroups = listDefaultTenantRbacGroups;
10
+ /**
11
+ * Versão do catálogo, gravada em `IModuleProvisioningRun.types_version`.
12
+ * Mudou capability, passo, grupo ou métrica de algum módulo ⇒ incrementar: o
13
+ * run antigo precisa dizer com que catálogo foi planejado.
14
+ */
15
+ exports.PLATFORM_MODULE_CATALOG_VERSION = "2026-10-06.1";
16
+ exports.PLATFORM_MODULE_TELEMEDICINE = "telemedicine";
17
+ exports.PLATFORM_MODULE_CSQ = "csq";
18
+ /**
19
+ * Catálogo de módulos da plataforma (feat-263). É código, não Firestore: carrega
20
+ * comportamento (passos, grupos, métricas) e precisa de versionamento e tipos.
21
+ *
22
+ * Módulo novo = uma entrada aqui + publish. O estado por tenant continua em
23
+ * `tenants/{t}.modules[key]`.
24
+ */
25
+ exports.PLATFORM_MODULE_CATALOG = {
26
+ telemedicine: {
27
+ key: exports.PLATFORM_MODULE_TELEMEDICINE,
28
+ label: "Virtual Care (Telemedicina)",
29
+ description: "Teleconsultas por vídeo integradas à agenda, com gravação e admissão automática.",
30
+ app_id: "evo-telemedicine",
31
+ // Defaults idênticos ao `TELEMEDICINE_DEFAULT_CAPABILITIES` da R5 (feat-160):
32
+ // transcrição NUNCA nasce ligada (gate documental D12).
33
+ capabilities: {
34
+ recording: { label: "Gravação", default: true },
35
+ transcription: { label: "Transcrição", default: false },
36
+ auto_admit: { label: "Admissão automática", default: true },
37
+ },
38
+ // `active/contract` sem `expires_at`: a franquia faz parte do contrato; um
39
+ // `expires_at` desligaria a base inteira numa data, em silêncio.
40
+ default_entitlement: { status: "active", source: "contract" },
41
+ default_for_new_tenants: true,
42
+ rbac_groups: ["evo-telemedicine"],
43
+ allowance_metrics: [{ metric: "teleconsultations_unrecorded", default_included: 5 }],
44
+ rollout: {
45
+ path: "platform/evo-telemedicine/config/rollout",
46
+ layout: "legacy_fields",
47
+ flags: {
48
+ create: "create_enabled",
49
+ join: "join_enabled",
50
+ recording: "recording_enabled",
51
+ transcription: "transcription_enabled",
52
+ },
53
+ },
54
+ depends_on: [],
55
+ provisioning: {
56
+ enable: ["entitlement", "rbac_group", "rbac_recompute", "contract_allowance", "rollout_check"],
57
+ // O grupo RBAC fica no disable (Q7): o upsell depende de `evo_telemedicine_read`.
58
+ disable: ["entitlement", "contract_check"],
59
+ },
60
+ legacy: { audit_collections: ["apps/evo-telemedicine/entitlement-audit"] },
61
+ },
62
+ csq: {
63
+ key: exports.PLATFORM_MODULE_CSQ,
64
+ label: "CSQ — Qualidade do atendimento",
65
+ description: "Avaliação automática da qualidade das conversas encerradas (laudo por ticket).",
66
+ app_id: "evo-csq",
67
+ capabilities: {},
68
+ default_entitlement: { status: "active", source: "manual" },
69
+ default_for_new_tenants: false,
70
+ rbac_groups: [],
71
+ allowance_metrics: [],
72
+ rollout: null,
73
+ depends_on: [],
74
+ provisioning: {
75
+ enable: ["entitlement"],
76
+ disable: ["entitlement"],
77
+ },
78
+ },
79
+ };
80
+ function getModuleDefinition(key) {
81
+ if (!key || typeof key !== "string")
82
+ return null;
83
+ if (!Object.prototype.hasOwnProperty.call(exports.PLATFORM_MODULE_CATALOG, key))
84
+ return null;
85
+ return exports.PLATFORM_MODULE_CATALOG[key];
86
+ }
87
+ /** Definições em ordem estável (ordem de declaração). */
88
+ function listModuleDefinitions() {
89
+ const out = [];
90
+ for (const key in exports.PLATFORM_MODULE_CATALOG) {
91
+ if (Object.prototype.hasOwnProperty.call(exports.PLATFORM_MODULE_CATALOG, key)) {
92
+ out.push(exports.PLATFORM_MODULE_CATALOG[key]);
93
+ }
94
+ }
95
+ return out;
96
+ }
97
+ /** Chaves do catálogo em ordem de declaração. */
98
+ function listModuleKeys() {
99
+ const out = [];
100
+ for (const key in exports.PLATFORM_MODULE_CATALOG) {
101
+ if (Object.prototype.hasOwnProperty.call(exports.PLATFORM_MODULE_CATALOG, key))
102
+ out.push(key);
103
+ }
104
+ return out;
105
+ }
106
+ /** Capabilities default do módulo (mapa novo a cada chamada). */
107
+ function buildModuleDefaultCapabilities(definition) {
108
+ const out = {};
109
+ for (const cap in definition.capabilities) {
110
+ if (Object.prototype.hasOwnProperty.call(definition.capabilities, cap)) {
111
+ out[cap] = definition.capabilities[cap].default;
112
+ }
113
+ }
114
+ return out;
115
+ }
116
+ /** Módulos que tenant novo recebe habilitados. */
117
+ function listDefaultModulesForNewTenants() {
118
+ return listModuleDefinitions().filter((d) => d.default_for_new_tenants);
119
+ }
120
+ /** União, sem repetição e em ordem, dos grupos RBAC dos módulos default de tenant novo. */
121
+ function listDefaultTenantRbacGroups() {
122
+ const out = [];
123
+ for (const def of listDefaultModulesForNewTenants()) {
124
+ for (const g of def.rbac_groups) {
125
+ if (out.indexOf(g) === -1)
126
+ out.push(g);
127
+ }
128
+ }
129
+ return out;
130
+ }