@yoryoboy/bi-mcp 1.7.0 → 1.9.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/dist/.tsbuildinfo +1 -1
- package/dist/index.js +27 -5
- package/dist/index.js.map +2 -2
- package/dist/mcp-use.json +6 -3
- package/dist/src/services/mercadolibre/mercadolibre-billing.js +77 -0
- package/dist/src/services/mercadolibre/mercadolibre-billing.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-items.js +29 -2
- package/dist/src/services/mercadolibre/mercadolibre-items.js.map +2 -2
- package/dist/src/services/mercadolibre/mercadolibre-orders.js +18 -0
- package/dist/src/services/mercadolibre/mercadolibre-orders.js.map +2 -2
- package/dist/src/tools/mercadolibre/estimate-listing-fee.js +26 -34
- package/dist/src/tools/mercadolibre/estimate-listing-fee.js.map +2 -2
- package/dist/src/tools/mercadolibre/estimate-product-profitability.js +546 -0
- package/dist/src/tools/mercadolibre/estimate-product-profitability.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-item-details.js +3 -25
- package/dist/src/tools/mercadolibre/get-item-details.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-sales-by-item.js +129 -14
- package/dist/src/tools/mercadolibre/get-sales-by-item.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-sales-trend.js +137 -14
- package/dist/src/tools/mercadolibre/get-sales-trend.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-shipping-summary.js +450 -57
- package/dist/src/tools/mercadolibre/get-shipping-summary.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-site-categories-and-listing-types.js +50 -0
- package/dist/src/tools/mercadolibre/get-site-categories-and-listing-types.js.map +7 -0
- package/dist/src/tools/mercadolibre/index.js +2 -0
- package/dist/src/tools/mercadolibre/index.js.map +2 -2
- package/dist/src/tools/mercadolibre/listing-fee-helpers.js +265 -0
- package/dist/src/tools/mercadolibre/listing-fee-helpers.js.map +7 -0
- package/dist/src/tools/mercadolibre/search-items.js +207 -52
- package/dist/src/tools/mercadolibre/search-items.js.map +2 -2
- package/package.json +1 -1
|
@@ -6,38 +6,16 @@ import {
|
|
|
6
6
|
getMercadoLibreItemDetailsWithFallbackBatch
|
|
7
7
|
} from "../../services/mercadolibre/mercadolibre-items.js";
|
|
8
8
|
import { stripNulls } from "../../utils/strip-payload.js";
|
|
9
|
-
import { asArray, asRecord, normalizeString, toNumber } from "./helpers.js";
|
|
10
9
|
import { resolveMercadoLibreProfileOrSelection } from "./profile-resolution.js";
|
|
11
10
|
import { mercadolibreProfileIdSchemaField } from "./write-helpers.js";
|
|
12
11
|
const MAX_ITEM_IDS = 50;
|
|
13
12
|
function formatItem(item) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
title: normalizeString(item.title),
|
|
17
|
-
status: normalizeString(item.status),
|
|
18
|
-
sub_status: Array.isArray(item.sub_status) ? item.sub_status : void 0,
|
|
19
|
-
category_id: normalizeString(item.category_id),
|
|
20
|
-
listing_type_id: normalizeString(item.listing_type_id),
|
|
21
|
-
price: toNumber(item.price),
|
|
22
|
-
currency_id: normalizeString(item.currency_id, "UNKNOWN"),
|
|
23
|
-
available_quantity: toNumber(item.available_quantity),
|
|
24
|
-
sold_quantity: toNumber(item.sold_quantity),
|
|
25
|
-
condition: normalizeString(item.condition),
|
|
26
|
-
permalink: normalizeString(item.permalink),
|
|
27
|
-
pictures: asArray(item.pictures).map((picture) => ({
|
|
28
|
-
id: normalizeString(picture.id),
|
|
29
|
-
url: normalizeString(picture.url ?? picture.secure_url)
|
|
30
|
-
})),
|
|
31
|
-
shipping: stripNulls({
|
|
32
|
-
mode: normalizeString(asRecord(item.shipping).mode),
|
|
33
|
-
logistic_type: normalizeString(asRecord(item.shipping).logistic_type),
|
|
34
|
-
free_shipping: asRecord(item.shipping).free_shipping
|
|
35
|
-
})
|
|
36
|
-
});
|
|
13
|
+
const { pictures: _pictures, ...rest } = item;
|
|
14
|
+
return stripNulls(rest);
|
|
37
15
|
}
|
|
38
16
|
const meliGetItemDetailsSchema = z.object({
|
|
39
17
|
profileId: mercadolibreProfileIdSchemaField,
|
|
40
|
-
itemIds: z.array(z.string().trim().min(1).describe("MercadoLibre item id.")).min(1).max(MAX_ITEM_IDS).describe("One or more MercadoLibre item ids (up to 50).")
|
|
18
|
+
itemIds: z.array(z.string().trim().min(1).describe("MercadoLibre item id.")).min(1).max(MAX_ITEM_IDS).describe("One or more MercadoLibre item ids (up to 50). Returns rich item details close to the MercadoLibre /items payload, excluding pictures.")
|
|
41
19
|
});
|
|
42
20
|
async function meliGetItemDetailsHandler(params) {
|
|
43
21
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/tools/mercadolibre/get-item-details.ts"],
|
|
4
|
-
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { formatMercadoLibreError } from \"../../services/mercadolibre/mercadolibre-api.js\";\nimport {\n getMercadoLibreItemDetailsBatch,\n getMercadoLibreItemDetailsWithFallbackBatch,\n} from \"../../services/mercadolibre/mercadolibre-items.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { formatMercadoLibreError } from \"../../services/mercadolibre/mercadolibre-api.js\";\nimport {\n getMercadoLibreItemDetailsBatch,\n getMercadoLibreItemDetailsWithFallbackBatch,\n} from \"../../services/mercadolibre/mercadolibre-items.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\nimport { resolveMercadoLibreProfileOrSelection } from \"./profile-resolution.js\";\nimport { mercadolibreProfileIdSchemaField } from \"./write-helpers.js\";\n\nconst MAX_ITEM_IDS = 50;\n\nfunction formatItem(item: Record<string, unknown>) {\n const { pictures: _pictures, ...rest } = item;\n return stripNulls(rest);\n}\n\nexport const meliGetItemDetailsSchema = z.object({\n profileId: mercadolibreProfileIdSchemaField,\n itemIds: z\n .array(z.string().trim().min(1).describe(\"MercadoLibre item id.\"))\n .min(1)\n .max(MAX_ITEM_IDS)\n .describe(\"One or more MercadoLibre item ids (up to 50). Returns rich item details close to the MercadoLibre /items payload, excluding pictures.\"),\n});\n\nexport async function meliGetItemDetailsHandler(params: z.infer<typeof meliGetItemDetailsSchema>) {\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 uniqueItemIds = Array.from(new Set(params.itemIds));\n\n const multigetItems = await getMercadoLibreItemDetailsBatch(profileId, uniqueItemIds);\n if (multigetItems.length === uniqueItemIds.length) {\n return object(\n stripNulls({\n metadata: {\n profile_id: profileId,\n requested: uniqueItemIds.length,\n successful: multigetItems.length,\n failed: 0,\n source: \"items_multiget\",\n },\n items: multigetItems.map(formatItem),\n })\n );\n }\n\n const batch = await getMercadoLibreItemDetailsWithFallbackBatch(profileId, uniqueItemIds, {\n maxConcurrency: 10,\n maxRetries: 2,\n });\n\n return object(\n stripNulls({\n metadata: {\n profile_id: profileId,\n requested: uniqueItemIds.length,\n successful: batch.successful.length,\n failed: batch.failed.length,\n has_failures: batch.failed.length > 0,\n source: \"item_by_item_fallback\",\n },\n items: batch.successful.map((entry) => formatItem(entry.document)),\n failures: batch.failed.map((failure) => ({\n item_id: failure.id,\n message: failure.message,\n status_code: failure.statusCode,\n attempts: failure.attempts,\n retryable: failure.retryable,\n })),\n })\n );\n } catch (err) {\n return error(formatMercadoLibreError(err, \"Failed to fetch MercadoLibre item details\"));\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,6CAA6C;AACtD,SAAS,wCAAwC;AAEjD,MAAM,eAAe;AAErB,SAAS,WAAW,MAA+B;AACjD,QAAM,EAAE,UAAU,WAAW,GAAG,KAAK,IAAI;AACzC,SAAO,WAAW,IAAI;AACxB;AAEO,MAAM,2BAA2B,EAAE,OAAO;AAAA,EAC/C,WAAW;AAAA,EACX,SAAS,EACN,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,SAAS,uBAAuB,CAAC,EAChE,IAAI,CAAC,EACL,IAAI,YAAY,EAChB,SAAS,uIAAuI;AACrJ,CAAC;AAED,eAAsB,0BAA0B,QAAkD;AAChG,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,gBAAgB,MAAM,KAAK,IAAI,IAAI,OAAO,OAAO,CAAC;AAExD,UAAM,gBAAgB,MAAM,gCAAgC,WAAW,aAAa;AACpF,QAAI,cAAc,WAAW,cAAc,QAAQ;AACjD,aAAO;AAAA,QACL,WAAW;AAAA,UACT,UAAU;AAAA,YACR,YAAY;AAAA,YACZ,WAAW,cAAc;AAAA,YACzB,YAAY,cAAc;AAAA,YAC1B,QAAQ;AAAA,YACR,QAAQ;AAAA,UACV;AAAA,UACA,OAAO,cAAc,IAAI,UAAU;AAAA,QACrC,CAAC;AAAA,MACH;AAAA,IACF;AAEA,UAAM,QAAQ,MAAM,4CAA4C,WAAW,eAAe;AAAA,MACxF,gBAAgB;AAAA,MAChB,YAAY;AAAA,IACd,CAAC;AAED,WAAO;AAAA,MACL,WAAW;AAAA,QACT,UAAU;AAAA,UACR,YAAY;AAAA,UACZ,WAAW,cAAc;AAAA,UACzB,YAAY,MAAM,WAAW;AAAA,UAC7B,QAAQ,MAAM,OAAO;AAAA,UACrB,cAAc,MAAM,OAAO,SAAS;AAAA,UACpC,QAAQ;AAAA,QACV;AAAA,QACA,OAAO,MAAM,WAAW,IAAI,CAAC,UAAU,WAAW,MAAM,QAAQ,CAAC;AAAA,QACjE,UAAU,MAAM,OAAO,IAAI,CAAC,aAAa;AAAA,UACvC,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ;AAAA,UACjB,aAAa,QAAQ;AAAA,UACrB,UAAU,QAAQ;AAAA,UAClB,WAAW,QAAQ;AAAA,QACrB,EAAE;AAAA,MACJ,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,WAAO,MAAM,wBAAwB,KAAK,2CAA2C,CAAC;AAAA,EACxF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { error, object } from "mcp-use/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
formatMercadoLibreError,
|
|
5
|
+
normalizeMercadoLibrePaging
|
|
6
|
+
} from "../../services/mercadolibre/mercadolibre-api.js";
|
|
7
|
+
import {
|
|
8
|
+
searchMercadoLibreOrders,
|
|
9
|
+
searchMercadoLibreOrdersBatch
|
|
10
|
+
} from "../../services/mercadolibre/mercadolibre-orders.js";
|
|
5
11
|
import { stripNulls } from "../../utils/strip-payload.js";
|
|
6
12
|
import {
|
|
7
13
|
asArray,
|
|
@@ -15,6 +21,17 @@ import {
|
|
|
15
21
|
} from "./helpers.js";
|
|
16
22
|
import { resolveMercadoLibreProfileOrSelection } from "./profile-resolution.js";
|
|
17
23
|
import { mercadolibreProfileIdSchemaField } from "./write-helpers.js";
|
|
24
|
+
const PAGE_FETCH_CONCURRENCY = 15;
|
|
25
|
+
const PAGE_FETCH_MAX_RETRIES = 2;
|
|
26
|
+
const salesByItemSchema = [
|
|
27
|
+
"item_id",
|
|
28
|
+
"title",
|
|
29
|
+
"variation_id",
|
|
30
|
+
"orders",
|
|
31
|
+
"units",
|
|
32
|
+
"revenue",
|
|
33
|
+
"last_order_date"
|
|
34
|
+
];
|
|
18
35
|
const meliGetSalesByItemSchema = z.object({
|
|
19
36
|
profileId: mercadolibreProfileIdSchemaField,
|
|
20
37
|
startDate: z.string().regex(mercadoLibreDateRegex).describe("Start date in YYYY-MM-DD format."),
|
|
@@ -23,6 +40,33 @@ const meliGetSalesByItemSchema = z.object({
|
|
|
23
40
|
limit: z.number().int().min(1).max(50).optional().describe("Orders page size to aggregate."),
|
|
24
41
|
offset: z.number().int().min(0).max(5e3).optional().describe("Orders offset to aggregate.")
|
|
25
42
|
});
|
|
43
|
+
function buildSalesByItemMetadata(params) {
|
|
44
|
+
const hasFailures = params.pagesFailed > 0;
|
|
45
|
+
const universeFullyFetched = !hasFailures && params.ordersAggregated >= params.total;
|
|
46
|
+
const nextOffset = params.effectiveOffset + params.ordersAggregated;
|
|
47
|
+
const hasMore = hasFailures || nextOffset < params.total;
|
|
48
|
+
let continuation = "Agregado completo. No more pages for this query.";
|
|
49
|
+
if (hasFailures) {
|
|
50
|
+
const offsets = params.failedOffsets.slice(0, 5).join(", ");
|
|
51
|
+
const moreOffsets = params.failedOffsets.length > 5 ? ` y ${params.failedOffsets.length - 5} offsets adicionales` : "";
|
|
52
|
+
continuation = `Agregado parcial por paginas fallidas. Reintentar la misma tool con los mismos filtros para recomputar el total. Offsets fallidos: ${offsets}${moreOffsets}.`;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
total: params.total,
|
|
56
|
+
limit: params.effectiveLimit,
|
|
57
|
+
offset: params.effectiveOffset,
|
|
58
|
+
returned: params.ordersAggregated,
|
|
59
|
+
orders_aggregated: params.ordersAggregated,
|
|
60
|
+
has_more: hasMore,
|
|
61
|
+
next_offset: !hasFailures && nextOffset < params.total ? nextOffset : void 0,
|
|
62
|
+
continuation,
|
|
63
|
+
fetch_mode: "full_aggregate",
|
|
64
|
+
pages_requested: params.pagesRequested,
|
|
65
|
+
pages_succeeded: params.pagesSucceeded,
|
|
66
|
+
pages_failed: params.pagesFailed,
|
|
67
|
+
universe_fully_fetched: universeFullyFetched
|
|
68
|
+
};
|
|
69
|
+
}
|
|
26
70
|
async function meliGetSalesByItemHandler(params) {
|
|
27
71
|
try {
|
|
28
72
|
const profileResolution = await resolveMercadoLibreProfileOrSelection(params.profileId);
|
|
@@ -30,17 +74,66 @@ async function meliGetSalesByItemHandler(params) {
|
|
|
30
74
|
return profileResolution.response;
|
|
31
75
|
}
|
|
32
76
|
const profileId = profileResolution.value.profileId;
|
|
77
|
+
const requestedLimit = params.limit ?? 50;
|
|
78
|
+
const requestedOffset = params.offset ?? 0;
|
|
33
79
|
const response = await searchMercadoLibreOrders(profileId, {
|
|
34
80
|
seller: "",
|
|
35
81
|
from: params.startDate,
|
|
36
82
|
to: params.endDate,
|
|
37
83
|
status: params.status,
|
|
38
|
-
limit:
|
|
39
|
-
offset:
|
|
84
|
+
limit: requestedLimit,
|
|
85
|
+
offset: requestedOffset
|
|
40
86
|
});
|
|
41
|
-
const
|
|
87
|
+
const paging = normalizeMercadoLibrePaging(asRecord(response.paging));
|
|
88
|
+
const baseOrders = asArray(response.results);
|
|
89
|
+
const effectiveLimit = paging.limit || requestedLimit || baseOrders.length;
|
|
90
|
+
const effectiveOffset = paging.offset || requestedOffset || 0;
|
|
91
|
+
const allOrders = [...baseOrders];
|
|
92
|
+
const failedPages = [];
|
|
93
|
+
let pagesRequested = 1;
|
|
94
|
+
let pagesSucceeded = 1;
|
|
95
|
+
if (effectiveLimit > 0 && paging.total > effectiveOffset + baseOrders.length) {
|
|
96
|
+
const nextOffsets = [];
|
|
97
|
+
for (let nextOffset = effectiveOffset + effectiveLimit; nextOffset < paging.total; nextOffset += effectiveLimit) {
|
|
98
|
+
nextOffsets.push(nextOffset);
|
|
99
|
+
}
|
|
100
|
+
pagesRequested += nextOffsets.length;
|
|
101
|
+
if (nextOffsets.length > 0) {
|
|
102
|
+
const batch = await searchMercadoLibreOrdersBatch(
|
|
103
|
+
profileId,
|
|
104
|
+
{
|
|
105
|
+
seller: "",
|
|
106
|
+
from: params.startDate,
|
|
107
|
+
to: params.endDate,
|
|
108
|
+
status: params.status,
|
|
109
|
+
limit: effectiveLimit
|
|
110
|
+
},
|
|
111
|
+
nextOffsets,
|
|
112
|
+
{
|
|
113
|
+
maxConcurrency: PAGE_FETCH_CONCURRENCY,
|
|
114
|
+
maxRetries: PAGE_FETCH_MAX_RETRIES
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
pagesSucceeded += batch.successful.length;
|
|
118
|
+
batch.successful.forEach((entry) => {
|
|
119
|
+
allOrders.push(...asArray(entry.document.results));
|
|
120
|
+
});
|
|
121
|
+
batch.failed.forEach((failure) => {
|
|
122
|
+
const failedOffset = Number(failure.id);
|
|
123
|
+
failedPages.push({
|
|
124
|
+
offset: failedOffset,
|
|
125
|
+
limit: effectiveLimit,
|
|
126
|
+
page_number: Math.floor(failedOffset / effectiveLimit) + 1,
|
|
127
|
+
message: failure.message,
|
|
128
|
+
status_code: failure.statusCode,
|
|
129
|
+
attempts: failure.attempts,
|
|
130
|
+
retryable: failure.retryable
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
42
135
|
const salesByCurrency = {};
|
|
43
|
-
for (const order of
|
|
136
|
+
for (const order of allOrders) {
|
|
44
137
|
const currencyId = normalizeString(order.currency_id, "UNKNOWN");
|
|
45
138
|
const orderDate = compactDateTime(order.date_created);
|
|
46
139
|
for (const orderItem of asArray(order.order_items)) {
|
|
@@ -71,19 +164,41 @@ async function meliGetSalesByItemHandler(params) {
|
|
|
71
164
|
const result = Object.fromEntries(
|
|
72
165
|
Object.entries(salesByCurrency).map(([currencyId, items]) => [
|
|
73
166
|
currencyId,
|
|
74
|
-
Object.values(items).map((item) => ({
|
|
167
|
+
Object.values(items).map((item) => ({
|
|
168
|
+
...item,
|
|
169
|
+
revenue: roundMoney(item.revenue)
|
|
170
|
+
})).sort((left, right) => right.revenue - left.revenue)
|
|
171
|
+
])
|
|
172
|
+
);
|
|
173
|
+
const compactResult = Object.fromEntries(
|
|
174
|
+
Object.entries(result).map(([currencyId, items]) => [
|
|
175
|
+
currencyId,
|
|
176
|
+
items.map((item) => [
|
|
177
|
+
item.item_id,
|
|
178
|
+
item.title,
|
|
179
|
+
item.variation_id,
|
|
180
|
+
item.orders,
|
|
181
|
+
item.units,
|
|
182
|
+
item.revenue,
|
|
183
|
+
item.last_order_date ?? ""
|
|
184
|
+
])
|
|
75
185
|
])
|
|
76
186
|
);
|
|
77
187
|
return object(
|
|
78
188
|
stripNulls({
|
|
79
189
|
profile_id: profileId,
|
|
80
|
-
metadata: {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
190
|
+
metadata: buildSalesByItemMetadata({
|
|
191
|
+
total: paging.total,
|
|
192
|
+
effectiveLimit,
|
|
193
|
+
effectiveOffset,
|
|
194
|
+
ordersAggregated: allOrders.length,
|
|
195
|
+
pagesRequested,
|
|
196
|
+
pagesSucceeded,
|
|
197
|
+
pagesFailed: failedPages.length,
|
|
198
|
+
failedOffsets: failedPages.map((failure) => failure.offset)
|
|
199
|
+
}),
|
|
200
|
+
sales_by_item_schema: salesByItemSchema,
|
|
201
|
+
sales_by_currency: compactResult
|
|
87
202
|
})
|
|
88
203
|
);
|
|
89
204
|
} catch (err) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/tools/mercadolibre/get-sales-by-item.ts"],
|
|
4
|
-
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport {\n formatMercadoLibreError,\n normalizeMercadoLibrePaging,\n} from \"../../services/mercadolibre/mercadolibre-api.js\";\nimport {\n searchMercadoLibreOrders,\n searchMercadoLibreOrdersBatch,\n} from \"../../services/mercadolibre/mercadolibre-orders.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\nimport {\n asArray,\n asRecord,\n compactDateTime,\n currencyBucket,\n mercadoLibreDateRegex,\n normalizeString,\n roundMoney,\n toNumber,\n} from \"./helpers.js\";\nimport { resolveMercadoLibreProfileOrSelection } from \"./profile-resolution.js\";\nimport { mercadolibreProfileIdSchemaField } from \"./write-helpers.js\";\n\nconst PAGE_FETCH_CONCURRENCY = 15;\nconst PAGE_FETCH_MAX_RETRIES = 2;\nconst salesByItemSchema = [\n \"item_id\",\n \"title\",\n \"variation_id\",\n \"orders\",\n \"units\",\n \"revenue\",\n \"last_order_date\",\n] as const;\n\nexport const meliGetSalesByItemSchema = z.object({\n profileId: mercadolibreProfileIdSchemaField,\n startDate: z.string().regex(mercadoLibreDateRegex).describe(\"Start date in YYYY-MM-DD format.\"),\n endDate: z.string().regex(mercadoLibreDateRegex).describe(\"End date in YYYY-MM-DD format.\"),\n status: z.string().trim().min(1).optional().describe(\"Optional order status filter.\"),\n limit: z.number().int().min(1).max(50).optional().describe(\"Orders page size to aggregate.\"),\n offset: z.number().int().min(0).max(5000).optional().describe(\"Orders offset to aggregate.\"),\n});\n\ntype FailedPage = {\n offset: number;\n limit: number;\n page_number: number;\n message: string;\n status_code?: number;\n attempts: number;\n retryable: boolean;\n};\n\nfunction buildSalesByItemMetadata(params: {\n total: number;\n effectiveLimit: number;\n effectiveOffset: number;\n ordersAggregated: number;\n pagesRequested: number;\n pagesSucceeded: number;\n pagesFailed: number;\n failedOffsets: number[];\n}) {\n const hasFailures = params.pagesFailed > 0;\n const universeFullyFetched = !hasFailures && params.ordersAggregated >= params.total;\n const nextOffset = params.effectiveOffset + params.ordersAggregated;\n const hasMore = hasFailures || nextOffset < params.total;\n\n let continuation = \"Agregado completo. No more pages for this query.\";\n if (hasFailures) {\n const offsets = params.failedOffsets.slice(0, 5).join(\", \");\n const moreOffsets =\n params.failedOffsets.length > 5\n ? ` y ${params.failedOffsets.length - 5} offsets adicionales`\n : \"\";\n continuation =\n `Agregado parcial por paginas fallidas. Reintentar la misma tool con los mismos filtros para recomputar el total. ` +\n `Offsets fallidos: ${offsets}${moreOffsets}.`;\n }\n\n return {\n total: params.total,\n limit: params.effectiveLimit,\n offset: params.effectiveOffset,\n returned: params.ordersAggregated,\n orders_aggregated: params.ordersAggregated,\n has_more: hasMore,\n next_offset: !hasFailures && nextOffset < params.total ? nextOffset : undefined,\n continuation,\n fetch_mode: \"full_aggregate\",\n pages_requested: params.pagesRequested,\n pages_succeeded: params.pagesSucceeded,\n pages_failed: params.pagesFailed,\n universe_fully_fetched: universeFullyFetched,\n };\n}\n\nexport async function meliGetSalesByItemHandler(\n params: z.infer<typeof meliGetSalesByItemSchema>\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 requestedLimit = params.limit ?? 50;\n const requestedOffset = params.offset ?? 0;\n const response = await searchMercadoLibreOrders(profileId, {\n seller: \"\",\n from: params.startDate,\n to: params.endDate,\n status: params.status,\n limit: requestedLimit,\n offset: requestedOffset,\n });\n\n const paging = normalizeMercadoLibrePaging(asRecord(response.paging));\n const baseOrders = asArray<Record<string, unknown>>(response.results);\n const effectiveLimit = paging.limit || requestedLimit || baseOrders.length;\n const effectiveOffset = paging.offset || requestedOffset || 0;\n const allOrders = [...baseOrders];\n const failedPages: FailedPage[] = [];\n let pagesRequested = 1;\n let pagesSucceeded = 1;\n\n if (effectiveLimit > 0 && paging.total > effectiveOffset + baseOrders.length) {\n const nextOffsets: number[] = [];\n for (\n let nextOffset = effectiveOffset + effectiveLimit;\n nextOffset < paging.total;\n nextOffset += effectiveLimit\n ) {\n nextOffsets.push(nextOffset);\n }\n\n pagesRequested += nextOffsets.length;\n\n if (nextOffsets.length > 0) {\n const batch = await searchMercadoLibreOrdersBatch(\n profileId,\n {\n seller: \"\",\n from: params.startDate,\n to: params.endDate,\n status: params.status,\n limit: effectiveLimit,\n },\n nextOffsets,\n {\n maxConcurrency: PAGE_FETCH_CONCURRENCY,\n maxRetries: PAGE_FETCH_MAX_RETRIES,\n }\n );\n\n pagesSucceeded += batch.successful.length;\n batch.successful.forEach((entry) => {\n allOrders.push(...asArray<Record<string, unknown>>(entry.document.results));\n });\n batch.failed.forEach((failure) => {\n const failedOffset = Number(failure.id);\n failedPages.push({\n offset: failedOffset,\n limit: effectiveLimit,\n page_number: Math.floor(failedOffset / effectiveLimit) + 1,\n message: failure.message,\n status_code: failure.statusCode,\n attempts: failure.attempts,\n retryable: failure.retryable,\n });\n });\n }\n }\n\n type SalesByItemRow = {\n item_id: string;\n title: string;\n variation_id: string;\n orders: number;\n units: number;\n revenue: number;\n last_order_date?: string | null;\n };\n const salesByCurrency: Record<string, Record<string, SalesByItemRow>> = {};\n\n for (const order of allOrders) {\n const currencyId = normalizeString(order.currency_id, \"UNKNOWN\");\n const orderDate = compactDateTime(order.date_created);\n for (const orderItem of asArray<Record<string, unknown>>(order.order_items)) {\n const item = asRecord(orderItem.item);\n const itemId = normalizeString(item.id);\n const variationId = normalizeString(item.variation_id);\n const itemKey = `${itemId}::${variationId}`;\n const bucket = currencyBucket<Record<string, SalesByItemRow>>(\n salesByCurrency,\n currencyId,\n () => ({})\n );\n bucket[itemKey] = bucket[itemKey] ?? {\n item_id: itemId,\n title: normalizeString(item.title),\n variation_id: variationId,\n orders: 0,\n units: 0,\n revenue: 0,\n last_order_date: orderDate,\n };\n bucket[itemKey].orders += 1;\n bucket[itemKey].units += toNumber(orderItem.quantity);\n bucket[itemKey].revenue += toNumber(orderItem.unit_price) * toNumber(orderItem.quantity);\n bucket[itemKey].last_order_date = orderDate ?? bucket[itemKey].last_order_date;\n }\n }\n\n const result = Object.fromEntries(\n Object.entries(salesByCurrency).map(([currencyId, items]) => [\n currencyId,\n Object.values(items)\n .map((item) => ({\n ...item,\n revenue: roundMoney(item.revenue),\n }))\n .sort((left, right) => right.revenue - left.revenue),\n ])\n );\n\n const compactResult = Object.fromEntries(\n Object.entries(result).map(([currencyId, items]) => [\n currencyId,\n items.map((item) => [\n item.item_id,\n item.title,\n item.variation_id,\n item.orders,\n item.units,\n item.revenue,\n item.last_order_date ?? \"\",\n ]),\n ])\n );\n\n return object(\n stripNulls({\n profile_id: profileId,\n metadata: buildSalesByItemMetadata({\n total: paging.total,\n effectiveLimit,\n effectiveOffset,\n ordersAggregated: allOrders.length,\n pagesRequested,\n pagesSucceeded,\n pagesFailed: failedPages.length,\n failedOffsets: failedPages.map((failure) => failure.offset),\n }),\n sales_by_item_schema: salesByItemSchema,\n sales_by_currency: compactResult,\n })\n );\n } catch (err) {\n return error(formatMercadoLibreError(err, \"Failed to aggregate MercadoLibre sales by item\"));\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,25 +1,91 @@
|
|
|
1
1
|
import { error, object } from "mcp-use/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
formatMercadoLibreError,
|
|
5
|
+
normalizeMercadoLibrePaging
|
|
6
|
+
} from "../../services/mercadolibre/mercadolibre-api.js";
|
|
7
|
+
import {
|
|
8
|
+
searchMercadoLibreOrders,
|
|
9
|
+
searchMercadoLibreOrdersBatch
|
|
10
|
+
} from "../../services/mercadolibre/mercadolibre-orders.js";
|
|
5
11
|
import { stripNulls } from "../../utils/strip-payload.js";
|
|
6
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
asArray,
|
|
14
|
+
asRecord,
|
|
15
|
+
mercadoLibreDateRegex,
|
|
16
|
+
normalizeString,
|
|
17
|
+
roundMoney,
|
|
18
|
+
toNumber
|
|
19
|
+
} from "./helpers.js";
|
|
7
20
|
import { resolveMercadoLibreProfileOrSelection } from "./profile-resolution.js";
|
|
8
21
|
import { mercadolibreProfileIdSchemaField } from "./write-helpers.js";
|
|
22
|
+
const PAGE_FETCH_CONCURRENCY = 15;
|
|
23
|
+
const PAGE_FETCH_MAX_RETRIES = 2;
|
|
24
|
+
const DEFAULT_PAGE_LIMIT = 50;
|
|
25
|
+
const BUSINESS_TIME_ZONE = "America/Argentina/Buenos_Aires";
|
|
26
|
+
function formatDateInTimeZone(dateValue, timeZone) {
|
|
27
|
+
const formatter = new Intl.DateTimeFormat("en-CA", {
|
|
28
|
+
timeZone,
|
|
29
|
+
year: "numeric",
|
|
30
|
+
month: "2-digit",
|
|
31
|
+
day: "2-digit"
|
|
32
|
+
});
|
|
33
|
+
const parts = formatter.formatToParts(dateValue);
|
|
34
|
+
const year = parts.find((part) => part.type === "year")?.value;
|
|
35
|
+
const month = parts.find((part) => part.type === "month")?.value;
|
|
36
|
+
const day = parts.find((part) => part.type === "day")?.value;
|
|
37
|
+
if (!year || !month || !day) {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
return `${year}-${month}-${day}`;
|
|
41
|
+
}
|
|
42
|
+
function getLocalDateBucket(createdAt, timeZone) {
|
|
43
|
+
if (!createdAt) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
const date = new Date(createdAt);
|
|
47
|
+
if (Number.isNaN(date.getTime())) {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
return formatDateInTimeZone(date, timeZone);
|
|
51
|
+
}
|
|
9
52
|
function getWeekBucket(dateValue) {
|
|
10
53
|
const date = /* @__PURE__ */ new Date(`${dateValue}T00:00:00.000Z`);
|
|
11
54
|
const day = date.getUTCDay() || 7;
|
|
12
55
|
date.setUTCDate(date.getUTCDate() - day + 1);
|
|
13
56
|
return date.toISOString().slice(0, 10);
|
|
14
57
|
}
|
|
58
|
+
function buildSalesTrendMetadata(params) {
|
|
59
|
+
const hasFailures = params.pagesFailed > 0;
|
|
60
|
+
const universeFullyFetched = !hasFailures && params.ordersAggregated >= params.total;
|
|
61
|
+
let continuation = "Agregado completo. No more pages for this query.";
|
|
62
|
+
if (hasFailures) {
|
|
63
|
+
const offsets = params.failedOffsets.slice(0, 5).join(", ");
|
|
64
|
+
const moreOffsets = params.failedOffsets.length > 5 ? ` y ${params.failedOffsets.length - 5} offsets adicionales` : "";
|
|
65
|
+
continuation = `Agregado parcial por paginas fallidas. Reintentar la misma tool con los mismos filtros para recomputar el trend completo. Offsets fallidos: ${offsets}${moreOffsets}.`;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
total_matching_orders: params.total,
|
|
69
|
+
limit: params.effectiveLimit,
|
|
70
|
+
offset: params.effectiveOffset,
|
|
71
|
+
returned: params.ordersAggregated,
|
|
72
|
+
orders_aggregated: params.ordersAggregated,
|
|
73
|
+
continuation,
|
|
74
|
+
fetch_mode: "full_aggregate",
|
|
75
|
+
pages_requested: params.pagesRequested,
|
|
76
|
+
pages_succeeded: params.pagesSucceeded,
|
|
77
|
+
pages_failed: params.pagesFailed,
|
|
78
|
+
universe_fully_fetched: universeFullyFetched
|
|
79
|
+
};
|
|
80
|
+
}
|
|
15
81
|
const meliGetSalesTrendSchema = z.object({
|
|
16
82
|
profileId: mercadolibreProfileIdSchemaField,
|
|
17
83
|
startDate: z.string().regex(mercadoLibreDateRegex).describe("Start date in YYYY-MM-DD format."),
|
|
18
84
|
endDate: z.string().regex(mercadoLibreDateRegex).describe("End date in YYYY-MM-DD format."),
|
|
19
85
|
granularity: z.enum(["day", "week"]).optional().describe("Trend granularity. Defaults to day."),
|
|
20
86
|
status: z.string().trim().min(1).optional().describe("Optional order status filter."),
|
|
21
|
-
limit: z.number().int().min(1).max(50).optional().describe("
|
|
22
|
-
offset: z.number().int().min(0).max(5e3).optional().describe("
|
|
87
|
+
limit: z.number().int().min(1).max(50).optional().describe("Optional MercadoLibre page size used internally while aggregating the full trend. Defaults to 50."),
|
|
88
|
+
offset: z.number().int().min(0).max(5e3).optional().describe("Optional starting offset used internally before fetching the remaining pages needed to aggregate the full trend. Defaults to 0.")
|
|
23
89
|
});
|
|
24
90
|
async function meliGetSalesTrendHandler(params) {
|
|
25
91
|
try {
|
|
@@ -29,18 +95,68 @@ async function meliGetSalesTrendHandler(params) {
|
|
|
29
95
|
}
|
|
30
96
|
const profileId = profileResolution.value.profileId;
|
|
31
97
|
const granularity = params.granularity ?? "day";
|
|
98
|
+
const requestedLimit = params.limit ?? DEFAULT_PAGE_LIMIT;
|
|
99
|
+
const requestedOffset = params.offset ?? 0;
|
|
32
100
|
const response = await searchMercadoLibreOrders(profileId, {
|
|
33
101
|
seller: "",
|
|
34
102
|
from: params.startDate,
|
|
35
103
|
to: params.endDate,
|
|
36
104
|
status: params.status,
|
|
37
|
-
limit:
|
|
38
|
-
offset:
|
|
105
|
+
limit: requestedLimit,
|
|
106
|
+
offset: requestedOffset
|
|
39
107
|
});
|
|
108
|
+
const paging = normalizeMercadoLibrePaging(asRecord(response.paging));
|
|
109
|
+
const baseOrders = asArray(response.results);
|
|
110
|
+
const effectiveLimit = paging.limit || requestedLimit || baseOrders.length;
|
|
111
|
+
const effectiveOffset = paging.offset || requestedOffset || 0;
|
|
112
|
+
const allOrders = [...baseOrders];
|
|
113
|
+
const failedPages = [];
|
|
114
|
+
let pagesRequested = 1;
|
|
115
|
+
let pagesSucceeded = 1;
|
|
116
|
+
if (effectiveLimit > 0 && paging.total > effectiveOffset + baseOrders.length) {
|
|
117
|
+
const nextOffsets = [];
|
|
118
|
+
for (let nextOffset = effectiveOffset + effectiveLimit; nextOffset < paging.total; nextOffset += effectiveLimit) {
|
|
119
|
+
nextOffsets.push(nextOffset);
|
|
120
|
+
}
|
|
121
|
+
pagesRequested += nextOffsets.length;
|
|
122
|
+
if (nextOffsets.length > 0) {
|
|
123
|
+
const batch = await searchMercadoLibreOrdersBatch(
|
|
124
|
+
profileId,
|
|
125
|
+
{
|
|
126
|
+
seller: "",
|
|
127
|
+
from: params.startDate,
|
|
128
|
+
to: params.endDate,
|
|
129
|
+
status: params.status,
|
|
130
|
+
limit: effectiveLimit
|
|
131
|
+
},
|
|
132
|
+
nextOffsets,
|
|
133
|
+
{
|
|
134
|
+
maxConcurrency: PAGE_FETCH_CONCURRENCY,
|
|
135
|
+
maxRetries: PAGE_FETCH_MAX_RETRIES
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
pagesSucceeded += batch.successful.length;
|
|
139
|
+
batch.successful.forEach((entry) => {
|
|
140
|
+
allOrders.push(...asArray(entry.document.results));
|
|
141
|
+
});
|
|
142
|
+
batch.failed.forEach((failure) => {
|
|
143
|
+
const failedOffset = Number(failure.id);
|
|
144
|
+
failedPages.push({
|
|
145
|
+
offset: failedOffset,
|
|
146
|
+
limit: effectiveLimit,
|
|
147
|
+
page_number: Math.floor(failedOffset / effectiveLimit) + 1,
|
|
148
|
+
message: failure.message,
|
|
149
|
+
status_code: failure.statusCode,
|
|
150
|
+
attempts: failure.attempts,
|
|
151
|
+
retryable: failure.retryable
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
40
156
|
const buckets = {};
|
|
41
|
-
for (const order of
|
|
157
|
+
for (const order of allOrders) {
|
|
42
158
|
const createdAt = normalizeString(order.date_created);
|
|
43
|
-
const dateBucket = createdAt
|
|
159
|
+
const dateBucket = getLocalDateBucket(createdAt, BUSINESS_TIME_ZONE);
|
|
44
160
|
if (!dateBucket) {
|
|
45
161
|
continue;
|
|
46
162
|
}
|
|
@@ -55,6 +171,16 @@ async function meliGetSalesTrendHandler(params) {
|
|
|
55
171
|
buckets[currencyId][bucketKey].orders += 1;
|
|
56
172
|
buckets[currencyId][bucketKey].revenue += toNumber(order.total_amount);
|
|
57
173
|
}
|
|
174
|
+
const metadata = buildSalesTrendMetadata({
|
|
175
|
+
total: paging.total,
|
|
176
|
+
effectiveLimit,
|
|
177
|
+
effectiveOffset,
|
|
178
|
+
ordersAggregated: allOrders.length,
|
|
179
|
+
pagesRequested,
|
|
180
|
+
pagesSucceeded,
|
|
181
|
+
pagesFailed: failedPages.length,
|
|
182
|
+
failedOffsets: failedPages.map((failure) => failure.offset)
|
|
183
|
+
});
|
|
58
184
|
return object(
|
|
59
185
|
stripNulls({
|
|
60
186
|
profile_id: profileId,
|
|
@@ -65,11 +191,8 @@ async function meliGetSalesTrendHandler(params) {
|
|
|
65
191
|
Object.values(items).map((item) => ({ ...item, revenue: roundMoney(item.revenue) })).sort((left, right) => left.bucket.localeCompare(right.bucket))
|
|
66
192
|
])
|
|
67
193
|
),
|
|
68
|
-
metadata
|
|
69
|
-
|
|
70
|
-
limit: params.limit ?? 50,
|
|
71
|
-
offset: params.offset ?? 0
|
|
72
|
-
}
|
|
194
|
+
metadata,
|
|
195
|
+
failed_pages: failedPages.length > 0 ? failedPages : void 0
|
|
73
196
|
})
|
|
74
197
|
);
|
|
75
198
|
} catch (err) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/tools/mercadolibre/get-sales-trend.ts"],
|
|
4
|
-
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport {\n formatMercadoLibreError,\n normalizeMercadoLibrePaging,\n} from \"../../services/mercadolibre/mercadolibre-api.js\";\nimport {\n searchMercadoLibreOrders,\n searchMercadoLibreOrdersBatch,\n} from \"../../services/mercadolibre/mercadolibre-orders.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\nimport {\n asArray,\n asRecord,\n mercadoLibreDateRegex,\n normalizeString,\n roundMoney,\n toNumber,\n} from \"./helpers.js\";\nimport { resolveMercadoLibreProfileOrSelection } from \"./profile-resolution.js\";\nimport { mercadolibreProfileIdSchemaField } from \"./write-helpers.js\";\n\nconst PAGE_FETCH_CONCURRENCY = 15;\nconst PAGE_FETCH_MAX_RETRIES = 2;\nconst DEFAULT_PAGE_LIMIT = 50;\nconst BUSINESS_TIME_ZONE = \"America/Argentina/Buenos_Aires\";\n\ntype FailedPage = {\n offset: number;\n limit: number;\n page_number: number;\n message: string;\n status_code?: number;\n attempts: number;\n retryable: boolean;\n};\n\nfunction formatDateInTimeZone(dateValue: Date, timeZone: string): string {\n const formatter = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n });\n\n const parts = formatter.formatToParts(dateValue);\n const year = parts.find((part) => part.type === \"year\")?.value;\n const month = parts.find((part) => part.type === \"month\")?.value;\n const day = parts.find((part) => part.type === \"day\")?.value;\n\n if (!year || !month || !day) {\n return \"\";\n }\n\n return `${year}-${month}-${day}`;\n}\n\nfunction getLocalDateBucket(createdAt: string, timeZone: string): string {\n if (!createdAt) {\n return \"\";\n }\n\n const date = new Date(createdAt);\n if (Number.isNaN(date.getTime())) {\n return \"\";\n }\n\n return formatDateInTimeZone(date, timeZone);\n}\n\nfunction getWeekBucket(dateValue: string): string {\n const date = new Date(`${dateValue}T00:00:00.000Z`);\n const day = date.getUTCDay() || 7;\n date.setUTCDate(date.getUTCDate() - day + 1);\n return date.toISOString().slice(0, 10);\n}\n\nfunction buildSalesTrendMetadata(params: {\n total: number;\n effectiveLimit: number;\n effectiveOffset: number;\n ordersAggregated: number;\n pagesRequested: number;\n pagesSucceeded: number;\n pagesFailed: number;\n failedOffsets: number[];\n}) {\n const hasFailures = params.pagesFailed > 0;\n const universeFullyFetched = !hasFailures && params.ordersAggregated >= params.total;\n\n let continuation = \"Agregado completo. No more pages for this query.\";\n if (hasFailures) {\n const offsets = params.failedOffsets.slice(0, 5).join(\", \");\n const moreOffsets =\n params.failedOffsets.length > 5\n ? ` y ${params.failedOffsets.length - 5} offsets adicionales`\n : \"\";\n continuation =\n `Agregado parcial por paginas fallidas. Reintentar la misma tool con los mismos filtros para recomputar el trend completo. ` +\n `Offsets fallidos: ${offsets}${moreOffsets}.`;\n }\n\n return {\n total_matching_orders: params.total,\n limit: params.effectiveLimit,\n offset: params.effectiveOffset,\n returned: params.ordersAggregated,\n orders_aggregated: params.ordersAggregated,\n continuation,\n fetch_mode: \"full_aggregate\",\n pages_requested: params.pagesRequested,\n pages_succeeded: params.pagesSucceeded,\n pages_failed: params.pagesFailed,\n universe_fully_fetched: universeFullyFetched,\n };\n}\n\nexport const meliGetSalesTrendSchema = z.object({\n profileId: mercadolibreProfileIdSchemaField,\n startDate: z.string().regex(mercadoLibreDateRegex).describe(\"Start date in YYYY-MM-DD format.\"),\n endDate: z.string().regex(mercadoLibreDateRegex).describe(\"End date in YYYY-MM-DD format.\"),\n granularity: z.enum([\"day\", \"week\"]).optional().describe(\"Trend granularity. Defaults to day.\"),\n status: z.string().trim().min(1).optional().describe(\"Optional order status filter.\"),\n limit: z.number().int().min(1).max(50).optional().describe(\"Optional MercadoLibre page size used internally while aggregating the full trend. Defaults to 50.\"),\n offset: z.number().int().min(0).max(5000).optional().describe(\"Optional starting offset used internally before fetching the remaining pages needed to aggregate the full trend. Defaults to 0.\"),\n});\n\nexport async function meliGetSalesTrendHandler(params: z.infer<typeof meliGetSalesTrendSchema>) {\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 granularity = params.granularity ?? \"day\";\n const requestedLimit = params.limit ?? DEFAULT_PAGE_LIMIT;\n const requestedOffset = params.offset ?? 0;\n const response = await searchMercadoLibreOrders(profileId, {\n seller: \"\",\n from: params.startDate,\n to: params.endDate,\n status: params.status,\n limit: requestedLimit,\n offset: requestedOffset,\n });\n\n const paging = normalizeMercadoLibrePaging(asRecord(response.paging));\n const baseOrders = asArray<Record<string, unknown>>(response.results);\n const effectiveLimit = paging.limit || requestedLimit || baseOrders.length;\n const effectiveOffset = paging.offset || requestedOffset || 0;\n const allOrders = [...baseOrders];\n const failedPages: FailedPage[] = [];\n let pagesRequested = 1;\n let pagesSucceeded = 1;\n\n if (effectiveLimit > 0 && paging.total > effectiveOffset + baseOrders.length) {\n const nextOffsets: number[] = [];\n for (\n let nextOffset = effectiveOffset + effectiveLimit;\n nextOffset < paging.total;\n nextOffset += effectiveLimit\n ) {\n nextOffsets.push(nextOffset);\n }\n\n pagesRequested += nextOffsets.length;\n\n if (nextOffsets.length > 0) {\n const batch = await searchMercadoLibreOrdersBatch(\n profileId,\n {\n seller: \"\",\n from: params.startDate,\n to: params.endDate,\n status: params.status,\n limit: effectiveLimit,\n },\n nextOffsets,\n {\n maxConcurrency: PAGE_FETCH_CONCURRENCY,\n maxRetries: PAGE_FETCH_MAX_RETRIES,\n }\n );\n\n pagesSucceeded += batch.successful.length;\n batch.successful.forEach((entry) => {\n allOrders.push(...asArray<Record<string, unknown>>(entry.document.results));\n });\n batch.failed.forEach((failure) => {\n const failedOffset = Number(failure.id);\n failedPages.push({\n offset: failedOffset,\n limit: effectiveLimit,\n page_number: Math.floor(failedOffset / effectiveLimit) + 1,\n message: failure.message,\n status_code: failure.statusCode,\n attempts: failure.attempts,\n retryable: failure.retryable,\n });\n });\n }\n }\n\n const buckets: Record<string, Record<string, { bucket: string; orders: number; revenue: number }>> = {};\n\n for (const order of allOrders) {\n const createdAt = normalizeString(order.date_created);\n const dateBucket = getLocalDateBucket(createdAt, BUSINESS_TIME_ZONE);\n if (!dateBucket) {\n continue;\n }\n\n const bucketKey = granularity === \"week\" ? getWeekBucket(dateBucket) : dateBucket;\n const currencyId = normalizeString(order.currency_id, \"UNKNOWN\");\n buckets[currencyId] = buckets[currencyId] ?? {};\n buckets[currencyId][bucketKey] = buckets[currencyId][bucketKey] ?? {\n bucket: bucketKey,\n orders: 0,\n revenue: 0,\n };\n buckets[currencyId][bucketKey].orders += 1;\n buckets[currencyId][bucketKey].revenue += toNumber(order.total_amount);\n }\n\n const metadata = buildSalesTrendMetadata({\n total: paging.total,\n effectiveLimit,\n effectiveOffset,\n ordersAggregated: allOrders.length,\n pagesRequested,\n pagesSucceeded,\n pagesFailed: failedPages.length,\n failedOffsets: failedPages.map((failure) => failure.offset),\n });\n\n return object(\n stripNulls({\n profile_id: profileId,\n granularity,\n trend_by_currency: Object.fromEntries(\n Object.entries(buckets).map(([currencyId, items]) => [\n currencyId,\n Object.values(items)\n .map((item) => ({ ...item, revenue: roundMoney(item.revenue) }))\n .sort((left, right) => left.bucket.localeCompare(right.bucket)),\n ])\n ),\n metadata,\n failed_pages: failedPages.length > 0 ? failedPages : undefined,\n })\n );\n } catch (err) {\n return error(formatMercadoLibreError(err, \"Failed to build MercadoLibre sales trend\"));\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|