@yoryoboy/bi-mcp 1.16.0 → 1.19.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 (158) hide show
  1. package/README.md +12 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/index.js +179 -6
  4. package/dist/index.js.map +2 -2
  5. package/dist/mcp-use.json +3 -6
  6. package/dist/src/config/__tests__/google-store.test.js +30 -0
  7. package/dist/src/config/__tests__/google-store.test.js.map +7 -0
  8. package/dist/src/config/__tests__/mcp-tool-deadline.test.js +64 -0
  9. package/dist/src/config/__tests__/mcp-tool-deadline.test.js.map +7 -0
  10. package/dist/src/config/google-store.js +18 -0
  11. package/dist/src/config/google-store.js.map +2 -2
  12. package/dist/src/config/mcp-tool-deadline-context.js +34 -0
  13. package/dist/src/config/mcp-tool-deadline-context.js.map +7 -0
  14. package/dist/src/config/mcp-tool-deadline.js +67 -0
  15. package/dist/src/config/mcp-tool-deadline.js.map +7 -0
  16. package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js +38 -3
  17. package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js.map +2 -2
  18. package/dist/src/services/mercadolibre/__tests__/mercadolibre-buyer-reconciliation.test.js +226 -0
  19. package/dist/src/services/mercadolibre/__tests__/mercadolibre-buyer-reconciliation.test.js.map +7 -0
  20. package/dist/src/services/mercadolibre/__tests__/mercadolibre-claim-reconciliation.test.js +127 -0
  21. package/dist/src/services/mercadolibre/__tests__/mercadolibre-claim-reconciliation.test.js.map +7 -0
  22. package/dist/src/services/mercadolibre/__tests__/mercadolibre-kits.test.js +103 -0
  23. package/dist/src/services/mercadolibre/__tests__/mercadolibre-kits.test.js.map +7 -0
  24. package/dist/src/services/mercadolibre/__tests__/mercadolibre-order-billing-info.test.js +582 -0
  25. package/dist/src/services/mercadolibre/__tests__/mercadolibre-order-billing-info.test.js.map +7 -0
  26. package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js +10 -0
  27. package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js.map +2 -2
  28. package/dist/src/services/mercadolibre/__tests__/mercadolibre-packs.test.js +36 -0
  29. package/dist/src/services/mercadolibre/__tests__/mercadolibre-packs.test.js.map +7 -0
  30. package/dist/src/services/mercadolibre/__tests__/mercadolibre-payments.test.js +137 -0
  31. package/dist/src/services/mercadolibre/__tests__/mercadolibre-payments.test.js.map +7 -0
  32. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-group-ads.test.js +105 -0
  33. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-group-ads.test.js.map +7 -0
  34. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-groups.test.js +143 -0
  35. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-groups.test.js.map +7 -0
  36. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-campaigns.test.js +100 -0
  37. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-campaigns.test.js.map +7 -0
  38. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads.test.js +350 -0
  39. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads.test.js.map +7 -0
  40. package/dist/src/services/mercadolibre/__tests__/mercadolibre-return-reconciliation.test.js +228 -0
  41. package/dist/src/services/mercadolibre/__tests__/mercadolibre-return-reconciliation.test.js.map +7 -0
  42. package/dist/src/services/mercadolibre/__tests__/mercadolibre-shipping.test.js +58 -0
  43. package/dist/src/services/mercadolibre/__tests__/mercadolibre-shipping.test.js.map +7 -0
  44. package/dist/src/services/mercadolibre/mercadolibre-api.js +15 -3
  45. package/dist/src/services/mercadolibre/mercadolibre-api.js.map +2 -2
  46. package/dist/src/services/mercadolibre/mercadolibre-billing.js +110 -3
  47. package/dist/src/services/mercadolibre/mercadolibre-billing.js.map +2 -2
  48. package/dist/src/services/mercadolibre/mercadolibre-buyer-reconciliation.js +368 -0
  49. package/dist/src/services/mercadolibre/mercadolibre-buyer-reconciliation.js.map +7 -0
  50. package/dist/src/services/mercadolibre/mercadolibre-claim-reconciliation.js +399 -0
  51. package/dist/src/services/mercadolibre/mercadolibre-claim-reconciliation.js.map +7 -0
  52. package/dist/src/services/mercadolibre/mercadolibre-kits.js +55 -0
  53. package/dist/src/services/mercadolibre/mercadolibre-kits.js.map +7 -0
  54. package/dist/src/services/mercadolibre/mercadolibre-order-billing-info.js +392 -0
  55. package/dist/src/services/mercadolibre/mercadolibre-order-billing-info.js.map +7 -0
  56. package/dist/src/services/mercadolibre/mercadolibre-orders.js +8 -8
  57. package/dist/src/services/mercadolibre/mercadolibre-orders.js.map +2 -2
  58. package/dist/src/services/mercadolibre/mercadolibre-packs.js +21 -0
  59. package/dist/src/services/mercadolibre/mercadolibre-packs.js.map +7 -0
  60. package/dist/src/services/mercadolibre/mercadolibre-payments.js +60 -0
  61. package/dist/src/services/mercadolibre/mercadolibre-payments.js.map +7 -0
  62. package/dist/src/services/mercadolibre/mercadolibre-product-ads.js +429 -0
  63. package/dist/src/services/mercadolibre/mercadolibre-product-ads.js.map +7 -0
  64. package/dist/src/services/mercadolibre/mercadolibre-return-reconciliation.js +436 -0
  65. package/dist/src/services/mercadolibre/mercadolibre-return-reconciliation.js.map +7 -0
  66. package/dist/src/services/mercadolibre/mercadolibre-shipping.js +35 -0
  67. package/dist/src/services/mercadolibre/mercadolibre-shipping.js.map +7 -0
  68. package/dist/src/services/vtex/__tests__/vtex-api.test.js +67 -0
  69. package/dist/src/services/vtex/__tests__/vtex-api.test.js.map +7 -0
  70. package/dist/src/services/vtex/vtex-api.js +6 -0
  71. package/dist/src/services/vtex/vtex-api.js.map +2 -2
  72. package/dist/src/tools/config/__tests__/list-profiles.test.js +154 -0
  73. package/dist/src/tools/config/__tests__/list-profiles.test.js.map +7 -0
  74. package/dist/src/tools/config/list-profiles.js +39 -3
  75. package/dist/src/tools/config/list-profiles.js.map +2 -2
  76. package/dist/src/tools/mercadolibre/__tests__/get-order-billing-info.test.js +339 -0
  77. package/dist/src/tools/mercadolibre/__tests__/get-order-billing-info.test.js.map +7 -0
  78. package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-group-ads.test.js +225 -0
  79. package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-group-ads.test.js.map +7 -0
  80. package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-groups.test.js +148 -0
  81. package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-groups.test.js.map +7 -0
  82. package/dist/src/tools/mercadolibre/__tests__/product-ads-campaigns.test.js +60 -0
  83. package/dist/src/tools/mercadolibre/__tests__/product-ads-campaigns.test.js.map +7 -0
  84. package/dist/src/tools/mercadolibre/__tests__/product-ads-helpers.test.js +128 -0
  85. package/dist/src/tools/mercadolibre/__tests__/product-ads-helpers.test.js.map +7 -0
  86. package/dist/src/tools/mercadolibre/__tests__/product-ads-tools.test.js +435 -0
  87. package/dist/src/tools/mercadolibre/__tests__/product-ads-tools.test.js.map +7 -0
  88. package/dist/src/tools/mercadolibre/__tests__/reconcile-buyer-data.test.js +31 -0
  89. package/dist/src/tools/mercadolibre/__tests__/reconcile-buyer-data.test.js.map +7 -0
  90. package/dist/src/tools/mercadolibre/__tests__/reconcile-claim-data.test.js +36 -0
  91. package/dist/src/tools/mercadolibre/__tests__/reconcile-claim-data.test.js.map +7 -0
  92. package/dist/src/tools/mercadolibre/__tests__/validate-connection.test.js +205 -0
  93. package/dist/src/tools/mercadolibre/__tests__/validate-connection.test.js.map +7 -0
  94. package/dist/src/tools/mercadolibre/get-billing-provisions-by-order.js +313 -0
  95. package/dist/src/tools/mercadolibre/get-billing-provisions-by-order.js.map +7 -0
  96. package/dist/src/tools/mercadolibre/get-bundle-context-by-item.js +176 -0
  97. package/dist/src/tools/mercadolibre/get-bundle-context-by-item.js.map +7 -0
  98. package/dist/src/tools/mercadolibre/get-full-storage-charges.js +183 -0
  99. package/dist/src/tools/mercadolibre/get-full-storage-charges.js.map +7 -0
  100. package/dist/src/tools/mercadolibre/get-order-billing-info.js +189 -0
  101. package/dist/src/tools/mercadolibre/get-order-billing-info.js.map +7 -0
  102. package/dist/src/tools/mercadolibre/get-pack-context-by-order.js +150 -0
  103. package/dist/src/tools/mercadolibre/get-pack-context-by-order.js.map +7 -0
  104. package/dist/src/tools/mercadolibre/get-payment-details.js +322 -0
  105. package/dist/src/tools/mercadolibre/get-payment-details.js.map +7 -0
  106. package/dist/src/tools/mercadolibre/get-product-ads-ad-group-ads.js +144 -0
  107. package/dist/src/tools/mercadolibre/get-product-ads-ad-group-ads.js.map +7 -0
  108. package/dist/src/tools/mercadolibre/get-product-ads-advertisers.js +59 -0
  109. package/dist/src/tools/mercadolibre/get-product-ads-advertisers.js.map +7 -0
  110. package/dist/src/tools/mercadolibre/get-shipping-quote-by-declared-dimensions.js +104 -0
  111. package/dist/src/tools/mercadolibre/get-shipping-quote-by-declared-dimensions.js.map +7 -0
  112. package/dist/src/tools/mercadolibre/index.js +32 -0
  113. package/dist/src/tools/mercadolibre/index.js.map +2 -2
  114. package/dist/src/tools/mercadolibre/product-ads-helpers.js +130 -0
  115. package/dist/src/tools/mercadolibre/product-ads-helpers.js.map +7 -0
  116. package/dist/src/tools/mercadolibre/reconcile-buyer-data.js +27 -0
  117. package/dist/src/tools/mercadolibre/reconcile-buyer-data.js.map +7 -0
  118. package/dist/src/tools/mercadolibre/reconcile-claim-data.js +46 -0
  119. package/dist/src/tools/mercadolibre/reconcile-claim-data.js.map +7 -0
  120. package/dist/src/tools/mercadolibre/reconcile-return-data.js +25 -0
  121. package/dist/src/tools/mercadolibre/reconcile-return-data.js.map +7 -0
  122. package/dist/src/tools/mercadolibre/search-product-ads-ad-groups.js +162 -0
  123. package/dist/src/tools/mercadolibre/search-product-ads-ad-groups.js.map +7 -0
  124. package/dist/src/tools/mercadolibre/search-product-ads-ads.js +216 -0
  125. package/dist/src/tools/mercadolibre/search-product-ads-ads.js.map +7 -0
  126. package/dist/src/tools/mercadolibre/search-product-ads-campaigns.js +116 -0
  127. package/dist/src/tools/mercadolibre/search-product-ads-campaigns.js.map +7 -0
  128. package/dist/src/tools/mercadolibre/validate-connection.js +40 -12
  129. package/dist/src/tools/mercadolibre/validate-connection.js.map +2 -2
  130. package/dist/tests/meli/mercadolibre-billing-provisions.test.js +378 -0
  131. package/dist/tests/meli/mercadolibre-billing-provisions.test.js.map +2 -2
  132. package/dist/tests/meli/mercadolibre-order-billing-info-surface.test.js +123 -0
  133. package/dist/tests/meli/mercadolibre-order-billing-info-surface.test.js.map +7 -0
  134. package/dist/tests/meli/mercadolibre-payment-details.test.js +356 -0
  135. package/dist/tests/meli/mercadolibre-payment-details.test.js.map +7 -0
  136. package/dist/tests/meli/mercadolibre-payments.test.js +62 -0
  137. package/dist/tests/meli/mercadolibre-payments.test.js.map +7 -0
  138. package/dist/tests/meli/mercadolibre-product-ads-ad-groups-surface.test.js +90 -0
  139. package/dist/tests/meli/mercadolibre-product-ads-ad-groups-surface.test.js.map +7 -0
  140. package/dist/tests/meli/mercadolibre-product-ads-campaigns-surface.test.js +72 -0
  141. package/dist/tests/meli/mercadolibre-product-ads-campaigns-surface.test.js.map +7 -0
  142. package/dist/tests/meli/mercadolibre-tool-handlers-pack-kit-shipping.test.js +400 -0
  143. package/dist/tests/meli/mercadolibre-tool-handlers-pack-kit-shipping.test.js.map +7 -0
  144. package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js +31 -12
  145. package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js.map +2 -2
  146. package/dist/tests/vtex/vtex-commercial-conditions-docs.test.js +1 -1
  147. package/dist/tests/vtex/vtex-commercial-conditions-docs.test.js.map +1 -1
  148. package/package.json +1 -1
  149. package/dist/src/google-ads/report-utils.js +0 -78
  150. package/dist/src/google-ads/report-utils.js.map +0 -7
  151. package/dist/src/search-console/search-console-utils.js +0 -275
  152. package/dist/src/search-console/search-console-utils.js.map +0 -7
  153. package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js +0 -165
  154. package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js.map +0 -7
  155. package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js +0 -233
  156. package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js.map +0 -7
  157. package/dist/src/tools/vtex/catalog-admin-batch.js +0 -298
  158. package/dist/src/tools/vtex/catalog-admin-batch.js.map +0 -7
