@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/http.js CHANGED
@@ -132,10 +132,10 @@ var ApiClient = class {
132
132
  };
133
133
 
134
134
  // src/index.ts
135
- var MCP_VERSION = "1.16.2";
135
+ var MCP_VERSION = "1.16.3";
136
136
 
137
137
  // src/tools/tasks.ts
138
- import { z as z38 } from "zod";
138
+ import { z as z39 } from "zod";
139
139
 
140
140
  // src/resolve-image.ts
141
141
  import { lookup as dnsLookup } from "dns/promises";
@@ -3288,8 +3288,21 @@ import { z as z31 } from "zod";
3288
3288
  // ../../packages/types/src/campaigns.ts
3289
3289
  import { z as z30 } from "zod";
3290
3290
  var CAMPAIGN_LIMITS = {
3291
- /** Maximum number of creatives per campaign */
3292
- MAX_CREATIVES: 4,
3291
+ /**
3292
+ * Maximum number of creatives (= ads) per campaign.
3293
+ *
3294
+ * Not a Meta limit — Meta allows far more ads per ad set. This is our own
3295
+ * bound on how many uploads one publish call will push through the Graph API
3296
+ * in a single request. It was 4 with no stated reason, which forced callers
3297
+ * with a normal batch (3 videos + 3 images) to drop two by hand.
3298
+ */
3299
+ MAX_CREATIVES: 10,
3300
+ /**
3301
+ * Maximum ad sets per campaign. Distinct from MAX_CREATIVES, which it used
3302
+ * to share a constant with — that coupling meant raising the creative cap
3303
+ * would silently raise the ad-set cap too.
3304
+ */
3305
+ MAX_AD_SETS: 4,
3293
3306
  /** Maximum video duration in seconds */
3294
3307
  MAX_VIDEO_DURATION_SECONDS: 180,
3295
3308
  /** Maximum file size in bytes (350MB) */
@@ -3332,8 +3345,21 @@ var CampaignCreativeType = z30.enum([
3332
3345
  var CampaignCreative = z30.object({
3333
3346
  /** Unique identifier for the creative */
3334
3347
  id: z30.string(),
3335
- /** The uploaded file (null when loaded from server) */
3336
- file: z30.instanceof(File).nullable(),
3348
+ /**
3349
+ * The uploaded file (null when loaded from server).
3350
+ *
3351
+ * Deliberately NOT `z.instanceof(File)`: `File` is a browser global that Node
3352
+ * only exposes from v20. `z.instanceof` dereferences it while the schema is
3353
+ * being *constructed*, so merely importing this module threw
3354
+ * `ReferenceError: File is not defined` on Node 18 — which killed the whole
3355
+ * MCP CLI on startup, because its bundle pulls this barrel in for unrelated
3356
+ * types and never touches this schema. `z.custom` defers the check to
3357
+ * validation time and guards the global, so the module imports on any runtime
3358
+ * and only the browser upload path needs a real `File`.
3359
+ */
3360
+ file: z30.custom((value) => typeof File !== "undefined" && value instanceof File, {
3361
+ message: "Expected a File"
3362
+ }).nullable(),
3337
3363
  /** Type of media: image or video */
3338
3364
  type: CampaignCreativeType,
3339
3365
  /** Creative title (max 120 chars) - Maps to Meta's "Título" */
@@ -3442,6 +3468,9 @@ var HttpUrl = z30.string().url().refine((value) => {
3442
3468
  return false;
3443
3469
  }
3444
3470
  }, "URL must use http or https");
3471
+ 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), {
3472
+ message: 'adAccountId must look like "act_1234567890" (a bare numeric id is also accepted)'
3473
+ });
3445
3474
  var MetaCampaignDestination = z30.discriminatedUnion("type", [
3446
3475
  z30.object({
3447
3476
  type: z30.literal("landing"),
@@ -3474,7 +3503,7 @@ var MetaCampaignConfigBase = z30.object({
3474
3503
  campaignDailyBudget: z30.number().int().positive().optional(),
3475
3504
  /** Omitted preserves the historical recommended-floor behavior. */
3476
3505
  budgetPolicy: MetaBudgetPolicy.optional(),
3477
- adSets: z30.array(MetaAdSetConfig).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3506
+ adSets: z30.array(MetaAdSetConfig).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
3478
3507
  destination: MetaCampaignDestination,
3479
3508
  saveAsDefault: z30.boolean().default(true)
3480
3509
  });
@@ -3507,7 +3536,7 @@ var MetaCampaignConfig = MetaCampaignConfigBase.superRefine((value, ctx) => {
3507
3536
  var MetaCampaignDefaults = MetaCampaignConfigBase.omit({
3508
3537
  saveAsDefault: true
3509
3538
  }).extend({
3510
- adSets: z30.array(MetaAdSetDefaults).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3539
+ adSets: z30.array(MetaAdSetDefaults).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
3511
3540
  /**
3512
3541
  * Ad-account currency (ISO 4217: USD/COP/MXN/...) at the time the user saved
3513
3542
  * these defaults. The wizard re-uses the saved budget only when the current
@@ -3536,7 +3565,7 @@ var UploadToMetaRequest = z30.object({
3536
3565
  thumbnailUrl: HttpUrl.optional()
3537
3566
  })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3538
3567
  /** Meta Ad Account ID */
3539
- adAccountId: z30.string().min(1),
3568
+ adAccountId: MetaAdAccountId,
3540
3569
  /** Optional Meta Pixel ID */
3541
3570
  pixelId: z30.string().optional(),
3542
3571
  /** Landing page URL (required for landing destinations; omitted for whatsapp) */
@@ -3618,7 +3647,7 @@ var MetaCampaignOverridesSchema = z31.object({
3618
3647
  destination: MetaCampaignDestination.optional()
3619
3648
  });
3620
3649
  var FacebookCreateCampaignBodySchema = z31.object({
3621
- adAccountId: z31.string().min(1, "Debes seleccionar una cuenta publicitaria"),
3650
+ adAccountId: MetaAdAccountId,
3622
3651
  pixelId: z31.string().optional(),
3623
3652
  landingUrl: HttpUrl2.optional(),
3624
3653
  campaignConfig: MetaCampaignConfig.optional(),
@@ -3694,8 +3723,8 @@ var FacebookCreateCampaignFromCreativesBodySchema = z31.object({
3694
3723
  mediaUrl: HttpUrl2,
3695
3724
  thumbnailUrl: HttpUrl2.optional(),
3696
3725
  creativeId: z31.string().min(1).optional()
3697
- })).min(1).max(4),
3698
- adAccountId: z31.string().min(1),
3726
+ })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3727
+ adAccountId: MetaAdAccountId,
3699
3728
  pixelId: z31.string().optional(),
3700
3729
  /** Required unless campaignConfig.destination is 'whatsapp' (see superRefine below). */
3701
3730
  landingUrl: HttpUrl2.optional(),
@@ -3810,8 +3839,8 @@ var FacebookCreateCampaignFromTasksBodySchema = z31.object({
3810
3839
  description: z31.string().max(500).optional(),
3811
3840
  cta: MetaCtaType.optional(),
3812
3841
  creativeId: z31.string().min(1).optional()
3813
- })).min(1).max(4),
3814
- adAccountId: z31.string().min(1),
3842
+ })).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
3843
+ adAccountId: MetaAdAccountId,
3815
3844
  pixelId: z31.string().optional(),
3816
3845
  landingUrl: HttpUrl2.optional(),
3817
3846
  targetCountry: z31.string().length(2).default("US"),
@@ -6364,6 +6393,43 @@ var RegenerateEditRequestSchema = z36.object({
6364
6393
  comment: z36.string().min(1).max(2e3)
6365
6394
  });
6366
6395
 
6396
+ // ../../packages/types/src/scene-subject-contract.ts
6397
+ import { z as z37 } from "zod";
6398
+ var SceneSubjectContractSchema = z37.object({
6399
+ maxPeople: z37.number().int().min(1).max(10),
6400
+ minPeople: z37.number().int().min(0).max(10),
6401
+ minDistinctPeople: z37.number().int().min(0).max(10),
6402
+ maxHands: z37.number().int().min(0).max(20)
6403
+ }).superRefine((value, ctx) => {
6404
+ if (value.minPeople > value.maxPeople) {
6405
+ ctx.addIssue({
6406
+ code: z37.ZodIssueCode.custom,
6407
+ path: [
6408
+ "minPeople"
6409
+ ],
6410
+ message: "minPeople cannot exceed maxPeople"
6411
+ });
6412
+ }
6413
+ if (value.minDistinctPeople > value.maxPeople) {
6414
+ ctx.addIssue({
6415
+ code: z37.ZodIssueCode.custom,
6416
+ path: [
6417
+ "minDistinctPeople"
6418
+ ],
6419
+ message: "minDistinctPeople cannot exceed maxPeople"
6420
+ });
6421
+ }
6422
+ if (value.minDistinctPeople > value.minPeople) {
6423
+ ctx.addIssue({
6424
+ code: z37.ZodIssueCode.custom,
6425
+ path: [
6426
+ "minDistinctPeople"
6427
+ ],
6428
+ message: "minDistinctPeople cannot exceed minPeople"
6429
+ });
6430
+ }
6431
+ });
6432
+
6367
6433
  // src/resolve-media.ts
6368
6434
  var MAX_MEDIA_BYTES = CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES;
6369
6435
  var MEDIA_FORMATS = [
@@ -6603,10 +6669,10 @@ async function resolveMediaRef(ref, opts) {
6603
6669
  __name(resolveMediaRef, "resolveMediaRef");
6604
6670
 
6605
6671
  // src/tools/shared.ts
6606
- import { z as z37 } from "zod";
6607
- var Lang = z37.string().min(2).max(10);
6608
- var Country = z37.string().min(2).max(3);
6609
- var LandingStrategyEnum = z37.enum([
6672
+ import { z as z38 } from "zod";
6673
+ var Lang = z38.string().min(2).max(10);
6674
+ var Country = z38.string().min(2).max(3);
6675
+ var LandingStrategyEnum = z38.enum([
6610
6676
  "auto",
6611
6677
  "classic",
6612
6678
  "pain_solution",
@@ -6615,12 +6681,12 @@ var LandingStrategyEnum = z37.enum([
6615
6681
  "offer_value",
6616
6682
  "lifestyle_identity"
6617
6683
  ]);
6618
- var LandingImageFormatEnum = z37.enum([
6684
+ var LandingImageFormatEnum = z38.enum([
6619
6685
  "9:16",
6620
6686
  "4:5",
6621
6687
  "1:1"
6622
6688
  ]);
6623
- var VideoPresetEnum = z37.enum([
6689
+ var VideoPresetEnum = z38.enum([
6624
6690
  "auto",
6625
6691
  "ugc_dynamic",
6626
6692
  "ugc_unboxing_chaos",
@@ -6631,7 +6697,7 @@ var VideoPresetEnum = z37.enum([
6631
6697
  "pixar_problem",
6632
6698
  "pixar_sassy"
6633
6699
  ]);
6634
- var TaskCreativeVariantVideoPresetEnum = z37.enum([
6700
+ var TaskCreativeVariantVideoPresetEnum = z38.enum([
6635
6701
  "auto",
6636
6702
  "ugc_dynamic",
6637
6703
  "problem_solution",
@@ -6690,7 +6756,7 @@ function resolveVideoPreset(preset) {
6690
6756
  return VIDEO_PRESET_RESOLUTION[preset] ?? null;
6691
6757
  }
6692
6758
  __name(resolveVideoPreset, "resolveVideoPreset");
6693
- var VideoTierEnum = z37.enum([
6759
+ var VideoTierEnum = z38.enum([
6694
6760
  "basic"
6695
6761
  ]);
6696
6762
  function resolveVideoTier(_tier) {
@@ -6705,17 +6771,17 @@ function resolveVideoFormatForDuration(durationSeconds) {
6705
6771
  return durationSeconds === 30 ? "double_30s" : "single_15s";
6706
6772
  }
6707
6773
  __name(resolveVideoFormatForDuration, "resolveVideoFormatForDuration");
6708
- var VideoResolutionEnum = z37.enum([
6774
+ var VideoResolutionEnum = z38.enum([
6709
6775
  "480p",
6710
6776
  "720p"
6711
6777
  ]);
6712
- var VideoStyleEnum = z37.enum([
6778
+ var VideoStyleEnum = z38.enum([
6713
6779
  "ugc",
6714
6780
  "cartoon_3d",
6715
6781
  "product_only",
6716
6782
  "ai_dynamic"
6717
6783
  ]);
6718
- var NarrativeStyleEnum = z37.enum([
6784
+ var NarrativeStyleEnum = z38.enum([
6719
6785
  "review",
6720
6786
  "problem_product_cta",
6721
6787
  "product_showcase",
@@ -6730,7 +6796,7 @@ var NarrativeStyleEnum = z37.enum([
6730
6796
  "ugc_skeptic_confession",
6731
6797
  "ugc_demo_review"
6732
6798
  ]);
6733
- var PublicNarrativeStyleEnum = z37.enum([
6799
+ var PublicNarrativeStyleEnum = z38.enum([
6734
6800
  "review",
6735
6801
  "ugc_unboxing_chaos",
6736
6802
  "problem_product_cta",
@@ -6740,7 +6806,7 @@ var PublicNarrativeStyleEnum = z37.enum([
6740
6806
  "animated_problem_pixar",
6741
6807
  "sassy_product_pixar"
6742
6808
  ]);
6743
- var VideoModelEnum = z37.enum([
6809
+ var VideoModelEnum = z38.enum([
6744
6810
  "veo",
6745
6811
  "wan",
6746
6812
  "bytedance",
@@ -6748,25 +6814,25 @@ var VideoModelEnum = z37.enum([
6748
6814
  "grok",
6749
6815
  "kling"
6750
6816
  ]);
6751
- var ProductReferenceUserChoiceEnum = z37.enum([
6817
+ var ProductReferenceUserChoiceEnum = z38.enum([
6752
6818
  "same_product",
6753
6819
  "variants",
6754
6820
  "different_products",
6755
6821
  "combo_distinct"
6756
6822
  ]);
6757
- var ProductReferenceCandidateGroupingEnum = z37.enum([
6823
+ var ProductReferenceCandidateGroupingEnum = z38.enum([
6758
6824
  "same_product_same_variant",
6759
6825
  "same_product_different_variants",
6760
6826
  "different_products",
6761
6827
  "unclear"
6762
6828
  ]);
6763
- var GapDurationChoiceMirror = z37.object({
6764
- angleIndex: z37.number().int().nonnegative(),
6765
- recommendedSec: z37.union([
6766
- z37.literal(15),
6767
- z37.literal(30)
6829
+ var GapDurationChoiceMirror = z38.object({
6830
+ angleIndex: z38.number().int().nonnegative(),
6831
+ recommendedSec: z38.union([
6832
+ z38.literal(15),
6833
+ z38.literal(30)
6768
6834
  ]),
6769
- estimatedDeltaCredits: z37.number().int().nonnegative().optional()
6835
+ estimatedDeltaCredits: z38.number().int().nonnegative().optional()
6770
6836
  });
6771
6837
  function buildSelectedImageUrlsHash(urls) {
6772
6838
  const input = [
@@ -6780,13 +6846,13 @@ function buildSelectedImageUrlsHash(urls) {
6780
6846
  return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
6781
6847
  }
6782
6848
  __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
6783
- var ImageCreativeStyleEnum = z37.enum([
6849
+ var ImageCreativeStyleEnum = z38.enum([
6784
6850
  "studio",
6785
6851
  "floating",
6786
6852
  "ingredients",
6787
6853
  "in_use"
6788
6854
  ]);
6789
- var ImageCreativeArchetypeEnum = z37.enum([
6855
+ var ImageCreativeArchetypeEnum = z38.enum([
6790
6856
  "direct_response_product_ad",
6791
6857
  "hero_outcome",
6792
6858
  "offer_value_prop",
@@ -6795,23 +6861,23 @@ var ImageCreativeArchetypeEnum = z37.enum([
6795
6861
  "problem_solution",
6796
6862
  "lifestyle_identity"
6797
6863
  ]);
6798
- var ImageCreativeVisualTreatmentEnum = z37.enum([
6864
+ var ImageCreativeVisualTreatmentEnum = z38.enum([
6799
6865
  "performance_product_ad",
6800
6866
  "premium_dtc_carousel"
6801
6867
  ]);
6802
- var ImageCreativeAspectRatioEnum = z37.enum([
6868
+ var ImageCreativeAspectRatioEnum = z38.enum([
6803
6869
  "1:1",
6804
6870
  "4:5",
6805
6871
  "9:16",
6806
6872
  "16:9"
6807
6873
  ]);
6808
- var ImageRefSchema = z37.object({
6809
- 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.'),
6810
- 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`.')
6874
+ var ImageRefSchema = z38.object({
6875
+ 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.'),
6876
+ 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`.')
6811
6877
  }).refine((v) => Boolean(v.url) !== Boolean(v.path), {
6812
6878
  message: "each image entry must have exactly one of `url` or `path`"
6813
6879
  });
6814
- 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.');
6880
+ 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.');
6815
6881
  function asText(value) {
6816
6882
  return {
6817
6883
  content: [
@@ -7189,6 +7255,11 @@ function resolveMessage(code, backendMessage) {
7189
7255
  return `The service returned an error (code: ${code}). Try again shortly or contact support if it persists.`;
7190
7256
  }
7191
7257
  __name(resolveMessage, "resolveMessage");
7258
+ function invalidInputFromZod(error) {
7259
+ const detail = error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
7260
+ return mcp(McpErrorCode.INVALID_INPUT, detail || resolveMessage(McpErrorCode.INVALID_INPUT, void 0));
7261
+ }
7262
+ __name(invalidInputFromZod, "invalidInputFromZod");
7192
7263
  function translateError(err) {
7193
7264
  if (err instanceof McpError) {
7194
7265
  return err;
@@ -7451,66 +7522,66 @@ function renderOutputsMarkdown(outputs) {
7451
7522
  __name(renderOutputsMarkdown, "renderOutputsMarkdown");
7452
7523
 
7453
7524
  // src/tools/tasks.ts
7454
- var CreateProductTaskInput = z38.object({
7525
+ var CreateProductTaskInput = z39.object({
7455
7526
  images: ImagesInput,
7456
- 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."),
7527
+ 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."),
7457
7528
  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.'),
7458
7529
  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.'),
7459
- 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.'),
7460
- 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".'),
7461
- 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.'),
7462
- 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.'),
7463
- 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."),
7464
- 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.'),
7530
+ 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.'),
7531
+ 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".'),
7532
+ 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.'),
7533
+ 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.'),
7534
+ 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."),
7535
+ 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.'),
7465
7536
  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.'),
7466
- durationSeconds: z38.union([
7467
- z38.literal(15),
7468
- z38.literal(30)
7537
+ durationSeconds: z39.union([
7538
+ z39.literal(15),
7539
+ z39.literal(30)
7469
7540
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo del video: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
7470
- 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."),
7541
+ 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."),
7471
7542
  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.'),
7472
7543
  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).'),
7473
- 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."),
7544
+ 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."),
7474
7545
  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.'),
7475
- outputs: z38.array(z38.enum([
7546
+ outputs: z39.array(z39.enum([
7476
7547
  "landing",
7477
7548
  "video",
7478
7549
  "image"
7479
7550
  ])).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.'),
7480
- 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."),
7481
- 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."),
7482
- 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.'),
7551
+ 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."),
7552
+ 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."),
7553
+ 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.'),
7483
7554
  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".'),
7484
7555
  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".')
7485
7556
  }).strict();
7486
- var GetTaskStatusInput = z38.object({
7487
- taskId: z38.string().min(1),
7488
- waitSeconds: z38.number().int().min(0).max(120).optional()
7489
- });
7490
- var ListTasksInput = z38.object({
7491
- limit: z38.number().int().min(1).max(50).optional(),
7492
- offset: z38.number().int().min(0).optional(),
7493
- search: z38.string().optional()
7494
- });
7495
- var EditTaskDraftInput = z38.object({
7496
- taskId: z38.string().min(1),
7497
- angleIndex: z38.number().int().min(0),
7498
- angle: z38.object({
7499
- angle: z38.string().min(1).max(480),
7500
- buyerPersona: z38.string().min(1).max(360),
7501
- actor: z38.string().min(1).max(360).optional(),
7502
- gender: z38.enum([
7557
+ var GetTaskStatusInput = z39.object({
7558
+ taskId: z39.string().min(1),
7559
+ waitSeconds: z39.number().int().min(0).max(120).optional()
7560
+ });
7561
+ var ListTasksInput = z39.object({
7562
+ limit: z39.number().int().min(1).max(50).optional(),
7563
+ offset: z39.number().int().min(0).optional(),
7564
+ search: z39.string().optional()
7565
+ });
7566
+ var EditTaskDraftInput = z39.object({
7567
+ taskId: z39.string().min(1),
7568
+ angleIndex: z39.number().int().min(0),
7569
+ angle: z39.object({
7570
+ angle: z39.string().min(1).max(480),
7571
+ buyerPersona: z39.string().min(1).max(360),
7572
+ actor: z39.string().min(1).max(360).optional(),
7573
+ gender: z39.enum([
7503
7574
  "female",
7504
7575
  "male"
7505
7576
  ]).optional(),
7506
- scriptNotes: z38.string().max(2e3).optional()
7577
+ scriptNotes: z39.string().max(2e3).optional()
7507
7578
  })
7508
7579
  });
7509
- var PendingActionPayload = z38.discriminatedUnion("kind", [
7510
- z38.object({
7511
- kind: z38.literal("product_reference_confirmation"),
7580
+ var PendingActionPayload = z39.discriminatedUnion("kind", [
7581
+ z39.object({
7582
+ kind: z39.literal("product_reference_confirmation"),
7512
7583
  userChoice: ProductReferenceUserChoiceEnum,
7513
- confirmedProductNames: z38.array(z38.string().min(1).max(120)).min(2).max(8).optional(),
7584
+ confirmedProductNames: z39.array(z39.string().min(1).max(120)).min(2).max(8).optional(),
7514
7585
  // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
7515
7586
  // reachable via MCP. Valid ONLY together with userChoice='variants' (same
7516
7587
  // product in several colors): renders one shot per color and stitches them,
@@ -7521,62 +7592,62 @@ var PendingActionPayload = z38.discriminatedUnion("kind", [
7521
7592
  // safe because multi-scene IS the intended render for a color variant set,
7522
7593
  // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
7523
7594
  // confirm call; the backend re-gates it (variants + variant_set only).
7524
- 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".')
7595
+ 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".')
7525
7596
  }),
7526
- z38.object({
7527
- kind: z38.literal("angle_review_approval"),
7597
+ z39.object({
7598
+ kind: z39.literal("angle_review_approval"),
7528
7599
  // All edits are optional — empty action body just approves the
7529
7600
  // backend-generated draft as-is. Field shapes mirror the angle-review
7530
7601
  // request contracts in @packages/types/wizard.ts; the handler routes to
7531
7602
  // the dedicated video/image/autopilot endpoint based on StatusResponse.kind.
7532
- customAngle: z38.string().min(1).max(480).optional(),
7603
+ customAngle: z39.string().min(1).max(480).optional(),
7533
7604
  // Keep the shared MCP action within the strictest downstream contract:
7534
7605
  // autopilot/image resume endpoints cap these fields at 1500 characters.
7535
- painPoints: z38.string().max(1500).optional(),
7536
- benefits: z38.string().max(1500).optional(),
7537
- features: z38.array(z38.string().min(1).max(300)).max(10).optional(),
7538
- gapDurationSeconds: z38.union([
7539
- z38.literal(15),
7540
- z38.literal(30)
7606
+ painPoints: z39.string().max(1500).optional(),
7607
+ benefits: z39.string().max(1500).optional(),
7608
+ features: z39.array(z39.string().min(1).max(300)).max(10).optional(),
7609
+ gapDurationSeconds: z39.union([
7610
+ z39.literal(15),
7611
+ z39.literal(30)
7541
7612
  ]).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.")
7542
7613
  })
7543
7614
  ]);
7544
- var ResolvePendingActionInput = z38.object({
7545
- taskId: z38.string().min(1),
7615
+ var ResolvePendingActionInput = z39.object({
7616
+ taskId: z39.string().min(1),
7546
7617
  action: PendingActionPayload
7547
7618
  }).strict();
7548
7619
  function resolveVariantConfirmVideoTier(action) {
7549
7620
  return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
7550
7621
  }
7551
7622
  __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
7552
- var StatusForResolveSchema = z38.object({
7623
+ var StatusForResolveSchema = z39.object({
7553
7624
  // Metadata used to select the backend resume contract. Older API responses
7554
7625
  // may omit these fields, so the resolver keeps the generic fallback.
7555
- kind: z38.enum([
7626
+ kind: z39.enum([
7556
7627
  "autopilot",
7557
7628
  "video",
7558
7629
  "image",
7559
7630
  "landing",
7560
7631
  "mix"
7561
7632
  ]).optional(),
7562
- selectedOutputs: z38.array(z38.enum([
7633
+ selectedOutputs: z39.array(z39.enum([
7563
7634
  "landing",
7564
7635
  "video",
7565
7636
  "image"
7566
7637
  ])).optional(),
7567
- videoAngleCount: z38.number().int().min(1).max(3).optional(),
7568
- ugcFormat: z38.enum([
7638
+ videoAngleCount: z39.number().int().min(1).max(3).optional(),
7639
+ ugcFormat: z39.enum([
7569
7640
  "single_8s",
7570
7641
  "single_15s",
7571
7642
  "double_30s"
7572
7643
  ]).optional(),
7573
- videoStyle: z38.enum([
7644
+ videoStyle: z39.enum([
7574
7645
  "ugc",
7575
7646
  "cartoon_3d",
7576
7647
  "product_only",
7577
7648
  "ai_dynamic"
7578
7649
  ]).optional(),
7579
- videoModel: z38.enum([
7650
+ videoModel: z39.enum([
7580
7651
  "veo",
7581
7652
  "wan",
7582
7653
  "bytedance",
@@ -7584,26 +7655,26 @@ var StatusForResolveSchema = z38.object({
7584
7655
  "grok",
7585
7656
  "kling"
7586
7657
  ]).optional(),
7587
- videoTier: z38.enum([
7658
+ videoTier: z39.enum([
7588
7659
  "legacy",
7589
7660
  "standard",
7590
7661
  "pro",
7591
7662
  "single_call"
7592
7663
  ]).optional(),
7593
- videoResolution: z38.enum([
7664
+ videoResolution: z39.enum([
7594
7665
  "480p",
7595
7666
  "720p"
7596
7667
  ]).optional(),
7597
7668
  narrativeStyle: NarrativeStyleEnum.optional(),
7598
- pendingUserAction: z38.object({
7599
- kind: z38.string(),
7600
- candidateGrouping: z38.string().optional(),
7601
- selectedImageUrls: z38.array(z38.string()).optional(),
7602
- proposedProductNames: z38.array(z38.string()).optional(),
7603
- customAngleDraft: z38.string().optional(),
7604
- painPointsDraft: z38.string().optional(),
7605
- benefitsDraft: z38.string().optional(),
7606
- featuresDraft: z38.array(z38.string()).optional(),
7669
+ pendingUserAction: z39.object({
7670
+ kind: z39.string(),
7671
+ candidateGrouping: z39.string().optional(),
7672
+ selectedImageUrls: z39.array(z39.string()).optional(),
7673
+ proposedProductNames: z39.array(z39.string()).optional(),
7674
+ customAngleDraft: z39.string().optional(),
7675
+ painPointsDraft: z39.string().optional(),
7676
+ benefitsDraft: z39.string().optional(),
7677
+ featuresDraft: z39.array(z39.string()).optional(),
7607
7678
  // Recommendation consumed only when the caller explicitly sends
7608
7679
  // gapDurationSeconds: 30 in the angle approval action.
7609
7680
  gapDurationChoice: GapDurationChoiceMirror.optional()
@@ -7925,54 +7996,54 @@ __name(registerTaskTools, "registerTaskTools");
7925
7996
 
7926
7997
  // src/tools/creatives.ts
7927
7998
  import { randomUUID } from "crypto";
7928
- import { z as z39 } from "zod";
7999
+ import { z as z40 } from "zod";
7929
8000
  var CreativeRefs = {
7930
8001
  images: ImagesInput,
7931
8002
  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.'),
7932
8003
  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.'),
7933
- 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.'),
7934
- 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.'),
7935
- 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".'),
7936
- 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.'),
7937
- 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."),
7938
- 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.'),
7939
- 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.'),
7940
- isCombo: z39.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
7941
- 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."),
7942
- 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."),
7943
- 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.')
8004
+ 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.'),
8005
+ 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.'),
8006
+ 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".'),
8007
+ 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.'),
8008
+ 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."),
8009
+ 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.'),
8010
+ 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.'),
8011
+ isCombo: z40.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
8012
+ 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."),
8013
+ 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."),
8014
+ 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.')
7944
8015
  };
7945
- var VideoInput = z39.object({
8016
+ var VideoInput = z40.object({
7946
8017
  images: CreativeRefs.images,
7947
- title: z39.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
7948
- 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".'),
8018
+ title: z40.string().min(1).describe('T\xEDtulo corto del producto. Aparece en la primera vista del video. Ej. "Crema Anti-Edad Premium".'),
8019
+ 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".'),
7949
8020
  language: CreativeRefs.language,
7950
8021
  country: CreativeRefs.country,
7951
- aspectRatio: z39.enum([
8022
+ aspectRatio: z40.enum([
7952
8023
  "9:16",
7953
8024
  "16:9",
7954
8025
  "Auto"
7955
8026
  ]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
7956
- durationSeconds: z39.union([
7957
- z39.literal(15),
7958
- z39.literal(30)
8027
+ durationSeconds: z40.union([
8028
+ z40.literal(15),
8029
+ z40.literal(30)
7959
8030
  ]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
7960
8031
  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).'),
7961
8032
  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.'),
7962
8033
  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).'),
7963
- 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"].'),
8034
+ 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"].'),
7964
8035
  price: CreativeRefs.price,
7965
8036
  offer: CreativeRefs.offer,
7966
8037
  productContext: CreativeRefs.productContext,
7967
8038
  productLabel: CreativeRefs.productLabel,
7968
- 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.'),
7969
- 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.'),
7970
- 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."),
7971
- 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."),
8039
+ 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.'),
8040
+ 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.'),
8041
+ 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."),
8042
+ 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."),
7972
8043
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
7973
8044
  brandId: CreativeRefs.brandId
7974
8045
  }).strict();
7975
- var FreeVideoQualityEnum = z39.enum([
8046
+ var FreeVideoQualityEnum = z40.enum([
7976
8047
  "economical",
7977
8048
  "high_detail"
7978
8049
  ]);
@@ -7994,38 +8065,38 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
7994
8065
  15
7995
8066
  ]
7996
8067
  };
7997
- var FreeVideoInput = z39.object({
8068
+ var FreeVideoInput = z40.object({
7998
8069
  images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
7999
- 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."),
8070
+ 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."),
8000
8071
  quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
8001
- 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."),
8072
+ 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."),
8002
8073
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
8003
- generateAiMusic: z39.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
8004
- addSubtitles: z39.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
8005
- 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.")
8074
+ generateAiMusic: z40.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
8075
+ addSubtitles: z40.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
8076
+ 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.")
8006
8077
  }).strict();
8007
8078
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
8008
8079
  var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
8009
8080
  var visualTreatmentList = ImageCreativeVisualTreatmentEnum.options.join(" | ");
8010
8081
  var aspectRatioList = ImageCreativeAspectRatioEnum.options.join(" | ");
8011
- var ImageInput = z39.object({
8082
+ var ImageInput = z40.object({
8012
8083
  images: CreativeRefs.images,
8013
- productTitle: z39.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
8014
- primaryAngle: z39.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
8084
+ productTitle: z40.string().min(1).describe('Nombre del producto. Ej. "Crema Anti-Edad Premium".'),
8085
+ primaryAngle: z40.string().min(1).describe('\xC1ngulo de venta principal. Ej. "Reduce arrugas en 14 d\xEDas".'),
8015
8086
  buyerPersona: CreativeRefs.buyerPersona,
8016
8087
  language: CreativeRefs.language,
8017
8088
  country: CreativeRefs.country,
8018
- nVariants: z39.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
8019
- 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.'),
8089
+ nVariants: z40.number().int().min(1).max(6).optional().describe("Opcional. N\xFAmero de variantes a generar (1-6). Default 3."),
8090
+ 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.'),
8020
8091
  imageStyle: ImageCreativeStyleEnum.optional().describe(`Opcional. Estilo del shot del producto. Valores: ${imageStyleList}. Si se omite, la IA escoge seg\xFAn el archetype.`),
8021
8092
  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).`),
8022
8093
  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.`),
8023
- 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."),
8094
+ 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."),
8024
8095
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe(`Opcional. Relaci\xF3n de aspecto. Valores: ${aspectRatioList}. Default "4:5" (vertical Instagram feed).`),
8025
- generateAdCopy: z39.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
8026
- 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."),
8027
- features: z39.array(z39.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
8028
- 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.'),
8096
+ generateAdCopy: z40.boolean().optional().describe("Opcional. Si true, tambi\xE9n genera headline/body/CTA para el ad. Default false."),
8097
+ 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."),
8098
+ features: z40.array(z40.string()).optional().describe('Opcional. Features/beneficios a destacar visualmente. Ej. ["resistente al agua", "ultra liviano"].'),
8099
+ 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.'),
8029
8100
  price: CreativeRefs.price,
8030
8101
  compareAtPrice: CreativeRefs.compareAtPrice,
8031
8102
  offer: CreativeRefs.offer,
@@ -8036,11 +8107,11 @@ var ImageInput = z39.object({
8036
8107
  brandId: CreativeRefs.brandId,
8037
8108
  creativeDirection: CreativeRefs.creativeDirection
8038
8109
  });
8039
- var LandingInput = z39.object({
8110
+ var LandingInput = z40.object({
8040
8111
  images: CreativeRefs.images,
8041
8112
  language: CreativeRefs.language,
8042
8113
  country: CreativeRefs.country,
8043
- 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.'),
8114
+ 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.'),
8044
8115
  buyerPersona: CreativeRefs.buyerPersona,
8045
8116
  price: CreativeRefs.price,
8046
8117
  compareAtPrice: CreativeRefs.compareAtPrice,
@@ -8138,8 +8209,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
8138
8209
  __name(registerCreativeTools, "registerCreativeTools");
8139
8210
 
8140
8211
  // src/tools/creative-variants.ts
8141
- import { z as z40 } from "zod";
8142
- var LandingSectionKeyEnum = z40.enum([
8212
+ import { z as z41 } from "zod";
8213
+ var LandingSectionKeyEnum = z41.enum([
8143
8214
  "problem_hook",
8144
8215
  "problem_cost",
8145
8216
  "solution_mechanism",
@@ -8179,24 +8250,24 @@ var LandingSectionKeyEnum = z40.enum([
8179
8250
  "lifestyle_fit",
8180
8251
  "join_lifestyle"
8181
8252
  ]);
8182
- var CreativeVariantKindEnum = z40.enum([
8253
+ var CreativeVariantKindEnum = z41.enum([
8183
8254
  "video",
8184
8255
  "image",
8185
8256
  "landing"
8186
8257
  ]);
8187
- var EstimateCreativeVariantInputBase = z40.object({
8188
- sourceTaskId: z40.string().uuid().describe("ID de la tarea original completada."),
8258
+ var EstimateCreativeVariantInputBase = z41.object({
8259
+ sourceTaskId: z41.string().uuid().describe("ID de la tarea original completada."),
8189
8260
  kind: CreativeVariantKindEnum,
8190
8261
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Solo cambia el preset p\xFAblico; el servidor conserva el modelo/tier del task origen."),
8191
8262
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video. Resoluci\xF3n del nuevo render."),
8192
- nVariants: z40.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
8263
+ nVariants: z41.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
8193
8264
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si ya conoces la secci\xF3n, evita cotizar la clasificaci\xF3n con IA."),
8194
- 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.")
8265
+ 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.")
8195
8266
  }).strict();
8196
8267
  var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine((input, ctx) => {
8197
8268
  if (input.kind === "video" && input.nVariants !== void 0) {
8198
8269
  ctx.addIssue({
8199
- code: z40.ZodIssueCode.custom,
8270
+ code: z41.ZodIssueCode.custom,
8200
8271
  path: [
8201
8272
  "nVariants"
8202
8273
  ],
@@ -8206,7 +8277,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8206
8277
  if (input.kind !== "video") {
8207
8278
  if (input.videoPreset !== void 0) {
8208
8279
  ctx.addIssue({
8209
- code: z40.ZodIssueCode.custom,
8280
+ code: z41.ZodIssueCode.custom,
8210
8281
  path: [
8211
8282
  "videoPreset"
8212
8283
  ],
@@ -8215,7 +8286,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8215
8286
  }
8216
8287
  if (input.videoResolution !== void 0) {
8217
8288
  ctx.addIssue({
8218
- code: z40.ZodIssueCode.custom,
8289
+ code: z41.ZodIssueCode.custom,
8219
8290
  path: [
8220
8291
  "videoResolution"
8221
8292
  ],
@@ -8225,7 +8296,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8225
8296
  }
8226
8297
  if (input.kind !== "image" && input.nVariants !== void 0) {
8227
8298
  ctx.addIssue({
8228
- code: z40.ZodIssueCode.custom,
8299
+ code: z41.ZodIssueCode.custom,
8229
8300
  path: [
8230
8301
  "nVariants"
8231
8302
  ],
@@ -8235,7 +8306,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8235
8306
  if (input.kind !== "landing") {
8236
8307
  if (input.sectionKey !== void 0) {
8237
8308
  ctx.addIssue({
8238
- code: z40.ZodIssueCode.custom,
8309
+ code: z41.ZodIssueCode.custom,
8239
8310
  path: [
8240
8311
  "sectionKey"
8241
8312
  ],
@@ -8244,7 +8315,7 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8244
8315
  }
8245
8316
  if (input.instructions !== void 0) {
8246
8317
  ctx.addIssue({
8247
- code: z40.ZodIssueCode.custom,
8318
+ code: z41.ZodIssueCode.custom,
8248
8319
  path: [
8249
8320
  "instructions"
8250
8321
  ],
@@ -8253,31 +8324,31 @@ var EstimateCreativeVariantInput = EstimateCreativeVariantInputBase.superRefine(
8253
8324
  }
8254
8325
  }
8255
8326
  });
8256
- var CreateCreativeVariantInputBase = z40.object({
8257
- sourceTaskId: z40.string().uuid().describe("ID de la tarea original completada."),
8327
+ var CreateCreativeVariantInputBase = z41.object({
8328
+ sourceTaskId: z41.string().uuid().describe("ID de la tarea original completada."),
8258
8329
  kind: CreativeVariantKindEnum,
8259
- confirm: z40.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
8260
- 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."),
8261
- angle: z40.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
8262
- buyerPersona: z40.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
8263
- scriptNotes: z40.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
8264
- creativeDirection: z40.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
8330
+ confirm: z41.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
8331
+ 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."),
8332
+ angle: z41.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
8333
+ buyerPersona: z41.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
8334
+ scriptNotes: z41.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
8335
+ creativeDirection: z41.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n creativa para una variante de imagen."),
8265
8336
  videoPreset: TaskCreativeVariantVideoPresetEnum.optional().describe("Opcional para video. Preset p\xFAblico; el servidor resuelve modelo/tier desde el task origen."),
8266
8337
  videoResolution: VideoResolutionEnum.optional().describe("Opcional para video."),
8267
- generateAiMusic: z40.boolean().optional().describe("Opcional para video."),
8268
- nVariants: z40.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
8338
+ generateAiMusic: z41.boolean().optional().describe("Opcional para video."),
8339
+ nVariants: z41.number().int().min(1).max(3).optional().describe("Opcional para imagen. N\xFAmero de im\xE1genes adicionales (1-3)."),
8269
8340
  imageStyle: ImageCreativeStyleEnum.optional().describe("Opcional para imagen."),
8270
8341
  creativeArchetype: ImageCreativeArchetypeEnum.optional().describe("Opcional para imagen."),
8271
8342
  visualTreatment: ImageCreativeVisualTreatmentEnum.optional().describe("Opcional para imagen."),
8272
8343
  aspectRatio: ImageCreativeAspectRatioEnum.optional().describe("Opcional para imagen."),
8273
- styleMix: z40.boolean().optional().describe("Opcional para imagen."),
8344
+ styleMix: z41.boolean().optional().describe("Opcional para imagen."),
8274
8345
  sectionKey: LandingSectionKeyEnum.optional().describe("Opcional para landing. Si se omite, se clasifica con IA a partir de instructions."),
8275
- 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.")
8346
+ 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.")
8276
8347
  }).strict();
8277
8348
  var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((input, ctx) => {
8278
8349
  if ((input.kind === "video" || input.kind === "image") && !input.angle) {
8279
8350
  ctx.addIssue({
8280
- code: z40.ZodIssueCode.custom,
8351
+ code: z41.ZodIssueCode.custom,
8281
8352
  path: [
8282
8353
  "angle"
8283
8354
  ],
@@ -8286,7 +8357,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
8286
8357
  }
8287
8358
  if (input.kind === "landing" && !input.sectionKey && !input.instructions) {
8288
8359
  ctx.addIssue({
8289
- code: z40.ZodIssueCode.custom,
8360
+ code: z41.ZodIssueCode.custom,
8290
8361
  path: [
8291
8362
  "instructions"
8292
8363
  ],
@@ -8332,7 +8403,7 @@ var CreateCreativeVariantInput = CreateCreativeVariantInputBase.superRefine((inp
8332
8403
  for (const field of fieldsByKind[input.kind]) {
8333
8404
  if (input[field] !== void 0) {
8334
8405
  ctx.addIssue({
8335
- code: z40.ZodIssueCode.custom,
8406
+ code: z41.ZodIssueCode.custom,
8336
8407
  path: [
8337
8408
  field
8338
8409
  ],
@@ -8449,27 +8520,27 @@ function registerCreativeVariantTools(server, api) {
8449
8520
  __name(registerCreativeVariantTools, "registerCreativeVariantTools");
8450
8521
 
8451
8522
  // src/tools/script-review.ts
8452
- import { z as z41 } from "zod";
8453
- var GetVideoScriptInput = z41.object({
8454
- taskId: z41.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
8455
- 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.')
8523
+ import { z as z42 } from "zod";
8524
+ var GetVideoScriptInput = z42.object({
8525
+ taskId: z42.string().min(1).describe("ID de la tarea (la misma que devolvi\xF3 create_product_task / generate_video_creative)."),
8526
+ 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.')
8456
8527
  }).strict();
8457
8528
  var VIDEO_SCRIPT_APPROVAL_MAX_SCENES2 = 50;
8458
8529
  var VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2 = 2e3;
8459
- var ApproveSceneEdit = z41.object({
8460
- index: z41.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
8461
- dialogue: z41.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
8530
+ var ApproveSceneEdit = z42.object({
8531
+ index: z42.number().int().nonnegative().describe("\xCDndice de la escena (0-based, mismo que devuelve get_video_script)."),
8532
+ dialogue: z42.string().min(1).max(VIDEO_SCRIPT_APPROVAL_MAX_DIALOGUE_CHARS2).refine((v) => v.trim().length > 0, {
8462
8533
  message: "dialogue no puede ser s\xF3lo espacios"
8463
8534
  }).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.")
8464
8535
  }).strict();
8465
- var ApproveVideoScriptInput = z41.object({
8466
- taskId: z41.string().min(1).describe("ID de la tarea."),
8467
- 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.'),
8468
- 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.")
8536
+ var ApproveVideoScriptInput = z42.object({
8537
+ taskId: z42.string().min(1).describe("ID de la tarea."),
8538
+ 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.'),
8539
+ 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.")
8469
8540
  }).strict();
8470
- var RegenerateVideoScriptInput = z41.object({
8471
- taskId: z41.string().min(1).describe("ID de la tarea."),
8472
- 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.'),
8541
+ var RegenerateVideoScriptInput = z42.object({
8542
+ taskId: z42.string().min(1).describe("ID de la tarea."),
8543
+ 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.'),
8473
8544
  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).')
8474
8545
  }).strict();
8475
8546
  function registerScriptReviewTools(server, api) {
@@ -8509,9 +8580,9 @@ function registerScriptReviewTools(server, api) {
8509
8580
  __name(registerScriptReviewTools, "registerScriptReviewTools");
8510
8581
 
8511
8582
  // src/tools/connections.ts
8512
- import { z as z42 } from "zod";
8583
+ import { z as z43 } from "zod";
8513
8584
  function registerConnectionTools(server, api) {
8514
- 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 () => {
8585
+ 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 () => {
8515
8586
  try {
8516
8587
  const r = await api.getJson("/v1/shopify/shops");
8517
8588
  return asText(r);
@@ -8519,10 +8590,10 @@ function registerConnectionTools(server, api) {
8519
8590
  throw translateError(e);
8520
8591
  }
8521
8592
  });
8522
- 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({
8523
- shopDomain: z42.string().min(3).max(120),
8524
- query: z42.string().trim().max(120).optional(),
8525
- taskId: z42.string().uuid().optional()
8593
+ 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({
8594
+ shopDomain: z43.string().min(3).max(120),
8595
+ query: z43.string().trim().max(120).optional(),
8596
+ taskId: z43.string().uuid().optional()
8526
8597
  }).shape, async ({ shopDomain, query, taskId }) => {
8527
8598
  try {
8528
8599
  const params = new URLSearchParams({
@@ -8536,7 +8607,7 @@ function registerConnectionTools(server, api) {
8536
8607
  throw translateError(e);
8537
8608
  }
8538
8609
  });
8539
- 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 () => {
8610
+ 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 () => {
8540
8611
  try {
8541
8612
  const r = await api.getJson("/v1/facebook/ad-accounts");
8542
8613
  return asText(r);
@@ -8544,7 +8615,7 @@ function registerConnectionTools(server, api) {
8544
8615
  throw translateError(e);
8545
8616
  }
8546
8617
  });
8547
- 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 () => {
8618
+ 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 () => {
8548
8619
  try {
8549
8620
  const r = await api.getJson("/v1/tiktok/advertisers");
8550
8621
  return asText(r);
@@ -8556,115 +8627,128 @@ function registerConnectionTools(server, api) {
8556
8627
  __name(registerConnectionTools, "registerConnectionTools");
8557
8628
 
8558
8629
  // src/tools/publish.ts
8559
- import { z as z43 } from "zod";
8560
- var ShopifyInput = z43.object({
8561
- taskId: z43.string().min(1),
8562
- shopDomain: z43.string().min(1),
8630
+ import { z as z44 } from "zod";
8631
+ var ShopifyInput = z44.object({
8632
+ taskId: z44.string().min(1),
8633
+ shopDomain: z44.string().min(1),
8563
8634
  /** Override: number of landing images to include. Default = ALL images the
8564
8635
  * task generated (the MCP fetches /v1/results first to count). Bounded
8565
8636
  * generously here; the backend validates each index against
8566
8637
  * landing.images.length and rejects out-of-range. */
8567
- numLandingImages: z43.number().int().min(1).max(20).optional(),
8568
- variantIndex: z43.number().int().min(0).optional(),
8638
+ numLandingImages: z44.number().int().min(1).max(20).optional(),
8639
+ variantIndex: z44.number().int().min(0).optional(),
8569
8640
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
8570
8641
  * When true, the landing renders variant-picker pills; when false, it ships
8571
8642
  * a single-option product page. Defaults to false. */
8572
- showVariantPicker: z43.boolean().optional(),
8643
+ showVariantPicker: z44.boolean().optional(),
8573
8644
  /** Default `auto` updates the product previously published for this task and
8574
8645
  * shop. Use `existing` with targetProductId to choose another product, or
8575
8646
  * `new` only when the user explicitly wants a duplicate/new listing. */
8576
- publicationMode: z43.enum([
8647
+ publicationMode: z44.enum([
8577
8648
  "auto",
8578
8649
  "existing",
8579
8650
  "new"
8580
8651
  ]).optional(),
8581
8652
  /** Shopify product GID, numeric id, or admin product URL. Required when
8582
8653
  * publicationMode is `existing`. */
8583
- targetProductId: z43.string().trim().min(1).max(300).optional()
8654
+ targetProductId: z44.string().trim().min(1).max(300).optional()
8584
8655
  });
8585
- var McpCampaignConfig = z43.object({
8656
+ var McpCampaignConfig = z44.object({
8586
8657
  /** ALWAYS in US dollars, whatever the ad account's currency is. ProductMaker
8587
8658
  * converts to the account currency server-side. Do NOT pre-convert: on a COP
8588
8659
  * account the user's "60.000 al día" is `dailyBudgetUsd: 15`, not 60000. */
8589
- dailyBudgetUsd: z43.number().positive().optional(),
8660
+ dailyBudgetUsd: z44.number().positive().optional(),
8590
8661
  /** ISO-3166-1 alpha-2 country codes. Meta accepts max 1 (single-country
8591
8662
  * ad set). TikTok takes only the first entry. */
8592
- targetCountries: z43.array(z43.string().length(2)).max(1).optional(),
8593
- campaignName: z43.string().trim().min(1).max(100).optional()
8594
- });
8595
- var MetaInput = z43.object({
8596
- taskId: z43.string().min(1),
8597
- adAccountId: z43.string().min(1),
8598
- pixelId: z43.string().min(1).optional(),
8599
- landingUrl: z43.string().url().optional(),
8663
+ targetCountries: z44.array(z44.string().length(2)).max(1).optional(),
8664
+ campaignName: z44.string().trim().min(1).max(100).optional()
8665
+ });
8666
+ var McpAdAccountId = MetaAdAccountId.describe('Meta ad account id. Accepts "act_1234567890" or the bare numeric id "1234567890".');
8667
+ var MetaInput = z44.object({
8668
+ taskId: z44.string().min(1),
8669
+ adAccountId: McpAdAccountId,
8670
+ pixelId: z44.string().min(1).optional(),
8671
+ landingUrl: z44.string().url().optional(),
8600
8672
  /** Select which angles to publish by 0-based index. Omit to publish all
8601
8673
  * successful angles. Example: a task with 3 angles → `[0,2]` publishes #0 + #2. */
8602
- angleIndexes: z43.array(z43.number().int().nonnegative()).optional(),
8674
+ angleIndexes: z44.array(z44.number().int().nonnegative()).optional(),
8603
8675
  campaignConfig: McpCampaignConfig.optional(),
8604
8676
  /** Override the ad's primary text (body copy). If omitted, ProductMaker
8605
8677
  * uses the auto-generated copy attached to the task. Capped at 500 chars
8606
8678
  * (Meta's primary-text limit). */
8607
- primaryText: z43.string().trim().min(1).max(500).optional(),
8679
+ primaryText: z44.string().trim().min(1).max(500).optional(),
8608
8680
  /** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
8609
8681
  * (wa.me) instead of a landing page. */
8610
- phoneNumber: z43.string().min(8).max(32).optional()
8682
+ phoneNumber: z44.string().min(8).max(32).optional()
8611
8683
  });
8612
- var GroupedTaskSource = z43.object({
8613
- type: z43.literal("task"),
8614
- taskId: z43.string().min(1),
8615
- angleIndex: z43.number().int().min(0).optional(),
8616
- videoVariant: z43.enum([
8684
+ var GroupedTaskSource = z44.object({
8685
+ type: z44.literal("task"),
8686
+ taskId: z44.string().min(1),
8687
+ angleIndex: z44.number().int().min(0).optional(),
8688
+ videoVariant: z44.enum([
8617
8689
  "with_subtitles",
8618
8690
  "without_subtitles"
8619
8691
  ]).optional(),
8620
- imageVariantIndex: z43.number().int().min(0).optional()
8621
- });
8622
- var GroupedMediaSource = z43.object({
8623
- type: z43.literal("media"),
8624
- url: z43.string().url().optional(),
8625
- path: z43.string().min(1).optional(),
8626
- thumbnailUrl: z43.string().url().optional(),
8627
- thumbnailPath: z43.string().min(1).optional()
8692
+ imageVariantIndex: z44.number().int().min(0).optional()
8693
+ });
8694
+ var GroupedMediaSource = z44.object({
8695
+ type: z44.literal("media"),
8696
+ url: z44.string().url().optional(),
8697
+ path: z44.string().min(1).optional(),
8698
+ thumbnailUrl: z44.string().url().optional(),
8699
+ thumbnailPath: z44.string().min(1).optional()
8628
8700
  }).refine((value) => Boolean(value.url) !== Boolean(value.path), {
8629
8701
  message: "media source needs exactly one of `url` or `path`"
8630
8702
  }).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
8631
8703
  message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
8632
8704
  });
8633
- var GroupedCreative = z43.object({
8634
- type: z43.enum([
8705
+ var GroupedCreative = z44.object({
8706
+ type: z44.enum([
8635
8707
  "image",
8636
8708
  "video"
8637
8709
  ]),
8638
- source: z43.union([
8710
+ source: z44.union([
8639
8711
  GroupedTaskSource,
8640
8712
  GroupedMediaSource
8641
8713
  ]),
8642
- title: z43.string().max(120).optional(),
8643
- description: z43.string().max(500).optional(),
8714
+ title: z44.string().max(120).optional(),
8715
+ description: z44.string().max(500).optional(),
8644
8716
  cta: MetaCtaType.optional(),
8645
- creativeId: z43.string().min(1).optional()
8717
+ creativeId: z44.string().min(1).optional()
8646
8718
  });
8647
- var GroupedCampaignConfig = z43.object({
8719
+ var GroupedCampaignConfig = z44.object({
8648
8720
  campaignType: MetaCampaignType.default("sales"),
8649
8721
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
8650
8722
  budgetMode: MetaBudgetMode.default("campaign"),
8651
- campaignDailyBudget: z43.number().int().positive().optional(),
8652
- adSets: z43.array(z43.object({
8653
- tempId: z43.string().min(1).max(80),
8654
- name: z43.string().min(1).max(100),
8655
- dailyBudget: z43.number().int().positive().optional(),
8656
- creativeIds: z43.array(z43.string().min(1)).min(1)
8657
- })).min(1).max(4),
8658
- destination: z43.object({
8659
- type: z43.literal("landing"),
8660
- landingUrl: z43.string().url()
8661
- }),
8662
- saveAsDefault: z43.boolean().default(false),
8723
+ campaignDailyBudget: z44.number().int().positive().optional(),
8724
+ adSets: z44.array(z44.object({
8725
+ tempId: z44.string().min(1).max(80),
8726
+ name: z44.string().min(1).max(100),
8727
+ dailyBudget: z44.number().int().positive().optional(),
8728
+ creativeIds: z44.array(z44.string().min(1)).min(1)
8729
+ })).min(1).max(CAMPAIGN_LIMITS.MAX_AD_SETS),
8730
+ /**
8731
+ * Valid values are 'landing' and 'whatsapp' — there is no 'website'. This
8732
+ * used to be hardcoded to `landing`, which both rejected `website` with an
8733
+ * opaque `invalid_literal` and made the WhatsApp destination — supported by
8734
+ * the API since #849 — unreachable from this tool.
8735
+ */
8736
+ destination: z44.discriminatedUnion("type", [
8737
+ z44.object({
8738
+ type: z44.literal("landing"),
8739
+ landingUrl: z44.string().url()
8740
+ }),
8741
+ z44.object({
8742
+ type: z44.literal("whatsapp"),
8743
+ phoneNumber: z44.string().min(8).max(32)
8744
+ })
8745
+ ]),
8746
+ saveAsDefault: z44.boolean().default(false),
8663
8747
  budgetPolicy: MetaBudgetPolicy.optional()
8664
8748
  }).superRefine((value, ctx) => {
8665
8749
  if (value.budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
8666
8750
  ctx.addIssue({
8667
- code: z43.ZodIssueCode.custom,
8751
+ code: z44.ZodIssueCode.custom,
8668
8752
  path: [
8669
8753
  "campaignDailyBudget"
8670
8754
  ],
@@ -8675,7 +8759,7 @@ var GroupedCampaignConfig = z43.object({
8675
8759
  value.adSets.forEach((adSet, index) => {
8676
8760
  if (adSet.dailyBudget === void 0) {
8677
8761
  ctx.addIssue({
8678
- code: z43.ZodIssueCode.custom,
8762
+ code: z44.ZodIssueCode.custom,
8679
8763
  path: [
8680
8764
  "adSets",
8681
8765
  index,
@@ -8687,19 +8771,31 @@ var GroupedCampaignConfig = z43.object({
8687
8771
  });
8688
8772
  }
8689
8773
  });
8690
- var PublishCreativesToMetaInputBase = z43.object({
8691
- creatives: z43.array(GroupedCreative).min(1).max(4),
8692
- adAccountId: z43.string().min(1),
8693
- pixelId: z43.string().min(1).optional(),
8694
- landingUrl: z43.string().url(),
8695
- targetCountry: z43.string().length(2),
8696
- campaignName: z43.string().trim().min(1).max(100),
8774
+ var PublishCreativesToMetaInputBase = z44.object({
8775
+ creatives: z44.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
8776
+ adAccountId: McpAdAccountId,
8777
+ pixelId: z44.string().min(1).optional(),
8778
+ /** Required unless `campaignConfig.destination` is whatsapp — see the
8779
+ * superRefine below. Mirrors the API's `UploadToMetaRequest`. */
8780
+ landingUrl: z44.string().url().optional(),
8781
+ targetCountry: z44.string().length(2),
8782
+ campaignName: z44.string().trim().min(1).max(100),
8697
8783
  campaignConfig: GroupedCampaignConfig.optional()
8698
8784
  });
8699
8785
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
8700
- if (value.campaignConfig && value.campaignConfig.destination.landingUrl !== value.landingUrl) {
8786
+ const destination = value.campaignConfig?.destination;
8787
+ if (destination?.type !== "whatsapp" && !value.landingUrl) {
8788
+ ctx.addIssue({
8789
+ code: z44.ZodIssueCode.custom,
8790
+ path: [
8791
+ "landingUrl"
8792
+ ],
8793
+ message: "landingUrl is required unless campaignConfig.destination.type is whatsapp"
8794
+ });
8795
+ }
8796
+ if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
8701
8797
  ctx.addIssue({
8702
- code: z43.ZodIssueCode.custom,
8798
+ code: z44.ZodIssueCode.custom,
8703
8799
  path: [
8704
8800
  "campaignConfig",
8705
8801
  "destination",
@@ -8709,12 +8805,12 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
8709
8805
  });
8710
8806
  }
8711
8807
  });
8712
- var PresignUploadResponseSchema = z43.object({
8713
- uploadUrl: z43.string().url().refine((value) => {
8808
+ var PresignUploadResponseSchema = z44.object({
8809
+ uploadUrl: z44.string().url().refine((value) => {
8714
8810
  const url = new URL(value);
8715
8811
  return !url.username && !url.password && url.protocol === "https:";
8716
8812
  }, "presigned upload URL must be HTTPS without embedded credentials"),
8717
- publicUrl: z43.string().url().refine((value) => {
8813
+ publicUrl: z44.string().url().refine((value) => {
8718
8814
  const url = new URL(value);
8719
8815
  return !url.username && !url.password;
8720
8816
  }, "public media URL must not contain embedded credentials")
@@ -8739,16 +8835,16 @@ async function resolveAndUploadMedia(api, ref, expectedKind, transport) {
8739
8835
  return uploadResolvedMedia(api, media);
8740
8836
  }
8741
8837
  __name(resolveAndUploadMedia, "resolveAndUploadMedia");
8742
- var TikTokInput = z43.object({
8743
- taskId: z43.string().min(1),
8744
- advertiserId: z43.string().min(1),
8745
- pixelId: z43.string().min(1).optional(),
8746
- landingUrl: z43.string().url().optional(),
8747
- angleIndexes: z43.array(z43.number().int().nonnegative()).optional(),
8838
+ var TikTokInput = z44.object({
8839
+ taskId: z44.string().min(1),
8840
+ advertiserId: z44.string().min(1),
8841
+ pixelId: z44.string().min(1).optional(),
8842
+ landingUrl: z44.string().url().optional(),
8843
+ angleIndexes: z44.array(z44.number().int().nonnegative()).optional(),
8748
8844
  campaignConfig: McpCampaignConfig.optional(),
8749
8845
  /** Override the ad's description (TikTok's body copy). If omitted,
8750
8846
  * ProductMaker uses the auto-generated copy attached to the task. */
8751
- primaryText: z43.string().trim().min(1).max(500).optional()
8847
+ primaryText: z44.string().trim().min(1).max(500).optional()
8752
8848
  });
8753
8849
  function registerPublishTools(server, api, opts = {}) {
8754
8850
  const transport = opts.transport ?? "http";
@@ -8787,7 +8883,7 @@ function registerPublishTools(server, api, opts = {}) {
8787
8883
  throw translateError(e);
8788
8884
  }
8789
8885
  });
8790
- 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 }) => {
8886
+ 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 }) => {
8791
8887
  try {
8792
8888
  const body = {
8793
8889
  ...rest
@@ -8815,8 +8911,11 @@ function registerPublishTools(server, api, opts = {}) {
8815
8911
  throw translateError(e);
8816
8912
  }
8817
8913
  });
8818
- 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) => {
8914
+ 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) => {
8819
8915
  try {
8916
+ const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
8917
+ if (!parsed.success) throw invalidInputFromZod(parsed.error);
8918
+ const input = parsed.data;
8820
8919
  const creatives = [];
8821
8920
  for (let index = 0; index < input.creatives.length; index += 1) {
8822
8921
  const creative = input.creatives[index];
@@ -8844,9 +8943,6 @@ function registerPublishTools(server, api, opts = {}) {
8844
8943
  }, creative.type, transport);
8845
8944
  let thumbnailUrl;
8846
8945
  if (creative.type === "video") {
8847
- if (!creative.source.thumbnailUrl && !creative.source.thumbnailPath && !creative.source.path) {
8848
- 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.");
8849
- }
8850
8946
  if (creative.source.thumbnailUrl || creative.source.thumbnailPath) {
8851
8947
  thumbnailUrl = await resolveAndUploadMedia(api, {
8852
8948
  url: creative.source.thumbnailUrl,
@@ -8959,13 +9055,13 @@ function toTikTokCampaignConfig(cfg) {
8959
9055
  __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
8960
9056
 
8961
9057
  // src/tools/campaigns.ts
8962
- import { z as z44 } from "zod";
8963
- var MetaStatusInput = z44.object({
8964
- campaignId: z44.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
9058
+ import { z as z45 } from "zod";
9059
+ var MetaStatusInput = z45.object({
9060
+ campaignId: z45.string().min(1).describe("Meta campaign ID returned by publish_to_meta.")
8965
9061
  });
8966
- var TikTokStatusInput = z44.object({
8967
- campaignId: z44.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
8968
- advertiserId: z44.string().min(1).describe("The same advertiserId used to publish the campaign.")
9062
+ var TikTokStatusInput = z45.object({
9063
+ campaignId: z45.string().min(1).describe("TikTok campaign ID returned by publish_to_tiktok."),
9064
+ advertiserId: z45.string().min(1).describe("The same advertiserId used to publish the campaign.")
8969
9065
  });
8970
9066
  function registerCampaignTools(server, api) {
8971
9067
  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) => {
@@ -9062,10 +9158,10 @@ function renderTikTokStatusMarkdown(r) {
9062
9158
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
9063
9159
 
9064
9160
  // ../../packages/product-economics/src/product-costing.ts
9065
- import { z as z46 } from "zod";
9161
+ import { z as z47 } from "zod";
9066
9162
 
9067
9163
  // ../../packages/product-economics/src/product-costing-countries.ts
9068
- import { z as z45 } from "zod";
9164
+ import { z as z46 } from "zod";
9069
9165
 
9070
9166
  // ../../packages/product-economics/src/product-costing-shipping-references.json
9071
9167
  var product_costing_shipping_references_default = {
@@ -9129,21 +9225,21 @@ var product_costing_shipping_references_default = {
9129
9225
  };
9130
9226
 
9131
9227
  // ../../packages/product-economics/src/product-costing-countries.ts
9132
- var ShippingReferenceSchema = z45.object({
9133
- reference: z45.number().positive(),
9134
- min: z45.number().nonnegative(),
9135
- max: z45.number().positive(),
9136
- updatedAt: z45.string().date(),
9137
- confidence: z45.enum([
9228
+ var ShippingReferenceSchema = z46.object({
9229
+ reference: z46.number().positive(),
9230
+ min: z46.number().nonnegative(),
9231
+ max: z46.number().positive(),
9232
+ updatedAt: z46.string().date(),
9233
+ confidence: z46.enum([
9138
9234
  "high",
9139
9235
  "medium",
9140
9236
  "medium-low"
9141
9237
  ]),
9142
- sources: z45.array(z45.string().url().startsWith("https://")).min(1).max(3)
9238
+ sources: z46.array(z46.string().url().startsWith("https://")).min(1).max(3)
9143
9239
  }).superRefine(({ reference, min, max }, context) => {
9144
9240
  if (min > reference || reference > max) {
9145
9241
  context.addIssue({
9146
- code: z45.ZodIssueCode.custom,
9242
+ code: z46.ZodIssueCode.custom,
9147
9243
  message: "shipping_reference_outside_range",
9148
9244
  path: [
9149
9245
  "reference"
@@ -9151,7 +9247,7 @@ var ShippingReferenceSchema = z45.object({
9151
9247
  });
9152
9248
  }
9153
9249
  });
9154
- var SHIPPING_REFERENCES = z45.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
9250
+ var SHIPPING_REFERENCES = z46.record(ShippingReferenceSchema).parse(product_costing_shipping_references_default);
9155
9251
  var PRODUCT_COSTING_COUNTRY_CODES = Object.freeze(Object.keys(SHIPPING_REFERENCES));
9156
9252
  var SPANISH_LOCALE_COUNTRIES = /* @__PURE__ */ new Set([
9157
9253
  "MX",
@@ -9333,18 +9429,18 @@ function getProductCostingCountryName(country) {
9333
9429
  __name(getProductCostingCountryName, "getProductCostingCountryName");
9334
9430
 
9335
9431
  // ../../packages/product-economics/src/product-costing.ts
9336
- var ProductCostingInputSchema = z46.object({
9337
- country: z46.string().refine(isProductCostingCountryCode, "unsupported_country"),
9338
- supplierCost: z46.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
9339
- shippingCost: z46.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
9340
- returnRate: z46.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
9341
- cancellationRate: z46.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
9342
- adsManagerCpa: z46.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
9343
- desiredMargin: z46.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
9432
+ var ProductCostingInputSchema = z47.object({
9433
+ country: z47.string().refine(isProductCostingCountryCode, "unsupported_country"),
9434
+ supplierCost: z47.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
9435
+ shippingCost: z47.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
9436
+ returnRate: z47.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
9437
+ cancellationRate: z47.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
9438
+ adsManagerCpa: z47.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
9439
+ desiredMargin: z47.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
9344
9440
  }).superRefine(({ returnRate, cancellationRate }, context) => {
9345
9441
  if (returnRate + cancellationRate >= 1) {
9346
9442
  context.addIssue({
9347
- code: z46.ZodIssueCode.custom,
9443
+ code: z47.ZodIssueCode.custom,
9348
9444
  message: "delivery_rate_positive",
9349
9445
  path: [
9350
9446
  "cancellationRate"
@@ -9411,14 +9507,14 @@ function calculateProductCosting(rawInput) {
9411
9507
  __name(calculateProductCosting, "calculateProductCosting");
9412
9508
 
9413
9509
  // src/tools/product-costing.ts
9414
- import { z as z47 } from "zod";
9415
- var optionalAmount = /* @__PURE__ */ __name((description) => z47.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
9416
- var optionalPercent = /* @__PURE__ */ __name((description) => z47.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
9417
- var ProductCostingToolInput = z47.object({
9418
- country: z47.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
9510
+ import { z as z48 } from "zod";
9511
+ var optionalAmount = /* @__PURE__ */ __name((description) => z48.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalAmount");
9512
+ var optionalPercent = /* @__PURE__ */ __name((description) => z48.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
9513
+ var ProductCostingToolInput = z48.object({
9514
+ country: z48.string().trim().toUpperCase().refine(isProductCostingCountryCode, {
9419
9515
  message: "Pa\xEDs no soportado por ProductMaker."
9420
9516
  }).optional().describe("C\xF3digo ISO-2 de cualquiera de los pa\xEDses disponibles en ProductMaker. Omite para Colombia."),
9421
- 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."),
9517
+ 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."),
9422
9518
  supplierCostCop: optionalAmount("Alias anterior para costo del proveedor en Colombia. Prefiere supplierCost."),
9423
9519
  shippingCost: optionalAmount("Costo de un env\xEDo en la moneda del pa\xEDs. Omite para usar el valor gu\xEDa vigente."),
9424
9520
  shippingCostCop: optionalAmount("Alias anterior para flete en Colombia. Prefiere shippingCost."),