@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.
- package/README.md +12 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.js +179 -6
- package/dist/index.js.map +2 -2
- package/dist/mcp-use.json +3 -6
- package/dist/src/config/__tests__/google-store.test.js +30 -0
- package/dist/src/config/__tests__/google-store.test.js.map +7 -0
- package/dist/src/config/__tests__/mcp-tool-deadline.test.js +64 -0
- package/dist/src/config/__tests__/mcp-tool-deadline.test.js.map +7 -0
- package/dist/src/config/google-store.js +18 -0
- package/dist/src/config/google-store.js.map +2 -2
- package/dist/src/config/mcp-tool-deadline-context.js +34 -0
- package/dist/src/config/mcp-tool-deadline-context.js.map +7 -0
- package/dist/src/config/mcp-tool-deadline.js +67 -0
- package/dist/src/config/mcp-tool-deadline.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js +38 -3
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js.map +2 -2
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-buyer-reconciliation.test.js +226 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-buyer-reconciliation.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-claim-reconciliation.test.js +127 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-claim-reconciliation.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-kits.test.js +103 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-kits.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-order-billing-info.test.js +582 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-order-billing-info.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js +10 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js.map +2 -2
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-packs.test.js +36 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-packs.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-payments.test.js +137 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-payments.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-group-ads.test.js +105 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-group-ads.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-groups.test.js +143 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-groups.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-campaigns.test.js +100 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-campaigns.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads.test.js +350 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-return-reconciliation.test.js +228 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-return-reconciliation.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-shipping.test.js +58 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-shipping.test.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-api.js +15 -3
- package/dist/src/services/mercadolibre/mercadolibre-api.js.map +2 -2
- package/dist/src/services/mercadolibre/mercadolibre-billing.js +110 -3
- package/dist/src/services/mercadolibre/mercadolibre-billing.js.map +2 -2
- package/dist/src/services/mercadolibre/mercadolibre-buyer-reconciliation.js +368 -0
- package/dist/src/services/mercadolibre/mercadolibre-buyer-reconciliation.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-claim-reconciliation.js +399 -0
- package/dist/src/services/mercadolibre/mercadolibre-claim-reconciliation.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-kits.js +55 -0
- package/dist/src/services/mercadolibre/mercadolibre-kits.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-order-billing-info.js +392 -0
- package/dist/src/services/mercadolibre/mercadolibre-order-billing-info.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-orders.js +8 -8
- package/dist/src/services/mercadolibre/mercadolibre-orders.js.map +2 -2
- package/dist/src/services/mercadolibre/mercadolibre-packs.js +21 -0
- package/dist/src/services/mercadolibre/mercadolibre-packs.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-payments.js +60 -0
- package/dist/src/services/mercadolibre/mercadolibre-payments.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-product-ads.js +429 -0
- package/dist/src/services/mercadolibre/mercadolibre-product-ads.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-return-reconciliation.js +436 -0
- package/dist/src/services/mercadolibre/mercadolibre-return-reconciliation.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-shipping.js +35 -0
- package/dist/src/services/mercadolibre/mercadolibre-shipping.js.map +7 -0
- package/dist/src/services/vtex/__tests__/vtex-api.test.js +67 -0
- package/dist/src/services/vtex/__tests__/vtex-api.test.js.map +7 -0
- package/dist/src/services/vtex/vtex-api.js +6 -0
- package/dist/src/services/vtex/vtex-api.js.map +2 -2
- package/dist/src/tools/config/__tests__/list-profiles.test.js +154 -0
- package/dist/src/tools/config/__tests__/list-profiles.test.js.map +7 -0
- package/dist/src/tools/config/list-profiles.js +39 -3
- package/dist/src/tools/config/list-profiles.js.map +2 -2
- package/dist/src/tools/mercadolibre/__tests__/get-order-billing-info.test.js +339 -0
- package/dist/src/tools/mercadolibre/__tests__/get-order-billing-info.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-group-ads.test.js +225 -0
- package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-group-ads.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-groups.test.js +148 -0
- package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-groups.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/__tests__/product-ads-campaigns.test.js +60 -0
- package/dist/src/tools/mercadolibre/__tests__/product-ads-campaigns.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/__tests__/product-ads-helpers.test.js +128 -0
- package/dist/src/tools/mercadolibre/__tests__/product-ads-helpers.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/__tests__/product-ads-tools.test.js +435 -0
- package/dist/src/tools/mercadolibre/__tests__/product-ads-tools.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/__tests__/reconcile-buyer-data.test.js +31 -0
- package/dist/src/tools/mercadolibre/__tests__/reconcile-buyer-data.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/__tests__/reconcile-claim-data.test.js +36 -0
- package/dist/src/tools/mercadolibre/__tests__/reconcile-claim-data.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/__tests__/validate-connection.test.js +205 -0
- package/dist/src/tools/mercadolibre/__tests__/validate-connection.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-billing-provisions-by-order.js +313 -0
- package/dist/src/tools/mercadolibre/get-billing-provisions-by-order.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-bundle-context-by-item.js +176 -0
- package/dist/src/tools/mercadolibre/get-bundle-context-by-item.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-full-storage-charges.js +183 -0
- package/dist/src/tools/mercadolibre/get-full-storage-charges.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-order-billing-info.js +189 -0
- package/dist/src/tools/mercadolibre/get-order-billing-info.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-pack-context-by-order.js +150 -0
- package/dist/src/tools/mercadolibre/get-pack-context-by-order.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-payment-details.js +322 -0
- package/dist/src/tools/mercadolibre/get-payment-details.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-product-ads-ad-group-ads.js +144 -0
- package/dist/src/tools/mercadolibre/get-product-ads-ad-group-ads.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-product-ads-advertisers.js +59 -0
- package/dist/src/tools/mercadolibre/get-product-ads-advertisers.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-shipping-quote-by-declared-dimensions.js +104 -0
- package/dist/src/tools/mercadolibre/get-shipping-quote-by-declared-dimensions.js.map +7 -0
- package/dist/src/tools/mercadolibre/index.js +32 -0
- package/dist/src/tools/mercadolibre/index.js.map +2 -2
- package/dist/src/tools/mercadolibre/product-ads-helpers.js +130 -0
- package/dist/src/tools/mercadolibre/product-ads-helpers.js.map +7 -0
- package/dist/src/tools/mercadolibre/reconcile-buyer-data.js +27 -0
- package/dist/src/tools/mercadolibre/reconcile-buyer-data.js.map +7 -0
- package/dist/src/tools/mercadolibre/reconcile-claim-data.js +46 -0
- package/dist/src/tools/mercadolibre/reconcile-claim-data.js.map +7 -0
- package/dist/src/tools/mercadolibre/reconcile-return-data.js +25 -0
- package/dist/src/tools/mercadolibre/reconcile-return-data.js.map +7 -0
- package/dist/src/tools/mercadolibre/search-product-ads-ad-groups.js +162 -0
- package/dist/src/tools/mercadolibre/search-product-ads-ad-groups.js.map +7 -0
- package/dist/src/tools/mercadolibre/search-product-ads-ads.js +216 -0
- package/dist/src/tools/mercadolibre/search-product-ads-ads.js.map +7 -0
- package/dist/src/tools/mercadolibre/search-product-ads-campaigns.js +116 -0
- package/dist/src/tools/mercadolibre/search-product-ads-campaigns.js.map +7 -0
- package/dist/src/tools/mercadolibre/validate-connection.js +40 -12
- package/dist/src/tools/mercadolibre/validate-connection.js.map +2 -2
- package/dist/tests/meli/mercadolibre-billing-provisions.test.js +378 -0
- package/dist/tests/meli/mercadolibre-billing-provisions.test.js.map +2 -2
- package/dist/tests/meli/mercadolibre-order-billing-info-surface.test.js +123 -0
- package/dist/tests/meli/mercadolibre-order-billing-info-surface.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-payment-details.test.js +356 -0
- package/dist/tests/meli/mercadolibre-payment-details.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-payments.test.js +62 -0
- package/dist/tests/meli/mercadolibre-payments.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-product-ads-ad-groups-surface.test.js +90 -0
- package/dist/tests/meli/mercadolibre-product-ads-ad-groups-surface.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-product-ads-campaigns-surface.test.js +72 -0
- package/dist/tests/meli/mercadolibre-product-ads-campaigns-surface.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-pack-kit-shipping.test.js +400 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-pack-kit-shipping.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js +31 -12
- package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js.map +2 -2
- package/dist/tests/vtex/vtex-commercial-conditions-docs.test.js +1 -1
- package/dist/tests/vtex/vtex-commercial-conditions-docs.test.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/google-ads/report-utils.js +0 -78
- package/dist/src/google-ads/report-utils.js.map +0 -7
- package/dist/src/search-console/search-console-utils.js +0 -275
- package/dist/src/search-console/search-console-utils.js.map +0 -7
- package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js +0 -165
- package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js.map +0 -7
- package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js +0 -233
- package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js.map +0 -7
- package/dist/src/tools/vtex/catalog-admin-batch.js +0 -298
- 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": "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",
|
|
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 {
|
|
5
|
-
import {
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
seller_id:
|
|
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:
|
|
33
|
-
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 {
|
|
5
|
-
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,oCAAoC;AAC7C,SAAS,
|
|
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();
|