@@ -0,0 +1,116 @@
1
+ import { error, object } from "mcp-use/server";
2
+ import { z } from "zod";
3
+ import { formatMercadoLibreError, normalizeMercadoLibrePaging } from "../../services/mercadolibre/mercadolibre-api.js";
4
+ import {
5
+ searchMercadoLibreProductAdsCampaigns
6
+ } from "../../services/mercadolibre/mercadolibre-product-ads.js";
7
+ import { stripNulls } from "../../utils/strip-payload.js";
8
+ import {
9
+ mercadolibreProductAdsAdvertiserIdSchemaField,
10
+ mercadolibreProductAdsProfileIdSchemaField,
11
+ mercadolibreProductAdsSiteIdSchemaField,
12
+ resolveMercadoLibreProductAdsContext
13
+ } from "./product-ads-helpers.js";
14
+ import { mercadoLibreDateRegex } from "./helpers.js";
15
+ import { resolveMercadoLibreProfileOrSelection } from "./profile-resolution.js";
16
+ const CAMPAIGNS_PAGE_LIMIT = 50;
17
+ function isValidCalendarDate(value) {
18
+ const [year, month, day] = value.split("-").map(Number);
19
+ const date = new Date(Date.UTC(year, month - 1, day));
20
+ return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
21
+ }
22
+ const meliSearchProductAdsCampaignsSchema = z.object({
23
+ profileId: mercadolibreProductAdsProfileIdSchemaField,
24
+ siteId: mercadolibreProductAdsSiteIdSchemaField,
25
+ advertiserId: mercadolibreProductAdsAdvertiserIdSchemaField,
26
+ dateFrom: z.string().regex(mercadoLibreDateRegex).describe("Start date in YYYY-MM-DD format for the Product Ads window."),
27
+ dateTo: z.string().regex(mercadoLibreDateRegex).describe("End date in YYYY-MM-DD format for the Product Ads window."),
28
+ mode: z.literal("page").default("page").describe("Page-only search mode."),
29
+ limit: z.number().int().min(1).max(100).optional().describe("Requested page size."),
30
+ offset: z.number().int().min(0).optional().describe("Requested page offset.")
31
+ }).superRefine((value, ctx) => {
32
+ for (const field of ["dateFrom", "dateTo"]) {
33
+ if (!isValidCalendarDate(value[field])) {
34
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: [field], message: `${field} must be a valid calendar date.` });
35
+ }
36
+ }
37
+ if (value.dateFrom > value.dateTo) {
38
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dateTo"], message: "dateTo must be on or after dateFrom." });
39
+ }
40
+ });
41
+ async function meliSearchProductAdsCampaignsHandler(params) {
42
+ try {
43
+ const validation = meliSearchProductAdsCampaignsSchema.safeParse(params);
44
+ if (!validation.success) {
45
+ return error(validation.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
46
+ }
47
+ params = validation.data;
48
+ const profileResolution = await resolveMercadoLibreProfileOrSelection(params.profileId);
49
+ if (!profileResolution.ok) return profileResolution.response;
50
+ const profileId = profileResolution.value.profileId;
51
+ const siteId = params.siteId?.trim();
52
+ const advertiserId = params.advertiserId?.trim();
53
+ const directContext = siteId && advertiserId ? { siteId, advertiserId, resolutionMode: "explicit" } : await resolveMercadoLibreProductAdsContext({ profileId, siteId, advertiserId });
54
+ if ("ok" in directContext && !directContext.ok) return directContext.response;
55
+ const context = "ok" in directContext ? directContext.value : directContext;
56
+ const requestedLimit = params.limit ?? CAMPAIGNS_PAGE_LIMIT;
57
+ const requestedOffset = params.offset ?? 0;
58
+ const logObservation = (observation) => {
59
+ console.info(JSON.stringify({
60
+ profile_id: profileId,
61
+ site_id: context.siteId,
62
+ advertiser_id: context.advertiserId,
63
+ ...observation,
64
+ event: "mercadolibre.product_ads.campaigns.discovery"
65
+ }));
66
+ };
67
+ const page = await searchMercadoLibreProductAdsCampaigns(
68
+ profileId,
69
+ {
70
+ siteId: context.siteId,
71
+ advertiserId: context.advertiserId,
72
+ dateFrom: params.dateFrom,
73
+ dateTo: params.dateTo,
74
+ limit: requestedLimit,
75
+ offset: requestedOffset
76
+ },
77
+ { onObservation: logObservation }
78
+ );
79
+ const paging = normalizeMercadoLibrePaging(page.paging);
80
+ const nextOffset = paging.offset + paging.limit;
81
+ const hasMore = paging.limit > 0 && nextOffset < paging.total;
82
+ return object({
83
+ ...stripNulls({
84
+ profile_id: profileId,
85
+ site_id: context.siteId,
86
+ advertiser_id: context.advertiserId,
87
+ product: "PADS",
88
+ mode: "page",
89
+ paging: page.paging,
90
+ metadata: {
91
+ entity: "campaigns",
92
+ fetch_mode: "page",
93
+ total: paging.total,
94
+ requested_limit: params.limit,
95
+ effective_limit: paging.limit,
96
+ requested_offset: requestedOffset,
97
+ effective_offset: paging.offset,
98
+ returned: page.results.length,
99
+ has_more: hasMore,
100
+ next_offset: hasMore ? nextOffset : void 0,
101
+ continuation: hasMore ? `Re-run meli_search_product_ads_campaigns with mode=page and offset=${nextOffset}, limit=${paging.limit} to continue.` : "No more Product Ads campaign pages remain for this query.",
102
+ ad_groups_hint: "Use each returned campaign id as campaign context for meli_search_product_ads_ad_groups by passing filters=[{ field: 'campaigns', value: '<campaign id>' }]."
103
+ },
104
+ context_resolution: { mode: context.resolutionMode, advertiser_id: context.advertiserId, advertiser_site_id: context.siteId }
105
+ }),
106
+ results: page.results.map((row) => stripNulls(row))
107
+ });
108
+ } catch (err) {
109
+ return error(formatMercadoLibreError(err, "Failed to search MercadoLibre Product Ads campaigns"));
110
+ }
111
+ }
112
+ export {
113
+ meliSearchProductAdsCampaignsHandler,
114
+ meliSearchProductAdsCampaignsSchema
115
+ };
116
+ //# sourceMappingURL=search-product-ads-campaigns.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/tools/mercadolibre/search-product-ads-campaigns.ts"],
4
+ "sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { formatMercadoLibreError, normalizeMercadoLibrePaging } from \"../../services/mercadolibre/mercadolibre-api.js\";\nimport {\n searchMercadoLibreProductAdsCampaigns,\n type MercadoLibreProductAdsCampaignsObservation,\n} from \"../../services/mercadolibre/mercadolibre-product-ads.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\nimport {\n mercadolibreProductAdsAdvertiserIdSchemaField,\n mercadolibreProductAdsProfileIdSchemaField,\n mercadolibreProductAdsSiteIdSchemaField,\n resolveMercadoLibreProductAdsContext,\n} from \"./product-ads-helpers.js\";\nimport { mercadoLibreDateRegex } from \"./helpers.js\";\nimport { resolveMercadoLibreProfileOrSelection } from \"./profile-resolution.js\";\n\nconst CAMPAIGNS_PAGE_LIMIT = 50;\n\nfunction isValidCalendarDate(value: string) {\n const [year, month, day] = value.split(\"-\").map(Number);\n const date = new Date(Date.UTC(year, month - 1, day));\n return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;\n}\n\nexport const meliSearchProductAdsCampaignsSchema = z\n .object({\n profileId: mercadolibreProductAdsProfileIdSchemaField,\n siteId: mercadolibreProductAdsSiteIdSchemaField,\n advertiserId: mercadolibreProductAdsAdvertiserIdSchemaField,\n dateFrom: z.string().regex(mercadoLibreDateRegex).describe(\"Start date in YYYY-MM-DD format for the Product Ads window.\"),\n dateTo: z.string().regex(mercadoLibreDateRegex).describe(\"End date in YYYY-MM-DD format for the Product Ads window.\"),\n mode: z.literal(\"page\").default(\"page\").describe(\"Page-only search mode.\"),\n limit: z.number().int().min(1).max(100).optional().describe(\"Requested page size.\"),\n offset: z.number().int().min(0).optional().describe(\"Requested page offset.\"),\n })\n .superRefine((value, ctx) => {\n for (const field of [\"dateFrom\", \"dateTo\"] as const) {\n if (!isValidCalendarDate(value[field])) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, path: [field], message: `${field} must be a valid calendar date.` });\n }\n }\n if (value.dateFrom > value.dateTo) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, path: [\"dateTo\"], message: \"dateTo must be on or after dateFrom.\" });\n }\n });\n\nexport async function meliSearchProductAdsCampaignsHandler(\n params: z.infer<typeof meliSearchProductAdsCampaignsSchema>\n) {\n try {\n const validation = meliSearchProductAdsCampaignsSchema.safeParse(params);\n if (!validation.success) {\n return error(validation.error.issues.map((issue) => `${issue.path.join(\".\")}: ${issue.message}`).join(\"; \"));\n }\n params = validation.data;\n const profileResolution = await resolveMercadoLibreProfileOrSelection(params.profileId);\n if (!profileResolution.ok) return profileResolution.response;\n const profileId = profileResolution.value.profileId;\n const siteId = params.siteId?.trim();\n const advertiserId = params.advertiserId?.trim();\n const directContext = siteId && advertiserId ? { siteId, advertiserId, resolutionMode: \"explicit\" as const } : await resolveMercadoLibreProductAdsContext({ profileId, siteId, advertiserId });\n if (\"ok\" in directContext && !directContext.ok) return directContext.response;\n const context = \"ok\" in directContext ? directContext.value : directContext;\n const requestedLimit = params.limit ?? CAMPAIGNS_PAGE_LIMIT;\n const requestedOffset = params.offset ?? 0;\n const logObservation = (observation: MercadoLibreProductAdsCampaignsObservation) => {\n console.info(JSON.stringify({\n profile_id: profileId,\n site_id: context.siteId,\n advertiser_id: context.advertiserId,\n ...observation,\n event: \"mercadolibre.product_ads.campaigns.discovery\",\n }));\n };\n const page = await searchMercadoLibreProductAdsCampaigns(\n profileId,\n {\n siteId: context.siteId, advertiserId: context.advertiserId, dateFrom: params.dateFrom, dateTo: params.dateTo,\n limit: requestedLimit, offset: requestedOffset,\n },\n { onObservation: logObservation }\n );\n const paging = normalizeMercadoLibrePaging(page.paging);\n const nextOffset = paging.offset + paging.limit;\n const hasMore = paging.limit > 0 && nextOffset < paging.total;\n return object({\n ...stripNulls({\n profile_id: profileId, site_id: context.siteId, advertiser_id: context.advertiserId, product: \"PADS\", mode: \"page\", paging: page.paging,\n metadata: {\n entity: \"campaigns\", fetch_mode: \"page\", total: paging.total, requested_limit: params.limit, effective_limit: paging.limit,\n requested_offset: requestedOffset, effective_offset: paging.offset, returned: page.results.length, has_more: hasMore,\n next_offset: hasMore ? nextOffset : undefined,\n continuation: hasMore\n ? `Re-run meli_search_product_ads_campaigns with mode=page and offset=${nextOffset}, limit=${paging.limit} to continue.`\n : \"No more Product Ads campaign pages remain for this query.\",\n ad_groups_hint: \"Use each returned campaign id as campaign context for meli_search_product_ads_ad_groups by passing filters=[{ field: 'campaigns', value: '<campaign id>' }].\",\n },\n context_resolution: { mode: context.resolutionMode, advertiser_id: context.advertiserId, advertiser_site_id: context.siteId },\n }),\n results: page.results.map((row) => stripNulls(row)),\n });\n } catch (err) {\n return error(formatMercadoLibreError(err, \"Failed to search MercadoLibre Product Ads campaigns\"));\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,yBAAyB,mCAAmC;AACrE;AAAA,EACE;AAAA,OAEK;AACP,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,6BAA6B;AACtC,SAAS,6CAA6C;AAEtD,MAAM,uBAAuB;AAE7B,SAAS,oBAAoB,OAAe;AAC1C,QAAM,CAAC,MAAM,OAAO,GAAG,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI,MAAM;AACtD,QAAM,OAAO,IAAI,KAAK,KAAK,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;AACpD,SAAO,KAAK,eAAe,MAAM,QAAQ,KAAK,YAAY,MAAM,QAAQ,KAAK,KAAK,WAAW,MAAM;AACrG;AAEO,MAAM,sCAAsC,EAChD,OAAO;AAAA,EACN,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,UAAU,EAAE,OAAO,EAAE,MAAM,qBAAqB,EAAE,SAAS,6DAA6D;AAAA,EACxH,QAAQ,EAAE,OAAO,EAAE,MAAM,qBAAqB,EAAE,SAAS,2DAA2D;AAAA,EACpH,MAAM,EAAE,QAAQ,MAAM,EAAE,QAAQ,MAAM,EAAE,SAAS,wBAAwB;AAAA,EACzE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,sBAAsB;AAAA,EAClF,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,wBAAwB;AAC9E,CAAC,EACA,YAAY,CAAC,OAAO,QAAQ;AAC3B,aAAW,SAAS,CAAC,YAAY,QAAQ,GAAY;AACnD,QAAI,CAAC,oBAAoB,MAAM,KAAK,CAAC,GAAG;AACtC,UAAI,SAAS,EAAE,MAAM,EAAE,aAAa,QAAQ,MAAM,CAAC,KAAK,GAAG,SAAS,GAAG,KAAK,kCAAkC,CAAC;AAAA,IACjH;AAAA,EACF;AACA,MAAI,MAAM,WAAW,MAAM,QAAQ;AACjC,QAAI,SAAS,EAAE,MAAM,EAAE,aAAa,QAAQ,MAAM,CAAC,QAAQ,GAAG,SAAS,uCAAuC,CAAC;AAAA,EACjH;AACF,CAAC;AAEH,eAAsB,qCACpB,QACA;AACA,MAAI;AACF,UAAM,aAAa,oCAAoC,UAAU,MAAM;AACvE,QAAI,CAAC,WAAW,SAAS;AACvB,aAAO,MAAM,WAAW,MAAM,OAAO,IAAI,CAAC,UAAU,GAAG,MAAM,KAAK,KAAK,GAAG,CAAC,KAAK,MAAM,OAAO,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,IAC7G;AACA,aAAS,WAAW;AACpB,UAAM,oBAAoB,MAAM,sCAAsC,OAAO,SAAS;AACtF,QAAI,CAAC,kBAAkB,GAAI,QAAO,kBAAkB;AACpD,UAAM,YAAY,kBAAkB,MAAM;AAC1C,UAAM,SAAS,OAAO,QAAQ,KAAK;AACnC,UAAM,eAAe,OAAO,cAAc,KAAK;AAC/C,UAAM,gBAAgB,UAAU,eAAe,EAAE,QAAQ,cAAc,gBAAgB,WAAoB,IAAI,MAAM,qCAAqC,EAAE,WAAW,QAAQ,aAAa,CAAC;AAC7L,QAAI,QAAQ,iBAAiB,CAAC,cAAc,GAAI,QAAO,cAAc;AACrE,UAAM,UAAU,QAAQ,gBAAgB,cAAc,QAAQ;AAC9D,UAAM,iBAAiB,OAAO,SAAS;AACvC,UAAM,kBAAkB,OAAO,UAAU;AACzC,UAAM,iBAAiB,CAAC,gBAA4D;AAClF,cAAQ,KAAK,KAAK,UAAU;AAAA,QAC1B,YAAY;AAAA,QACZ,SAAS,QAAQ;AAAA,QACjB,eAAe,QAAQ;AAAA,QACvB,GAAG;AAAA,QACH,OAAO;AAAA,MACT,CAAC,CAAC;AAAA,IACJ;AACA,UAAM,OAAO,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,QACE,QAAQ,QAAQ;AAAA,QAAQ,cAAc,QAAQ;AAAA,QAAc,UAAU,OAAO;AAAA,QAAU,QAAQ,OAAO;AAAA,QACtG,OAAO;AAAA,QAAgB,QAAQ;AAAA,MACjC;AAAA,MACA,EAAE,eAAe,eAAe;AAAA,IAClC;AACA,UAAM,SAAS,4BAA4B,KAAK,MAAM;AACtD,UAAM,aAAa,OAAO,SAAS,OAAO;AAC1C,UAAM,UAAU,OAAO,QAAQ,KAAK,aAAa,OAAO;AACxD,WAAO,OAAO;AAAA,MACZ,GAAG,WAAW;AAAA,QACZ,YAAY;AAAA,QAAW,SAAS,QAAQ;AAAA,QAAQ,eAAe,QAAQ;AAAA,QAAc,SAAS;AAAA,QAAQ,MAAM;AAAA,QAAQ,QAAQ,KAAK;AAAA,QACjI,UAAU;AAAA,UACR,QAAQ;AAAA,UAAa,YAAY;AAAA,UAAQ,OAAO,OAAO;AAAA,UAAO,iBAAiB,OAAO;AAAA,UAAO,iBAAiB,OAAO;AAAA,UACrH,kBAAkB;AAAA,UAAiB,kBAAkB,OAAO;AAAA,UAAQ,UAAU,KAAK,QAAQ;AAAA,UAAQ,UAAU;AAAA,UAC7G,aAAa,UAAU,aAAa;AAAA,UACpC,cAAc,UACV,sEAAsE,UAAU,WAAW,OAAO,KAAK,kBACvG;AAAA,UACJ,gBAAgB;AAAA,QAClB;AAAA,QACA,oBAAoB,EAAE,MAAM,QAAQ,gBAAgB,eAAe,QAAQ,cAAc,oBAAoB,QAAQ,OAAO;AAAA,MAC9H,CAAC;AAAA,MACD,SAAS,KAAK,QAAQ,IAAI,CAAC,QAAQ,WAAW,GAAG,CAAC;AAAA,IACpD,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,WAAO,MAAM,wBAAwB,KAAK,qDAAqD,CAAC;AAAA,EAClG;AACF;",
6
+ "names": []
7
+ }
@@ -1,14 +1,29 @@
1
1
  import { error, object } from "mcp-use/server";
