@yoryoboy/bi-mcp 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +87 -87
  2. package/bin/bi-mcp.js +9 -9
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/mcp-use.json +2 -2
  7. package/dist/public/icon.svg +6 -6
  8. package/dist/scripts/_helpers.js.map +1 -1
  9. package/dist/scripts/admin-profile-delete.js.map +1 -1
  10. package/dist/scripts/admin-profile-list.js.map +1 -1
  11. package/dist/scripts/admin-profile-upsert.js.map +1 -1
  12. package/dist/scripts/admin-vtex-list.js.map +1 -1
  13. package/dist/scripts/admin-vtex-upsert.js.map +1 -1
  14. package/dist/scripts/admin-vtex-validate.js.map +1 -1
  15. package/dist/scripts/decrypt-secret.js +36 -0
  16. package/dist/scripts/decrypt-secret.js.map +7 -0
  17. package/dist/scripts/run-migrations.js.map +1 -1
  18. package/dist/scripts/test-db-connection.js.map +1 -1
  19. package/dist/src/analytics/ga4-channel-groups.js.map +1 -1
  20. package/dist/src/analytics/ga4-report-utils.js.map +1 -1
  21. package/dist/src/config/benchmarks.js.map +1 -1
  22. package/dist/src/config/google-store.js.map +1 -1
  23. package/dist/src/config/google.js.map +1 -1
  24. package/dist/src/config/mercadolibre-profile-store.js.map +1 -1
  25. package/dist/src/config/mercadolibre.js.map +1 -1
  26. package/dist/src/config/meta.js.map +1 -1
  27. package/dist/src/config/profile-store.js.map +1 -1
  28. package/dist/src/config/vtex-crypto.js.map +1 -1
  29. package/dist/src/config/vtex-profile-store.js.map +1 -1
  30. package/dist/src/config/vtex.js.map +1 -1
  31. package/dist/src/db/client.js.map +1 -1
  32. package/dist/src/meta/meta-utils.js.map +1 -1
  33. package/dist/src/prompts/reporte-ventas.js.map +1 -1
  34. package/dist/src/services/analytics/ga4-client.js.map +1 -1
  35. package/dist/src/services/analytics/oauth.js.map +1 -1
  36. package/dist/src/services/google-ads/google-ads-client.js.map +1 -1
  37. package/dist/src/services/mercadolibre/mercadolibre-api.js.map +1 -1
  38. package/dist/src/services/mercadolibre/mercadolibre-items.js.map +1 -1
  39. package/dist/src/services/mercadolibre/mercadolibre-orders.js +19 -5
  40. package/dist/src/services/mercadolibre/mercadolibre-orders.js.map +2 -2
  41. package/dist/src/services/mercadolibre/mercadolibre-questions.js.map +1 -1
  42. package/dist/src/services/meta/meta-ads.js.map +1 -1
  43. package/dist/src/services/meta/meta-api.js.map +1 -1
  44. package/dist/src/services/search-console/search-console-client.js.map +1 -1
  45. package/dist/src/services/search-console/search-console-utils.js.map +1 -1
  46. package/dist/src/services/vtex/vtex-api.js.map +1 -1
  47. package/dist/src/services/vtex/vtex-catalog-write.js.map +1 -1
  48. package/dist/src/services/vtex/vtex-catalog.js.map +1 -1
  49. package/dist/src/services/vtex/vtex-logistics.js.map +1 -1
  50. package/dist/src/services/vtex/vtex-orders-write.js.map +1 -1
  51. package/dist/src/services/vtex/vtex-orders.js.map +1 -1
  52. package/dist/src/services/vtex/vtex-pricing-write.js.map +1 -1
  53. package/dist/src/services/vtex/vtex-pricing.js.map +1 -1
  54. package/dist/src/services/vtex/vtex-write.js.map +1 -1
  55. package/dist/src/tools/analytics/attribution-gaps.js.map +1 -1
  56. package/dist/src/tools/analytics/channel-mix.js.map +1 -1
  57. package/dist/src/tools/analytics/ecommerce-tracking-health.js.map +1 -1
  58. package/dist/src/tools/analytics/engagement-overview.js.map +1 -1
  59. package/dist/src/tools/analytics/index.js.map +1 -1
  60. package/dist/src/tools/analytics/list-accessible-properties.js.map +1 -1
  61. package/dist/src/tools/analytics/property-info.js.map +1 -1
  62. package/dist/src/tools/analytics/revenue-by-channel.js.map +1 -1
  63. package/dist/src/tools/analytics/revenue-overview.js.map +1 -1
  64. package/dist/src/tools/analytics/revenue-trend.js.map +1 -1
  65. package/dist/src/tools/analytics/source-medium-breakdown.js.map +1 -1
  66. package/dist/src/tools/analytics/top-landing-pages.js.map +1 -1
  67. package/dist/src/tools/config/check-database-connection.js.map +1 -1
  68. package/dist/src/tools/config/index.js.map +1 -1
  69. package/dist/src/tools/config/list-profiles.js.map +1 -1
  70. package/dist/src/tools/google-ads/account-overview.js.map +1 -1
  71. package/dist/src/tools/google-ads/account-risks.js.map +1 -1
  72. package/dist/src/tools/google-ads/break-even-analysis.js.map +1 -1
  73. package/dist/src/tools/google-ads/campaign-performance.js.map +1 -1
  74. package/dist/src/tools/google-ads/channel-mix.js.map +1 -1
  75. package/dist/src/tools/google-ads/compare-accounts.js.map +1 -1
  76. package/dist/src/tools/google-ads/customer-clients.js.map +1 -1
  77. package/dist/src/tools/google-ads/customer-info.js.map +1 -1
  78. package/dist/src/tools/google-ads/index.js.map +1 -1
  79. package/dist/src/tools/google-ads/scaling-health.js.map +1 -1
  80. package/dist/src/tools/google-ads/search-terms-summary.js.map +1 -1
  81. package/dist/src/tools/google-ads/time-series.js.map +1 -1
  82. package/dist/src/tools/index.js.map +1 -1
  83. package/dist/src/tools/mercadolibre/answer-question.js.map +1 -1
  84. package/dist/src/tools/mercadolibre/create-item.js.map +1 -1
  85. package/dist/src/tools/mercadolibre/estimate-listing-fee.js.map +1 -1
  86. package/dist/src/tools/mercadolibre/get-account-context.js.map +1 -1
  87. package/dist/src/tools/mercadolibre/get-category-requirements.js.map +1 -1
  88. package/dist/src/tools/mercadolibre/get-item-details.js.map +1 -1
  89. package/dist/src/tools/mercadolibre/get-item-visits.js.map +1 -1
  90. package/dist/src/tools/mercadolibre/get-listing-quality.js.map +1 -1
  91. package/dist/src/tools/mercadolibre/get-order-details.js.map +1 -1
  92. package/dist/src/tools/mercadolibre/get-orders-summary.js +670 -38
  93. package/dist/src/tools/mercadolibre/get-orders-summary.js.map +2 -2
  94. package/dist/src/tools/mercadolibre/get-sales-by-item.js.map +1 -1
  95. package/dist/src/tools/mercadolibre/get-sales-trend.js.map +1 -1
  96. package/dist/src/tools/mercadolibre/get-shipping-summary.js.map +1 -1
  97. package/dist/src/tools/mercadolibre/get-store-performance.js.map +1 -1
  98. package/dist/src/tools/mercadolibre/helpers.js +13 -0
  99. package/dist/src/tools/mercadolibre/helpers.js.map +2 -2
  100. package/dist/src/tools/mercadolibre/index.js.map +1 -1
  101. package/dist/src/tools/mercadolibre/pause-or-reactivate-item.js.map +1 -1
  102. package/dist/src/tools/mercadolibre/predict-category.js.map +1 -1
  103. package/dist/src/tools/mercadolibre/profile-resolution.js.map +1 -1
  104. package/dist/src/tools/mercadolibre/search-items.js.map +1 -1
  105. package/dist/src/tools/mercadolibre/search-questions.js.map +1 -1
  106. package/dist/src/tools/mercadolibre/update-item-basic-fields.js.map +1 -1
  107. package/dist/src/tools/mercadolibre/update-item-description.js.map +1 -1
  108. package/dist/src/tools/mercadolibre/update-item-pictures.js.map +1 -1
  109. package/dist/src/tools/mercadolibre/validate-connection.js.map +1 -1
  110. package/dist/src/tools/mercadolibre/write-helpers.js.map +1 -1
  111. package/dist/src/tools/meta/account-overview.js.map +1 -1
  112. package/dist/src/tools/meta/ad-account-info.js.map +1 -1
  113. package/dist/src/tools/meta/ads-performance.js.map +1 -1
  114. package/dist/src/tools/meta/campaign-performance.js.map +1 -1
  115. package/dist/src/tools/meta/index.js.map +1 -1
  116. package/dist/src/tools/meta/list-accessible-ad-accounts.js.map +1 -1
  117. package/dist/src/tools/meta/list-accessible-businesses.js.map +1 -1
  118. package/dist/src/tools/meta/placement-mix.js.map +1 -1
  119. package/dist/src/tools/meta/time-series.js.map +1 -1
  120. package/dist/src/tools/search-console/country-breakdown.js.map +1 -1
  121. package/dist/src/tools/search-console/device-breakdown.js.map +1 -1
  122. package/dist/src/tools/search-console/high-impression-low-click-queries.js.map +1 -1
  123. package/dist/src/tools/search-console/index.js.map +1 -1
  124. package/dist/src/tools/search-console/list-accessible-sites.js.map +1 -1
  125. package/dist/src/tools/search-console/low-ctr-opportunities.js.map +1 -1
  126. package/dist/src/tools/search-console/page-performance.js.map +1 -1
  127. package/dist/src/tools/search-console/product-demand-low-capture-queries.js.map +1 -1
  128. package/dist/src/tools/search-console/query-page-matrix.js.map +1 -1
  129. package/dist/src/tools/search-console/query-performance.js.map +1 -1
  130. package/dist/src/tools/search-console/quick-win-opportunities.js.map +1 -1
  131. package/dist/src/tools/search-console/rising-non-brand-queries.js.map +1 -1
  132. package/dist/src/tools/search-console/search-performance.js.map +1 -1
  133. package/dist/src/tools/search-console/site-context.js.map +1 -1
  134. package/dist/src/tools/search-console/visibility-declines.js.map +1 -1
  135. package/dist/src/tools/vtex/activate-sku.js.map +1 -1
  136. package/dist/src/tools/vtex/add-order-tracking.js.map +1 -1
  137. package/dist/src/tools/vtex/associate-specification.js.map +1 -1
  138. package/dist/src/tools/vtex/attach-catalog-image.js.map +1 -1
  139. package/dist/src/tools/vtex/cancel-order.js.map +1 -1
  140. package/dist/src/tools/vtex/computed-price.js.map +1 -1
  141. package/dist/src/tools/vtex/create-brand.js.map +1 -1
  142. package/dist/src/tools/vtex/create-category.js.map +1 -1
  143. package/dist/src/tools/vtex/create-product-with-sku.js.map +1 -1
  144. package/dist/src/tools/vtex/create-product.js.map +1 -1
  145. package/dist/src/tools/vtex/create-sku.js.map +1 -1
  146. package/dist/src/tools/vtex/create-specification-value.js.map +1 -1
  147. package/dist/src/tools/vtex/create-specification.js.map +1 -1
  148. package/dist/src/tools/vtex/deactivate-sku.js.map +1 -1
  149. package/dist/src/tools/vtex/delete-fixed-price.js.map +1 -1
  150. package/dist/src/tools/vtex/index.js.map +1 -1
  151. package/dist/src/tools/vtex/inventory-check.js.map +1 -1
  152. package/dist/src/tools/vtex/invoice-order.js.map +1 -1
  153. package/dist/src/tools/vtex/order-details.js.map +1 -1
  154. package/dist/src/tools/vtex/orders-summary.js.map +1 -1
  155. package/dist/src/tools/vtex/product-offers.js.map +1 -1
  156. package/dist/src/tools/vtex/profile-resolution.js.map +1 -1
  157. package/dist/src/tools/vtex/sku-offers.js.map +1 -1
  158. package/dist/src/tools/vtex/sku-price.js.map +1 -1
  159. package/dist/src/tools/vtex/toggle-unlimited-quantity.js.map +1 -1
  160. package/dist/src/tools/vtex/update-inventory.js.map +1 -1
  161. package/dist/src/tools/vtex/update-lead-time.js.map +1 -1
  162. package/dist/src/tools/vtex/update-product-basic-fields.js.map +1 -1
  163. package/dist/src/tools/vtex/update-sku-basic-fields.js.map +1 -1
  164. package/dist/src/tools/vtex/update-sku-price.js.map +1 -1
  165. package/dist/src/tools/vtex/upsert-fixed-price.js.map +1 -1
  166. package/dist/src/tools/vtex/warehouse-inventory.js.map +1 -1
  167. package/dist/src/tools/vtex/write-helpers.js.map +1 -1
  168. package/dist/src/utils/case-conversion.js.map +1 -1
  169. package/dist/src/utils/currency.js.map +1 -1
  170. package/dist/src/utils/format-order-details.js.map +1 -1
  171. package/dist/src/utils/google-ads.js.map +1 -1
  172. package/dist/src/utils/money.js.map +1 -1
  173. package/dist/src/utils/order-status.js.map +1 -1
  174. package/dist/src/utils/pagination.js.map +1 -1
  175. package/dist/src/utils/strip-payload.js.map +1 -1
  176. package/dist/src/utils/type-guards.js.map +1 -1
  177. package/package.json +4 -3
  178. package/public/icon.svg +6 -6
  179. package/dist/src/google-ads/report-utils.js +0 -78
  180. package/dist/src/google-ads/report-utils.js.map +0 -7
  181. package/dist/src/search-console/search-console-utils.js +0 -275
  182. package/dist/src/search-console/search-console-utils.js.map +0 -7
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/analytics/ga4-report-utils.ts"],
4
- "sourcesContent": ["import { getDefaultGa4PropertyId } from '../config/google.js';\nimport { getProfileGoogleServiceMapping } from '../config/google-store.js';\nimport type { Ga4AccountSummary, Ga4MetricHeader, Ga4MetricValue, Ga4ReportResponse, Ga4ReportRow } from '../services/analytics/ga4-client.js';\n\nexport async function resolveGa4PropertyId(propertyId?: string, profileId?: string): Promise<string> {\n const explicitPropertyId = propertyId?.trim();\n if (explicitPropertyId) return explicitPropertyId;\n\n const explicitProfileId = profileId?.trim();\n if (explicitProfileId) {\n const mapping = await getProfileGoogleServiceMapping(explicitProfileId);\n if (mapping?.ga4PropertyId) return mapping.ga4PropertyId;\n }\n\n const defaultPropertyId = getDefaultGa4PropertyId();\n if (defaultPropertyId) return defaultPropertyId;\n\n throw new Error(\n explicitProfileId\n ? `Missing GA4 property ID. profileId \"${explicitProfileId}\" has no active GA4 mapping, and GA4_PROPERTY_ID is not configured.`\n : 'Missing GA4 property ID. Provide propertyId, pass profileId with a configured mapping, or configure GA4_PROPERTY_ID.'\n );\n}\n\nexport function parseMetricValue(value: string | undefined): number { if (!value) return 0; const parsedValue = Number(value); return Number.isFinite(parsedValue) ? parsedValue : 0; }\nexport function getMetricValueByName(row: Ga4ReportRow, metricHeaders: Ga4MetricHeader[] | undefined, metricName: string): number { const metricIndex = metricHeaders?.findIndex((metric) => metric.name === metricName) ?? -1; if (metricIndex < 0) return 0; return parseMetricValue(row.metricValues?.[metricIndex]?.value); }\nexport function getDimensionValue(row: Ga4ReportRow, index = 0): string { return row.dimensionValues?.[index]?.value ?? ''; }\nexport function toPercent(numerator: number, denominator: number): number { if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) return 0; return (numerator / denominator) * 100; }\nexport function round(value: number, decimals = 2): number { if (!Number.isFinite(value)) return 0; const factor = 10 ** decimals; return Math.round(value * factor) / factor; }\nexport function formatGa4Date(value: string): string { if (!/^\\d{8}$/.test(value)) return value; return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`; }\nexport function findPropertySummary(accountSummaries: Ga4AccountSummary[], propertyId: string) { for (const account of accountSummaries) { for (const property of account.propertySummaries ?? []) { if (property.property === `properties/${propertyId}`) { return { accountId: account.account.replace('accounts/', ''), accountName: account.displayName, propertyId, propertyName: property.displayName, propertyType: property.propertyType }; } } } return undefined; }\nexport function detectSeverity(sharePercent: number): 'low' | 'medium' | 'high' { if (sharePercent >= 10) return 'high'; if (sharePercent >= 3) return 'medium'; return 'low'; }\nexport function isProblematicAttributionValue(value: string): boolean { const normalizedValue = value.toLowerCase(); return normalizedValue.includes('(not set)') || normalizedValue.includes('(data not available)') || normalizedValue.includes('unassigned'); }\nexport function sumMetricValues(rows: Ga4ReportRow[] | undefined, metricHeaders: Ga4MetricHeader[] | undefined, metricName: string): number { return (rows ?? []).reduce((total, row) => total + getMetricValueByName(row, metricHeaders, metricName), 0); }\nexport function extractQuotaSnapshot(report: Ga4ReportResponse): Record<string, number> | undefined { const quota = report.propertyQuota; if (!quota) return undefined; return { tokens_per_day_remaining: quota.tokensPerDay?.remaining ?? 0, tokens_per_hour_remaining: quota.tokensPerHour?.remaining ?? 0, concurrent_requests_remaining: quota.concurrentRequests?.remaining ?? 0, tokens_per_project_per_hour_remaining: quota.tokensPerProjectPerHour?.remaining ?? 0 }; }\nexport function findMetricHeader(metricHeaders: Ga4MetricHeader[] | undefined, metricName: string): Ga4MetricHeader | undefined { return metricHeaders?.find((metric) => metric.name === metricName); }\nexport function getFirstMetricValue(metricValues: Ga4MetricValue[] | undefined, index: number): number { return parseMetricValue(metricValues?.[index]?.value); }\n"],
4
+ "sourcesContent": ["import { getDefaultGa4PropertyId } from '../config/google.js';\r\nimport { getProfileGoogleServiceMapping } from '../config/google-store.js';\r\nimport type { Ga4AccountSummary, Ga4MetricHeader, Ga4MetricValue, Ga4ReportResponse, Ga4ReportRow } from '../services/analytics/ga4-client.js';\r\n\r\nexport async function resolveGa4PropertyId(propertyId?: string, profileId?: string): Promise<string> {\r\n const explicitPropertyId = propertyId?.trim();\r\n if (explicitPropertyId) return explicitPropertyId;\r\n\r\n const explicitProfileId = profileId?.trim();\r\n if (explicitProfileId) {\r\n const mapping = await getProfileGoogleServiceMapping(explicitProfileId);\r\n if (mapping?.ga4PropertyId) return mapping.ga4PropertyId;\r\n }\r\n\r\n const defaultPropertyId = getDefaultGa4PropertyId();\r\n if (defaultPropertyId) return defaultPropertyId;\r\n\r\n throw new Error(\r\n explicitProfileId\r\n ? `Missing GA4 property ID. profileId \"${explicitProfileId}\" has no active GA4 mapping, and GA4_PROPERTY_ID is not configured.`\r\n : 'Missing GA4 property ID. Provide propertyId, pass profileId with a configured mapping, or configure GA4_PROPERTY_ID.'\r\n );\r\n}\r\n\r\nexport function parseMetricValue(value: string | undefined): number { if (!value) return 0; const parsedValue = Number(value); return Number.isFinite(parsedValue) ? parsedValue : 0; }\r\nexport function getMetricValueByName(row: Ga4ReportRow, metricHeaders: Ga4MetricHeader[] | undefined, metricName: string): number { const metricIndex = metricHeaders?.findIndex((metric) => metric.name === metricName) ?? -1; if (metricIndex < 0) return 0; return parseMetricValue(row.metricValues?.[metricIndex]?.value); }\r\nexport function getDimensionValue(row: Ga4ReportRow, index = 0): string { return row.dimensionValues?.[index]?.value ?? ''; }\r\nexport function toPercent(numerator: number, denominator: number): number { if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) return 0; return (numerator / denominator) * 100; }\r\nexport function round(value: number, decimals = 2): number { if (!Number.isFinite(value)) return 0; const factor = 10 ** decimals; return Math.round(value * factor) / factor; }\r\nexport function formatGa4Date(value: string): string { if (!/^\\d{8}$/.test(value)) return value; return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`; }\r\nexport function findPropertySummary(accountSummaries: Ga4AccountSummary[], propertyId: string) { for (const account of accountSummaries) { for (const property of account.propertySummaries ?? []) { if (property.property === `properties/${propertyId}`) { return { accountId: account.account.replace('accounts/', ''), accountName: account.displayName, propertyId, propertyName: property.displayName, propertyType: property.propertyType }; } } } return undefined; }\r\nexport function detectSeverity(sharePercent: number): 'low' | 'medium' | 'high' { if (sharePercent >= 10) return 'high'; if (sharePercent >= 3) return 'medium'; return 'low'; }\r\nexport function isProblematicAttributionValue(value: string): boolean { const normalizedValue = value.toLowerCase(); return normalizedValue.includes('(not set)') || normalizedValue.includes('(data not available)') || normalizedValue.includes('unassigned'); }\r\nexport function sumMetricValues(rows: Ga4ReportRow[] | undefined, metricHeaders: Ga4MetricHeader[] | undefined, metricName: string): number { return (rows ?? []).reduce((total, row) => total + getMetricValueByName(row, metricHeaders, metricName), 0); }\r\nexport function extractQuotaSnapshot(report: Ga4ReportResponse): Record<string, number> | undefined { const quota = report.propertyQuota; if (!quota) return undefined; return { tokens_per_day_remaining: quota.tokensPerDay?.remaining ?? 0, tokens_per_hour_remaining: quota.tokensPerHour?.remaining ?? 0, concurrent_requests_remaining: quota.concurrentRequests?.remaining ?? 0, tokens_per_project_per_hour_remaining: quota.tokensPerProjectPerHour?.remaining ?? 0 }; }\r\nexport function findMetricHeader(metricHeaders: Ga4MetricHeader[] | undefined, metricName: string): Ga4MetricHeader | undefined { return metricHeaders?.find((metric) => metric.name === metricName); }\r\nexport function getFirstMetricValue(metricValues: Ga4MetricValue[] | undefined, index: number): number { return parseMetricValue(metricValues?.[index]?.value); }\r\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/benchmarks.ts"],
4
- "sourcesContent": ["import { z } from \"zod\";\nimport { object } from \"mcp-use/server\";\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// BENCHMARKS DE ARGENTINA\n// Fuente: Ecommerce Growth & Profit OS \u2013 Argentina Edition (V3.5)\n// Secci\u00F3n: \uD83C\uDDE6\uD83C\uDDF7 CONTEXTO ARGENTINA\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// IIBB por provincia\n// Porcentajes expresados como n\u00FAmeros (ej: 3.0 = 3.0%)\nexport const IIBB_BY_PROVINCE: Record<\n string,\n { general: number; simplificado: number; retencionesBancarias: number }\n> = {\n CABA: { general: 3.0, simplificado: 1.5, retencionesBancarias: 2.5 },\n \"Buenos Aires\": { general: 3.5, simplificado: 2.0, retencionesBancarias: 3.0 },\n C\u00F3rdoba: { general: 4.0, simplificado: 2.5, retencionesBancarias: 2.0 },\n \"Santa Fe\": { general: 3.5, simplificado: 2.0, retencionesBancarias: 2.5 },\n Mendoza: { general: 4.0, simplificado: 2.5, retencionesBancarias: 2.0 },\n \"Interior (promedio)\": { general: 3.5, simplificado: 2.0, retencionesBancarias: 2.0 },\n};\n\n// Costos de financiaci\u00F3n en cuotas sin inter\u00E9s\n// Porcentajes expresados como n\u00FAmeros (ej: 7.0 = 7.0%)\n// Rangos representados como valor medio de referencia\nexport const INSTALLMENT_COSTS: Record<\n string,\n { ahora12: number; propio: number; planZ: number }\n> = {\n \"3\": { ahora12: 7.0, propio: 8.0, planZ: 6.0 }, // 3 MSI\n \"6\": { ahora12: 11.5, propio: 13.5, planZ: 10.0 }, // 6 MSI\n \"12\": { ahora12: 18.0, propio: 22.5, planZ: 16.5 }, // 12 MSI\n \"18\": { ahora12: 25.0, propio: null as unknown as number, planZ: 22.0 }, // 18 MSI (Propio: N/A)\n};\n\n// Fees de pasarelas de pago\n// fee: porcentaje de transacci\u00F3n (valor medio del rango, sin IVA)\n// ivaMultiplier: multiplicador para aplicar IVA (1.21 = 21% IVA)\n// liquidacionDias: rango de d\u00EDas de liquidaci\u00F3n\nexport const PAYMENT_GATEWAY_FEES: Record<\n string,\n { feeMin: number; feeMax: number; feeMid: number; ivaMultiplier: number; liquidacionDiasMin: number; liquidacionDiasMax: number }\n> = {\n mercado_pago: {\n feeMin: 4.5,\n feeMax: 5.5,\n feeMid: 5.0,\n ivaMultiplier: 1.21,\n liquidacionDiasMin: 14,\n liquidacionDiasMax: 21,\n },\n mobbex: {\n feeMin: 2.8,\n feeMax: 3.5,\n feeMid: 3.15,\n ivaMultiplier: 1.21,\n liquidacionDiasMin: 7,\n liquidacionDiasMax: 14,\n },\n payway: {\n feeMin: 2.5,\n feeMax: 3.2,\n feeMid: 2.85,\n ivaMultiplier: 1.21,\n liquidacionDiasMin: 7,\n liquidacionDiasMax: 10,\n },\n dlocal_go: {\n feeMin: 3.0,\n feeMax: 4.0,\n feeMid: 3.5,\n ivaMultiplier: 1.21,\n liquidacionDiasMin: 10,\n liquidacionDiasMax: 14,\n },\n};\n\n// Costos log\u00EDsticos de referencia (ARS, rangos)\n// Valores al momento de publicaci\u00F3n del prompt de Juli\u00E1n \u2014 actualizar seg\u00FAn inflaci\u00F3n\nexport const LOGISTICS_COSTS: Record<\n string,\n { estandarMin: number; estandarMax: number; expressMin: number; expressMax: number; pesoLimiteKg: number }\n> = {\n CABA: { estandarMin: 4000, estandarMax: 6000, expressMin: 6000, expressMax: 9000, pesoLimiteKg: 5 },\n GBA: { estandarMin: 5000, estandarMax: 8000, expressMin: 8000, expressMax: 12000, pesoLimiteKg: 5 },\n \"Interior Cercano\": { estandarMin: 7000, estandarMax: 12000, expressMin: 15000, expressMax: 20000, pesoLimiteKg: 5 },\n \"Interior Lejano\": { estandarMin: 12000, estandarMax: 20000, expressMin: 25000, expressMax: 35000, pesoLimiteKg: 5 },\n Patagonia: { estandarMin: 18000, estandarMax: 30000, expressMin: 35000, expressMax: 50000, pesoLimiteKg: 5 },\n};\n\n// Calendario comercial Argentina \u2014 impacto revenue respecto a d\u00EDas normales\nexport const COMMERCIAL_CALENDAR: Array<{\n evento: string;\n mes: number; // 1-12\n impactoRevenueMin: number; // porcentaje adicional (200 = +200%)\n impactoRevenueMax: number;\n preparacionDias: number;\n esTemporadaBaja: boolean;\n}> = [\n { evento: \"Hot Sale\", mes: 5, impactoRevenueMin: 200, impactoRevenueMax: 500, preparacionDias: 60, esTemporadaBaja: false },\n { evento: \"CyberMonday\", mes: 11, impactoRevenueMin: 200, impactoRevenueMax: 400, preparacionDias: 45, esTemporadaBaja: false },\n { evento: \"D\u00EDa de la Madre\", mes: 10, impactoRevenueMin: 100, impactoRevenueMax: 200, preparacionDias: 30, esTemporadaBaja: false },\n { evento: \"Navidad\", mes: 12, impactoRevenueMin: 150, impactoRevenueMax: 300, preparacionDias: 45, esTemporadaBaja: false },\n { evento: \"D\u00EDa del Padre\", mes: 6, impactoRevenueMin: 50, impactoRevenueMax: 100, preparacionDias: 20, esTemporadaBaja: false },\n { evento: \"D\u00EDa del Ni\u00F1o\", mes: 8, impactoRevenueMin: 50, impactoRevenueMax: 150, preparacionDias: 20, esTemporadaBaja: false },\n { evento: \"Temporada Baja\", mes: 1, impactoRevenueMin: -50, impactoRevenueMax: -30, preparacionDias: 0, esTemporadaBaja: true },\n { evento: \"Temporada Baja\", mes: 2, impactoRevenueMin: -50, impactoRevenueMax: -30, preparacionDias: 0, esTemporadaBaja: true },\n];\n\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// TOOL: config_get_benchmarks\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const getBenchmarksSchema = z.object({\n type: z\n .enum([\"iibb\", \"installments\", \"payment_gateways\", \"logistics\", \"calendar\", \"all\"])\n .describe(\n \"Tipo de benchmark a consultar. \" +\n \"'iibb' = al\u00EDcuotas de Ingresos Brutos por provincia. \" +\n \"'installments' = costo de cuotas sin inter\u00E9s (Ahora 12, Plan Z, etc.). \" +\n \"'payment_gateways' = fees de pasarelas de pago (Mercado Pago, Mobbex, etc.). \" +\n \"'logistics' = costos de env\u00EDo de referencia por zona. \" +\n \"'calendar' = calendario comercial Argentina con impacto de revenue. \" +\n \"'all' = devuelve todos los benchmarks.\"\n ),\n});\n\nexport async function getBenchmarksHandler(\n params: z.infer<typeof getBenchmarksSchema>\n) {\n switch (params.type) {\n case \"iibb\":\n return object({\n type: \"iibb\",\n description: \"Al\u00EDcuotas de Ingresos Brutos por provincia (porcentajes sobre venta neta)\",\n data: IIBB_BY_PROVINCE,\n });\n\n case \"installments\":\n return object({\n type: \"installments\",\n description: \"Costo financiero de cuotas sin inter\u00E9s. Valores son porcentajes sobre el monto de venta. 'propio' para 18 cuotas es N/A.\",\n data: INSTALLMENT_COSTS,\n });\n\n case \"payment_gateways\":\n return object({\n type: \"payment_gateways\",\n description: \"Fees de pasarelas de pago. Los fees NO incluyen IVA \u2014 multiplicar por ivaMultiplier (1.21) para el costo real.\",\n data: PAYMENT_GATEWAY_FEES,\n });\n\n case \"logistics\":\n return object({\n type: \"logistics\",\n description: \"Costos log\u00EDsticos de referencia en ARS. Actualizar seg\u00FAn inflaci\u00F3n vigente.\",\n data: LOGISTICS_COSTS,\n });\n\n case \"calendar\":\n return object({\n type: \"calendar\",\n description: \"Calendario comercial Argentina. impactoRevenue es porcentaje adicional sobre d\u00EDas normales.\",\n data: COMMERCIAL_CALENDAR,\n });\n\n case \"all\":\n return object({\n iibb: IIBB_BY_PROVINCE,\n installments: INSTALLMENT_COSTS,\n paymentGateways: PAYMENT_GATEWAY_FEES,\n logistics: LOGISTICS_COSTS,\n calendar: COMMERCIAL_CALENDAR,\n });\n }\n}\n"],
4
+ "sourcesContent": ["import { z } from \"zod\";\r\nimport { object } from \"mcp-use/server\";\r\n\r\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n// BENCHMARKS DE ARGENTINA\r\n// Fuente: Ecommerce Growth & Profit OS \u2013 Argentina Edition (V3.5)\r\n// Secci\u00F3n: \uD83C\uDDE6\uD83C\uDDF7 CONTEXTO ARGENTINA\r\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n// IIBB por provincia\r\n// Porcentajes expresados como n\u00FAmeros (ej: 3.0 = 3.0%)\r\nexport const IIBB_BY_PROVINCE: Record<\r\n string,\r\n { general: number; simplificado: number; retencionesBancarias: number }\r\n> = {\r\n CABA: { general: 3.0, simplificado: 1.5, retencionesBancarias: 2.5 },\r\n \"Buenos Aires\": { general: 3.5, simplificado: 2.0, retencionesBancarias: 3.0 },\r\n C\u00F3rdoba: { general: 4.0, simplificado: 2.5, retencionesBancarias: 2.0 },\r\n \"Santa Fe\": { general: 3.5, simplificado: 2.0, retencionesBancarias: 2.5 },\r\n Mendoza: { general: 4.0, simplificado: 2.5, retencionesBancarias: 2.0 },\r\n \"Interior (promedio)\": { general: 3.5, simplificado: 2.0, retencionesBancarias: 2.0 },\r\n};\r\n\r\n// Costos de financiaci\u00F3n en cuotas sin inter\u00E9s\r\n// Porcentajes expresados como n\u00FAmeros (ej: 7.0 = 7.0%)\r\n// Rangos representados como valor medio de referencia\r\nexport const INSTALLMENT_COSTS: Record<\r\n string,\r\n { ahora12: number; propio: number; planZ: number }\r\n> = {\r\n \"3\": { ahora12: 7.0, propio: 8.0, planZ: 6.0 }, // 3 MSI\r\n \"6\": { ahora12: 11.5, propio: 13.5, planZ: 10.0 }, // 6 MSI\r\n \"12\": { ahora12: 18.0, propio: 22.5, planZ: 16.5 }, // 12 MSI\r\n \"18\": { ahora12: 25.0, propio: null as unknown as number, planZ: 22.0 }, // 18 MSI (Propio: N/A)\r\n};\r\n\r\n// Fees de pasarelas de pago\r\n// fee: porcentaje de transacci\u00F3n (valor medio del rango, sin IVA)\r\n// ivaMultiplier: multiplicador para aplicar IVA (1.21 = 21% IVA)\r\n// liquidacionDias: rango de d\u00EDas de liquidaci\u00F3n\r\nexport const PAYMENT_GATEWAY_FEES: Record<\r\n string,\r\n { feeMin: number; feeMax: number; feeMid: number; ivaMultiplier: number; liquidacionDiasMin: number; liquidacionDiasMax: number }\r\n> = {\r\n mercado_pago: {\r\n feeMin: 4.5,\r\n feeMax: 5.5,\r\n feeMid: 5.0,\r\n ivaMultiplier: 1.21,\r\n liquidacionDiasMin: 14,\r\n liquidacionDiasMax: 21,\r\n },\r\n mobbex: {\r\n feeMin: 2.8,\r\n feeMax: 3.5,\r\n feeMid: 3.15,\r\n ivaMultiplier: 1.21,\r\n liquidacionDiasMin: 7,\r\n liquidacionDiasMax: 14,\r\n },\r\n payway: {\r\n feeMin: 2.5,\r\n feeMax: 3.2,\r\n feeMid: 2.85,\r\n ivaMultiplier: 1.21,\r\n liquidacionDiasMin: 7,\r\n liquidacionDiasMax: 10,\r\n },\r\n dlocal_go: {\r\n feeMin: 3.0,\r\n feeMax: 4.0,\r\n feeMid: 3.5,\r\n ivaMultiplier: 1.21,\r\n liquidacionDiasMin: 10,\r\n liquidacionDiasMax: 14,\r\n },\r\n};\r\n\r\n// Costos log\u00EDsticos de referencia (ARS, rangos)\r\n// Valores al momento de publicaci\u00F3n del prompt de Juli\u00E1n \u2014 actualizar seg\u00FAn inflaci\u00F3n\r\nexport const LOGISTICS_COSTS: Record<\r\n string,\r\n { estandarMin: number; estandarMax: number; expressMin: number; expressMax: number; pesoLimiteKg: number }\r\n> = {\r\n CABA: { estandarMin: 4000, estandarMax: 6000, expressMin: 6000, expressMax: 9000, pesoLimiteKg: 5 },\r\n GBA: { estandarMin: 5000, estandarMax: 8000, expressMin: 8000, expressMax: 12000, pesoLimiteKg: 5 },\r\n \"Interior Cercano\": { estandarMin: 7000, estandarMax: 12000, expressMin: 15000, expressMax: 20000, pesoLimiteKg: 5 },\r\n \"Interior Lejano\": { estandarMin: 12000, estandarMax: 20000, expressMin: 25000, expressMax: 35000, pesoLimiteKg: 5 },\r\n Patagonia: { estandarMin: 18000, estandarMax: 30000, expressMin: 35000, expressMax: 50000, pesoLimiteKg: 5 },\r\n};\r\n\r\n// Calendario comercial Argentina \u2014 impacto revenue respecto a d\u00EDas normales\r\nexport const COMMERCIAL_CALENDAR: Array<{\r\n evento: string;\r\n mes: number; // 1-12\r\n impactoRevenueMin: number; // porcentaje adicional (200 = +200%)\r\n impactoRevenueMax: number;\r\n preparacionDias: number;\r\n esTemporadaBaja: boolean;\r\n}> = [\r\n { evento: \"Hot Sale\", mes: 5, impactoRevenueMin: 200, impactoRevenueMax: 500, preparacionDias: 60, esTemporadaBaja: false },\r\n { evento: \"CyberMonday\", mes: 11, impactoRevenueMin: 200, impactoRevenueMax: 400, preparacionDias: 45, esTemporadaBaja: false },\r\n { evento: \"D\u00EDa de la Madre\", mes: 10, impactoRevenueMin: 100, impactoRevenueMax: 200, preparacionDias: 30, esTemporadaBaja: false },\r\n { evento: \"Navidad\", mes: 12, impactoRevenueMin: 150, impactoRevenueMax: 300, preparacionDias: 45, esTemporadaBaja: false },\r\n { evento: \"D\u00EDa del Padre\", mes: 6, impactoRevenueMin: 50, impactoRevenueMax: 100, preparacionDias: 20, esTemporadaBaja: false },\r\n { evento: \"D\u00EDa del Ni\u00F1o\", mes: 8, impactoRevenueMin: 50, impactoRevenueMax: 150, preparacionDias: 20, esTemporadaBaja: false },\r\n { evento: \"Temporada Baja\", mes: 1, impactoRevenueMin: -50, impactoRevenueMax: -30, preparacionDias: 0, esTemporadaBaja: true },\r\n { evento: \"Temporada Baja\", mes: 2, impactoRevenueMin: -50, impactoRevenueMax: -30, preparacionDias: 0, esTemporadaBaja: true },\r\n];\r\n\r\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n// TOOL: config_get_benchmarks\r\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nexport const getBenchmarksSchema = z.object({\r\n type: z\r\n .enum([\"iibb\", \"installments\", \"payment_gateways\", \"logistics\", \"calendar\", \"all\"])\r\n .describe(\r\n \"Tipo de benchmark a consultar. \" +\r\n \"'iibb' = al\u00EDcuotas de Ingresos Brutos por provincia. \" +\r\n \"'installments' = costo de cuotas sin inter\u00E9s (Ahora 12, Plan Z, etc.). \" +\r\n \"'payment_gateways' = fees de pasarelas de pago (Mercado Pago, Mobbex, etc.). \" +\r\n \"'logistics' = costos de env\u00EDo de referencia por zona. \" +\r\n \"'calendar' = calendario comercial Argentina con impacto de revenue. \" +\r\n \"'all' = devuelve todos los benchmarks.\"\r\n ),\r\n});\r\n\r\nexport async function getBenchmarksHandler(\r\n params: z.infer<typeof getBenchmarksSchema>\r\n) {\r\n switch (params.type) {\r\n case \"iibb\":\r\n return object({\r\n type: \"iibb\",\r\n description: \"Al\u00EDcuotas de Ingresos Brutos por provincia (porcentajes sobre venta neta)\",\r\n data: IIBB_BY_PROVINCE,\r\n });\r\n\r\n case \"installments\":\r\n return object({\r\n type: \"installments\",\r\n description: \"Costo financiero de cuotas sin inter\u00E9s. Valores son porcentajes sobre el monto de venta. 'propio' para 18 cuotas es N/A.\",\r\n data: INSTALLMENT_COSTS,\r\n });\r\n\r\n case \"payment_gateways\":\r\n return object({\r\n type: \"payment_gateways\",\r\n description: \"Fees de pasarelas de pago. Los fees NO incluyen IVA \u2014 multiplicar por ivaMultiplier (1.21) para el costo real.\",\r\n data: PAYMENT_GATEWAY_FEES,\r\n });\r\n\r\n case \"logistics\":\r\n return object({\r\n type: \"logistics\",\r\n description: \"Costos log\u00EDsticos de referencia en ARS. Actualizar seg\u00FAn inflaci\u00F3n vigente.\",\r\n data: LOGISTICS_COSTS,\r\n });\r\n\r\n case \"calendar\":\r\n return object({\r\n type: \"calendar\",\r\n description: \"Calendario comercial Argentina. impactoRevenue es porcentaje adicional sobre d\u00EDas normales.\",\r\n data: COMMERCIAL_CALENDAR,\r\n });\r\n\r\n case \"all\":\r\n return object({\r\n iibb: IIBB_BY_PROVINCE,\r\n installments: INSTALLMENT_COSTS,\r\n paymentGateways: PAYMENT_GATEWAY_FEES,\r\n logistics: LOGISTICS_COSTS,\r\n calendar: COMMERCIAL_CALENDAR,\r\n });\r\n }\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/google-store.ts"],
4
- "sourcesContent": ["import { getDb } from '../db/client.js';\nimport { decryptSecret } from './vtex-crypto.js';\n\nexport type GoogleServiceStatus = 'active' | 'disabled' | 'expired' | 'error' | 'pending';\n\nexport interface GoogleOAuthConnectionRecord {\n id: string;\n encryptedRefreshToken: string | null;\n scopes: string[];\n status: GoogleServiceStatus;\n googleAccountEmail: string | null;\n lastValidatedAt: Date | null;\n lastError: string | null;\n}\n\nexport interface ProfileGoogleServiceMappingRecord {\n profileId: string;\n googleAdsCustomerId: string | null;\n googleAdsLabel: string | null;\n googleAdsStatus: GoogleServiceStatus;\n googleAdsLastValidatedAt: Date | null;\n googleAdsLastError: string | null;\n ga4PropertyId: string | null;\n ga4PropertyLabel: string | null;\n ga4Status: GoogleServiceStatus;\n ga4LastValidatedAt: Date | null;\n ga4LastError: string | null;\n searchConsoleSiteUrl: string | null;\n searchConsoleLabel: string | null;\n searchConsoleStatus: GoogleServiceStatus;\n searchConsoleLastValidatedAt: Date | null;\n searchConsoleLastError: string | null;\n updatedAt: Date | null;\n}\n\ninterface GoogleOAuthConnectionRow {\n id: string;\n encrypted_refresh_token: string | null;\n scopes: string[] | null;\n status: GoogleServiceStatus;\n google_account_email: string | null;\n last_validated_at: Date | null;\n last_error: string | null;\n}\n\ninterface ProfileGoogleServiceMappingRow {\n profile_id: string;\n google_ads_customer_id: string | null;\n google_ads_label: string | null;\n google_ads_status: GoogleServiceStatus;\n google_ads_last_validated_at: Date | null;\n google_ads_last_error: string | null;\n ga4_property_id: string | null;\n ga4_property_label: string | null;\n ga4_status: GoogleServiceStatus;\n ga4_last_validated_at: Date | null;\n ga4_last_error: string | null;\n search_console_site_url: string | null;\n search_console_label: string | null;\n search_console_status: GoogleServiceStatus;\n search_console_last_validated_at: Date | null;\n search_console_last_error: string | null;\n updated_at: Date | null;\n}\n\nfunction isMissingRelationError(error: unknown): boolean {\n return error instanceof Error && /does not exist/i.test(error.message);\n}\n\nfunction mapConnection(row: GoogleOAuthConnectionRow): GoogleOAuthConnectionRecord {\n return {\n id: row.id,\n encryptedRefreshToken: row.encrypted_refresh_token,\n scopes: Array.isArray(row.scopes) ? row.scopes : [],\n status: row.status,\n googleAccountEmail: row.google_account_email,\n lastValidatedAt: row.last_validated_at,\n lastError: row.last_error,\n };\n}\n\nfunction mapMapping(row: ProfileGoogleServiceMappingRow): ProfileGoogleServiceMappingRecord {\n return {\n profileId: row.profile_id,\n googleAdsCustomerId: row.google_ads_customer_id,\n googleAdsLabel: row.google_ads_label,\n googleAdsStatus: row.google_ads_status,\n googleAdsLastValidatedAt: row.google_ads_last_validated_at,\n googleAdsLastError: row.google_ads_last_error,\n ga4PropertyId: row.ga4_property_id,\n ga4PropertyLabel: row.ga4_property_label,\n ga4Status: row.ga4_status,\n ga4LastValidatedAt: row.ga4_last_validated_at,\n ga4LastError: row.ga4_last_error,\n searchConsoleSiteUrl: row.search_console_site_url,\n searchConsoleLabel: row.search_console_label,\n searchConsoleStatus: row.search_console_status,\n searchConsoleLastValidatedAt: row.search_console_last_validated_at,\n searchConsoleLastError: row.search_console_last_error,\n updatedAt: row.updated_at,\n };\n}\n\nexport async function getGoogleOAuthConnection(): Promise<GoogleOAuthConnectionRecord | null> {\n try {\n const result = await getDb().query<GoogleOAuthConnectionRow>(\n `select id, encrypted_refresh_token, scopes, status, google_account_email, last_validated_at, last_error\n from google_oauth_connections\n where id = 'global'`\n );\n const row = result.rows[0];\n return row ? mapConnection(row) : null;\n } catch (error) {\n if (isMissingRelationError(error)) return null;\n throw error;\n }\n}\n\nexport async function getActiveGoogleRefreshTokenFromStore(): Promise<string | null> {\n const connection = await getGoogleOAuthConnection();\n if (!connection || connection.status !== 'active' || !connection.encryptedRefreshToken) return null;\n return decryptSecret(connection.encryptedRefreshToken);\n}\n\nexport async function getProfileGoogleServiceMapping(profileId: string): Promise<ProfileGoogleServiceMappingRecord | null> {\n try {\n const result = await getDb().query<ProfileGoogleServiceMappingRow>(\n `select profile_id, google_ads_customer_id, google_ads_label, google_ads_status, google_ads_last_validated_at, google_ads_last_error,\n ga4_property_id, ga4_property_label, ga4_status, ga4_last_validated_at, ga4_last_error,\n search_console_site_url, search_console_label, search_console_status, search_console_last_validated_at, search_console_last_error,\n updated_at\n from profile_google_service_mappings\n where profile_id = $1`,\n [profileId]\n );\n const row = result.rows[0];\n return row ? mapMapping(row) : null;\n } catch (error) {\n if (isMissingRelationError(error)) return null;\n throw error;\n }\n}\n\nexport async function listProfileGoogleServiceMappings(): Promise<ProfileGoogleServiceMappingRecord[]> {\n try {\n const result = await getDb().query<ProfileGoogleServiceMappingRow>(\n `select profile_id, google_ads_customer_id, google_ads_label, google_ads_status, google_ads_last_validated_at, google_ads_last_error,\n ga4_property_id, ga4_property_label, ga4_status, ga4_last_validated_at, ga4_last_error,\n search_console_site_url, search_console_label, search_console_status, search_console_last_validated_at, search_console_last_error,\n updated_at\n from profile_google_service_mappings`\n );\n return result.rows.map(mapMapping);\n } catch (error) {\n if (isMissingRelationError(error)) return [];\n throw error;\n }\n}\n"],
4
+ "sourcesContent": ["import { getDb } from '../db/client.js';\r\nimport { decryptSecret } from './vtex-crypto.js';\r\n\r\nexport type GoogleServiceStatus = 'active' | 'disabled' | 'expired' | 'error' | 'pending';\r\n\r\nexport interface GoogleOAuthConnectionRecord {\r\n id: string;\r\n encryptedRefreshToken: string | null;\r\n scopes: string[];\r\n status: GoogleServiceStatus;\r\n googleAccountEmail: string | null;\r\n lastValidatedAt: Date | null;\r\n lastError: string | null;\r\n}\r\n\r\nexport interface ProfileGoogleServiceMappingRecord {\r\n profileId: string;\r\n googleAdsCustomerId: string | null;\r\n googleAdsLabel: string | null;\r\n googleAdsStatus: GoogleServiceStatus;\r\n googleAdsLastValidatedAt: Date | null;\r\n googleAdsLastError: string | null;\r\n ga4PropertyId: string | null;\r\n ga4PropertyLabel: string | null;\r\n ga4Status: GoogleServiceStatus;\r\n ga4LastValidatedAt: Date | null;\r\n ga4LastError: string | null;\r\n searchConsoleSiteUrl: string | null;\r\n searchConsoleLabel: string | null;\r\n searchConsoleStatus: GoogleServiceStatus;\r\n searchConsoleLastValidatedAt: Date | null;\r\n searchConsoleLastError: string | null;\r\n updatedAt: Date | null;\r\n}\r\n\r\ninterface GoogleOAuthConnectionRow {\r\n id: string;\r\n encrypted_refresh_token: string | null;\r\n scopes: string[] | null;\r\n status: GoogleServiceStatus;\r\n google_account_email: string | null;\r\n last_validated_at: Date | null;\r\n last_error: string | null;\r\n}\r\n\r\ninterface ProfileGoogleServiceMappingRow {\r\n profile_id: string;\r\n google_ads_customer_id: string | null;\r\n google_ads_label: string | null;\r\n google_ads_status: GoogleServiceStatus;\r\n google_ads_last_validated_at: Date | null;\r\n google_ads_last_error: string | null;\r\n ga4_property_id: string | null;\r\n ga4_property_label: string | null;\r\n ga4_status: GoogleServiceStatus;\r\n ga4_last_validated_at: Date | null;\r\n ga4_last_error: string | null;\r\n search_console_site_url: string | null;\r\n search_console_label: string | null;\r\n search_console_status: GoogleServiceStatus;\r\n search_console_last_validated_at: Date | null;\r\n search_console_last_error: string | null;\r\n updated_at: Date | null;\r\n}\r\n\r\nfunction isMissingRelationError(error: unknown): boolean {\r\n return error instanceof Error && /does not exist/i.test(error.message);\r\n}\r\n\r\nfunction mapConnection(row: GoogleOAuthConnectionRow): GoogleOAuthConnectionRecord {\r\n return {\r\n id: row.id,\r\n encryptedRefreshToken: row.encrypted_refresh_token,\r\n scopes: Array.isArray(row.scopes) ? row.scopes : [],\r\n status: row.status,\r\n googleAccountEmail: row.google_account_email,\r\n lastValidatedAt: row.last_validated_at,\r\n lastError: row.last_error,\r\n };\r\n}\r\n\r\nfunction mapMapping(row: ProfileGoogleServiceMappingRow): ProfileGoogleServiceMappingRecord {\r\n return {\r\n profileId: row.profile_id,\r\n googleAdsCustomerId: row.google_ads_customer_id,\r\n googleAdsLabel: row.google_ads_label,\r\n googleAdsStatus: row.google_ads_status,\r\n googleAdsLastValidatedAt: row.google_ads_last_validated_at,\r\n googleAdsLastError: row.google_ads_last_error,\r\n ga4PropertyId: row.ga4_property_id,\r\n ga4PropertyLabel: row.ga4_property_label,\r\n ga4Status: row.ga4_status,\r\n ga4LastValidatedAt: row.ga4_last_validated_at,\r\n ga4LastError: row.ga4_last_error,\r\n searchConsoleSiteUrl: row.search_console_site_url,\r\n searchConsoleLabel: row.search_console_label,\r\n searchConsoleStatus: row.search_console_status,\r\n searchConsoleLastValidatedAt: row.search_console_last_validated_at,\r\n searchConsoleLastError: row.search_console_last_error,\r\n updatedAt: row.updated_at,\r\n };\r\n}\r\n\r\nexport async function getGoogleOAuthConnection(): Promise<GoogleOAuthConnectionRecord | null> {\r\n try {\r\n const result = await getDb().query<GoogleOAuthConnectionRow>(\r\n `select id, encrypted_refresh_token, scopes, status, google_account_email, last_validated_at, last_error\r\n from google_oauth_connections\r\n where id = 'global'`\r\n );\r\n const row = result.rows[0];\r\n return row ? mapConnection(row) : null;\r\n } catch (error) {\r\n if (isMissingRelationError(error)) return null;\r\n throw error;\r\n }\r\n}\r\n\r\nexport async function getActiveGoogleRefreshTokenFromStore(): Promise<string | null> {\r\n const connection = await getGoogleOAuthConnection();\r\n if (!connection || connection.status !== 'active' || !connection.encryptedRefreshToken) return null;\r\n return decryptSecret(connection.encryptedRefreshToken);\r\n}\r\n\r\nexport async function getProfileGoogleServiceMapping(profileId: string): Promise<ProfileGoogleServiceMappingRecord | null> {\r\n try {\r\n const result = await getDb().query<ProfileGoogleServiceMappingRow>(\r\n `select profile_id, google_ads_customer_id, google_ads_label, google_ads_status, google_ads_last_validated_at, google_ads_last_error,\r\n ga4_property_id, ga4_property_label, ga4_status, ga4_last_validated_at, ga4_last_error,\r\n search_console_site_url, search_console_label, search_console_status, search_console_last_validated_at, search_console_last_error,\r\n updated_at\r\n from profile_google_service_mappings\r\n where profile_id = $1`,\r\n [profileId]\r\n );\r\n const row = result.rows[0];\r\n return row ? mapMapping(row) : null;\r\n } catch (error) {\r\n if (isMissingRelationError(error)) return null;\r\n throw error;\r\n }\r\n}\r\n\r\nexport async function listProfileGoogleServiceMappings(): Promise<ProfileGoogleServiceMappingRecord[]> {\r\n try {\r\n const result = await getDb().query<ProfileGoogleServiceMappingRow>(\r\n `select profile_id, google_ads_customer_id, google_ads_label, google_ads_status, google_ads_last_validated_at, google_ads_last_error,\r\n ga4_property_id, ga4_property_label, ga4_status, ga4_last_validated_at, ga4_last_error,\r\n search_console_site_url, search_console_label, search_console_status, search_console_last_validated_at, search_console_last_error,\r\n updated_at\r\n from profile_google_service_mappings`\r\n );\r\n return result.rows.map(mapMapping);\r\n } catch (error) {\r\n if (isMissingRelationError(error)) return [];\r\n throw error;\r\n }\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,aAAa;AACtB,SAAS,qBAAqB;AAgE9B,SAAS,uBAAuB,OAAyB;AACvD,SAAO,iBAAiB,SAAS,kBAAkB,KAAK,MAAM,OAAO;AACvE;AAEA,SAAS,cAAc,KAA4D;AACjF,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,uBAAuB,IAAI;AAAA,IAC3B,QAAQ,MAAM,QAAQ,IAAI,MAAM,IAAI,IAAI,SAAS,CAAC;AAAA,IAClD,QAAQ,IAAI;AAAA,IACZ,oBAAoB,IAAI;AAAA,IACxB,iBAAiB,IAAI;AAAA,IACrB,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,SAAS,WAAW,KAAwE;AAC1F,SAAO;AAAA,IACL,WAAW,IAAI;AAAA,IACf,qBAAqB,IAAI;AAAA,IACzB,gBAAgB,IAAI;AAAA,IACpB,iBAAiB,IAAI;AAAA,IACrB,0BAA0B,IAAI;AAAA,IAC9B,oBAAoB,IAAI;AAAA,IACxB,eAAe,IAAI;AAAA,IACnB,kBAAkB,IAAI;AAAA,IACtB,WAAW,IAAI;AAAA,IACf,oBAAoB,IAAI;AAAA,IACxB,cAAc,IAAI;AAAA,IAClB,sBAAsB,IAAI;AAAA,IAC1B,oBAAoB,IAAI;AAAA,IACxB,qBAAqB,IAAI;AAAA,IACzB,8BAA8B,IAAI;AAAA,IAClC,wBAAwB,IAAI;AAAA,IAC5B,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,eAAsB,2BAAwE;AAC5F,MAAI;AACF,UAAM,SAAS,MAAM,MAAM,EAAE;AAAA,MAC3B;AAAA;AAAA;AAAA,IAGF;AACA,UAAM,MAAM,OAAO,KAAK,CAAC;AACzB,WAAO,MAAM,cAAc,GAAG,IAAI;AAAA,EACpC,SAAS,OAAO;AACd,QAAI,uBAAuB,KAAK,EAAG,QAAO;AAC1C,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,uCAA+D;AACnF,QAAM,aAAa,MAAM,yBAAyB;AAClD,MAAI,CAAC,cAAc,WAAW,WAAW,YAAY,CAAC,WAAW,sBAAuB,QAAO;AAC/F,SAAO,cAAc,WAAW,qBAAqB;AACvD;AAEA,eAAsB,+BAA+B,WAAsE;AACzH,MAAI;AACF,UAAM,SAAS,MAAM,MAAM,EAAE;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,CAAC,SAAS;AAAA,IACZ;AACA,UAAM,MAAM,OAAO,KAAK,CAAC;AACzB,WAAO,MAAM,WAAW,GAAG,IAAI;AAAA,EACjC,SAAS,OAAO;AACd,QAAI,uBAAuB,KAAK,EAAG,QAAO;AAC1C,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,mCAAiF;AACrG,MAAI;AACF,UAAM,SAAS,MAAM,MAAM,EAAE;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF;AACA,WAAO,OAAO,KAAK,IAAI,UAAU;AAAA,EACnC,SAAS,OAAO;AACd,QAAI,uBAAuB,KAAK,EAAG,QAAO,CAAC;AAC3C,UAAM;AAAA,EACR;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/google.ts"],
4
- "sourcesContent": ["const REQUIRED_ENV_VARS = ['GOOGLE_OAUTH_CLIENT_ID', 'GOOGLE_OAUTH_CLIENT_SECRET'] as const;\n\ntype RequiredEnvVar = (typeof REQUIRED_ENV_VARS)[number];\n\nfunction requireEnv(name: RequiredEnvVar): string {\n const value = process.env[name]?.trim();\n if (!value) throw new Error(`Missing required environment variable: ${name}`);\n return value;\n}\n\nexport interface GoogleOAuthClientConfig {\n clientId: string;\n clientSecret: string;\n}\n\nexport const googleOAuthConfig: GoogleOAuthClientConfig = {\n clientId: requireEnv('GOOGLE_OAUTH_CLIENT_ID'),\n clientSecret: requireEnv('GOOGLE_OAUTH_CLIENT_SECRET'),\n};\n\n\nexport function getDefaultGa4PropertyId(): string | undefined {\n const value = process.env.GA4_PROPERTY_ID?.trim();\n return value ? value : undefined;\n}\n\nexport function getGoogleAdsDeveloperToken(): string {\n const value = process.env.GOOGLE_ADS_DEVELOPER_TOKEN?.trim();\n if (!value) throw new Error('Missing Google Ads developer token. Configure GOOGLE_ADS_DEVELOPER_TOKEN.');\n return value;\n}\n\nexport function getDefaultGoogleAdsLoginCustomerId(): string | undefined {\n const value = process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID?.trim();\n return value ? value.replace(/-/g, '') : undefined;\n}\n"],
4
+ "sourcesContent": ["const REQUIRED_ENV_VARS = ['GOOGLE_OAUTH_CLIENT_ID', 'GOOGLE_OAUTH_CLIENT_SECRET'] as const;\r\n\r\ntype RequiredEnvVar = (typeof REQUIRED_ENV_VARS)[number];\r\n\r\nfunction requireEnv(name: RequiredEnvVar): string {\r\n const value = process.env[name]?.trim();\r\n if (!value) throw new Error(`Missing required environment variable: ${name}`);\r\n return value;\r\n}\r\n\r\nexport interface GoogleOAuthClientConfig {\r\n clientId: string;\r\n clientSecret: string;\r\n}\r\n\r\nexport const googleOAuthConfig: GoogleOAuthClientConfig = {\r\n clientId: requireEnv('GOOGLE_OAUTH_CLIENT_ID'),\r\n clientSecret: requireEnv('GOOGLE_OAUTH_CLIENT_SECRET'),\r\n};\r\n\r\n\r\nexport function getDefaultGa4PropertyId(): string | undefined {\r\n const value = process.env.GA4_PROPERTY_ID?.trim();\r\n return value ? value : undefined;\r\n}\r\n\r\nexport function getGoogleAdsDeveloperToken(): string {\r\n const value = process.env.GOOGLE_ADS_DEVELOPER_TOKEN?.trim();\r\n if (!value) throw new Error('Missing Google Ads developer token. Configure GOOGLE_ADS_DEVELOPER_TOKEN.');\r\n return value;\r\n}\r\n\r\nexport function getDefaultGoogleAdsLoginCustomerId(): string | undefined {\r\n const value = process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID?.trim();\r\n return value ? value.replace(/-/g, '') : undefined;\r\n}\r\n"],
5
5
  "mappings": "AAAA,MAAM,oBAAoB,CAAC,0BAA0B,4BAA4B;AAIjF,SAAS,WAAW,MAA8B;AAChD,QAAM,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK;AACtC,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAC5E,SAAO;AACT;AAOO,MAAM,oBAA6C;AAAA,EACxD,UAAU,WAAW,wBAAwB;AAAA,EAC7C,cAAc,WAAW,4BAA4B;AACvD;AAGO,SAAS,0BAA8C;AAC5D,QAAM,QAAQ,QAAQ,IAAI,iBAAiB,KAAK;AAChD,SAAO,QAAQ,QAAQ;AACzB;AAEO,SAAS,6BAAqC;AACnD,QAAM,QAAQ,QAAQ,IAAI,4BAA4B,KAAK;AAC3D,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,2EAA2E;AACvG,SAAO;AACT;AAEO,SAAS,qCAAyD;AACvE,QAAM,QAAQ,QAAQ,IAAI,8BAA8B,KAAK;AAC7D,SAAO,QAAQ,MAAM,QAAQ,MAAM,EAAE,IAAI;AAC3C;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/mercadolibre-profile-store.ts"],
