@fiado/type-kit 3.595.0 → 3.597.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/_test_/unit/retailPromotions/constants.test.ts +1 -2
  2. package/_test_/unit/rewardsEligibility/catalogDtos.test.ts +1 -0
  3. package/_test_/unit/rules/relationContract.test.ts +84 -0
  4. package/_test_/unit/rules/segmentRelation.test.ts +62 -0
  5. package/_test_/unit/rules/traceNested.test.ts +115 -0
  6. package/_test_/unit/rules/validateNodes.test.ts +68 -0
  7. package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.js +0 -1
  8. package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.d.ts +0 -2
  9. package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.js +0 -2
  10. package/bin/rewardsEligibility/dtos/backoffice/ResultTraceResponse.d.ts +28 -1
  11. package/bin/rewardsEligibility/dtos/backoffice/ResultTraceResponse.js +9 -1
  12. package/bin/rewardsEligibility/dtos/backoffice/RuleCatalogResponse.d.ts +16 -0
  13. package/bin/rewardsEligibility/dtos/backoffice/RuleCatalogResponse.js +5 -1
  14. package/bin/rewardsEligibility/enums/RulesRelationCardinalityEnum.d.ts +1 -0
  15. package/bin/rewardsEligibility/enums/RulesRelationCardinalityEnum.js +17 -0
  16. package/bin/rewardsEligibility/enums/RulesRelationTargetEnum.d.ts +1 -0
  17. package/bin/rewardsEligibility/enums/RulesRelationTargetEnum.js +17 -0
  18. package/bin/rewardsEligibility/index.d.ts +2 -0
  19. package/bin/rewardsEligibility/index.js +2 -0
  20. package/bin/rules/constants/RulesRelationLimits.d.ts +15 -0
  21. package/bin/rules/constants/RulesRelationLimits.js +21 -0
  22. package/bin/rules/dtos/RulesCatalogResponse.d.ts +16 -0
  23. package/bin/rules/dtos/RulesCatalogResponse.js +5 -1
  24. package/bin/rules/dtos/RulesTraceResponse.d.ts +34 -1
  25. package/bin/rules/dtos/RulesTraceResponse.js +12 -1
  26. package/bin/rules/dtos/ValidateRulesResponse.d.ts +22 -0
  27. package/bin/rules/dtos/ValidateRulesResponse.js +9 -1
  28. package/bin/rules/dtos/population/SegmentGroup.d.ts +2 -1
  29. package/bin/rules/dtos/population/SegmentGroup.js +2 -0
  30. package/bin/rules/dtos/population/SegmentRelationCondition.d.ts +18 -0
  31. package/bin/rules/dtos/population/SegmentRelationCondition.js +69 -0
  32. package/bin/rules/enums/RulesNodeErrorCodeEnum.d.ts +21 -0
  33. package/bin/rules/enums/RulesNodeErrorCodeEnum.js +25 -0
  34. package/bin/rules/enums/RulesRelationCardinalityEnum.d.ts +5 -0
  35. package/bin/rules/enums/RulesRelationCardinalityEnum.js +9 -0
  36. package/bin/rules/enums/RulesRelationTargetEnum.d.ts +5 -0
  37. package/bin/rules/enums/RulesRelationTargetEnum.js +9 -0
  38. package/bin/rules/enums/SegmentConditionKindEnum.d.ts +2 -1
  39. package/bin/rules/enums/SegmentConditionKindEnum.js +1 -0
  40. package/bin/rules/enums/SegmentRelationQuantifierKindEnum.d.ts +5 -0
  41. package/bin/rules/enums/SegmentRelationQuantifierKindEnum.js +9 -0
  42. package/bin/rules/index.d.ts +6 -0
  43. package/bin/rules/index.js +6 -0
  44. package/package.json +1 -1
  45. package/src/retailPromotions/constants/retailPromotionsErrorHttpStatus.ts +0 -1
  46. package/src/retailPromotions/enums/RetailPromotionsErrorCodeEnum.ts +0 -2
  47. package/src/rewardsEligibility/dtos/backoffice/ResultTraceResponse.ts +23 -1
  48. package/src/rewardsEligibility/dtos/backoffice/RuleCatalogResponse.ts +17 -0
  49. package/src/rewardsEligibility/enums/RulesRelationCardinalityEnum.ts +1 -0
  50. package/src/rewardsEligibility/enums/RulesRelationTargetEnum.ts +1 -0
  51. package/src/rewardsEligibility/index.ts +2 -0
  52. package/src/rules/constants/RulesRelationLimits.ts +18 -0
  53. package/src/rules/dtos/RulesCatalogResponse.ts +17 -0
  54. package/src/rules/dtos/RulesTraceResponse.ts +29 -1
  55. package/src/rules/dtos/ValidateRulesResponse.ts +25 -0
  56. package/src/rules/dtos/population/SegmentGroup.ts +3 -1
  57. package/src/rules/dtos/population/SegmentRelationCondition.ts +25 -0
  58. package/src/rules/enums/RulesNodeErrorCodeEnum.ts +21 -0
  59. package/src/rules/enums/RulesRelationCardinalityEnum.ts +5 -0
  60. package/src/rules/enums/RulesRelationTargetEnum.ts +5 -0
  61. package/src/rules/enums/SegmentConditionKindEnum.ts +1 -0
  62. package/src/rules/enums/SegmentRelationQuantifierKindEnum.ts +5 -0
  63. package/src/rules/index.ts +6 -0