2
2
  import { z } from "zod";
3
3
  import { getMercadoLibreConnectionRow } from "../../config/mercadolibre-profile-store.js";
4
- import { getMercadoLibreAccessForProfile } from "../../config/mercadolibre.js";
5
- import { getMercadoLibreUsersMe, formatMercadoLibreError } from "../../services/mercadolibre/mercadolibre-api.js";
4
+ import { decryptSecret } from "../../config/vtex-crypto.js";
5
+ import {
6
+ formatMercadoLibreError,
7
+ getMercadoLibreUsersMeWithAccessToken
8
+ } from "../../services/mercadolibre/mercadolibre-api.js";
6
9
  import { stripNulls } from "../../utils/strip-payload.js";
7
10
  import { resolveMercadoLibreProfileOrSelection } from "./profile-resolution.js";
8
11
  import { mercadolibreProfileIdSchemaField } from "./write-helpers.js";
9
12
  const meliValidateConnectionSchema = z.object({
10
13
  profileId: mercadolibreProfileIdSchemaField
11
14
  });
15
+ function credentialMaintenanceRequired(profileId, reason, expiresAt) {
16
+ return object(
17
+ stripNulls({
18
+ profile_id: profileId,
19
+ observation_only: true,
20
+ validation_status: "credential_maintenance_required",
21
+ credential_status: reason,
22
+ expires_at: expiresAt?.toISOString(),
23
+ message: "No usable unexpired stored access token is available. Credential maintenance or refresh is required; this observation-only validation did not refresh tokens or update connection state."
24
+ })
25
+ );
26
+ }
12
27
  async function meliValidateConnectionHandler(params) {
13
28
  try {
14
29
  const profileResolution = await resolveMercadoLibreProfileOrSelection(params.profileId);
@@ -16,21 +31,34 @@ async function meliValidateConnectionHandler(params) {
16
31
  return profileResolution.response;
17
32
  }
18
33
  const profileId = profileResolution.value.profileId;
19
- const before = await getMercadoLibreConnectionRow(profileId);
20
- const access = await getMercadoLibreAccessForProfile(profileId);
21
- const account = await getMercadoLibreUsersMe(profileId);
22
- const after = await getMercadoLibreConnectionRow(profileId);
23
- const refreshed = before?.expiresAt?.toISOString() !== after?.expiresAt?.toISOString() || before?.encryptedAccessToken !== after?.encryptedAccessToken;
34
+ const connection = await getMercadoLibreConnectionRow(profileId);
35
+ if (!connection) {
36
+ return credentialMaintenanceRequired(profileId, "connection_not_found");
37
+ }
38
+ if (connection.status === "disabled") {
39
+ return credentialMaintenanceRequired(profileId, "connection_disabled", connection.expiresAt);
40
+ }
41
+ if (!connection.encryptedAccessToken) {
42
+ return credentialMaintenanceRequired(profileId, "token_missing", connection.expiresAt);
43
+ }
44
+ if (!connection.expiresAt) {
45
+ return credentialMaintenanceRequired(profileId, "expiry_unknown");
46
+ }
47
+ if (connection.expiresAt.getTime() <= Date.now()) {
48
+ return credentialMaintenanceRequired(profileId, "token_expired", connection.expiresAt);
49
+ }
50
+ const accessToken = decryptSecret(connection.encryptedAccessToken);
51
+ const account = await getMercadoLibreUsersMeWithAccessToken(accessToken);
24
52
  return object(
25
53
  stripNulls({
26
54
  profile_id: profileId,
27
- validation_status: after?.status ?? "active",
28
- refreshed,
29
- seller_id: access.sellerId,
55
+ observation_only: true,
56
+ validation_status: "valid",
57
+ seller_id: connection.sellerId,
30
58
  account_id: account.id,
31
59
  nickname: account.nickname,
32
- expires_at: access.expiresAt,
33
- scopes: access.scopes
60
+ expires_at: connection.expiresAt.toISOString(),
61
+ scopes: connection.scopes
34
62
  })
35
63
  );
36
64
  } catch (err) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/tools/mercadolibre/validate-connection.ts"],
4
- "sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { getMercadoLibreConnectionRow } from \"../../config/mercadolibre-profile-store.js\";\nimport { getMercadoLibreAccessForProfile } from \"../../config/mercadolibre.js\";\nimport { getMercadoLibreUsersMe, formatMercadoLibreError } from \"../../services/mercadolibre/mercadolibre-api.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\nimport { resolveMercadoLibreProfileOrSelection } from \"./profile-resolution.js\";\nimport { mercadolibreProfileIdSchemaField } from \"./write-helpers.js\";\n\nexport const meliValidateConnectionSchema = z.object({\n profileId: mercadolibreProfileIdSchemaField,\n});\n\nexport async function meliValidateConnectionHandler(\n params: z.infer<typeof meliValidateConnectionSchema>\n) {\n try {\n const profileResolution = await resolveMercadoLibreProfileOrSelection(params.profileId);\n if (!profileResolution.ok) {\n return profileResolution.response;\n }\n\n const profileId = profileResolution.value.profileId;\n const before = await getMercadoLibreConnectionRow(profileId);\n const access = await getMercadoLibreAccessForProfile(profileId);\n const account = await getMercadoLibreUsersMe(profileId);\n const after = await getMercadoLibreConnectionRow(profileId);\n\n const refreshed =\n before?.expiresAt?.toISOString() !== after?.expiresAt?.toISOString() ||\n before?.encryptedAccessToken !== after?.encryptedAccessToken;\n\n return object(\n stripNulls({\n profile_id: profileId,\n validation_status: after?.status ?? \"active\",\n refreshed,\n seller_id: access.sellerId,\n account_id: account.id,\n nickname: account.nickname,\n expires_at: access.expiresAt,\n scopes: access.scopes,\n })\n );\n } catch (err) {\n return error(formatMercadoLibreError(err, \"Failed to validate MercadoLibre connection\"));\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,oCAAoC;AAC7C,SAAS,uCAAuC;AAChD,SAAS,wBAAwB,+BAA+B;AAChE,SAAS,kBAAkB;AAC3B,SAAS,6CAA6C;AACtD,SAAS,wCAAwC;AAE1C,MAAM,+BAA+B,EAAE,OAAO;AAAA,EACnD,WAAW;AACb,CAAC;AAED,eAAsB,8BACpB,QACA;AACA,MAAI;AACF,UAAM,oBAAoB,MAAM,sCAAsC,OAAO,SAAS;AACtF,QAAI,CAAC,kBAAkB,IAAI;AACzB,aAAO,kBAAkB;AAAA,IAC3B;AAEA,UAAM,YAAY,kBAAkB,MAAM;AAC1C,UAAM,SAAS,MAAM,6BAA6B,SAAS;AAC3D,UAAM,SAAS,MAAM,gCAAgC,SAAS;AAC9D,UAAM,UAAU,MAAM,uBAAuB,SAAS;AACtD,UAAM,QAAQ,MAAM,6BAA6B,SAAS;AAE1D,UAAM,YACJ,QAAQ,WAAW,YAAY,MAAM,OAAO,WAAW,YAAY,KACnE,QAAQ,yBAAyB,OAAO;AAE1C,WAAO;AAAA,MACL,WAAW;AAAA,QACT,YAAY;AAAA,QACZ,mBAAmB,OAAO,UAAU;AAAA,QACpC;AAAA,QACA,WAAW,OAAO;AAAA,QAClB,YAAY,QAAQ;AAAA,QACpB,UAAU,QAAQ;AAAA,QAClB,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,WAAO,MAAM,wBAAwB,KAAK,4CAA4C,CAAC;AAAA,EACzF;AACF;",
4
+ "sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { getMercadoLibreConnectionRow } from \"../../config/mercadolibre-profile-store.js\";\nimport { decryptSecret } from \"../../config/vtex-crypto.js\";\nimport {\n formatMercadoLibreError,\n getMercadoLibreUsersMeWithAccessToken,\n} from \"../../services/mercadolibre/mercadolibre-api.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\nimport { resolveMercadoLibreProfileOrSelection } from \"./profile-resolution.js\";\nimport { mercadolibreProfileIdSchemaField } from \"./write-helpers.js\";\n\nexport const meliValidateConnectionSchema = z.object({\n profileId: mercadolibreProfileIdSchemaField,\n});\n\nfunction credentialMaintenanceRequired(\n profileId: string,\n reason: \"connection_not_found\" | \"connection_disabled\" | \"token_missing\" | \"token_expired\" | \"expiry_unknown\",\n expiresAt?: Date | null\n) {\n return object(\n stripNulls({\n profile_id: profileId,\n observation_only: true,\n validation_status: \"credential_maintenance_required\",\n credential_status: reason,\n expires_at: expiresAt?.toISOString(),\n message:\n \"No usable unexpired stored access token is available. Credential maintenance or refresh is required; this observation-only validation did not refresh tokens or update connection state.\",\n })\n );\n}\n\nexport async function meliValidateConnectionHandler(\n params: z.infer<typeof meliValidateConnectionSchema>\n) {\n try {\n const profileResolution = await resolveMercadoLibreProfileOrSelection(params.profileId);\n if (!profileResolution.ok) {\n return profileResolution.response;\n }\n\n const profileId = profileResolution.value.profileId;\n const connection = await getMercadoLibreConnectionRow(profileId);\n\n if (!connection) {\n return credentialMaintenanceRequired(profileId, \"connection_not_found\");\n }\n\n if (connection.status === \"disabled\") {\n return credentialMaintenanceRequired(profileId, \"connection_disabled\", connection.expiresAt);\n }\n\n if (!connection.encryptedAccessToken) {\n return credentialMaintenanceRequired(profileId, \"token_missing\", connection.expiresAt);\n }\n\n if (!connection.expiresAt) {\n return credentialMaintenanceRequired(profileId, \"expiry_unknown\");\n }\n\n if (connection.expiresAt.getTime() <= Date.now()) {\n return credentialMaintenanceRequired(profileId, \"token_expired\", connection.expiresAt);\n }\n\n const accessToken = decryptSecret(connection.encryptedAccessToken);\n const account = await getMercadoLibreUsersMeWithAccessToken(accessToken);\n\n return object(\n stripNulls({\n profile_id: profileId,\n observation_only: true,\n validation_status: \"valid\",\n seller_id: connection.sellerId,\n account_id: account.id,\n nickname: account.nickname,\n expires_at: connection.expiresAt.toISOString(),\n scopes: connection.scopes,\n })\n );\n } catch (err) {\n return error(formatMercadoLibreError(err, \"Failed to validate MercadoLibre connection\"));\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,oCAAoC;AAC7C,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,6CAA6C;AACtD,SAAS,wCAAwC;AAE1C,MAAM,+BAA+B,EAAE,OAAO;AAAA,EACnD,WAAW;AACb,CAAC;AAED,SAAS,8BACP,WACA,QACA,WACA;AACA,SAAO;AAAA,IACL,WAAW;AAAA,MACT,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB,mBAAmB;AAAA,MACnB,YAAY,WAAW,YAAY;AAAA,MACnC,SACE;AAAA,IACJ,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,8BACpB,QACA;AACA,MAAI;AACF,UAAM,oBAAoB,MAAM,sCAAsC,OAAO,SAAS;AACtF,QAAI,CAAC,kBAAkB,IAAI;AACzB,aAAO,kBAAkB;AAAA,IAC3B;AAEA,UAAM,YAAY,kBAAkB,MAAM;AAC1C,UAAM,aAAa,MAAM,6BAA6B,SAAS;AAE/D,QAAI,CAAC,YAAY;AACf,aAAO,8BAA8B,WAAW,sBAAsB;AAAA,IACxE;AAEA,QAAI,WAAW,WAAW,YAAY;AACpC,aAAO,8BAA8B,WAAW,uBAAuB,WAAW,SAAS;AAAA,IAC7F;AAEA,QAAI,CAAC,WAAW,sBAAsB;AACpC,aAAO,8BAA8B,WAAW,iBAAiB,WAAW,SAAS;AAAA,IACvF;AAEA,QAAI,CAAC,WAAW,WAAW;AACzB,aAAO,8BAA8B,WAAW,gBAAgB;AAAA,IAClE;AAEA,QAAI,WAAW,UAAU,QAAQ,KAAK,KAAK,IAAI,GAAG;AAChD,aAAO,8BAA8B,WAAW,iBAAiB,WAAW,SAAS;AAAA,IACvF;AAEA,UAAM,cAAc,cAAc,WAAW,oBAAoB;AACjE,UAAM,UAAU,MAAM,sCAAsC,WAAW;AAEvE,WAAO;AAAA,MACL,WAAW;AAAA,QACT,YAAY;AAAA,QACZ,kBAAkB;AAAA,QAClB,mBAAmB;AAAA,QACnB,WAAW,WAAW;AAAA,QACtB,YAAY,QAAQ;AAAA,QACpB,UAAU,QAAQ;AAAA,QAClB,YAAY,WAAW,UAAU,YAAY;AAAA,QAC7C,QAAQ,WAAW;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,WAAO,MAAM,wBAAwB,KAAK,4CAA4C,CAAC;AAAA,EACzF;AACF;",
6
6
  "names": []
7
7
  }
