@yoryoboy/bi-mcp 1.6.0 → 1.7.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 (178) 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/run-migrations.js.map +1 -1
  16. package/dist/scripts/test-db-connection.js.map +1 -1
  17. package/dist/src/analytics/ga4-channel-groups.js.map +1 -1
  18. package/dist/src/analytics/ga4-report-utils.js.map +1 -1
  19. package/dist/src/config/benchmarks.js.map +1 -1
  20. package/dist/src/config/google-store.js.map +1 -1
  21. package/dist/src/config/google.js.map +1 -1
  22. package/dist/src/config/mercadolibre-profile-store.js.map +1 -1
  23. package/dist/src/config/mercadolibre.js.map +1 -1
  24. package/dist/src/config/meta.js.map +1 -1
  25. package/dist/src/config/profile-store.js.map +1 -1
  26. package/dist/src/config/vtex-crypto.js.map +1 -1
  27. package/dist/src/config/vtex-profile-store.js.map +1 -1
  28. package/dist/src/config/vtex.js.map +1 -1
  29. package/dist/src/db/client.js.map +1 -1
  30. package/dist/src/google-ads/report-utils.js +78 -0
  31. package/dist/src/google-ads/report-utils.js.map +7 -0
  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/search-console/search-console-utils.js +275 -0
  35. package/dist/src/search-console/search-console-utils.js.map +7 -0
  36. package/dist/src/services/analytics/ga4-client.js.map +1 -1
  37. package/dist/src/services/analytics/oauth.js.map +1 -1
  38. package/dist/src/services/google-ads/google-ads-client.js.map +1 -1
  39. package/dist/src/services/mercadolibre/mercadolibre-api.js.map +1 -1
  40. package/dist/src/services/mercadolibre/mercadolibre-items.js.map +1 -1
  41. package/dist/src/services/mercadolibre/mercadolibre-orders.js.map +1 -1
  42. package/dist/src/services/mercadolibre/mercadolibre-questions.js.map +1 -1
  43. package/dist/src/services/meta/meta-ads.js.map +1 -1
  44. package/dist/src/services/meta/meta-api.js.map +1 -1
  45. package/dist/src/services/search-console/search-console-client.js.map +1 -1
  46. package/dist/src/services/search-console/search-console-utils.js.map +1 -1
  47. package/dist/src/services/vtex/vtex-api.js.map +1 -1
  48. package/dist/src/services/vtex/vtex-catalog-write.js.map +1 -1
  49. package/dist/src/services/vtex/vtex-catalog.js.map +1 -1
  50. package/dist/src/services/vtex/vtex-logistics.js.map +1 -1
  51. package/dist/src/services/vtex/vtex-orders-write.js.map +1 -1
  52. package/dist/src/services/vtex/vtex-orders.js.map +1 -1
  53. package/dist/src/services/vtex/vtex-pricing-write.js.map +1 -1
  54. package/dist/src/services/vtex/vtex-pricing.js.map +1 -1
  55. package/dist/src/services/vtex/vtex-write.js.map +1 -1
  56. package/dist/src/tools/analytics/attribution-gaps.js.map +1 -1
  57. package/dist/src/tools/analytics/channel-mix.js.map +1 -1
  58. package/dist/src/tools/analytics/ecommerce-tracking-health.js.map +1 -1
  59. package/dist/src/tools/analytics/engagement-overview.js.map +1 -1
  60. package/dist/src/tools/analytics/index.js.map +1 -1
  61. package/dist/src/tools/analytics/list-accessible-properties.js.map +1 -1
  62. package/dist/src/tools/analytics/property-info.js.map +1 -1
  63. package/dist/src/tools/analytics/revenue-by-channel.js.map +1 -1
  64. package/dist/src/tools/analytics/revenue-overview.js.map +1 -1
  65. package/dist/src/tools/analytics/revenue-trend.js.map +1 -1
  66. package/dist/src/tools/analytics/source-medium-breakdown.js.map +1 -1
  67. package/dist/src/tools/analytics/top-landing-pages.js.map +1 -1
  68. package/dist/src/tools/config/check-database-connection.js.map +1 -1
  69. package/dist/src/tools/config/index.js.map +1 -1
  70. package/dist/src/tools/config/list-profiles.js.map +1 -1
  71. package/dist/src/tools/google-ads/account-overview.js.map +1 -1
  72. package/dist/src/tools/google-ads/account-risks.js.map +1 -1
  73. package/dist/src/tools/google-ads/break-even-analysis.js.map +1 -1
  74. package/dist/src/tools/google-ads/campaign-performance.js.map +1 -1
  75. package/dist/src/tools/google-ads/channel-mix.js.map +1 -1
  76. package/dist/src/tools/google-ads/compare-accounts.js.map +1 -1
  77. package/dist/src/tools/google-ads/customer-clients.js.map +1 -1
  78. package/dist/src/tools/google-ads/customer-info.js.map +1 -1
  79. package/dist/src/tools/google-ads/index.js.map +1 -1
  80. package/dist/src/tools/google-ads/scaling-health.js.map +1 -1
  81. package/dist/src/tools/google-ads/search-terms-summary.js.map +1 -1
  82. package/dist/src/tools/google-ads/time-series.js.map +1 -1
  83. package/dist/src/tools/index.js.map +1 -1
  84. package/dist/src/tools/mercadolibre/answer-question.js.map +1 -1
  85. package/dist/src/tools/mercadolibre/create-item.js.map +1 -1
  86. package/dist/src/tools/mercadolibre/estimate-listing-fee.js.map +1 -1
  87. package/dist/src/tools/mercadolibre/get-account-context.js.map +1 -1
  88. package/dist/src/tools/mercadolibre/get-category-requirements.js.map +1 -1
  89. package/dist/src/tools/mercadolibre/get-item-details.js.map +1 -1
  90. package/dist/src/tools/mercadolibre/get-item-visits.js.map +1 -1
  91. package/dist/src/tools/mercadolibre/get-listing-quality.js.map +1 -1
  92. package/dist/src/tools/mercadolibre/get-order-details.js.map +1 -1
  93. package/dist/src/tools/mercadolibre/get-orders-summary.js +366 -28
  94. package/dist/src/tools/mercadolibre/get-orders-summary.js.map +2 -2
  95. package/dist/src/tools/mercadolibre/get-sales-by-item.js.map +1 -1
  96. package/dist/src/tools/mercadolibre/get-sales-trend.js.map +1 -1
  97. package/dist/src/tools/mercadolibre/get-shipping-summary.js.map +1 -1
  98. package/dist/src/tools/mercadolibre/get-store-performance.js.map +1 -1
  99. package/dist/src/tools/mercadolibre/helpers.js.map +1 -1
  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 +1 -1
  178. package/public/icon.svg +6 -6
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/benchmarks.ts"],
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"],
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"],
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';\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"],
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"],
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;\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"],
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"],
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\";\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"],
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"],
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\";\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"],
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"],
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\";\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"],
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"],
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\";\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"],
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"],
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\";\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"],
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"],
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\";\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"],
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"],
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\";\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"],
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"],
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\";\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"],
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"],
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
  }
