ponch-mcp-server 1.0.83 → 1.0.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +784 -495
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2478,7 +2478,7 @@ function registerCoreTools(server, session) {
|
|
|
2478
2478
|
}
|
|
2479
2479
|
|
|
2480
2480
|
// src/tools/marketing.ts
|
|
2481
|
-
var
|
|
2481
|
+
var import_zod51 = require("zod");
|
|
2482
2482
|
|
|
2483
2483
|
// ../packages/martin-schemas/dist/index.js
|
|
2484
2484
|
var import_zod21 = require("zod");
|
|
@@ -9753,6 +9753,96 @@ async function getCurrentUsage(_tenantId, _quotaName) {
|
|
|
9753
9753
|
function getWrapperMessage(key, locale) {
|
|
9754
9754
|
return getMessage(`marketing.wrapper.${key}`, locale);
|
|
9755
9755
|
}
|
|
9756
|
+
function _extractReceived(issue) {
|
|
9757
|
+
if (issue.received !== void 0 && issue.received !== null) {
|
|
9758
|
+
return String(issue.received);
|
|
9759
|
+
}
|
|
9760
|
+
const m = issue.message?.match(/received\s+(\w+)/i);
|
|
9761
|
+
return m ? m[1] : "unknown";
|
|
9762
|
+
}
|
|
9763
|
+
function _placeholderForType(expected, locale) {
|
|
9764
|
+
switch (expected) {
|
|
9765
|
+
case "string":
|
|
9766
|
+
return locale === "en" ? '"a-string"' : '"un-texto"';
|
|
9767
|
+
case "number":
|
|
9768
|
+
return "42";
|
|
9769
|
+
case "integer":
|
|
9770
|
+
return "42";
|
|
9771
|
+
case "boolean":
|
|
9772
|
+
return "true";
|
|
9773
|
+
case "array":
|
|
9774
|
+
return "[]";
|
|
9775
|
+
case "object":
|
|
9776
|
+
return "{}";
|
|
9777
|
+
case "null":
|
|
9778
|
+
return "null";
|
|
9779
|
+
default:
|
|
9780
|
+
return locale === "en" ? '"a-value"' : '"un-valor"';
|
|
9781
|
+
}
|
|
9782
|
+
}
|
|
9783
|
+
function _unitForOrigin(origin, locale) {
|
|
9784
|
+
if (origin === "string") return locale === "en" ? "characters" : "caracteres";
|
|
9785
|
+
if (origin === "array") return locale === "en" ? "items" : "elementos";
|
|
9786
|
+
if (origin === "set") return locale === "en" ? "items" : "elementos";
|
|
9787
|
+
if (origin === "number" || origin === "bigint" || origin === "int") return "";
|
|
9788
|
+
if (origin === "date") return locale === "en" ? "date" : "fecha";
|
|
9789
|
+
return locale === "en" ? "units" : "unidades";
|
|
9790
|
+
}
|
|
9791
|
+
function getIssueHumanText(issue, locale) {
|
|
9792
|
+
const field = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
9793
|
+
switch (issue.code) {
|
|
9794
|
+
case "invalid_type": {
|
|
9795
|
+
const expected = String(issue.expected ?? "unknown");
|
|
9796
|
+
const received = _extractReceived(issue);
|
|
9797
|
+
const example = _placeholderForType(issue.expected, locale);
|
|
9798
|
+
return getMessage("marketing.wrapper.issue_invalid_type", locale, {
|
|
9799
|
+
field,
|
|
9800
|
+
expected,
|
|
9801
|
+
received,
|
|
9802
|
+
example
|
|
9803
|
+
});
|
|
9804
|
+
}
|
|
9805
|
+
case "too_small": {
|
|
9806
|
+
const minimum = String(issue.minimum ?? "");
|
|
9807
|
+
const unit = _unitForOrigin(issue.origin, locale);
|
|
9808
|
+
return getMessage("marketing.wrapper.issue_too_small", locale, {
|
|
9809
|
+
field,
|
|
9810
|
+
minimum,
|
|
9811
|
+
unit
|
|
9812
|
+
});
|
|
9813
|
+
}
|
|
9814
|
+
case "too_big": {
|
|
9815
|
+
const maximum = String(issue.maximum ?? "");
|
|
9816
|
+
const unit = _unitForOrigin(issue.origin, locale);
|
|
9817
|
+
return getMessage("marketing.wrapper.issue_too_big", locale, {
|
|
9818
|
+
field,
|
|
9819
|
+
maximum,
|
|
9820
|
+
unit
|
|
9821
|
+
});
|
|
9822
|
+
}
|
|
9823
|
+
case "invalid_enum_value":
|
|
9824
|
+
case "invalid_value": {
|
|
9825
|
+
const opts = (issue.options ?? []).map((o) => JSON.stringify(o)).join(", ");
|
|
9826
|
+
return getMessage("marketing.wrapper.issue_invalid_enum", locale, {
|
|
9827
|
+
field,
|
|
9828
|
+
options: opts
|
|
9829
|
+
});
|
|
9830
|
+
}
|
|
9831
|
+
case "invalid_string":
|
|
9832
|
+
case "invalid_format": {
|
|
9833
|
+
const hint = issue.validation ? `(${issue.validation})` : "";
|
|
9834
|
+
return getMessage("marketing.wrapper.issue_invalid_string", locale, {
|
|
9835
|
+
field,
|
|
9836
|
+
hint
|
|
9837
|
+
});
|
|
9838
|
+
}
|
|
9839
|
+
default:
|
|
9840
|
+
return getMessage("marketing.wrapper.issue_generic", locale, {
|
|
9841
|
+
field,
|
|
9842
|
+
message: issue.message
|
|
9843
|
+
});
|
|
9844
|
+
}
|
|
9845
|
+
}
|
|
9756
9846
|
var NIVELES_ORDER = ["ninguno", "ver", "editar", "completo"];
|
|
9757
9847
|
function nivelAlcanza(nivel, accion) {
|
|
9758
9848
|
return NIVELES_ORDER.indexOf(nivel) >= NIVELES_ORDER.indexOf(accion);
|
|
@@ -9855,9 +9945,16 @@ function wrapWithContract(contract, helper, options = {}) {
|
|
|
9855
9945
|
expected: issue.expected
|
|
9856
9946
|
};
|
|
9857
9947
|
});
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9948
|
+
let text;
|
|
9949
|
+
if (parseResult.error.issues.length === 0) {
|
|
9950
|
+
text = getWrapperMessage("input_invalido", locale);
|
|
9951
|
+
} else {
|
|
9952
|
+
const intro = getWrapperMessage("input_invalido_intro", locale);
|
|
9953
|
+
const lines = parseResult.error.issues.map(
|
|
9954
|
+
(i) => getIssueHumanText(i, locale)
|
|
9955
|
+
);
|
|
9956
|
+
text = `${intro} ${lines.join(" ")}`;
|
|
9957
|
+
}
|
|
9861
9958
|
await writeAuditLog({
|
|
9862
9959
|
tenantId: ctx.tenantId,
|
|
9863
9960
|
brandId: ctx.brandId ?? null,
|
|
@@ -10736,18 +10833,20 @@ var import_zod35 = require("zod");
|
|
|
10736
10833
|
var import_firebase_admin7 = require("firebase-admin");
|
|
10737
10834
|
var import_zod36 = require("zod");
|
|
10738
10835
|
var import_zod37 = require("zod");
|
|
10739
|
-
var import_firebase_admin8 = require("firebase-admin");
|
|
10740
10836
|
var import_zod38 = require("zod");
|
|
10741
10837
|
var import_zod39 = require("zod");
|
|
10838
|
+
var import_firebase_admin8 = require("firebase-admin");
|
|
10742
10839
|
var import_zod40 = require("zod");
|
|
10743
|
-
var import_firebase_admin9 = require("firebase-admin");
|
|
10744
10840
|
var import_zod41 = require("zod");
|
|
10745
|
-
var import_firebase_admin10 = require("firebase-admin");
|
|
10746
10841
|
var import_zod42 = require("zod");
|
|
10842
|
+
var import_firebase_admin9 = require("firebase-admin");
|
|
10747
10843
|
var import_zod43 = require("zod");
|
|
10844
|
+
var import_firebase_admin10 = require("firebase-admin");
|
|
10748
10845
|
var import_zod44 = require("zod");
|
|
10749
10846
|
var import_zod45 = require("zod");
|
|
10750
10847
|
var import_zod46 = require("zod");
|
|
10848
|
+
var import_zod47 = require("zod");
|
|
10849
|
+
var import_zod48 = require("zod");
|
|
10751
10850
|
var import_firestore6 = require("firebase-admin/firestore");
|
|
10752
10851
|
var RULE_NEGATIVES = {
|
|
10753
10852
|
allowFaces: "no people, no faces, no hands",
|
|
@@ -12062,6 +12161,229 @@ var rawContract7 = {
|
|
|
12062
12161
|
var getCalendarContract = MartinContractSchema.parse(
|
|
12063
12162
|
rawContract7
|
|
12064
12163
|
);
|
|
12164
|
+
async function seoSnapshotReader(input) {
|
|
12165
|
+
const { db, tenantId, brandId } = input;
|
|
12166
|
+
const brandRef = db.collection("tenants").doc(tenantId).collection("marketing_config").doc(brandId);
|
|
12167
|
+
const brandSnap = await brandRef.get();
|
|
12168
|
+
if (!brandSnap.exists) {
|
|
12169
|
+
return { ok: false, code: "BRAND_NOT_FOUND" };
|
|
12170
|
+
}
|
|
12171
|
+
const snap = await db.collection("tenants").doc(tenantId).collection("marketing_snapshots_semrush").where("brandId", "==", brandId).orderBy("mes", "desc").limit(1).get();
|
|
12172
|
+
if (snap.empty) {
|
|
12173
|
+
return { ok: false, code: "SEO_SNAPSHOT_MISSING" };
|
|
12174
|
+
}
|
|
12175
|
+
const doc = snap.docs[0];
|
|
12176
|
+
const data = doc.data();
|
|
12177
|
+
const mes = typeof data.mes === "string" ? data.mes : "";
|
|
12178
|
+
return {
|
|
12179
|
+
ok: true,
|
|
12180
|
+
brandId,
|
|
12181
|
+
mes,
|
|
12182
|
+
snapshot: { id: doc.id, ...data }
|
|
12183
|
+
};
|
|
12184
|
+
}
|
|
12185
|
+
var ParamsSchema8 = import_zod38.z.object({
|
|
12186
|
+
tenantId: import_zod38.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
12187
|
+
brandId: import_zod38.z.string().min(1).describe("Brand identifier within the tenant.")
|
|
12188
|
+
});
|
|
12189
|
+
var SuccessSchema = import_zod38.z.object({
|
|
12190
|
+
ok: import_zod38.z.literal(true),
|
|
12191
|
+
brandId: import_zod38.z.string(),
|
|
12192
|
+
mes: import_zod38.z.string(),
|
|
12193
|
+
snapshot: import_zod38.z.record(import_zod38.z.string(), import_zod38.z.unknown())
|
|
12194
|
+
});
|
|
12195
|
+
var FailureSchema = import_zod38.z.object({
|
|
12196
|
+
ok: import_zod38.z.literal(false),
|
|
12197
|
+
code: import_zod38.z.enum(["BRAND_NOT_FOUND", "SEO_SNAPSHOT_MISSING"])
|
|
12198
|
+
});
|
|
12199
|
+
var OutputSchema8 = import_zod38.z.discriminatedUnion("ok", [SuccessSchema, FailureSchema]);
|
|
12200
|
+
var rawContract8 = {
|
|
12201
|
+
name: "get_seo_snapshot",
|
|
12202
|
+
description: "Read the latest Semrush SEO snapshot for a brand. Returns rank, top keywords, opportunities, and competitors.",
|
|
12203
|
+
paramsSchema: ParamsSchema8,
|
|
12204
|
+
outputSchema: OutputSchema8,
|
|
12205
|
+
requiresConfirmation: false,
|
|
12206
|
+
destructive: false,
|
|
12207
|
+
affectsPublication: false,
|
|
12208
|
+
affectsExternal: false,
|
|
12209
|
+
martinSummaryTemplate: (input, output, locale) => {
|
|
12210
|
+
if (!output.ok) {
|
|
12211
|
+
if (output.code === "BRAND_NOT_FOUND") {
|
|
12212
|
+
return locale === "en" ? `Brand ${input.brandId} not found.` : `No encontr\xE9 la brand ${input.brandId}.`;
|
|
12213
|
+
}
|
|
12214
|
+
return locale === "en" ? `No SEO snapshot for brand ${input.brandId} yet.` : `A\xFAn no hay snapshot SEO de ${input.brandId}.`;
|
|
12215
|
+
}
|
|
12216
|
+
return locale === "en" ? `Latest SEO snapshot for ${input.brandId} (${output.mes}).` : `Snapshot SEO m\xE1s reciente de ${input.brandId} (${output.mes}).`;
|
|
12217
|
+
},
|
|
12218
|
+
// AUDITA SIEMPRE — regla A5. Lectura no muta, changes son null/null.
|
|
12219
|
+
auditAction: "marketing.seo_snapshot.leer",
|
|
12220
|
+
quotasConsumed: [],
|
|
12221
|
+
permissionScope: "module",
|
|
12222
|
+
permissionKey: "marketing",
|
|
12223
|
+
permissionAction: "ver",
|
|
12224
|
+
sideEffects: ["reads_firestore"]
|
|
12225
|
+
};
|
|
12226
|
+
var seoSnapshotReaderContract = MartinContractSchema.parse(
|
|
12227
|
+
rawContract8
|
|
12228
|
+
);
|
|
12229
|
+
var BEST_PRACTICES_EN = {
|
|
12230
|
+
title: "Primary keyword, clear and short. Page H1.",
|
|
12231
|
+
description: "Above grid: 2-3 sentences (50-70 words). Below grid: 200-400 words with long-tail keywords. Pages with descriptions rank 2.7x higher.",
|
|
12232
|
+
metaTitle: "50-60 chars. Keyword first. Format: {Keyword} | {Brand}. Max 600px.",
|
|
12233
|
+
metaDescription: "120-158 chars. Keyword + CTA + value prop. 120 mobile, 158 desktop.",
|
|
12234
|
+
handle: "Keyword in URL. NEVER change an indexed URL without a 301 redirect.",
|
|
12235
|
+
imageAlt: "Descriptive with keyword. Accessibility + Google Images + AEO.",
|
|
12236
|
+
aeo: "AI engines validate images against schema. Structured data matters."
|
|
12237
|
+
};
|
|
12238
|
+
var SCHEMA_FOR_SAVE_EN = {
|
|
12239
|
+
_instructions: "STRICT SCHEMA for save_collection_suggestions. Use EXACTLY these field names. The system WILL REJECT mismatched fields.",
|
|
12240
|
+
_example: {
|
|
12241
|
+
collectionId: 123456789,
|
|
12242
|
+
suggestedTitle: "Purple Flowers | Same-Day CDMX Delivery",
|
|
12243
|
+
suggestedDescription: "<p>Discover our purple flowers collection...</p>",
|
|
12244
|
+
suggestedMetaTitle: "Purple Flowers CDMX | Same-Day Delivery",
|
|
12245
|
+
suggestedMetaDescription: "Send purple flowers to your door in CDMX. Fresh arrangements with purple roses, tulips, and lilies. Same-day delivery.",
|
|
12246
|
+
suggestedHandle: null,
|
|
12247
|
+
suggestedImageAlt: "Bouquet of purple flowers with roses and tulips \u2014 local florist CDMX",
|
|
12248
|
+
keyword: "purple flowers cdmx",
|
|
12249
|
+
notas: "Meta title optimized with local keyword. Description expanded with long-tail keywords. Handle unchanged (already indexed)."
|
|
12250
|
+
},
|
|
12251
|
+
_rules: [
|
|
12252
|
+
"collectionId: REQUIRED. Numeric Shopify ID (not GID).",
|
|
12253
|
+
"suggestedTitle: string. Page H1. Clear with keyword.",
|
|
12254
|
+
"suggestedDescription: HTML string. 200-400 words. Include links to related collections.",
|
|
12255
|
+
"suggestedMetaTitle: string. 50-60 chars. Keyword first. NEVER over 60.",
|
|
12256
|
+
"suggestedMetaDescription: string. 120-158 chars. Keyword + CTA + value prop. NEVER over 158.",
|
|
12257
|
+
"suggestedHandle: string | null. Only change if the current handle is bad. null = no change.",
|
|
12258
|
+
"suggestedImageAlt: string. Descriptive with keyword. For accessibility + Google Images.",
|
|
12259
|
+
"keyword: string. Target keyword for this collection (from the marketing plan).",
|
|
12260
|
+
"notas: string. Brief explanation of what changed and why."
|
|
12261
|
+
],
|
|
12262
|
+
_never: [
|
|
12263
|
+
"NEVER use Spanish field names (titulo, descripcion, etc.) \u2014 use English names.",
|
|
12264
|
+
"NEVER fabricate collectionId \u2014 use the id from the collections array.",
|
|
12265
|
+
"NEVER exceed 60 chars in metaTitle or 158 chars in metaDescription.",
|
|
12266
|
+
"NEVER change the handle of an indexed collection without justification."
|
|
12267
|
+
]
|
|
12268
|
+
};
|
|
12269
|
+
async function collectionsReader(input) {
|
|
12270
|
+
const { db, tenantId, brandId } = input;
|
|
12271
|
+
const brandRef = db.collection("tenants").doc(tenantId).collection("marketing_config").doc(brandId);
|
|
12272
|
+
const brandSnap = await brandRef.get();
|
|
12273
|
+
if (!brandSnap.exists) {
|
|
12274
|
+
return { ok: false, code: "BRAND_NOT_FOUND" };
|
|
12275
|
+
}
|
|
12276
|
+
const collSnap = await db.collection("tenants").doc(tenantId).collection("marketing_snapshots").doc(brandId).collection("collections").get();
|
|
12277
|
+
if (collSnap.empty) {
|
|
12278
|
+
return { ok: false, code: "NO_COLLECTIONS_NESTED" };
|
|
12279
|
+
}
|
|
12280
|
+
const items = collSnap.docs.map((d) => d.data());
|
|
12281
|
+
const brand = brandSnap.data() ?? {};
|
|
12282
|
+
const plan = brand.plan ?? {};
|
|
12283
|
+
const keywordsPrioritarios = Array.isArray(plan.keywordsPrioritarios) ? plan.keywordsPrioritarios : [];
|
|
12284
|
+
const existingSuggestions = brand.collectionSuggestions ?? {};
|
|
12285
|
+
const collections = items.map((c) => {
|
|
12286
|
+
const seo = c.seo || {};
|
|
12287
|
+
const featured = c.featuredImage || null;
|
|
12288
|
+
return {
|
|
12289
|
+
id: c.platformId,
|
|
12290
|
+
title: c.title,
|
|
12291
|
+
handle: c.handle,
|
|
12292
|
+
body_html: typeof c.descriptionHtml === "string" ? c.descriptionHtml.slice(0, 500) : null,
|
|
12293
|
+
metaTitle: seo.metaTitle ?? null,
|
|
12294
|
+
metaDescription: seo.metaDescription ?? null,
|
|
12295
|
+
products_count: null,
|
|
12296
|
+
collectionType: "canonical",
|
|
12297
|
+
image: featured ? { src: featured.url, alt: featured.altText } : null,
|
|
12298
|
+
existingSuggestion: existingSuggestions[String(c.platformId)] ?? null
|
|
12299
|
+
};
|
|
12300
|
+
});
|
|
12301
|
+
return {
|
|
12302
|
+
ok: true,
|
|
12303
|
+
brandId,
|
|
12304
|
+
totalCollections: collections.length,
|
|
12305
|
+
keywordsPrioritarios,
|
|
12306
|
+
collections,
|
|
12307
|
+
bestPractices: BEST_PRACTICES_EN,
|
|
12308
|
+
schemaParaSave: SCHEMA_FOR_SAVE_EN
|
|
12309
|
+
};
|
|
12310
|
+
}
|
|
12311
|
+
var ParamsSchema9 = import_zod39.z.object({
|
|
12312
|
+
tenantId: import_zod39.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
12313
|
+
brandId: import_zod39.z.string().min(1).describe("Brand identifier within the tenant.")
|
|
12314
|
+
});
|
|
12315
|
+
var CollectionItemSchema = import_zod39.z.object({
|
|
12316
|
+
id: import_zod39.z.unknown(),
|
|
12317
|
+
title: import_zod39.z.unknown(),
|
|
12318
|
+
handle: import_zod39.z.unknown(),
|
|
12319
|
+
body_html: import_zod39.z.string().nullable(),
|
|
12320
|
+
metaTitle: import_zod39.z.unknown(),
|
|
12321
|
+
metaDescription: import_zod39.z.unknown(),
|
|
12322
|
+
products_count: import_zod39.z.null(),
|
|
12323
|
+
collectionType: import_zod39.z.literal("canonical"),
|
|
12324
|
+
image: import_zod39.z.object({ src: import_zod39.z.unknown(), alt: import_zod39.z.unknown() }).nullable(),
|
|
12325
|
+
existingSuggestion: import_zod39.z.record(import_zod39.z.string(), import_zod39.z.unknown()).nullable()
|
|
12326
|
+
});
|
|
12327
|
+
var BestPracticesSchema = import_zod39.z.object({
|
|
12328
|
+
title: import_zod39.z.string(),
|
|
12329
|
+
description: import_zod39.z.string(),
|
|
12330
|
+
metaTitle: import_zod39.z.string(),
|
|
12331
|
+
metaDescription: import_zod39.z.string(),
|
|
12332
|
+
handle: import_zod39.z.string(),
|
|
12333
|
+
imageAlt: import_zod39.z.string(),
|
|
12334
|
+
aeo: import_zod39.z.string()
|
|
12335
|
+
});
|
|
12336
|
+
var SchemaForSaveSchema = import_zod39.z.object({
|
|
12337
|
+
_instructions: import_zod39.z.string(),
|
|
12338
|
+
_example: import_zod39.z.record(import_zod39.z.string(), import_zod39.z.unknown()),
|
|
12339
|
+
_rules: import_zod39.z.array(import_zod39.z.string()),
|
|
12340
|
+
_never: import_zod39.z.array(import_zod39.z.string())
|
|
12341
|
+
});
|
|
12342
|
+
var SuccessSchema2 = import_zod39.z.object({
|
|
12343
|
+
ok: import_zod39.z.literal(true),
|
|
12344
|
+
brandId: import_zod39.z.string(),
|
|
12345
|
+
totalCollections: import_zod39.z.number(),
|
|
12346
|
+
// Shape real: Array<{ keyword, posicion, volumen, dificultad, prioridad, accion }>.
|
|
12347
|
+
// Passthrough loose para no acoplar el reader al schema interno del plan.
|
|
12348
|
+
keywordsPrioritarios: import_zod39.z.array(import_zod39.z.record(import_zod39.z.string(), import_zod39.z.unknown())),
|
|
12349
|
+
collections: import_zod39.z.array(CollectionItemSchema),
|
|
12350
|
+
bestPractices: BestPracticesSchema,
|
|
12351
|
+
schemaParaSave: SchemaForSaveSchema
|
|
12352
|
+
});
|
|
12353
|
+
var FailureSchema2 = import_zod39.z.object({
|
|
12354
|
+
ok: import_zod39.z.literal(false),
|
|
12355
|
+
code: import_zod39.z.enum(["BRAND_NOT_FOUND", "NO_COLLECTIONS_NESTED"])
|
|
12356
|
+
});
|
|
12357
|
+
var OutputSchema9 = import_zod39.z.discriminatedUnion("ok", [SuccessSchema2, FailureSchema2]);
|
|
12358
|
+
var rawContract9 = {
|
|
12359
|
+
name: "get_collections",
|
|
12360
|
+
description: "Read all canonical collections (Shopify/WordPress/web-only) for a brand with their 6 SEO fields, plus best practices and the strict schema for generating SEO suggestions.",
|
|
12361
|
+
paramsSchema: ParamsSchema9,
|
|
12362
|
+
outputSchema: OutputSchema9,
|
|
12363
|
+
requiresConfirmation: false,
|
|
12364
|
+
destructive: false,
|
|
12365
|
+
affectsPublication: false,
|
|
12366
|
+
affectsExternal: false,
|
|
12367
|
+
martinSummaryTemplate: (input, output, locale) => {
|
|
12368
|
+
if (!output.ok) {
|
|
12369
|
+
if (output.code === "BRAND_NOT_FOUND") {
|
|
12370
|
+
return locale === "en" ? `Brand ${input.brandId} not found.` : `No encontr\xE9 la brand ${input.brandId}.`;
|
|
12371
|
+
}
|
|
12372
|
+
return locale === "en" ? `No collections synced for ${input.brandId} yet.` : `A\xFAn no hay colecciones sincronizadas para ${input.brandId}.`;
|
|
12373
|
+
}
|
|
12374
|
+
return locale === "en" ? `${output.totalCollections} collections for ${input.brandId}.` : `${output.totalCollections} colecciones para ${input.brandId}.`;
|
|
12375
|
+
},
|
|
12376
|
+
// AUDITA SIEMPRE — regla A5. Lectura no muta, changes son null/null.
|
|
12377
|
+
auditAction: "marketing.colecciones.listar",
|
|
12378
|
+
quotasConsumed: [],
|
|
12379
|
+
permissionScope: "module",
|
|
12380
|
+
permissionKey: "marketing",
|
|
12381
|
+
permissionAction: "ver",
|
|
12382
|
+
sideEffects: ["reads_firestore"]
|
|
12383
|
+
};
|
|
12384
|
+
var collectionsReaderContract = MartinContractSchema.parse(
|
|
12385
|
+
rawContract9
|
|
12386
|
+
);
|
|
12065
12387
|
var PLATAFORMA_A_FORMATO = {
|
|
12066
12388
|
gbp: "gbp_4_3",
|
|
12067
12389
|
shopify_blog: "blog_3_2",
|
|
@@ -12173,35 +12495,35 @@ async function photoAssigner(input) {
|
|
|
12173
12495
|
formato
|
|
12174
12496
|
};
|
|
12175
12497
|
}
|
|
12176
|
-
var
|
|
12177
|
-
tenantId:
|
|
12178
|
-
brandId:
|
|
12179
|
-
contenidoRef:
|
|
12180
|
-
fotoId:
|
|
12181
|
-
calendarioItemRef:
|
|
12498
|
+
var ParamsSchema10 = import_zod40.z.object({
|
|
12499
|
+
tenantId: import_zod40.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
12500
|
+
brandId: import_zod40.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
12501
|
+
contenidoRef: import_zod40.z.string().min(1).describe("Document ID in marketing_contenido (the post receiving the photo)."),
|
|
12502
|
+
fotoId: import_zod40.z.string().min(1).describe("Photo ID in marketing_fotos. Photo must be in state='editada'."),
|
|
12503
|
+
calendarioItemRef: import_zod40.z.string().regex(/^semana:\d+:slot:\d+$/).optional().describe(
|
|
12182
12504
|
'Calendar slot reference in format "semana:N:slot:M". Optional \u2014 if provided, syncs the slot.fotoIdAsignada UI snapshot atomically with the content update. OMIT if there is no calendar slot to sync.'
|
|
12183
12505
|
)
|
|
12184
12506
|
});
|
|
12185
|
-
var
|
|
12186
|
-
|
|
12187
|
-
ok:
|
|
12188
|
-
fotoId:
|
|
12189
|
-
contenidoRef:
|
|
12190
|
-
plataforma:
|
|
12191
|
-
varianteUrl:
|
|
12192
|
-
formato:
|
|
12507
|
+
var OutputSchema10 = import_zod40.z.discriminatedUnion("ok", [
|
|
12508
|
+
import_zod40.z.object({
|
|
12509
|
+
ok: import_zod40.z.literal(true),
|
|
12510
|
+
fotoId: import_zod40.z.string(),
|
|
12511
|
+
contenidoRef: import_zod40.z.string(),
|
|
12512
|
+
plataforma: import_zod40.z.string(),
|
|
12513
|
+
varianteUrl: import_zod40.z.string().nullable(),
|
|
12514
|
+
formato: import_zod40.z.string()
|
|
12193
12515
|
}),
|
|
12194
|
-
|
|
12195
|
-
ok:
|
|
12196
|
-
error:
|
|
12197
|
-
code:
|
|
12516
|
+
import_zod40.z.object({
|
|
12517
|
+
ok: import_zod40.z.literal(false),
|
|
12518
|
+
error: import_zod40.z.string(),
|
|
12519
|
+
code: import_zod40.z.enum(["CONTENT_NOT_FOUND", "CONTENT_TENANT_MISMATCH", "PHOTO_NOT_FOUND", "PHOTO_NOT_READY"]).optional()
|
|
12198
12520
|
})
|
|
12199
12521
|
]);
|
|
12200
|
-
var
|
|
12522
|
+
var rawContract10 = {
|
|
12201
12523
|
name: "assign_photo_to_content",
|
|
12202
12524
|
description: 'Assign an edited photo to an existing content. Writes atomically to BOTH marketing_contenido.fotoId+mediaVariante (canonical for publish) AND marketing_calendario slot.fotoIdAsignada (UI snapshot, only if calendarioItemRef provided). NEVER use update_calendar_slot for this \u2014 that one writes to the agenda, not the post. The photo must be in state="editada".',
|
|
12203
|
-
paramsSchema:
|
|
12204
|
-
outputSchema:
|
|
12525
|
+
paramsSchema: ParamsSchema10,
|
|
12526
|
+
outputSchema: OutputSchema10,
|
|
12205
12527
|
// Cambia la foto vinculada al contenido — reversible (volver a llamar con
|
|
12206
12528
|
// otra fotoId), no publica nada externo. La foto editada queda intacta;
|
|
12207
12529
|
// solo se actualiza la referencia.
|
|
@@ -12243,7 +12565,7 @@ var rawContract8 = {
|
|
|
12243
12565
|
sideEffects: ["writes_firestore", "updates_calendar_slot"]
|
|
12244
12566
|
};
|
|
12245
12567
|
var photoAssignerContract = MartinContractSchema.parse(
|
|
12246
|
-
|
|
12568
|
+
rawContract10
|
|
12247
12569
|
);
|
|
12248
12570
|
function findPageByHeuristic(pages, pattern) {
|
|
12249
12571
|
return pages.find((p) => pattern.test(p.title || "")) || pages.find((p) => pattern.test(p.handle || "")) || null;
|
|
@@ -12432,27 +12754,27 @@ var BRAND_BRIEF_SCHEMA_HINT = {
|
|
|
12432
12754
|
escenas: [{ id: string, nombre: string, promptHint: string }]
|
|
12433
12755
|
}`
|
|
12434
12756
|
};
|
|
12435
|
-
var
|
|
12436
|
-
tenantId:
|
|
12437
|
-
brandId:
|
|
12757
|
+
var ParamsSchema11 = import_zod41.z.object({
|
|
12758
|
+
tenantId: import_zod41.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
12759
|
+
brandId: import_zod41.z.string().min(1).describe("Brand identifier within the tenant.")
|
|
12438
12760
|
});
|
|
12439
|
-
var
|
|
12440
|
-
|
|
12441
|
-
ok:
|
|
12761
|
+
var OutputSchema11 = import_zod41.z.discriminatedUnion("ok", [
|
|
12762
|
+
import_zod41.z.object({
|
|
12763
|
+
ok: import_zod41.z.literal(true),
|
|
12442
12764
|
/** Payload con instrucción + datos del negocio para que Claude genere el brief. */
|
|
12443
|
-
payload:
|
|
12765
|
+
payload: import_zod41.z.record(import_zod41.z.string(), import_zod41.z.unknown())
|
|
12444
12766
|
}),
|
|
12445
|
-
|
|
12446
|
-
ok:
|
|
12447
|
-
error:
|
|
12448
|
-
code:
|
|
12767
|
+
import_zod41.z.object({
|
|
12768
|
+
ok: import_zod41.z.literal(false),
|
|
12769
|
+
error: import_zod41.z.string(),
|
|
12770
|
+
code: import_zod41.z.enum(["BRAND_NOT_FOUND"]).optional()
|
|
12449
12771
|
})
|
|
12450
12772
|
]);
|
|
12451
|
-
var
|
|
12773
|
+
var rawContract11 = {
|
|
12452
12774
|
name: "generate_brand_brief",
|
|
12453
12775
|
description: "Aggregate business data (Shopify, SEO, GBP, scraped site_content, brand config, tenant locations) into a payload to generate a pre-filled Brand Brief. Read-only \u2014 does NOT write the brief; the caller saves it via save_brand_brief.",
|
|
12454
|
-
paramsSchema:
|
|
12455
|
-
outputSchema:
|
|
12776
|
+
paramsSchema: ParamsSchema11,
|
|
12777
|
+
outputSchema: OutputSchema11,
|
|
12456
12778
|
// Lectura pura, sin side effects de escritura ni publicación.
|
|
12457
12779
|
requiresConfirmation: false,
|
|
12458
12780
|
destructive: false,
|
|
@@ -12477,7 +12799,7 @@ var rawContract9 = {
|
|
|
12477
12799
|
sideEffects: ["reads_firestore"]
|
|
12478
12800
|
};
|
|
12479
12801
|
var brandBriefBuilderContract = MartinContractSchema.parse(
|
|
12480
|
-
|
|
12802
|
+
rawContract11
|
|
12481
12803
|
);
|
|
12482
12804
|
async function resolveLastImportId(db, tenantId, brandId) {
|
|
12483
12805
|
const configSnap = await db.collection("tenants").doc(tenantId).collection("marketing_config").doc(brandId).get();
|
|
@@ -12665,28 +12987,28 @@ var BLOG_STRATEGY_REGLAS = [
|
|
|
12665
12987
|
"blogId, handle, title, ultimoPostFecha, ultimoPostKeyword, totalArticulos \u2014 copiar del shopifyBlogs (datos reales del import)",
|
|
12666
12988
|
"defaultBlogId y defaultBlogHandle \u2014 usar el primer blog de la lista"
|
|
12667
12989
|
];
|
|
12668
|
-
var
|
|
12669
|
-
tenantId:
|
|
12670
|
-
brandId:
|
|
12990
|
+
var ParamsSchema12 = import_zod42.z.object({
|
|
12991
|
+
tenantId: import_zod42.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
12992
|
+
brandId: import_zod42.z.string().min(1).describe("Brand identifier within the tenant.")
|
|
12671
12993
|
});
|
|
12672
|
-
var
|
|
12673
|
-
|
|
12674
|
-
ok:
|
|
12675
|
-
payload:
|
|
12994
|
+
var OutputSchema12 = import_zod42.z.discriminatedUnion("ok", [
|
|
12995
|
+
import_zod42.z.object({
|
|
12996
|
+
ok: import_zod42.z.literal(true),
|
|
12997
|
+
payload: import_zod42.z.record(import_zod42.z.string(), import_zod42.z.unknown()).describe(
|
|
12676
12998
|
"Aggregated business data + instructions + schema hints for the LLM to generate a marketing plan. Includes seoSnapshot, productos, historialReciente, shopifyColecciones, shopifyBlogs, brandBrief, planActual, and schema/rules hints for coleccionesPriorizadas and blogStrategy."
|
|
12677
12999
|
)
|
|
12678
13000
|
}),
|
|
12679
|
-
|
|
12680
|
-
ok:
|
|
12681
|
-
error:
|
|
12682
|
-
code:
|
|
13001
|
+
import_zod42.z.object({
|
|
13002
|
+
ok: import_zod42.z.literal(false),
|
|
13003
|
+
error: import_zod42.z.string(),
|
|
13004
|
+
code: import_zod42.z.enum(["BRAND_NOT_FOUND", "SEO_SNAPSHOT_MISSING"]).optional()
|
|
12683
13005
|
})
|
|
12684
13006
|
]);
|
|
12685
|
-
var
|
|
13007
|
+
var rawContract12 = {
|
|
12686
13008
|
name: "generate_marketing_plan",
|
|
12687
13009
|
description: "Aggregate business data (seoSnapshot, productos, brand config, brandBrief, historial, Shopify collections + blogs) into a payload for the LLM to generate a marketing plan. Read-only \u2014 does NOT save the plan. The caller saves via save_marketing_plan or update_marketing_plan_field after generating. Requires an existing seoSnapshot on the brand (returns SEO_SNAPSHOT_MISSING otherwise).",
|
|
12688
|
-
paramsSchema:
|
|
12689
|
-
outputSchema:
|
|
13010
|
+
paramsSchema: ParamsSchema12,
|
|
13011
|
+
outputSchema: OutputSchema12,
|
|
12690
13012
|
// Lectura pura, no muta nada.
|
|
12691
13013
|
requiresConfirmation: false,
|
|
12692
13014
|
destructive: false,
|
|
@@ -12711,7 +13033,7 @@ var rawContract10 = {
|
|
|
12711
13033
|
sideEffects: ["reads_firestore"]
|
|
12712
13034
|
};
|
|
12713
13035
|
var marketingPlanBuilderContract = MartinContractSchema.parse(
|
|
12714
|
-
|
|
13036
|
+
rawContract12
|
|
12715
13037
|
);
|
|
12716
13038
|
function buildGenId() {
|
|
12717
13039
|
return `mkt_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -12952,54 +13274,54 @@ async function contenidoWriter(input) {
|
|
|
12952
13274
|
pipelineLinked
|
|
12953
13275
|
};
|
|
12954
13276
|
}
|
|
12955
|
-
var
|
|
12956
|
-
tenantId:
|
|
12957
|
-
brandId:
|
|
12958
|
-
plataforma:
|
|
12959
|
-
tipo:
|
|
13277
|
+
var ParamsSchema13 = import_zod43.z.object({
|
|
13278
|
+
tenantId: import_zod43.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
13279
|
+
brandId: import_zod43.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
13280
|
+
plataforma: import_zod43.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Target platform for the content."),
|
|
13281
|
+
tipo: import_zod43.z.string().optional().describe(
|
|
12960
13282
|
"Content type ('post', 'blog', 'carousel', 'reel', 'story', 'review_response'). Match to the platform."
|
|
12961
13283
|
),
|
|
12962
|
-
keyword:
|
|
12963
|
-
languageCode:
|
|
13284
|
+
keyword: import_zod43.z.string().optional().describe("Primary keyword for the content. OMIT if not applicable."),
|
|
13285
|
+
languageCode: import_zod43.z.string().optional().describe(
|
|
12964
13286
|
"Content language code (e.g. 'es', 'en'). For shopify_blog auto-injects to datos.languageCode if not present."
|
|
12965
13287
|
),
|
|
12966
|
-
fotoId:
|
|
12967
|
-
datos:
|
|
13288
|
+
fotoId: import_zod43.z.string().optional().describe("Linked photo ID. OMIT if no photo associated yet (use assign_photo_to_content later)."),
|
|
13289
|
+
datos: import_zod43.z.record(import_zod43.z.string(), import_zod43.z.unknown()).describe(
|
|
12968
13290
|
"Platform-specific content payload. Validated against Blog/GBP/IG/Review schemas. Use buildDatosBlog/GBP/IG/Review helpers to construct safely."
|
|
12969
13291
|
),
|
|
12970
|
-
calendarioItemRef:
|
|
13292
|
+
calendarioItemRef: import_zod43.z.string().optional().describe(
|
|
12971
13293
|
'Calendar slot reference (format "semana:N:slot:M"). If provided, links content to that slot AND discards prior non-discarded content of the same slot.'
|
|
12972
13294
|
)
|
|
12973
13295
|
});
|
|
12974
|
-
var PipelineLinkResultSchema =
|
|
12975
|
-
linked:
|
|
12976
|
-
paso:
|
|
12977
|
-
motivo:
|
|
12978
|
-
code:
|
|
12979
|
-
_instrucciones:
|
|
13296
|
+
var PipelineLinkResultSchema = import_zod43.z.object({
|
|
13297
|
+
linked: import_zod43.z.boolean(),
|
|
13298
|
+
paso: import_zod43.z.string().nullable(),
|
|
13299
|
+
motivo: import_zod43.z.string().optional(),
|
|
13300
|
+
code: import_zod43.z.string().optional(),
|
|
13301
|
+
_instrucciones: import_zod43.z.string().optional()
|
|
12980
13302
|
});
|
|
12981
|
-
var
|
|
12982
|
-
|
|
12983
|
-
ok:
|
|
12984
|
-
contenidoId:
|
|
12985
|
-
estado:
|
|
12986
|
-
plataforma:
|
|
12987
|
-
descartados:
|
|
13303
|
+
var OutputSchema13 = import_zod43.z.discriminatedUnion("ok", [
|
|
13304
|
+
import_zod43.z.object({
|
|
13305
|
+
ok: import_zod43.z.literal(true),
|
|
13306
|
+
contenidoId: import_zod43.z.string(),
|
|
13307
|
+
estado: import_zod43.z.string(),
|
|
13308
|
+
plataforma: import_zod43.z.enum(["gbp", "shopify_blog", "instagram", "review"]),
|
|
13309
|
+
descartados: import_zod43.z.number().int().nonnegative(),
|
|
12988
13310
|
pipelineLinked: PipelineLinkResultSchema
|
|
12989
13311
|
}),
|
|
12990
|
-
|
|
12991
|
-
ok:
|
|
12992
|
-
error:
|
|
12993
|
-
code:
|
|
12994
|
-
activosEstaSemana:
|
|
12995
|
-
limite:
|
|
13312
|
+
import_zod43.z.object({
|
|
13313
|
+
ok: import_zod43.z.literal(false),
|
|
13314
|
+
error: import_zod43.z.string(),
|
|
13315
|
+
code: import_zod43.z.enum(["CONTENT_FREQUENCY_LIMIT", "CONTENT_DATA_VALIDATION_FAILED"]).optional(),
|
|
13316
|
+
activosEstaSemana: import_zod43.z.number().int().optional(),
|
|
13317
|
+
limite: import_zod43.z.number().int().optional()
|
|
12996
13318
|
})
|
|
12997
13319
|
]);
|
|
12998
|
-
var
|
|
13320
|
+
var rawContract13 = {
|
|
12999
13321
|
name: "save_generated_content",
|
|
13000
13322
|
description: "Save newly generated marketing content to marketing_contenido. The content is saved as pendiente_aprobacion \u2014 it is NOT published until the tenant approves it. Enforces weekly frequency limits per platform. If calendarioItemRef is provided, links to the slot and discards any prior content of the same slot. Validates datos against the platform schema.",
|
|
13001
|
-
paramsSchema:
|
|
13002
|
-
outputSchema:
|
|
13323
|
+
paramsSchema: ParamsSchema13,
|
|
13324
|
+
outputSchema: OutputSchema13,
|
|
13003
13325
|
// Crea borrador + descarta borrador previo del mismo slot. Reversible
|
|
13004
13326
|
// (volver a llamar con datos corregidos crea un nuevo borrador y descarta
|
|
13005
13327
|
// el actual). NO publica nada externo — la CF de publish se encarga
|
|
@@ -13044,7 +13366,7 @@ var rawContract11 = {
|
|
|
13044
13366
|
sideEffects: ["writes_firestore", "updates_calendar_slot"]
|
|
13045
13367
|
};
|
|
13046
13368
|
var contenidoWriterContract = MartinContractSchema.parse(
|
|
13047
|
-
|
|
13369
|
+
rawContract13
|
|
13048
13370
|
);
|
|
13049
13371
|
function fmtDate(d) {
|
|
13050
13372
|
const y = d.getFullYear();
|
|
@@ -13319,34 +13641,34 @@ OBLIGATORIO: calendarioItemRef con formato EXACTO "semana:N:slot:M" (ej: "semana
|
|
|
13319
13641
|
OBLIGATORIO: fotoId \u2014 SIEMPRE pasa el ID de la foto que elegiste con get_photos_for_slot.
|
|
13320
13642
|
Si el slot tiene notas[], LEERLAS y usarlas como contexto. Si estado es revisar, regenerar adaptando a las notas.
|
|
13321
13643
|
BLOG SLOTS: Usa _blogJIT para el contexto de cada slot shopify_blog \u2014 blogTarget, tono, author, articulosExistentes para interlinking.`;
|
|
13322
|
-
var
|
|
13323
|
-
tenantId:
|
|
13324
|
-
brandId:
|
|
13325
|
-
semana:
|
|
13644
|
+
var ParamsSchema14 = import_zod44.z.object({
|
|
13645
|
+
tenantId: import_zod44.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
13646
|
+
brandId: import_zod44.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
13647
|
+
semana: import_zod44.z.number().int().min(1).max(5).optional().describe(
|
|
13326
13648
|
"Week number within the current month (1-5). OMIT to default to the current week inferred from today."
|
|
13327
13649
|
),
|
|
13328
|
-
modo:
|
|
13650
|
+
modo: import_zod44.z.enum(["planificar", "generar"]).optional().describe(
|
|
13329
13651
|
"Operation mode. 'planificar' (default): propose distribution platform+keyword+tipo per day. 'generar': requires slots in pre_aprobado/revisar \u2014 generate actual content. OMIT to default to 'planificar'."
|
|
13330
13652
|
)
|
|
13331
13653
|
});
|
|
13332
|
-
var
|
|
13333
|
-
|
|
13334
|
-
ok:
|
|
13335
|
-
payload:
|
|
13654
|
+
var OutputSchema14 = import_zod44.z.discriminatedUnion("ok", [
|
|
13655
|
+
import_zod44.z.object({
|
|
13656
|
+
ok: import_zod44.z.literal(true),
|
|
13657
|
+
payload: import_zod44.z.record(import_zod44.z.string(), import_zod44.z.unknown()).describe(
|
|
13336
13658
|
"Aggregated context for the LLM. Shape varies by modo: 'planificar' returns brand skeleton + slotsYaExistentes + historial; 'generar' returns slotsParaGenerar + fotosDisponibles + brand.blogStrategy + blogJIT (if blog slots present)."
|
|
13337
13659
|
)
|
|
13338
13660
|
}),
|
|
13339
|
-
|
|
13340
|
-
ok:
|
|
13341
|
-
error:
|
|
13342
|
-
code:
|
|
13661
|
+
import_zod44.z.object({
|
|
13662
|
+
ok: import_zod44.z.literal(false),
|
|
13663
|
+
error: import_zod44.z.string(),
|
|
13664
|
+
code: import_zod44.z.enum(["BRAND_NOT_FOUND", "WEEK_HAS_NO_SLOTS"]).optional()
|
|
13343
13665
|
})
|
|
13344
13666
|
]);
|
|
13345
|
-
var
|
|
13667
|
+
var rawContract14 = {
|
|
13346
13668
|
name: "generate_weekly_content",
|
|
13347
13669
|
description: "Build the week's content. Two modes: 'planificar' aggregates context for the LLM to propose distribution (LLM then uses add_calendar_slot/update_calendar_slot per day); 'generar' returns pre-approved slots + photos + blog JIT context for the LLM to generate actual content (LLM then calls save_generated_content per slot). AUTO-CREATES the monthly marketing_calendario if it does not exist. NEVER generates content directly \u2014 always returns a payload for the LLM consumer.",
|
|
13348
|
-
paramsSchema:
|
|
13349
|
-
outputSchema:
|
|
13670
|
+
paramsSchema: ParamsSchema14,
|
|
13671
|
+
outputSchema: OutputSchema14,
|
|
13350
13672
|
// Auto-create de calendario es bootstrap reversible (volver a llamar usa el
|
|
13351
13673
|
// calendario existente). NO publica nada externo. modo='generar' tampoco
|
|
13352
13674
|
// publica — solo prepara contexto.
|
|
@@ -13391,7 +13713,7 @@ var rawContract12 = {
|
|
|
13391
13713
|
sideEffects: ["reads_firestore", "writes_firestore"]
|
|
13392
13714
|
};
|
|
13393
13715
|
var weeklyContentBuilderContract = MartinContractSchema.parse(
|
|
13394
|
-
|
|
13716
|
+
rawContract14
|
|
13395
13717
|
);
|
|
13396
13718
|
var DEFAULT_TEXT_THRESHOLD = 0.35;
|
|
13397
13719
|
var DEFAULT_IMAGE_THRESHOLD = 0.08;
|
|
@@ -13580,69 +13902,69 @@ async function contentFinder(input) {
|
|
|
13580
13902
|
}
|
|
13581
13903
|
return result;
|
|
13582
13904
|
}
|
|
13583
|
-
var IncludeSchema =
|
|
13584
|
-
products:
|
|
13585
|
-
collections:
|
|
13586
|
-
articles:
|
|
13587
|
-
pages:
|
|
13905
|
+
var IncludeSchema = import_zod45.z.object({
|
|
13906
|
+
products: import_zod45.z.boolean(),
|
|
13907
|
+
collections: import_zod45.z.boolean(),
|
|
13908
|
+
articles: import_zod45.z.boolean(),
|
|
13909
|
+
pages: import_zod45.z.boolean()
|
|
13588
13910
|
}).describe(
|
|
13589
13911
|
"Toggles for which Shopify content types to search. Default truthy for products/collections/articles, false for pages. Set false to skip a category for performance."
|
|
13590
13912
|
);
|
|
13591
|
-
var LimitSchema =
|
|
13592
|
-
products:
|
|
13593
|
-
collections:
|
|
13594
|
-
articles:
|
|
13595
|
-
pages:
|
|
13913
|
+
var LimitSchema = import_zod45.z.object({
|
|
13914
|
+
products: import_zod45.z.number().int().min(0).max(20),
|
|
13915
|
+
collections: import_zod45.z.number().int().min(0).max(10),
|
|
13916
|
+
articles: import_zod45.z.number().int().min(0).max(20),
|
|
13917
|
+
pages: import_zod45.z.number().int().min(0).max(10)
|
|
13596
13918
|
}).describe(
|
|
13597
13919
|
"Per-category result count caps. Defaults: products 5, collections 3, articles 5, pages 2."
|
|
13598
13920
|
);
|
|
13599
|
-
var
|
|
13600
|
-
tenantId:
|
|
13601
|
-
brandId:
|
|
13602
|
-
contexto:
|
|
13921
|
+
var ParamsSchema15 = import_zod45.z.object({
|
|
13922
|
+
tenantId: import_zod45.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
13923
|
+
brandId: import_zod45.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
13924
|
+
contexto: import_zod45.z.string().min(1).describe(
|
|
13603
13925
|
"Search context: a paragraph, keyword, or intent string. Embedded for vector search."
|
|
13604
13926
|
),
|
|
13605
|
-
fecha:
|
|
13927
|
+
fecha: import_zod45.z.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe(
|
|
13606
13928
|
"Content date in YYYY-MM-DD. Used to detect active season and prioritize matching collections."
|
|
13607
13929
|
),
|
|
13608
13930
|
include: IncludeSchema,
|
|
13609
13931
|
limit: LimitSchema,
|
|
13610
|
-
diversidad:
|
|
13932
|
+
diversidad: import_zod45.z.boolean().describe(
|
|
13611
13933
|
"Whether to apply Jaccard title diversification + handle dedupe to results. Default true."
|
|
13612
13934
|
),
|
|
13613
|
-
mode:
|
|
13935
|
+
mode: import_zod45.z.enum(["text", "hybrid"]).optional().describe(
|
|
13614
13936
|
"Search mode. 'text' (default): single query per collection against embeddingText, fast. 'hybrid': parallel text + image queries with Reciprocal Rank Fusion, 2x queries \u2014 useful when text mode returns few results or to surface visually-similar items with weak SEO."
|
|
13615
13937
|
)
|
|
13616
13938
|
});
|
|
13617
|
-
var VectorResultSchema =
|
|
13618
|
-
id:
|
|
13619
|
-
similarity:
|
|
13939
|
+
var VectorResultSchema = import_zod45.z.object({
|
|
13940
|
+
id: import_zod45.z.string(),
|
|
13941
|
+
similarity: import_zod45.z.number().optional()
|
|
13620
13942
|
}).passthrough();
|
|
13621
|
-
var TemporadaSchema2 =
|
|
13622
|
-
coleccion:
|
|
13623
|
-
titulo:
|
|
13624
|
-
razon:
|
|
13625
|
-
fechaInicio:
|
|
13626
|
-
fechaFin:
|
|
13943
|
+
var TemporadaSchema2 = import_zod45.z.object({
|
|
13944
|
+
coleccion: import_zod45.z.string().nullable(),
|
|
13945
|
+
titulo: import_zod45.z.string().nullable(),
|
|
13946
|
+
razon: import_zod45.z.string().nullable(),
|
|
13947
|
+
fechaInicio: import_zod45.z.string().nullable(),
|
|
13948
|
+
fechaFin: import_zod45.z.string().nullable()
|
|
13627
13949
|
});
|
|
13628
|
-
var SuggestedActionSchema2 =
|
|
13629
|
-
var DetectedConflictSchema2 =
|
|
13630
|
-
var
|
|
13631
|
-
productos:
|
|
13632
|
-
colecciones:
|
|
13633
|
-
articles:
|
|
13634
|
-
pages:
|
|
13635
|
-
_instrucciones:
|
|
13636
|
-
_negativePrompt:
|
|
13950
|
+
var SuggestedActionSchema2 = import_zod45.z.record(import_zod45.z.string(), import_zod45.z.unknown());
|
|
13951
|
+
var DetectedConflictSchema2 = import_zod45.z.record(import_zod45.z.string(), import_zod45.z.unknown());
|
|
13952
|
+
var OutputSchema15 = import_zod45.z.object({
|
|
13953
|
+
productos: import_zod45.z.array(VectorResultSchema),
|
|
13954
|
+
colecciones: import_zod45.z.array(VectorResultSchema),
|
|
13955
|
+
articles: import_zod45.z.array(VectorResultSchema),
|
|
13956
|
+
pages: import_zod45.z.array(VectorResultSchema),
|
|
13957
|
+
_instrucciones: import_zod45.z.string(),
|
|
13958
|
+
_negativePrompt: import_zod45.z.string(),
|
|
13637
13959
|
_temporadaActiva: TemporadaSchema2.nullable(),
|
|
13638
13960
|
_detectedConflict: DetectedConflictSchema2.optional(),
|
|
13639
|
-
_suggestedActions:
|
|
13961
|
+
_suggestedActions: import_zod45.z.array(SuggestedActionSchema2)
|
|
13640
13962
|
});
|
|
13641
|
-
var
|
|
13963
|
+
var rawContract15 = {
|
|
13642
13964
|
name: "find_content_for_topic",
|
|
13643
13965
|
description: "Find Shopify content (products, collections, articles, pages) semantically related to a context/keyword + date. Uses multimodal vector search (1408d Vertex). Mode 'text' (default) queries embeddingText (fast); 'hybrid' merges text + image results via Reciprocal Rank Fusion (rescues items with weak text SEO). Auto-injects JIT context: linking instructions, brand visual negatives, active season collection, and conflict detection (>85% similar article).",
|
|
13644
|
-
paramsSchema:
|
|
13645
|
-
outputSchema:
|
|
13966
|
+
paramsSchema: ParamsSchema15,
|
|
13967
|
+
outputSchema: OutputSchema15,
|
|
13646
13968
|
// Lectura pura (vector search). Failures internas en search degradan a
|
|
13647
13969
|
// arrays vacíos — el helper NO falla.
|
|
13648
13970
|
requiresConfirmation: false,
|
|
@@ -13665,7 +13987,7 @@ var rawContract13 = {
|
|
|
13665
13987
|
sideEffects: ["reads_firestore"]
|
|
13666
13988
|
};
|
|
13667
13989
|
var contentFinderContract = MartinContractSchema.parse(
|
|
13668
|
-
|
|
13990
|
+
rawContract15
|
|
13669
13991
|
);
|
|
13670
13992
|
var DEFAULT_PHOTO_THRESHOLD = 0.7;
|
|
13671
13993
|
var DEFAULT_SHOPIFY_THRESHOLD = 0.65;
|
|
@@ -13812,51 +14134,51 @@ async function slotAssetFinder(input) {
|
|
|
13812
14134
|
_fuente: fuente
|
|
13813
14135
|
};
|
|
13814
14136
|
}
|
|
13815
|
-
var
|
|
13816
|
-
tenantId:
|
|
13817
|
-
brandId:
|
|
13818
|
-
keyword:
|
|
14137
|
+
var ParamsSchema16 = import_zod46.z.object({
|
|
14138
|
+
tenantId: import_zod46.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
14139
|
+
brandId: import_zod46.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
14140
|
+
keyword: import_zod46.z.string().min(1).describe(
|
|
13819
14141
|
"Slot keyword to search photos for. Used as embedding query for multimodal vector search."
|
|
13820
14142
|
),
|
|
13821
|
-
plataforma:
|
|
14143
|
+
plataforma: import_zod46.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe(
|
|
13822
14144
|
"Target platform \u2014 determines the variant format to resolve (gbp_4_3, blog_3_2, ig_4_5, ig_1_1)."
|
|
13823
14145
|
),
|
|
13824
|
-
fecha:
|
|
14146
|
+
fecha: import_zod46.z.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe(
|
|
13825
14147
|
"Slot date in YYYY-MM-DD. Used to detect active season and apply seasonal catalog overrides."
|
|
13826
14148
|
),
|
|
13827
|
-
limit:
|
|
14149
|
+
limit: import_zod46.z.number().int().min(1).max(10).optional().describe("Max number of photos to return. Default 5. Max 10.")
|
|
13828
14150
|
});
|
|
13829
|
-
var TemporadaSchema22 =
|
|
13830
|
-
coleccion:
|
|
13831
|
-
titulo:
|
|
13832
|
-
razon:
|
|
13833
|
-
fechaInicio:
|
|
13834
|
-
fechaFin:
|
|
14151
|
+
var TemporadaSchema22 = import_zod46.z.object({
|
|
14152
|
+
coleccion: import_zod46.z.string().nullable(),
|
|
14153
|
+
titulo: import_zod46.z.string().nullable(),
|
|
14154
|
+
razon: import_zod46.z.string().nullable(),
|
|
14155
|
+
fechaInicio: import_zod46.z.string().nullable(),
|
|
14156
|
+
fechaFin: import_zod46.z.string().nullable()
|
|
13835
14157
|
});
|
|
13836
|
-
var
|
|
14158
|
+
var OutputSchema16 = import_zod46.z.discriminatedUnion("ok", [
|
|
13837
14159
|
// Note: success case does not include `ok: true` literal in helper return —
|
|
13838
14160
|
// helper returns the success shape directly. Adapter to discriminated union
|
|
13839
14161
|
// happens at wrapper level if needed; here we accept both shapes.
|
|
13840
|
-
|
|
13841
|
-
ok:
|
|
13842
|
-
fotos:
|
|
13843
|
-
_instrucciones:
|
|
13844
|
-
_negativePrompt:
|
|
14162
|
+
import_zod46.z.object({
|
|
14163
|
+
ok: import_zod46.z.literal(true),
|
|
14164
|
+
fotos: import_zod46.z.array(import_zod46.z.record(import_zod46.z.string(), import_zod46.z.unknown())),
|
|
14165
|
+
_instrucciones: import_zod46.z.string(),
|
|
14166
|
+
_negativePrompt: import_zod46.z.string(),
|
|
13845
14167
|
_temporadaActiva: TemporadaSchema22.nullable(),
|
|
13846
|
-
_bloqueoProducto:
|
|
13847
|
-
_fuente:
|
|
14168
|
+
_bloqueoProducto: import_zod46.z.boolean(),
|
|
14169
|
+
_fuente: import_zod46.z.enum(["tenant", "shopify_product"])
|
|
13848
14170
|
}),
|
|
13849
|
-
|
|
13850
|
-
ok:
|
|
13851
|
-
error:
|
|
13852
|
-
code:
|
|
14171
|
+
import_zod46.z.object({
|
|
14172
|
+
ok: import_zod46.z.literal(false),
|
|
14173
|
+
error: import_zod46.z.string(),
|
|
14174
|
+
code: import_zod46.z.enum(["BRAND_NOT_FOUND"]).optional()
|
|
13853
14175
|
})
|
|
13854
14176
|
]);
|
|
13855
|
-
var
|
|
14177
|
+
var rawContract16 = {
|
|
13856
14178
|
name: "get_photos_for_slot",
|
|
13857
14179
|
description: "Find tenant photos for a calendar slot (keyword + platform + date) via multimodal vector search (1408d Vertex). Resolves the platform-specific variant (gbp_4_3/blog_3_2/ig_4_5/ig_1_1). Auto-injects JIT context: how to choose, brand visual negatives, active season overrides, and source layer (tenant photos vs Shopify product fallback). If <3 photos returned, consider calling request_photo_shoot to alert the tenant.",
|
|
13858
|
-
paramsSchema:
|
|
13859
|
-
outputSchema:
|
|
14180
|
+
paramsSchema: ParamsSchema16,
|
|
14181
|
+
outputSchema: OutputSchema16,
|
|
13860
14182
|
requiresConfirmation: false,
|
|
13861
14183
|
destructive: false,
|
|
13862
14184
|
affectsPublication: false,
|
|
@@ -13883,7 +14205,7 @@ var rawContract14 = {
|
|
|
13883
14205
|
sideEffects: ["reads_firestore"]
|
|
13884
14206
|
};
|
|
13885
14207
|
var slotAssetFinderContract = MartinContractSchema.parse(
|
|
13886
|
-
|
|
14208
|
+
rawContract16
|
|
13887
14209
|
);
|
|
13888
14210
|
var DEFAULT_SIMILARITY_THRESHOLD = 0.6;
|
|
13889
14211
|
function cosineSimilarity(a, b) {
|
|
@@ -13960,44 +14282,44 @@ async function canvaTemplateSelector(input) {
|
|
|
13960
14282
|
_instrucciones: "Plantilla Canva seleccionada. Llama a marketingDesignWithCanva({contenidoId, plantillaId, fotoVariantePath, textos}) para renderizar la pieza final."
|
|
13961
14283
|
};
|
|
13962
14284
|
}
|
|
13963
|
-
var
|
|
13964
|
-
tenantId:
|
|
13965
|
-
brandId:
|
|
13966
|
-
plataforma:
|
|
14285
|
+
var ParamsSchema17 = import_zod47.z.object({
|
|
14286
|
+
tenantId: import_zod47.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
14287
|
+
brandId: import_zod47.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
14288
|
+
plataforma: import_zod47.z.string().min(1).describe(
|
|
13967
14289
|
"Target platform (e.g. 'gbp', 'shopify_blog', 'instagram'). Filters templates that declare this plataforma."
|
|
13968
14290
|
),
|
|
13969
|
-
tipoContenido:
|
|
14291
|
+
tipoContenido: import_zod47.z.string().min(1).describe(
|
|
13970
14292
|
"Content type (e.g. 'post', 'carousel', 'story', 'blog'). Filters templates that declare this tipoContenido."
|
|
13971
14293
|
),
|
|
13972
|
-
keyword:
|
|
14294
|
+
keyword: import_zod47.z.string().min(1).describe("Slot keyword. Used as embedding query for cosine similarity match.")
|
|
13973
14295
|
});
|
|
13974
|
-
var
|
|
13975
|
-
|
|
13976
|
-
plantillaId:
|
|
13977
|
-
titulo:
|
|
13978
|
-
thumbnailUrl:
|
|
13979
|
-
similarity:
|
|
13980
|
-
_instrucciones:
|
|
14296
|
+
var OutputSchema17 = import_zod47.z.union([
|
|
14297
|
+
import_zod47.z.object({
|
|
14298
|
+
plantillaId: import_zod47.z.string(),
|
|
14299
|
+
titulo: import_zod47.z.string().nullable(),
|
|
14300
|
+
thumbnailUrl: import_zod47.z.string().nullable(),
|
|
14301
|
+
similarity: import_zod47.z.number(),
|
|
14302
|
+
_instrucciones: import_zod47.z.string()
|
|
13981
14303
|
}),
|
|
13982
|
-
|
|
13983
|
-
plantillaId:
|
|
13984
|
-
motivo:
|
|
14304
|
+
import_zod47.z.object({
|
|
14305
|
+
plantillaId: import_zod47.z.null(),
|
|
14306
|
+
motivo: import_zod47.z.enum([
|
|
13985
14307
|
"brand_no_encontrada",
|
|
13986
14308
|
"no_canva",
|
|
13987
14309
|
"no_match_plataforma",
|
|
13988
14310
|
"modo_cliente_no_soportado",
|
|
13989
14311
|
"similarity_baja"
|
|
13990
14312
|
]),
|
|
13991
|
-
similarity:
|
|
13992
|
-
_instrucciones:
|
|
13993
|
-
_todo:
|
|
14313
|
+
similarity: import_zod47.z.number().optional(),
|
|
14314
|
+
_instrucciones: import_zod47.z.string().optional(),
|
|
14315
|
+
_todo: import_zod47.z.string().optional()
|
|
13994
14316
|
})
|
|
13995
14317
|
]);
|
|
13996
|
-
var
|
|
14318
|
+
var rawContract17 = {
|
|
13997
14319
|
name: "select_canva_template",
|
|
13998
14320
|
description: "Select the best Canva template from the tenant for a slot. Filters templates by plataforma+tipoContenido, then cosine-similarity matches embeddings vs the keyword query. Returns plantillaId on match (similarity >= 0.60). On miss returns plantillaId=null with motivo enum (no_canva, no_match_plataforma, modo_cliente_no_soportado, similarity_baja, brand_no_encontrada). The LLM should degrade gracefully (generate without Canva template) on any miss.",
|
|
13999
|
-
paramsSchema:
|
|
14000
|
-
outputSchema:
|
|
14321
|
+
paramsSchema: ParamsSchema17,
|
|
14322
|
+
outputSchema: OutputSchema17,
|
|
14001
14323
|
// Lectura pura. NO falla — todo "no encontré" es resultado válido del helper.
|
|
14002
14324
|
requiresConfirmation: false,
|
|
14003
14325
|
destructive: false,
|
|
@@ -14024,7 +14346,7 @@ var rawContract15 = {
|
|
|
14024
14346
|
sideEffects: ["reads_firestore"]
|
|
14025
14347
|
};
|
|
14026
14348
|
var canvaTemplateSelectorContract = MartinContractSchema.parse(
|
|
14027
|
-
|
|
14349
|
+
rawContract17
|
|
14028
14350
|
);
|
|
14029
14351
|
function buildDirectorPlanInstrucciones(catalogoVisual) {
|
|
14030
14352
|
const etiquetas = catalogoVisual.etiquetas || {};
|
|
@@ -14248,38 +14570,38 @@ async function photoDirectorExecute(input) {
|
|
|
14248
14570
|
_instrucciones: buildDirectorExecuteSuccessInstrucciones(result.balanceAfter)
|
|
14249
14571
|
};
|
|
14250
14572
|
}
|
|
14251
|
-
var PlanParamsSchema =
|
|
14252
|
-
tenantId:
|
|
14253
|
-
fotoId:
|
|
14573
|
+
var PlanParamsSchema = import_zod48.z.object({
|
|
14574
|
+
tenantId: import_zod48.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
14575
|
+
fotoId: import_zod48.z.string().min(1).describe("Photo ID in marketing_fotos. The photo must have an archivoOriginal uploaded.")
|
|
14254
14576
|
});
|
|
14255
|
-
var PlanContextSchema =
|
|
14256
|
-
fotoId:
|
|
14257
|
-
archivoOriginal:
|
|
14258
|
-
estrategia:
|
|
14259
|
-
brandBrief:
|
|
14260
|
-
segmento:
|
|
14261
|
-
personalidad:
|
|
14577
|
+
var PlanContextSchema = import_zod48.z.object({
|
|
14578
|
+
fotoId: import_zod48.z.string(),
|
|
14579
|
+
archivoOriginal: import_zod48.z.string(),
|
|
14580
|
+
estrategia: import_zod48.z.string(),
|
|
14581
|
+
brandBrief: import_zod48.z.object({
|
|
14582
|
+
segmento: import_zod48.z.string().nullable(),
|
|
14583
|
+
personalidad: import_zod48.z.unknown().nullable()
|
|
14262
14584
|
}),
|
|
14263
|
-
visualRules:
|
|
14264
|
-
catalogoVisual:
|
|
14265
|
-
estiloVisual:
|
|
14266
|
-
notaTenant:
|
|
14267
|
-
productoLinkeadoManual:
|
|
14268
|
-
_instrucciones:
|
|
14585
|
+
visualRules: import_zod48.z.record(import_zod48.z.string(), import_zod48.z.unknown()),
|
|
14586
|
+
catalogoVisual: import_zod48.z.record(import_zod48.z.string(), import_zod48.z.unknown()),
|
|
14587
|
+
estiloVisual: import_zod48.z.unknown().nullable(),
|
|
14588
|
+
notaTenant: import_zod48.z.unknown().nullable(),
|
|
14589
|
+
productoLinkeadoManual: import_zod48.z.unknown().nullable(),
|
|
14590
|
+
_instrucciones: import_zod48.z.string()
|
|
14269
14591
|
});
|
|
14270
|
-
var PlanOutputSchema =
|
|
14271
|
-
|
|
14272
|
-
ok:
|
|
14273
|
-
imageBase64:
|
|
14592
|
+
var PlanOutputSchema = import_zod48.z.discriminatedUnion("ok", [
|
|
14593
|
+
import_zod48.z.object({
|
|
14594
|
+
ok: import_zod48.z.literal(true),
|
|
14595
|
+
imageBase64: import_zod48.z.string().describe("Compressed photo as base64 for transport to the LLM."),
|
|
14274
14596
|
context: PlanContextSchema
|
|
14275
14597
|
}),
|
|
14276
|
-
|
|
14277
|
-
ok:
|
|
14278
|
-
code:
|
|
14279
|
-
error:
|
|
14280
|
-
_instrucciones:
|
|
14281
|
-
fix:
|
|
14282
|
-
fotoId:
|
|
14598
|
+
import_zod48.z.object({
|
|
14599
|
+
ok: import_zod48.z.literal(false),
|
|
14600
|
+
code: import_zod48.z.string(),
|
|
14601
|
+
error: import_zod48.z.string(),
|
|
14602
|
+
_instrucciones: import_zod48.z.string().optional(),
|
|
14603
|
+
fix: import_zod48.z.string().optional(),
|
|
14604
|
+
fotoId: import_zod48.z.string().optional()
|
|
14283
14605
|
})
|
|
14284
14606
|
]);
|
|
14285
14607
|
var planRawContract = {
|
|
@@ -14309,43 +14631,43 @@ var planRawContract = {
|
|
|
14309
14631
|
var photoDirectorPlanContract = MartinContractSchema.parse(
|
|
14310
14632
|
planRawContract
|
|
14311
14633
|
);
|
|
14312
|
-
var ExecuteParamsSchema =
|
|
14634
|
+
var ExecuteParamsSchema = import_zod48.z.object({
|
|
14313
14635
|
// tenantId is injected by the wrapper from ctx (A7 module-scope pattern,
|
|
14314
14636
|
// even though the helper function itself derives tenantId via the foto's
|
|
14315
14637
|
// brandId). Needed here for extractTargetPath canonical path.
|
|
14316
|
-
tenantId:
|
|
14317
|
-
fotoId:
|
|
14318
|
-
prompt:
|
|
14638
|
+
tenantId: import_zod48.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
14639
|
+
fotoId: import_zod48.z.string().min(1).describe("Photo ID to edit (must have been planned via plan_photo_edit)."),
|
|
14640
|
+
prompt: import_zod48.z.string().nullable().describe(
|
|
14319
14641
|
"English prompt for Gemini Image Edit when acciones includes edit_background. Pass null when strategy is 'tal_cual' (no editing \u2014 just regenerate thumbnail + embedding with new tags)."
|
|
14320
14642
|
),
|
|
14321
|
-
acciones:
|
|
14643
|
+
acciones: import_zod48.z.array(import_zod48.z.enum(["edit_background", "none"])).describe(
|
|
14322
14644
|
"Edit operations to perform. Use ['edit_background'] for AI background edit, ['none'] when strategy is 'tal_cual'."
|
|
14323
14645
|
),
|
|
14324
|
-
descripcion:
|
|
14325
|
-
tipo:
|
|
14326
|
-
tagsPrimarios:
|
|
14327
|
-
tagsSecundarios:
|
|
14328
|
-
tagsContexto:
|
|
14646
|
+
descripcion: import_zod48.z.string().describe("Semantic description in Spanish (saved as descripcionVision on the photo)."),
|
|
14647
|
+
tipo: import_zod48.z.string().nullable().describe("Photo type from the brand catalogoVisual. null only if catalog has no matching type."),
|
|
14648
|
+
tagsPrimarios: import_zod48.z.array(import_zod48.z.string()).describe("Primary tags from the catalogoVisual."),
|
|
14649
|
+
tagsSecundarios: import_zod48.z.array(import_zod48.z.string()).describe("Secondary tags from the catalogoVisual."),
|
|
14650
|
+
tagsContexto: import_zod48.z.array(import_zod48.z.string()).describe("Contextual tags (occasion, mood, style).")
|
|
14329
14651
|
});
|
|
14330
|
-
var ExecuteOutputSchema =
|
|
14331
|
-
|
|
14332
|
-
ok:
|
|
14333
|
-
fotoId:
|
|
14334
|
-
archivoEditado:
|
|
14335
|
-
thumbnailUrl:
|
|
14336
|
-
iteracion:
|
|
14337
|
-
editCosto:
|
|
14338
|
-
creditsConsumed:
|
|
14339
|
-
balanceAfter:
|
|
14340
|
-
imageBase64:
|
|
14341
|
-
_instrucciones:
|
|
14652
|
+
var ExecuteOutputSchema = import_zod48.z.discriminatedUnion("ok", [
|
|
14653
|
+
import_zod48.z.object({
|
|
14654
|
+
ok: import_zod48.z.literal(true),
|
|
14655
|
+
fotoId: import_zod48.z.string(),
|
|
14656
|
+
archivoEditado: import_zod48.z.string().optional(),
|
|
14657
|
+
thumbnailUrl: import_zod48.z.string().optional(),
|
|
14658
|
+
iteracion: import_zod48.z.number().optional(),
|
|
14659
|
+
editCosto: import_zod48.z.number().optional(),
|
|
14660
|
+
creditsConsumed: import_zod48.z.number().optional(),
|
|
14661
|
+
balanceAfter: import_zod48.z.number().optional(),
|
|
14662
|
+
imageBase64: import_zod48.z.string().nullable(),
|
|
14663
|
+
_instrucciones: import_zod48.z.string()
|
|
14342
14664
|
}),
|
|
14343
|
-
|
|
14344
|
-
ok:
|
|
14345
|
-
error:
|
|
14346
|
-
code:
|
|
14347
|
-
details:
|
|
14348
|
-
_instrucciones:
|
|
14665
|
+
import_zod48.z.object({
|
|
14666
|
+
ok: import_zod48.z.literal(false),
|
|
14667
|
+
error: import_zod48.z.string(),
|
|
14668
|
+
code: import_zod48.z.string(),
|
|
14669
|
+
details: import_zod48.z.record(import_zod48.z.string(), import_zod48.z.unknown()).optional(),
|
|
14670
|
+
_instrucciones: import_zod48.z.string()
|
|
14349
14671
|
})
|
|
14350
14672
|
]);
|
|
14351
14673
|
var executeRawContract = {
|
|
@@ -15053,6 +15375,12 @@ function callGetCalendar(input) {
|
|
|
15053
15375
|
function callAddCalendarSlot(input) {
|
|
15054
15376
|
return callCF("marketingAddCalendarSlotCallable", input);
|
|
15055
15377
|
}
|
|
15378
|
+
function callSeoSnapshotReader(input) {
|
|
15379
|
+
return callCF("marketingSeoSnapshotReaderCallable", input);
|
|
15380
|
+
}
|
|
15381
|
+
function callCollectionsReader(input) {
|
|
15382
|
+
return callCF("marketingCollectionsReaderCallable", input);
|
|
15383
|
+
}
|
|
15056
15384
|
function callBrandBriefWriter(input) {
|
|
15057
15385
|
return callCF("marketingBrandBriefWriterCallable", input);
|
|
15058
15386
|
}
|
|
@@ -15121,7 +15449,7 @@ function callListarRutinas(input) {
|
|
|
15121
15449
|
}
|
|
15122
15450
|
|
|
15123
15451
|
// src/tools/marketing/photos.ts
|
|
15124
|
-
var
|
|
15452
|
+
var import_zod49 = require("zod");
|
|
15125
15453
|
|
|
15126
15454
|
// src/services/marketingEmbeddings.ts
|
|
15127
15455
|
var import_google_auth_library = require("google-auth-library");
|
|
@@ -15417,11 +15745,11 @@ REGLAS:
|
|
|
15417
15745
|
|
|
15418
15746
|
USAR: antes de generar contenido de cualquier slot del calendario.`,
|
|
15419
15747
|
{
|
|
15420
|
-
brandId:
|
|
15421
|
-
keyword:
|
|
15422
|
-
plataforma:
|
|
15423
|
-
fecha:
|
|
15424
|
-
limit:
|
|
15748
|
+
brandId: import_zod49.z.string().optional().describe("ID de la brand"),
|
|
15749
|
+
keyword: import_zod49.z.string().describe("Keyword del slot"),
|
|
15750
|
+
plataforma: import_zod49.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Plataforma destino del slot"),
|
|
15751
|
+
fecha: import_zod49.z.string().describe("Fecha del slot en ISO YYYY-MM-DD"),
|
|
15752
|
+
limit: import_zod49.z.number().int().min(1).max(10).default(5)
|
|
15425
15753
|
},
|
|
15426
15754
|
async ({ brandId: inputBrandId, keyword, plataforma, fecha, limit }) => {
|
|
15427
15755
|
const tenantId = session.requireTenant();
|
|
@@ -15469,7 +15797,7 @@ DESPUES de ver la foto, decide:
|
|
|
15469
15797
|
|
|
15470
15798
|
Luego llama execute_photo_edit con tu analisis y prompt.`,
|
|
15471
15799
|
{
|
|
15472
|
-
fotoId:
|
|
15800
|
+
fotoId: import_zod49.z.string().describe("ID de la foto")
|
|
15473
15801
|
},
|
|
15474
15802
|
async ({ fotoId }) => {
|
|
15475
15803
|
const tenantId = session.requireTenant();
|
|
@@ -15517,14 +15845,14 @@ Retorna la foto editada para que la revises. Si no te gusta:
|
|
|
15517
15845
|
|
|
15518
15846
|
Si estrategia era 'tal_cual', pasa acciones=['none'] \u2014 no se edita pero si se genera thumbnail y embedding con tus tags.`,
|
|
15519
15847
|
{
|
|
15520
|
-
fotoId:
|
|
15521
|
-
prompt:
|
|
15522
|
-
acciones:
|
|
15523
|
-
descripcion:
|
|
15524
|
-
tipo:
|
|
15525
|
-
tagsPrimarios:
|
|
15526
|
-
tagsSecundarios:
|
|
15527
|
-
tagsContexto:
|
|
15848
|
+
fotoId: import_zod49.z.string(),
|
|
15849
|
+
prompt: import_zod49.z.string().nullable().describe("Prompt en ingles para Gemini Image Edit. null si tal_cual"),
|
|
15850
|
+
acciones: import_zod49.z.array(import_zod49.z.enum(["edit_background", "none"])),
|
|
15851
|
+
descripcion: import_zod49.z.string().describe("Descripcion semantica en espanol"),
|
|
15852
|
+
tipo: import_zod49.z.string().nullable().describe("Tipo del catalogoVisual del tenant"),
|
|
15853
|
+
tagsPrimarios: import_zod49.z.array(import_zod49.z.string()),
|
|
15854
|
+
tagsSecundarios: import_zod49.z.array(import_zod49.z.string()),
|
|
15855
|
+
tagsContexto: import_zod49.z.array(import_zod49.z.string())
|
|
15528
15856
|
},
|
|
15529
15857
|
async ({ fotoId, prompt, acciones, descripcion, tipo, tagsPrimarios, tagsSecundarios, tagsContexto }) => {
|
|
15530
15858
|
const tenantId = session.requireTenant();
|
|
@@ -15600,7 +15928,7 @@ Retorna:
|
|
|
15600
15928
|
- creditos: { balance, planId, periodEnd, costoPhotoEdit }
|
|
15601
15929
|
- _instrucciones: que hacer segun el estado`,
|
|
15602
15930
|
{
|
|
15603
|
-
fotoId:
|
|
15931
|
+
fotoId: import_zod49.z.string().describe("ID de la foto")
|
|
15604
15932
|
},
|
|
15605
15933
|
async ({ fotoId }) => {
|
|
15606
15934
|
const tenantId = session.requireTenant();
|
|
@@ -15700,11 +16028,11 @@ Retorna:
|
|
|
15700
16028
|
"find_products_for_content",
|
|
15701
16029
|
`[DEPRECATED 179.5] Wrapper interno de find_content_for_topic. Para nuevas integraciones, usa find_content_for_topic con include.products=true directamente. Este wrapper existe solo por compat hacia atras.`,
|
|
15702
16030
|
{
|
|
15703
|
-
brandId:
|
|
15704
|
-
contexto:
|
|
15705
|
-
fecha:
|
|
15706
|
-
limit:
|
|
15707
|
-
diversidad:
|
|
16031
|
+
brandId: import_zod49.z.string().optional().describe("ID de la brand"),
|
|
16032
|
+
contexto: import_zod49.z.string().describe("Parrafo, keyword o intencion del contenido"),
|
|
16033
|
+
fecha: import_zod49.z.string().describe("Fecha del contenido en ISO YYYY-MM-DD"),
|
|
16034
|
+
limit: import_zod49.z.number().int().min(1).max(10).default(5),
|
|
16035
|
+
diversidad: import_zod49.z.boolean().default(true)
|
|
15708
16036
|
},
|
|
15709
16037
|
async ({ brandId: inputBrandId, contexto, fecha, limit, diversidad }) => {
|
|
15710
16038
|
console.warn(
|
|
@@ -15781,10 +16109,10 @@ RETORNA { plantillaId, titulo, thumbnailUrl } o { plantillaId: null, motivo: 'no
|
|
|
15781
16109
|
|
|
15782
16110
|
USAR: solo si tenant tiene Canva conectado (tenants/{tenantId}/marketing_config/{brandId}.canva.connected=true).`,
|
|
15783
16111
|
{
|
|
15784
|
-
brandId:
|
|
15785
|
-
plataforma:
|
|
15786
|
-
tipoContenido:
|
|
15787
|
-
keyword:
|
|
16112
|
+
brandId: import_zod49.z.string().optional().describe("ID de la brand"),
|
|
16113
|
+
plataforma: import_zod49.z.string().describe("gbp | instagram | shopify_blog"),
|
|
16114
|
+
tipoContenido: import_zod49.z.string().describe("post | carousel | story | blog"),
|
|
16115
|
+
keyword: import_zod49.z.string().describe("Keyword del slot")
|
|
15788
16116
|
},
|
|
15789
16117
|
async ({ brandId: inputBrandId, plataforma, tipoContenido, keyword }) => {
|
|
15790
16118
|
const tenantId = session.requireTenant();
|
|
@@ -15829,15 +16157,15 @@ USAR: cuando get_photos_for_slot retorna pocas fotos y el slot aun no esta cubie
|
|
|
15829
16157
|
|
|
15830
16158
|
IMPORTANTE \u2014 campo 'razon': Este texto lo ve el tenant en la app. Escribe en lenguaje amigable y claro, NO tecnico. Ejemplo MALO: "get_photos_for_slot retorno 0 fotos para shopify_blog". Ejemplo BUENO: "No hay fotos de alcatraz para el blog del 8 de abril". Siempre en espanol si el tenant habla espanol.`,
|
|
15831
16159
|
{
|
|
15832
|
-
brandId:
|
|
15833
|
-
semana:
|
|
15834
|
-
necesidades:
|
|
15835
|
-
|
|
15836
|
-
tema:
|
|
15837
|
-
keyword:
|
|
15838
|
-
cantidadSugerida:
|
|
15839
|
-
razon:
|
|
15840
|
-
slotsAfectados:
|
|
16160
|
+
brandId: import_zod49.z.string().optional().describe("ID de la brand"),
|
|
16161
|
+
semana: import_zod49.z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "semana debe ser ISO YYYY-MM-DD").describe("Semana en ISO del lunes"),
|
|
16162
|
+
necesidades: import_zod49.z.array(
|
|
16163
|
+
import_zod49.z.object({
|
|
16164
|
+
tema: import_zod49.z.string(),
|
|
16165
|
+
keyword: import_zod49.z.string(),
|
|
16166
|
+
cantidadSugerida: import_zod49.z.number().int().positive(),
|
|
16167
|
+
razon: import_zod49.z.string(),
|
|
16168
|
+
slotsAfectados: import_zod49.z.array(import_zod49.z.string()).optional().describe('Refs de slots afectados (formato "semana:N:slot:M")')
|
|
15841
16169
|
})
|
|
15842
16170
|
).min(1)
|
|
15843
16171
|
},
|
|
@@ -15900,7 +16228,7 @@ IMPORTANTE \u2014 campo 'razon': Este texto lo ve el tenant en la app. Escribe e
|
|
|
15900
16228
|
}
|
|
15901
16229
|
|
|
15902
16230
|
// src/tools/marketing/content.ts
|
|
15903
|
-
var
|
|
16231
|
+
var import_zod50 = require("zod");
|
|
15904
16232
|
var _logOverride = null;
|
|
15905
16233
|
async function logToMcpLogs(entry) {
|
|
15906
16234
|
if (_logOverride) return _logOverride(entry);
|
|
@@ -15913,22 +16241,22 @@ async function logToMcpLogs(entry) {
|
|
|
15913
16241
|
} catch {
|
|
15914
16242
|
}
|
|
15915
16243
|
}
|
|
15916
|
-
var IncludeSchema2 =
|
|
15917
|
-
products:
|
|
15918
|
-
collections:
|
|
15919
|
-
articles:
|
|
15920
|
-
pages:
|
|
16244
|
+
var IncludeSchema2 = import_zod50.z.object({
|
|
16245
|
+
products: import_zod50.z.boolean().default(true),
|
|
16246
|
+
collections: import_zod50.z.boolean().default(true),
|
|
16247
|
+
articles: import_zod50.z.boolean().default(true),
|
|
16248
|
+
pages: import_zod50.z.boolean().default(false)
|
|
15921
16249
|
}).default({
|
|
15922
16250
|
products: true,
|
|
15923
16251
|
collections: true,
|
|
15924
16252
|
articles: true,
|
|
15925
16253
|
pages: false
|
|
15926
16254
|
});
|
|
15927
|
-
var LimitSchema2 =
|
|
15928
|
-
products:
|
|
15929
|
-
collections:
|
|
15930
|
-
articles:
|
|
15931
|
-
pages:
|
|
16255
|
+
var LimitSchema2 = import_zod50.z.object({
|
|
16256
|
+
products: import_zod50.z.number().int().min(0).max(20).default(5),
|
|
16257
|
+
collections: import_zod50.z.number().int().min(0).max(10).default(3),
|
|
16258
|
+
articles: import_zod50.z.number().int().min(0).max(20).default(5),
|
|
16259
|
+
pages: import_zod50.z.number().int().min(0).max(10).default(2)
|
|
15932
16260
|
}).default({ products: 5, collections: 3, articles: 5, pages: 2 });
|
|
15933
16261
|
function registerContentTools(server, session) {
|
|
15934
16262
|
server.tool(
|
|
@@ -15956,13 +16284,13 @@ MODOS (parametro mode):
|
|
|
15956
16284
|
|
|
15957
16285
|
Cuando uses hybrid: inspecciona el campo \`modesFound\` en los resultados. 2 = el doc aparecio en ambos modos (alta confianza). 1 = aparecio solo en text (problema visual) o solo en image (problema SEO). Esos "solo image" son candidatos perfectos para recomendar optimizacion al tenant.`,
|
|
15958
16286
|
{
|
|
15959
|
-
brandId:
|
|
15960
|
-
contexto:
|
|
15961
|
-
fecha:
|
|
16287
|
+
brandId: import_zod50.z.string().optional().describe("ID de la brand"),
|
|
16288
|
+
contexto: import_zod50.z.string().min(1).describe("Parrafo, keyword o intencion"),
|
|
16289
|
+
fecha: import_zod50.z.string().describe("Fecha del contenido en ISO YYYY-MM-DD"),
|
|
15962
16290
|
include: IncludeSchema2.optional(),
|
|
15963
16291
|
limit: LimitSchema2.optional(),
|
|
15964
|
-
diversidad:
|
|
15965
|
-
mode:
|
|
16292
|
+
diversidad: import_zod50.z.boolean().default(true),
|
|
16293
|
+
mode: import_zod50.z.enum(["text", "hybrid"]).default("text").describe("Modo de busqueda: 'text' (rapido, 1 query) o 'hybrid' (text+image via RRF, 2x queries, rescata SEO debil)")
|
|
15966
16294
|
},
|
|
15967
16295
|
async ({ brandId: inputBrandId, contexto, fecha, include, limit, diversidad, mode }) => {
|
|
15968
16296
|
const tenantId = session.requireTenant();
|
|
@@ -16173,8 +16501,8 @@ function registerMarketingTools(server, session) {
|
|
|
16173
16501
|
"get_calendar",
|
|
16174
16502
|
"Lee el calendario editorial del mes. Muestra semanas con items planificados por plataforma.",
|
|
16175
16503
|
{
|
|
16176
|
-
brandId:
|
|
16177
|
-
mes:
|
|
16504
|
+
brandId: import_zod51.z.string().optional().describe("ID de la brand"),
|
|
16505
|
+
mes: import_zod51.z.string().optional().describe("Mes en formato YYYY-MM (default: mes actual)")
|
|
16178
16506
|
},
|
|
16179
16507
|
async ({ brandId: inputBrandId, mes }) => {
|
|
16180
16508
|
const tenantId = session.requireTenant();
|
|
@@ -16210,29 +16538,46 @@ function registerMarketingTools(server, session) {
|
|
|
16210
16538
|
);
|
|
16211
16539
|
server.tool(
|
|
16212
16540
|
"get_seo_snapshot",
|
|
16213
|
-
"
|
|
16541
|
+
"Read the latest Semrush SEO snapshot for a brand: rank, top keywords, opportunities, competitors.",
|
|
16214
16542
|
{
|
|
16215
|
-
brandId:
|
|
16543
|
+
brandId: import_zod51.z.string().optional().describe("Brand identifier within the tenant")
|
|
16216
16544
|
},
|
|
16217
16545
|
async ({ brandId: inputBrandId }) => {
|
|
16218
16546
|
const tenantId = session.requireTenant();
|
|
16219
16547
|
const brandId = inputBrandId ?? session.requireBrand();
|
|
16220
|
-
const
|
|
16221
|
-
|
|
16222
|
-
|
|
16223
|
-
|
|
16224
|
-
|
|
16225
|
-
|
|
16548
|
+
const ctx = await buildContext(session, brandId);
|
|
16549
|
+
const result = await dispatchWithContract({
|
|
16550
|
+
contract: seoSnapshotReaderContract,
|
|
16551
|
+
helper: seoSnapshotReader,
|
|
16552
|
+
callable: callSeoSnapshotReader,
|
|
16553
|
+
input: { tenantId, brandId },
|
|
16554
|
+
ctx
|
|
16555
|
+
});
|
|
16556
|
+
let payload;
|
|
16557
|
+
if (result.state === "success" && result.structuredOutput) {
|
|
16558
|
+
const out = result.structuredOutput;
|
|
16559
|
+
if (out.ok) {
|
|
16560
|
+
payload = out.snapshot;
|
|
16561
|
+
} else {
|
|
16562
|
+
payload = { ok: false, code: out.code, mensaje: result.text };
|
|
16563
|
+
}
|
|
16564
|
+
} else {
|
|
16565
|
+
payload = {
|
|
16566
|
+
ok: false,
|
|
16567
|
+
state: result.state,
|
|
16568
|
+
mensaje: result.text,
|
|
16569
|
+
...result.validationErrors && result.validationErrors.length > 0 ? { validationErrors: result.validationErrors } : {}
|
|
16570
|
+
};
|
|
16226
16571
|
}
|
|
16227
|
-
return { content: [{ type: "text", text: JSON.stringify(
|
|
16572
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
16228
16573
|
}
|
|
16229
16574
|
);
|
|
16230
16575
|
server.tool(
|
|
16231
16576
|
"get_photo_gallery",
|
|
16232
16577
|
"Fotos disponibles filtradas por estado. Muestra fotos con metadata y conteo.",
|
|
16233
16578
|
{
|
|
16234
|
-
brandId:
|
|
16235
|
-
estado:
|
|
16579
|
+
brandId: import_zod51.z.string().optional().describe("ID de la brand"),
|
|
16580
|
+
estado: import_zod51.z.string().optional().describe("Filtrar por estado (nueva, procesando, editada, usada, descartada, error)")
|
|
16236
16581
|
},
|
|
16237
16582
|
async ({ brandId: inputBrandId, estado }) => {
|
|
16238
16583
|
session.requireTenant();
|
|
@@ -16272,7 +16617,7 @@ function registerMarketingTools(server, session) {
|
|
|
16272
16617
|
"generate_marketing_plan",
|
|
16273
16618
|
"Aggregate the data needed to generate a strategic marketing plan: SEO snapshot + products. The LLM uses the system prompt to generate the plan from this payload.",
|
|
16274
16619
|
{
|
|
16275
|
-
brandId:
|
|
16620
|
+
brandId: import_zod51.z.string().optional().describe("ID de la brand")
|
|
16276
16621
|
},
|
|
16277
16622
|
async ({ brandId: inputBrandId }) => {
|
|
16278
16623
|
const tenantId = session.requireTenant();
|
|
@@ -16286,8 +16631,8 @@ function registerMarketingTools(server, session) {
|
|
|
16286
16631
|
"save_marketing_plan",
|
|
16287
16632
|
"Save a marketing plan to the brand configuration. Writes to tenants/{tenantId}/marketing_config/{brandId}.plan. If the plan object includes a blogStrategy field, it is extracted and stored at brand level (not nested inside plan).",
|
|
16288
16633
|
{
|
|
16289
|
-
brandId:
|
|
16290
|
-
plan:
|
|
16634
|
+
brandId: import_zod51.z.string().optional().describe("Brand ID. If omitted, uses the active brand from session context."),
|
|
16635
|
+
plan: import_zod51.z.record(import_zod51.z.string(), import_zod51.z.unknown()).describe("Full marketing plan object. Common fields: keywordsPrioritarios, gapsCompetencia, temporadas, tonoMarca, quickWins, coleccionesPriorizadas, blogStrategy.")
|
|
16291
16636
|
},
|
|
16292
16637
|
async ({ brandId: inputBrandId, plan }) => {
|
|
16293
16638
|
const tenantId = session.requireTenant();
|
|
@@ -16315,9 +16660,9 @@ function registerMarketingTools(server, session) {
|
|
|
16315
16660
|
"update_marketing_plan_field",
|
|
16316
16661
|
"Actualiza UN campo del plan de marketing sin sobreescribir el resto. Merge parcial. Ideal para agregar coleccionesPriorizadas, actualizar quickWins, etc.",
|
|
16317
16662
|
{
|
|
16318
|
-
brandId:
|
|
16319
|
-
field:
|
|
16320
|
-
value:
|
|
16663
|
+
brandId: import_zod51.z.string().optional().describe("ID de la brand"),
|
|
16664
|
+
field: import_zod51.z.string().describe('Nombre del campo a actualizar (ej: "coleccionesPriorizadas", "quickWins", "temporadas")'),
|
|
16665
|
+
value: import_zod51.z.unknown().describe("Valor del campo")
|
|
16321
16666
|
},
|
|
16322
16667
|
async ({ brandId: inputBrandId, field, value }) => {
|
|
16323
16668
|
const tenantId = session.requireTenant();
|
|
@@ -16330,7 +16675,7 @@ function registerMarketingTools(server, session) {
|
|
|
16330
16675
|
"generate_brand_brief",
|
|
16331
16676
|
"Aggregate all business data needed to generate a Brand Brief: Shopify (products, collections, orders, shop info), SEO snapshot, GBP profiles, scraped site_content, brand config, tenant locations. Read-only \u2014 does NOT write the brief. After receiving the payload, generate the brief content and save it via save_brand_brief.",
|
|
16332
16677
|
{
|
|
16333
|
-
brandId:
|
|
16678
|
+
brandId: import_zod51.z.string().optional().describe("Brand ID. If omitted, uses the active brand from session context.")
|
|
16334
16679
|
},
|
|
16335
16680
|
async ({ brandId: inputBrandId }) => {
|
|
16336
16681
|
const tenantId = session.requireTenant();
|
|
@@ -16363,8 +16708,8 @@ function registerMarketingTools(server, session) {
|
|
|
16363
16708
|
"save_brand_brief",
|
|
16364
16709
|
"Save the Brand Brief at tenants/{tenantId}/marketing_config/{brandId}.brandBrief. Partial merge \u2014 only the brandBrief field is overwritten.",
|
|
16365
16710
|
{
|
|
16366
|
-
brandId:
|
|
16367
|
-
brandBrief:
|
|
16711
|
+
brandId: import_zod51.z.string().optional().describe("ID de la brand"),
|
|
16712
|
+
brandBrief: import_zod51.z.record(import_zod51.z.string(), import_zod51.z.unknown()).describe("Full Brand Brief object.")
|
|
16368
16713
|
},
|
|
16369
16714
|
async ({ brandId: inputBrandId, brandBrief }) => {
|
|
16370
16715
|
const tenantId = session.requireTenant();
|
|
@@ -16390,9 +16735,9 @@ function registerMarketingTools(server, session) {
|
|
|
16390
16735
|
"generate_weekly_content",
|
|
16391
16736
|
"Aggregate calendar + photos + plan data to generate the week's content. The LLM generates with the system prompt, then uses save_generated_content to persist.",
|
|
16392
16737
|
{
|
|
16393
|
-
brandId:
|
|
16394
|
-
semana:
|
|
16395
|
-
modo:
|
|
16738
|
+
brandId: import_zod51.z.string().optional().describe("ID de la brand"),
|
|
16739
|
+
semana: import_zod51.z.number().optional().describe("Numero de semana (1-5). Default: semana actual del mes."),
|
|
16740
|
+
modo: import_zod51.z.enum(["planificar", "generar"]).optional().describe("'planificar' = proponer distribucion (plataforma+keyword por dia). 'generar' = generar contenido real para slots ya planificados. Default: 'planificar'.")
|
|
16396
16741
|
},
|
|
16397
16742
|
async ({ brandId: inputBrandId, semana, modo }) => {
|
|
16398
16743
|
const tenantId = session.requireTenant();
|
|
@@ -16420,14 +16765,14 @@ function registerMarketingTools(server, session) {
|
|
|
16420
16765
|
|
|
16421
16766
|
IMPORTANT: If you selected a photo with get_photos_for_slot, ALWAYS pass fotoId here. Without fotoId the post will be published without an image.`,
|
|
16422
16767
|
{
|
|
16423
|
-
brandId:
|
|
16424
|
-
plataforma:
|
|
16425
|
-
tipo:
|
|
16426
|
-
keyword:
|
|
16427
|
-
languageCode:
|
|
16428
|
-
fotoId:
|
|
16429
|
-
datos:
|
|
16430
|
-
calendarioItemRef:
|
|
16768
|
+
brandId: import_zod51.z.string().optional().describe("ID de la brand"),
|
|
16769
|
+
plataforma: import_zod51.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Plataforma destino"),
|
|
16770
|
+
tipo: import_zod51.z.string().optional().describe("Tipo de contenido (post, blog, carousel, etc.)"),
|
|
16771
|
+
keyword: import_zod51.z.string().optional().describe("Keyword target"),
|
|
16772
|
+
languageCode: import_zod51.z.string().optional().describe("Idioma (es/en)"),
|
|
16773
|
+
fotoId: import_zod51.z.string().optional().describe("ID de la foto a asociar"),
|
|
16774
|
+
datos: import_zod51.z.record(import_zod51.z.string(), import_zod51.z.unknown()).describe("Datos especificos de la plataforma (output de buildDatos*)"),
|
|
16775
|
+
calendarioItemRef: import_zod51.z.string().optional().describe("Referencia al item del calendario")
|
|
16431
16776
|
},
|
|
16432
16777
|
async ({ brandId: inputBrandId, plataforma, tipo, keyword, languageCode, fotoId, datos, calendarioItemRef }) => {
|
|
16433
16778
|
const tenantId = session.requireTenant();
|
|
@@ -16463,13 +16808,13 @@ Usa para: corregir body, metaTitle, tags, fotoId, o cualquier campo sin tener qu
|
|
|
16463
16808
|
NO puede cambiar: tenantId, brandId, id (inmutables).
|
|
16464
16809
|
Si pasas campos dentro de "datos", se hace merge con los datos existentes (no los reemplaza entero).`,
|
|
16465
16810
|
{
|
|
16466
|
-
contenidoId:
|
|
16467
|
-
datos:
|
|
16468
|
-
fotoId:
|
|
16469
|
-
keyword:
|
|
16470
|
-
languageCode:
|
|
16471
|
-
estado:
|
|
16472
|
-
calendarioItemRef:
|
|
16811
|
+
contenidoId: import_zod51.z.string().describe("ID del doc en marketing_contenido"),
|
|
16812
|
+
datos: import_zod51.z.record(import_zod51.z.string(), import_zod51.z.unknown()).optional().describe('Campos de datos a actualizar (merge parcial sobre datos existentes). Ej: { body: "...", metaTitle: "..." }'),
|
|
16813
|
+
fotoId: import_zod51.z.string().nullable().optional().describe("Actualizar foto asociada"),
|
|
16814
|
+
keyword: import_zod51.z.string().nullable().optional().describe("Actualizar keyword"),
|
|
16815
|
+
languageCode: import_zod51.z.string().optional().describe("Actualizar idioma"),
|
|
16816
|
+
estado: import_zod51.z.enum(["borrador", "generado", "pendiente_aprobacion", "aprobado", "rechazado"]).optional().describe("Cambiar estado manualmente"),
|
|
16817
|
+
calendarioItemRef: import_zod51.z.string().nullable().optional().describe("Vincular a un slot del calendario")
|
|
16473
16818
|
},
|
|
16474
16819
|
async ({ contenidoId, datos: newDatos, fotoId, keyword, languageCode, estado, calendarioItemRef }) => {
|
|
16475
16820
|
const tenantId = session.requireTenant();
|
|
@@ -16503,19 +16848,19 @@ Si pasas campos dentro de "datos", se hace merge con los datos existentes (no lo
|
|
|
16503
16848
|
"add_calendar_slot",
|
|
16504
16849
|
"Add a NEW slot to the editorial calendar. To modify an existing slot use update_calendar_slot instead.",
|
|
16505
16850
|
{
|
|
16506
|
-
brandId:
|
|
16507
|
-
mes:
|
|
16508
|
-
semana:
|
|
16509
|
-
slot:
|
|
16510
|
-
dia:
|
|
16511
|
-
plataforma:
|
|
16512
|
-
tipo:
|
|
16513
|
-
keyword:
|
|
16514
|
-
tema:
|
|
16515
|
-
productoId:
|
|
16516
|
-
estado:
|
|
16517
|
-
locationId:
|
|
16518
|
-
locationNombre:
|
|
16851
|
+
brandId: import_zod51.z.string().describe('Brand ID (e.g. "ponch", "teleglobos").'),
|
|
16852
|
+
mes: import_zod51.z.string().describe('Calendar month in YYYY-MM format (e.g. "2026-05").'),
|
|
16853
|
+
semana: import_zod51.z.number().describe("Week number within the month (1-5)."),
|
|
16854
|
+
slot: import_zod51.z.object({
|
|
16855
|
+
dia: import_zod51.z.string().describe("Slot date in YYYY-MM-DD format. Must fall within the week's fechaInicio/fechaFin range."),
|
|
16856
|
+
plataforma: import_zod51.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Target publishing platform."),
|
|
16857
|
+
tipo: import_zod51.z.enum(["post", "blog", "carousel", "reel", "story", "review_response"]).describe("Content type."),
|
|
16858
|
+
keyword: import_zod51.z.string().describe("Primary keyword for the content."),
|
|
16859
|
+
tema: import_zod51.z.string().optional().describe("Content topic/theme. OMIT this field if it does not apply. Do NOT send empty string or null."),
|
|
16860
|
+
productoId: import_zod51.z.string().optional().describe("Linked product ID. OMIT this field if no product is linked."),
|
|
16861
|
+
estado: import_zod51.z.enum(["planificado", "pre_aprobado", "generado", "revisar", "aprobado", "publicado", "rechazado"]).optional().describe('Initial status. OMIT to use default "planificado".'),
|
|
16862
|
+
locationId: import_zod51.z.string().optional().describe("GBP location ID \u2014 only when plataforma=gbp AND tenant is multi-location. OMIT otherwise."),
|
|
16863
|
+
locationNombre: import_zod51.z.string().optional().describe("Human-readable GBP location name. OMIT if locationId is not provided.")
|
|
16519
16864
|
}).describe("New slot data.")
|
|
16520
16865
|
},
|
|
16521
16866
|
async ({ brandId, mes, semana, slot }) => {
|
|
@@ -16541,27 +16886,27 @@ Si pasas campos dentro de "datos", se hace merge con los datos existentes (no lo
|
|
|
16541
16886
|
"update_calendar_slot",
|
|
16542
16887
|
"MODIFY an EXISTING slot in the editorial calendar. To create a new slot use add_calendar_slot. If slotIndex does not exist, returns error SLOT_NOT_FOUND.",
|
|
16543
16888
|
{
|
|
16544
|
-
brandId:
|
|
16545
|
-
mes:
|
|
16546
|
-
semana:
|
|
16547
|
-
slotIndex:
|
|
16548
|
-
cambios:
|
|
16549
|
-
dia:
|
|
16550
|
-
plataforma:
|
|
16551
|
-
tipo:
|
|
16552
|
-
keyword:
|
|
16553
|
-
tema:
|
|
16554
|
-
productoId:
|
|
16555
|
-
estado:
|
|
16556
|
-
contenidoRef:
|
|
16557
|
-
fotoIdAsignada:
|
|
16558
|
-
notas:
|
|
16559
|
-
locationId:
|
|
16560
|
-
locationNombre:
|
|
16889
|
+
brandId: import_zod51.z.string().optional().describe("Brand ID. If omitted, uses the active brand from session context."),
|
|
16890
|
+
mes: import_zod51.z.string().describe("Calendar month in YYYY-MM format."),
|
|
16891
|
+
semana: import_zod51.z.number().describe("Week number within the month (1-5)."),
|
|
16892
|
+
slotIndex: import_zod51.z.number().describe("Slot index (0-based). Must point to an existing slot \u2014 to add new slots use add_calendar_slot."),
|
|
16893
|
+
cambios: import_zod51.z.object({
|
|
16894
|
+
dia: import_zod51.z.string().nullable().optional().describe("Slot date in YYYY-MM-DD. OMIT if not changing date. Use null to explicitly clear."),
|
|
16895
|
+
plataforma: import_zod51.z.enum(["gbp", "shopify_blog", "instagram", "review"]).nullable().optional().describe("OMIT if not changing platform."),
|
|
16896
|
+
tipo: import_zod51.z.enum(["post", "blog", "carousel", "reel", "story", "review_response"]).nullable().optional().describe("OMIT if not changing content type."),
|
|
16897
|
+
keyword: import_zod51.z.string().nullable().optional().describe("OMIT if not changing keyword."),
|
|
16898
|
+
tema: import_zod51.z.string().nullable().optional().describe("OMIT if not changing topic."),
|
|
16899
|
+
productoId: import_zod51.z.string().nullable().optional().describe("OMIT if not changing linked product. Use null to unlink."),
|
|
16900
|
+
estado: import_zod51.z.enum(["planificado", "pre_aprobado", "generado", "revisar", "aprobado", "publicado", "rechazado"]).optional().describe("OMIT if not changing status manually."),
|
|
16901
|
+
contenidoRef: import_zod51.z.string().nullable().optional().describe("OMIT \u2014 managed by the system, not by callers."),
|
|
16902
|
+
fotoIdAsignada: import_zod51.z.string().nullable().optional().describe("Use assign_photo_to_content for photos. OMIT here."),
|
|
16903
|
+
notas: import_zod51.z.array(NotaCalendarioSchema).optional().describe("Append-only notes for the slot."),
|
|
16904
|
+
locationId: import_zod51.z.string().nullable().optional().describe("GBP location ID \u2014 only when plataforma=gbp."),
|
|
16905
|
+
locationNombre: import_zod51.z.string().nullable().optional().describe("Human-readable GBP location name (for UI).")
|
|
16561
16906
|
}).strict().describe("Fields to update on the slot. Only declared fields accepted; unknown fields are rejected. OMIT any field you are not changing."),
|
|
16562
|
-
accionContenidoExistente:
|
|
16563
|
-
|
|
16564
|
-
|
|
16907
|
+
accionContenidoExistente: import_zod51.z.union([
|
|
16908
|
+
import_zod51.z.enum(["descartar", "nuevo_slot", "mantener"]),
|
|
16909
|
+
import_zod51.z.string().regex(/^mover:semana:\d+:slot:\d+$/, "Format: mover:semana:N:slot:M")
|
|
16565
16910
|
]).optional().describe(
|
|
16566
16911
|
'Required ONLY when the slot already has a contenidoRef AND the cambios touch semantic fields (keyword/tema/plataforma/tipo). In that case, the helper returns ACCION_CONTENIDO_EXISTENTE_REQUIRED with the 4 options listed below \u2014 ask the tenant which one to apply. OMIT this field in any other case. Do NOT send null. Valid values: "descartar" (mark existing content as discarded and apply changes to this slot) | "mover:semana:N:slot:M" (move existing content to target empty slot N/M and apply changes to origin slot) | "nuevo_slot" (do NOT touch this slot or its content; create a new slot same day with the changes \u2014 useful for multiple posts per day) | "mantener" (keep existing content here and apply changes anyway \u2014 typo-fix case where old content stays valid).'
|
|
16567
16912
|
)
|
|
@@ -16606,9 +16951,9 @@ ESCRIBE EN DOS LUGARES:
|
|
|
16606
16951
|
|
|
16607
16952
|
NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agenda, no en el post.`,
|
|
16608
16953
|
{
|
|
16609
|
-
contenidoRef:
|
|
16610
|
-
fotoId:
|
|
16611
|
-
calendarioItemRef:
|
|
16954
|
+
contenidoRef: import_zod51.z.string().describe("ID del doc en marketing_contenido"),
|
|
16955
|
+
fotoId: import_zod51.z.string().describe("ID de la foto en marketing_fotos (estado editada)"),
|
|
16956
|
+
calendarioItemRef: import_zod51.z.string().optional().describe('Ref del slot (formato "semana:N:slot:M") para actualizar fotoIdAsignada')
|
|
16612
16957
|
},
|
|
16613
16958
|
async ({ contenidoRef, fotoId, calendarioItemRef }) => {
|
|
16614
16959
|
const tenantId = session.requireTenant();
|
|
@@ -16634,7 +16979,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
16634
16979
|
"approve_content",
|
|
16635
16980
|
"Aprueba contenido para publicacion. Valida transicion de estado.",
|
|
16636
16981
|
{
|
|
16637
|
-
contenidoId:
|
|
16982
|
+
contenidoId: import_zod51.z.string().describe("ID del contenido a aprobar")
|
|
16638
16983
|
},
|
|
16639
16984
|
async ({ contenidoId }) => {
|
|
16640
16985
|
session.requireTenant();
|
|
@@ -16658,8 +17003,8 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
16658
17003
|
"reject_content",
|
|
16659
17004
|
"Rechaza contenido con motivo. Valida transicion de estado.",
|
|
16660
17005
|
{
|
|
16661
|
-
contenidoId:
|
|
16662
|
-
motivo:
|
|
17006
|
+
contenidoId: import_zod51.z.string().describe("ID del contenido a rechazar"),
|
|
17007
|
+
motivo: import_zod51.z.string().describe("Motivo del rechazo")
|
|
16663
17008
|
},
|
|
16664
17009
|
async ({ contenidoId, motivo }) => {
|
|
16665
17010
|
session.requireTenant();
|
|
@@ -16680,96 +17025,40 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
16680
17025
|
);
|
|
16681
17026
|
server.tool(
|
|
16682
17027
|
"get_collections",
|
|
16683
|
-
"
|
|
17028
|
+
"Read all canonical collections (Shopify/WordPress/web-only) for a brand with their 6 SEO fields, plus best practices and the strict schema for generating SEO suggestions.",
|
|
16684
17029
|
{
|
|
16685
|
-
brandId:
|
|
17030
|
+
brandId: import_zod51.z.string().optional().describe("Brand identifier within the tenant")
|
|
16686
17031
|
},
|
|
16687
17032
|
async ({ brandId: inputBrandId }) => {
|
|
16688
17033
|
const tenantId = session.requireTenant();
|
|
16689
17034
|
const brandId = inputBrandId ?? session.requireBrand();
|
|
16690
|
-
const
|
|
16691
|
-
const
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
17035
|
+
const ctx = await buildContext(session, brandId);
|
|
17036
|
+
const result = await dispatchWithContract({
|
|
17037
|
+
contract: collectionsReaderContract,
|
|
17038
|
+
helper: collectionsReader,
|
|
17039
|
+
callable: callCollectionsReader,
|
|
17040
|
+
input: { tenantId, brandId },
|
|
17041
|
+
ctx
|
|
17042
|
+
});
|
|
17043
|
+
let payload;
|
|
17044
|
+
if (result.state === "success" && result.structuredOutput) {
|
|
17045
|
+
payload = result.structuredOutput;
|
|
17046
|
+
} else {
|
|
17047
|
+
payload = {
|
|
16695
17048
|
ok: false,
|
|
16696
|
-
|
|
16697
|
-
|
|
16698
|
-
|
|
16699
|
-
const brand = await readDoc(`tenants/${tenantId}/marketing_config`, brandId);
|
|
16700
|
-
const plan = brand?.plan ?? {};
|
|
16701
|
-
const keywords = plan.keywordsPrioritarios ?? [];
|
|
16702
|
-
const existingSuggestions = brand?.collectionSuggestions ?? {};
|
|
16703
|
-
const collections = items.map((c) => {
|
|
16704
|
-
const seo = c.seo || {};
|
|
16705
|
-
const featured = c.featuredImage || null;
|
|
16706
|
-
return {
|
|
16707
|
-
id: c.platformId,
|
|
16708
|
-
title: c.title,
|
|
16709
|
-
handle: c.handle,
|
|
16710
|
-
body_html: typeof c.descriptionHtml === "string" ? c.descriptionHtml.slice(0, 500) : null,
|
|
16711
|
-
metaTitle: seo.metaTitle ?? null,
|
|
16712
|
-
metaDescription: seo.metaDescription ?? null,
|
|
16713
|
-
products_count: null,
|
|
16714
|
-
// adapter v2 aun no calcula products_count por collection
|
|
16715
|
-
collectionType: "canonical",
|
|
16716
|
-
image: featured ? { src: featured.url, alt: featured.altText } : null,
|
|
16717
|
-
existingSuggestion: existingSuggestions[String(c.platformId)] ?? null
|
|
17049
|
+
state: result.state,
|
|
17050
|
+
mensaje: result.text,
|
|
17051
|
+
...result.validationErrors && result.validationErrors.length > 0 ? { validationErrors: result.validationErrors } : {}
|
|
16718
17052
|
};
|
|
16719
|
-
}
|
|
16720
|
-
return { content: [{ type: "text", text: JSON.stringify(
|
|
16721
|
-
ok: true,
|
|
16722
|
-
totalCollections: collections.length,
|
|
16723
|
-
keywordsPrioritarios: keywords,
|
|
16724
|
-
collections,
|
|
16725
|
-
bestPractices: {
|
|
16726
|
-
title: "Keyword principal, claro y corto. H1 de la p\xE1gina.",
|
|
16727
|
-
description: "Arriba del grid: 2-3 frases (50-70 palabras). Abajo: 200-400 palabras con long-tail keywords. P\xE1ginas con descripci\xF3n rankean 2.7x m\xE1s.",
|
|
16728
|
-
metaTitle: "50-60 chars. Keyword al inicio. Formato: {Keyword} | {Brand}. Max 600px.",
|
|
16729
|
-
metaDescription: "120-158 chars. Keyword + CTA + value prop. 120 mobile, 158 desktop.",
|
|
16730
|
-
handle: "Keyword en URL. NUNCA cambiar URL indexado sin redirect 301.",
|
|
16731
|
-
imageAlt: "Descriptivo con keyword. Accesibilidad + Google Images + AEO.",
|
|
16732
|
-
aeo: "AI engines validan im\xE1genes contra schema. Structured data importa."
|
|
16733
|
-
},
|
|
16734
|
-
schemaParaSave: {
|
|
16735
|
-
_instrucciones: "SCHEMA ESTRICTO para save_collection_suggestions. Usa EXACTAMENTE estos nombres de campo. El sistema RECHAZAR\xC1 campos que no coincidan.",
|
|
16736
|
-
_ejemplo: {
|
|
16737
|
-
collectionId: 123456789,
|
|
16738
|
-
suggestedTitle: "Flores Moradas | Env\xEDo CDMX",
|
|
16739
|
-
suggestedDescription: "<p>Descubre nuestra colecci\xF3n de flores moradas...</p>",
|
|
16740
|
-
suggestedMetaTitle: "Flores Moradas CDMX | Env\xEDo Mismo D\xEDa",
|
|
16741
|
-
suggestedMetaDescription: "Env\xEDa flores moradas a domicilio en CDMX. Arreglos frescos con rosas, tulipanes y lirios morados. Entrega el mismo d\xEDa.",
|
|
16742
|
-
suggestedHandle: null,
|
|
16743
|
-
suggestedImageAlt: "Ramo de flores moradas con rosas y tulipanes - Ponch y Capric\xF3 florer\xEDa CDMX",
|
|
16744
|
-
keyword: "flores moradas cdmx",
|
|
16745
|
-
notas: "Meta title optimizado con keyword local. Description ampliada con long-tail keywords. Handle no cambia (ya indexado)."
|
|
16746
|
-
},
|
|
16747
|
-
_reglas: [
|
|
16748
|
-
"collectionId: OBLIGATORIO. ID num\xE9rico de Shopify (no GID)",
|
|
16749
|
-
"suggestedTitle: string. H1 de la p\xE1gina. Claro con keyword",
|
|
16750
|
-
"suggestedDescription: string HTML. 200-400 palabras. Incluir links a colecciones relacionadas",
|
|
16751
|
-
"suggestedMetaTitle: string. 50-60 chars. Keyword al inicio. NUNCA m\xE1s de 60",
|
|
16752
|
-
"suggestedMetaDescription: string. 120-158 chars. Keyword + CTA + value prop. NUNCA m\xE1s de 158",
|
|
16753
|
-
"suggestedHandle: string | null. Solo cambiar si el handle actual es malo. null = no cambiar",
|
|
16754
|
-
"suggestedImageAlt: string. Descriptivo con keyword. Para accesibilidad + Google Images",
|
|
16755
|
-
"keyword: string. Keyword target de esta colecci\xF3n (del plan de marketing)",
|
|
16756
|
-
"notas: string. Explicaci\xF3n breve de qu\xE9 cambiaste y por qu\xE9"
|
|
16757
|
-
],
|
|
16758
|
-
_nunca: [
|
|
16759
|
-
"NUNCA usar titulo, descripcion, metaTitulo, metaDescripcion \u2014 usar los nombres en ingl\xE9s",
|
|
16760
|
-
"NUNCA inventar collectionId \u2014 usar el id del array collections",
|
|
16761
|
-
"NUNCA meta title > 60 chars o meta description > 158 chars",
|
|
16762
|
-
"NUNCA cambiar handle de colecci\xF3n indexada sin justificaci\xF3n"
|
|
16763
|
-
]
|
|
16764
|
-
}
|
|
16765
|
-
}) }] };
|
|
17053
|
+
}
|
|
17054
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
16766
17055
|
}
|
|
16767
17056
|
);
|
|
16768
17057
|
server.tool(
|
|
16769
17058
|
"save_collection_suggestions",
|
|
16770
17059
|
"Guarda sugerencias SEO para colecciones de Shopify. El tenant las aprueba en la UI.",
|
|
16771
17060
|
{
|
|
16772
|
-
brandId:
|
|
17061
|
+
brandId: import_zod51.z.string().optional().describe("ID de la brand"),
|
|
16773
17062
|
suggestions: CollectionSuggestionsInputArraySchema.describe("Array de sugerencias SEO. Cada una con max 60 chars en metaTitle, max 158 en metaDescription, max 125 en imageAlt")
|
|
16774
17063
|
},
|
|
16775
17064
|
async ({ brandId: inputBrandId, suggestions }) => {
|
|
@@ -16795,7 +17084,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
16795
17084
|
}
|
|
16796
17085
|
|
|
16797
17086
|
// src/tools/martin.ts
|
|
16798
|
-
var
|
|
17087
|
+
var import_zod52 = require("zod");
|
|
16799
17088
|
function renderResult(result) {
|
|
16800
17089
|
const payload = {
|
|
16801
17090
|
state: result.state,
|
|
@@ -16811,16 +17100,16 @@ function registerMartinTools(server, session) {
|
|
|
16811
17100
|
"recordar_memoria",
|
|
16812
17101
|
"Save a user preference, rule, pattern or aversion that the system will respect in future interactions. Personal to the calling user (scope: self). Applied automatically in Martin's prompt for subsequent conversations.",
|
|
16813
17102
|
{
|
|
16814
|
-
tipo:
|
|
17103
|
+
tipo: import_zod52.z.enum(["preferencia", "regla", "patron", "aversion"]).describe(
|
|
16815
17104
|
"Memory type: 'preferencia' (personal preference), 'regla' (operational rule), 'patron' (observed behavioral pattern), 'aversion' (explicit do-not-do rule)."
|
|
16816
17105
|
),
|
|
16817
|
-
categoria:
|
|
16818
|
-
contenido:
|
|
16819
|
-
origen:
|
|
16820
|
-
tipo:
|
|
16821
|
-
conversacionId:
|
|
16822
|
-
cardId:
|
|
16823
|
-
rationale:
|
|
17106
|
+
categoria: import_zod52.z.enum(["compras", "produccion", "dispatch", "ventas", "marketing", "operacion", "personal", "delegacion"]).describe("Business area this memory applies to."),
|
|
17107
|
+
contenido: import_zod52.z.string().min(3).max(500).describe("Memory content in the user's own words (3-500 chars)."),
|
|
17108
|
+
origen: import_zod52.z.object({
|
|
17109
|
+
tipo: import_zod52.z.enum(["conversacion", "feedback_card", "inferido_automatico"]),
|
|
17110
|
+
conversacionId: import_zod52.z.string().nullable(),
|
|
17111
|
+
cardId: import_zod52.z.string().nullable(),
|
|
17112
|
+
rationale: import_zod52.z.array(import_zod52.z.string()).optional()
|
|
16824
17113
|
}).describe("Where this memory was inferred from (conversation, card feedback, or auto-inferred).")
|
|
16825
17114
|
},
|
|
16826
17115
|
async ({ tipo, categoria, contenido, origen }) => {
|
|
@@ -16839,9 +17128,9 @@ function registerMartinTools(server, session) {
|
|
|
16839
17128
|
"olvidar_memoria",
|
|
16840
17129
|
"Archive a memory that no longer applies. The memory remains visible in settings but is no longer enforced in future interactions. Requires confirmation: the first call returns a confirmation prompt; pass confirm=true on the second call to execute.",
|
|
16841
17130
|
{
|
|
16842
|
-
memoriaId:
|
|
16843
|
-
motivo:
|
|
16844
|
-
confirm:
|
|
17131
|
+
memoriaId: import_zod52.z.string().min(1).describe("Memory document ID to archive."),
|
|
17132
|
+
motivo: import_zod52.z.string().optional().describe("Optional reason for archiving (logged for audit)."),
|
|
17133
|
+
confirm: import_zod52.z.boolean().optional().describe("Set to true on the re-invocation after the user confirms. Default false.")
|
|
16845
17134
|
},
|
|
16846
17135
|
async ({ memoriaId, motivo, confirm }) => {
|
|
16847
17136
|
const ctx = await buildContext(session, null, { confirmationGranted: confirm === true });
|
|
@@ -16859,25 +17148,25 @@ function registerMartinTools(server, session) {
|
|
|
16859
17148
|
"programar_rutina",
|
|
16860
17149
|
"Schedule a recurring or one-time task. Useful for monthly reports, reminders, scheduled posts, suggested purchases. Personal to the calling user (scope: self). Requires confirmation.",
|
|
16861
17150
|
{
|
|
16862
|
-
uidDestinatario:
|
|
16863
|
-
tipo:
|
|
16864
|
-
frecuencia:
|
|
16865
|
-
config:
|
|
16866
|
-
diaSemana:
|
|
16867
|
-
diaMes:
|
|
16868
|
-
hora:
|
|
16869
|
-
fechaPuntual:
|
|
17151
|
+
uidDestinatario: import_zod52.z.string().optional().describe("User ID who should receive the routine output. OMIT to default to the calling user (most common case)."),
|
|
17152
|
+
tipo: import_zod52.z.enum(["reporte", "recordatorio", "accion_delegada", "publicacion", "compra_sugerida"]).describe("Routine type from canonical TipoRutinaEnum."),
|
|
17153
|
+
frecuencia: import_zod52.z.enum(["diaria", "semanal", "quincenal", "mensual", "trimestral", "puntual"]).describe("Execution cadence."),
|
|
17154
|
+
config: import_zod52.z.object({
|
|
17155
|
+
diaSemana: import_zod52.z.number().int().min(0).max(6).nullable(),
|
|
17156
|
+
diaMes: import_zod52.z.number().int().min(1).max(31).nullable(),
|
|
17157
|
+
hora: import_zod52.z.string().regex(/^\d{2}:\d{2}$/),
|
|
17158
|
+
fechaPuntual: import_zod52.z.string().datetime({ offset: true }).nullable()
|
|
16870
17159
|
}).describe("Schedule configuration. Match fields to the frecuencia (semanal needs diaSemana; mensual needs diaMes; puntual needs fechaPuntual)."),
|
|
16871
|
-
accion:
|
|
16872
|
-
tool:
|
|
16873
|
-
params:
|
|
17160
|
+
accion: import_zod52.z.object({
|
|
17161
|
+
tool: import_zod52.z.string().min(1),
|
|
17162
|
+
params: import_zod52.z.record(import_zod52.z.string(), import_zod52.z.unknown())
|
|
16874
17163
|
}).describe("Action to execute on each fire (MCP tool name + params)."),
|
|
16875
|
-
origen:
|
|
16876
|
-
tipo:
|
|
16877
|
-
conversacionId:
|
|
16878
|
-
cardId:
|
|
17164
|
+
origen: import_zod52.z.object({
|
|
17165
|
+
tipo: import_zod52.z.enum(["conversacion", "feedback_card", "configurada_explicito"]),
|
|
17166
|
+
conversacionId: import_zod52.z.string().nullable(),
|
|
17167
|
+
cardId: import_zod52.z.string().nullable()
|
|
16879
17168
|
}).describe("Where this routine was scheduled from."),
|
|
16880
|
-
confirm:
|
|
17169
|
+
confirm: import_zod52.z.boolean().optional().describe("Set to true on the re-invocation after the user confirms. Default false.")
|
|
16881
17170
|
},
|
|
16882
17171
|
async ({ uidDestinatario, tipo, frecuencia, config, accion, origen, confirm }) => {
|
|
16883
17172
|
const ctx = await buildContext(session, null, { confirmationGranted: confirm === true });
|
|
@@ -16906,8 +17195,8 @@ function registerMartinTools(server, session) {
|
|
|
16906
17195
|
"pausar_rutina",
|
|
16907
17196
|
"Pause an active routine temporarily. Can be resumed later by re-enabling it.",
|
|
16908
17197
|
{
|
|
16909
|
-
rutinaId:
|
|
16910
|
-
motivo:
|
|
17198
|
+
rutinaId: import_zod52.z.string().min(1).describe("Routine document ID to pause."),
|
|
17199
|
+
motivo: import_zod52.z.string().optional().describe("Optional reason for pausing (logged for audit).")
|
|
16911
17200
|
},
|
|
16912
17201
|
async ({ rutinaId, motivo }) => {
|
|
16913
17202
|
const ctx = await buildContext(session, null);
|
|
@@ -16933,9 +17222,9 @@ function registerMartinTools(server, session) {
|
|
|
16933
17222
|
"archivar_rutina",
|
|
16934
17223
|
"Archive a routine that no longer applies. The routine is preserved for audit purposes but will NOT be executed. Requires confirmation.",
|
|
16935
17224
|
{
|
|
16936
|
-
rutinaId:
|
|
16937
|
-
motivo:
|
|
16938
|
-
confirm:
|
|
17225
|
+
rutinaId: import_zod52.z.string().min(1).describe("Routine document ID to archive (will not execute again)."),
|
|
17226
|
+
motivo: import_zod52.z.string().optional().describe("Optional reason for archiving (logged for audit)."),
|
|
17227
|
+
confirm: import_zod52.z.boolean().optional().describe("Set to true on the re-invocation after the user confirms. Default false.")
|
|
16939
17228
|
},
|
|
16940
17229
|
async ({ rutinaId, motivo, confirm }) => {
|
|
16941
17230
|
const ctx = await buildContext(session, null, { confirmationGranted: confirm === true });
|