@@ -28,10 +28,22 @@ vi.mock("../../src/tools/mercadolibre/profile-resolution.js", () => ({
28
28
  resolveMercadoLibreProfileOrSelection: resolveProfileMock
29
29
  }));
30
30
  import {
31
+ buildMercadoLibreBillingOrderDetailsPath,
32
+ clampMercadoLibreBillingOrderIdsBatchSize,
31
33
  getMercadoLibreBillingProvisionPage,
32
34
  getMercadoLibreBillingProvisions,
35
+ getMercadoLibreBillingProvisionsByOrder,
33
36
  scopeToGroupPath
34
37
  } from "../../src/services/mercadolibre/mercadolibre-billing.js";
38
+ import {
39
+ meliGetBillingProvisionsByOrderHandler,
40
+ meliGetBillingProvisionsByOrderSchema,
41
+ ordersSchema,
42
+ paymentInfoSchema,
43
+ detailsSchema as byOrderDetailsSchema,
44
+ detailsSalesSchema as byOrderDetailsSalesSchema,
45
+ detailsItemsSchema as byOrderDetailsItemsSchema
46
+ } from "../../src/tools/mercadolibre/get-billing-provisions-by-order.js";
35
47
  import {
36
48
  meliGetBillingProvisionsSummaryHandler,
37
49
  meliGetBillingProvisionsSummarySchema,
@@ -80,6 +92,16 @@ describe("scopeToGroupPath", () => {
80
92
  expect(scopeToGroupPath("full")).toBe("ML/full");
81
93
  });
82
94
  });
