@productmaker/mcp 1.16.2 → 1.16.3

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 (3) hide show
  1. package/dist/http.js +413 -317
  2. package/dist/stdio.js +413 -317
  3. package/package.json +4 -4
package/dist/stdio.js CHANGED
@@ -129,10 +129,10 @@ var ApiClient = class {
129
129
  };
130
130
 
131
131
  // src/index.ts
132
- var MCP_VERSION = "1.16.2";
132
+ var MCP_VERSION = "1.16.3";
133
133
 
134
134
  // src/tools/tasks.ts
135
- import { z as z38 } from "zod";
135
+ import { z as z39 } from "zod";
136
136
 
137
137
  // src/resolve-image.ts
138
138
  import { lookup as dnsLookup } from "dns/promises";
@@ -3285,8 +3285,21 @@ import { z as z31 } from "zod";
3285
3285
  // ../../packages/types/src/campaigns.ts
3286
3286
  import { z as z30 } from "zod";
3287
3287
  var CAMPAIGN_LIMITS = {
3288
- /** Maximum number of creatives per campaign */
3289
- MAX_CREATIVES: 4,
3288
+ /**
3289
+ * Maximum number of creatives (= ads) per campaign.
3290
+ *
3291
+ * Not a Meta limit — Meta allows far more ads per ad set. This is our own
3292
+ * bound on how many uploads one publish call will push through the Graph API
3293
+ * in a single request. It was 4 with no stated reason, which forced callers
3294
+ * with a normal batch (3 videos + 3 images) to drop two by hand.
3295
+ */
3296
+ MAX_CREATIVES: 10,
3297
+ /**
3298
+ * Maximum ad sets per campaign. Distinct from MAX_CREATIVES, which it used
3299
+ * to share a constant with — that coupling meant raising the creative cap
3300
+ * would silently raise the ad-set cap too.
3301
+ */
3302
+ MAX_AD_SETS: 4,
3290
3303
  /** Maximum video duration in seconds */
3291
3304
  MAX_VIDEO_DURATION_SECONDS: 180,
3292
3305
  /** Maximum file size in bytes (350MB) */
@@ -3329,8 +3342,21 @@ var CampaignCreativeType = z30.enum([
3329
3342
  var CampaignCreative = z30.object({
3330
3343
  /** Unique identifier for the creative */
3331
3344
  id: z30.string(),
3332
- /** The uploaded file (null when loaded from server) */
3333
- file: z30.instanceof(File).nullable(),
3345
+ /**
3346
+ * The uploaded file (null when loaded from server).
3347
+ *
3348
+ * Deliberately NOT `z.instanceof(File)`: `File` is a browser global that Node
3349
+ * only exposes from v20. `z.instanceof` dereferences it while the schema is
3350
+ * being *constructed*, so merely importing this module threw
3351
+ * `ReferenceError: File is not defined` on Node 18 — which killed the whole
3352
+ * MCP CLI on startup, because its bundle pulls this barrel in for unrelated
3353
+ * types and never touches this schema. `z.custom` defers the check to
3354
+ * validation time and guards the global, so the module imports on any runtime
3355
+ * and only the browser upload path needs a real `File`.
3356
+ */
3357
+ file: z30.custom((value) => typeof File !== "undefined" && value instanceof File, {
3358
+ message: "Expected a File"
3359
+ }).nullable(),
3334
3360
  /** Type of media: image or video */
3335
3361
  type: CampaignCreativeType,
3336
3362
  /** Creative title (max 120 chars) - Maps to Meta's "Título" */
@@ -3439,6 +3465,9 @@ var HttpUrl = z30.string().url().refine((value) => {
3439
3465
  return false;
3440
3466
  }
3441
3467
  }, "URL must use http or https");
3468
+ var MetaAdAccountId = z30.string().trim().min(1, "Debes seleccionar una cuenta publicitaria").transform((value) => /^\d+$/.test(value) ? `act_${value}` : value).refine((value) => /^act_\d+$/.test(value), {
3469
+ message: 'adAccountId must look like "act_1234567890" (a bare numeric id is also accepted)'
3470
+ });
3442
3471
  var MetaCampaignDestination = z30.discriminatedUnion("type", [
3443
3472
  z30.object({
3444
3473
  type: z30.literal("landing"),
@@ -3471,7 +3500,7 @@ var MetaCampaignConfigBase = z30.object({
3471
3500
  campaignDailyBudget: z30.number().int().positive().optional(),
3472
3501
  /** Omitted preserves the historical recommended-floor behavior. */
3473
3502
  budgetPolicy: MetaBudgetPolicy.optional(),
3474
- adSets: z30.array(MetaAdSetConfig).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3503
+ adSets: z30.array(MetaAdSetConfig).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
3475
3504
  destination: MetaCampaignDestination,
3476
3505
  saveAsDefault: z30.boolean().default(true)
3477
3506
  });
@@ -3504,7 +3533,7 @@ var MetaCampaignConfig = MetaCampaignConfigBase.superRefine((value, ctx) => {
3504
3533
  var MetaCampaignDefaults = MetaCampaignConfigBase.omit({
3505
3534
  saveAsDefault: true
3506
3535
  }).extend({
3507
- adSets: z30.array(MetaAdSetDefaults).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3536
+ adSets: z30.array(MetaAdSetDefaults).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
3508
3537
  /**
3509
3538
  * Ad-account currency (ISO 4217: USD/COP/MXN/...) at the time the user saved
3510
3539
  * these defaults. The wizard re-uses the saved budget only when the current
@@ -3533,7 +3562,7 @@ var UploadToMetaRequest = z30.object({
3533
3562
  thumbnailUrl: HttpUrl.optional()
3534
3563
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3535
3564
  /** Meta Ad Account ID */
3536
- adAccountId: z30.string().min(1),
3565
+ adAccountId: MetaAdAccountId,
3537
3566
  /** Optional Meta Pixel ID */
3538
3567
  pixelId: z30.string().optional(),
3539
3568
  /** Landing page URL (required for landing destinations; omitted for whatsapp) */
@@ -3615,7 +3644,7 @@ var MetaCampaignOverridesSchema = z31.object({
3615
3644
  destination: MetaCampaignDestination.optional()
3616
3645
  });
3617
3646
  var FacebookCreateCampaignBodySchema = z31.object({
3618
- adAccountId: z31.string().min(1, "Debes seleccionar una cuenta publicitaria"),
3647
+ adAccountId: MetaAdAccountId,
3619
3648
  pixelId: z31.string().optional(),
3620
3649
  landingUrl: HttpUrl2.optional(),
3621
3650
  campaignConfig: MetaCampaignConfig.optional(),
@@ -3691,8 +3720,8 @@ var FacebookCreateCampaignFromCreativesBodySchema = z31.object({
3691
3720
  mediaUrl: HttpUrl2,
3692
3721
  thumbnailUrl: HttpUrl2.optional(),
3693
3722
  creativeId: z31.string().min(1).optional()
3694
- })).min(1).max(4),
3695
- adAccountId: z31.string().min(1),
3723
+ })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3724
+ adAccountId: MetaAdAccountId,
3696
3725
  pixelId: z31.string().optional(),
3697
3726
  /** Required unless campaignConfig.destination is 'whatsapp' (see superRefine below). */
3698
3727
  landingUrl: HttpUrl2.optional(),
@@ -3807,8 +3836,8 @@ var FacebookCreateCampaignFromTasksBodySchema = z31.object({
3807
3836
  description: z31.string().max(500).optional(),
3808
3837
  cta: MetaCtaType.optional(),
3809
3838
  creativeId: z31.string().min(1).optional()
3810
- })).min(1).max(4),
3811
- adAccountId: z31.string().min(1),
3839
+ })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3840
+ adAccountId: MetaAdAccountId,
3812
3841
  pixelId: z31.string().optional(),
3813
3842
  landingUrl: HttpUrl2.optional(),
3814
3843
  targetCountry: z31.string().length(2).default("US"),
@@ -6361,6 +6390,43 @@ var RegenerateEditRequestSchema = z36.object({
6361
6390
  comment: z36.string().min(1).max(2e3)
6362
6391
  });
6363
6392
 
6393
+ // ../../packages/types/src/scene-subject-contract.ts
6394
+ import { z as z37 } from "zod";
6395
+ var SceneSubjectContractSchema = z37.object({
6396
+ maxPeople: z37.number().int().min(1).max(10),
6397
+ minPeople: z37.number().int().min(0).max(10),
6398
+ minDistinctPeople: z37.number().int().min(0).max(10),
6399
+ maxHands: z37.number().int().min(0).max(20)
6400
+ }).superRefine((value, ctx) => {
6401
+ if (value.minPeople > value.maxPeople) {
6402
+ ctx.addIssue({
6403
+ code: z37.ZodIssueCode.custom,
6404
+ path: [
6405
+ "minPeople"
6406
+ ],
6407
+ message: "minPeople cannot exceed maxPeople"
6408
+ });
6409
+ }
6410
+ if (value.minDistinctPeople > value.maxPeople) {
6411
+ ctx.addIssue({
6412
+ code: z37.ZodIssueCode.custom,
6413
+ path: [
6414
+ "minDistinctPeople"
6415
+ ],
6416
+ message: "minDistinctPeople cannot exceed maxPeople"
6417
+ });
6418
+ }
6419
+ if (value.minDistinctPeople > value.minPeople) {
6420
+ ctx.addIssue({
6421
+ code: z37.ZodIssueCode.custom,
6422
+ path: [
6423
+ "minDistinctPeople"
6424
+ ],
6425
+ message: "minDistinctPeople cannot exceed minPeople"
6426
+ });
6427
+ }
6428
+ });
6429
+
6364
6430
  // src/resolve-media.ts
6365
6431
  var MAX_MEDIA_BYTES = CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES;
6366
6432
  var MEDIA_FORMATS = [
@@ -6600,10 +6666,10 @@ async function resolveMediaRef(ref, opts) {
6600
6666
  __name(resolveMediaRef, "resolveMediaRef");
6601
6667
 
6602
6668
  // src/tools/shared.ts
6603
- import { z as z37 } from "zod";
6604
- var Lang = z37.string().min(2).max(10);
6605
- var Country = z37.string().min(2).max(3);
6606
- var LandingStrategyEnum = z37.enum([
6669
+ import { z as z38 } from "zod";
6670
+ var Lang = z38.string().min(2).max(10);
6671
+ var Country = z38.string().min(2).max(3);
6672
+ var LandingStrategyEnum = z38.enum([
6607
6673
  "auto",
6608
6674
  "classic",
6609
6675
  "pain_solution",
@@ -6612,12 +6678,12 @@ var LandingStrategyEnum = z37.enum([
6612
6678
  "offer_value",
6613
6679
  "lifestyle_identity"
6614
6680
  ]);
6615
- var LandingImageFormatEnum = z37.enum([
6681
+ var LandingImageFormatEnum = z38.enum([
6616
6682
  "9:16",
6617
6683
  "4:5",
6618
6684
  "1:1"
6619
6685
  ]);
6620
- var VideoPresetEnum = z37.enum([
6686
+ var VideoPresetEnum = z38.enum([
6621
6687
  "auto",
6622
6688
  "ugc_dynamic",
6623
6689
  "ugc_unboxing_chaos",
@@ -6628,7 +6694,7 @@ var VideoPresetEnum = z37.enum([
6628
6694
  "pixar_problem",
6629
6695
  "pixar_sassy"
6630
6696
  ]);
6631
- var TaskCreativeVariantVideoPresetEnum = z37.enum([
6697
+ var TaskCreativeVariantVideoPresetEnum = z38.enum([
6632
6698
  "auto",
6633
6699
  "ugc_dynamic",
6634
6700
  "problem_solution",
@@ -6687,7 +6753,7 @@ function resolveVideoPreset(preset) {
6687
6753
  return VIDEO_PRESET_RESOLUTION[preset] ?? null;
6688
6754
  }
6689
6755
  __name(resolveVideoPreset, "resolveVideoPreset");
6690
- var VideoTierEnum = z37.enum([
6756
+ var VideoTierEnum = z38.enum([
6691
6757
  "basic"
6692
6758
  ]);
6693
6759
  function resolveVideoTier(_tier) {
@@ -6702,17 +6768,17 @@ function resolveVideoFormatForDuration(durationSeconds) {
6702
6768
  return durationSeconds === 30 ? "double_30s" : "single_15s";
6703
6769
  }
6704
6770
  __name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
6705
- var VideoResolutionEnum = z37.enum([
6771
+ var VideoResolutionEnum = z38.enum([
6706
6772
  "480p",
6707
6773
  "720p"
6708
6774
  ]);
6709
- var VideoStyleEnum = z37.enum([
6775
+ var VideoStyleEnum = z38.enum([
6710
6776
  "ugc",
6711
6777
  "cartoon_3d",
6712
6778
  "product_only",
6713
6779
  "ai_dynamic"
6714
6780
  ]);
6715
- var NarrativeStyleEnum = z37.enum([
6781
+ var NarrativeStyleEnum = z38.enum([
6716
6782
  "review",
6717
6783
  "problem_product_cta",
6718
6784
  "product_showcase",
@@ -6727,7 +6793,7 @@ var NarrativeStyleEnum = z37.enum([
6727
6793
  "ugc_skeptic_confession",
6728
6794
  "ugc_demo_review"
6729
6795
  ]);
6730
- var PublicNarrativeStyleEnum = z37.enum([
6796
+ var PublicNarrativeStyleEnum = z38.enum([
6731
6797
  "review",
6732
6798
  "ugc_unboxing_chaos",
6733
6799
  "problem_product_cta",
@@ -6737,7 +6803,7 @@ var PublicNarrativeStyleEnum = z37.enum([
6737
6803
  "animated_problem_pixar",
6738
6804
  "sassy_product_pixar"
6739
6805
  ]);
6740
- var VideoModelEnum = z37.enum([
6806
+ var VideoModelEnum = z38.enum([
6741
6807
  "veo",
6742
6808
  "wan",
6743
6809
  "bytedance",
@@ -6745,25 +6811,25 @@ var VideoModelEnum = z37.enum([
6745
6811
  "grok",
6746
6812
  "kling"
6747
6813
  ]);
6748
- var ProductReferenceUserChoiceEnum = z37.enum([
6814
+ var ProductReferenceUserChoiceEnum = z38.enum([
6749
6815
  "same_product",
6750
6816
  "variants",
6751
6817
  "different_products",
6752
6818
  "combo_distinct"
6753
6819
  ]);
6754
- var ProductReferenceCandidateGroupingEnum = z37.enum([
6820
+ var ProductReferenceCandidateGroupingEnum = z38.enum([
6755
6821
  "same_product_same_variant",
6756
6822
  "same_product_different_variants",
6757
6823
  "different_products",
6758
6824
  "unclear"
6759
6825
  ]);
6760
- var GapDurationChoiceMirror = z37.object({
6761
- angleIndex: z37.number().int().nonnegative(),
6762
- recommendedSec: z37.union([
6763
- z37.literal(15),
6764
- z37.literal(30)
6826
+ var GapDurationChoiceMirror = z38.object({
6827
+ angleIndex: z38.number().int().nonnegative(),
6828
+ recommendedSec: z38.union([
6829
+ z38.literal(15),
6830
+ z38.literal(30)
6765
6831
  ]),
6766
- estimatedDeltaCredits: z37.number().int().nonnegative().optional()
6832
+ estimatedDeltaCredits: z38.number().int().nonnegative().optional()
6767
6833
  });
6768
6834
  function buildSelectedImageUrlsHash(urls) {
6769
6835
  const input = [
@@ -6777,13 +6843,13 @@ function buildSelectedImageUrlsHash(urls) {
6777
6843
  return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
6778
6844
  }
6779
6845
  __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
6780
- var ImageCreativeStyleEnum = z37.enum([
6846
+ var ImageCreativeStyleEnum = z38.enum([
6781
6847
  "studio",
6782
6848
  "floating",
6783
6849
  "ingredients",
6784
6850
  "in_use"
6785
6851
  ]);
6786
- var ImageCreativeArchetypeEnum = z37.enum([
6852
+ var ImageCreativeArchetypeEnum = z38.enum([
6787
6853
  "direct_response_product_ad",
6788
6854
  "hero_outcome",
6789
6855
  "offer_value_prop",
@@ -6792,23 +6858,23 @@ var ImageCreativeArchetypeEnum = z37.enum([
6792
6858
  "problem_solution",
6793
6859
  "lifestyle_identity"
6794
6860
  ]);
6795
- var ImageCreativeVisualTreatmentEnum = z37.enum([
6861
+ var ImageCreativeVisualTreatmentEnum = z38.enum([
6796
6862
  "performance_product_ad",
6797
6863
  "premium_dtc_carousel"
6798
6864
  ]);
6799
- var ImageCreativeAspectRatioEnum = z37.enum([
6865
+ var ImageCreativeAspectRatioEnum = z38.enum([
6800
6866
  "1:1",
6801
6867
  "4:5",
6802
6868
  "9:16",
6803
6869
  "16:9"
6804
6870
  ]);
6805
- var ImageRefSchema = z37.object({
6806
- url: z37.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
6807
- path: z37.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.')
6871
+ var ImageRefSchema = z38.object({
6872
+ url: z38.string().url().optional().describe('URL p\xFAblica HTTPS de la foto. Ej: una URL de Shopify CDN, Cloudinary, Imgur. \xDAsala cuando el usuario te diga "est\xE1 en X.com/foto.jpg" o cuando la foto ya est\xE9 hospedada.'),
6873
+ path: z38.string().min(1).optional().describe('Ruta del archivo en el computador del usuario. Ej: "/Users/maria/Desktop/foto.jpg", "~/Pictures/camiseta.png", "C:\\Users\\Maria\\Desktop\\foto.jpg". El MCP local la lee del disco y la sube. SOLO funciona cuando el MCP corre local (stdio). NO funciona en mcp.productmaker.app (HTTP) \u2014 ah\xED usa `url`.')
6808
6874
  }).refine((v) => Boolean(v.url) !== Boolean(v.path), {
6809
6875
  message: "each image entry must have exactly one of `url` or `path`"
6810
6876
  });
6811
- var ImagesInput = z37.array(ImageRefSchema).min(1).max(5).describe('1 a 5 fotos del MISMO producto. La PRIMERA es la principal (hero); las dem\xE1s son \xE1ngulos adicionales. NO mezcles productos distintos en una sola llamada.\n\nCada entrada tiene EXACTAMENTE uno de:\n\u2022 `url` \u2014 URL p\xFAblica HTTPS (Shopify CDN, Cloudinary). Prefi\xE9rela si la foto ya est\xE1 hospedada.\n\u2022 `path` \u2014 Ruta del archivo en el computador del usuario (ej. "/Users/maria/Desktop/foto.jpg", "~/Downloads/camiseta.png"). \xDAsala cuando el usuario te dijo d\xF3nde guard\xF3 la foto. Solo MCP local \u2014 el cliente web rechaza paths.\n\nSi el usuario te peg\xF3 la foto en el chat pero no te dio ni URL ni ruta: PREG\xDANTALE d\xF3nde la tiene guardada (Escritorio, Descargas, etc.). Si el `path` falla con IMAGE_PATH_NOT_FOUND, prueba 1-2 ubicaciones obvias alternativas (~/Desktop, ~/Downloads) antes de molestar al usuario. Tras 3 intentos fallidos, p\xEDdele la ruta completa.');
6877
+ var ImagesInput = z38.array(ImageRefSchema).min(1).max(5).describe('1 a 5 fotos del MISMO producto. La PRIMERA es la principal (hero); las dem\xE1s son \xE1ngulos adicionales. NO mezcles productos distintos en una sola llamada.\n\nCada entrada tiene EXACTAMENTE uno de:\n\u2022 `url` \u2014 URL p\xFAblica HTTPS (Shopify CDN, Cloudinary). Prefi\xE9rela si la foto ya est\xE1 hospedada.\n\u2022 `path` \u2014 Ruta del archivo en el computador del usuario (ej. "/Users/maria/Desktop/foto.jpg", "~/Downloads/camiseta.png"). \xDAsala cuando el usuario te dijo d\xF3nde guard\xF3 la foto. Solo MCP local \u2014 el cliente web rechaza paths.\n\nSi el usuario te peg\xF3 la foto en el chat pero no te dio ni URL ni ruta: PREG\xDANTALE d\xF3nde la tiene guardada (Escritorio, Descargas, etc.). Si el `path` falla con IMAGE_PATH_NOT_FOUND, prueba 1-2 ubicaciones obvias alternativas (~/Desktop, ~/Downloads) antes de molestar al usuario. Tras 3 intentos fallidos, p\xEDdele la ruta completa.');
6812
6878
  function asText(value) {
6813
6879
  return {
6814
6880
  content: [
@@ -7186,6 +7252,11 @@ function resolveMessage(code, backendMessage) {
7186
7252
  return `The service returned an error (code: ${code}). Try again shortly or contact support if it persists.`;
7187
7253
  }
7188
7254
  __name(resolveMessage, "resolveMessage");
7255
+ function invalidInputFromZod(error) {
7256
+ const detail = error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
7257
+ return mcp(McpErrorCode.INVALID_INPUT, detail || resolveMessage(McpErrorCode.INVALID_INPUT, void 0));
7258
+ }
7259
+ __name(invalidInputFromZod, "invalidInputFromZod");
7189
7260
  function translateError(err) {
7190
7261
  if (err instanceof McpError) {
7191
7262
  return err;
@@ -7407,66 +7478,66 @@ function renderOutputsMarkdown(outputs) {
7407
7478
  __name(renderOutputsMarkdown, "renderOutputsMarkdown");
7408
7479
 
7409
7480
  // src/tools/tasks.ts
7410
- var CreateProductTaskInput = z38.object({
7481
+ var CreateProductTaskInput = z39.object({
7411
7482
  images: ImagesInput,
7412
- productUrl: z38.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
7483
+ productUrl: z39.string().url().optional().describe("Opcional. URL de la p\xE1gina del producto (Shopify, AliExpress, Amazon, etc.) para enriquecer el contexto. Si se omite, la IA infiere todo desde la imagen."),
7413
7484
  language: Lang.describe('C\xF3digo de idioma para el contenido generado. Ejemplos: "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas "es" por default.'),
7414
7485
  country: Country.describe('C\xF3digo ISO del pa\xEDs objetivo. Ejemplos: "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago locales y tono. REQUERIDO \u2014 si el usuario no lo especific\xF3, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat.'),
7415
- price: z38.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
7416
- compareAtPrice: z38.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
7417
- offer: z38.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
7418
- paymentMethods: z38.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
7419
- productContext: z38.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
7420
- productLabel: z38.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
7486
+ price: z39.string().min(1).max(120).optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
7487
+ compareAtPrice: z39.string().min(1).max(120).optional().describe('Opcional. Precio anterior para comunicar un descuento real en landing e im\xE1genes. Ej. "$129.000" cuando price="$89.900".'),
7488
+ offer: z39.string().min(1).max(240).optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
7489
+ paymentMethods: z39.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
7490
+ productContext: z39.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
7491
+ productLabel: z39.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
7421
7492
  videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). Los presets funcionan a 15s y 30s; 30s usa el ritmo Multiescena. Opciones: "auto", "ugc_dynamic" (UGC testimonial), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones), "ugc_skeptic_confession" (esc\xE9ptico\u2192conversi\xF3n), "ugc_demo_review" (demo en uso), "problem_solution" (problema\u2192producto\u2192CTA), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla), "product_showcase" (solo producto, sin actor), "wearable_showcase" (ropa/accesorios puestos). Si el usuario pide algo fuera de esta lista (ej. "estilo anime"), p\xEDdele una opci\xF3n v\xE1lida.'),
7422
- durationSeconds: z38.union([
7423
- z38.literal(15),
7424
- z38.literal(30)
7493
+ durationSeconds: z39.union([
7494
+ z39.literal(15),
7495
+ z39.literal(30)
7425
7496
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
7426
- pauseForAngleReview: z38.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
7497
+ pauseForAngleReview: z39.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. \xDAsalo si quiere decidir 15/30s cuando aparezca un winner. Default false."),
7427
7498
  videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista" para 15s; al pedir durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
7428
7499
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez (~el doble de precio). Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
7429
- multiAngleVideos: z38.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
7500
+ multiAngleVideos: z39.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
7430
7501
  angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "ugc_unboxing_chaos", "problem_product_cta", "product_showcase", "wearable_showcase", "cinematic", "animated_problem_pixar", "sassy_product_pixar". angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para varios \xE1ngulos.'),
7431
- outputs: z38.array(z38.enum([
7502
+ outputs: z39.array(z39.enum([
7432
7503
  "landing",
7433
7504
  "video",
7434
7505
  "image"
7435
7506
  ])).min(2).max(3).optional().describe('Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). Para 1 solo output, usa generate_landing / generate_video_creative / generate_image_creatives.'),
7436
- competitorScanEnabled: z38.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
7437
- brandId: z38.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
7438
- creativeDirection: z38.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
7507
+ competitorScanEnabled: z39.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
7508
+ brandId: z39.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
7509
+ creativeDirection: z39.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
7439
7510
  landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution", "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity", "offer_value" o "lifestyle_identity". Default "classic".'),
7440
7511
  landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".')
7441
7512
  }).strict();
7442
- var GetTaskStatusInput = z38.object({
7443
- taskId: z38.string().min(1),
7444
- waitSeconds: z38.number().int().min(0).max(120).optional()
7445
- });
7446
- var ListTasksInput = z38.object({
7447
- limit: z38.number().int().min(1).max(50).optional(),
7448
- offset: z38.number().int().min(0).optional(),
7449
- search: z38.string().optional()
7450
- });
7451
- var EditTaskDraftInput = z38.object({
7452
- taskId: z38.string().min(1),
7453
- angleIndex: z38.number().int().min(0),
7454
- angle: z38.object({
7455
- angle: z38.string().min(1).max(480),
7456
- buyerPersona: z38.string().min(1).max(360),
7457
- actor: z38.string().min(1).max(360).optional(),
7458
- gender: z38.enum([
7513
+ var GetTaskStatusInput = z39.object({
7514
+ taskId: z39.string().min(1),
7515
+ waitSeconds: z39.number().int().min(0).max(120).optional()
7516
+ });
7517
+ var ListTasksInput = z39.object({
7518
+ limit: z39.number().int().min(1).max(50).optional(),
7519
+ offset: z39.number().int().min(0).optional(),
7520
+ search: z39.string().optional()
7521
+ });
7522
+ var EditTaskDraftInput = z39.object({
7523
+ taskId: z39.string().min(1),
7524
+ angleIndex: z39.number().int().min(0),
7525
+ angle: z39.object({
7526
+ angle: z39.string().min(1).max(480),
7527
+ buyerPersona: z39.string().min(1).max(360),
7528
+ actor: z39.string().min(1).max(360).optional(),
7529
+ gender: z39.enum([
7459
7530
  "female",
7460
7531
  "male"
7461
7532
  ]).optional(),
7462
- scriptNotes: z38.string().max(2e3).optional()
7533
+ scriptNotes: z39.string().max(2e3).optional()
7463
7534
  })
7464
7535
  });
7465
- var PendingActionPayload = z38.discriminatedUnion("kind", [
7466
- z38.object({
7467
- kind: z38.literal("product_reference_confirmation"),
7536
+ var PendingActionPayload = z39.discriminatedUnion("kind", [
7537
+ z39.object({
7538
+ kind: z39.literal("product_reference_confirmation"),
7468
7539
  userChoice: ProductReferenceUserChoiceEnum,
7469
- confirmedProductNames: z38.array(z38.string().min(1).max(120)).min(2).max(8).optional(),
7540
+ confirmedProductNames: z39.array(z39.string().min(1).max(120)).min(2).max(8).optional(),
7470
7541
  // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
7471
7542
  // reachable via MCP. Valid ONLY together with userChoice='variants' (same
7472
7543
  // product in several colors): renders one shot per color and stitches them,
@@ -7477,62 +7548,62 @@ var PendingActionPayload = z38.discriminatedUnion("kind", [
7477
7548
  // safe because multi-scene IS the intended render for a color variant set,
7478
7549
  // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
7479
7550
  // confirm call; the backend re-gates it (variants + variant_set only).
7480
- useMultiSceneShowcase: z38.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
7551
+ useMultiSceneShowcase: z39.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
7481
7552
  }),
7482
- z38.object({
7483
- kind: z38.literal("angle_review_approval"),
7553
+ z39.object({
7554
+ kind: z39.literal("angle_review_approval"),
7484
7555
  // All edits are optional — empty action body just approves the
7485
7556
  // backend-generated draft as-is. Field shapes mirror the angle-review
7486
7557
  // request contracts in @packages/types/wizard.ts; the handler routes to
7487
7558
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
7488
- customAngle: z38.string().min(1).max(480).optional(),
7559
+ customAngle: z39.string().min(1).max(480).optional(),
7489
7560
  // Keep the shared MCP action within the strictest downstream contract:
7490
7561
  // autopilot/image resume endpoints cap these fields at 1500 characters.
7491
- painPoints: z38.string().max(1500).optional(),
7492
- benefits: z38.string().max(1500).optional(),
7493
- features: z38.array(z38.string().min(1).max(300)).max(10).optional(),
7494
- gapDurationSeconds: z38.union([
7495
- z38.literal(15),
7496
- z38.literal(30)
7562
+ painPoints: z39.string().max(1500).optional(),
7563
+ benefits: z39.string().max(1500).optional(),
7564
+ features: z39.array(z39.string().min(1).max(300)).max(10).optional(),
7565
+ gapDurationSeconds: z39.union([
7566
+ z39.literal(15),
7567
+ z39.literal(30)
7497
7568
  ]).optional().describe("Opcional. Solo para aceptar expl\xEDcitamente la recomendaci\xF3n gapDurationChoice del status. 30 env\xEDa el override double_30s para el \xE1ngulo recomendado; 15 u omitido conserva la duraci\xF3n cotizada.")
7498
7569
  })
7499
7570
  ]);
7500
- var ResolvePendingActionInput = z38.object({
7501
- taskId: z38.string().min(1),
7571
+ var ResolvePendingActionInput = z39.object({
7572
+ taskId: z39.string().min(1),
7502
7573
  action: PendingActionPayload
7503
7574
  }).strict();
7504
7575
  function resolveVariantConfirmVideoTier(action) {
7505
7576
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
7506
7577
  }
7507
7578
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
7508
- var StatusForResolveSchema = z38.object({
7579
+ var StatusForResolveSchema = z39.object({
7509
7580
  // Metadata used to select the backend resume contract. Older API responses
7510
7581
  // may omit these fields, so the resolver keeps the generic fallback.
7511
- kind: z38.enum([
7582
+ kind: z39.enum([
7512
7583
  "autopilot",
7513
7584
  "video",
7514
7585
  "image",
7515
7586
  "landing",
7516
7587
  "mix"
7517
7588
  ]).optional(),
7518
- selectedOutputs: z38.array(z38.enum([
7589
+ selectedOutputs: z39.array(z39.enum([
7519
7590
  "landing",
7520
7591
  "video",
7521
7592
  "image"
7522
7593
  ])).optional(),
7523
- videoAngleCount: z38.number().int().min(1).max(3).optional(),
7524
- ugcFormat: z38.enum([
7594
+ videoAngleCount: z39.number().int().min(1).max(3).optional(),
7595
+ ugcFormat: z39.enum([
7525
7596
  "single_8s",
7526
7597
  "single_15s",
7527
7598
  "double_30s"
7528
7599
  ]).optional(),
7529
- videoStyle: z38.enum([
7600
+ videoStyle: z39.enum([
7530
7601
  "ugc",
7531
7602
  "cartoon_3d",
7532
7603
  "product_only",
7533
7604
  "ai_dynamic"
7534
7605
  ]).optional(),
7535
- videoModel: z38.enum([
7606
+ videoModel: z39.enum([
7536
7607
  "veo",
7537
7608
  "wan",
7538
7609
  "bytedance",
@@ -7540,26 +7611,26 @@ var StatusForResolveSchema = z38.object({
7540
7611
  "grok",
7541
7612
  "kling"
7542
7613
  ]).optional(),
7543
- videoTier: z38.enum([
7614
+ videoTier: z39.enum([
7544
7615
  "legacy",
7545
7616
  "standard",
7546
7617
  "pro",
7547
7618
  "single_call"
7548
7619
  ]).optional(),
7549
- videoResolution: z38.enum([
7620
+ videoResolution: z39.enum([
7550
7621
  "480p",
7551
7622
  "720p"
7552
7623
  ]).optional(),
7553
7624
  narrativeStyle: NarrativeStyleEnum.optional(),
7554
- pendingUserAction: z38.object({
7555
- kind: z38.string(),
7556
- candidateGrouping: z38.string().optional(),
7557
- selectedImageUrls: z38.array(z38.string()).optional(),
7558
- proposedProductNames: z38.array(z38.string()).optional(),
7559
- customAngleDraft: z38.string().optional(),
7560
- painPointsDraft: z38.string().optional(),
7561
- benefitsDraft: z38.string().optional(),
7562
- featuresDraft: z38.array(z38.string()).optional(),
7625
+ pendingUserAction: z39.object({
7626
+ kind: z39.string(),
7627
+ candidateGrouping: z39.string().optional(),
7628
+ selectedImageUrls: z39.array(z39.string()).optional(),
7629
+ proposedProductNames: z39.array(z39.string()).optional(),
7630
+ customAngleDraft: z39.string().optional(),
7631
+ painPointsDraft: z39.string().optional(),
7632
+ benefitsDraft: z39.string().optional(),
7633
+ featuresDraft: z39.array(z39.string()).optional(),
7563
7634
  // Recommendation consumed only when the caller explicitly sends
7564
7635
  // gapDurationSeconds: 30 in the angle approval action.
7565
7636
  gapDurationChoice: GapDurationChoiceMirror.optional()
@@ -7881,54 +7952,54 @@ __name(registerTaskTools, "registerTaskTools");
7881
7952
 
7882
7953
  // src/tools/creatives.ts
7883
7954
  import { randomUUID } from "crypto";
7884
- import { z as z39 } from "zod";
7955
+ import { z as z40 } from "zod";
7885
7956
  var CreativeRefs = {
7886
7957
  images: ImagesInput,
7887
7958
  language: Lang.describe('Idioma del contenido. Ej. "es", "en", "pt". REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
7888
7959
  country: Country.describe('Pa\xEDs objetivo ISO. Ej. "CO", "MX", "US", "AR". Define moneda, m\xE9todos de pago y tono. REQUERIDO \u2014 si el usuario no lo dijo, PREG\xDANTALE antes de invocar. NUNCA infieras desde el contexto del chat ni asumas un default.'),
7889
- price: z39.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
7890
- offer: z39.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
7891
- compareAtPrice: z39.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
7892
- paymentMethods: z39.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago a mostrar. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
7893
- productContext: z39.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
7894
- productLabel: z39.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
7895
- buyerPersona: z39.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
7896
- isCombo: z39.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
7897
- competitorScanEnabled: z39.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
7898
- brandId: z39.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer el creativo."),
7899
- creativeDirection: z39.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Aplica SOLO a im\xE1genes y landing. Ej. im\xE1genes: "amarillo, azul y rojo, minimalista"; landing: "elegante, paleta fr\xEDa". Para video usa scriptNotes (ambiente/escenario). M\xE1x 500 caracteres.')
7960
+ price: z40.string().min(1).max(120).optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
7961
+ offer: z40.string().min(1).max(240).optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si el usuario te dice "t\xFA elige", omite el campo.'),
7962
+ compareAtPrice: z40.string().min(1).max(120).optional().describe('Opcional. Precio anterior o de referencia para mostrar descuento. Ej. "$129.000" cuando price="$89.900".'),
7963
+ paymentMethods: z40.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago a mostrar. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
7964
+ productContext: z40.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
7965
+ productLabel: z40.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
7966
+ buyerPersona: z40.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
7967
+ isCombo: z40.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
7968
+ competitorScanEnabled: z40.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
7969
+ brandId: z40.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer el creativo."),
7970
+ creativeDirection: z40.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Aplica SOLO a im\xE1genes y landing. Ej. im\xE1genes: "amarillo, azul y rojo, minimalista"; landing: "elegante, paleta fr\xEDa". Para video usa scriptNotes (ambiente/escenario). M\xE1x 500 caracteres.')
7900
7971
  };
7901
- var VideoInput = z39.object({
7972
+ var VideoInput = z40.object({
7902
7973
  images: CreativeRefs.images,
7903
- title: z39.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
7904
- primaryAngle: z39.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
7974
+ title: z40.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
7975
+ primaryAngle: z40.string().min(1).describe('\xC1ngulo de venta principal (frase del beneficio). Ej. "Reduce arrugas en 14 d\xEDas" o "Para quienes odian las cremas grasosas".'),
7905
7976
  language: CreativeRefs.language,
7906
7977
  country: CreativeRefs.country,
7907
- aspectRatio: z39.enum([
7978
+ aspectRatio: z40.enum([
7908
7979
  "9:16",
7909
7980
  "16:9",
7910
7981
  "Auto"
7911
7982
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
7912
- durationSeconds: z39.union([
7913
- z39.literal(15),
7914
- z39.literal(30)
7983
+ durationSeconds: z40.union([
7984
+ z40.literal(15),
7985
+ z40.literal(30)
7915
7986
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
7916
7987
  videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). Los presets funcionan tanto a 15s como a 30s; el formato de 30s conserva el ritmo multi-escena. Opciones: "auto", "ugc_dynamic" (UGC cl\xE1sico testimonial multi-hook), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones numeradas), "ugc_skeptic_confession" (esc\xE9ptico\u2192conversi\xF3n), "ugc_demo_review" (demo en uso a lo largo de d\xEDas), "problem_solution" (problema\u2192producto\u2192CTA), "cinematic" (historia visual premium), "pixar_problem" (problema animado 3D estilo Pixar), "pixar_sassy" (producto que habla con personalidad), "product_showcase" (sin actor, solo producto), "wearable_showcase" (ropa/accesorios puestos).'),
7917
7988
  videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista"; para durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
7918
7989
  videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "720p" = m\xE1xima nitidez (RECOMENDADO, default si se omite); "480p" = render m\xE1s econ\xF3mico. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 720p).'),
7919
- features: z39.array(z39.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
7990
+ features: z40.array(z40.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
7920
7991
  price: CreativeRefs.price,
7921
7992
  offer: CreativeRefs.offer,
7922
7993
  productContext: CreativeRefs.productContext,
7923
7994
  productLabel: CreativeRefs.productLabel,
7924
- cta: z39.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
7925
- scriptNotes: z39.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
7926
- pauseForVideoScriptReview: z39.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
7927
- pauseForAngleReview: z39.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. Default false."),
7995
+ cta: z40.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
7996
+ scriptNotes: z40.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
7997
+ pauseForVideoScriptReview: z40.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
7998
+ pauseForAngleReview: z40.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo para que el usuario lo confirme o edite antes del render. Default false."),
7928
7999
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
7929
8000
  brandId: CreativeRefs.brandId
7930
8001
  }).strict();
7931
- var FreeVideoQualityEnum = z39.enum([
8002
+ var FreeVideoQualityEnum = z40.enum([
7932
8003
  "economical",
7933
8004
  "high_detail"
7934
8005
  ]);
@@ -7950,38 +8021,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
7950
8021
  15
7951
8022
  ]
7952
8023
  };
7953
- var FreeVideoInput = z39.object({
8024
+ var FreeVideoInput = z40.object({
7954
8025
  images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
7955
- rawPrompt: z39.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
8026
+ rawPrompt: z40.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
7956
8027
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
7957
- durationSeconds: z39.number().int().min(1).max(15).describe("Duraci\xF3n exacta. Econ\xF3mico permite 6, 8, 10 o 15s; Alto detalle permite 4, 8, 12 o 15s."),
8028
+ durationSeconds: z40.number().int().min(1).max(15).describe("Duraci\xF3n exacta. Econ\xF3mico permite 6, 8, 10 o 15s; Alto detalle permite 4, 8, 12 o 15s."),
7958
8029
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
7959
- generateAiMusic: z39.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
7960
- addSubtitles: z39.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
7961
- idempotencyKey: z39.string().uuid().optional().describe("Opcional. UUID para reintentar exactamente esta misma solicitud sin crear ni cobrar otro video. Reutiliza el mismo valor si el cliente no recibi\xF3 la respuesta.")
8030
+ generateAiMusic: z40.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
8031
+ addSubtitles: z40.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
8032
+ idempotencyKey: z40.string().uuid().optional().describe("Opcional. UUID para reintentar exactamente esta misma solicitud sin crear ni cobrar otro video. Reutiliza el mismo valor si el cliente no recibi\xF3 la respuesta.")
7962
8033
  }).strict();
7963
8034
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
7964
8035
  var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
7965
8036
  var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
7966
8037
  var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
7967
- var ImageInput = z39.object({
8038
+ var ImageInput = z40.object({
7968
8039
  images: CreativeRefs.images,
7969
- productTitle: z39.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
7970
- primaryAngle: z39.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
8040
+ productTitle: z40.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
8041
+ primaryAngle: z40.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
7971
8042
  buyerPersona: CreativeRefs.buyerPersona,
7972
8043
  language: CreativeRefs.language,
7973
8044
  country: CreativeRefs.country,
7974
- nVariants: z39.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
7975
- imageModel: z39.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
8045
+ nVariants: z40.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
8046
+ imageModel: z40.string().optional().describe('Opcional. Modelo de imagen. Ej. "gpt-image-2", "nano-banana-2". Om\xEDtelo para usar el default optimizado por costo/calidad.'),
7976
8047
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
7977
8048
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe(`Opcional. Arquetipo CRO de la composici\xF3n. Valores: ${archetypeList}. Si se omite, la IA escoge seg\xFAn \xE1ngulo+oferta (ej. "offer_value_prop" cuando hay descuento, "social_proof_trust" cuando el \xE1ngulo es testimonial).`),
7978
8049
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe(`Opcional. Tratamiento gr\xE1fico. Valores: ${visualTreatmentList} ("performance_product_ad" = dealer-style alto CTR para Meta/TikTok ads; "premium_dtc_carousel" = fotograf\xEDa limpia estilo Allbirds/Glossier). Si se omite, la IA escoge.`),
7979
- styleMix: z39.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
8050
+ styleMix: z40.boolean().optional().describe("Opcional. Si true, las N variantes mezclan distintos imageStyle para A/B test. Si false/omitido, todas comparten estilo. Default false."),
7980
8051
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe(`Opcional. Relaci\xF3n de aspecto. Valores: ${aspectRatioList}. Default "4:5" (vertical Instagram feed).`),
7981
- generateAdCopy: z39.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
7982
- pauseForAngleReview: z39.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo y antes de las im\xE1genes para que el usuario edite v\xEDa edit_task_draft. Default false."),
7983
- features: z39.array(z39.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
7984
- brandColors: z39.array(z39.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
8052
+ generateAdCopy: z40.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
8053
+ pauseForAngleReview: z40.boolean().optional().describe("Opcional. Si true, pausa despu\xE9s de generar el \xE1ngulo y antes de las im\xE1genes para que el usuario edite v\xEDa edit_task_draft. Default false."),
8054
+ features: z40.array(z40.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
8055
+ brandColors: z40.array(z40.string()).optional().describe('Opcional. Paleta de marca en hex. Ej. ["#FF6B35", "#004E89"]. Si se omite, la IA elige seg\xFAn el producto.'),
7985
8056
  price: CreativeRefs.price,
7986
8057
  compareAtPrice: CreativeRefs.compareAtPrice,
7987
8058
  offer: CreativeRefs.offer,
@@ -7992,11 +8063,11 @@ var ImageInput = z39.object({
7992
8063
  brandId: CreativeRefs.brandId,
7993
8064
  creativeDirection: CreativeRefs.creativeDirection
7994
8065
  });
7995
- var LandingInput = z39.object({
8066
+ var LandingInput = z40.object({
7996
8067
  images: CreativeRefs.images,
7997
8068
  language: CreativeRefs.language,
7998
8069
  country: CreativeRefs.country,
7999
- salesAngle: z39.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
8070
+ salesAngle: z40.string().optional().describe('Opcional. \xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas". Si se omite, la IA lo deduce de la imagen.'),
8000
8071
  buyerPersona: CreativeRefs.buyerPersona,
8001
8072
  price: CreativeRefs.price,
8002
8073
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -8094,8 +8165,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
8094
8165
  __name(registerCreativeTools, "registerCreativeTools");
8095
8166
 
8096
8167
  // src/tools/creative-variants.ts
8097
- import { z as z40 } from "zod";
8098
- var LandingSectionKeyEnum = z40.enum([
8168
+ import { z as z41 } from "zod";
8169
+ var LandingSectionKeyEnum = z41.enum([
8099
8170
  "problem_hook",
8100
8171
  "problem_cost",
8101
8172
  "solution_mechanism",
@@ -8135,24 +8206,24 @@ var LandingSectionKeyEnum = z40.enum([
8135
8206
  "lifestyle_fit",
8136
8207
  "join_lifestyle"
8137
8208
  ]);
8138
- var CreativeVariantKindEnum = z40.enum([
8209
+ var CreativeVariantKindEnum = z41.enum([
8139
8210
  "video",
8140
8211
  "image",
8141
8212
  "landing"
8142
8213
  ]);
8143
- var EstimateCreativeVariantInputBase = z40.object({
8144
- sourceTaskId: z40.string().uuid().describe("ID de la tarea original completada."),
8214
+ var EstimateCreativeVariantInputBase = z41.object({
8215
+ sourceTaskId: z41.string().uuid().describe("ID de la tarea original completada."),
8145
8216
  kind: CreativeVariantKindEnum,
8146
8217
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
8147
8218
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
8148
- nVariants: z40.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
8219
+ nVariants: z41.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
8149
8220
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
8150
- instructions: z40.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
8221
+ instructions: z41.string().trim().min(1).max(2e3).optional().describe("Opcional para landing. Si se env\xEDa sin sectionKey, el estimado suma la clasificaci\xF3n pagada con IA.")
8151
8222
  }).strict();
8152
8223
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
8153
8224
  if (input.kind === "video" && input.nVariants !== void 0) {
8154
8225
  ctx.addIssue({
8155
- code: z40.ZodIssueCode.custom,
8226
+ code: z41.ZodIssueCode.custom,
8156
8227
  path: [
8157
8228
  "nVariants"
8158
8229
  ],
@@ -8162,7 +8233,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8162
8233
  if (input.kind !== "video") {
8163
8234
  if (input.videoPreset !== void 0) {
8164
8235
  ctx.addIssue({
8165
- code: z40.ZodIssueCode.custom,
8236
+ code: z41.ZodIssueCode.custom,
8166
8237
  path: [
8167
8238
  "videoPreset"
8168
8239
  ],
@@ -8171,7 +8242,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8171
8242
  }
8172
8243
  if (input.videoResolution !== void 0) {
8173
8244
  ctx.addIssue({
8174
- code: z40.ZodIssueCode.custom,
8245
+ code: z41.ZodIssueCode.custom,
8175
8246
  path: [
8176
8247
  "videoResolution"
8177
8248
  ],
@@ -8181,7 +8252,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8181
8252
  }
8182
8253
  if (input.kind !== "image" && input.nVariants !== void 0) {
8183
8254
  ctx.addIssue({
8184
- code: z40.ZodIssueCode.custom,
8255
+ code: z41.ZodIssueCode.custom,
8185
8256
  path: [
8186
8257
  "nVariants"
8187
8258
  ],
@@ -8191,7 +8262,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8191
8262
  if (input.kind !== "landing") {
8192
8263
  if (input.sectionKey !== void 0) {
8193
8264
  ctx.addIssue({
8194
- code: z40.ZodIssueCode.custom,
8265
+ code: z41.ZodIssueCode.custom,
8195
8266
  path: [
8196
8267
  "sectionKey"
8197
8268
  ],
@@ -8200,7 +8271,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8200
8271
  }
8201
8272
  if (input.instructions !== void 0) {
8202
8273
  ctx.addIssue({
8203
- code: z40.ZodIssueCode.custom,
8274
+ code: z41.ZodIssueCode.custom,
8204
8275
  path: [
8205
8276
  "instructions"
8206
8277
  ],
@@ -8209,31 +8280,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8209
8280
  }
8210
8281
  }
8211
8282
  });
8212
- var CreateCreativeVariantInputBase = z40.object({
8213
- sourceTaskId: z40.string().uuid().describe("ID de la tarea original completada."),
8283
+ var CreateCreativeVariantInputBase = z41.object({
8284
+ sourceTaskId: z41.string().uuid().describe("ID de la tarea original completada."),
8214
8285
  kind: CreativeVariantKindEnum,
8215
- confirm: z40.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
8216
- clientRequestId: z40.string().uuid().describe("UUID estable y obligatorio para esta intenci\xF3n. Reutil\xEDzalo ante timeouts o transporte ambiguo. Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n y un UUID nuevo."),
8217
- angle: z40.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
8218
- buyerPersona: z40.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
8219
- scriptNotes: z40.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
8220
- creativeDirection: z40.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
8286
+ confirm: z41.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
8287
+ clientRequestId: z41.string().uuid().describe("UUID estable y obligatorio para esta intenci\xF3n. Reutil\xEDzalo ante timeouts o transporte ambiguo. Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n y un UUID nuevo."),
8288
+ angle: z41.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
8289
+ buyerPersona: z41.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
8290
+ scriptNotes: z41.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
8291
+ creativeDirection: z41.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
8221
8292
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
8222
8293
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
8223
- generateAiMusic: z40.boolean().optional().describe("Opcional para video."),
8224
- nVariants: z40.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
8294
+ generateAiMusic: z41.boolean().optional().describe("Opcional para video."),
8295
+ nVariants: z41.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
8225
8296
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
8226
8297
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
8227
8298
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
8228
8299
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
8229
- styleMix: z40.boolean().optional().describe("Opcional para imagen."),
8300
+ styleMix: z41.boolean().optional().describe("Opcional para imagen."),
8230
8301
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
8231
- instructions: z40.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
8302
+ instructions: z41.string().trim().min(1).max(2e3).optional().describe("Solicitud libre para una nueva secci\xF3n de landing. La IA la clasifica si no env\xEDas sectionKey.")
8232
8303
  }).strict();
8233
8304
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
8234
8305
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
8235
8306
  ctx.addIssue({
8236
- code: z40.ZodIssueCode.custom,
8307
+ code: z41.ZodIssueCode.custom,
8237
8308
  path: [
8238
8309
  "angle"
8239
8310
  ],
@@ -8242,7 +8313,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
8242
8313
  }
8243
8314
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
8244
8315
  ctx.addIssue({
8245
- code: z40.ZodIssueCode.custom,
8316
+ code: z41.ZodIssueCode.custom,
8246
8317
  path: [
8247
8318
  "instructions"
8248
8319
  ],
@@ -8288,7 +8359,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
8288
8359
  for (const field of fieldsByKind[input.kind]) {
8289
8360
  if (input[field] !== void 0) {
8290
8361
  ctx.addIssue({
8291
- code: z40.ZodIssueCode.custom,
8362
+ code: z41.ZodIssueCode.custom,
8292
8363
  path: [
8293
8364
  field
8294
8365
  ],
@@ -8405,27 +8476,27 @@ function registerCreativeVariantTools(server, api) {
8405
8476
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
8406
8477
 
8407
8478
  // src/tools/script-review.ts
8408
- import { z as z41 } from "zod";
8409
- var GetVideoScriptInput = z41.object({
8410
- taskId: z41.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
8411
- angleKey: z41.string().min(1).optional().describe('Clave del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). REQUERIDO para tareas multi-angle: si se omite, el backend resuelve a "angle_0" LITERAL (no "el primer pendiente"), as\xED que en multi-angle donde angle_0 ya est\xE9 aprobado/saltado obtendr\xEDas status="approved" y nunca alcanzar\xEDas el \xE1ngulo realmente pendiente. Para single-angle puede omitirse.')
8479
+ import { z as z42 } from "zod";
8480
+ var GetVideoScriptInput = z42.object({
8481
+ taskId: z42.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
8482
+ angleKey: z42.string().min(1).optional().describe('Clave del \xE1ngulo cuyo guion se quiere leer (formato "angle_0", "angle_1", ...). REQUERIDO para tareas multi-angle: si se omite, el backend resuelve a "angle_0" LITERAL (no "el primer pendiente"), as\xED que en multi-angle donde angle_0 ya est\xE9 aprobado/saltado obtendr\xEDas status="approved" y nunca alcanzar\xEDas el \xE1ngulo realmente pendiente. Para single-angle puede omitirse.')
8412
8483
  }).strict();
8413
8484
  var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
8414
8485
  var VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2 = 2e3;
8415
- var ApproveSceneEdit = z41.object({
8416
- index: z41.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
8417
- dialogue: z41.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
8486
+ var ApproveSceneEdit = z42.object({
8487
+ index: z42.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
8488
+ dialogue: z42.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
8418
8489
  message: "dialogue no puede ser s\xF3lo espacios"
8419
8490
  }).describe("Di\xE1logo final de la escena. NO puede estar vac\xEDo ni ser s\xF3lo espacios. Puede ser el original sin cambios o la edici\xF3n del usuario.")
8420
8491
  }).strict();
8421
- var ApproveVideoScriptInput = z41.object({
8422
- taskId: z41.string().min(1).describe("ID de la tarea."),
8423
- angleKey: z41.string().trim().min(1).max(120).optional().describe('Mismo angleKey usado en get_video_script. REQUERIDO en multi-angle (si se omite el backend resuelve a "angle_0" literal). En single-angle puede omitirse.'),
8424
- scenes: z41.array(ApproveSceneEdit).min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_SCENES2).describe("Escenas con los di\xE1logos finales. El backend mergea por `index` \u2014 las escenas NO incluidas conservan el di\xE1logo original. Debe haber entre 1 y 50 entradas. Para cada entrada que mandes con un dialogue distinto del original, el backend valida minWords \u2264 words \u2264 maxWords y rechaza con 400 si te sales del rango.")
8492
+ var ApproveVideoScriptInput = z42.object({
8493
+ taskId: z42.string().min(1).describe("ID de la tarea."),
8494
+ angleKey: z42.string().trim().min(1).max(120).optional().describe('Mismo angleKey usado en get_video_script. REQUERIDO en multi-angle (si se omite el backend resuelve a "angle_0" literal). En single-angle puede omitirse.'),
8495
+ scenes: z42.array(ApproveSceneEdit).min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_SCENES2).describe("Escenas con los di\xE1logos finales. El backend mergea por `index` \u2014 las escenas NO incluidas conservan el di\xE1logo original. Debe haber entre 1 y 50 entradas. Para cada entrada que mandes con un dialogue distinto del original, el backend valida minWords \u2264 words \u2264 maxWords y rechaza con 400 si te sales del rango.")
8425
8496
  }).strict();
8426
- var RegenerateVideoScriptInput = z41.object({
8427
- taskId: z41.string().min(1).describe("ID de la tarea."),
8428
- angleKey: z41.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
8497
+ var RegenerateVideoScriptInput = z42.object({
8498
+ taskId: z42.string().min(1).describe("ID de la tarea."),
8499
+ angleKey: z42.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
8429
8500
  narrativeStyle: NarrativeStyleEnum.describe('Narrativa para el nuevo guion. Incluye valores hist\xF3ricos porque esta herramienta tambi\xE9n regenera tareas antiguas. REQUERIDO \u2014 el backend NO acepta "mantener la actual"; siempre tienes que elegir una. Si el usuario quiere re-rolear con la misma narrativa, pasa la que ya tiene el \xE1ngulo (la ves en get_video_script o en get_task_status).')
8430
8501
  }).strict();
8431
8502
  function registerScriptReviewTools(server, api) {
@@ -8465,9 +8536,9 @@ function registerScriptReviewTools(server, api) {
8465
8536
  __name(registerScriptReviewTools, "registerScriptReviewTools");
8466
8537
 
8467
8538
  // src/tools/connections.ts
8468
- import { z as z42 } from "zod";
8539
+ import { z as z43 } from "zod";
8469
8540
  function registerConnectionTools(server, api) {
8470
- server.tool("list_shopify_shops", "Lista las tiendas Shopify conectadas por este usuario. Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.", z42.object({}).shape, async () => {
8541
+ server.tool("list_shopify_shops", "Lista las tiendas Shopify conectadas por este usuario. Si la lista est\xE1 vac\xEDa, dile al usuario que conecte una tienda en productmaker.app/integrations antes de intentar publicar.", z43.object({}).shape, async () => {
8471
8542
  try {
8472
8543
  const r = await api.getJson("/v1/shopify/shops");
8473
8544
  return asText(r);
@@ -8475,10 +8546,10 @@ function registerConnectionTools(server, api) {
8475
8546
  throw translateError(e);
8476
8547
  }
8477
8548
  });
8478
- server.tool("list_shopify_products", 'Busca productos en una tienda Shopify conectada para elegir el target de una republicaci\xF3n. Usa el `id` devuelto junto con `publicationMode="existing"` en `publish_to_shopify`. Si `taskId` se proporciona, la respuesta tambi\xE9n indica el producto publicado actualmente para esa tarea y tienda. No modifica Shopify.', z42.object({
8479
- shopDomain: z42.string().min(3).max(120),
8480
- query: z42.string().trim().max(120).optional(),
8481
- taskId: z42.string().uuid().optional()
8549
+ server.tool("list_shopify_products", 'Busca productos en una tienda Shopify conectada para elegir el target de una republicaci\xF3n. Usa el `id` devuelto junto con `publicationMode="existing"` en `publish_to_shopify`. Si `taskId` se proporciona, la respuesta tambi\xE9n indica el producto publicado actualmente para esa tarea y tienda. No modifica Shopify.', z43.object({
8550
+ shopDomain: z43.string().min(3).max(120),
8551
+ query: z43.string().trim().max(120).optional(),
8552
+ taskId: z43.string().uuid().optional()
8482
8553
  }).shape, async ({ shopDomain, query, taskId }) => {
8483
8554
  try {
8484
8555
  const params = new URLSearchParams({
@@ -8492,7 +8563,7 @@ function registerConnectionTools(server, api) {
8492
8563
  throw translateError(e);
8493
8564
  }
8494
8565
  });
8495
- server.tool("list_meta_ad_accounts", "Lista las cuentas publicitarias de Meta conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar Meta en productmaker.app/integrations.", z42.object({}).shape, async () => {
8566
+ server.tool("list_meta_ad_accounts", "Lista las cuentas publicitarias de Meta conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar Meta en productmaker.app/integrations.", z43.object({}).shape, async () => {
8496
8567
  try {
8497
8568
  const r = await api.getJson("/v1/facebook/ad-accounts");
8498
8569
  return asText(r);
@@ -8500,7 +8571,7 @@ function registerConnectionTools(server, api) {
8500
8571
  throw translateError(e);
8501
8572
  }
8502
8573
  });
8503
- server.tool("list_tiktok_advertisers", "Lista las cuentas de TikTok Ads conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.", z42.object({}).shape, async () => {
8574
+ server.tool("list_tiktok_advertisers", "Lista las cuentas de TikTok Ads conectadas. Si est\xE1 vac\xEDa, ind\xEDcale al usuario conectar TikTok en productmaker.app/integrations.", z43.object({}).shape, async () => {
8504
8575
  try {
8505
8576
  const r = await api.getJson("/v1/tiktok/advertisers");
8506
8577
  return asText(r);
@@ -8512,115 +8583,128 @@ function registerConnectionTools(server, api) {
8512
8583
  __name(registerConnectionTools, "registerConnectionTools");
8513
8584
 
8514
8585
  // src/tools/publish.ts
8515
- import { z as z43 } from "zod";
8516
- var ShopifyInput = z43.object({
8517
- taskId: z43.string().min(1),
8518
- shopDomain: z43.string().min(1),
8586
+ import { z as z44 } from "zod";
8587
+ var ShopifyInput = z44.object({
8588
+ taskId: z44.string().min(1),
8589
+ shopDomain: z44.string().min(1),
8519
8590
  /** Override: number of landing images to include. Default = ALL images the
8520
8591
  * task generated (the MCP fetches /v1/results first to count). Bounded
8521
8592
  * generously here; the backend validates each index against
8522
8593
  * landing.images.length and rejects out-of-range. */
8523
- numLandingImages: z43.number().int().min(1).max(20).optional(),
8524
- variantIndex: z43.number().int().min(0).optional(),
8594
+ numLandingImages: z44.number().int().min(1).max(20).optional(),
8595
+ variantIndex: z44.number().int().min(0).optional(),
8525
8596
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
8526
8597
  * When true, the landing renders variant-picker pills; when false, it ships
8527
8598
  * a single-option product page. Defaults to false. */
8528
- showVariantPicker: z43.boolean().optional(),
8599
+ showVariantPicker: z44.boolean().optional(),
8529
8600
  /** Default `auto` updates the product previously published for this task and
8530
8601
  * shop. Use `existing` with targetProductId to choose another product, or
8531
8602
  * `new` only when the user explicitly wants a duplicate/new listing. */
8532
- publicationMode: z43.enum([
8603
+ publicationMode: z44.enum([
8533
8604
  "auto",
8534
8605
  "existing",
8535
8606
  "new"
8536
8607
  ]).optional(),
8537
8608
  /** Shopify product GID, numeric id, or admin product URL. Required when
8538
8609
  * publicationMode is `existing`. */
8539
- targetProductId: z43.string().trim().min(1).max(300).optional()
8610
+ targetProductId: z44.string().trim().min(1).max(300).optional()
8540
8611
  });
8541
- var McpCampaignConfig = z43.object({
8612
+ var McpCampaignConfig = z44.object({
8542
8613
  /** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
8543
8614
  * converts to the account currency server-side. Do NOT pre-convert: on a COP
8544
8615
  * account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
8545
- dailyBudgetUsd: z43.number().positive().optional(),
8616
+ dailyBudgetUsd: z44.number().positive().optional(),
8546
8617
  /** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
8547
8618
  * ad set). TikTok takes only the first entry. */
8548
- targetCountries: z43.array(z43.string().length(2)).max(1).optional(),
8549
- campaignName: z43.string().trim().min(1).max(100).optional()
8550
- });
8551
- var MetaInput = z43.object({
8552
- taskId: z43.string().min(1),
8553
- adAccountId: z43.string().min(1),
8554
- pixelId: z43.string().min(1).optional(),
8555
- landingUrl: z43.string().url().optional(),
8619
+ targetCountries: z44.array(z44.string().length(2)).max(1).optional(),
8620
+ campaignName: z44.string().trim().min(1).max(100).optional()
8621
+ });
8622
+ var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
8623
+ var MetaInput = z44.object({
8624
+ taskId: z44.string().min(1),
8625
+ adAccountId: McpAdAccountId,
8626
+ pixelId: z44.string().min(1).optional(),
8627
+ landingUrl: z44.string().url().optional(),
8556
8628
  /** Select which angles to publish by 0-based index. Omit to publish all
8557
8629
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
8558
- angleIndexes: z43.array(z43.number().int().nonnegative()).optional(),
8630
+ angleIndexes: z44.array(z44.number().int().nonnegative()).optional(),
8559
8631
  campaignConfig: McpCampaignConfig.optional(),
8560
8632
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
8561
8633
  * uses the auto-generated copy attached to the task. Capped at 500 chars
8562
8634
  * (Meta's primary-text limit). */
8563
- primaryText: z43.string().trim().min(1).max(500).optional(),
8635
+ primaryText: z44.string().trim().min(1).max(500).optional(),
8564
8636
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
8565
8637
  * (wa.me) instead of a landing page. */
8566
- phoneNumber: z43.string().min(8).max(32).optional()
8638
+ phoneNumber: z44.string().min(8).max(32).optional()
8567
8639
  });
8568
- var GroupedTaskSource = z43.object({
8569
- type: z43.literal("task"),
8570
- taskId: z43.string().min(1),
8571
- angleIndex: z43.number().int().min(0).optional(),
8572
- videoVariant: z43.enum([
8640
+ var GroupedTaskSource = z44.object({
8641
+ type: z44.literal("task"),
8642
+ taskId: z44.string().min(1),
8643
+ angleIndex: z44.number().int().min(0).optional(),
8644
+ videoVariant: z44.enum([
8573
8645
  "with_subtitles",
8574
8646
  "without_subtitles"
8575
8647
  ]).optional(),
8576
- imageVariantIndex: z43.number().int().min(0).optional()
8577
- });
8578
- var GroupedMediaSource = z43.object({
8579
- type: z43.literal("media"),
8580
- url: z43.string().url().optional(),
8581
- path: z43.string().min(1).optional(),
8582
- thumbnailUrl: z43.string().url().optional(),
8583
- thumbnailPath: z43.string().min(1).optional()
8648
+ imageVariantIndex: z44.number().int().min(0).optional()
8649
+ });
8650
+ var GroupedMediaSource = z44.object({
8651
+ type: z44.literal("media"),
8652
+ url: z44.string().url().optional(),
8653
+ path: z44.string().min(1).optional(),
8654
+ thumbnailUrl: z44.string().url().optional(),
8655
+ thumbnailPath: z44.string().min(1).optional()
8584
8656
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
8585
8657
  message: "media source needs exactly one of `url` or `path`"
8586
8658
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
8587
8659
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
8588
8660
  });
8589
- var GroupedCreative = z43.object({
8590
- type: z43.enum([
8661
+ var GroupedCreative = z44.object({
8662
+ type: z44.enum([
8591
8663
  "image",
8592
8664
  "video"
8593
8665
  ]),
8594
- source: z43.union([
8666
+ source: z44.union([
8595
8667
  GroupedTaskSource,
8596
8668
  GroupedMediaSource
8597
8669
  ]),
8598
- title: z43.string().max(120).optional(),
8599
- description: z43.string().max(500).optional(),
8670
+ title: z44.string().max(120).optional(),
8671
+ description: z44.string().max(500).optional(),
8600
8672
  cta: MetaCtaType.optional(),
8601
- creativeId: z43.string().min(1).optional()
8673
+ creativeId: z44.string().min(1).optional()
8602
8674
  });
8603
- var GroupedCampaignConfig = z43.object({
8675
+ var GroupedCampaignConfig = z44.object({
8604
8676
  campaignType: MetaCampaignType.default("sales"),
8605
8677
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
8606
8678
  budgetMode: MetaBudgetMode.default("campaign"),
8607
- campaignDailyBudget: z43.number().int().positive().optional(),
8608
- adSets: z43.array(z43.object({
8609
- tempId: z43.string().min(1).max(80),
8610
- name: z43.string().min(1).max(100),
8611
- dailyBudget: z43.number().int().positive().optional(),
8612
- creativeIds: z43.array(z43.string().min(1)).min(1)
8613
- })).min(1).max(4),
8614
- destination: z43.object({
8615
- type: z43.literal("landing"),
8616
- landingUrl: z43.string().url()
8617
- }),
8618
- saveAsDefault: z43.boolean().default(false),
8679
+ campaignDailyBudget: z44.number().int().positive().optional(),
8680
+ adSets: z44.array(z44.object({
8681
+ tempId: z44.string().min(1).max(80),
8682
+ name: z44.string().min(1).max(100),
8683
+ dailyBudget: z44.number().int().positive().optional(),
8684
+ creativeIds: z44.array(z44.string().min(1)).min(1)
8685
+ })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
8686
+ /**
8687
+ * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
8688
+ * used to be hardcoded to `landing`, which both rejected `website` with an
8689
+ * opaque `invalid_literal` and made the WhatsApp destination — supported by
8690
+ * the API since #849 — unreachable from this tool.
8691
+ */
8692
+ destination: z44.discriminatedUnion("type", [
8693
+ z44.object({
8694
+ type: z44.literal("landing"),
8695
+ landingUrl: z44.string().url()
8696
+ }),
8697
+ z44.object({
8698
+ type: z44.literal("whatsapp"),
8699
+ phoneNumber: z44.string().min(8).max(32)
8700
+ })
8701
+ ]),
8702
+ saveAsDefault: z44.boolean().default(false),
8619
8703
  budgetPolicy: MetaBudgetPolicy.optional()
8620
8704
  }).superRefine((value, ctx) => {
8621
8705
  if (value.budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
8622
8706
  ctx.addIssue({
8623
- code: z43.ZodIssueCode.custom,
8707
+ code: z44.ZodIssueCode.custom,
8624
8708
  path: [
8625
8709
  "campaignDailyBudget"
8626
8710
  ],
@@ -8631,7 +8715,7 @@ var GroupedCampaignConfig = z43.object({
8631
8715
  value.adSets.forEach((adSet, index) => {
8632
8716
  if (adSet.dailyBudget === void 0) {
8633
8717
  ctx.addIssue({
8634
- code: z43.ZodIssueCode.custom,
8718
+ code: z44.ZodIssueCode.custom,
8635
8719
  path: [
8636
8720
  "adSets",
8637
8721
  index,
@@ -8643,19 +8727,31 @@ var GroupedCampaignConfig = z43.object({
8643
8727
  });
8644
8728
  }
8645
8729
  });
8646
- var PublishCreativesToMetaInputBase = z43.object({
8647
- creatives: z43.array(GroupedCreative).min(1).max(4),
8648
- adAccountId: z43.string().min(1),
8649
- pixelId: z43.string().min(1).optional(),
8650
- landingUrl: z43.string().url(),
8651
- targetCountry: z43.string().length(2),
8652
- campaignName: z43.string().trim().min(1).max(100),
8730
+ var PublishCreativesToMetaInputBase = z44.object({
8731
+ creatives: z44.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
8732
+ adAccountId: McpAdAccountId,
8733
+ pixelId: z44.string().min(1).optional(),
8734
+ /** Required unless `campaignConfig.destination` is whatsapp — see the
8735
+ * superRefine below. Mirrors the API's `UploadToMetaRequest`. */
8736
+ landingUrl: z44.string().url().optional(),
8737
+ targetCountry: z44.string().length(2),
8738
+ campaignName: z44.string().trim().min(1).max(100),
8653
8739
  campaignConfig: GroupedCampaignConfig.optional()
8654
8740
  });
8655
8741
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
8656
- if (value.campaignConfig && value.campaignConfig.destination.landingUrl !== value.landingUrl) {
8742
+ const destination = value.campaignConfig?.destination;
8743
+ if (destination?.type !== "whatsapp" && !value.landingUrl) {
8744
+ ctx.addIssue({
8745
+ code: z44.ZodIssueCode.custom,
8746
+ path: [
8747
+ "landingUrl"
8748
+ ],
8749
+ message: "landingUrl is required unless campaignConfig.destination.type is whatsapp"
8750
+ });
8751
+ }
8752
+ if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
8657
8753
  ctx.addIssue({
8658
- code: z43.ZodIssueCode.custom,
8754
+ code: z44.ZodIssueCode.custom,
8659
8755
  path: [
8660
8756
  "campaignConfig",
8661
8757
  "destination",
@@ -8665,12 +8761,12 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
8665
8761
  });
8666
8762
  }
8667
8763
  });
8668
- var PresignUploadResponseSchema = z43.object({
8669
- uploadUrl: z43.string().url().refine((value) => {
8764
+ var PresignUploadResponseSchema = z44.object({
8765
+ uploadUrl: z44.string().url().refine((value) => {
8670
8766
  const url = new URL(value);
8671
8767
  return !url.username && !url.password && url.protocol === "https:";
8672
8768
  }, "presigned upload URL must be HTTPS without embedded credentials"),
8673
- publicUrl: z43.string().url().refine((value) => {
8769
+ publicUrl: z44.string().url().refine((value) => {
8674
8770
  const url = new URL(value);
8675
8771
  return !url.username && !url.password;
8676
8772
  }, "public media URL must not contain embedded credentials")
@@ -8695,16 +8791,16 @@ async function resolveAndUploadMedia(api, ref, expectedKind, transport) {
8695
8791
  return uploadResolvedMedia(api, media);
8696
8792
  }
8697
8793
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
8698
- var TikTokInput = z43.object({
8699
- taskId: z43.string().min(1),
8700
- advertiserId: z43.string().min(1),
8701
- pixelId: z43.string().min(1).optional(),
8702
- landingUrl: z43.string().url().optional(),
8703
- angleIndexes: z43.array(z43.number().int().nonnegative()).optional(),
8794
+ var TikTokInput = z44.object({
8795
+ taskId: z44.string().min(1),
8796
+ advertiserId: z44.string().min(1),
8797
+ pixelId: z44.string().min(1).optional(),
8798
+ landingUrl: z44.string().url().optional(),
8799
+ angleIndexes: z44.array(z44.number().int().nonnegative()).optional(),
8704
8800
  campaignConfig: McpCampaignConfig.optional(),
8705
8801
  /** Override the ad's description (TikTok's body copy). If omitted,
8706
8802
  * ProductMaker uses the auto-generated copy attached to the task. */
8707
- primaryText: z43.string().trim().min(1).max(500).optional()
8803
+ primaryText: z44.string().trim().min(1).max(500).optional()
8708
8804
  });
8709
8805
  function registerPublishTools(server, api, opts = {}) {
8710
8806
  const transport = opts.transport ?? "http";
@@ -8743,7 +8839,7 @@ function registerPublishTools(server, api, opts = {}) {
8743
8839
  throw translateError(e);
8744
8840
  }
8745
8841
  });
8746
- server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde la tarea. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en Meta Ads Manager. Use `list_meta_ad_accounts` antes para un adAccountId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 si la tarea gener\xF3 3 \xE1ngulos y el usuario solo quiere los #0 y #2, pasa [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario SIEMPRE en d\xF3lares, ej. 25 \u2014 ProductMaker lo convierte a la moneda de la cuenta; si el usuario te dice '60.000 COP al d\xEDa' pasa el equivalente en USD, NUNCA el n\xFAmero en moneda local), `targetCountries` (c\xF3digos ISO-2, m\xE1x 1 para Meta), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. `phoneNumber` (opcional): si el usuario quiere que el bot\xF3n del anuncio abra un chat de WhatsApp en vez de una landing, pasa el n\xFAmero (con c\xF3digo de pa\xEDs, ej. +573001234567) \u2014 no hace falta `landingUrl` en ese caso. Despu\xE9s de publicar usa `get_meta_campaign_status` para chequear el estado.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
8842
+ server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Si quieres juntar creativos de VARIAS tareas en una misma campa\xF1a o en un mismo ad set, usa `publish_creatives_to_meta` \u2014 esta herramienta crea una campa\xF1a por tarea. `adAccountId` acepta tanto 'act_1234567890' como el id num\xE9rico pelado '1234567890'. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en Meta Ads Manager. Use `list_meta_ad_accounts` antes para un adAccountId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 si la tarea gener\xF3 3 \xE1ngulos y el usuario solo quiere los #0 y #2, pasa [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario SIEMPRE en d\xF3lares, ej. 25 \u2014 ProductMaker lo convierte a la moneda de la cuenta; si el usuario te dice '60.000 COP al d\xEDa' pasa el equivalente en USD, NUNCA el n\xFAmero en moneda local), `targetCountries` (c\xF3digos ISO-2, m\xE1x 1 para Meta), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. `phoneNumber` (opcional): si el usuario quiere que el bot\xF3n del anuncio abra un chat de WhatsApp en vez de una landing, pasa el n\xFAmero (con c\xF3digo de pa\xEDs, ej. +573001234567) \u2014 no hace falta `landingUrl` en ese caso. Despu\xE9s de publicar usa `get_meta_campaign_status` para chequear el estado.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
8747
8843
  try {
8748
8844
  const body = {
8749
8845
  ...rest
@@ -8771,8 +8867,11 @@ function registerPublishTools(server, api, opts = {}) {
8771
8867
  throw translateError(e);
8772
8868
  }
8773
8869
  });
8774
- server.tool("publish_creatives_to_meta", 'Crea UNA campa\xF1a de Meta Ads en estado PAUSED con 1\u20134 creativos agrupados en el mismo flujo. Cada creativo puede venir de una tarea (source.type="task") \u2014 usa angleIndex, videoVariant (with_subtitles/without_subtitles) o imageVariantIndex \u2014 o de media (source.type="media") con URL p\xFAblica o ruta local (stdio). Para videos media puedes dar thumbnailUrl o thumbnailPath; si el video es local, ProductMaker sube el archivo y genera la miniatura con FFmpeg server-side. La herramienta valida extensi\xF3n, MIME, magic bytes y tama\xF1o antes de crear la campa\xF1a. campaignConfig permite definir el ad set y budgetPolicy: exact respeta el presupuesto solicitado y falla expl\xEDcitamente si Meta exige un m\xEDnimo; recommended_floor aplica el piso recomendado. La campa\xF1a queda PAUSED; reporta campaignId, adSetId, IDs de ads, requestedBudget, appliedBudget y el enlace de Ads Manager. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (input) => {
8870
+ server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads en estado PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + " creativos (de tareas distintas si hace falta) en el mismo flujo, repartidos en hasta " + CAMPAIGN_LIMITS.MAX_AD_SETS + ' ad sets. Cada creativo puede venir de una tarea (source.type="task") \u2014 usa angleIndex, videoVariant (with_subtitles/without_subtitles) o imageVariantIndex \u2014 o de media (source.type="media") con URL p\xFAblica o ruta local (stdio). Para videos media, thumbnailUrl/thumbnailPath son OPCIONALES en ambos casos: ProductMaker sube el archivo y genera la miniatura con FFmpeg server-side, tanto si diste una URL como una ruta local. La herramienta valida extensi\xF3n, MIME, magic bytes y tama\xF1o antes de crear la campa\xF1a. `adAccountId` acepta tanto "act_1234567890" como el id num\xE9rico pelado "1234567890". `campaignConfig.destination` acepta exactamente dos valores: {type:"landing", landingUrl} o {type:"whatsapp", phoneNumber} \u2014 NO existe "website". campaignConfig permite definir el ad set y budgetPolicy: exact respeta el presupuesto solicitado y falla expl\xEDcitamente si Meta exige un m\xEDnimo; recommended_floor aplica el piso recomendado. La campa\xF1a queda PAUSED; reporta campaignId, adSetId, IDs de ads, requestedBudget, appliedBudget y el enlace de Ads Manager. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
8775
8871
  try {
8872
+ const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
8873
+ if (!parsed.success) throw invalidInputFromZod(parsed.error);
8874
+ const input = parsed.data;
8776
8875
  const creatives = [];
8777
8876
  for (let index = 0; index < input.creatives.length; index += 1) {
8778
8877
  const creative = input.creatives[index];
@@ -8800,9 +8899,6 @@ function registerPublishTools(server, api, opts = {}) {
8800
8899
  }, creative.type, transport);
8801
8900
  let thumbnailUrl;
8802
8901
  if (creative.type === "video") {
8803
- if (!creative.source.thumbnailUrl && !creative.source.thumbnailPath && !creative.source.path) {
8804
- throw new ResolveMediaError("META_NO_THUMBNAIL_SOURCE", "Video media requires thumbnailUrl or thumbnailPath unless the video is a local path; no campaign entities were created.");
8805
- }
8806
8902
  if (creative.source.thumbnailUrl || creative.source.thumbnailPath) {
8807
8903
  thumbnailUrl = await resolveAndUploadMedia(api, {
8808
8904
  url: creative.source.thumbnailUrl,
@@ -8915,13 +9011,13 @@ function toTikTokCampaignConfig(cfg) {
8915
9011
  __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
8916
9012
 
8917
9013
  // src/tools/campaigns.ts
8918
- import { z as z44 } from "zod";
8919
- var MetaStatusInput = z44.object({
8920
- campaignId: z44.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
9014
+ import { z as z45 } from "zod";
9015
+ var MetaStatusInput = z45.object({
9016
+ campaignId: z45.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
8921
9017
  });
8922
- var TikTokStatusInput = z44.object({
8923
- campaignId: z44.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
8924
- advertiserId: z44.string().min(1).describe("The same advertiserId used to publish the campaign.")
9018
+ var TikTokStatusInput = z45.object({
9019
+ campaignId: z45.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
9020
+ advertiserId: z45.string().min(1).describe("The same advertiserId used to publish the campaign.")
8925
9021
  });
8926
9022
  function registerCampaignTools(server, api) {
8927
9023
  server.tool("get_meta_campaign_status", "Consulta el estado actual + m\xE9tricas lifetime (spend, impressions, clicks, reach) de una campa\xF1a Meta publicada. Llama esto DESPU\xC9S de `publish_to_meta` para confirmar que la campa\xF1a se cre\xF3 correctamente y para reportar performance al usuario. Es read-only \u2014 no modifica nada en Meta Ads Manager.", MetaStatusInput.shape, async (input) => {
@@ -9018,10 +9114,10 @@ function renderTikTokStatusMarkdown(r) {
9018
9114
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
9019
9115
 
9020
9116
  // ../../packages/product-economics/src/product-costing.ts
9021
- import { z as z46 } from "zod";
9117
+ import { z as z47 } from "zod";
9022
9118
 
9023
9119
  // ../../packages/product-economics/src/product-costing-countries.ts
9024
- import { z as z45 } from "zod";
9120
+ import { z as z46 } from "zod";
9025
9121
 
9026
9122
  // ../../packages/product-economics/src/product-costing-shipping-references.json
9027
9123
  var product_costing_shipping_references_default = {
@@ -9085,21 +9181,21 @@ var product_costing_shipping_references_default = {
9085
9181
  };
9086
9182
 
9087
9183
  // ../../packages/product-economics/src/product-costing-countries.ts
9088
- var ShippingReferenceSchema = z45.object({
9089
- reference: z45.number().positive(),
9090
- min: z45.number().nonnegative(),
9091
- max: z45.number().positive(),
9092
- updatedAt: z45.string().date(),
9093
- confidence: z45.enum([
9184
+ var ShippingReferenceSchema = z46.object({
9185
+ reference: z46.number().positive(),
9186
+ min: z46.number().nonnegative(),
9187
+ max: z46.number().positive(),
9188
+ updatedAt: z46.string().date(),
9189
+ confidence: z46.enum([
9094
9190
  "high",
9095
9191
  "medium",
9096
9192
  "medium-low"
9097
9193
  ]),
9098
- sources: z45.array(z45.string().url().startsWith("https://")).min(1).max(3)
9194
+ sources: z46.array(z46.string().url().startsWith("https://")).min(1).max(3)
9099
9195
  }).superRefine(({ reference, min, max }, context) => {
9100
9196
  if (min > reference || reference > max) {
9101
9197
  context.addIssue({
9102
- code: z45.ZodIssueCode.custom,
9198
+ code: z46.ZodIssueCode.custom,
9103
9199
  message: "shipping_reference_outside_range",
9104
9200
  path: [
9105
9201
  "reference"
@@ -9107,7 +9203,7 @@ var ShippingReferenceSchema = z45.object({
9107
9203
  });
9108
9204
  }
9109
9205
  });
9110
- var SHIPPING_REFERENCES = z45.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
9206
+ var SHIPPING_REFERENCES = z46.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
9111
9207
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
9112
9208
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
9113
9209
  "MX",
@@ -9289,18 +9385,18 @@ function getProductCostingCountryName(country) {
9289
9385
  __name(getProductCostingCountryName, "getProductCostingCountryName");
9290
9386
 
9291
9387
  // ../../packages/product-economics/src/product-costing.ts
9292
- var ProductCostingInputSchema = z46.object({
9293
- country: z46.string().refine(isProductCostingCountryCode, "unsupported_country"),
9294
- supplierCost: z46.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
9295
- shippingCost: z46.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
9296
- returnRate: z46.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
9297
- cancellationRate: z46.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
9298
- adsManagerCpa: z46.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
9299
- desiredMargin: z46.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
9388
+ var ProductCostingInputSchema = z47.object({
9389
+ country: z47.string().refine(isProductCostingCountryCode, "unsupported_country"),
9390
+ supplierCost: z47.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
9391
+ shippingCost: z47.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
9392
+ returnRate: z47.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
9393
+ cancellationRate: z47.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
9394
+ adsManagerCpa: z47.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
9395
+ desiredMargin: z47.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
9300
9396
  }).superRefine(({ returnRate, cancellationRate }, context) => {
9301
9397
  if (returnRate + cancellationRate >= 1) {
9302
9398
  context.addIssue({
9303
- code: z46.ZodIssueCode.custom,
9399
+ code: z47.ZodIssueCode.custom,
9304
9400
  message: "delivery_rate_positive",
9305
9401
  path: [
9306
9402
  "cancellationRate"
@@ -9367,14 +9463,14 @@ function calculateProductCosting(rawInput) {
9367
9463
  __name(calculateProductCosting, "calculateProductCosting");
9368
9464
 
9369
9465
  // src/tools/product-costing.ts
9370
- import { z as z47 } from "zod";
9371
- var optionalAmount = /* @__PURE__ */ __name((description) => z47.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
9372
- var optionalPercent = /* @__PURE__ */ __name((description) => z47.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
9373
- var ProductCostingToolInput = z47.object({
9374
- country: z47.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
9466
+ import { z as z48 } from "zod";
9467
+ var optionalAmount = /* @__PURE__ */ __name((description) => z48.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
9468
+ var optionalPercent = /* @__PURE__ */ __name((description) => z48.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
9469
+ var ProductCostingToolInput = z48.object({
9470
+ country: z48.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
9375
9471
  message: "Pa\xEDs no soportado por ProductMaker."
9376
9472
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
9377
- supplierCost: z47.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
9473
+ supplierCost: z48.number().finite().max(Number.MAX_SAFE_INTEGER).positive().optional().describe("Costo real de una unidad con el proveedor, en la moneda del pa\xEDs. Requerido salvo que uses supplierCostCop para Colombia."),
9378
9474
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
9379
9475
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
9380
9476
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),