@@ -0,0 +1,78 @@
1
+ import {
2
+ getDefaultGoogleAdsLoginCustomerId,
3
+ getGoogleAdsDeveloperToken
4
+ } from "../config/google.js";
5
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
6
+ function normalizeCustomerId(customerId) {
7
+ const normalized = customerId.trim().replace(/-/g, "");
8
+ if (!/^\d+$/.test(normalized)) {
9
+ throw new Error("Invalid Google Ads customer ID. Use digits only, with or without hyphens.");
10
+ }
11
+ return normalized;
12
+ }
13
+ function resolveGoogleAdsCustomerId(customerId) {
14
+ if (!customerId?.trim()) {
15
+ throw new Error("Missing Google Ads customer ID. Provide customerId.");
16
+ }
17
+ return normalizeCustomerId(customerId);
18
+ }
19
+ function resolveGoogleAdsLoginCustomerId(loginCustomerId) {
20
+ const explicitCustomerId = loginCustomerId?.trim();
21
+ if (explicitCustomerId) {
22
+ return normalizeCustomerId(explicitCustomerId);
23
+ }
24
+ return getDefaultGoogleAdsLoginCustomerId();
25
+ }
26
+ function resolveGoogleAdsDeveloperToken() {
27
+ return getGoogleAdsDeveloperToken();
28
+ }
29
+ function parseGoogleAdsMetricValue(value) {
30
+ if (typeof value === "number" && Number.isFinite(value)) {
31
+ return value;
32
+ }
33
+ if (typeof value === "string") {
34
+ const parsedValue = Number(value);
35
+ return Number.isFinite(parsedValue) ? parsedValue : 0;
36
+ }
37
+ return 0;
38
+ }
39
+ function microsToCurrency(value, decimals = 2) {
40
+ return round(parseGoogleAdsMetricValue(value) / 1e6, decimals);
41
+ }
42
+ function round(value, decimals = 2) {
43
+ if (!Number.isFinite(value)) {
44
+ return 0;
45
+ }
46
+ const factor = 10 ** decimals;
47
+ return Math.round(value * factor) / factor;
48
+ }
49
+ function toPercent(numerator, denominator) {
50
+ if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) {
51
+ return 0;
52
+ }
53
+ return numerator / denominator * 100;
54
+ }
55
+ function getFieldValue(row, path, fallback) {
56
+ const segments = path.split(".");
57
+ let current = row;
58
+ for (const segment of segments) {
59
+ if (!current || typeof current !== "object") {
60
+ return fallback;
61
+ }
62
+ current = current[segment];
63
+ }
64
+ return current ?? fallback;
65
+ }
66
+ export {
67
+ dateRegex,
68
+ getFieldValue,
69
+ microsToCurrency,
70
+ normalizeCustomerId,
71
+ parseGoogleAdsMetricValue,
72
+ resolveGoogleAdsCustomerId,
73
+ resolveGoogleAdsDeveloperToken,
74
+ resolveGoogleAdsLoginCustomerId,
75
+ round,
76
+ toPercent
77
+ };
78
+ //# sourceMappingURL=report-utils.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/google-ads/report-utils.ts"],
4
+ "sourcesContent": ["import {\n getDefaultGoogleAdsLoginCustomerId,\n getGoogleAdsDeveloperToken,\n} from \"../config/google.js\";\nimport type { GoogleAdsMetricValue, GoogleAdsSearchRow } from \"../services/google-ads/google-ads-client.js\";\n\nexport const dateRegex = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nexport function normalizeCustomerId(customerId: string): string {\n const normalized = customerId.trim().replace(/-/g, \"\");\n if (!/^\\d+$/.test(normalized)) {\n throw new Error(\"Invalid Google Ads customer ID. Use digits only, with or without hyphens.\");\n }\n\n return normalized;\n}\n\nexport function resolveGoogleAdsCustomerId(customerId?: string): string {\n if (!customerId?.trim()) {\n throw new Error(\"Missing Google Ads customer ID. Provide customerId.\");\n }\n\n return normalizeCustomerId(customerId);\n}\n\nexport function resolveGoogleAdsLoginCustomerId(loginCustomerId?: string): string | undefined {\n const explicitCustomerId = loginCustomerId?.trim();\n if (explicitCustomerId) {\n return normalizeCustomerId(explicitCustomerId);\n }\n\n return getDefaultGoogleAdsLoginCustomerId();\n}\n\nexport function resolveGoogleAdsDeveloperToken(): string {\n return getGoogleAdsDeveloperToken();\n}\n\nexport function parseGoogleAdsMetricValue(value: GoogleAdsMetricValue | 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 microsToCurrency(value: GoogleAdsMetricValue | undefined, decimals = 2): number {\n return round(parseGoogleAdsMetricValue(value) / 1_000_000, decimals);\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 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 getFieldValue<T>(row: GoogleAdsSearchRow, path: string, fallback?: T): T | undefined {\n const segments = path.split(\".\");\n let current: unknown = row;\n\n for (const segment of segments) {\n if (!current || typeof current !== \"object\") {\n return fallback;\n }\n\n current = (current as Record<string, unknown>)[segment];\n }\n\n return (current as T | undefined) ?? fallback;\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAGA,MAAM,YAAY;AAElB,SAAS,oBAAoB,YAA4B;AAC9D,QAAM,aAAa,WAAW,KAAK,EAAE,QAAQ,MAAM,EAAE;AACrD,MAAI,CAAC,QAAQ,KAAK,UAAU,GAAG;AAC7B,UAAM,IAAI,MAAM,2EAA2E;AAAA,EAC7F;AAEA,SAAO;AACT;AAEO,SAAS,2BAA2B,YAA6B;AACtE,MAAI,CAAC,YAAY,KAAK,GAAG;AACvB,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AAEA,SAAO,oBAAoB,UAAU;AACvC;AAEO,SAAS,gCAAgC,iBAA8C;AAC5F,QAAM,qBAAqB,iBAAiB,KAAK;AACjD,MAAI,oBAAoB;AACtB,WAAO,oBAAoB,kBAAkB;AAAA,EAC/C;AAEA,SAAO,mCAAmC;AAC5C;AAEO,SAAS,iCAAyC;AACvD,SAAO,2BAA2B;AACpC;AAEO,SAAS,0BAA0B,OAAiD;AACzF,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,cAAc,OAAO,KAAK;AAChC,WAAO,OAAO,SAAS,WAAW,IAAI,cAAc;AAAA,EACtD;AAEA,SAAO;AACT;AAEO,SAAS,iBAAiB,OAAyC,WAAW,GAAW;AAC9F,SAAO,MAAM,0BAA0B,KAAK,IAAI,KAAW,QAAQ;AACrE;AAEO,SAAS,MAAM,OAAe,WAAW,GAAW;AACzD,MAAI,CAAC,OAAO,SAAS,KAAK,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,MAAM;AACrB,SAAO,KAAK,MAAM,QAAQ,MAAM,IAAI;AACtC;AAEO,SAAS,UAAU,WAAmB,aAA6B;AACxE,MAAI,CAAC,OAAO,SAAS,SAAS,KAAK,CAAC,OAAO,SAAS,WAAW,KAAK,eAAe,GAAG;AACpF,WAAO;AAAA,EACT;AAEA,SAAQ,YAAY,cAAe;AACrC;AAEO,SAAS,cAAiB,KAAyB,MAAc,UAA6B;AACnG,QAAM,WAAW,KAAK,MAAM,GAAG;AAC/B,MAAI,UAAmB;AAEvB,aAAW,WAAW,UAAU;AAC9B,QAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,aAAO;AAAA,IACT;AAEA,cAAW,QAAoC,OAAO;AAAA,EACxD;AAEA,SAAQ,WAA6B;AACvC;",
6
+ "names": []
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\";\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"],
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"],
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\";\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"],
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"],
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
  }