95
+ describe("Mercado Libre billing path helpers", () => {
96
+ it("keeps the by-order route fixed to the official ML path", () => {
97
+ expect(buildMercadoLibreBillingOrderDetailsPath()).toBe("/billing/integration/group/ML/order/details");
98
+ });
99
+ it("caps by-order batches at 60 order IDs per request", () => {
100
+ expect(clampMercadoLibreBillingOrderIdsBatchSize(61)).toBe(60);
101
+ expect(clampMercadoLibreBillingOrderIdsBatchSize(60)).toBe(60);
102
+ expect(clampMercadoLibreBillingOrderIdsBatchSize(0)).toBe(1);
103
+ });
104
+ });
83
105
  describe("getMercadoLibreBillingProvisionPage", () => {
84
106
  beforeEach(() => {
85
107
  vi.clearAllMocks();
@@ -145,6 +167,7 @@ describe("getMercadoLibreBillingProvisionPage", () => {
145
167
  expect(p.marketplace_type).toBe("classic");
146
168
  expect(p.order_ids).toBe("O1");
147
169
  expect(p.item_ids).toBe("I1");
170
+ expect(p.site_id).toBeUndefined();
148
171
  });
149
172
  it("reads flat total/limit from real MELI envelope (no paging nesting)", async () => {
150
173
  axiosGetMock.mockResolvedValue({
@@ -235,6 +258,361 @@ describe("getMercadoLibreBillingProvisions", () => {
235
258
  expect(result.coverage_complete).toBe(true);
236
259
  });
237
260
  });
261
+ describe("meliGetBillingProvisionsByOrderHandler", () => {
262
+ beforeEach(() => {
263
+ vi.clearAllMocks();
264
+ vi.mocked(resolveProfileMock).mockResolvedValue({
265
+ ok: true,
266
+ value: { profileId: "meli-profile-1" }
267
+ });
268
+ });
269
+ it("requires profileId and at least one orderId", () => {
270
+ expect(
271
+ () => meliGetBillingProvisionsByOrderSchema.parse({ orderIds: ["O1"] })
272
+ ).toThrow();
273
+ expect(
274
+ () => meliGetBillingProvisionsByOrderSchema.parse({ profileId: "profile-1", orderIds: ["O1"] })
275
+ ).not.toThrow();
276
+ });
277
+ function officialOrderRow(overrides = {}) {
278
+ const paymentInfo = overrides.payment_info ?? {};
279
+ const details = overrides.details;
280
+ return {
281
+ order_id: overrides.order_id ?? `ORD-${Math.random().toString(36).slice(2, 8)}`,
282
+ sale_fee: overrides.sale_fee ?? 12.34,
283
+ document_id: overrides.document_id ?? "DOC-1",
284
+ currency_id: overrides.currency_id ?? "ARS",
285
+ marketplace: overrides.marketplace ?? "MELI",
286
+ payment_info: {
287
+ payment_id: paymentInfo.payment_id ?? "PAY-1",
288
+ payment_type: paymentInfo.payment_type ?? "credit_card",
289
+ payment_method_id: paymentInfo.payment_method_id ?? "visa",
290
+ installments: Object.hasOwn(paymentInfo, "installments") ? paymentInfo.installments : 1,
291
+ transaction_amount: Object.hasOwn(paymentInfo, "transaction_amount") ? paymentInfo.transaction_amount : 100,
292
+ total_paid_amount: Object.hasOwn(paymentInfo, "total_paid_amount") ? paymentInfo.total_paid_amount : 100,
293
+ shipping_cost: Object.hasOwn(paymentInfo, "shipping_cost") ? paymentInfo.shipping_cost : 0,
294
+ coupon_amount: Object.hasOwn(paymentInfo, "coupon_amount") ? paymentInfo.coupon_amount : 0,
295
+ status: paymentInfo.status ?? "approved",
296
+ status_detail: paymentInfo.status_detail ?? "accredited",
297
+ ...paymentInfo
298
+ },
299
+ details: details ?? [
300
+ {
301
+ charge_info: {
302
+ detail_id: "detail-1",
303
+ detail_type: "CHARGE",
304
+ detail_sub_type: "CVFF",
305
+ transaction_detail: "Costo por unidad vendida",
306
+ detail_amount: 12.34,
307
+ concept_type: "SALE_FEE",
308
+ operation_id: "OP-1",
309
+ shipping_id: "SHIP-1",
310
+ pack_id: "PACK-1",
311
+ inventory_id: "INV-1"
312
+ },
313
+ currency_info: { currency_id: "ARS" },
314
+ sales_info: [
315
+ {
316
+ order_id: overrides.order_id ?? "ORD-1",
317
+ operation_id: "OP-1",
318
+ sale_date_time: "2026-06-01T10:00:00Z",
319
+ payer_nickname: "buyer-1",
320
+ transaction_amount: 100
321
+ }
322
+ ],
323
+ items_info: [
324
+ {
325
+ item_id: "ITEM-1",
326
+ item_title: "Producto 1",
327
+ item_amount: 1,
328
+ item_price: 100,
329
+ item_kit_id: null,
330
+ store_id: "STORE-1",
331
+ order_id: overrides.order_id ?? "ORD-1",
332
+ fees_added_in_publication: "No"
333
+ }
334
+ ]
335
+ }
336
+ ],
337
+ ...overrides
338
+ };
339
+ }
340
+ it("uses the official order details endpoint without site_id and chunks at 60 IDs", async () => {
341
+ axiosGetMock.mockResolvedValueOnce({ data: { results: [officialOrderRow({ order_id: "O1" })], total: 1, limit: 60 } }).mockResolvedValueOnce({ data: { results: [officialOrderRow({ order_id: "O61" })], total: 1, limit: 1 } });
342
+ const orderIds = Array.from({ length: 61 }, (_, index) => `O${index + 1}`);
343
+ const result = await getMercadoLibreBillingProvisionsByOrder("profile-1", {
344
+ orderIds
345
+ });
346
+ expect(axiosGetMock).toHaveBeenCalledTimes(2);
347
+ expect(axiosGetMock.mock.calls[0][0]).toBe("/billing/integration/group/ML/order/details");
348
+ expect(axiosGetMock.mock.calls[0][1]).toEqual({ params: { order_ids: orderIds.slice(0, 60).join(",") } });
349
+ expect(axiosGetMock.mock.calls[1][1]).toEqual({ params: { order_ids: ["O61"].join(",") } });
350
+ expect(result.chunks_total).toBe(2);
351
+ expect(result.chunks_succeeded).toBe(2);
352
+ expect(result.chunks_failed).toBe(0);
353
+ expect(result.order_ids_found).toEqual(["O1", "O61"]);
354
+ expect(result.order_ids_not_found).toEqual(orderIds.filter((id) => !["O1", "O61"].includes(id)));
355
+ });
356
+ it("handles 600 order IDs by chunking internally into 60-ID requests", async () => {
357
+ axiosGetMock.mockResolvedValue({
358
+ data: {
359
+ results: [],
360
+ total: 0,
361
+ limit: 60
362
+ }
363
+ });
364
+ const orderIds = Array.from({ length: 600 }, (_, index) => `O${index + 1}`);
365
+ const result = await getMercadoLibreBillingProvisionsByOrder("profile-1", {
366
+ orderIds
367
+ });
368
+ expect(axiosGetMock).toHaveBeenCalledTimes(10);
369
+ expect(result.chunks_total).toBe(10);
370
+ expect(result.chunks_succeeded).toBe(10);
371
+ expect(result.chunks_failed).toBe(0);
372
+ expect(result.order_ids_requested).toEqual(orderIds);
373
+ expect(result.order_ids_not_found).toEqual(orderIds);
374
+ });
375
+ it("deduplicates found order ids from the official per-order payload", async () => {
376
+ axiosGetMock.mockResolvedValue({
377
+ data: {
378
+ results: [
379
+ officialOrderRow({
380
+ order_id: "O1",
381
+ details: [
382
+ {
383
+ detail_id: "detail-1",
384
+ sales_info: [
385
+ { order_id: "O1", operation_id: "OP-1", sale_date_time: "2026-06-01T10:00:00Z", payer_nickname: "buyer-1", transaction_amount: 100 }
386
+ ],
387
+ items_info: [
388
+ { item_id: "ITEM-1", item_title: "Producto 1", item_amount: 1, item_price: 100, order_id: "O1" }
389
+ ]
390
+ }
391
+ ]
392
+ })
393
+ ],
394
+ total: 1,
395
+ limit: 60
396
+ }
397
+ });
398
+ const result = await getMercadoLibreBillingProvisionsByOrder("profile-1", {
399
+ orderIds: ["O1", "O2"]
400
+ });
401
+ expect(result.results).toHaveLength(1);
402
+ expect(result.order_ids_found).toEqual(["O1"]);
403
+ expect(result.order_ids_not_found).toEqual(["O2"]);
404
+ expect(result.coverage_complete).toBe(true);
405
+ });
406
+ it("returns the compact order, payment and detail blocks from the official payload", async () => {
407
+ axiosGetMock.mockResolvedValue({
408
+ data: {
409
+ results: [officialOrderRow({ order_id: "O1", sale_fee: null })],
410
+ total: 1,
411
+ limit: 60
412
+ }
413
+ });
414
+ const result = await meliGetBillingProvisionsByOrderHandler({
415
+ profileId: "profile-1",
416
+ orderIds: ["O1"]
417
+ });
418
+ expect(result).toHaveProperty("kind", "object");
419
+ const payload = result.payload;
420
+ expect(payload.profile_id).toBe("meli-profile-1");
421
+ expect(payload.endpoint).toBe("/billing/integration/group/ML/order/details");
422
+ expect(payload.orders_schema).toEqual(ordersSchema);
423
+ expect(payload.payment_info_schema).toEqual(paymentInfoSchema);
424
+ expect(payload.details_schema).toEqual(byOrderDetailsSchema);
425
+ expect(payload.details_sales_schema).toEqual(byOrderDetailsSalesSchema);
426
+ expect(payload.details_items_schema).toEqual(byOrderDetailsItemsSchema);
427
+ const orders = payload.orders;
428
+ const paymentInfo = payload.payment_info;
429
+ const details = payload.details;
430
+ const detailsSales = payload.details_sales;
431
+ const detailsItems = payload.details_items;
432
+ expect(orders).toHaveLength(1);
433
+ expect(orders[0][0]).toBe("O1");
434
+ expect(orders[0][1]).toBeNull();
435
+ expect(paymentInfo).toHaveLength(1);
436
+ expect(paymentInfo[0][0]).toBe("O1");
437
+ expect(details).toHaveLength(1);
438
+ expect(details[0][0]).toBe("O1");
439
+ expect(details[0]).toEqual([
440
+ "O1",
441
+ "detail-1",
442
+ "CHARGE",
443
+ "CVFF",
444
+ "Costo por unidad vendida",
445
+ 12.34,
446
+ "ARS",
447
+ "SALE_FEE",
448
+ "OP-1",
449
+ "SHIP-1",
450
+ "PACK-1",
451
+ "INV-1"
452
+ ]);
453
+ expect(detailsSales).toHaveLength(1);
454
+ expect(detailsSales[0][4]).toBeNull();
455
+ expect(detailsItems).toHaveLength(1);
456
+ expect(detailsItems[0]).toHaveLength(byOrderDetailsItemsSchema.length);
457
+ expect(detailsItems[0][8]).toBe("No");
458
+ const metadata = payload.metadata;
459
+ expect(metadata.order_ids_requested).toEqual(["O1"]);
460
+ expect(metadata.order_ids_found).toEqual(["O1"]);
461
+ expect(metadata.order_ids_not_found).toEqual([]);
462
+ expect(metadata.coverage_complete).toBe(true);
463
+ });
464
+ it("projects CVFN from nested charge_info with amount, currency and sales order join", async () => {
465
+ axiosGetMock.mockResolvedValue({
466
+ data: {
467
+ results: [officialOrderRow({
468
+ order_id: null,
469
+ currency_id: null,
470
+ details: [{
471
+ charge_info: {
472
+ detail_id: "detail-cvfn",
473
+ detail_type: "CHARGE",
474
+ detail_sub_type: "CVFN",
475
+ transaction_detail: "Costo por ofrecer cuotas",
476
+ detail_amount: -321.45,
477
+ concept_type: "FINANCING_FEE"
478
+ },
479
+ currency_info: { currency_id: "ARS" },
480
+ sales_info: [{ order_id: "O-CVFN", payer_nickname: "must-not-leak" }]
481
+ }]
482
+ })]
483
+ }
484
+ });
485
+ const result = await meliGetBillingProvisionsByOrderHandler({
486
+ profileId: "profile-1",
487
+ orderIds: ["O-CVFN"]
488
+ });
489
+ const payload = result.payload;
490
+ const details = payload.details;
491
+ const sales = payload.details_sales;
492
+ expect(details[0].slice(0, 8)).toEqual([
493
+ "O-CVFN",
494
+ "detail-cvfn",
495
+ "CHARGE",
496
+ "CVFN",
497
+ "Costo por ofrecer cuotas",
498
+ -321.45,
499
+ "ARS",
500
+ "FINANCING_FEE"
501
+ ]);
502
+ expect(sales[0][0]).toBe("detail-cvfn");
503
+ expect(sales[0][1]).toBe("O-CVFN");
504
+ expect(sales[0][4]).toBeNull();
505
+ });
506
+ it("keeps missing nested structures nullable without inventing charge data", async () => {
507
+ axiosGetMock.mockResolvedValue({
508
+ data: { results: [officialOrderRow({ order_id: "O-PARTIAL", details: [{}] })] }
509
+ });
510
+ const result = await meliGetBillingProvisionsByOrderHandler({
511
+ profileId: "profile-1",
512
+ orderIds: ["O-PARTIAL"]
513
+ });
514
+ const payload = result.payload;
515
+ const details = payload.details;
516
+ expect(details).toEqual([["O-PARTIAL", null, null, null, null, null, "ARS", null, null, null, null, null]]);
517
+ expect(payload.metadata.coverage_complete).toBe(true);
518
+ });
519
+ it("preserves fees_added_in_publication per item as the final reversible column", async () => {
520
+ axiosGetMock.mockResolvedValue({
521
+ data: {
522
+ results: [officialOrderRow({
523
+ order_id: "O-FEES",
524
+ details: [{
525
+ charge_info: { detail_id: "detail-fees" },
526
+ sales_info: [{ order_id: "O-FEES" }],
527
+ items_info: [
528
+ { item_id: "ITEM-SI", item_amount: 0, item_price: 0, fees_added_in_publication: "Si" },
529
+ { item_id: "ITEM-NO", item_amount: 2, item_price: 10, fees_added_in_publication: "No" },
530
+ { item_id: "ITEM-UNKNOWN", fees_added_in_publication: "YES" },
531
+ { item_id: "ITEM-MISSING" }
532
+ ]
533
+ }]
534
+ })]
535
+ }
536
+ });
537
+ const result = await meliGetBillingProvisionsByOrderHandler({
538
+ profileId: "profile-1",
539
+ orderIds: ["O-FEES"]
540
+ });
541
+ const payload = result.payload;
542
+ const schema = payload.details_items_schema;
543
+ const items = payload.details_items;
544
+ expect(schema.at(-1)).toBe("fees_added_in_publication");
545
+ expect(items).toHaveLength(4);
546
+ expect(items.map((row) => [row[0], row[1], row[7], row[8]])).toEqual([
547
+ ["detail-fees", "ITEM-SI", "O-FEES", "Si"],
548
+ ["detail-fees", "ITEM-NO", "O-FEES", "No"],
549
+ ["detail-fees", "ITEM-UNKNOWN", "O-FEES", null],
550
+ ["detail-fees", "ITEM-MISSING", "O-FEES", null]
551
+ ]);
552
+ expect(items[0][3]).toBe(0);
553
+ expect(items[0][4]).toBe(0);
554
+ expect(items[2][3]).toBeNull();
555
+ expect(items[2][4]).toBeNull();
556
+ for (const row of items) expect(row).toHaveLength(schema.length);
557
+ });
558
+ it("keeps optional payment and sales numbers nullable while preserving real zero", async () => {
559
+ axiosGetMock.mockResolvedValue({
560
+ data: {
561
+ results: [officialOrderRow({
562
+ order_id: "O-NUMBERS",
563
+ payment_info: {
564
+ installments: void 0,
565
+ transaction_amount: "invalid",
566
+ total_paid_amount: 0,
567
+ shipping_cost: "0",
568
+ coupon_amount: void 0
569
+ },
570
+ details: [{
571
+ charge_info: { detail_id: "detail-numbers", detail_amount: 0 },
572
+ sales_info: [
573
+ { order_id: "O-NUMBERS" },
574
+ { order_id: "O-NUMBERS", transaction_amount: 0 }
575
+ ],
576
+ items_info: []
577
+ }]
578
+ })]
579
+ }
580
+ });
581
+ const result = await meliGetBillingProvisionsByOrderHandler({
582
+ profileId: "profile-1",
583
+ orderIds: ["O-NUMBERS"]
584
+ });
585
+ const payload = result.payload;
586
+ const payment = payload.payment_info[0];
587
+ const sales = payload.details_sales;
588
+ expect(payment.slice(4, 9)).toEqual([null, null, 0, 0, null]);
589
+ expect(sales.map((row) => row[5])).toEqual([null, 0]);
590
+ expect(payload.details[0][5]).toBe(0);
591
+ });
592
+ it("falls back orders.order_id to nested sales_info", async () => {
593
+ axiosGetMock.mockResolvedValue({
594
+ data: {
595
+ results: [officialOrderRow({
596
+ order_id: null,
597
+ payment_info: { order_id: void 0 },
598
+ details: [{
599
+ charge_info: { detail_id: "detail-nested-order" },
600
+ sales_info: [{ order_id: "O-NESTED" }],
601
+ items_info: [{ item_id: "ITEM-NESTED", fees_added_in_publication: "Si" }]
602
+ }]
603
+ })]
604
+ }
605
+ });
606
+ const result = await meliGetBillingProvisionsByOrderHandler({
607
+ profileId: "profile-1",
608
+ orderIds: ["O-NESTED"]
609
+ });
610
+ const payload = result.payload;
611
+ expect(payload.orders[0][0]).toBe("O-NESTED");
612
+ expect(payload.details_items[0][7]).toBe("O-NESTED");
613
+ expect(payload.metadata.order_ids_found).toEqual(["O-NESTED"]);
614
+ });
615
+ });
238
616
  describe("meliGetBillingProvisionsSummaryHandler", () => {
239
617
  beforeEach(() => {
240
618
  vi.clearAllMocks();