@yoryoboy/bi-mcp 1.8.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 +15 -4
- 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.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 -21
- 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/index.js +1 -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/package.json +1 -1
|
@@ -0,0 +1,546 @@
|
|
|
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 { getMercadoLibreBillingDetails } from "../../services/mercadolibre/mercadolibre-billing.js";
|
|
5
|
+
import {
|
|
6
|
+
getMercadoLibreShipmentCostsBatch,
|
|
7
|
+
searchMercadoLibreOrders,
|
|
8
|
+
searchMercadoLibreOrdersBatch
|
|
9
|
+
} from "../../services/mercadolibre/mercadolibre-orders.js";
|
|
10
|
+
import { stripNulls } from "../../utils/strip-payload.js";
|
|
11
|
+
import {
|
|
12
|
+
asArray,
|
|
13
|
+
asRecord,
|
|
14
|
+
mercadoLibreDateRegex,
|
|
15
|
+
normalizeScalarString,
|
|
16
|
+
normalizeString,
|
|
17
|
+
roundMoney,
|
|
18
|
+
toNumber
|
|
19
|
+
} from "./helpers.js";
|
|
20
|
+
import {
|
|
21
|
+
buildMercadoLibreListingFeeEstimate
|
|
22
|
+
} from "./listing-fee-helpers.js";
|
|
23
|
+
import { resolveMercadoLibreProfileOrSelection } from "./profile-resolution.js";
|
|
24
|
+
import { mercadolibreProfileIdSchemaField } from "./write-helpers.js";
|
|
25
|
+
const DEFAULT_AUDIT_DAYS = 30;
|
|
26
|
+
const ORDERS_PAGE_LIMIT = 50;
|
|
27
|
+
const PAGE_FETCH_CONCURRENCY = 12;
|
|
28
|
+
const PAGE_FETCH_MAX_RETRIES = 2;
|
|
29
|
+
const BILLING_PAGE_LIMIT = 150;
|
|
30
|
+
const BILLING_MAX_PAGES_PER_PERIOD = 20;
|
|
31
|
+
const meliEstimateProductProfitabilitySchema = z.object({
|
|
32
|
+
profileId: mercadolibreProfileIdSchemaField,
|
|
33
|
+
mode: z.enum(["current_item", "target_margin"]).describe("Profitability mode. current_item analyzes an existing listing; target_margin estimates the price needed for a desired margin."),
|
|
34
|
+
itemId: z.string().trim().min(1).optional().describe("Optional MercadoLibre item id. Strongly recommended for current_item so the tool can autocomplete listing and logistics data."),
|
|
35
|
+
query: z.string().trim().min(2).optional().describe("Optional product phrase used to suggest category candidates when categoryId is missing."),
|
|
36
|
+
price: z.number().positive().optional().describe("Selling price to analyze. Overrides the MercadoLibre item price when itemId is provided."),
|
|
37
|
+
targetMarginPct: z.number().min(0).max(95).optional().describe("Desired profit margin as percentage of selling price. Required for target_margin to calculate a target price."),
|
|
38
|
+
unitCost: z.number().nonnegative().optional().describe("Unit product cost paid by the seller. MercadoLibre does not provide this; pass it for real profitability."),
|
|
39
|
+
packagingCost: z.number().nonnegative().optional().describe("Packaging cost per sold unit. MercadoLibre does not provide this; defaults to zero when omitted but is reported as missing."),
|
|
40
|
+
operatingCost: z.number().nonnegative().optional().describe("Additional internal operating cost per sold unit. MercadoLibre does not provide this; defaults to zero when omitted."),
|
|
41
|
+
replacementCost: z.number().nonnegative().optional().describe("Current replacement cost per unit. Used as cost basis if unitCost is omitted and useful for future pricing decisions."),
|
|
42
|
+
returnReservePct: z.number().min(0).max(100).optional().describe("Reserve for returns, refunds, and post-sale friction as percentage of selling price. Defaults to zero when omitted."),
|
|
43
|
+
taxRatePct: z.number().min(0).max(100).optional().describe("Optional tax/withholding reserve as percentage of selling price. Use when Billing ML is not enough or for simulations."),
|
|
44
|
+
shippingCost: z.number().nonnegative().optional().describe("Optional shipping cost per unit to use in profitability. Overrides historical shipping average when provided."),
|
|
45
|
+
categoryId: z.string().trim().min(1).optional().describe("MercadoLibre category id override for fee estimation."),
|
|
46
|
+
listingTypeId: z.string().trim().min(1).optional().describe("MercadoLibre listing type id override for fee estimation."),
|
|
47
|
+
shippingMode: z.string().trim().min(1).optional().describe("MercadoLibre shipping mode override for fee estimation."),
|
|
48
|
+
logisticType: z.string().trim().min(1).optional().describe("MercadoLibre logistic type override for fee estimation."),
|
|
49
|
+
billableWeight: z.number().positive().optional().describe("Billable package weight in grams for fee estimation."),
|
|
50
|
+
currencyId: z.string().trim().min(3).optional().describe("Currency id. Defaults to item currency or ARS."),
|
|
51
|
+
tags: z.array(z.string().trim().min(1)).optional().describe("Optional MercadoLibre fee tags such as campaign or installment tags."),
|
|
52
|
+
useBillingAudit: z.boolean().optional().describe("When true, includes MercadoLibre Billing details as historical audit. Defaults to true for current_item with itemId."),
|
|
53
|
+
auditStartDate: z.string().regex(mercadoLibreDateRegex).optional().describe("Historical audit start date in YYYY-MM-DD. Defaults to the last 30 days for current_item."),
|
|
54
|
+
auditEndDate: z.string().regex(mercadoLibreDateRegex).optional().describe("Historical audit end date in YYYY-MM-DD. Defaults to today for current_item.")
|
|
55
|
+
});
|
|
56
|
+
function isoDate(date) {
|
|
57
|
+
return date.toISOString().slice(0, 10);
|
|
58
|
+
}
|
|
59
|
+
function defaultAuditWindow() {
|
|
60
|
+
const end = /* @__PURE__ */ new Date();
|
|
61
|
+
const start = new Date(end);
|
|
62
|
+
start.setUTCDate(start.getUTCDate() - DEFAULT_AUDIT_DAYS);
|
|
63
|
+
return {
|
|
64
|
+
startDate: isoDate(start),
|
|
65
|
+
endDate: isoDate(end)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function parseDate(value) {
|
|
69
|
+
return /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
70
|
+
}
|
|
71
|
+
function monthKeysBetween(startDate, endDate) {
|
|
72
|
+
const start = parseDate(startDate);
|
|
73
|
+
const end = parseDate(endDate);
|
|
74
|
+
const keys = /* @__PURE__ */ new Set();
|
|
75
|
+
const cursor = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), 1));
|
|
76
|
+
const last = new Date(Date.UTC(end.getUTCFullYear(), end.getUTCMonth(), 1));
|
|
77
|
+
while (cursor <= last) {
|
|
78
|
+
keys.add(`${cursor.getUTCFullYear()}-${String(cursor.getUTCMonth() + 1).padStart(2, "0")}-01`);
|
|
79
|
+
cursor.setUTCMonth(cursor.getUTCMonth() + 1);
|
|
80
|
+
}
|
|
81
|
+
return [...keys];
|
|
82
|
+
}
|
|
83
|
+
function dateInRange(dateTime, startDate, endDate) {
|
|
84
|
+
const date = dateTime.slice(0, 10);
|
|
85
|
+
return date >= startDate && date <= endDate;
|
|
86
|
+
}
|
|
87
|
+
function extractShipmentCost(costs) {
|
|
88
|
+
const senders = asArray(costs.senders);
|
|
89
|
+
const senderCost = senders.reduce((sum, sender) => sum + toNumber(sender.cost), 0);
|
|
90
|
+
return roundMoney(
|
|
91
|
+
senderCost || toNumber(asRecord(costs.senders).cost) || toNumber(asRecord(costs.receiver).cost) || toNumber(costs.gross_amount) || toNumber(costs.cost)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
function getOrderTotal(order) {
|
|
95
|
+
return toNumber(order.total_amount) || toNumber(order.paid_amount);
|
|
96
|
+
}
|
|
97
|
+
async function fetchOrdersForAudit(profileId, params) {
|
|
98
|
+
const firstPage = await searchMercadoLibreOrders(profileId, {
|
|
99
|
+
seller: "",
|
|
100
|
+
from: params.startDate,
|
|
101
|
+
to: params.endDate,
|
|
102
|
+
limit: ORDERS_PAGE_LIMIT,
|
|
103
|
+
offset: 0
|
|
104
|
+
});
|
|
105
|
+
const paging = normalizeMercadoLibrePaging(asRecord(firstPage.paging));
|
|
106
|
+
const orders = [...asArray(firstPage.results)];
|
|
107
|
+
let pagesRequested = 1;
|
|
108
|
+
let pagesSucceeded = 1;
|
|
109
|
+
const failures = [];
|
|
110
|
+
const offsets = [];
|
|
111
|
+
for (let offset = ORDERS_PAGE_LIMIT; offset < paging.total; offset += ORDERS_PAGE_LIMIT) {
|
|
112
|
+
offsets.push(offset);
|
|
113
|
+
}
|
|
114
|
+
if (offsets.length > 0) {
|
|
115
|
+
pagesRequested += offsets.length;
|
|
116
|
+
const batch = await searchMercadoLibreOrdersBatch(
|
|
117
|
+
profileId,
|
|
118
|
+
{
|
|
119
|
+
seller: "",
|
|
120
|
+
from: params.startDate,
|
|
121
|
+
to: params.endDate,
|
|
122
|
+
limit: ORDERS_PAGE_LIMIT
|
|
123
|
+
},
|
|
124
|
+
offsets,
|
|
125
|
+
{
|
|
126
|
+
maxConcurrency: PAGE_FETCH_CONCURRENCY,
|
|
127
|
+
maxRetries: PAGE_FETCH_MAX_RETRIES
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
pagesSucceeded += batch.successful.length;
|
|
131
|
+
batch.successful.forEach((entry) => orders.push(...asArray(entry.document.results)));
|
|
132
|
+
batch.failed.forEach(
|
|
133
|
+
(failure) => failures.push({
|
|
134
|
+
offset: Number(failure.id),
|
|
135
|
+
message: failure.message,
|
|
136
|
+
status_code: failure.statusCode
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
orders,
|
|
142
|
+
total: paging.total,
|
|
143
|
+
pages_requested: pagesRequested,
|
|
144
|
+
pages_succeeded: pagesSucceeded,
|
|
145
|
+
pages_failed: failures.length,
|
|
146
|
+
failures
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async function buildHistoricalOrderAudit(profileId, params) {
|
|
150
|
+
const fetched = await fetchOrdersForAudit(profileId, params);
|
|
151
|
+
const matchingOrders = [];
|
|
152
|
+
const shipmentRevenueShares = /* @__PURE__ */ new Map();
|
|
153
|
+
let units = 0;
|
|
154
|
+
let revenue = 0;
|
|
155
|
+
let saleFee = 0;
|
|
156
|
+
let multiUnitLines = 0;
|
|
157
|
+
for (const order of fetched.orders) {
|
|
158
|
+
const orderItems = asArray(order.order_items);
|
|
159
|
+
let matchedRevenue = 0;
|
|
160
|
+
for (const orderItem of orderItems) {
|
|
161
|
+
const item = asRecord(orderItem.item);
|
|
162
|
+
if (normalizeString(item.id) !== params.itemId) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const quantity = toNumber(orderItem.quantity);
|
|
166
|
+
const unitPrice = toNumber(orderItem.unit_price);
|
|
167
|
+
const lineRevenue = unitPrice * quantity;
|
|
168
|
+
units += quantity;
|
|
169
|
+
revenue += lineRevenue;
|
|
170
|
+
saleFee += toNumber(orderItem.sale_fee);
|
|
171
|
+
matchedRevenue += lineRevenue;
|
|
172
|
+
if (quantity > 1) {
|
|
173
|
+
multiUnitLines += 1;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (matchedRevenue > 0) {
|
|
177
|
+
matchingOrders.push(order);
|
|
178
|
+
const shipmentId = normalizeScalarString(asRecord(order.shipping).id);
|
|
179
|
+
const orderTotal = getOrderTotal(order);
|
|
180
|
+
if (shipmentId) {
|
|
181
|
+
shipmentRevenueShares.set(
|
|
182
|
+
shipmentId,
|
|
183
|
+
(shipmentRevenueShares.get(shipmentId) ?? 0) + (orderTotal > 0 ? matchedRevenue / orderTotal : 1)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
let shippingCost = 0;
|
|
189
|
+
const shipmentIds = [...shipmentRevenueShares.keys()];
|
|
190
|
+
const shippingFailures = [];
|
|
191
|
+
if (shipmentIds.length > 0) {
|
|
192
|
+
const costsBatch = await getMercadoLibreShipmentCostsBatch(profileId, shipmentIds, {
|
|
193
|
+
maxConcurrency: PAGE_FETCH_CONCURRENCY,
|
|
194
|
+
maxRetries: PAGE_FETCH_MAX_RETRIES
|
|
195
|
+
});
|
|
196
|
+
costsBatch.successful.forEach((entry) => {
|
|
197
|
+
const share = Math.min(1, shipmentRevenueShares.get(entry.id) ?? 1);
|
|
198
|
+
shippingCost += extractShipmentCost(entry.document) * share;
|
|
199
|
+
});
|
|
200
|
+
costsBatch.failed.forEach(
|
|
201
|
+
(failure) => shippingFailures.push({
|
|
202
|
+
shipment_id: failure.id,
|
|
203
|
+
message: failure.message,
|
|
204
|
+
status_code: failure.statusCode
|
|
205
|
+
})
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return stripNulls({
|
|
209
|
+
window: {
|
|
210
|
+
start_date: params.startDate,
|
|
211
|
+
end_date: params.endDate
|
|
212
|
+
},
|
|
213
|
+
coverage: {
|
|
214
|
+
total_matching_orders: fetched.total,
|
|
215
|
+
orders_inspected: fetched.orders.length,
|
|
216
|
+
item_orders: matchingOrders.length,
|
|
217
|
+
pages_requested: fetched.pages_requested,
|
|
218
|
+
pages_succeeded: fetched.pages_succeeded,
|
|
219
|
+
pages_failed: fetched.pages_failed,
|
|
220
|
+
universe_fully_fetched: fetched.pages_failed === 0
|
|
221
|
+
},
|
|
222
|
+
item_metrics: {
|
|
223
|
+
orders: matchingOrders.length,
|
|
224
|
+
units,
|
|
225
|
+
revenue: roundMoney(revenue),
|
|
226
|
+
avg_unit_price: units > 0 ? roundMoney(revenue / units) : 0,
|
|
227
|
+
sale_fee_total: roundMoney(saleFee),
|
|
228
|
+
avg_sale_fee_per_unit: units > 0 ? roundMoney(saleFee / units) : 0,
|
|
229
|
+
sale_fee_rate_on_revenue: revenue > 0 ? Number((saleFee / revenue).toFixed(4)) : 0,
|
|
230
|
+
shipping_cost_total: roundMoney(shippingCost),
|
|
231
|
+
avg_shipping_cost_per_unit: units > 0 ? roundMoney(shippingCost / units) : 0,
|
|
232
|
+
multi_unit_lines: multiUnitLines
|
|
233
|
+
},
|
|
234
|
+
warnings: multiUnitLines > 0 ? [
|
|
235
|
+
"MercadoLibre order_items.sale_fee se usa como viene de la API; si ML lo devuelve por l\xEDnea y no por unidad, revisar multi-unit lines."
|
|
236
|
+
] : void 0,
|
|
237
|
+
failures: [...fetched.failures, ...shippingFailures]
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function extractBillingOrderIds(row) {
|
|
241
|
+
return asArray(row.sales_info).map((sale) => normalizeScalarString(sale.order_id)).filter(Boolean);
|
|
242
|
+
}
|
|
243
|
+
function extractBillingSaleDates(row) {
|
|
244
|
+
return asArray(row.sales_info).map((sale) => normalizeString(sale.sale_date_time)).filter(Boolean);
|
|
245
|
+
}
|
|
246
|
+
function billingRowMatchesItem(row, itemId, startDate, endDate) {
|
|
247
|
+
const itemMatches = asArray(row.items_info).some(
|
|
248
|
+
(item) => normalizeString(item.item_id) === itemId
|
|
249
|
+
);
|
|
250
|
+
const dateMatches = extractBillingSaleDates(row).some((date) => dateInRange(date, startDate, endDate));
|
|
251
|
+
return itemMatches && dateMatches;
|
|
252
|
+
}
|
|
253
|
+
async function buildBillingAudit(profileId, params) {
|
|
254
|
+
const periodKeys = monthKeysBetween(params.startDate, params.endDate);
|
|
255
|
+
const rowsByDetailId = /* @__PURE__ */ new Map();
|
|
256
|
+
const periodResults = [];
|
|
257
|
+
const failures = [];
|
|
258
|
+
for (const periodKey of periodKeys) {
|
|
259
|
+
const result = await getMercadoLibreBillingDetails(profileId, {
|
|
260
|
+
periodKey,
|
|
261
|
+
group: "ML",
|
|
262
|
+
documentType: "BILL",
|
|
263
|
+
limit: BILLING_PAGE_LIMIT,
|
|
264
|
+
maxPages: BILLING_MAX_PAGES_PER_PERIOD
|
|
265
|
+
});
|
|
266
|
+
periodResults.push({
|
|
267
|
+
period_key: result.period_key,
|
|
268
|
+
total_reported: result.total_reported,
|
|
269
|
+
rows_fetched: result.rows.length,
|
|
270
|
+
pages_requested: result.pages_requested,
|
|
271
|
+
pages_succeeded: result.pages_succeeded,
|
|
272
|
+
pages_failed: result.pages_failed
|
|
273
|
+
});
|
|
274
|
+
failures.push(...result.failures.map((failure) => ({ period_key: periodKey, ...failure })));
|
|
275
|
+
for (const row of result.rows) {
|
|
276
|
+
if (!billingRowMatchesItem(row, params.itemId, params.startDate, params.endDate)) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const detailId = normalizeScalarString(asRecord(row.charge_info).detail_id);
|
|
280
|
+
rowsByDetailId.set(detailId || `${periodKey}:${rowsByDetailId.size}`, row);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const breakdown = /* @__PURE__ */ new Map();
|
|
284
|
+
let totalCharges = 0;
|
|
285
|
+
const orderIds = /* @__PURE__ */ new Set();
|
|
286
|
+
for (const row of rowsByDetailId.values()) {
|
|
287
|
+
const chargeInfo = asRecord(row.charge_info);
|
|
288
|
+
const subtype = normalizeString(chargeInfo.detail_sub_type, "UNKNOWN");
|
|
289
|
+
const label = normalizeString(chargeInfo.transaction_detail, subtype);
|
|
290
|
+
const amount = toNumber(chargeInfo.detail_amount);
|
|
291
|
+
const current = breakdown.get(subtype) ?? { amount: 0, rows: 0, label };
|
|
292
|
+
current.amount += amount;
|
|
293
|
+
current.rows += 1;
|
|
294
|
+
breakdown.set(subtype, current);
|
|
295
|
+
totalCharges += amount;
|
|
296
|
+
extractBillingOrderIds(row).forEach((orderId) => orderIds.add(orderId));
|
|
297
|
+
}
|
|
298
|
+
return stripNulls({
|
|
299
|
+
enabled: true,
|
|
300
|
+
window: {
|
|
301
|
+
start_date: params.startDate,
|
|
302
|
+
end_date: params.endDate,
|
|
303
|
+
period_keys: periodKeys
|
|
304
|
+
},
|
|
305
|
+
coverage: {
|
|
306
|
+
periods_requested: periodKeys.length,
|
|
307
|
+
rows_matched: rowsByDetailId.size,
|
|
308
|
+
orders_matched: orderIds.size,
|
|
309
|
+
total_charge_amount: roundMoney(totalCharges),
|
|
310
|
+
partial: failures.length > 0,
|
|
311
|
+
period_results: periodResults
|
|
312
|
+
},
|
|
313
|
+
charge_breakdown: Array.from(breakdown.entries()).sort((left, right) => Math.abs(right[1].amount) - Math.abs(left[1].amount)).map(([detail_sub_type, data]) => ({
|
|
314
|
+
detail_sub_type,
|
|
315
|
+
transaction_detail: data.label,
|
|
316
|
+
rows: data.rows,
|
|
317
|
+
amount: roundMoney(data.amount)
|
|
318
|
+
})),
|
|
319
|
+
failures
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
function buildMissingUserInputs(params) {
|
|
323
|
+
const missing = [];
|
|
324
|
+
if (params.unitCost === void 0 && params.replacementCost === void 0) {
|
|
325
|
+
missing.push({
|
|
326
|
+
field: "unitCost",
|
|
327
|
+
question: "\xBFCu\xE1nto te cuesta comprar o fabricar una unidad?",
|
|
328
|
+
impact: "Sin costo unitario no se puede afirmar ganancia real; solo break-even."
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
if (params.packagingCost === void 0) {
|
|
332
|
+
missing.push({
|
|
333
|
+
field: "packagingCost",
|
|
334
|
+
question: "\xBFCu\xE1nto gast\xE1s en packaging por unidad vendida?",
|
|
335
|
+
impact: "Mejora el margen neto unitario."
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
if (params.operatingCost === void 0) {
|
|
339
|
+
missing.push({
|
|
340
|
+
field: "operatingCost",
|
|
341
|
+
question: "\xBFTen\xE9s alg\xFAn costo operativo interno por unidad?",
|
|
342
|
+
impact: "Permite pasar de margen marketplace a margen operativo."
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (params.returnReservePct === void 0) {
|
|
346
|
+
missing.push({
|
|
347
|
+
field: "returnReservePct",
|
|
348
|
+
question: "\xBFQu\xE9 porcentaje quer\xE9s reservar para devoluciones/cancelaciones?",
|
|
349
|
+
impact: "Evita sobrestimar margen si hay fricci\xF3n post-venta."
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (params.mode === "target_margin" && params.targetMarginPct === void 0) {
|
|
353
|
+
missing.push({
|
|
354
|
+
field: "targetMarginPct",
|
|
355
|
+
question: "\xBFQu\xE9 margen objetivo quer\xE9s lograr sobre el precio de venta?",
|
|
356
|
+
impact: "Necesario para calcular precio m\xEDnimo objetivo."
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return missing;
|
|
360
|
+
}
|
|
361
|
+
function chooseFeeForProfitability(feeEstimate) {
|
|
362
|
+
return feeEstimate.selected_quote ?? feeEstimate.quotes.find((quote) => quote.listing_type_id === "gold_special") ?? feeEstimate.quotes[0];
|
|
363
|
+
}
|
|
364
|
+
function inferConfidence(params) {
|
|
365
|
+
if (params.feePrecision === "high" && params.hasCostBasis && params.hasShipping && params.hasBillingAudit) {
|
|
366
|
+
return "high";
|
|
367
|
+
}
|
|
368
|
+
if (params.feePrecision !== "low" && (params.hasCostBasis || params.hasHistoricalAudit)) {
|
|
369
|
+
return "medium";
|
|
370
|
+
}
|
|
371
|
+
return "low";
|
|
372
|
+
}
|
|
373
|
+
function estimateTargetPrice(params) {
|
|
374
|
+
if (!params.selectedQuote || params.targetMarginPct === void 0 || params.costBasis === void 0) {
|
|
375
|
+
return void 0;
|
|
376
|
+
}
|
|
377
|
+
const feePct = params.selectedQuote.sale_fee_details.percentage_fee / 100;
|
|
378
|
+
const fixedFee = params.selectedQuote.sale_fee_details.fixed_fee;
|
|
379
|
+
const targetPct = params.targetMarginPct / 100;
|
|
380
|
+
const returnPct = params.returnReservePct / 100;
|
|
381
|
+
const taxPct = params.taxRatePct / 100;
|
|
382
|
+
const denominator = 1 - feePct - targetPct - returnPct - taxPct;
|
|
383
|
+
const fixedCosts = fixedFee + params.costBasis + params.packagingCost + params.operatingCost + params.shippingCost;
|
|
384
|
+
if (denominator <= 0) {
|
|
385
|
+
return {
|
|
386
|
+
possible: false,
|
|
387
|
+
reason: "El margen objetivo m\xE1s fee/retenciones/reservas supera el 100% del precio."
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
possible: true,
|
|
392
|
+
estimated_price: roundMoney(fixedCosts / denominator),
|
|
393
|
+
formula_basis: {
|
|
394
|
+
fee_percentage: params.selectedQuote.sale_fee_details.percentage_fee,
|
|
395
|
+
fixed_fee: fixedFee,
|
|
396
|
+
target_margin_pct: params.targetMarginPct,
|
|
397
|
+
return_reserve_pct: params.returnReservePct,
|
|
398
|
+
tax_rate_pct: params.taxRatePct,
|
|
399
|
+
fixed_costs: roundMoney(fixedCosts)
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
async function meliEstimateProductProfitabilityHandler(params) {
|
|
404
|
+
try {
|
|
405
|
+
const profileResolution = await resolveMercadoLibreProfileOrSelection(params.profileId);
|
|
406
|
+
if (!profileResolution.ok) {
|
|
407
|
+
return profileResolution.response;
|
|
408
|
+
}
|
|
409
|
+
const profileId = profileResolution.value.profileId;
|
|
410
|
+
const defaultWindow = defaultAuditWindow();
|
|
411
|
+
const auditStartDate = params.auditStartDate ?? defaultWindow.startDate;
|
|
412
|
+
const auditEndDate = params.auditEndDate ?? defaultWindow.endDate;
|
|
413
|
+
const useBillingAudit = params.useBillingAudit ?? (params.mode === "current_item" && Boolean(params.itemId));
|
|
414
|
+
const feeEstimate = await buildMercadoLibreListingFeeEstimate({
|
|
415
|
+
profileId,
|
|
416
|
+
itemId: params.itemId,
|
|
417
|
+
query: params.query,
|
|
418
|
+
price: params.price,
|
|
419
|
+
categoryId: params.categoryId,
|
|
420
|
+
listingTypeId: params.listingTypeId,
|
|
421
|
+
shippingMode: params.shippingMode,
|
|
422
|
+
logisticType: params.logisticType,
|
|
423
|
+
billableWeight: params.billableWeight,
|
|
424
|
+
currencyId: params.currencyId,
|
|
425
|
+
tags: params.tags
|
|
426
|
+
});
|
|
427
|
+
const selectedQuote = chooseFeeForProfitability(feeEstimate);
|
|
428
|
+
const orderAudit = params.itemId && params.mode === "current_item" ? await buildHistoricalOrderAudit(profileId, {
|
|
429
|
+
itemId: params.itemId,
|
|
430
|
+
startDate: auditStartDate,
|
|
431
|
+
endDate: auditEndDate
|
|
432
|
+
}) : void 0;
|
|
433
|
+
let billingAudit;
|
|
434
|
+
if (useBillingAudit && params.itemId) {
|
|
435
|
+
try {
|
|
436
|
+
billingAudit = await buildBillingAudit(profileId, {
|
|
437
|
+
itemId: params.itemId,
|
|
438
|
+
startDate: auditStartDate,
|
|
439
|
+
endDate: auditEndDate
|
|
440
|
+
});
|
|
441
|
+
} catch (billingError) {
|
|
442
|
+
billingAudit = {
|
|
443
|
+
enabled: true,
|
|
444
|
+
failed: true,
|
|
445
|
+
message: formatMercadoLibreError(billingError, "Failed to build MercadoLibre billing audit")
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const price = params.price ?? selectedQuote?.price ?? toNumber(feeEstimate.context.used_params.price);
|
|
450
|
+
const estimatedSaleFee = selectedQuote?.sale_fee_amount ?? 0;
|
|
451
|
+
const historicalMetrics = asRecord(orderAudit?.item_metrics);
|
|
452
|
+
const historicalShippingPerUnit = toNumber(historicalMetrics.avg_shipping_cost_per_unit);
|
|
453
|
+
const shippingCost = params.shippingCost ?? historicalShippingPerUnit;
|
|
454
|
+
const costBasis = params.unitCost ?? params.replacementCost;
|
|
455
|
+
const packagingCost = params.packagingCost ?? 0;
|
|
456
|
+
const operatingCost = params.operatingCost ?? 0;
|
|
457
|
+
const returnReservePct = params.returnReservePct ?? 0;
|
|
458
|
+
const taxRatePct = params.taxRatePct ?? 0;
|
|
459
|
+
const returnReserveAmount = roundMoney(price * (returnReservePct / 100));
|
|
460
|
+
const taxReserveAmount = roundMoney(price * (taxRatePct / 100));
|
|
461
|
+
const knownMarketplaceCosts = roundMoney(estimatedSaleFee + shippingCost + returnReserveAmount + taxReserveAmount);
|
|
462
|
+
const knownUserCosts = costBasis !== void 0 ? roundMoney(costBasis + packagingCost + operatingCost) : void 0;
|
|
463
|
+
const profitAmount = knownUserCosts !== void 0 ? roundMoney(price - knownMarketplaceCosts - knownUserCosts) : void 0;
|
|
464
|
+
const marginPct = profitAmount !== void 0 && price > 0 ? Number((profitAmount / price * 100).toFixed(2)) : void 0;
|
|
465
|
+
const breakEvenBeforeUserCosts = roundMoney(price - knownMarketplaceCosts);
|
|
466
|
+
const missingUserInputs = buildMissingUserInputs(params);
|
|
467
|
+
const targetPrice = estimateTargetPrice({
|
|
468
|
+
selectedQuote,
|
|
469
|
+
targetMarginPct: params.targetMarginPct,
|
|
470
|
+
costBasis,
|
|
471
|
+
packagingCost,
|
|
472
|
+
operatingCost,
|
|
473
|
+
shippingCost,
|
|
474
|
+
returnReservePct,
|
|
475
|
+
taxRatePct
|
|
476
|
+
});
|
|
477
|
+
const confidence = inferConfidence({
|
|
478
|
+
feePrecision: feeEstimate.context.precision,
|
|
479
|
+
hasCostBasis: costBasis !== void 0,
|
|
480
|
+
hasHistoricalAudit: Boolean(orderAudit && toNumber(historicalMetrics.units) > 0),
|
|
481
|
+
hasBillingAudit: Boolean(billingAudit && !billingAudit.failed),
|
|
482
|
+
hasShipping: shippingCost > 0
|
|
483
|
+
});
|
|
484
|
+
return object(
|
|
485
|
+
stripNulls({
|
|
486
|
+
profile_id: profileId,
|
|
487
|
+
mode: params.mode,
|
|
488
|
+
known_from_mercadolibre: {
|
|
489
|
+
item_id: params.itemId,
|
|
490
|
+
title: feeEstimate.context.title,
|
|
491
|
+
seller_sku: feeEstimate.context.seller_sku,
|
|
492
|
+
price,
|
|
493
|
+
category_id: feeEstimate.context.used_params.category_id,
|
|
494
|
+
listing_type_id: feeEstimate.context.used_params.listing_type_id,
|
|
495
|
+
shipping_mode: feeEstimate.context.used_params.shipping_mode,
|
|
496
|
+
logistic_type: feeEstimate.context.used_params.logistic_type,
|
|
497
|
+
billable_weight: feeEstimate.context.used_params.billable_weight,
|
|
498
|
+
currency_id: feeEstimate.context.used_params.currency_id
|
|
499
|
+
},
|
|
500
|
+
estimated_fee: {
|
|
501
|
+
precision: feeEstimate.context.precision,
|
|
502
|
+
selected_quote: selectedQuote,
|
|
503
|
+
all_quotes: feeEstimate.quotes,
|
|
504
|
+
missing_params: feeEstimate.context.missing_params
|
|
505
|
+
},
|
|
506
|
+
historical_audit: stripNulls({
|
|
507
|
+
orders: orderAudit,
|
|
508
|
+
billing: billingAudit
|
|
509
|
+
}),
|
|
510
|
+
profitability: {
|
|
511
|
+
status: profitAmount === void 0 ? "needs_user_costs" : profitAmount >= 0 ? "profitable" : "not_profitable",
|
|
512
|
+
price,
|
|
513
|
+
estimated_sale_fee: estimatedSaleFee,
|
|
514
|
+
shipping_cost_used: roundMoney(shippingCost),
|
|
515
|
+
return_reserve_amount: returnReserveAmount,
|
|
516
|
+
tax_reserve_amount: taxReserveAmount,
|
|
517
|
+
known_marketplace_costs: knownMarketplaceCosts,
|
|
518
|
+
known_user_costs: knownUserCosts,
|
|
519
|
+
profit_amount: profitAmount,
|
|
520
|
+
margin_pct: marginPct,
|
|
521
|
+
target_price: targetPrice
|
|
522
|
+
},
|
|
523
|
+
break_even: {
|
|
524
|
+
max_total_user_cost_before_losing_money: breakEvenBeforeUserCosts,
|
|
525
|
+
explanation: "Si tus costos de producto + packaging + operaci\xF3n superan este monto, la venta queda en p\xE9rdida antes de considerar otros costos no informados."
|
|
526
|
+
},
|
|
527
|
+
missing_user_inputs: missingUserInputs,
|
|
528
|
+
guided_questions: [...feeEstimate.context.guided_questions ?? [], ...missingUserInputs],
|
|
529
|
+
assumptions: [
|
|
530
|
+
"COGS, packaging, costos operativos y costo de reposici\xF3n no los provee MercadoLibre; se toman de inputs del usuario.",
|
|
531
|
+
"Si no se informa returnReservePct o taxRatePct, se usa 0 y se avisa como menor precisi\xF3n.",
|
|
532
|
+
"La rentabilidad estimada usa listing_prices para fee futuro; la auditor\xEDa hist\xF3rica usa \xF3rdenes, shipments y Billing ML cuando est\xE1 habilitado.",
|
|
533
|
+
"El precio objetivo usa la estructura porcentual/fija del quote seleccionado; si MercadoLibre cambia el tramo de costo fijo, conviene recalcular con el precio propuesto."
|
|
534
|
+
],
|
|
535
|
+
confidence
|
|
536
|
+
})
|
|
537
|
+
);
|
|
538
|
+
} catch (err) {
|
|
539
|
+
return error(formatMercadoLibreError(err, "Failed to estimate MercadoLibre product profitability"));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
export {
|
|
543
|
+
meliEstimateProductProfitabilityHandler,
|
|
544
|
+
meliEstimateProductProfitabilitySchema
|
|
545
|
+
};
|
|
546
|
+
//# sourceMappingURL=estimate-product-profitability.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/tools/mercadolibre/estimate-product-profitability.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 { getMercadoLibreBillingDetails } from \"../../services/mercadolibre/mercadolibre-billing.js\";\nimport {\n getMercadoLibreShipmentCostsBatch,\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 normalizeScalarString,\n normalizeString,\n roundMoney,\n toNumber,\n} from \"./helpers.js\";\nimport {\n buildMercadoLibreListingFeeEstimate,\n type ListingFeeEstimateResult,\n type ListingFeeQuote,\n} from \"./listing-fee-helpers.js\";\nimport { resolveMercadoLibreProfileOrSelection } from \"./profile-resolution.js\";\nimport { mercadolibreProfileIdSchemaField } from \"./write-helpers.js\";\n\nconst DEFAULT_AUDIT_DAYS = 30;\nconst ORDERS_PAGE_LIMIT = 50;\nconst PAGE_FETCH_CONCURRENCY = 12;\nconst PAGE_FETCH_MAX_RETRIES = 2;\nconst BILLING_PAGE_LIMIT = 150;\nconst BILLING_MAX_PAGES_PER_PERIOD = 20;\n\ntype ConfidenceLevel = \"low\" | \"medium\" | \"high\";\n\nexport const meliEstimateProductProfitabilitySchema = z.object({\n profileId: mercadolibreProfileIdSchemaField,\n mode: z\n .enum([\"current_item\", \"target_margin\"])\n .describe(\"Profitability mode. current_item analyzes an existing listing; target_margin estimates the price needed for a desired margin.\"),\n itemId: z\n .string()\n .trim()\n .min(1)\n .optional()\n .describe(\"Optional MercadoLibre item id. Strongly recommended for current_item so the tool can autocomplete listing and logistics data.\"),\n query: z\n .string()\n .trim()\n .min(2)\n .optional()\n .describe(\"Optional product phrase used to suggest category candidates when categoryId is missing.\"),\n price: z\n .number()\n .positive()\n .optional()\n .describe(\"Selling price to analyze. Overrides the MercadoLibre item price when itemId is provided.\"),\n targetMarginPct: z\n .number()\n .min(0)\n .max(95)\n .optional()\n .describe(\"Desired profit margin as percentage of selling price. Required for target_margin to calculate a target price.\"),\n unitCost: z\n .number()\n .nonnegative()\n .optional()\n .describe(\"Unit product cost paid by the seller. MercadoLibre does not provide this; pass it for real profitability.\"),\n packagingCost: z\n .number()\n .nonnegative()\n .optional()\n .describe(\"Packaging cost per sold unit. MercadoLibre does not provide this; defaults to zero when omitted but is reported as missing.\"),\n operatingCost: z\n .number()\n .nonnegative()\n .optional()\n .describe(\"Additional internal operating cost per sold unit. MercadoLibre does not provide this; defaults to zero when omitted.\"),\n replacementCost: z\n .number()\n .nonnegative()\n .optional()\n .describe(\"Current replacement cost per unit. Used as cost basis if unitCost is omitted and useful for future pricing decisions.\"),\n returnReservePct: z\n .number()\n .min(0)\n .max(100)\n .optional()\n .describe(\"Reserve for returns, refunds, and post-sale friction as percentage of selling price. Defaults to zero when omitted.\"),\n taxRatePct: z\n .number()\n .min(0)\n .max(100)\n .optional()\n .describe(\"Optional tax/withholding reserve as percentage of selling price. Use when Billing ML is not enough or for simulations.\"),\n shippingCost: z\n .number()\n .nonnegative()\n .optional()\n .describe(\"Optional shipping cost per unit to use in profitability. Overrides historical shipping average when provided.\"),\n categoryId: z.string().trim().min(1).optional().describe(\"MercadoLibre category id override for fee estimation.\"),\n listingTypeId: z.string().trim().min(1).optional().describe(\"MercadoLibre listing type id override for fee estimation.\"),\n shippingMode: z.string().trim().min(1).optional().describe(\"MercadoLibre shipping mode override for fee estimation.\"),\n logisticType: z.string().trim().min(1).optional().describe(\"MercadoLibre logistic type override for fee estimation.\"),\n billableWeight: z.number().positive().optional().describe(\"Billable package weight in grams for fee estimation.\"),\n currencyId: z.string().trim().min(3).optional().describe(\"Currency id. Defaults to item currency or ARS.\"),\n tags: z.array(z.string().trim().min(1)).optional().describe(\"Optional MercadoLibre fee tags such as campaign or installment tags.\"),\n useBillingAudit: z\n .boolean()\n .optional()\n .describe(\"When true, includes MercadoLibre Billing details as historical audit. Defaults to true for current_item with itemId.\"),\n auditStartDate: z\n .string()\n .regex(mercadoLibreDateRegex)\n .optional()\n .describe(\"Historical audit start date in YYYY-MM-DD. Defaults to the last 30 days for current_item.\"),\n auditEndDate: z\n .string()\n .regex(mercadoLibreDateRegex)\n .optional()\n .describe(\"Historical audit end date in YYYY-MM-DD. Defaults to today for current_item.\"),\n});\n\nfunction isoDate(date: Date) {\n return date.toISOString().slice(0, 10);\n}\n\nfunction defaultAuditWindow() {\n const end = new Date();\n const start = new Date(end);\n start.setUTCDate(start.getUTCDate() - DEFAULT_AUDIT_DAYS);\n return {\n startDate: isoDate(start),\n endDate: isoDate(end),\n };\n}\n\nfunction parseDate(value: string) {\n return new Date(`${value}T00:00:00.000Z`);\n}\n\nfunction monthKeysBetween(startDate: string, endDate: string) {\n const start = parseDate(startDate);\n const end = parseDate(endDate);\n const keys = new Set<string>();\n const cursor = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), 1));\n const last = new Date(Date.UTC(end.getUTCFullYear(), end.getUTCMonth(), 1));\n\n while (cursor <= last) {\n keys.add(`${cursor.getUTCFullYear()}-${String(cursor.getUTCMonth() + 1).padStart(2, \"0\")}-01`);\n cursor.setUTCMonth(cursor.getUTCMonth() + 1);\n }\n\n return [...keys];\n}\n\nfunction dateInRange(dateTime: string, startDate: string, endDate: string) {\n const date = dateTime.slice(0, 10);\n return date >= startDate && date <= endDate;\n}\n\nfunction extractShipmentCost(costs: Record<string, unknown>) {\n const senders = asArray<Record<string, unknown>>(costs.senders);\n const senderCost = senders.reduce((sum, sender) => sum + toNumber(sender.cost), 0);\n return roundMoney(\n senderCost ||\n toNumber(asRecord(costs.senders).cost) ||\n toNumber(asRecord(costs.receiver).cost) ||\n toNumber(costs.gross_amount) ||\n toNumber(costs.cost)\n );\n}\n\nfunction getOrderTotal(order: Record<string, unknown>) {\n return toNumber(order.total_amount) || toNumber(order.paid_amount);\n}\n\nasync function fetchOrdersForAudit(\n profileId: string,\n params: { startDate: string; endDate: string }\n) {\n const firstPage = await searchMercadoLibreOrders(profileId, {\n seller: \"\",\n from: params.startDate,\n to: params.endDate,\n limit: ORDERS_PAGE_LIMIT,\n offset: 0,\n });\n const paging = normalizeMercadoLibrePaging(asRecord(firstPage.paging));\n const orders = [...asArray<Record<string, unknown>>(firstPage.results)];\n let pagesRequested = 1;\n let pagesSucceeded = 1;\n const failures: Array<{ offset: number; message: string; status_code?: number }> = [];\n\n const offsets: number[] = [];\n for (let offset = ORDERS_PAGE_LIMIT; offset < paging.total; offset += ORDERS_PAGE_LIMIT) {\n offsets.push(offset);\n }\n\n if (offsets.length > 0) {\n pagesRequested += offsets.length;\n const batch = await searchMercadoLibreOrdersBatch(\n profileId,\n {\n seller: \"\",\n from: params.startDate,\n to: params.endDate,\n limit: ORDERS_PAGE_LIMIT,\n },\n offsets,\n {\n maxConcurrency: PAGE_FETCH_CONCURRENCY,\n maxRetries: PAGE_FETCH_MAX_RETRIES,\n }\n );\n pagesSucceeded += batch.successful.length;\n batch.successful.forEach((entry) => orders.push(...asArray<Record<string, unknown>>(entry.document.results)));\n batch.failed.forEach((failure) =>\n failures.push({\n offset: Number(failure.id),\n message: failure.message,\n status_code: failure.statusCode,\n })\n );\n }\n\n return {\n orders,\n total: paging.total,\n pages_requested: pagesRequested,\n pages_succeeded: pagesSucceeded,\n pages_failed: failures.length,\n failures,\n };\n}\n\nasync function buildHistoricalOrderAudit(\n profileId: string,\n params: { itemId: string; startDate: string; endDate: string }\n) {\n const fetched = await fetchOrdersForAudit(profileId, params);\n const matchingOrders: Record<string, unknown>[] = [];\n const shipmentRevenueShares = new Map<string, number>();\n let units = 0;\n let revenue = 0;\n let saleFee = 0;\n let multiUnitLines = 0;\n\n for (const order of fetched.orders) {\n const orderItems = asArray<Record<string, unknown>>(order.order_items);\n let matchedRevenue = 0;\n\n for (const orderItem of orderItems) {\n const item = asRecord(orderItem.item);\n if (normalizeString(item.id) !== params.itemId) {\n continue;\n }\n\n const quantity = toNumber(orderItem.quantity);\n const unitPrice = toNumber(orderItem.unit_price);\n const lineRevenue = unitPrice * quantity;\n units += quantity;\n revenue += lineRevenue;\n saleFee += toNumber(orderItem.sale_fee);\n matchedRevenue += lineRevenue;\n if (quantity > 1) {\n multiUnitLines += 1;\n }\n }\n\n if (matchedRevenue > 0) {\n matchingOrders.push(order);\n const shipmentId = normalizeScalarString(asRecord(order.shipping).id);\n const orderTotal = getOrderTotal(order);\n if (shipmentId) {\n shipmentRevenueShares.set(\n shipmentId,\n (shipmentRevenueShares.get(shipmentId) ?? 0) + (orderTotal > 0 ? matchedRevenue / orderTotal : 1)\n );\n }\n }\n }\n\n let shippingCost = 0;\n const shipmentIds = [...shipmentRevenueShares.keys()];\n const shippingFailures: Array<{ shipment_id: string; message: string; status_code?: number }> = [];\n if (shipmentIds.length > 0) {\n const costsBatch = await getMercadoLibreShipmentCostsBatch(profileId, shipmentIds, {\n maxConcurrency: PAGE_FETCH_CONCURRENCY,\n maxRetries: PAGE_FETCH_MAX_RETRIES,\n });\n costsBatch.successful.forEach((entry) => {\n const share = Math.min(1, shipmentRevenueShares.get(entry.id) ?? 1);\n shippingCost += extractShipmentCost(entry.document) * share;\n });\n costsBatch.failed.forEach((failure) =>\n shippingFailures.push({\n shipment_id: failure.id,\n message: failure.message,\n status_code: failure.statusCode,\n })\n );\n }\n\n return stripNulls({\n window: {\n start_date: params.startDate,\n end_date: params.endDate,\n },\n coverage: {\n total_matching_orders: fetched.total,\n orders_inspected: fetched.orders.length,\n item_orders: matchingOrders.length,\n pages_requested: fetched.pages_requested,\n pages_succeeded: fetched.pages_succeeded,\n pages_failed: fetched.pages_failed,\n universe_fully_fetched: fetched.pages_failed === 0,\n },\n item_metrics: {\n orders: matchingOrders.length,\n units,\n revenue: roundMoney(revenue),\n avg_unit_price: units > 0 ? roundMoney(revenue / units) : 0,\n sale_fee_total: roundMoney(saleFee),\n avg_sale_fee_per_unit: units > 0 ? roundMoney(saleFee / units) : 0,\n sale_fee_rate_on_revenue: revenue > 0 ? Number((saleFee / revenue).toFixed(4)) : 0,\n shipping_cost_total: roundMoney(shippingCost),\n avg_shipping_cost_per_unit: units > 0 ? roundMoney(shippingCost / units) : 0,\n multi_unit_lines: multiUnitLines,\n },\n warnings:\n multiUnitLines > 0\n ? [\n \"MercadoLibre order_items.sale_fee se usa como viene de la API; si ML lo devuelve por l\u00EDnea y no por unidad, revisar multi-unit lines.\",\n ]\n : undefined,\n failures: [...fetched.failures, ...shippingFailures],\n });\n}\n\nfunction extractBillingOrderIds(row: Record<string, unknown>) {\n return asArray<Record<string, unknown>>(row.sales_info)\n .map((sale) => normalizeScalarString(sale.order_id))\n .filter(Boolean);\n}\n\nfunction extractBillingSaleDates(row: Record<string, unknown>) {\n return asArray<Record<string, unknown>>(row.sales_info)\n .map((sale) => normalizeString(sale.sale_date_time))\n .filter(Boolean);\n}\n\nfunction billingRowMatchesItem(row: Record<string, unknown>, itemId: string, startDate: string, endDate: string) {\n const itemMatches = asArray<Record<string, unknown>>(row.items_info).some(\n (item) => normalizeString(item.item_id) === itemId\n );\n const dateMatches = extractBillingSaleDates(row).some((date) => dateInRange(date, startDate, endDate));\n return itemMatches && dateMatches;\n}\n\nasync function buildBillingAudit(\n profileId: string,\n params: { itemId: string; startDate: string; endDate: string }\n) {\n const periodKeys = monthKeysBetween(params.startDate, params.endDate);\n const rowsByDetailId = new Map<string, Record<string, unknown>>();\n const periodResults = [];\n const failures = [];\n\n for (const periodKey of periodKeys) {\n const result = await getMercadoLibreBillingDetails(profileId, {\n periodKey,\n group: \"ML\",\n documentType: \"BILL\",\n limit: BILLING_PAGE_LIMIT,\n maxPages: BILLING_MAX_PAGES_PER_PERIOD,\n });\n periodResults.push({\n period_key: result.period_key,\n total_reported: result.total_reported,\n rows_fetched: result.rows.length,\n pages_requested: result.pages_requested,\n pages_succeeded: result.pages_succeeded,\n pages_failed: result.pages_failed,\n });\n failures.push(...result.failures.map((failure) => ({ period_key: periodKey, ...failure })));\n\n for (const row of result.rows) {\n if (!billingRowMatchesItem(row, params.itemId, params.startDate, params.endDate)) {\n continue;\n }\n\n const detailId = normalizeScalarString(asRecord(row.charge_info).detail_id);\n rowsByDetailId.set(detailId || `${periodKey}:${rowsByDetailId.size}`, row);\n }\n }\n\n const breakdown = new Map<string, { amount: number; rows: number; label: string }>();\n let totalCharges = 0;\n const orderIds = new Set<string>();\n\n for (const row of rowsByDetailId.values()) {\n const chargeInfo = asRecord(row.charge_info);\n const subtype = normalizeString(chargeInfo.detail_sub_type, \"UNKNOWN\");\n const label = normalizeString(chargeInfo.transaction_detail, subtype);\n const amount = toNumber(chargeInfo.detail_amount);\n const current = breakdown.get(subtype) ?? { amount: 0, rows: 0, label };\n current.amount += amount;\n current.rows += 1;\n breakdown.set(subtype, current);\n totalCharges += amount;\n extractBillingOrderIds(row).forEach((orderId) => orderIds.add(orderId));\n }\n\n return stripNulls({\n enabled: true,\n window: {\n start_date: params.startDate,\n end_date: params.endDate,\n period_keys: periodKeys,\n },\n coverage: {\n periods_requested: periodKeys.length,\n rows_matched: rowsByDetailId.size,\n orders_matched: orderIds.size,\n total_charge_amount: roundMoney(totalCharges),\n partial: failures.length > 0,\n period_results: periodResults,\n },\n charge_breakdown: Array.from(breakdown.entries())\n .sort((left, right) => Math.abs(right[1].amount) - Math.abs(left[1].amount))\n .map(([detail_sub_type, data]) => ({\n detail_sub_type,\n transaction_detail: data.label,\n rows: data.rows,\n amount: roundMoney(data.amount),\n })),\n failures,\n });\n}\n\nfunction buildMissingUserInputs(params: z.infer<typeof meliEstimateProductProfitabilitySchema>) {\n const missing = [];\n if (params.unitCost === undefined && params.replacementCost === undefined) {\n missing.push({\n field: \"unitCost\",\n question: \"\u00BFCu\u00E1nto te cuesta comprar o fabricar una unidad?\",\n impact: \"Sin costo unitario no se puede afirmar ganancia real; solo break-even.\",\n });\n }\n if (params.packagingCost === undefined) {\n missing.push({\n field: \"packagingCost\",\n question: \"\u00BFCu\u00E1nto gast\u00E1s en packaging por unidad vendida?\",\n impact: \"Mejora el margen neto unitario.\",\n });\n }\n if (params.operatingCost === undefined) {\n missing.push({\n field: \"operatingCost\",\n question: \"\u00BFTen\u00E9s alg\u00FAn costo operativo interno por unidad?\",\n impact: \"Permite pasar de margen marketplace a margen operativo.\",\n });\n }\n if (params.returnReservePct === undefined) {\n missing.push({\n field: \"returnReservePct\",\n question: \"\u00BFQu\u00E9 porcentaje quer\u00E9s reservar para devoluciones/cancelaciones?\",\n impact: \"Evita sobrestimar margen si hay fricci\u00F3n post-venta.\",\n });\n }\n if (params.mode === \"target_margin\" && params.targetMarginPct === undefined) {\n missing.push({\n field: \"targetMarginPct\",\n question: \"\u00BFQu\u00E9 margen objetivo quer\u00E9s lograr sobre el precio de venta?\",\n impact: \"Necesario para calcular precio m\u00EDnimo objetivo.\",\n });\n }\n return missing;\n}\n\nfunction chooseFeeForProfitability(feeEstimate: ListingFeeEstimateResult): ListingFeeQuote | undefined {\n return feeEstimate.selected_quote ?? feeEstimate.quotes.find((quote) => quote.listing_type_id === \"gold_special\") ?? feeEstimate.quotes[0];\n}\n\nfunction inferConfidence(params: {\n feePrecision: string;\n hasCostBasis: boolean;\n hasHistoricalAudit: boolean;\n hasBillingAudit: boolean;\n hasShipping: boolean;\n}): ConfidenceLevel {\n if (params.feePrecision === \"high\" && params.hasCostBasis && params.hasShipping && params.hasBillingAudit) {\n return \"high\";\n }\n if (params.feePrecision !== \"low\" && (params.hasCostBasis || params.hasHistoricalAudit)) {\n return \"medium\";\n }\n return \"low\";\n}\n\nfunction estimateTargetPrice(params: {\n selectedQuote?: ListingFeeQuote;\n targetMarginPct?: number;\n costBasis?: number;\n packagingCost: number;\n operatingCost: number;\n shippingCost: number;\n returnReservePct: number;\n taxRatePct: number;\n}) {\n if (!params.selectedQuote || params.targetMarginPct === undefined || params.costBasis === undefined) {\n return undefined;\n }\n\n const feePct = params.selectedQuote.sale_fee_details.percentage_fee / 100;\n const fixedFee = params.selectedQuote.sale_fee_details.fixed_fee;\n const targetPct = params.targetMarginPct / 100;\n const returnPct = params.returnReservePct / 100;\n const taxPct = params.taxRatePct / 100;\n const denominator = 1 - feePct - targetPct - returnPct - taxPct;\n const fixedCosts = fixedFee + params.costBasis + params.packagingCost + params.operatingCost + params.shippingCost;\n\n if (denominator <= 0) {\n return {\n possible: false,\n reason: \"El margen objetivo m\u00E1s fee/retenciones/reservas supera el 100% del precio.\",\n };\n }\n\n return {\n possible: true,\n estimated_price: roundMoney(fixedCosts / denominator),\n formula_basis: {\n fee_percentage: params.selectedQuote.sale_fee_details.percentage_fee,\n fixed_fee: fixedFee,\n target_margin_pct: params.targetMarginPct,\n return_reserve_pct: params.returnReservePct,\n tax_rate_pct: params.taxRatePct,\n fixed_costs: roundMoney(fixedCosts),\n },\n };\n}\n\nexport async function meliEstimateProductProfitabilityHandler(\n params: z.infer<typeof meliEstimateProductProfitabilitySchema>\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 defaultWindow = defaultAuditWindow();\n const auditStartDate = params.auditStartDate ?? defaultWindow.startDate;\n const auditEndDate = params.auditEndDate ?? defaultWindow.endDate;\n const useBillingAudit = params.useBillingAudit ?? (params.mode === \"current_item\" && Boolean(params.itemId));\n\n const feeEstimate = await buildMercadoLibreListingFeeEstimate({\n profileId,\n itemId: params.itemId,\n query: params.query,\n price: params.price,\n categoryId: params.categoryId,\n listingTypeId: params.listingTypeId,\n shippingMode: params.shippingMode,\n logisticType: params.logisticType,\n billableWeight: params.billableWeight,\n currencyId: params.currencyId,\n tags: params.tags,\n });\n const selectedQuote = chooseFeeForProfitability(feeEstimate);\n\n const orderAudit =\n params.itemId && params.mode === \"current_item\"\n ? await buildHistoricalOrderAudit(profileId, {\n itemId: params.itemId,\n startDate: auditStartDate,\n endDate: auditEndDate,\n })\n : undefined;\n\n let billingAudit: Record<string, unknown> | undefined;\n if (useBillingAudit && params.itemId) {\n try {\n billingAudit = await buildBillingAudit(profileId, {\n itemId: params.itemId,\n startDate: auditStartDate,\n endDate: auditEndDate,\n });\n } catch (billingError) {\n billingAudit = {\n enabled: true,\n failed: true,\n message: formatMercadoLibreError(billingError, \"Failed to build MercadoLibre billing audit\"),\n };\n }\n }\n\n const price = params.price ?? selectedQuote?.price ?? toNumber(feeEstimate.context.used_params.price);\n const estimatedSaleFee = selectedQuote?.sale_fee_amount ?? 0;\n const historicalMetrics = asRecord(orderAudit?.item_metrics);\n const historicalShippingPerUnit = toNumber(historicalMetrics.avg_shipping_cost_per_unit);\n const shippingCost = params.shippingCost ?? historicalShippingPerUnit;\n const costBasis = params.unitCost ?? params.replacementCost;\n const packagingCost = params.packagingCost ?? 0;\n const operatingCost = params.operatingCost ?? 0;\n const returnReservePct = params.returnReservePct ?? 0;\n const taxRatePct = params.taxRatePct ?? 0;\n const returnReserveAmount = roundMoney(price * (returnReservePct / 100));\n const taxReserveAmount = roundMoney(price * (taxRatePct / 100));\n const knownMarketplaceCosts = roundMoney(estimatedSaleFee + shippingCost + returnReserveAmount + taxReserveAmount);\n const knownUserCosts = costBasis !== undefined ? roundMoney(costBasis + packagingCost + operatingCost) : undefined;\n const profitAmount =\n knownUserCosts !== undefined\n ? roundMoney(price - knownMarketplaceCosts - knownUserCosts)\n : undefined;\n const marginPct =\n profitAmount !== undefined && price > 0 ? Number(((profitAmount / price) * 100).toFixed(2)) : undefined;\n const breakEvenBeforeUserCosts = roundMoney(price - knownMarketplaceCosts);\n const missingUserInputs = buildMissingUserInputs(params);\n const targetPrice = estimateTargetPrice({\n selectedQuote,\n targetMarginPct: params.targetMarginPct,\n costBasis,\n packagingCost,\n operatingCost,\n shippingCost,\n returnReservePct,\n taxRatePct,\n });\n const confidence = inferConfidence({\n feePrecision: feeEstimate.context.precision,\n hasCostBasis: costBasis !== undefined,\n hasHistoricalAudit: Boolean(orderAudit && toNumber(historicalMetrics.units) > 0),\n hasBillingAudit: Boolean(billingAudit && !billingAudit.failed),\n hasShipping: shippingCost > 0,\n });\n\n return object(\n stripNulls({\n profile_id: profileId,\n mode: params.mode,\n known_from_mercadolibre: {\n item_id: params.itemId,\n title: feeEstimate.context.title,\n seller_sku: feeEstimate.context.seller_sku,\n price,\n category_id: feeEstimate.context.used_params.category_id,\n listing_type_id: feeEstimate.context.used_params.listing_type_id,\n shipping_mode: feeEstimate.context.used_params.shipping_mode,\n logistic_type: feeEstimate.context.used_params.logistic_type,\n billable_weight: feeEstimate.context.used_params.billable_weight,\n currency_id: feeEstimate.context.used_params.currency_id,\n },\n estimated_fee: {\n precision: feeEstimate.context.precision,\n selected_quote: selectedQuote,\n all_quotes: feeEstimate.quotes,\n missing_params: feeEstimate.context.missing_params,\n },\n historical_audit: stripNulls({\n orders: orderAudit,\n billing: billingAudit,\n }),\n profitability: {\n status:\n profitAmount === undefined\n ? \"needs_user_costs\"\n : profitAmount >= 0\n ? \"profitable\"\n : \"not_profitable\",\n price,\n estimated_sale_fee: estimatedSaleFee,\n shipping_cost_used: roundMoney(shippingCost),\n return_reserve_amount: returnReserveAmount,\n tax_reserve_amount: taxReserveAmount,\n known_marketplace_costs: knownMarketplaceCosts,\n known_user_costs: knownUserCosts,\n profit_amount: profitAmount,\n margin_pct: marginPct,\n target_price: targetPrice,\n },\n break_even: {\n max_total_user_cost_before_losing_money: breakEvenBeforeUserCosts,\n explanation:\n \"Si tus costos de producto + packaging + operaci\u00F3n superan este monto, la venta queda en p\u00E9rdida antes de considerar otros costos no informados.\",\n },\n missing_user_inputs: missingUserInputs,\n guided_questions: [...(feeEstimate.context.guided_questions ?? []), ...missingUserInputs],\n assumptions: [\n \"COGS, packaging, costos operativos y costo de reposici\u00F3n no los provee MercadoLibre; se toman de inputs del usuario.\",\n \"Si no se informa returnReservePct o taxRatePct, se usa 0 y se avisa como menor precisi\u00F3n.\",\n \"La rentabilidad estimada usa listing_prices para fee futuro; la auditor\u00EDa hist\u00F3rica usa \u00F3rdenes, shipments y Billing ML cuando est\u00E1 habilitado.\",\n \"El precio objetivo usa la estructura porcentual/fija del quote seleccionado; si MercadoLibre cambia el tramo de costo fijo, conviene recalcular con el precio propuesto.\",\n ],\n confidence,\n })\n );\n } catch (err) {\n return error(formatMercadoLibreError(err, \"Failed to estimate MercadoLibre product profitability\"));\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|