4
- "sourcesContent": ["import { getDb } from \"../db/client.js\";\n\nexport type MercadoLibreConnectionStatus = \"active\" | \"disabled\" | \"expired\" | \"error\" | \"pending\";\n\nexport interface MercadoLibreConnectionRecord {\n id: string;\n profileId: string;\n profileName?: string;\n sellerId: string;\n encryptedAccessToken: string;\n encryptedRefreshToken: string | null;\n expiresAt: Date | null;\n scopes: string[];\n status: MercadoLibreConnectionStatus;\n lastValidatedAt: Date | null;\n lastError: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface MercadoLibreConnectionRow {\n id: string;\n profile_id: string;\n profile_name?: string;\n seller_id: string;\n encrypted_access_token: string;\n encrypted_refresh_token: string | null;\n expires_at: Date | null;\n scopes: string[] | null;\n status: MercadoLibreConnectionStatus;\n last_validated_at: Date | null;\n last_error: string | null;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction mapMercadoLibreConnectionRow(\n row: MercadoLibreConnectionRow\n): MercadoLibreConnectionRecord {\n return {\n id: row.id,\n profileId: row.profile_id,\n profileName: row.profile_name,\n sellerId: row.seller_id,\n encryptedAccessToken: row.encrypted_access_token,\n encryptedRefreshToken: row.encrypted_refresh_token,\n expiresAt: row.expires_at,\n scopes: Array.isArray(row.scopes) ? row.scopes : [],\n status: row.status,\n lastValidatedAt: row.last_validated_at,\n lastError: row.last_error,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport async function getMercadoLibreConnectionRow(\n profileId: string\n): Promise<MercadoLibreConnectionRecord | null> {\n const result = await getDb().query<MercadoLibreConnectionRow>(\n `\n select\n pmc.id,\n pmc.profile_id,\n p.name as profile_name,\n pmc.seller_id,\n pmc.encrypted_access_token,\n pmc.encrypted_refresh_token,\n pmc.expires_at,\n pmc.scopes,\n pmc.status,\n pmc.last_validated_at,\n pmc.last_error,\n pmc.created_at,\n pmc.updated_at\n from profile_mercadolibre_connections pmc\n inner join profiles p on p.id = pmc.profile_id\n where pmc.profile_id = $1\n `,\n [profileId]\n );\n\n const row = result.rows[0];\n return row ? mapMercadoLibreConnectionRow(row) : null;\n}\n\nexport async function updateMercadoLibreConnectionTokens(input: {\n profileId: string;\n sellerId: string;\n encryptedAccessToken: string;\n encryptedRefreshToken: string | null;\n expiresAt: Date | null;\n scopes: string[];\n status: MercadoLibreConnectionStatus;\n lastError?: string | null;\n}): Promise<MercadoLibreConnectionRecord> {\n const result = await getDb().query<MercadoLibreConnectionRow>(\n `\n update profile_mercadolibre_connections\n set\n seller_id = $2,\n encrypted_access_token = $3,\n encrypted_refresh_token = $4,\n expires_at = $5,\n scopes = $6,\n status = $7,\n last_validated_at = now(),\n last_error = $8,\n updated_at = now()\n where profile_id = $1\n returning\n id,\n profile_id,\n seller_id,\n encrypted_access_token,\n encrypted_refresh_token,\n expires_at,\n scopes,\n status,\n last_validated_at,\n last_error,\n created_at,\n updated_at\n `,\n [\n input.profileId,\n input.sellerId,\n input.encryptedAccessToken,\n input.encryptedRefreshToken,\n input.expiresAt,\n input.scopes,\n input.status,\n input.lastError ?? null,\n ]\n );\n\n return mapMercadoLibreConnectionRow(result.rows[0]);\n}\n\nexport async function markMercadoLibreConnectionValidation(\n profileId: string,\n status: MercadoLibreConnectionStatus,\n lastError?: string | null\n): Promise<void> {\n await getDb().query(\n `\n update profile_mercadolibre_connections\n set\n status = $2,\n last_validated_at = now(),\n last_error = $3,\n updated_at = now()\n where profile_id = $1\n `,\n [profileId, status, lastError ?? null]\n );\n}\n"],
4
+ "sourcesContent": ["import { getDb } from \"../db/client.js\";\r\n\r\nexport type MercadoLibreConnectionStatus = \"active\" | \"disabled\" | \"expired\" | \"error\" | \"pending\";\r\n\r\nexport interface MercadoLibreConnectionRecord {\r\n id: string;\r\n profileId: string;\r\n profileName?: string;\r\n sellerId: string;\r\n encryptedAccessToken: string;\r\n encryptedRefreshToken: string | null;\r\n expiresAt: Date | null;\r\n scopes: string[];\r\n status: MercadoLibreConnectionStatus;\r\n lastValidatedAt: Date | null;\r\n lastError: string | null;\r\n createdAt: Date;\r\n updatedAt: Date;\r\n}\r\n\r\ninterface MercadoLibreConnectionRow {\r\n id: string;\r\n profile_id: string;\r\n profile_name?: string;\r\n seller_id: string;\r\n encrypted_access_token: string;\r\n encrypted_refresh_token: string | null;\r\n expires_at: Date | null;\r\n scopes: string[] | null;\r\n status: MercadoLibreConnectionStatus;\r\n last_validated_at: Date | null;\r\n last_error: string | null;\r\n created_at: Date;\r\n updated_at: Date;\r\n}\r\n\r\nfunction mapMercadoLibreConnectionRow(\r\n row: MercadoLibreConnectionRow\r\n): MercadoLibreConnectionRecord {\r\n return {\r\n id: row.id,\r\n profileId: row.profile_id,\r\n profileName: row.profile_name,\r\n sellerId: row.seller_id,\r\n encryptedAccessToken: row.encrypted_access_token,\r\n encryptedRefreshToken: row.encrypted_refresh_token,\r\n expiresAt: row.expires_at,\r\n scopes: Array.isArray(row.scopes) ? row.scopes : [],\r\n status: row.status,\r\n lastValidatedAt: row.last_validated_at,\r\n lastError: row.last_error,\r\n createdAt: row.created_at,\r\n updatedAt: row.updated_at,\r\n };\r\n}\r\n\r\nexport async function getMercadoLibreConnectionRow(\r\n profileId: string\r\n): Promise<MercadoLibreConnectionRecord | null> {\r\n const result = await getDb().query<MercadoLibreConnectionRow>(\r\n `\r\n select\r\n pmc.id,\r\n pmc.profile_id,\r\n p.name as profile_name,\r\n pmc.seller_id,\r\n pmc.encrypted_access_token,\r\n pmc.encrypted_refresh_token,\r\n pmc.expires_at,\r\n pmc.scopes,\r\n pmc.status,\r\n pmc.last_validated_at,\r\n pmc.last_error,\r\n pmc.created_at,\r\n pmc.updated_at\r\n from profile_mercadolibre_connections pmc\r\n inner join profiles p on p.id = pmc.profile_id\r\n where pmc.profile_id = $1\r\n `,\r\n [profileId]\r\n );\r\n\r\n const row = result.rows[0];\r\n return row ? mapMercadoLibreConnectionRow(row) : null;\r\n}\r\n\r\nexport async function updateMercadoLibreConnectionTokens(input: {\r\n profileId: string;\r\n sellerId: string;\r\n encryptedAccessToken: string;\r\n encryptedRefreshToken: string | null;\r\n expiresAt: Date | null;\r\n scopes: string[];\r\n status: MercadoLibreConnectionStatus;\r\n lastError?: string | null;\r\n}): Promise<MercadoLibreConnectionRecord> {\r\n const result = await getDb().query<MercadoLibreConnectionRow>(\r\n `\r\n update profile_mercadolibre_connections\r\n set\r\n seller_id = $2,\r\n encrypted_access_token = $3,\r\n encrypted_refresh_token = $4,\r\n expires_at = $5,\r\n scopes = $6,\r\n status = $7,\r\n last_validated_at = now(),\r\n last_error = $8,\r\n updated_at = now()\r\n where profile_id = $1\r\n returning\r\n id,\r\n profile_id,\r\n seller_id,\r\n encrypted_access_token,\r\n encrypted_refresh_token,\r\n expires_at,\r\n scopes,\r\n status,\r\n last_validated_at,\r\n last_error,\r\n created_at,\r\n updated_at\r\n `,\r\n [\r\n input.profileId,\r\n input.sellerId,\r\n input.encryptedAccessToken,\r\n input.encryptedRefreshToken,\r\n input.expiresAt,\r\n input.scopes,\r\n input.status,\r\n input.lastError ?? null,\r\n ]\r\n );\r\n\r\n return mapMercadoLibreConnectionRow(result.rows[0]);\r\n}\r\n\r\nexport async function markMercadoLibreConnectionValidation(\r\n profileId: string,\r\n status: MercadoLibreConnectionStatus,\r\n lastError?: string | null\r\n): Promise<void> {\r\n await getDb().query(\r\n `\r\n update profile_mercadolibre_connections\r\n set\r\n status = $2,\r\n last_validated_at = now(),\r\n last_error = $3,\r\n updated_at = now()\r\n where profile_id = $1\r\n `,\r\n [profileId, status, lastError ?? null]\r\n );\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,aAAa;AAoCtB,SAAS,6BACP,KAC8B;AAC9B,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,WAAW,IAAI;AAAA,IACf,aAAa,IAAI;AAAA,IACjB,UAAU,IAAI;AAAA,IACd,sBAAsB,IAAI;AAAA,IAC1B,uBAAuB,IAAI;AAAA,IAC3B,WAAW,IAAI;AAAA,IACf,QAAQ,MAAM,QAAQ,IAAI,MAAM,IAAI,IAAI,SAAS,CAAC;AAAA,IAClD,QAAQ,IAAI;AAAA,IACZ,iBAAiB,IAAI;AAAA,IACrB,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,eAAsB,6BACpB,WAC8C;AAC9C,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAmBA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,MAAM,OAAO,KAAK,CAAC;AACzB,SAAO,MAAM,6BAA6B,GAAG,IAAI;AACnD;AAEA,eAAsB,mCAAmC,OASf;AACxC,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA2BA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,aAAa;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,6BAA6B,OAAO,KAAK,CAAC,CAAC;AACpD;AAEA,eAAsB,qCACpB,WACA,QACA,WACe;AACf,QAAM,MAAM,EAAE;AAAA,IACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,CAAC,WAAW,QAAQ,aAAa,IAAI;AAAA,EACvC;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/mercadolibre.ts"],
4
- "sourcesContent": ["import { assertActiveProfile } from \"./profile-store.js\";\nimport { decryptSecret, encryptSecret } from \"./vtex-crypto.js\";\nimport {\n getMercadoLibreConnectionRow,\n markMercadoLibreConnectionValidation,\n updateMercadoLibreConnectionTokens,\n} from \"./mercadolibre-profile-store.js\";\n\nconst TOKEN_URL = \"https://api.mercadolibre.com/oauth/token\";\nconst REFRESH_SKEW_MS = 5 * 60 * 1000;\n\nexport interface MercadoLibreAccessContext {\n profileId: string;\n sellerId: string;\n accessToken: string;\n scopes: string[];\n expiresAt: string | null;\n}\n\ninterface MercadoLibreTokenSet {\n accessToken: string;\n refreshToken: string | null;\n userId: string;\n expiresAt: string | null;\n scopes: string[];\n}\n\nfunction env(name: string): string {\n const value = process.env[name]?.trim();\n if (!value) {\n throw new Error(`Missing ${name}`);\n }\n\n return value;\n}\n\nfunction sanitizeMercadoLibreText(value: unknown): string {\n const raw = typeof value === \"string\" ? value : String(value ?? \"MercadoLibre request failed\");\n return raw\n .replace(/access_token[=:][^\\s&]+/gi, \"access_token=[redacted]\")\n .replace(/refresh_token[=:][^\\s&]+/gi, \"refresh_token=[redacted]\")\n .replace(/client_secret[=:][^\\s&]+/gi, \"client_secret=[redacted]\")\n .replace(/code[=:][^\\s&]+/gi, \"code=[redacted]\")\n .replace(/Bearer\\s+[A-Za-z0-9._-]+/gi, \"Bearer [redacted]\")\n .slice(0, 220);\n}\n\nfunction parseScopes(scope: unknown): string[] {\n if (typeof scope !== \"string\") {\n return [];\n }\n\n return scope\n .split(/[ ,]+/)\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\nfunction expiresAtFrom(expiresIn: unknown): string | null {\n if (typeof expiresIn !== \"number\" || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n return null;\n }\n\n return new Date(Date.now() + expiresIn * 1000).toISOString();\n}\n\nfunction shouldRefreshMercadoLibreToken(expiresAt: Date | null): boolean {\n if (!expiresAt) {\n return false;\n }\n\n return expiresAt.getTime() <= Date.now() + REFRESH_SKEW_MS;\n}\n\nasync function readRefreshError(response: Response): Promise<Error> {\n const body = await response.json().catch(() => null);\n if (body && typeof body === \"object\") {\n const candidate = body as {\n error?: unknown;\n message?: unknown;\n error_description?: unknown;\n };\n const code = typeof candidate.error === \"string\" ? candidate.error : response.statusText;\n const message =\n sanitizeMercadoLibreText(candidate.message) ||\n sanitizeMercadoLibreText(candidate.error_description) ||\n sanitizeMercadoLibreText(code);\n return new Error(`MercadoLibre token refresh failed (${response.status}): ${message}`);\n }\n\n return new Error(\n `MercadoLibre token refresh failed (${response.status}): ${sanitizeMercadoLibreText(response.statusText)}`\n );\n}\n\nasync function refreshMercadoLibreToken(refreshToken: string): Promise<MercadoLibreTokenSet> {\n const response = await fetch(TOKEN_URL, {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n client_id: env(\"MERCADOLIBRE_CLIENT_ID\"),\n client_secret: env(\"MERCADOLIBRE_CLIENT_SECRET\"),\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n throw await readRefreshError(response);\n }\n\n const payload = (await response.json()) as Record<string, unknown>;\n const accessToken = payload.access_token;\n const userId = payload.user_id;\n\n if (typeof accessToken !== \"string\" || !accessToken) {\n throw new Error(\"MercadoLibre token refresh response missing access_token\");\n }\n\n if ((typeof userId !== \"string\" && typeof userId !== \"number\") || String(userId).length === 0) {\n throw new Error(\"MercadoLibre token refresh response missing user_id\");\n }\n\n return {\n accessToken,\n refreshToken:\n typeof payload.refresh_token === \"string\" && payload.refresh_token\n ? payload.refresh_token\n : refreshToken,\n userId: String(userId),\n expiresAt: expiresAtFrom(payload.expires_in),\n scopes: parseScopes(payload.scope),\n };\n}\n\nexport async function getMercadoLibreAccessForProfile(\n profileId: string\n): Promise<MercadoLibreAccessContext> {\n await assertActiveProfile(profileId);\n\n const connection = await getMercadoLibreConnectionRow(profileId);\n if (!connection) {\n throw new Error(`MercadoLibre connection not found for profileId \"${profileId}\"`);\n }\n\n if (connection.status === \"disabled\") {\n throw new Error(`MercadoLibre connection for profileId \"${profileId}\" is disabled`);\n }\n\n try {\n let accessToken = decryptSecret(connection.encryptedAccessToken);\n let refreshToken = connection.encryptedRefreshToken\n ? decryptSecret(connection.encryptedRefreshToken)\n : null;\n let scopes = connection.scopes;\n let expiresAt = connection.expiresAt ? connection.expiresAt.toISOString() : null;\n\n if (shouldRefreshMercadoLibreToken(connection.expiresAt)) {\n if (!refreshToken) {\n await markMercadoLibreConnectionValidation(profileId, \"expired\", \"Refresh token is missing\");\n throw new Error(\n `MercadoLibre connection for profileId \"${profileId}\" is expired and has no refresh token`\n );\n }\n\n const refreshedTokens = await refreshMercadoLibreToken(refreshToken);\n if (refreshedTokens.userId !== connection.sellerId) {\n await markMercadoLibreConnectionValidation(\n profileId,\n \"error\",\n \"Refreshed MercadoLibre seller_id does not match stored seller_id\"\n );\n throw new Error(\n `MercadoLibre refresh returned seller_id \"${refreshedTokens.userId}\" but expected \"${connection.sellerId}\"`\n );\n }\n\n const encryptedAccessToken = encryptSecret(refreshedTokens.accessToken);\n const encryptedRefreshToken = refreshedTokens.refreshToken\n ? encryptSecret(refreshedTokens.refreshToken)\n : null;\n const nextExpiresAt = refreshedTokens.expiresAt ? new Date(refreshedTokens.expiresAt) : null;\n\n await updateMercadoLibreConnectionTokens({\n profileId,\n sellerId: connection.sellerId,\n encryptedAccessToken,\n encryptedRefreshToken,\n expiresAt: nextExpiresAt,\n scopes: refreshedTokens.scopes,\n status: \"active\",\n lastError: null,\n });\n\n accessToken = refreshedTokens.accessToken;\n refreshToken = refreshedTokens.refreshToken;\n scopes = refreshedTokens.scopes;\n expiresAt = refreshedTokens.expiresAt;\n }\n\n await markMercadoLibreConnectionValidation(profileId, \"active\", null);\n\n return {\n profileId,\n sellerId: connection.sellerId,\n accessToken,\n scopes,\n expiresAt,\n };\n } catch (error) {\n const message = sanitizeMercadoLibreText(error instanceof Error ? error.message : error);\n\n const status = message.toLowerCase().includes(\"expired\") ? \"expired\" : \"error\";\n await markMercadoLibreConnectionValidation(profileId, status, message);\n\n throw new Error(message);\n }\n}\n"],
4
+ "sourcesContent": ["import { assertActiveProfile } from \"./profile-store.js\";\r\nimport { decryptSecret, encryptSecret } from \"./vtex-crypto.js\";\r\nimport {\r\n getMercadoLibreConnectionRow,\r\n markMercadoLibreConnectionValidation,\r\n updateMercadoLibreConnectionTokens,\r\n} from \"./mercadolibre-profile-store.js\";\r\n\r\nconst TOKEN_URL = \"https://api.mercadolibre.com/oauth/token\";\r\nconst REFRESH_SKEW_MS = 5 * 60 * 1000;\r\n\r\nexport interface MercadoLibreAccessContext {\r\n profileId: string;\r\n sellerId: string;\r\n accessToken: string;\r\n scopes: string[];\r\n expiresAt: string | null;\r\n}\r\n\r\ninterface MercadoLibreTokenSet {\r\n accessToken: string;\r\n refreshToken: string | null;\r\n userId: string;\r\n expiresAt: string | null;\r\n scopes: string[];\r\n}\r\n\r\nfunction env(name: string): string {\r\n const value = process.env[name]?.trim();\r\n if (!value) {\r\n throw new Error(`Missing ${name}`);\r\n }\r\n\r\n return value;\r\n}\r\n\r\nfunction sanitizeMercadoLibreText(value: unknown): string {\r\n const raw = typeof value === \"string\" ? value : String(value ?? \"MercadoLibre request failed\");\r\n return raw\r\n .replace(/access_token[=:][^\\s&]+/gi, \"access_token=[redacted]\")\r\n .replace(/refresh_token[=:][^\\s&]+/gi, \"refresh_token=[redacted]\")\r\n .replace(/client_secret[=:][^\\s&]+/gi, \"client_secret=[redacted]\")\r\n .replace(/code[=:][^\\s&]+/gi, \"code=[redacted]\")\r\n .replace(/Bearer\\s+[A-Za-z0-9._-]+/gi, \"Bearer [redacted]\")\r\n .slice(0, 220);\r\n}\r\n\r\nfunction parseScopes(scope: unknown): string[] {\r\n if (typeof scope !== \"string\") {\r\n return [];\r\n }\r\n\r\n return scope\r\n .split(/[ ,]+/)\r\n .map((item) => item.trim())\r\n .filter(Boolean);\r\n}\r\n\r\nfunction expiresAtFrom(expiresIn: unknown): string | null {\r\n if (typeof expiresIn !== \"number\" || !Number.isFinite(expiresIn) || expiresIn <= 0) {\r\n return null;\r\n }\r\n\r\n return new Date(Date.now() + expiresIn * 1000).toISOString();\r\n}\r\n\r\nfunction shouldRefreshMercadoLibreToken(expiresAt: Date | null): boolean {\r\n if (!expiresAt) {\r\n return false;\r\n }\r\n\r\n return expiresAt.getTime() <= Date.now() + REFRESH_SKEW_MS;\r\n}\r\n\r\nasync function readRefreshError(response: Response): Promise<Error> {\r\n const body = await response.json().catch(() => null);\r\n if (body && typeof body === \"object\") {\r\n const candidate = body as {\r\n error?: unknown;\r\n message?: unknown;\r\n error_description?: unknown;\r\n };\r\n const code = typeof candidate.error === \"string\" ? candidate.error : response.statusText;\r\n const message =\r\n sanitizeMercadoLibreText(candidate.message) ||\r\n sanitizeMercadoLibreText(candidate.error_description) ||\r\n sanitizeMercadoLibreText(code);\r\n return new Error(`MercadoLibre token refresh failed (${response.status}): ${message}`);\r\n }\r\n\r\n return new Error(\r\n `MercadoLibre token refresh failed (${response.status}): ${sanitizeMercadoLibreText(response.statusText)}`\r\n );\r\n}\r\n\r\nasync function refreshMercadoLibreToken(refreshToken: string): Promise<MercadoLibreTokenSet> {\r\n const response = await fetch(TOKEN_URL, {\r\n method: \"POST\",\r\n headers: {\r\n Accept: \"application/json\",\r\n \"Content-Type\": \"application/x-www-form-urlencoded\",\r\n },\r\n body: new URLSearchParams({\r\n grant_type: \"refresh_token\",\r\n client_id: env(\"MERCADOLIBRE_CLIENT_ID\"),\r\n client_secret: env(\"MERCADOLIBRE_CLIENT_SECRET\"),\r\n refresh_token: refreshToken,\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n throw await readRefreshError(response);\r\n }\r\n\r\n const payload = (await response.json()) as Record<string, unknown>;\r\n const accessToken = payload.access_token;\r\n const userId = payload.user_id;\r\n\r\n if (typeof accessToken !== \"string\" || !accessToken) {\r\n throw new Error(\"MercadoLibre token refresh response missing access_token\");\r\n }\r\n\r\n if ((typeof userId !== \"string\" && typeof userId !== \"number\") || String(userId).length === 0) {\r\n throw new Error(\"MercadoLibre token refresh response missing user_id\");\r\n }\r\n\r\n return {\r\n accessToken,\r\n refreshToken:\r\n typeof payload.refresh_token === \"string\" && payload.refresh_token\r\n ? payload.refresh_token\r\n : refreshToken,\r\n userId: String(userId),\r\n expiresAt: expiresAtFrom(payload.expires_in),\r\n scopes: parseScopes(payload.scope),\r\n };\r\n}\r\n\r\nexport async function getMercadoLibreAccessForProfile(\r\n profileId: string\r\n): Promise<MercadoLibreAccessContext> {\r\n await assertActiveProfile(profileId);\r\n\r\n const connection = await getMercadoLibreConnectionRow(profileId);\r\n if (!connection) {\r\n throw new Error(`MercadoLibre connection not found for profileId \"${profileId}\"`);\r\n }\r\n\r\n if (connection.status === \"disabled\") {\r\n throw new Error(`MercadoLibre connection for profileId \"${profileId}\" is disabled`);\r\n }\r\n\r\n try {\r\n let accessToken = decryptSecret(connection.encryptedAccessToken);\r\n let refreshToken = connection.encryptedRefreshToken\r\n ? decryptSecret(connection.encryptedRefreshToken)\r\n : null;\r\n let scopes = connection.scopes;\r\n let expiresAt = connection.expiresAt ? connection.expiresAt.toISOString() : null;\r\n\r\n if (shouldRefreshMercadoLibreToken(connection.expiresAt)) {\r\n if (!refreshToken) {\r\n await markMercadoLibreConnectionValidation(profileId, \"expired\", \"Refresh token is missing\");\r\n throw new Error(\r\n `MercadoLibre connection for profileId \"${profileId}\" is expired and has no refresh token`\r\n );\r\n }\r\n\r\n const refreshedTokens = await refreshMercadoLibreToken(refreshToken);\r\n if (refreshedTokens.userId !== connection.sellerId) {\r\n await markMercadoLibreConnectionValidation(\r\n profileId,\r\n \"error\",\r\n \"Refreshed MercadoLibre seller_id does not match stored seller_id\"\r\n );\r\n throw new Error(\r\n `MercadoLibre refresh returned seller_id \"${refreshedTokens.userId}\" but expected \"${connection.sellerId}\"`\r\n );\r\n }\r\n\r\n const encryptedAccessToken = encryptSecret(refreshedTokens.accessToken);\r\n const encryptedRefreshToken = refreshedTokens.refreshToken\r\n ? encryptSecret(refreshedTokens.refreshToken)\r\n : null;\r\n const nextExpiresAt = refreshedTokens.expiresAt ? new Date(refreshedTokens.expiresAt) : null;\r\n\r\n await updateMercadoLibreConnectionTokens({\r\n profileId,\r\n sellerId: connection.sellerId,\r\n encryptedAccessToken,\r\n encryptedRefreshToken,\r\n expiresAt: nextExpiresAt,\r\n scopes: refreshedTokens.scopes,\r\n status: \"active\",\r\n lastError: null,\r\n });\r\n\r\n accessToken = refreshedTokens.accessToken;\r\n refreshToken = refreshedTokens.refreshToken;\r\n scopes = refreshedTokens.scopes;\r\n expiresAt = refreshedTokens.expiresAt;\r\n }\r\n\r\n await markMercadoLibreConnectionValidation(profileId, \"active\", null);\r\n\r\n return {\r\n profileId,\r\n sellerId: connection.sellerId,\r\n accessToken,\r\n scopes,\r\n expiresAt,\r\n };\r\n } catch (error) {\r\n const message = sanitizeMercadoLibreText(error instanceof Error ? error.message : error);\r\n\r\n const status = message.toLowerCase().includes(\"expired\") ? \"expired\" : \"error\";\r\n await markMercadoLibreConnectionValidation(profileId, status, message);\r\n\r\n throw new Error(message);\r\n }\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/meta.ts"],
4
- "sourcesContent": ["import { createHmac } from \"node:crypto\";\n\nfunction requireEnv(name: \"META_APP_ID\" | \"META_APP_SECRET\" | \"META_ACCESS_TOKEN\"): string {\n const value = process.env[name]?.trim();\n if (!value) {\n throw new Error(`Missing required environment variable: ${name}`);\n }\n\n return value;\n}\n\nexport interface MetaConfig {\n appId: string;\n appSecret: string;\n accessToken: string;\n apiVersion: string;\n baseUrl: string;\n}\n\nexport function getMetaConfig(): MetaConfig {\n const appId = requireEnv(\"META_APP_ID\");\n const appSecret = requireEnv(\"META_APP_SECRET\");\n const accessToken = requireEnv(\"META_ACCESS_TOKEN\");\n const apiVersion = process.env.META_API_VERSION?.trim() || \"v25.0\";\n\n return {\n appId,\n appSecret,\n accessToken,\n apiVersion,\n baseUrl: `https://graph.facebook.com/${apiVersion}`,\n };\n}\n\nexport function createMetaAppSecretProof(accessToken: string, appSecret: string): string {\n return createHmac(\"sha256\", appSecret).update(accessToken).digest(\"hex\");\n}\n"],
4
+ "sourcesContent": ["import { createHmac } from \"node:crypto\";\r\n\r\nfunction requireEnv(name: \"META_APP_ID\" | \"META_APP_SECRET\" | \"META_ACCESS_TOKEN\"): string {\r\n const value = process.env[name]?.trim();\r\n if (!value) {\r\n throw new Error(`Missing required environment variable: ${name}`);\r\n }\r\n\r\n return value;\r\n}\r\n\r\nexport interface MetaConfig {\r\n appId: string;\r\n appSecret: string;\r\n accessToken: string;\r\n apiVersion: string;\r\n baseUrl: string;\r\n}\r\n\r\nexport function getMetaConfig(): MetaConfig {\r\n const appId = requireEnv(\"META_APP_ID\");\r\n const appSecret = requireEnv(\"META_APP_SECRET\");\r\n const accessToken = requireEnv(\"META_ACCESS_TOKEN\");\r\n const apiVersion = process.env.META_API_VERSION?.trim() || \"v25.0\";\r\n\r\n return {\r\n appId,\r\n appSecret,\r\n accessToken,\r\n apiVersion,\r\n baseUrl: `https://graph.facebook.com/${apiVersion}`,\r\n };\r\n}\r\n\r\nexport function createMetaAppSecretProof(accessToken: string, appSecret: string): string {\r\n return createHmac(\"sha256\", appSecret).update(accessToken).digest(\"hex\");\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,kBAAkB;AAE3B,SAAS,WAAW,MAAuE;AACzF,QAAM,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK;AACtC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAAA,EAClE;AAEA,SAAO;AACT;AAUO,SAAS,gBAA4B;AAC1C,QAAM,QAAQ,WAAW,aAAa;AACtC,QAAM,YAAY,WAAW,iBAAiB;AAC9C,QAAM,cAAc,WAAW,mBAAmB;AAClD,QAAM,aAAa,QAAQ,IAAI,kBAAkB,KAAK,KAAK;AAE3D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,8BAA8B,UAAU;AAAA,EACnD;AACF;AAEO,SAAS,yBAAyB,aAAqB,WAA2B;AACvF,SAAO,WAAW,UAAU,SAAS,EAAE,OAAO,WAAW,EAAE,OAAO,KAAK;AACzE;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/profile-store.ts"],
4
- "sourcesContent": ["import { getDb } from \"../db/client.js\";\n\nexport interface ProfileRecord {\n id: string;\n name: string;\n isActive: boolean;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface DeletedProfileRecord {\n id: string;\n name: string;\n isActive: boolean;\n deletedAt: Date;\n}\n\ninterface ProfileRow {\n id: string;\n name: string;\n is_active: boolean;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction mapProfileRow(row: ProfileRow): ProfileRecord {\n return {\n id: row.id,\n name: row.name,\n isActive: row.is_active,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport async function getProfile(profileId: string): Promise<ProfileRecord | null> {\n const result = await getDb().query<ProfileRow>(\n `\n select id, name, is_active, created_at, updated_at\n from profiles\n where id = $1\n `,\n [profileId]\n );\n\n const row = result.rows[0];\n return row ? mapProfileRow(row) : null;\n}\n\nexport async function upsertProfile(input: {\n id: string;\n name: string;\n isActive: boolean;\n}): Promise<ProfileRecord> {\n const result = await getDb().query<ProfileRow>(\n `\n insert into profiles (id, name, is_active)\n values ($1, $2, $3)\n on conflict (id) do update\n set\n name = excluded.name,\n is_active = excluded.is_active,\n updated_at = now()\n returning id, name, is_active, created_at, updated_at\n `,\n [input.id, input.name, input.isActive]\n );\n\n return mapProfileRow(result.rows[0]);\n}\n\nexport async function listProfiles(options: { activeOnly?: boolean } = {}): Promise<ProfileRecord[]> {\n const result = await getDb().query<ProfileRow>(\n `\n select id, name, is_active, created_at, updated_at\n from profiles\n where ($1::boolean = false or is_active = true)\n order by id asc\n `,\n [options.activeOnly ?? false]\n );\n\n return result.rows.map(mapProfileRow);\n}\n\nexport async function deleteProfile(profileId: string): Promise<DeletedProfileRecord | null> {\n const result = await getDb().query<ProfileRow>(\n `\n delete from profiles\n where id = $1\n returning id, name, is_active, created_at, updated_at\n `,\n [profileId]\n );\n\n const row = result.rows[0];\n if (!row) {\n return null;\n }\n\n return {\n id: row.id,\n name: row.name,\n isActive: row.is_active,\n deletedAt: new Date(),\n };\n}\n\nexport async function assertActiveProfile(profileId: string): Promise<ProfileRecord> {\n const profile = await getProfile(profileId);\n\n if (!profile) {\n throw new Error(`Profile not found: \"${profileId}\"`);\n }\n\n if (!profile.isActive) {\n throw new Error(`Profile \"${profileId}\" is disabled`);\n }\n\n return profile;\n}\n"],
4
+ "sourcesContent": ["import { getDb } from \"../db/client.js\";\r\n\r\nexport interface ProfileRecord {\r\n id: string;\r\n name: string;\r\n isActive: boolean;\r\n createdAt: Date;\r\n updatedAt: Date;\r\n}\r\n\r\nexport interface DeletedProfileRecord {\r\n id: string;\r\n name: string;\r\n isActive: boolean;\r\n deletedAt: Date;\r\n}\r\n\r\ninterface ProfileRow {\r\n id: string;\r\n name: string;\r\n is_active: boolean;\r\n created_at: Date;\r\n updated_at: Date;\r\n}\r\n\r\nfunction mapProfileRow(row: ProfileRow): ProfileRecord {\r\n return {\r\n id: row.id,\r\n name: row.name,\r\n isActive: row.is_active,\r\n createdAt: row.created_at,\r\n updatedAt: row.updated_at,\r\n };\r\n}\r\n\r\nexport async function getProfile(profileId: string): Promise<ProfileRecord | null> {\r\n const result = await getDb().query<ProfileRow>(\r\n `\r\n select id, name, is_active, created_at, updated_at\r\n from profiles\r\n where id = $1\r\n `,\r\n [profileId]\r\n );\r\n\r\n const row = result.rows[0];\r\n return row ? mapProfileRow(row) : null;\r\n}\r\n\r\nexport async function upsertProfile(input: {\r\n id: string;\r\n name: string;\r\n isActive: boolean;\r\n}): Promise<ProfileRecord> {\r\n const result = await getDb().query<ProfileRow>(\r\n `\r\n insert into profiles (id, name, is_active)\r\n values ($1, $2, $3)\r\n on conflict (id) do update\r\n set\r\n name = excluded.name,\r\n is_active = excluded.is_active,\r\n updated_at = now()\r\n returning id, name, is_active, created_at, updated_at\r\n `,\r\n [input.id, input.name, input.isActive]\r\n );\r\n\r\n return mapProfileRow(result.rows[0]);\r\n}\r\n\r\nexport async function listProfiles(options: { activeOnly?: boolean } = {}): Promise<ProfileRecord[]> {\r\n const result = await getDb().query<ProfileRow>(\r\n `\r\n select id, name, is_active, created_at, updated_at\r\n from profiles\r\n where ($1::boolean = false or is_active = true)\r\n order by id asc\r\n `,\r\n [options.activeOnly ?? false]\r\n );\r\n\r\n return result.rows.map(mapProfileRow);\r\n}\r\n\r\nexport async function deleteProfile(profileId: string): Promise<DeletedProfileRecord | null> {\r\n const result = await getDb().query<ProfileRow>(\r\n `\r\n delete from profiles\r\n where id = $1\r\n returning id, name, is_active, created_at, updated_at\r\n `,\r\n [profileId]\r\n );\r\n\r\n const row = result.rows[0];\r\n if (!row) {\r\n return null;\r\n }\r\n\r\n return {\r\n id: row.id,\r\n name: row.name,\r\n isActive: row.is_active,\r\n deletedAt: new Date(),\r\n };\r\n}\r\n\r\nexport async function assertActiveProfile(profileId: string): Promise<ProfileRecord> {\r\n const profile = await getProfile(profileId);\r\n\r\n if (!profile) {\r\n throw new Error(`Profile not found: \"${profileId}\"`);\r\n }\r\n\r\n if (!profile.isActive) {\r\n throw new Error(`Profile \"${profileId}\" is disabled`);\r\n }\r\n\r\n return profile;\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,aAAa;AAyBtB,SAAS,cAAc,KAAgC;AACrD,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,eAAsB,WAAW,WAAkD;AACjF,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,MAAM,OAAO,KAAK,CAAC;AACzB,SAAO,MAAM,cAAc,GAAG,IAAI;AACpC;AAEA,eAAsB,cAAc,OAIT;AACzB,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,CAAC,MAAM,IAAI,MAAM,MAAM,MAAM,QAAQ;AAAA,EACvC;AAEA,SAAO,cAAc,OAAO,KAAK,CAAC,CAAC;AACrC;AAEA,eAAsB,aAAa,UAAoC,CAAC,GAA6B;AACnG,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,CAAC,QAAQ,cAAc,KAAK;AAAA,EAC9B;AAEA,SAAO,OAAO,KAAK,IAAI,aAAa;AACtC;AAEA,eAAsB,cAAc,WAAyD;AAC3F,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,MAAM,OAAO,KAAK,CAAC;AACzB,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,UAAU,IAAI;AAAA,IACd,WAAW,oBAAI,KAAK;AAAA,EACtB;AACF;AAEA,eAAsB,oBAAoB,WAA2C;AACnF,QAAM,UAAU,MAAM,WAAW,SAAS;AAE1C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,uBAAuB,SAAS,GAAG;AAAA,EACrD;AAEA,MAAI,CAAC,QAAQ,UAAU;AACrB,UAAM,IAAI,MAAM,YAAY,SAAS,eAAe;AAAA,EACtD;AAEA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/vtex-crypto.ts"],
4
- "sourcesContent": ["import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst IV_LENGTH = 12;\n\nfunction getEncryptionKey(): Buffer {\n const rawValue = process.env.DB_ENCRYPTION_KEY?.trim();\n if (!rawValue) {\n throw new Error(\n \"Missing DB_ENCRYPTION_KEY. Configure it before encrypting or decrypting persisted secrets.\"\n );\n }\n\n const buffer = Buffer.from(rawValue, \"hex\");\n if (buffer.length !== 32) {\n throw new Error(\n \"Invalid DB_ENCRYPTION_KEY. Expected a 32-byte key encoded as 64 hex characters.\"\n );\n }\n\n return buffer;\n}\n\nexport function encryptSecret(plainText: string): string {\n const iv = randomBytes(IV_LENGTH);\n const cipher = createCipheriv(ALGORITHM, getEncryptionKey(), iv);\n const encrypted = Buffer.concat([cipher.update(plainText, \"utf8\"), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n return `${iv.toString(\"hex\")}:${authTag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\n}\n\nexport function decryptSecret(payload: string): string {\n const [ivHex, authTagHex, encryptedHex] = payload.split(\":\");\n if (!ivHex || !authTagHex || !encryptedHex) {\n throw new Error(\"Invalid encrypted secret payload\");\n }\n\n const decipher = createDecipheriv(ALGORITHM, getEncryptionKey(), Buffer.from(ivHex, \"hex\"));\n decipher.setAuthTag(Buffer.from(authTagHex, \"hex\"));\n\n const decrypted = Buffer.concat([\n decipher.update(Buffer.from(encryptedHex, \"hex\")),\n decipher.final(),\n ]);\n\n return decrypted.toString(\"utf8\");\n}\n"],
4
+ "sourcesContent": ["import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\r\n\r\nconst ALGORITHM = \"aes-256-gcm\";\r\nconst IV_LENGTH = 12;\r\n\r\nfunction getEncryptionKey(): Buffer {\r\n const rawValue = process.env.DB_ENCRYPTION_KEY?.trim();\r\n if (!rawValue) {\r\n throw new Error(\r\n \"Missing DB_ENCRYPTION_KEY. Configure it before encrypting or decrypting persisted secrets.\"\r\n );\r\n }\r\n\r\n const buffer = Buffer.from(rawValue, \"hex\");\r\n if (buffer.length !== 32) {\r\n throw new Error(\r\n \"Invalid DB_ENCRYPTION_KEY. Expected a 32-byte key encoded as 64 hex characters.\"\r\n );\r\n }\r\n\r\n return buffer;\r\n}\r\n\r\nexport function encryptSecret(plainText: string): string {\r\n const iv = randomBytes(IV_LENGTH);\r\n const cipher = createCipheriv(ALGORITHM, getEncryptionKey(), iv);\r\n const encrypted = Buffer.concat([cipher.update(plainText, \"utf8\"), cipher.final()]);\r\n const authTag = cipher.getAuthTag();\r\n\r\n return `${iv.toString(\"hex\")}:${authTag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\r\n}\r\n\r\nexport function decryptSecret(payload: string): string {\r\n const [ivHex, authTagHex, encryptedHex] = payload.split(\":\");\r\n if (!ivHex || !authTagHex || !encryptedHex) {\r\n throw new Error(\"Invalid encrypted secret payload\");\r\n }\r\n\r\n const decipher = createDecipheriv(ALGORITHM, getEncryptionKey(), Buffer.from(ivHex, \"hex\"));\r\n decipher.setAuthTag(Buffer.from(authTagHex, \"hex\"));\r\n\r\n const decrypted = Buffer.concat([\r\n decipher.update(Buffer.from(encryptedHex, \"hex\")),\r\n decipher.final(),\r\n ]);\r\n\r\n return decrypted.toString(\"utf8\");\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,gBAAgB,kBAAkB,mBAAmB;AAE9D,MAAM,YAAY;AAClB,MAAM,YAAY;AAElB,SAAS,mBAA2B;AAClC,QAAM,WAAW,QAAQ,IAAI,mBAAmB,KAAK;AACrD,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,OAAO,KAAK,UAAU,KAAK;AAC1C,MAAI,OAAO,WAAW,IAAI;AACxB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,cAAc,WAA2B;AACvD,QAAM,KAAK,YAAY,SAAS;AAChC,QAAM,SAAS,eAAe,WAAW,iBAAiB,GAAG,EAAE;AAC/D,QAAM,YAAY,OAAO,OAAO,CAAC,OAAO,OAAO,WAAW,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;AAClF,QAAM,UAAU,OAAO,WAAW;AAElC,SAAO,GAAG,GAAG,SAAS,KAAK,CAAC,IAAI,QAAQ,SAAS,KAAK,CAAC,IAAI,UAAU,SAAS,KAAK,CAAC;AACtF;AAEO,SAAS,cAAc,SAAyB;AACrD,QAAM,CAAC,OAAO,YAAY,YAAY,IAAI,QAAQ,MAAM,GAAG;AAC3D,MAAI,CAAC,SAAS,CAAC,cAAc,CAAC,cAAc;AAC1C,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAEA,QAAM,WAAW,iBAAiB,WAAW,iBAAiB,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC;AAC1F,WAAS,WAAW,OAAO,KAAK,YAAY,KAAK,CAAC;AAElD,QAAM,YAAY,OAAO,OAAO;AAAA,IAC9B,SAAS,OAAO,OAAO,KAAK,cAAc,KAAK,CAAC;AAAA,IAChD,SAAS,MAAM;AAAA,EACjB,CAAC;AAED,SAAO,UAAU,SAAS,MAAM;AAClC;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/vtex-profile-store.ts"],
4
- "sourcesContent": ["import { getDb } from \"../db/client.js\";\n\nexport type VtexConnectionStatus = \"pending\" | \"valid\" | \"invalid\" | \"disabled\";\n\nexport interface VtexConnectionRecord {\n profileId: string;\n profileName?: string;\n accountName: string;\n apiKeyEncrypted: string;\n apiTokenEncrypted: string;\n isActive: boolean;\n status: VtexConnectionStatus;\n lastValidatedAt: Date | null;\n lastError: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface VtexConnectionRow {\n profile_id: string;\n profile_name?: string;\n account_name: string;\n api_key_encrypted: string;\n api_token_encrypted: string;\n is_active: boolean;\n status: VtexConnectionStatus;\n last_validated_at: Date | null;\n last_error: string | null;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction mapVtexConnectionRow(row: VtexConnectionRow): VtexConnectionRecord {\n return {\n profileId: row.profile_id,\n profileName: row.profile_name,\n accountName: row.account_name,\n apiKeyEncrypted: row.api_key_encrypted,\n apiTokenEncrypted: row.api_token_encrypted,\n isActive: row.is_active,\n status: row.status,\n lastValidatedAt: row.last_validated_at,\n lastError: row.last_error,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport async function getVtexConnectionRow(\n profileId: string\n): Promise<VtexConnectionRecord | null> {\n const result = await getDb().query<VtexConnectionRow>(\n `\n select\n pvc.profile_id,\n p.name as profile_name,\n pvc.account_name,\n pvc.api_key_encrypted,\n pvc.api_token_encrypted,\n pvc.is_active,\n pvc.status,\n pvc.last_validated_at,\n pvc.last_error,\n pvc.created_at,\n pvc.updated_at\n from profile_vtex_connections pvc\n inner join profiles p on p.id = pvc.profile_id\n where pvc.profile_id = $1\n `,\n [profileId]\n );\n\n const row = result.rows[0];\n return row ? mapVtexConnectionRow(row) : null;\n}\n\nexport async function upsertVtexConnection(input: {\n profileId: string;\n accountName: string;\n apiKeyEncrypted: string;\n apiTokenEncrypted: string;\n isActive: boolean;\n status: VtexConnectionStatus;\n lastValidatedAt?: Date | null;\n lastError?: string | null;\n}): Promise<VtexConnectionRecord> {\n const result = await getDb().query<VtexConnectionRow>(\n `\n insert into profile_vtex_connections (\n profile_id,\n account_name,\n api_key_encrypted,\n api_token_encrypted,\n is_active,\n status,\n last_validated_at,\n last_error\n )\n values ($1, $2, $3, $4, $5, $6, $7, $8)\n on conflict (profile_id) do update\n set\n account_name = excluded.account_name,\n api_key_encrypted = excluded.api_key_encrypted,\n api_token_encrypted = excluded.api_token_encrypted,\n is_active = excluded.is_active,\n status = excluded.status,\n last_validated_at = excluded.last_validated_at,\n last_error = excluded.last_error,\n updated_at = now()\n returning\n profile_id,\n account_name,\n api_key_encrypted,\n api_token_encrypted,\n is_active,\n status,\n last_validated_at,\n last_error,\n created_at,\n updated_at\n `,\n [\n input.profileId,\n input.accountName,\n input.apiKeyEncrypted,\n input.apiTokenEncrypted,\n input.isActive,\n input.status,\n input.lastValidatedAt ?? null,\n input.lastError ?? null,\n ]\n );\n\n return mapVtexConnectionRow(result.rows[0]);\n}\n\nexport async function markVtexConnectionValidation(\n profileId: string,\n status: VtexConnectionStatus,\n lastError?: string | null\n): Promise<void> {\n await getDb().query(\n `\n update profile_vtex_connections\n set\n status = $2,\n last_validated_at = now(),\n last_error = $3,\n updated_at = now()\n where profile_id = $1\n `,\n [profileId, status, lastError ?? null]\n );\n}\n\nexport async function listVtexConnections(): Promise<VtexConnectionRecord[]> {\n const result = await getDb().query<VtexConnectionRow>(\n `\n select\n pvc.profile_id,\n p.name as profile_name,\n pvc.account_name,\n pvc.api_key_encrypted,\n pvc.api_token_encrypted,\n pvc.is_active,\n pvc.status,\n pvc.last_validated_at,\n pvc.last_error,\n pvc.created_at,\n pvc.updated_at\n from profile_vtex_connections pvc\n inner join profiles p on p.id = pvc.profile_id\n order by pvc.profile_id asc\n `\n );\n\n return result.rows.map(mapVtexConnectionRow);\n}\n"],
4
+ "sourcesContent": ["import { getDb } from \"../db/client.js\";\r\n\r\nexport type VtexConnectionStatus = \"pending\" | \"valid\" | \"invalid\" | \"disabled\";\r\n\r\nexport interface VtexConnectionRecord {\r\n profileId: string;\r\n profileName?: string;\r\n accountName: string;\r\n apiKeyEncrypted: string;\r\n apiTokenEncrypted: string;\r\n isActive: boolean;\r\n status: VtexConnectionStatus;\r\n lastValidatedAt: Date | null;\r\n lastError: string | null;\r\n createdAt: Date;\r\n updatedAt: Date;\r\n}\r\n\r\ninterface VtexConnectionRow {\r\n profile_id: string;\r\n profile_name?: string;\r\n account_name: string;\r\n api_key_encrypted: string;\r\n api_token_encrypted: string;\r\n is_active: boolean;\r\n status: VtexConnectionStatus;\r\n last_validated_at: Date | null;\r\n last_error: string | null;\r\n created_at: Date;\r\n updated_at: Date;\r\n}\r\n\r\nfunction mapVtexConnectionRow(row: VtexConnectionRow): VtexConnectionRecord {\r\n return {\r\n profileId: row.profile_id,\r\n profileName: row.profile_name,\r\n accountName: row.account_name,\r\n apiKeyEncrypted: row.api_key_encrypted,\r\n apiTokenEncrypted: row.api_token_encrypted,\r\n isActive: row.is_active,\r\n status: row.status,\r\n lastValidatedAt: row.last_validated_at,\r\n lastError: row.last_error,\r\n createdAt: row.created_at,\r\n updatedAt: row.updated_at,\r\n };\r\n}\r\n\r\nexport async function getVtexConnectionRow(\r\n profileId: string\r\n): Promise<VtexConnectionRecord | null> {\r\n const result = await getDb().query<VtexConnectionRow>(\r\n `\r\n select\r\n pvc.profile_id,\r\n p.name as profile_name,\r\n pvc.account_name,\r\n pvc.api_key_encrypted,\r\n pvc.api_token_encrypted,\r\n pvc.is_active,\r\n pvc.status,\r\n pvc.last_validated_at,\r\n pvc.last_error,\r\n pvc.created_at,\r\n pvc.updated_at\r\n from profile_vtex_connections pvc\r\n inner join profiles p on p.id = pvc.profile_id\r\n where pvc.profile_id = $1\r\n `,\r\n [profileId]\r\n );\r\n\r\n const row = result.rows[0];\r\n return row ? mapVtexConnectionRow(row) : null;\r\n}\r\n\r\nexport async function upsertVtexConnection(input: {\r\n profileId: string;\r\n accountName: string;\r\n apiKeyEncrypted: string;\r\n apiTokenEncrypted: string;\r\n isActive: boolean;\r\n status: VtexConnectionStatus;\r\n lastValidatedAt?: Date | null;\r\n lastError?: string | null;\r\n}): Promise<VtexConnectionRecord> {\r\n const result = await getDb().query<VtexConnectionRow>(\r\n `\r\n insert into profile_vtex_connections (\r\n profile_id,\r\n account_name,\r\n api_key_encrypted,\r\n api_token_encrypted,\r\n is_active,\r\n status,\r\n last_validated_at,\r\n last_error\r\n )\r\n values ($1, $2, $3, $4, $5, $6, $7, $8)\r\n on conflict (profile_id) do update\r\n set\r\n account_name = excluded.account_name,\r\n api_key_encrypted = excluded.api_key_encrypted,\r\n api_token_encrypted = excluded.api_token_encrypted,\r\n is_active = excluded.is_active,\r\n status = excluded.status,\r\n last_validated_at = excluded.last_validated_at,\r\n last_error = excluded.last_error,\r\n updated_at = now()\r\n returning\r\n profile_id,\r\n account_name,\r\n api_key_encrypted,\r\n api_token_encrypted,\r\n is_active,\r\n status,\r\n last_validated_at,\r\n last_error,\r\n created_at,\r\n updated_at\r\n `,\r\n [\r\n input.profileId,\r\n input.accountName,\r\n input.apiKeyEncrypted,\r\n input.apiTokenEncrypted,\r\n input.isActive,\r\n input.status,\r\n input.lastValidatedAt ?? null,\r\n input.lastError ?? null,\r\n ]\r\n );\r\n\r\n return mapVtexConnectionRow(result.rows[0]);\r\n}\r\n\r\nexport async function markVtexConnectionValidation(\r\n profileId: string,\r\n status: VtexConnectionStatus,\r\n lastError?: string | null\r\n): Promise<void> {\r\n await getDb().query(\r\n `\r\n update profile_vtex_connections\r\n set\r\n status = $2,\r\n last_validated_at = now(),\r\n last_error = $3,\r\n updated_at = now()\r\n where profile_id = $1\r\n `,\r\n [profileId, status, lastError ?? null]\r\n );\r\n}\r\n\r\nexport async function listVtexConnections(): Promise<VtexConnectionRecord[]> {\r\n const result = await getDb().query<VtexConnectionRow>(\r\n `\r\n select\r\n pvc.profile_id,\r\n p.name as profile_name,\r\n pvc.account_name,\r\n pvc.api_key_encrypted,\r\n pvc.api_token_encrypted,\r\n pvc.is_active,\r\n pvc.status,\r\n pvc.last_validated_at,\r\n pvc.last_error,\r\n pvc.created_at,\r\n pvc.updated_at\r\n from profile_vtex_connections pvc\r\n inner join profiles p on p.id = pvc.profile_id\r\n order by pvc.profile_id asc\r\n `\r\n );\r\n\r\n return result.rows.map(mapVtexConnectionRow);\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,aAAa;AAgCtB,SAAS,qBAAqB,KAA8C;AAC1E,SAAO;AAAA,IACL,WAAW,IAAI;AAAA,IACf,aAAa,IAAI;AAAA,IACjB,aAAa,IAAI;AAAA,IACjB,iBAAiB,IAAI;AAAA,IACrB,mBAAmB,IAAI;AAAA,IACvB,UAAU,IAAI;AAAA,IACd,QAAQ,IAAI;AAAA,IACZ,iBAAiB,IAAI;AAAA,IACrB,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,eAAsB,qBACpB,WACsC;AACtC,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAiBA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,MAAM,OAAO,KAAK,CAAC;AACzB,SAAO,MAAM,qBAAqB,GAAG,IAAI;AAC3C;AAEA,eAAsB,qBAAqB,OAST;AAChC,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAkCA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,mBAAmB;AAAA,MACzB,MAAM,aAAa;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,qBAAqB,OAAO,KAAK,CAAC,CAAC;AAC5C;AAEA,eAAsB,6BACpB,WACA,QACA,WACe;AACf,QAAM,MAAM,EAAE;AAAA,IACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,CAAC,WAAW,QAAQ,aAAa,IAAI;AAAA,EACvC;AACF;AAEA,eAAsB,sBAAuD;AAC3E,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBF;AAEA,SAAO,OAAO,KAAK,IAAI,oBAAoB;AAC7C;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/vtex.ts"],
4
- "sourcesContent": ["import { assertActiveProfile } from \"./profile-store.js\";\nimport { decryptSecret } from \"./vtex-crypto.js\";\nimport { getVtexConnectionRow } from \"./vtex-profile-store.js\";\n\nexport interface VtexConfig {\n accountName: string;\n apiKey: string;\n apiToken: string;\n baseUrl: string;\n pricingBaseUrl: string;\n}\n\nexport function buildVtexUrls(accountName: string) {\n return {\n baseUrl: `https://${accountName}.vtexcommercestable.com.br`,\n pricingBaseUrl: `https://api.vtex.com/${accountName}`,\n };\n}\n\nexport async function getVtexConfigForProfile(profileId: string): Promise<VtexConfig> {\n await assertActiveProfile(profileId);\n\n const connection = await getVtexConnectionRow(profileId);\n if (!connection) {\n throw new Error(`VTEX connection not found for profileId \"${profileId}\"`);\n }\n\n if (!connection.isActive || connection.status === \"disabled\") {\n throw new Error(`VTEX connection for profileId \"${profileId}\" is disabled`);\n }\n\n const urls = buildVtexUrls(connection.accountName);\n\n return {\n accountName: connection.accountName,\n apiKey: decryptSecret(connection.apiKeyEncrypted),\n apiToken: decryptSecret(connection.apiTokenEncrypted),\n baseUrl: urls.baseUrl,\n pricingBaseUrl: urls.pricingBaseUrl,\n };\n}\n"],
4
+ "sourcesContent": ["import { assertActiveProfile } from \"./profile-store.js\";\r\nimport { decryptSecret } from \"./vtex-crypto.js\";\r\nimport { getVtexConnectionRow } from \"./vtex-profile-store.js\";\r\n\r\nexport interface VtexConfig {\r\n accountName: string;\r\n apiKey: string;\r\n apiToken: string;\r\n baseUrl: string;\r\n pricingBaseUrl: string;\r\n}\r\n\r\nexport function buildVtexUrls(accountName: string) {\r\n return {\r\n baseUrl: `https://${accountName}.vtexcommercestable.com.br`,\r\n pricingBaseUrl: `https://api.vtex.com/${accountName}`,\r\n };\r\n}\r\n\r\nexport async function getVtexConfigForProfile(profileId: string): Promise<VtexConfig> {\r\n await assertActiveProfile(profileId);\r\n\r\n const connection = await getVtexConnectionRow(profileId);\r\n if (!connection) {\r\n throw new Error(`VTEX connection not found for profileId \"${profileId}\"`);\r\n }\r\n\r\n if (!connection.isActive || connection.status === \"disabled\") {\r\n throw new Error(`VTEX connection for profileId \"${profileId}\" is disabled`);\r\n }\r\n\r\n const urls = buildVtexUrls(connection.accountName);\r\n\r\n return {\r\n accountName: connection.accountName,\r\n apiKey: decryptSecret(connection.apiKeyEncrypted),\r\n apiToken: decryptSecret(connection.apiTokenEncrypted),\r\n baseUrl: urls.baseUrl,\r\n pricingBaseUrl: urls.pricingBaseUrl,\r\n };\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,2BAA2B;AACpC,SAAS,qBAAqB;AAC9B,SAAS,4BAA4B;AAU9B,SAAS,cAAc,aAAqB;AACjD,SAAO;AAAA,IACL,SAAS,WAAW,WAAW;AAAA,IAC/B,gBAAgB,wBAAwB,WAAW;AAAA,EACrD;AACF;AAEA,eAAsB,wBAAwB,WAAwC;AACpF,QAAM,oBAAoB,SAAS;AAEnC,QAAM,aAAa,MAAM,qBAAqB,SAAS;AACvD,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,4CAA4C,SAAS,GAAG;AAAA,EAC1E;AAEA,MAAI,CAAC,WAAW,YAAY,WAAW,WAAW,YAAY;AAC5D,UAAM,IAAI,MAAM,kCAAkC,SAAS,eAAe;AAAA,EAC5E;AAEA,QAAM,OAAO,cAAc,WAAW,WAAW;AAEjD,SAAO;AAAA,IACL,aAAa,WAAW;AAAA,IACxB,QAAQ,cAAc,WAAW,eAAe;AAAA,IAChD,UAAU,cAAc,WAAW,iBAAiB;AAAA,IACpD,SAAS,KAAK;AAAA,IACd,gBAAgB,KAAK;AAAA,EACvB;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/db/client.ts"],
4
- "sourcesContent": ["import { Pool } from \"pg\";\n\nfunction requireDatabaseUrl(): string {\n const value = process.env.DATABASE_URL?.trim();\n if (!value) {\n throw new Error(\n \"Missing DATABASE_URL. Configure it in your environment before using the database client.\"\n );\n }\n\n return value;\n}\n\nlet pool: Pool | null = null;\n\nexport function getDb(): Pool {\n if (!pool) {\n pool = new Pool({\n connectionString: requireDatabaseUrl(),\n });\n }\n\n return pool;\n}\n\nexport function getDatabaseUrl(): string | undefined {\n const value = process.env.DATABASE_URL?.trim();\n return value ? value : undefined;\n}\n\nexport async function testDatabaseConnection() {\n const result = await getDb().query<{\n current_database: string;\n current_user: string;\n now: Date;\n }>(\n `select current_database(), current_user, now()`\n );\n\n return result.rows[0];\n}\n\nexport async function checkTableExists(tableName: string): Promise<boolean> {\n const result = await getDb().query<{ exists: boolean }>(\n `\n select exists (\n select 1\n from information_schema.tables\n where table_schema = 'public'\n and table_name = $1\n ) as exists\n `,\n [tableName]\n );\n\n return Boolean(result.rows[0]?.exists);\n}\n\nexport async function closeDb() {\n if (!pool) {\n return;\n }\n\n await pool.end();\n pool = null;\n}\n"],
4
+ "sourcesContent": ["import { Pool } from \"pg\";\r\n\r\nfunction requireDatabaseUrl(): string {\r\n const value = process.env.DATABASE_URL?.trim();\r\n if (!value) {\r\n throw new Error(\r\n \"Missing DATABASE_URL. Configure it in your environment before using the database client.\"\r\n );\r\n }\r\n\r\n return value;\r\n}\r\n\r\nlet pool: Pool | null = null;\r\n\r\nexport function getDb(): Pool {\r\n if (!pool) {\r\n pool = new Pool({\r\n connectionString: requireDatabaseUrl(),\r\n });\r\n }\r\n\r\n return pool;\r\n}\r\n\r\nexport function getDatabaseUrl(): string | undefined {\r\n const value = process.env.DATABASE_URL?.trim();\r\n return value ? value : undefined;\r\n}\r\n\r\nexport async function testDatabaseConnection() {\r\n const result = await getDb().query<{\r\n current_database: string;\r\n current_user: string;\r\n now: Date;\r\n }>(\r\n `select current_database(), current_user, now()`\r\n );\r\n\r\n return result.rows[0];\r\n}\r\n\r\nexport async function checkTableExists(tableName: string): Promise<boolean> {\r\n const result = await getDb().query<{ exists: boolean }>(\r\n `\r\n select exists (\r\n select 1\r\n from information_schema.tables\r\n where table_schema = 'public'\r\n and table_name = $1\r\n ) as exists\r\n `,\r\n [tableName]\r\n );\r\n\r\n return Boolean(result.rows[0]?.exists);\r\n}\r\n\r\nexport async function closeDb() {\r\n if (!pool) {\r\n return;\r\n }\r\n\r\n await pool.end();\r\n pool = null;\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,YAAY;AAErB,SAAS,qBAA6B;AACpC,QAAM,QAAQ,QAAQ,IAAI,cAAc,KAAK;AAC7C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,IAAI,OAAoB;AAEjB,SAAS,QAAc;AAC5B,MAAI,CAAC,MAAM;AACT,WAAO,IAAI,KAAK;AAAA,MACd,kBAAkB,mBAAmB;AAAA,IACvC,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEO,SAAS,iBAAqC;AACnD,QAAM,QAAQ,QAAQ,IAAI,cAAc,KAAK;AAC7C,SAAO,QAAQ,QAAQ;AACzB;AAEA,eAAsB,yBAAyB;AAC7C,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAK3B;AAAA,EACF;AAEA,SAAO,OAAO,KAAK,CAAC;AACtB;AAEA,eAAsB,iBAAiB,WAAqC;AAC1E,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,CAAC,SAAS;AAAA,EACZ;AAEA,SAAO,QAAQ,OAAO,KAAK,CAAC,GAAG,MAAM;AACvC;AAEA,eAAsB,UAAU;AAC9B,MAAI,CAAC,MAAM;AACT;AAAA,EACF;AAEA,QAAM,KAAK,IAAI;AACf,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/meta/meta-utils.ts"],
4
- "sourcesContent": ["import type { MetaAdAccount, MetaAdInsight } from \"../services/meta/meta-ads.js\";\n\nexport const dateRegex = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nexport interface MetaCurrencyAggregate {\n currency_code: string;\n spend: number;\n purchases: number;\n purchase_value: number;\n impressions: number;\n clicks: number;\n reach: number;\n ctr_percent: number;\n cpc: number;\n cpm: number;\n roas: number;\n}\n\nconst metaAdAccountStatusLabels: Record<number, string> = {\n 1: \"active\",\n};\n\nexport function normalizeMetaAdAccountId(adAccountId: string): string {\n const normalized = adAccountId.trim().replace(/^act_/i, \"\");\n if (!/^\\d+$/.test(normalized)) {\n throw new Error(\n \"Invalid Meta ad account ID. Use digits only, with or without the act_ prefix.\"\n );\n }\n\n return normalized;\n}\n\nexport function toActId(adAccountId: string): string {\n return `act_${normalizeMetaAdAccountId(adAccountId)}`;\n}\n\nexport function parseNumber(value: string | number | undefined): number {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return value;\n }\n\n if (typeof value === \"string\") {\n const parsedValue = Number(value);\n return Number.isFinite(parsedValue) ? parsedValue : 0;\n }\n\n return 0;\n}\n\nexport function round(value: number, decimals = 2): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n\n const factor = 10 ** decimals;\n return Math.round(value * factor) / factor;\n}\n\nexport function getMetaAdAccountStatusLabel(accountStatus: number | undefined): string | undefined {\n if (typeof accountStatus !== \"number\" || !Number.isFinite(accountStatus)) {\n return undefined;\n }\n\n return metaAdAccountStatusLabels[accountStatus];\n}\n\nexport function toPercent(numerator: number, denominator: number): number {\n if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) {\n return 0;\n }\n\n return (numerator / denominator) * 100;\n}\n\nexport function getActionValue(\n actions: Array<{ action_type?: string; value?: string }> | undefined,\n actionType: string\n): number {\n const match = actions?.find((entry) => entry.action_type === actionType);\n return parseNumber(match?.value);\n}\n\nexport function getMetricBundle(insight: MetaAdInsight | undefined) {\n const spend = parseNumber(insight?.spend);\n const impressions = parseNumber(insight?.impressions);\n const clicks = parseNumber(insight?.clicks);\n const reach = parseNumber(insight?.reach);\n const ctrPercent = parseNumber(insight?.ctr);\n const cpc = parseNumber(insight?.cpc);\n const cpm = parseNumber(insight?.cpm);\n const purchases = getActionValue(insight?.actions, \"purchase\");\n const purchaseValue =\n getActionValue(insight?.action_values, \"purchase\") ||\n parseNumber(insight?.purchase_roas?.[0]?.value) * spend;\n\n return {\n spend: round(spend),\n impressions,\n clicks,\n reach,\n purchases: round(purchases),\n purchase_value: round(purchaseValue),\n ctr_percent: round(ctrPercent),\n cpc: round(cpc),\n cpm: round(cpm),\n roas: round(purchaseValue / spend),\n conversion_rate_percent: round(toPercent(purchases, clicks)),\n };\n}\n\nexport function aggregateInsightsByCurrency(\n entries: Array<{ currencyCode?: string; insight?: MetaAdInsight }>\n): MetaCurrencyAggregate[] {\n const aggregates = new Map<string, MetaCurrencyAggregate>();\n\n for (const entry of entries) {\n const currencyCode = entry.currencyCode?.trim() || \"UNKNOWN\";\n const metrics = getMetricBundle(entry.insight);\n const current = aggregates.get(currencyCode) ?? {\n currency_code: currencyCode,\n spend: 0,\n purchases: 0,\n purchase_value: 0,\n impressions: 0,\n clicks: 0,\n reach: 0,\n ctr_percent: 0,\n cpc: 0,\n cpm: 0,\n roas: 0,\n };\n\n current.spend += metrics.spend;\n current.purchases += metrics.purchases;\n current.purchase_value += metrics.purchase_value;\n current.impressions += metrics.impressions;\n current.clicks += metrics.clicks;\n current.reach += metrics.reach;\n\n aggregates.set(currencyCode, current);\n }\n\n return Array.from(aggregates.values()).map((aggregate) => ({\n ...aggregate,\n spend: round(aggregate.spend),\n purchases: round(aggregate.purchases),\n purchase_value: round(aggregate.purchase_value),\n ctr_percent: round(toPercent(aggregate.clicks, aggregate.impressions)),\n cpc: round(aggregate.spend / aggregate.clicks),\n cpm: round((aggregate.spend * 1000) / aggregate.impressions),\n roas: round(aggregate.purchase_value / aggregate.spend),\n }));\n}\n\nexport function normalizeRequestedAccountIds(adAccountIds?: string[]): string[] | undefined {\n const normalized = adAccountIds\n ?.map((accountId) => normalizeMetaAdAccountId(accountId))\n .filter(Boolean);\n\n if (!normalized?.length) {\n return undefined;\n }\n\n return Array.from(new Set(normalized));\n}\n\nexport function resolveRequestedAccounts(\n requestedIds: string[] | undefined,\n accessibleAccounts: MetaAdAccount[]\n): MetaAdAccount[] {\n if (!accessibleAccounts.length) {\n throw new Error(\"No accessible Meta ad accounts were found for the configured token.\");\n }\n\n if (!requestedIds?.length) {\n return accessibleAccounts;\n }\n\n const accountMap = new Map(\n accessibleAccounts.map((account) => [normalizeMetaAdAccountId(account.account_id || account.id || \"\"), account])\n );\n const resolvedAccounts = requestedIds\n .map((accountId) => accountMap.get(accountId))\n .filter((account): account is MetaAdAccount => Boolean(account));\n\n if (!resolvedAccounts.length) {\n throw new Error(\"None of the requested Meta ad accounts are accessible with the configured token.\");\n }\n\n return resolvedAccounts;\n}\n"],
4
+ "sourcesContent": ["import type { MetaAdAccount, MetaAdInsight } from \"../services/meta/meta-ads.js\";\r\n\r\nexport const dateRegex = /^\\d{4}-\\d{2}-\\d{2}$/;\r\n\r\nexport interface MetaCurrencyAggregate {\r\n currency_code: string;\r\n spend: number;\r\n purchases: number;\r\n purchase_value: number;\r\n impressions: number;\r\n clicks: number;\r\n reach: number;\r\n ctr_percent: number;\r\n cpc: number;\r\n cpm: number;\r\n roas: number;\r\n}\r\n\r\nconst metaAdAccountStatusLabels: Record<number, string> = {\r\n 1: \"active\",\r\n};\r\n\r\nexport function normalizeMetaAdAccountId(adAccountId: string): string {\r\n const normalized = adAccountId.trim().replace(/^act_/i, \"\");\r\n if (!/^\\d+$/.test(normalized)) {\r\n throw new Error(\r\n \"Invalid Meta ad account ID. Use digits only, with or without the act_ prefix.\"\r\n );\r\n }\r\n\r\n return normalized;\r\n}\r\n\r\nexport function toActId(adAccountId: string): string {\r\n return `act_${normalizeMetaAdAccountId(adAccountId)}`;\r\n}\r\n\r\nexport function parseNumber(value: string | number | undefined): number {\r\n if (typeof value === \"number\" && Number.isFinite(value)) {\r\n return value;\r\n }\r\n\r\n if (typeof value === \"string\") {\r\n const parsedValue = Number(value);\r\n return Number.isFinite(parsedValue) ? parsedValue : 0;\r\n }\r\n\r\n return 0;\r\n}\r\n\r\nexport function round(value: number, decimals = 2): number {\r\n if (!Number.isFinite(value)) {\r\n return 0;\r\n }\r\n\r\n const factor = 10 ** decimals;\r\n return Math.round(value * factor) / factor;\r\n}\r\n\r\nexport function getMetaAdAccountStatusLabel(accountStatus: number | undefined): string | undefined {\r\n if (typeof accountStatus !== \"number\" || !Number.isFinite(accountStatus)) {\r\n return undefined;\r\n }\r\n\r\n return metaAdAccountStatusLabels[accountStatus];\r\n}\r\n\r\nexport function toPercent(numerator: number, denominator: number): number {\r\n if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) {\r\n return 0;\r\n }\r\n\r\n return (numerator / denominator) * 100;\r\n}\r\n\r\nexport function getActionValue(\r\n actions: Array<{ action_type?: string; value?: string }> | undefined,\r\n actionType: string\r\n): number {\r\n const match = actions?.find((entry) => entry.action_type === actionType);\r\n return parseNumber(match?.value);\r\n}\r\n\r\nexport function getMetricBundle(insight: MetaAdInsight | undefined) {\r\n const spend = parseNumber(insight?.spend);\r\n const impressions = parseNumber(insight?.impressions);\r\n const clicks = parseNumber(insight?.clicks);\r\n const reach = parseNumber(insight?.reach);\r\n const ctrPercent = parseNumber(insight?.ctr);\r\n const cpc = parseNumber(insight?.cpc);\r\n const cpm = parseNumber(insight?.cpm);\r\n const purchases = getActionValue(insight?.actions, \"purchase\");\r\n const purchaseValue =\r\n getActionValue(insight?.action_values, \"purchase\") ||\r\n parseNumber(insight?.purchase_roas?.[0]?.value) * spend;\r\n\r\n return {\r\n spend: round(spend),\r\n impressions,\r\n clicks,\r\n reach,\r\n purchases: round(purchases),\r\n purchase_value: round(purchaseValue),\r\n ctr_percent: round(ctrPercent),\r\n cpc: round(cpc),\r\n cpm: round(cpm),\r\n roas: round(purchaseValue / spend),\r\n conversion_rate_percent: round(toPercent(purchases, clicks)),\r\n };\r\n}\r\n\r\nexport function aggregateInsightsByCurrency(\r\n entries: Array<{ currencyCode?: string; insight?: MetaAdInsight }>\r\n): MetaCurrencyAggregate[] {\r\n const aggregates = new Map<string, MetaCurrencyAggregate>();\r\n\r\n for (const entry of entries) {\r\n const currencyCode = entry.currencyCode?.trim() || \"UNKNOWN\";\r\n const metrics = getMetricBundle(entry.insight);\r\n const current = aggregates.get(currencyCode) ?? {\r\n currency_code: currencyCode,\r\n spend: 0,\r\n purchases: 0,\r\n purchase_value: 0,\r\n impressions: 0,\r\n clicks: 0,\r\n reach: 0,\r\n ctr_percent: 0,\r\n cpc: 0,\r\n cpm: 0,\r\n roas: 0,\r\n };\r\n\r\n current.spend += metrics.spend;\r\n current.purchases += metrics.purchases;\r\n current.purchase_value += metrics.purchase_value;\r\n current.impressions += metrics.impressions;\r\n current.clicks += metrics.clicks;\r\n current.reach += metrics.reach;\r\n\r\n aggregates.set(currencyCode, current);\r\n }\r\n\r\n return Array.from(aggregates.values()).map((aggregate) => ({\r\n ...aggregate,\r\n spend: round(aggregate.spend),\r\n purchases: round(aggregate.purchases),\r\n purchase_value: round(aggregate.purchase_value),\r\n ctr_percent: round(toPercent(aggregate.clicks, aggregate.impressions)),\r\n cpc: round(aggregate.spend / aggregate.clicks),\r\n cpm: round((aggregate.spend * 1000) / aggregate.impressions),\r\n roas: round(aggregate.purchase_value / aggregate.spend),\r\n }));\r\n}\r\n\r\nexport function normalizeRequestedAccountIds(adAccountIds?: string[]): string[] | undefined {\r\n const normalized = adAccountIds\r\n ?.map((accountId) => normalizeMetaAdAccountId(accountId))\r\n .filter(Boolean);\r\n\r\n if (!normalized?.length) {\r\n return undefined;\r\n }\r\n\r\n return Array.from(new Set(normalized));\r\n}\r\n\r\nexport function resolveRequestedAccounts(\r\n requestedIds: string[] | undefined,\r\n accessibleAccounts: MetaAdAccount[]\r\n): MetaAdAccount[] {\r\n if (!accessibleAccounts.length) {\r\n throw new Error(\"No accessible Meta ad accounts were found for the configured token.\");\r\n }\r\n\r\n if (!requestedIds?.length) {\r\n return accessibleAccounts;\r\n }\r\n\r\n const accountMap = new Map(\r\n accessibleAccounts.map((account) => [normalizeMetaAdAccountId(account.account_id || account.id || \"\"), account])\r\n );\r\n const resolvedAccounts = requestedIds\r\n .map((accountId) => accountMap.get(accountId))\r\n .filter((account): account is MetaAdAccount => Boolean(account));\r\n\r\n if (!resolvedAccounts.length) {\r\n throw new Error(\"None of the requested Meta ad accounts are accessible with the configured token.\");\r\n }\r\n\r\n return resolvedAccounts;\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/prompts/reporte-ventas.ts"],
4
- "sourcesContent": ["import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { markdown } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nconst MODULE_DIR = dirname(fileURLToPath(import.meta.url));\nconst PROMPT_TEMPLATE_PATHS = [\n resolve(MODULE_DIR, \"prompt_reporte_ventas_semanal.md\"),\n resolve(process.cwd(), \"src/prompts/prompt_reporte_ventas_semanal.md\"),\n];\n\nexport const reporteVentasPromptSchema = z.object({\n tiempo: z\n .string()\n .describe(\n \"Periodo en lenguaje natural (ej: 'esta semana', 'enero 2026', 'del 2026-01-10 al 2026-01-25').\"\n ),\n});\n\nfunction getBuenosAiresTodayIso(): string {\n const parts = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: \"America/Argentina/Buenos_Aires\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).formatToParts(new Date());\n\n const year = parts.find((part) => part.type === \"year\")?.value ?? \"0000\";\n const month = parts.find((part) => part.type === \"month\")?.value ?? \"00\";\n const day = parts.find((part) => part.type === \"day\")?.value ?? \"00\";\n\n return `${year}-${month}-${day}`;\n}\n\nfunction loadReporteTemplate(): string {\n for (const path of PROMPT_TEMPLATE_PATHS) {\n if (existsSync(path)) {\n return readFileSync(path, \"utf8\").trim();\n }\n }\n\n throw new Error(\n `Prompt template file not found. Checked: ${PROMPT_TEMPLATE_PATHS.join(\", \")}`\n );\n}\n\nexport async function reporteVentasPromptHandler({\n tiempo,\n}: z.infer<typeof reporteVentasPromptSchema>) {\n const todayIso = getBuenosAiresTodayIso();\n const template = loadReporteTemplate();\n\n const invocationBlock = `\n## Contexto de invocacion\n\n- Solicitud temporal del usuario: \"${tiempo}\"\n- Fecha de referencia para interpretar relativos: ${todayIso} (America/Argentina/Buenos_Aires)\n\n## Reglas de interpretacion temporal (obligatorias)\n\n1. Converti la solicitud temporal del usuario a fechas absolutas \\`startDate\\` y \\`endDate\\` en formato \\`YYYY-MM-DD\\`.\n2. Si el usuario pide semana, usa rango lunes-domingo.\n3. Si pide mes, usa el primer y ultimo dia del mes.\n4. Si pide anio, usa del \\`YYYY-01-01\\` al \\`YYYY-12-31\\`.\n5. Si pide un rango explicito, respeta exactamente ese rango.\n6. Si usa expresiones relativas (por ejemplo \"ultimos 15 dias\"), resolvelas usando la fecha de referencia.\n7. Si la solicitud es ambigua y no se puede convertir con seguridad a fechas exactas, pedi una aclaracion minima antes de ejecutar herramientas.\n\n## Ejecucion de herramientas\n\n- Ejecuta siempre \\`vtex_get_orders_summary\\` para obtener el universo de ordenes del periodo.\n- Luego ejecuta \\`vtex_get_order_details\\` cubriendo el 100% de los \\`order_id\\` (lotes de hasta 50 IDs por llamada).\n- Con esos datos, genera el reporte con la estructura y reglas del template siguiente.\n`.trim();\n\n return markdown(`${invocationBlock}\\n\\n---\\n\\n${template}`);\n}\n"],
4
+ "sourcesContent": ["import { existsSync, readFileSync } from \"node:fs\";\r\nimport { dirname, resolve } from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport { markdown } from \"mcp-use/server\";\r\nimport { z } from \"zod\";\r\n\r\nconst MODULE_DIR = dirname(fileURLToPath(import.meta.url));\r\nconst PROMPT_TEMPLATE_PATHS = [\r\n resolve(MODULE_DIR, \"prompt_reporte_ventas_semanal.md\"),\r\n resolve(process.cwd(), \"src/prompts/prompt_reporte_ventas_semanal.md\"),\r\n];\r\n\r\nexport const reporteVentasPromptSchema = z.object({\r\n tiempo: z\r\n .string()\r\n .describe(\r\n \"Periodo en lenguaje natural (ej: 'esta semana', 'enero 2026', 'del 2026-01-10 al 2026-01-25').\"\r\n ),\r\n});\r\n\r\nfunction getBuenosAiresTodayIso(): string {\r\n const parts = new Intl.DateTimeFormat(\"en-CA\", {\r\n timeZone: \"America/Argentina/Buenos_Aires\",\r\n year: \"numeric\",\r\n month: \"2-digit\",\r\n day: \"2-digit\",\r\n }).formatToParts(new Date());\r\n\r\n const year = parts.find((part) => part.type === \"year\")?.value ?? \"0000\";\r\n const month = parts.find((part) => part.type === \"month\")?.value ?? \"00\";\r\n const day = parts.find((part) => part.type === \"day\")?.value ?? \"00\";\r\n\r\n return `${year}-${month}-${day}`;\r\n}\r\n\r\nfunction loadReporteTemplate(): string {\r\n for (const path of PROMPT_TEMPLATE_PATHS) {\r\n if (existsSync(path)) {\r\n return readFileSync(path, \"utf8\").trim();\r\n }\r\n }\r\n\r\n throw new Error(\r\n `Prompt template file not found. Checked: ${PROMPT_TEMPLATE_PATHS.join(\", \")}`\r\n );\r\n}\r\n\r\nexport async function reporteVentasPromptHandler({\r\n tiempo,\r\n}: z.infer<typeof reporteVentasPromptSchema>) {\r\n const todayIso = getBuenosAiresTodayIso();\r\n const template = loadReporteTemplate();\r\n\r\n const invocationBlock = `\r\n## Contexto de invocacion\r\n\r\n- Solicitud temporal del usuario: \"${tiempo}\"\r\n- Fecha de referencia para interpretar relativos: ${todayIso} (America/Argentina/Buenos_Aires)\r\n\r\n## Reglas de interpretacion temporal (obligatorias)\r\n\r\n1. Converti la solicitud temporal del usuario a fechas absolutas \\`startDate\\` y \\`endDate\\` en formato \\`YYYY-MM-DD\\`.\r\n2. Si el usuario pide semana, usa rango lunes-domingo.\r\n3. Si pide mes, usa el primer y ultimo dia del mes.\r\n4. Si pide anio, usa del \\`YYYY-01-01\\` al \\`YYYY-12-31\\`.\r\n5. Si pide un rango explicito, respeta exactamente ese rango.\r\n6. Si usa expresiones relativas (por ejemplo \"ultimos 15 dias\"), resolvelas usando la fecha de referencia.\r\n7. Si la solicitud es ambigua y no se puede convertir con seguridad a fechas exactas, pedi una aclaracion minima antes de ejecutar herramientas.\r\n\r\n## Ejecucion de herramientas\r\n\r\n- Ejecuta siempre \\`vtex_get_orders_summary\\` para obtener el universo de ordenes del periodo.\r\n- Luego ejecuta \\`vtex_get_order_details\\` cubriendo el 100% de los \\`order_id\\` (lotes de hasta 50 IDs por llamada).\r\n- Con esos datos, genera el reporte con la estructura y reglas del template siguiente.\r\n`.trim();\r\n\r\n return markdown(`${invocationBlock}\\n\\n---\\n\\n${template}`);\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,YAAY,oBAAoB;AACzC,SAAS,SAAS,eAAe;AACjC,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AACzB,SAAS,SAAS;AAElB,MAAM,aAAa,QAAQ,cAAc,YAAY,GAAG,CAAC;AACzD,MAAM,wBAAwB;AAAA,EAC5B,QAAQ,YAAY,kCAAkC;AAAA,EACtD,QAAQ,QAAQ,IAAI,GAAG,8CAA8C;AACvE;AAEO,MAAM,4BAA4B,EAAE,OAAO;AAAA,EAChD,QAAQ,EACL,OAAO,EACP;AAAA,IACC;AAAA,EACF;AACJ,CAAC;AAED,SAAS,yBAAiC;AACxC,QAAM,QAAQ,IAAI,KAAK,eAAe,SAAS;AAAA,IAC7C,UAAU;AAAA,IACV,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,EACP,CAAC,EAAE,cAAc,oBAAI,KAAK,CAAC;AAE3B,QAAM,OAAO,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,MAAM,GAAG,SAAS;AAClE,QAAM,QAAQ,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,OAAO,GAAG,SAAS;AACpE,QAAM,MAAM,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,KAAK,GAAG,SAAS;AAEhE,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG;AAChC;AAEA,SAAS,sBAA8B;AACrC,aAAW,QAAQ,uBAAuB;AACxC,QAAI,WAAW,IAAI,GAAG;AACpB,aAAO,aAAa,MAAM,MAAM,EAAE,KAAK;AAAA,IACzC;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR,4CAA4C,sBAAsB,KAAK,IAAI,CAAC;AAAA,EAC9E;AACF;AAEA,eAAsB,2BAA2B;AAAA,EAC/C;AACF,GAA8C;AAC5C,QAAM,WAAW,uBAAuB;AACxC,QAAM,WAAW,oBAAoB;AAErC,QAAM,kBAAkB;AAAA;AAAA;AAAA,qCAGW,MAAM;AAAA,oDACS,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiB1D,KAAK;AAEL,SAAO,SAAS,GAAG,eAAe;AAAA;AAAA;AAAA;AAAA,EAAc,QAAQ,EAAE;AAC5D;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/services/analytics/ga4-client.ts"],
4
- "sourcesContent": ["import { refreshGoogleAccessToken } from \"./oauth.js\";\n\nconst GA4_DATA_BASE_URL = \"https://analyticsdata.googleapis.com/v1beta\";\nconst GA4_ADMIN_BASE_URL = \"https://analyticsadmin.googleapis.com/v1alpha\";\n\nexport interface Ga4PropertySummary {\n property: string;\n displayName: string;\n propertyType?: string;\n parent?: string;\n}\n\nexport interface Ga4AccountSummary {\n name: string;\n account: string;\n displayName: string;\n propertySummaries?: Ga4PropertySummary[];\n}\n\nexport interface Ga4AccountSummariesResponse {\n accountSummaries?: Ga4AccountSummary[];\n nextPageToken?: string;\n}\n\nexport interface ListAccountSummariesResult {\n accountSummaries: Ga4AccountSummary[];\n nextPageToken?: string;\n}\n\nexport interface Ga4MetadataItem {\n apiName: string;\n uiName?: string;\n description?: string;\n category?: string;\n type?: string;\n customDefinition?: boolean;\n deprecatedApiNames?: string[];\n}\n\nexport interface Ga4CompatibilityItem {\n compatibility: string;\n dimensionMetadata?: Ga4MetadataItem;\n metricMetadata?: Ga4MetadataItem;\n}\n\nexport interface Ga4MetadataResponse {\n name: string;\n dimensions?: Ga4MetadataItem[];\n metrics?: Ga4MetadataItem[];\n comparisons?: Ga4MetadataItem[];\n}\n\nexport interface Ga4MetricHeader {\n name: string;\n type?: string;\n}\n\nexport interface Ga4DimensionHeader {\n name: string;\n}\n\nexport interface Ga4DimensionValue {\n value: string;\n}\n\nexport interface Ga4MetricValue {\n value: string;\n}\n\nexport interface Ga4ReportRow {\n dimensionValues?: Ga4DimensionValue[];\n metricValues?: Ga4MetricValue[];\n}\n\nexport interface Ga4PropertyQuotaBucket {\n consumed?: number;\n remaining?: number;\n}\n\nexport interface Ga4PropertyQuota {\n tokensPerDay?: Ga4PropertyQuotaBucket;\n tokensPerHour?: Ga4PropertyQuotaBucket;\n concurrentRequests?: Ga4PropertyQuotaBucket;\n serverErrorsPerProjectPerHour?: Ga4PropertyQuotaBucket;\n potentiallyThresholdedRequestsPerHour?: Ga4PropertyQuotaBucket;\n tokensPerProjectPerHour?: Ga4PropertyQuotaBucket;\n}\n\nexport interface Ga4ReportResponse {\n dimensionHeaders?: Ga4DimensionHeader[];\n metricHeaders?: Ga4MetricHeader[];\n rows?: Ga4ReportRow[];\n rowCount?: number;\n metadata?: {\n currencyCode?: string;\n timeZone?: string;\n [key: string]: unknown;\n };\n propertyQuota?: Ga4PropertyQuota;\n kind?: string;\n}\n\nexport interface Ga4RealtimeReportResponse {\n dimensionHeaders?: Ga4DimensionHeader[];\n metricHeaders?: Ga4MetricHeader[];\n rows?: Ga4ReportRow[];\n rowCount?: number;\n kind?: string;\n}\n\nexport interface Ga4CompatibilityResponse {\n dimensionCompatibilities?: Ga4CompatibilityItem[];\n metricCompatibilities?: Ga4CompatibilityItem[];\n}\n\ninterface GoogleApiErrorPayload {\n error?: {\n code?: number;\n message?: string;\n status?: string;\n };\n}\n\nasync function googleApiFetch<T>(url: string, init?: RequestInit): Promise<T> {\n const accessToken = await refreshGoogleAccessToken();\n\n const response = await fetch(url, {\n ...init,\n headers: {\n Authorization: `Bearer ${accessToken.accessToken}`,\n \"Content-Type\": \"application/json\",\n ...(init?.headers ?? {}),\n },\n });\n\n if (!response.ok) {\n const payload = (await response.json().catch(() => ({}))) as GoogleApiErrorPayload;\n const message =\n payload.error?.message ??\n payload.error?.status ??\n `Google API request failed with status ${response.status}`;\n\n throw new Error(message);\n }\n\n return (await response.json()) as T;\n}\n\nexport async function listAccountSummaries(): Promise<ListAccountSummariesResult> {\n const payload = await googleApiFetch<Ga4AccountSummariesResponse>(\n `${GA4_ADMIN_BASE_URL}/accountSummaries`\n );\n\n return {\n accountSummaries: payload.accountSummaries ?? [],\n nextPageToken: payload.nextPageToken,\n };\n}\n\nexport async function getMetadata(propertyId: string): Promise<Ga4MetadataResponse> {\n return googleApiFetch<Ga4MetadataResponse>(\n `${GA4_DATA_BASE_URL}/properties/${propertyId}/metadata`\n );\n}\n\nexport async function runReport(\n propertyId: string,\n body: Record<string, unknown>\n): Promise<Ga4ReportResponse> {\n return googleApiFetch<Ga4ReportResponse>(\n `${GA4_DATA_BASE_URL}/properties/${propertyId}:runReport`,\n {\n method: \"POST\",\n body: JSON.stringify(body),\n }\n );\n}\n\nexport async function runRealtimeReport(\n propertyId: string,\n body: Record<string, unknown>\n): Promise<Ga4RealtimeReportResponse> {\n return googleApiFetch<Ga4RealtimeReportResponse>(\n `${GA4_DATA_BASE_URL}/properties/${propertyId}:runRealtimeReport`,\n {\n method: \"POST\",\n body: JSON.stringify(body),\n }\n );\n}\n\nexport async function checkCompatibility(\n propertyId: string,\n body: Record<string, unknown>\n): Promise<Ga4CompatibilityResponse> {\n return googleApiFetch<Ga4CompatibilityResponse>(\n `${GA4_DATA_BASE_URL}/properties/${propertyId}:checkCompatibility`,\n {\n method: \"POST\",\n body: JSON.stringify(body),\n }\n );\n}\n"],
4
+ "sourcesContent": ["import { refreshGoogleAccessToken } from \"./oauth.js\";\r\n\r\nconst GA4_DATA_BASE_URL = \"https://analyticsdata.googleapis.com/v1beta\";\r\nconst GA4_ADMIN_BASE_URL = \"https://analyticsadmin.googleapis.com/v1alpha\";\r\n\r\nexport interface Ga4PropertySummary {\r\n property: string;\r\n displayName: string;\r\n propertyType?: string;\r\n parent?: string;\r\n}\r\n\r\nexport interface Ga4AccountSummary {\r\n name: string;\r\n account: string;\r\n displayName: string;\r\n propertySummaries?: Ga4PropertySummary[];\r\n}\r\n\r\nexport interface Ga4AccountSummariesResponse {\r\n accountSummaries?: Ga4AccountSummary[];\r\n nextPageToken?: string;\r\n}\r\n\r\nexport interface ListAccountSummariesResult {\r\n accountSummaries: Ga4AccountSummary[];\r\n nextPageToken?: string;\r\n}\r\n\r\nexport interface Ga4MetadataItem {\r\n apiName: string;\r\n uiName?: string;\r\n description?: string;\r\n category?: string;\r\n type?: string;\r\n customDefinition?: boolean;\r\n deprecatedApiNames?: string[];\r\n}\r\n\r\nexport interface Ga4CompatibilityItem {\r\n compatibility: string;\r\n dimensionMetadata?: Ga4MetadataItem;\r\n metricMetadata?: Ga4MetadataItem;\r\n}\r\n\r\nexport interface Ga4MetadataResponse {\r\n name: string;\r\n dimensions?: Ga4MetadataItem[];\r\n metrics?: Ga4MetadataItem[];\r\n comparisons?: Ga4MetadataItem[];\r\n}\r\n\r\nexport interface Ga4MetricHeader {\r\n name: string;\r\n type?: string;\r\n}\r\n\r\nexport interface Ga4DimensionHeader {\r\n name: string;\r\n}\r\n\r\nexport interface Ga4DimensionValue {\r\n value: string;\r\n}\r\n\r\nexport interface Ga4MetricValue {\r\n value: string;\r\n}\r\n\r\nexport interface Ga4ReportRow {\r\n dimensionValues?: Ga4DimensionValue[];\r\n metricValues?: Ga4MetricValue[];\r\n}\r\n\r\nexport interface Ga4PropertyQuotaBucket {\r\n consumed?: number;\r\n remaining?: number;\r\n}\r\n\r\nexport interface Ga4PropertyQuota {\r\n tokensPerDay?: Ga4PropertyQuotaBucket;\r\n tokensPerHour?: Ga4PropertyQuotaBucket;\r\n concurrentRequests?: Ga4PropertyQuotaBucket;\r\n serverErrorsPerProjectPerHour?: Ga4PropertyQuotaBucket;\r\n potentiallyThresholdedRequestsPerHour?: Ga4PropertyQuotaBucket;\r\n tokensPerProjectPerHour?: Ga4PropertyQuotaBucket;\r\n}\r\n\r\nexport interface Ga4ReportResponse {\r\n dimensionHeaders?: Ga4DimensionHeader[];\r\n metricHeaders?: Ga4MetricHeader[];\r\n rows?: Ga4ReportRow[];\r\n rowCount?: number;\r\n metadata?: {\r\n currencyCode?: string;\r\n timeZone?: string;\r\n [key: string]: unknown;\r\n };\r\n propertyQuota?: Ga4PropertyQuota;\r\n kind?: string;\r\n}\r\n\r\nexport interface Ga4RealtimeReportResponse {\r\n dimensionHeaders?: Ga4DimensionHeader[];\r\n metricHeaders?: Ga4MetricHeader[];\r\n rows?: Ga4ReportRow[];\r\n rowCount?: number;\r\n kind?: string;\r\n}\r\n\r\nexport interface Ga4CompatibilityResponse {\r\n dimensionCompatibilities?: Ga4CompatibilityItem[];\r\n metricCompatibilities?: Ga4CompatibilityItem[];\r\n}\r\n\r\ninterface GoogleApiErrorPayload {\r\n error?: {\r\n code?: number;\r\n message?: string;\r\n status?: string;\r\n };\r\n}\r\n\r\nasync function googleApiFetch<T>(url: string, init?: RequestInit): Promise<T> {\r\n const accessToken = await refreshGoogleAccessToken();\r\n\r\n const response = await fetch(url, {\r\n ...init,\r\n headers: {\r\n Authorization: `Bearer ${accessToken.accessToken}`,\r\n \"Content-Type\": \"application/json\",\r\n ...(init?.headers ?? {}),\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n const payload = (await response.json().catch(() => ({}))) as GoogleApiErrorPayload;\r\n const message =\r\n payload.error?.message ??\r\n payload.error?.status ??\r\n `Google API request failed with status ${response.status}`;\r\n\r\n throw new Error(message);\r\n }\r\n\r\n return (await response.json()) as T;\r\n}\r\n\r\nexport async function listAccountSummaries(): Promise<ListAccountSummariesResult> {\r\n const payload = await googleApiFetch<Ga4AccountSummariesResponse>(\r\n `${GA4_ADMIN_BASE_URL}/accountSummaries`\r\n );\r\n\r\n return {\r\n accountSummaries: payload.accountSummaries ?? [],\r\n nextPageToken: payload.nextPageToken,\r\n };\r\n}\r\n\r\nexport async function getMetadata(propertyId: string): Promise<Ga4MetadataResponse> {\r\n return googleApiFetch<Ga4MetadataResponse>(\r\n `${GA4_DATA_BASE_URL}/properties/${propertyId}/metadata`\r\n );\r\n}\r\n\r\nexport async function runReport(\r\n propertyId: string,\r\n body: Record<string, unknown>\r\n): Promise<Ga4ReportResponse> {\r\n return googleApiFetch<Ga4ReportResponse>(\r\n `${GA4_DATA_BASE_URL}/properties/${propertyId}:runReport`,\r\n {\r\n method: \"POST\",\r\n body: JSON.stringify(body),\r\n }\r\n );\r\n}\r\n\r\nexport async function runRealtimeReport(\r\n propertyId: string,\r\n body: Record<string, unknown>\r\n): Promise<Ga4RealtimeReportResponse> {\r\n return googleApiFetch<Ga4RealtimeReportResponse>(\r\n `${GA4_DATA_BASE_URL}/properties/${propertyId}:runRealtimeReport`,\r\n {\r\n method: \"POST\",\r\n body: JSON.stringify(body),\r\n }\r\n );\r\n}\r\n\r\nexport async function checkCompatibility(\r\n propertyId: string,\r\n body: Record<string, unknown>\r\n): Promise<Ga4CompatibilityResponse> {\r\n return googleApiFetch<Ga4CompatibilityResponse>(\r\n `${GA4_DATA_BASE_URL}/properties/${propertyId}:checkCompatibility`,\r\n {\r\n method: \"POST\",\r\n body: JSON.stringify(body),\r\n }\r\n );\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,gCAAgC;AAEzC,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAwH3B,eAAe,eAAkB,KAAa,MAAgC;AAC5E,QAAM,cAAc,MAAM,yBAAyB;AAEnD,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,GAAG;AAAA,IACH,SAAS;AAAA,MACP,eAAe,UAAU,YAAY,WAAW;AAAA,MAChD,gBAAgB;AAAA,MAChB,GAAI,MAAM,WAAW,CAAC;AAAA,IACxB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,UAAW,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACvD,UAAM,UACJ,QAAQ,OAAO,WACf,QAAQ,OAAO,UACf,yCAAyC,SAAS,MAAM;AAE1D,UAAM,IAAI,MAAM,OAAO;AAAA,EACzB;AAEA,SAAQ,MAAM,SAAS,KAAK;AAC9B;AAEA,eAAsB,uBAA4D;AAChF,QAAM,UAAU,MAAM;AAAA,IACpB,GAAG,kBAAkB;AAAA,EACvB;AAEA,SAAO;AAAA,IACL,kBAAkB,QAAQ,oBAAoB,CAAC;AAAA,IAC/C,eAAe,QAAQ;AAAA,EACzB;AACF;AAEA,eAAsB,YAAY,YAAkD;AAClF,SAAO;AAAA,IACL,GAAG,iBAAiB,eAAe,UAAU;AAAA,EAC/C;AACF;AAEA,eAAsB,UACpB,YACA,MAC4B;AAC5B,SAAO;AAAA,IACL,GAAG,iBAAiB,eAAe,UAAU;AAAA,IAC7C;AAAA,MACE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,eAAsB,kBACpB,YACA,MACoC;AACpC,SAAO;AAAA,IACL,GAAG,iBAAiB,eAAe,UAAU;AAAA,IAC7C;AAAA,MACE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,eAAsB,mBACpB,YACA,MACmC;AACnC,SAAO;AAAA,IACL,GAAG,iBAAiB,eAAe,UAAU;AAAA,IAC7C;AAAA,MACE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/services/analytics/oauth.ts"],
4
- "sourcesContent": ["import { googleOAuthConfig, type GoogleOAuthClientConfig } from '../../config/google.js';\nimport { getActiveGoogleRefreshTokenFromStore } from '../../config/google-store.js';\n\nexport interface GoogleAccessTokenResponse {\n accessToken: string;\n expiresIn: number;\n tokenType: string;\n scope?: string;\n}\n\nexport interface GoogleOAuthRefreshCredentials extends GoogleOAuthClientConfig {\n refreshToken: string;\n}\n\ninterface GoogleTokenApiResponse {\n access_token?: string;\n expires_in?: number;\n token_type?: string;\n scope?: string;\n error?: string;\n error_description?: string;\n}\n\nasync function resolveGoogleOAuthCredentials(): Promise<GoogleOAuthRefreshCredentials> {\n const refreshToken = await getActiveGoogleRefreshTokenFromStore();\n if (!refreshToken) {\n throw new Error(\n 'Missing Google OAuth refresh token. Connect Google in the dashboard and ensure the stored connection is active.'\n );\n }\n\n return {\n clientId: googleOAuthConfig.clientId,\n clientSecret: googleOAuthConfig.clientSecret,\n refreshToken,\n };\n}\n\nexport async function refreshGoogleAccessToken(\n credentials?: GoogleOAuthRefreshCredentials\n): Promise<GoogleAccessTokenResponse> {\n const resolved = credentials ?? (await resolveGoogleOAuthCredentials());\n const response = await fetch('https://oauth2.googleapis.com/token', {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n client_id: resolved.clientId,\n client_secret: resolved.clientSecret,\n refresh_token: resolved.refreshToken,\n grant_type: 'refresh_token',\n }),\n });\n\n const payload = (await response.json()) as GoogleTokenApiResponse;\n\n if (!response.ok || !payload.access_token || !payload.expires_in || !payload.token_type) {\n const errorMessage = payload.error_description ?? payload.error ?? `Unexpected Google OAuth response (${response.status})`;\n throw new Error(`Failed to refresh Google access token: ${errorMessage}`);\n }\n\n return {\n accessToken: payload.access_token,\n expiresIn: payload.expires_in,\n tokenType: payload.token_type,\n scope: payload.scope,\n };\n}\n"],
4
+ "sourcesContent": ["import { googleOAuthConfig, type GoogleOAuthClientConfig } from '../../config/google.js';\r\nimport { getActiveGoogleRefreshTokenFromStore } from '../../config/google-store.js';\r\n\r\nexport interface GoogleAccessTokenResponse {\r\n accessToken: string;\r\n expiresIn: number;\r\n tokenType: string;\r\n scope?: string;\r\n}\r\n\r\nexport interface GoogleOAuthRefreshCredentials extends GoogleOAuthClientConfig {\r\n refreshToken: string;\r\n}\r\n\r\ninterface GoogleTokenApiResponse {\r\n access_token?: string;\r\n expires_in?: number;\r\n token_type?: string;\r\n scope?: string;\r\n error?: string;\r\n error_description?: string;\r\n}\r\n\r\nasync function resolveGoogleOAuthCredentials(): Promise<GoogleOAuthRefreshCredentials> {\r\n const refreshToken = await getActiveGoogleRefreshTokenFromStore();\r\n if (!refreshToken) {\r\n throw new Error(\r\n 'Missing Google OAuth refresh token. Connect Google in the dashboard and ensure the stored connection is active.'\r\n );\r\n }\r\n\r\n return {\r\n clientId: googleOAuthConfig.clientId,\r\n clientSecret: googleOAuthConfig.clientSecret,\r\n refreshToken,\r\n };\r\n}\r\n\r\nexport async function refreshGoogleAccessToken(\r\n credentials?: GoogleOAuthRefreshCredentials\r\n): Promise<GoogleAccessTokenResponse> {\r\n const resolved = credentials ?? (await resolveGoogleOAuthCredentials());\r\n const response = await fetch('https://oauth2.googleapis.com/token', {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\r\n body: new URLSearchParams({\r\n client_id: resolved.clientId,\r\n client_secret: resolved.clientSecret,\r\n refresh_token: resolved.refreshToken,\r\n grant_type: 'refresh_token',\r\n }),\r\n });\r\n\r\n const payload = (await response.json()) as GoogleTokenApiResponse;\r\n\r\n if (!response.ok || !payload.access_token || !payload.expires_in || !payload.token_type) {\r\n const errorMessage = payload.error_description ?? payload.error ?? `Unexpected Google OAuth response (${response.status})`;\r\n throw new Error(`Failed to refresh Google access token: ${errorMessage}`);\r\n }\r\n\r\n return {\r\n accessToken: payload.access_token,\r\n expiresIn: payload.expires_in,\r\n tokenType: payload.token_type,\r\n scope: payload.scope,\r\n };\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,yBAAuD;AAChE,SAAS,4CAA4C;AAsBrD,eAAe,gCAAwE;AACrF,QAAM,eAAe,MAAM,qCAAqC;AAChE,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,kBAAkB;AAAA,IAC5B,cAAc,kBAAkB;AAAA,IAChC;AAAA,EACF;AACF;AAEA,eAAsB,yBACpB,aACoC;AACpC,QAAM,WAAW,eAAgB,MAAM,8BAA8B;AACrE,QAAM,WAAW,MAAM,MAAM,uCAAuC;AAAA,IAClE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,IAC/D,MAAM,IAAI,gBAAgB;AAAA,MACxB,WAAW,SAAS;AAAA,MACpB,eAAe,SAAS;AAAA,MACxB,eAAe,SAAS;AAAA,MACxB,YAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC;AAED,QAAM,UAAW,MAAM,SAAS,KAAK;AAErC,MAAI,CAAC,SAAS,MAAM,CAAC,QAAQ,gBAAgB,CAAC,QAAQ,cAAc,CAAC,QAAQ,YAAY;AACvF,UAAM,eAAe,QAAQ,qBAAqB,QAAQ,SAAS,qCAAqC,SAAS,MAAM;AACvH,UAAM,IAAI,MAAM,0CAA0C,YAAY,EAAE;AAAA,EAC1E;AAEA,SAAO;AAAA,IACL,aAAa,QAAQ;AAAA,IACrB,WAAW,QAAQ;AAAA,IACnB,WAAW,QAAQ;AAAA,IACnB,OAAO,QAAQ;AAAA,EACjB;AACF;",
6
6
  "names": []
7
7
  }