@@ -43,8 +43,7 @@ describe('catálogo de errores', () => {
43
43
  expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.CODE_NOT_FOUND).toBe(404);
44
44
  expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.DISCOUNT_AFTER_QUOTE).toBe(409);
45
45
  expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.CODE_NOT_OWNED).toBe(422);
46
- expect(Object.keys(RetailPromotionsErrorCodeEnum)).toHaveLength(36);
47
- expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.SPLIT_FINANCING_DISABLED).toBe(409);
46
+ expect(Object.keys(RetailPromotionsErrorCodeEnum)).toHaveLength(35);
48
47
  });
49
48
 
50
49
  it('la forma de pago que la promo no acepta es 422', () => {
@@ -11,6 +11,7 @@ describe('DTOs de catálogos (forma)', () => {
11
11
  fields: [{ field: 'accountAgeDays', label: 'Antigüedad', valueType: RuleValueTypeEnum.NUMBER, operators: ['GT', 'LTE'] }],
12
12
  }],
13
13
  operators: { GT: '>', LTE: '≤' },
14
+ relations: [],
14
15
  };
15
16
  expect(catalog.sources[0]?.fields[0]?.valueType).toBe('NUMBER');
16
17
  });
@@ -0,0 +1,84 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import {
4
+ RETURN_FIELD_ID_PATTERN,
5
+ RULES_EXPOSE_AS_PATTERN,
6
+ RULES_MAX_RELATION_BLOCKS,
7
+ RULES_MAX_RELATION_DEPTH,
8
+ RULES_MAX_WITHIN_LAST_DAYS,
9
+ RULES_RELATION_NAME_PATTERN,
10
+ RulesCatalogResponse,
11
+ RulesRelationCardinalityEnum,
12
+ RulesRelationTargetEnum,
13
+ relationReturnFieldId,
14
+ } from '../../../src/rules';
15
+ import {
16
+ RuleCatalogResponse,
17
+ RulesRelationCardinalityEnum as EligibilityCardinality,
18
+ RulesRelationTargetEnum as EligibilityTarget,
19
+ } from '../../../src/rewardsEligibility';
20
+
21
+ describe('contrato de relaciones (rules)', () => {
22
+ it('relationReturnFieldId concatena relación:exposeAs y cumple RETURN_FIELD_ID_PATTERN', () => {
23
+ const id = relationReturnFieldId('referidos', 'referidos_mx_count');
24
+ expect(id).toBe('referidos:referidos_mx_count');
25
+ expect(RETURN_FIELD_ID_PATTERN.test(id)).toBe(true);
26
+ });
27
+
28
+ it('RULES_RELATION_NAME_PATTERN acepta minúsculas con guiones y rechaza el resto', () => {
29
+ expect(RULES_RELATION_NAME_PATTERN.test('referidos')).toBe(true);
30
+ expect(RULES_RELATION_NAME_PATTERN.test('mi-relacion2')).toBe(true);
31
+ expect(RULES_RELATION_NAME_PATTERN.test('Referidos')).toBe(false);
32
+ expect(RULES_RELATION_NAME_PATTERN.test('mx count')).toBe(false);
33
+ });
34
+
35
+ it('RULES_EXPOSE_AS_PATTERN acepta letras/dígitos/guion bajo y rechaza espacios', () => {
36
+ expect(RULES_EXPOSE_AS_PATTERN.test('referidos_mx_count')).toBe(true);
37
+ expect(RULES_EXPOSE_AS_PATTERN.test('Referidos')).toBe(true);
38
+ expect(RULES_EXPOSE_AS_PATTERN.test('mx count')).toBe(false);
39
+ expect(RULES_EXPOSE_AS_PATTERN.test('1abc')).toBe(false);
40
+ });
41
+
42
+ it('topes fijados por el plan', () => {
43
+ expect(RULES_MAX_RELATION_DEPTH).toBe(2);
44
+ expect(RULES_MAX_RELATION_BLOCKS).toBe(5);
45
+ expect(RULES_MAX_WITHIN_LAST_DAYS).toBe(365);
46
+ });
47
+
48
+ it('valores de los enums', () => {
49
+ expect(RulesRelationCardinalityEnum).toEqual({ ONE: 'ONE', MANY: 'MANY' });
50
+ expect(RulesRelationTargetEnum).toEqual({ PERSON: 'PERSON', RECORD: 'RECORD' });
51
+ });
52
+
53
+ it('plainToInstance conserva relations del catálogo de rules', () => {
54
+ const parsed = plainToInstance(RulesCatalogResponse, {
55
+ items: [],
56
+ operators: { WITHIN_LAST_DAYS: 'en los últimos N días' },
57
+ relations: [
58
+ { name: 'referidos', label: 'Referidos', description: 'd', cardinality: 'MANY', target: 'PERSON', maxRelated: 500 },
59
+ { name: 'creditos', label: 'Créditos', description: 'd', cardinality: 'MANY', target: 'RECORD', fields: [] },
60
+ ],
61
+ });
62
+ expect(parsed.relations).toHaveLength(2);
63
+ expect(parsed.relations[0].cardinality).toBe(RulesRelationCardinalityEnum.MANY);
64
+ expect(parsed.relations[0].maxRelated).toBe(500);
65
+ expect(parsed.relations[1].target).toBe(RulesRelationTargetEnum.RECORD);
66
+ });
67
+ });
68
+
69
+ describe('contrato de relaciones (rewardsEligibility)', () => {
70
+ it('reutiliza los enums de rules', () => {
71
+ expect(EligibilityCardinality).toBe(RulesRelationCardinalityEnum);
72
+ expect(EligibilityTarget).toBe(RulesRelationTargetEnum);
73
+ });
74
+
75
+ it('plainToInstance conserva relations del catálogo de eligibility', () => {
76
+ const parsed = plainToInstance(RuleCatalogResponse, {
77
+ sources: [],
78
+ operators: {},
79
+ relations: [{ name: 'referidor', label: 'Referidor', description: 'd', cardinality: 'ONE', target: 'PERSON' }],
80
+ });
81
+ expect(parsed.relations[0].cardinality).toBe(EligibilityCardinality.ONE);
82
+ expect(parsed.relations[0].maxRelated).toBeUndefined();
83
+ });
84
+ });
@@ -0,0 +1,62 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import * as Rules from '../../../src/rules';
5
+
6
+ const filter = { kind: 'COLUMN', table: 'directory', column: 'status', op: 'EQUALS', value: 'ACTIVE' };
7
+ const relation = (over: object = {}): object => ({
8
+ kind: 'RELATION', relation: 'referidos',
9
+ quantifier: { kind: 'COUNT', op: 'GTE', value: 1 },
10
+ filters: [filter], ...over,
11
+ });
12
+ const group = (conditions: object[]): object => ({ match: 'ALL', conditions });
13
+
14
+ const errorsOf = async (plain: object): Promise<number> =>
15
+ (await validate(plainToInstance(Rules.SegmentGroup, plain, { excludeExtraneousValues: true }))).length;
16
+
17
+ describe('SegmentRelationCondition', () => {
18
+ it('enum RELATION y quantifier kinds', () => {
19
+ expect(Rules.SegmentConditionKindEnum.RELATION).toBe('RELATION');
20
+ expect(Object.values(Rules.SegmentRelationQuantifierKindEnum)).toEqual(['ANY', 'ALL', 'COUNT']);
21
+ });
22
+
23
+ it('instancia SegmentRelationCondition por discriminador', () => {
24
+ const g = plainToInstance(Rules.SegmentGroup, group([relation()]), { excludeExtraneousValues: true });
25
+ expect(g.conditions[0]).toBeInstanceOf(Rules.SegmentRelationCondition);
26
+ expect((g.conditions[0] as Rules.SegmentRelationCondition).quantifier).toBeInstanceOf(Rules.SegmentRelationQuantifier);
27
+ });
28
+
29
+ it('COUNT con filtros -> valido', async () => {
30
+ expect(await errorsOf(group([relation()]))).toBe(0);
31
+ });
32
+
33
+ it('ANY / ALL sin op ni value -> valido; ONE sin quantifier -> valido', async () => {
34
+ expect(await errorsOf(group([relation({ quantifier: { kind: 'ANY' } })]))).toBe(0);
35
+ expect(await errorsOf(group([relation({ quantifier: { kind: 'ALL' } })]))).toBe(0);
36
+ expect(await errorsOf(group([relation({ relation: 'referidor', quantifier: undefined })]))).toBe(0);
37
+ });
38
+
39
+ it('COUNT sin op o sin value -> error', async () => {
40
+ expect(await errorsOf(group([relation({ quantifier: { kind: 'COUNT', value: 1 } })]))).toBeGreaterThan(0);
41
+ expect(await errorsOf(group([relation({ quantifier: { kind: 'COUNT', op: 'GTE' } })]))).toBeGreaterThan(0);
42
+ });
43
+
44
+ it('relation con mayusculas o espacios -> error', async () => {
45
+ expect(await errorsOf(group([relation({ relation: 'Referidos' })]))).toBeGreaterThan(0);
46
+ expect(await errorsOf(group([relation({ relation: 'ref idos' })]))).toBeGreaterThan(0);
47
+ });
48
+
49
+ it('quantifier kind desconocido -> error', async () => {
50
+ expect(await errorsOf(group([relation({ quantifier: { kind: 'SOME' } })]))).toBeGreaterThan(0);
51
+ });
52
+
53
+ it('mas de 10 filters -> error; filters ausente -> error', async () => {
54
+ expect(await errorsOf(group([relation({ filters: Array(11).fill(filter) })]))).toBeGreaterThan(0);
55
+ expect(await errorsOf(group([relation({ filters: undefined })]))).toBeGreaterThan(0);
56
+ });
57
+
58
+ it('kind desconocido sigue rechazado; COLUMN y AGGREGATE siguen', async () => {
59
+ expect(await errorsOf(group([{ kind: 'OTHER' }]))).toBeGreaterThan(0);
60
+ expect(await errorsOf(group([filter]))).toBe(0);
61
+ });
62
+ });
@@ -0,0 +1,115 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import {
4
+ RulesOutcomeEnum,
5
+ RulesTraceConditionResponse,
6
+ RulesTraceGroupResponse,
7
+ RulesTraceRelatedResponse,
8
+ RulesTraceRelationResponse,
9
+ RulesTraceResponse,
10
+ } from '../../../src/rules';
11
+ import {
12
+ EvaluationOutcomeEnum,
13
+ TraceConditionResponse,
14
+ TraceGroupResponse,
15
+ TraceRelatedResponse,
16
+ TraceRelationResponse,
17
+ } from '../../../src/rewardsEligibility';
18
+
19
+ const condition: RulesTraceConditionResponse = {
20
+ source: 'directory',
21
+ field: 'country',
22
+ op: 'EQ',
23
+ expected: 'MX',
24
+ actual: 'MX',
25
+ outcome: RulesOutcomeEnum.MATCH,
26
+ };
27
+
28
+ const relatedTrace: RulesTraceResponse = {
29
+ match: 'ALL',
30
+ outcome: RulesOutcomeEnum.MATCH,
31
+ groups: [{ match: 'ALL', outcome: RulesOutcomeEnum.MATCH, conditions: [condition] }],
32
+ };
33
+
34
+ const relatedOne: RulesTraceRelatedResponse = {
35
+ directoryId: 'dir-1',
36
+ outcome: RulesOutcomeEnum.MATCH,
37
+ trace: relatedTrace,
38
+ };
39
+
40
+ const block: RulesTraceRelationResponse = {
41
+ kind: 'RELATION',
42
+ relation: 'referidos',
43
+ quantifier: { kind: 'COUNT', op: 'GTE', value: 1 },
44
+ exposeAs: 'referidos_mx_count',
45
+ outcome: RulesOutcomeEnum.MATCH,
46
+ matched: 1,
47
+ errored: 0,
48
+ total: 1,
49
+ related: [relatedOne],
50
+ };
51
+
52
+ describe('trazas anidadas (rules)', () => {
53
+ it('serializa y deserializa un bloque de relación con relacionado sin pérdida', () => {
54
+ const group: RulesTraceGroupResponse = {
55
+ match: 'ALL',
56
+ outcome: RulesOutcomeEnum.MATCH,
57
+ conditions: [condition, block],
58
+ };
59
+ const roundTrip: RulesTraceGroupResponse = JSON.parse(JSON.stringify(group));
60
+ expect(roundTrip).toStrictEqual(group);
61
+ });
62
+
63
+ it('el discriminante kind distingue bloque de condición plana', () => {
64
+ const group: RulesTraceGroupResponse = {
65
+ match: 'ANY',
66
+ outcome: RulesOutcomeEnum.MATCH,
67
+ conditions: [condition, block],
68
+ };
69
+ const blocks = group.conditions.filter((n): n is RulesTraceRelationResponse => 'kind' in n && n.kind === 'RELATION');
70
+ expect(blocks).toHaveLength(1);
71
+ expect(blocks[0].related[0].trace.groups[0].conditions[0]).toStrictEqual(condition);
72
+ });
73
+
74
+ it('una traza plana de 0.4.1 compila y se conserva igual', () => {
75
+ const flat: RulesTraceGroupResponse = { match: 'ALL', outcome: RulesOutcomeEnum.MATCH, conditions: [condition] };
76
+ expect(plainToInstance(RulesTraceGroupResponse, flat)).toEqual(flat);
77
+ });
78
+
79
+ it('un bloque en ERROR lleva error y sin related', () => {
80
+ const errored: RulesTraceRelationResponse = {
81
+ kind: 'RELATION',
82
+ relation: 'referidos',
83
+ outcome: RulesOutcomeEnum.ERROR,
84
+ matched: 0,
85
+ errored: 0,
86
+ total: 0,
87
+ related: [],
88
+ error: { code: 'RELATION_NOT_RESOLVED', message: 'sin resolver' },
89
+ };
90
+ expect(JSON.parse(JSON.stringify(errored))).toStrictEqual(errored);
91
+ });
92
+ });
93
+
94
+ describe('trazas anidadas (eligibility)', () => {
95
+ const cond: TraceConditionResponse = {
96
+ source: 'directory', field: 'country', op: 'EQ', expected: 'MX', actual: 'MX',
97
+ outcome: EvaluationOutcomeEnum.MATCH,
98
+ };
99
+ const related: TraceRelatedResponse = {
100
+ directoryId: 'dir-1',
101
+ outcome: EvaluationOutcomeEnum.MATCH,
102
+ trace: { match: 'ALL', outcome: EvaluationOutcomeEnum.MATCH, groups: [{ match: 'ALL', outcome: EvaluationOutcomeEnum.MATCH, conditions: [cond] }] },
103
+ };
104
+ const eBlock: TraceRelationResponse = {
105
+ kind: 'RELATION', relation: 'referidor',
106
+ outcome: EvaluationOutcomeEnum.MATCH, matched: 1, errored: 0, total: 1, related: [related],
107
+ };
108
+
109
+ it('serializa un grupo con condición plana y bloque', () => {
110
+ const group: TraceGroupResponse = { match: 'ALL', outcome: EvaluationOutcomeEnum.MATCH, conditions: [cond, eBlock] };
111
+ expect(JSON.parse(JSON.stringify(group))).toStrictEqual(JSON.parse(JSON.stringify(group)));
112
+ const back: TraceGroupResponse = JSON.parse(JSON.stringify(group));
113
+ expect(back.conditions[1]).toStrictEqual(JSON.parse(JSON.stringify(eBlock)));
114
+ });
115
+ });
@@ -0,0 +1,68 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import {
4
+ RETURN_FIELD_ID_PATTERN,
5
+ RulesExposedFieldResponse,
6
+ RulesNodeErrorCodeEnum,
7
+ RulesValueTypeEnum,
8
+ ValidateRulesResponse,
9
+ relationReturnFieldId,
10
+ } from '../../../src/rules';
11
+
12
+ const ENGINE_NODE_ERROR_CODES: string[] = [
13
+ 'UNKNOWN_RELATION',
14
+ 'INVALID_RELATION_NODE',
15
+ 'QUANTIFIER_REQUIRED',
16
+ 'QUANTIFIER_NOT_ALLOWED',
17
+ 'INVALID_QUANTIFIER',
18
+ 'SUBJECT_IN_RELATION',
19
+ 'EXPOSE_AS_NOT_ALLOWED',
20
+ 'INVALID_EXPOSE_AS',
21
+ 'DUPLICATE_EXPOSE_AS',
22
+ 'MAX_DEPTH_EXCEEDED',
23
+ 'MAX_BLOCKS_EXCEEDED',
24
+ ];
25
+
26
+ describe('validación de nodos de relación (rules)', () => {
27
+ it('RulesNodeErrorCodeEnum contiene los 11 códigos del engine, string a string', () => {
28
+ const values: string[] = Object.values(RulesNodeErrorCodeEnum);
29
+ ENGINE_NODE_ERROR_CODES.forEach(code => expect(values).toContain(code));
30
+ });
31
+
32
+ it('agrega los códigos del Plan B y cada clave coincide con su valor', () => {
33
+ expect(RulesNodeErrorCodeEnum.RELATION_SOURCE_NOT_BATCH_CAPABLE).toBe('RELATION_SOURCE_NOT_BATCH_CAPABLE');
34
+ expect(RulesNodeErrorCodeEnum.MAX_RELATED_EXCEEDED).toBe('MAX_RELATED_EXCEEDED');
35
+ expect(Object.values(RulesNodeErrorCodeEnum)).toHaveLength(ENGINE_NODE_ERROR_CODES.length + 2);
36
+ Object.entries(RulesNodeErrorCodeEnum).forEach(([key, value]) => expect(value).toBe(key));
37
+ });
38
+
39
+ it('un ValidateRulesResponse plano serializa con nodeErrors y exposedFields vacíos', () => {
40
+ const plain = { valid: true, errors: [], nodeErrors: [], exposedFields: [] };
41
+ const response = plainToInstance(ValidateRulesResponse, plain);
42
+ expect(JSON.parse(JSON.stringify(response))).toStrictEqual(plain);
43
+ expect(response.nodeErrors).toStrictEqual([]);
44
+ expect(response.exposedFields).toStrictEqual([]);
45
+ });
46
+
47
+ it('conserva nodeErrors con path/code/message al deserializar', () => {
48
+ const response = plainToInstance(ValidateRulesResponse, {
49
+ valid: false,
50
+ errors: ['x'],
51
+ nodeErrors: [{ path: 'groups[0].conditions[1]', code: 'UNKNOWN_RELATION', message: 'no existe' }],
52
+ exposedFields: [],
53
+ });
54
+ expect(response.nodeErrors[0].code).toBe(RulesNodeErrorCodeEnum.UNKNOWN_RELATION);
55
+ expect(response.nodeErrors[0].path).toBe('groups[0].conditions[1]');
56
+ });
57
+
58
+ it('exposedFields[].id cumple RETURN_FIELD_ID_PATTERN', () => {
59
+ const field: RulesExposedFieldResponse = {
60
+ id: relationReturnFieldId('referidos', 'referidos_mx_count'),
61
+ relation: 'referidos',
62
+ exposeAs: 'referidos_mx_count',
63
+ valueType: RulesValueTypeEnum.NUMBER,
64
+ };
65
+ expect(RETURN_FIELD_ID_PATTERN.test(field.id)).toBe(true);
66
+ expect(field.id).toBe('referidos:referidos_mx_count');
67
+ });
68
+ });
@@ -33,7 +33,6 @@ exports.RETAIL_PROMOTIONS_ERROR_HTTP_STATUS = {
33
33
  [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.SALE_PROMOTION_HOLD_CONFLICT]: 409,
34
34
  [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.INVALID_NEXT_TOKEN]: 400,
35
35
  [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.CATALOG_SKU_NOT_FOUND]: 404,
36
- [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.SPLIT_FINANCING_DISABLED]: 409,
37
36
  [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.PROMOTION_NOT_STACKABLE]: 422,
38
37
  [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.PROMOTION_PAYMENT_NOT_ELIGIBLE]: 422,
39
38
  [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.SYSTEM_PROMOTION_TRANSITION_NOT_ALLOWED]: 422,
@@ -43,8 +43,6 @@ export declare enum RetailPromotionsErrorCodeEnum {
43
43
  INVALID_NEXT_TOKEN = "INVALID_NEXT_TOKEN",
44
44
  /** El SKU no existe en el catálogo. */
45
45
  CATALOG_SKU_NOT_FOUND = "CATALOG_SKU_NOT_FOUND",
46
- /** Con el reparto apagado no se aplican palancas de precio ni código; los regalos sí. */
47
- SPLIT_FINANCING_DISABLED = "SPLIT_FINANCING_DISABLED",
48
46
  /** La promo no acumulable choca con otra en la misma línea. */
49
47
  PROMOTION_NOT_STACKABLE = "PROMOTION_NOT_STACKABLE",
50
48
  /** La promo no acepta la forma de pago o el medio de cobro de la venta. */
@@ -47,8 +47,6 @@ var RetailPromotionsErrorCodeEnum;
47
47
  RetailPromotionsErrorCodeEnum["INVALID_NEXT_TOKEN"] = "INVALID_NEXT_TOKEN";
48
48
  /** El SKU no existe en el catálogo. */
49
49
  RetailPromotionsErrorCodeEnum["CATALOG_SKU_NOT_FOUND"] = "CATALOG_SKU_NOT_FOUND";
50
- /** Con el reparto apagado no se aplican palancas de precio ni código; los regalos sí. */
51
- RetailPromotionsErrorCodeEnum["SPLIT_FINANCING_DISABLED"] = "SPLIT_FINANCING_DISABLED";
52
50
  /** La promo no acumulable choca con otra en la misma línea. */
53
51
  RetailPromotionsErrorCodeEnum["PROMOTION_NOT_STACKABLE"] = "PROMOTION_NOT_STACKABLE";
54
52
  /** La promo no acepta la forma de pago o el medio de cobro de la venta. */
@@ -21,10 +21,37 @@ export declare class TraceConditionResponse {
21
21
  message: string;
22
22
  };
23
23
  }
24
+ /** Un relacionado evaluado dentro de un bloque de relación, con su traza propia. */
25
+ export declare class TraceRelatedResponse {
26
+ directoryId: string;
27
+ outcome: EvaluationOutcomeEnum;
28
+ trace: ResultTraceResponse;
29
+ }
30
+ /** Un bloque de relación evaluado (ej. «referidos»). Discriminante: `kind: 'RELATION'`. Sin `origin`/`asOf`. */
31
+ export declare class TraceRelationResponse {
32
+ kind: 'RELATION';
33
+ relation: string;
34
+ quantifier?: {
35
+ kind: 'ANY' | 'ALL' | 'COUNT';
36
+ op?: 'GT' | 'GTE' | 'EQ' | 'LT' | 'LTE';
37
+ value?: number;
38
+ };
39
+ exposeAs?: string;
40
+ outcome: EvaluationOutcomeEnum;
41
+ matched: number;
42
+ errored: number;
43
+ total: number;
44
+ related: TraceRelatedResponse[];
45
+ /** Solo si `outcome` es `ERROR` (`RELATION_NOT_RESOLVED`, `RELATED_UNDECIDABLE`, `MAX_RELATED_EXCEEDED`…). */
46
+ error?: {
47
+ code: string;
48
+ message: string;
49
+ };
50
+ }
24
51
  export declare class TraceGroupResponse {
25
52
  match: 'ALL' | 'ANY';
26
53
  outcome: EvaluationOutcomeEnum;
27
- conditions: TraceConditionResponse[];
54
+ conditions: (TraceConditionResponse | TraceRelationResponse)[];
28
55
  }
29
56
  /** Traza completa de una evaluación: por qué cumplió o no (criterio de éxito 4). */
30
57
  export declare class ResultTraceResponse {
@@ -1,10 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ResultTraceResponse = exports.TraceGroupResponse = exports.TraceConditionResponse = void 0;
3
+ exports.ResultTraceResponse = exports.TraceGroupResponse = exports.TraceRelationResponse = exports.TraceRelatedResponse = exports.TraceConditionResponse = void 0;
4
4
  /** Una condición evaluada: qué se esperaba, qué se encontró y de dónde salió el dato (spec 5.4). */
5
5
  class TraceConditionResponse {
6
6
  }
7
7
  exports.TraceConditionResponse = TraceConditionResponse;
8
+ /** Un relacionado evaluado dentro de un bloque de relación, con su traza propia. */
9
+ class TraceRelatedResponse {
10
+ }
11
+ exports.TraceRelatedResponse = TraceRelatedResponse;
12
+ /** Un bloque de relación evaluado (ej. «referidos»). Discriminante: `kind: 'RELATION'`. Sin `origin`/`asOf`. */
13
+ class TraceRelationResponse {
14
+ }
15
+ exports.TraceRelationResponse = TraceRelationResponse;
8
16
  class TraceGroupResponse {
9
17
  }
10
18
  exports.TraceGroupResponse = TraceGroupResponse;
@@ -1,4 +1,6 @@
1
1
  import { InputField } from '../../../dynamicForm/dtos/InputField';
2
+ import { RulesRelationCardinalityEnum } from '../../../rules/enums/RulesRelationCardinalityEnum';
3
+ import { RulesRelationTargetEnum } from '../../../rules/enums/RulesRelationTargetEnum';
2
4
  import { RuleValueTypeEnum } from '../../enums/RuleValueTypeEnum';
3
5
  /** Un campo que una fuente resuelve para las reglas (`GET /rule-catalog`). */
4
6
  export declare class RuleCatalogFieldResponse {
@@ -26,8 +28,22 @@ export declare class RuleCatalogSourceResponse {
26
28
  description: string;
27
29
  fields: RuleCatalogFieldResponse[];
28
30
  }
31
+ /** Una relación que las reglas pueden recorrer (espejo de `RulesCatalogRelationResponse`). */
32
+ export declare class RuleCatalogRelationResponse {
33
+ name: string;
34
+ label: string;
35
+ description: string;
36
+ cardinality: RulesRelationCardinalityEnum;
37
+ target: RulesRelationTargetEnum;
38
+ /** Solo LIVE: tope de relacionados por sujeto; excederlo da ERROR, nunca trunca. */
39
+ maxRelated?: number;
40
+ /** Solo target RECORD: campos propios del registro. */
41
+ fields?: RuleCatalogFieldResponse[];
42
+ }
29
43
  export declare class RuleCatalogResponse {
30
44
  sources: RuleCatalogSourceResponse[];
31
45
  /** Etiqueta legible de cada operador (`GTE` → «≥»). */
32
46
  operators: Record<string, string>;
47
+ /** Relaciones disponibles (vacío si no hay). */
48
+ relations: RuleCatalogRelationResponse[];
33
49
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RuleCatalogResponse = exports.RuleCatalogSourceResponse = exports.RuleCatalogFieldResponse = void 0;
3
+ exports.RuleCatalogResponse = exports.RuleCatalogRelationResponse = exports.RuleCatalogSourceResponse = exports.RuleCatalogFieldResponse = void 0;
4
4
  /** Un campo que una fuente resuelve para las reglas (`GET /rule-catalog`). */
5
5
  class RuleCatalogFieldResponse {
6
6
  }
@@ -8,6 +8,10 @@ exports.RuleCatalogFieldResponse = RuleCatalogFieldResponse;
8
8
  class RuleCatalogSourceResponse {
9
9
  }
10
10
  exports.RuleCatalogSourceResponse = RuleCatalogSourceResponse;
11
+ /** Una relación que las reglas pueden recorrer (espejo de `RulesCatalogRelationResponse`). */
12
+ class RuleCatalogRelationResponse {
13
+ }
14
+ exports.RuleCatalogRelationResponse = RuleCatalogRelationResponse;
11
15
  class RuleCatalogResponse {
12
16
  }
13
17
  exports.RuleCatalogResponse = RuleCatalogResponse;
@@ -0,0 +1 @@
1
+ export * from '../../rules/enums/RulesRelationCardinalityEnum';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("../../rules/enums/RulesRelationCardinalityEnum"), exports);
@@ -0,0 +1 @@
1
+ export * from '../../rules/enums/RulesRelationTargetEnum';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("../../rules/enums/RulesRelationTargetEnum"), exports);
@@ -30,6 +30,8 @@ export * from './dtos/EvaluationReward';
30
30
  export * from './dtos/EvaluationTrigger';
31
31
  export * from './enums/MetricsDimensionEnum';
32
32
  export * from './enums/RuleValueTypeEnum';
33
+ export * from './enums/RulesRelationCardinalityEnum';
34
+ export * from './enums/RulesRelationTargetEnum';
33
35
  export * from './enums/RulesChatRoleEnum';
34
36
  export * from './constants/BackofficeLimits';
35
37
  export * from './constants/BatchLimits';
@@ -52,6 +52,8 @@ __exportStar(require("./dtos/EvaluationTrigger"), exports);
52
52
  //Backoffice (fase 3): enums, constantes y DTOs
53
53
  __exportStar(require("./enums/MetricsDimensionEnum"), exports);
54
54
  __exportStar(require("./enums/RuleValueTypeEnum"), exports);
55
+ __exportStar(require("./enums/RulesRelationCardinalityEnum"), exports);
56
+ __exportStar(require("./enums/RulesRelationTargetEnum"), exports);
55
57
  __exportStar(require("./enums/RulesChatRoleEnum"), exports);
56
58
  __exportStar(require("./constants/BackofficeLimits"), exports);
57
59
  __exportStar(require("./constants/BatchLimits"), exports);
@@ -0,0 +1,15 @@
1
+ /** Nombre de relación del catálogo (`referidos`, `referidor`). */
2
+ export declare const RULES_RELATION_NAME_PATTERN: RegExp;
3
+ /** Nombre con el que un bloque de relación expone su conteo como campo devuelto. */
4
+ export declare const RULES_EXPOSE_AS_PATTERN: RegExp;
5
+ /** Profundidad máxima de anidación de bloques de relación. */
6
+ export declare const RULES_MAX_RELATION_DEPTH = 2;
7
+ /** Máximo de bloques de relación por regla (todos los niveles). */
8
+ export declare const RULES_MAX_RELATION_BLOCKS = 5;
9
+ /** Máximo de días del operador `WITHIN_LAST_DAYS`. */
10
+ export declare const RULES_MAX_WITHIN_LAST_DAYS = 365;
11
+ /**
12
+ * Id del campo devuelto que expone un bloque de relación: `relación:exposeAs`
13
+ * (ej. `referidos:referidos_mx_count`). Cumple `RETURN_FIELD_ID_PATTERN`.
14
+ */
15
+ export declare function relationReturnFieldId(relation: string, exposeAs: string): string;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULES_MAX_WITHIN_LAST_DAYS = exports.RULES_MAX_RELATION_BLOCKS = exports.RULES_MAX_RELATION_DEPTH = exports.RULES_EXPOSE_AS_PATTERN = exports.RULES_RELATION_NAME_PATTERN = void 0;
4
+ exports.relationReturnFieldId = relationReturnFieldId;
5
+ /** Nombre de relación del catálogo (`referidos`, `referidor`). */
6
+ exports.RULES_RELATION_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
7
+ /** Nombre con el que un bloque de relación expone su conteo como campo devuelto. */
8
+ exports.RULES_EXPOSE_AS_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
9
+ /** Profundidad máxima de anidación de bloques de relación. */
10
+ exports.RULES_MAX_RELATION_DEPTH = 2;
11
+ /** Máximo de bloques de relación por regla (todos los niveles). */
12
+ exports.RULES_MAX_RELATION_BLOCKS = 5;
13
+ /** Máximo de días del operador `WITHIN_LAST_DAYS`. */
14
+ exports.RULES_MAX_WITHIN_LAST_DAYS = 365;
15
+ /**
16
+ * Id del campo devuelto que expone un bloque de relación: `relación:exposeAs`
17
+ * (ej. `referidos:referidos_mx_count`). Cumple `RETURN_FIELD_ID_PATTERN`.
18
+ */
19
+ function relationReturnFieldId(relation, exposeAs) {
20
+ return `${relation}:${exposeAs}`;
21
+ }
@@ -1,4 +1,6 @@
1
1
  import { InputField } from '../../dynamicForm/dtos/InputField';
2
+ import { RulesRelationCardinalityEnum } from '../enums/RulesRelationCardinalityEnum';
3
+ import { RulesRelationTargetEnum } from '../enums/RulesRelationTargetEnum';
2
4
  import { RulesValueTypeEnum } from '../enums/RulesValueTypeEnum';
3
5
  /** Un campo que un ítem del catálogo resuelve para las reglas. */
4
6
  export declare class RulesCatalogFieldResponse {
@@ -29,8 +31,22 @@ export declare class RulesCatalogItemResponse {
29
31
  description: string;
30
32
  fields: RulesCatalogFieldResponse[];
31
33
  }
34
+ /** Una relación que las reglas pueden recorrer (ej. `referidos`): bloque `RELATION` con sub-regla. */
35
+ export declare class RulesCatalogRelationResponse {
36
+ name: string;
37
+ label: string;
38
+ description: string;
39
+ cardinality: RulesRelationCardinalityEnum;
40
+ target: RulesRelationTargetEnum;
41
+ /** Solo LIVE: tope de relacionados por sujeto; excederlo da ERROR, nunca trunca. */
42
+ maxRelated?: number;
43
+ /** Solo target RECORD: campos propios del registro. */
44
+ fields?: RulesCatalogFieldResponse[];
45
+ }
32
46
  export declare class RulesCatalogResponse {
33
47
  items: RulesCatalogItemResponse[];
34
48
  /** Etiqueta legible de cada operador (`GTE` → «≥»). */
35
49
  operators: Record<string, string>;
50
+ /** Relaciones disponibles (vacío si no hay). */
51
+ relations: RulesCatalogRelationResponse[];
36
52
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RulesCatalogResponse = exports.RulesCatalogItemResponse = exports.RulesCatalogFieldResponse = void 0;
3
+ exports.RulesCatalogResponse = exports.RulesCatalogRelationResponse = exports.RulesCatalogItemResponse = exports.RulesCatalogFieldResponse = void 0;
4
4
  /** Un campo que un ítem del catálogo resuelve para las reglas. */
5
5
  class RulesCatalogFieldResponse {
6
6
  }
@@ -9,6 +9,10 @@ exports.RulesCatalogFieldResponse = RulesCatalogFieldResponse;
9
9
  class RulesCatalogItemResponse {
10
10
  }
11
11
  exports.RulesCatalogItemResponse = RulesCatalogItemResponse;
12
+ /** Una relación que las reglas pueden recorrer (ej. `referidos`): bloque `RELATION` con sub-regla. */
13
+ class RulesCatalogRelationResponse {
14
+ }
15
+ exports.RulesCatalogRelationResponse = RulesCatalogRelationResponse;
12
16
  class RulesCatalogResponse {
13
17
  }
14
18
  exports.RulesCatalogResponse = RulesCatalogResponse;