ponch-mcp-server 1.0.87 → 1.0.89
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 +934 -507
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -170,6 +170,41 @@ var Session = class {
|
|
|
170
170
|
this.context.tenantId = tenantId;
|
|
171
171
|
this.context.brandId = brandId;
|
|
172
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Cambia la brand activa SIN cambiar tenant. Disponible para:
|
|
175
|
+
* - User Modo B con multi-brand (brands.length > 1) — agency mode.
|
|
176
|
+
* - Super_admin Modo A (canSwitchTenant=true) — service account
|
|
177
|
+
* puede operar cualquier brand dentro del tenant activo seteado
|
|
178
|
+
* previamente vía set_context.
|
|
179
|
+
*
|
|
180
|
+
* DEUDA 3 sesión 211.1 H/I + fix J post-smoke 1.0.88.
|
|
181
|
+
*
|
|
182
|
+
* Lanza si:
|
|
183
|
+
* - El user no es super_admin Y no tiene multi-brand.
|
|
184
|
+
* - El brandId NO está en lista del user (defensa cross-brand Modo B).
|
|
185
|
+
* Para super_admin Modo A se delega validación al helper (Firestore
|
|
186
|
+
* existence check) — no hay "lista accesible" porque tiene acceso
|
|
187
|
+
* total al tenant.
|
|
188
|
+
*/
|
|
189
|
+
setBrand(brandId) {
|
|
190
|
+
const brands = this._authContext.brands ?? [];
|
|
191
|
+
const isSuperAdmin = this._authContext.canSwitchTenant;
|
|
192
|
+
if (!isSuperAdmin && brands.length <= 1) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`select_brand requires multi-brand access or super_admin. This user has access to ${brands.length} brand(s) and is not super_admin.`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (!isSuperAdmin) {
|
|
198
|
+
const allowed = brands.some((b) => b.id === brandId);
|
|
199
|
+
if (!allowed) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Brand "${brandId}" is not in the user's accessible brands list [${brands.map((b) => b.id).join(", ")}].`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.context.brandId = brandId;
|
|
206
|
+
this._authContext.brandId = brandId;
|
|
207
|
+
}
|
|
173
208
|
/**
|
|
174
209
|
* Revoca canSwitchTenant en runtime. Usado cuando el bootstrap detecta
|
|
175
210
|
* token expirado — el usuario debe re-autenticar via connect_account
|
|
@@ -433,7 +468,7 @@ function serverTimestamp() {
|
|
|
433
468
|
}
|
|
434
469
|
|
|
435
470
|
// src/tools/context.ts
|
|
436
|
-
var
|
|
471
|
+
var import_zod63 = require("zod");
|
|
437
472
|
var import_functions2 = require("firebase/functions");
|
|
438
473
|
var import_app3 = require("firebase/app");
|
|
439
474
|
|
|
@@ -1632,12 +1667,25 @@ var ContenidoSchema = import_zod12.z.object({
|
|
|
1632
1667
|
mediaVariante: MediaVarianteSchema.nullable().optional(),
|
|
1633
1668
|
creadoAt: import_zod12.z.unknown().nullable().optional(),
|
|
1634
1669
|
creadoPorId: import_zod12.z.string().nullable().optional(),
|
|
1670
|
+
// §9.2 PLAN_C2 — actor canónico de la creación. Auto-poblado desde
|
|
1671
|
+
// ctx.user.{nombre,clientType,clientMetadata} en server.tool.
|
|
1672
|
+
creadoPorNombre: import_zod12.z.string().nullable().optional(),
|
|
1673
|
+
creadoPorClient: import_zod12.z.string().nullable().optional(),
|
|
1674
|
+
creadoPorClientMetadata: import_zod12.z.record(import_zod12.z.string(), import_zod12.z.unknown()).nullable().optional(),
|
|
1635
1675
|
origen: import_zod12.z.enum(ORIGENES_CONTENIDO).nullable().optional(),
|
|
1636
1676
|
aprobadoAt: import_zod12.z.unknown().nullable().optional(),
|
|
1637
1677
|
aprobadoPorId: import_zod12.z.string().nullable().optional(),
|
|
1638
1678
|
aprobadoPorNombre: import_zod12.z.string().nullable().optional(),
|
|
1679
|
+
// §9.2 — extendido en Sub-A2.1; agregado al schema canónico aquí.
|
|
1680
|
+
aprobadoPorClient: import_zod12.z.string().nullable().optional(),
|
|
1681
|
+
aprobadoPorClientMetadata: import_zod12.z.record(import_zod12.z.string(), import_zod12.z.unknown()).nullable().optional(),
|
|
1639
1682
|
rechazadoAt: import_zod12.z.unknown().nullable().optional(),
|
|
1640
1683
|
rechazadoMotivo: import_zod12.z.string().nullable().optional(),
|
|
1684
|
+
// §9.2 — extendido en Sub-A2.2; agregado al schema canónico aquí.
|
|
1685
|
+
rechazadoPorId: import_zod12.z.string().nullable().optional(),
|
|
1686
|
+
rechazadoPorNombre: import_zod12.z.string().nullable().optional(),
|
|
1687
|
+
rechazadoPorClient: import_zod12.z.string().nullable().optional(),
|
|
1688
|
+
rechazadoPorClientMetadata: import_zod12.z.record(import_zod12.z.string(), import_zod12.z.unknown()).nullable().optional(),
|
|
1641
1689
|
editadoAt: import_zod12.z.unknown().nullable().optional(),
|
|
1642
1690
|
publicadoAt: import_zod12.z.unknown().nullable().optional(),
|
|
1643
1691
|
errorPublicacion: import_zod12.z.string().nullable().optional(),
|
|
@@ -1665,6 +1713,9 @@ function buildContenido(input) {
|
|
|
1665
1713
|
mediaVariante: input.mediaVariante ?? null,
|
|
1666
1714
|
creadoAt: input.creadoAt ?? null,
|
|
1667
1715
|
creadoPorId: input.creadoPorId ?? null,
|
|
1716
|
+
creadoPorNombre: input.creadoPorNombre ?? null,
|
|
1717
|
+
creadoPorClient: input.creadoPorClient ?? null,
|
|
1718
|
+
creadoPorClientMetadata: input.creadoPorClientMetadata ?? null,
|
|
1668
1719
|
origen: input.origen ?? null
|
|
1669
1720
|
});
|
|
1670
1721
|
}
|
|
@@ -3510,17 +3561,17 @@ var PolyDateFormatter = class {
|
|
|
3510
3561
|
constructor(dt, intl, opts) {
|
|
3511
3562
|
this.opts = opts;
|
|
3512
3563
|
this.originalZone = void 0;
|
|
3513
|
-
let
|
|
3564
|
+
let z39 = void 0;
|
|
3514
3565
|
if (this.opts.timeZone) {
|
|
3515
3566
|
this.dt = dt;
|
|
3516
3567
|
} else if (dt.zone.type === "fixed") {
|
|
3517
3568
|
const gmtOffset = -1 * (dt.offset / 60);
|
|
3518
3569
|
const offsetZ = gmtOffset >= 0 ? `Etc/GMT+${gmtOffset}` : `Etc/GMT${gmtOffset}`;
|
|
3519
3570
|
if (dt.offset !== 0 && IANAZone.create(offsetZ).valid) {
|
|
3520
|
-
|
|
3571
|
+
z39 = offsetZ;
|
|
3521
3572
|
this.dt = dt;
|
|
3522
3573
|
} else {
|
|
3523
|
-
|
|
3574
|
+
z39 = "UTC";
|
|
3524
3575
|
this.dt = dt.offset === 0 ? dt : dt.setZone("UTC").plus({ minutes: dt.offset });
|
|
3525
3576
|
this.originalZone = dt.zone;
|
|
3526
3577
|
}
|
|
@@ -3528,14 +3579,14 @@ var PolyDateFormatter = class {
|
|
|
3528
3579
|
this.dt = dt;
|
|
3529
3580
|
} else if (dt.zone.type === "iana") {
|
|
3530
3581
|
this.dt = dt;
|
|
3531
|
-
|
|
3582
|
+
z39 = dt.zone.name;
|
|
3532
3583
|
} else {
|
|
3533
|
-
|
|
3584
|
+
z39 = "UTC";
|
|
3534
3585
|
this.dt = dt.setZone("UTC").plus({ minutes: dt.offset });
|
|
3535
3586
|
this.originalZone = dt.zone;
|
|
3536
3587
|
}
|
|
3537
3588
|
const intlOpts = { ...this.opts };
|
|
3538
|
-
intlOpts.timeZone = intlOpts.timeZone ||
|
|
3589
|
+
intlOpts.timeZone = intlOpts.timeZone || z39;
|
|
3539
3590
|
this.dtf = getCachedDTF(intl, intlOpts);
|
|
3540
3591
|
}
|
|
3541
3592
|
format() {
|
|
@@ -10837,16 +10888,21 @@ var import_zod49 = require("zod");
|
|
|
10837
10888
|
var import_zod50 = require("zod");
|
|
10838
10889
|
var import_firebase_admin11 = require("firebase-admin");
|
|
10839
10890
|
var import_zod51 = require("zod");
|
|
10891
|
+
var import_firebase_admin12 = require("firebase-admin");
|
|
10840
10892
|
var import_zod52 = require("zod");
|
|
10841
10893
|
var import_zod53 = require("zod");
|
|
10842
|
-
var import_firebase_admin12 = require("firebase-admin");
|
|
10843
|
-
var import_zod54 = require("zod");
|
|
10844
10894
|
var import_firebase_admin13 = require("firebase-admin");
|
|
10895
|
+
var import_zod54 = require("zod");
|
|
10845
10896
|
var import_zod55 = require("zod");
|
|
10846
10897
|
var import_zod56 = require("zod");
|
|
10898
|
+
var import_firebase_admin14 = require("firebase-admin");
|
|
10847
10899
|
var import_zod57 = require("zod");
|
|
10900
|
+
var import_firebase_admin15 = require("firebase-admin");
|
|
10848
10901
|
var import_zod58 = require("zod");
|
|
10849
10902
|
var import_zod59 = require("zod");
|
|
10903
|
+
var import_zod60 = require("zod");
|
|
10904
|
+
var import_zod61 = require("zod");
|
|
10905
|
+
var import_zod62 = require("zod");
|
|
10850
10906
|
var import_firestore4 = require("firebase-admin/firestore");
|
|
10851
10907
|
var RULE_NEGATIVES = {
|
|
10852
10908
|
allowFaces: "no people, no faces, no hands",
|
|
@@ -11088,7 +11144,7 @@ var brandBriefWriterContract = MartinContractSchema.parse(
|
|
|
11088
11144
|
rawContract
|
|
11089
11145
|
);
|
|
11090
11146
|
async function save(input) {
|
|
11091
|
-
const { db, tenantId, brandId, plan } = input;
|
|
11147
|
+
const { db, tenantId, brandId, plan, actor } = input;
|
|
11092
11148
|
const brandRef = db.collection("tenants").doc(tenantId).collection("marketing_config").doc(brandId);
|
|
11093
11149
|
const brandSnap = await brandRef.get();
|
|
11094
11150
|
if (!brandSnap.exists) {
|
|
@@ -11098,7 +11154,11 @@ async function save(input) {
|
|
|
11098
11154
|
const planWithMeta = {
|
|
11099
11155
|
...planSinBlogStrategy,
|
|
11100
11156
|
fechaCreacion: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11101
|
-
|
|
11157
|
+
// §9.2 PLAN_C2 — actor real desde ctx.user (NO 'mcp-cowork' hardcoded).
|
|
11158
|
+
creadoPorId: actor.uid,
|
|
11159
|
+
creadoPorNombre: actor.nombre,
|
|
11160
|
+
creadoPorClient: actor.clientType,
|
|
11161
|
+
creadoPorClientMetadata: actor.clientMetadata ?? null
|
|
11102
11162
|
};
|
|
11103
11163
|
await brandRef.set({
|
|
11104
11164
|
plan: planWithMeta,
|
|
@@ -11177,7 +11237,13 @@ var ParamsSchema2 = import_zod36.z.object({
|
|
|
11177
11237
|
brandId: import_zod36.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
11178
11238
|
plan: import_zod36.z.record(import_zod36.z.string(), import_zod36.z.unknown()).describe(
|
|
11179
11239
|
"Full marketing plan object generated by the LLM. May include a blogStrategy field \u2014 if present, it is extracted and stored at brand level (not nested inside plan)."
|
|
11180
|
-
)
|
|
11240
|
+
),
|
|
11241
|
+
actor: import_zod36.z.object({
|
|
11242
|
+
uid: import_zod36.z.string().min(1).describe('User id who is saving the plan. Example: "your-user-uid".'),
|
|
11243
|
+
nombre: import_zod36.z.string().min(1).describe('Human-readable user name. Example: "Daniel Gonz\xE1lez".'),
|
|
11244
|
+
clientType: import_zod36.z.string().min(1).describe('Client surface origin. Example: one of "mcp_client" | "martin" | "web" | "admin".'),
|
|
11245
|
+
clientMetadata: import_zod36.z.record(import_zod36.z.string(), import_zod36.z.unknown()).nullable().optional().describe("Optional metadata about the client.")
|
|
11246
|
+
}).describe("Actor (user) saving the plan \u2014 extracted by the server.tool from ctx.user (\xA79.2).")
|
|
11181
11247
|
});
|
|
11182
11248
|
var OutputSchema2 = import_zod36.z.discriminatedUnion("ok", [
|
|
11183
11249
|
import_zod36.z.object({
|
|
@@ -13110,6 +13176,255 @@ var rawContract15 = {
|
|
|
13110
13176
|
var tenantContextSetterContract = MartinContractSchema.parse(
|
|
13111
13177
|
rawContract15
|
|
13112
13178
|
);
|
|
13179
|
+
async function photoDescarter(input) {
|
|
13180
|
+
const { db, tenantId, fotoId, actor } = input;
|
|
13181
|
+
const ref = db.collection("tenants").doc(tenantId).collection("marketing_fotos").doc(fotoId);
|
|
13182
|
+
const snap = await ref.get();
|
|
13183
|
+
if (!snap.exists) {
|
|
13184
|
+
return { ok: false, code: "FOTO_NOT_FOUND" };
|
|
13185
|
+
}
|
|
13186
|
+
const data = snap.data();
|
|
13187
|
+
const estadoActual = typeof data.estado === "string" ? data.estado : "";
|
|
13188
|
+
if (!validarTransicionFoto(estadoActual, ESTADO_FOTO.DESCARTADA)) {
|
|
13189
|
+
return {
|
|
13190
|
+
ok: false,
|
|
13191
|
+
code: "INVALID_STATE_TRANSITION",
|
|
13192
|
+
estadoActual
|
|
13193
|
+
};
|
|
13194
|
+
}
|
|
13195
|
+
await ref.update({
|
|
13196
|
+
estado: ESTADO_FOTO.DESCARTADA,
|
|
13197
|
+
descartadaAt: import_firebase_admin11.firestore.FieldValue.serverTimestamp(),
|
|
13198
|
+
descartadaPorId: actor.uid,
|
|
13199
|
+
descartadaPorNombre: actor.nombre,
|
|
13200
|
+
descartadaPorClient: actor.clientType,
|
|
13201
|
+
descartadaPorClientMetadata: actor.clientMetadata ?? null
|
|
13202
|
+
});
|
|
13203
|
+
return {
|
|
13204
|
+
ok: true,
|
|
13205
|
+
fotoId,
|
|
13206
|
+
estadoAnterior: estadoActual,
|
|
13207
|
+
nuevoEstado: ESTADO_FOTO.DESCARTADA
|
|
13208
|
+
};
|
|
13209
|
+
}
|
|
13210
|
+
var ParamsSchema16 = import_zod51.z.object({
|
|
13211
|
+
tenantId: import_zod51.z.string().min(1).describe('Tenant identifier (the business account). Example: "your-tenant-id".'),
|
|
13212
|
+
fotoId: import_zod51.z.string().min(1).describe('Photo identifier. Example: "your-tenant_your-brand_1234567890_abcdef".'),
|
|
13213
|
+
actor: import_zod51.z.object({
|
|
13214
|
+
uid: import_zod51.z.string().min(1).describe('User id who is discarding. Example: "your-user-uid".'),
|
|
13215
|
+
nombre: import_zod51.z.string().min(1).describe('Human-readable user name. Example: "Daniel Gonz\xE1lez".'),
|
|
13216
|
+
clientType: import_zod51.z.string().min(1).describe('Client surface origin. Example: one of "mcp_client" | "martin" | "web" | "admin".'),
|
|
13217
|
+
clientMetadata: import_zod51.z.record(import_zod51.z.string(), import_zod51.z.unknown()).nullable().optional().describe("Optional metadata about the client.")
|
|
13218
|
+
}).describe("Actor (user) performing the discard \u2014 extracted by the server.tool from ctx.user.")
|
|
13219
|
+
});
|
|
13220
|
+
var SuccessSchema7 = import_zod51.z.object({
|
|
13221
|
+
ok: import_zod51.z.literal(true),
|
|
13222
|
+
fotoId: import_zod51.z.string(),
|
|
13223
|
+
estadoAnterior: import_zod51.z.string(),
|
|
13224
|
+
nuevoEstado: import_zod51.z.literal("descartada")
|
|
13225
|
+
});
|
|
13226
|
+
var FailureSchema7 = import_zod51.z.object({
|
|
13227
|
+
ok: import_zod51.z.literal(false),
|
|
13228
|
+
code: import_zod51.z.enum(["FOTO_NOT_FOUND", "INVALID_STATE_TRANSITION"]),
|
|
13229
|
+
estadoActual: import_zod51.z.string().optional()
|
|
13230
|
+
});
|
|
13231
|
+
var OutputSchema16 = import_zod51.z.discriminatedUnion("ok", [SuccessSchema7, FailureSchema7]);
|
|
13232
|
+
var rawContract16 = {
|
|
13233
|
+
name: "discard_photo",
|
|
13234
|
+
description: "Mark a marketing photo as discarded. Validates state transition (nueva|editada|error \u2192 descartada). Use when the photo is no longer needed or has issues. Requires confirmation.",
|
|
13235
|
+
paramsSchema: ParamsSchema16,
|
|
13236
|
+
outputSchema: OutputSchema16,
|
|
13237
|
+
requiresConfirmation: true,
|
|
13238
|
+
destructive: false,
|
|
13239
|
+
affectsPublication: false,
|
|
13240
|
+
affectsExternal: false,
|
|
13241
|
+
martinConfirmationTemplate: (input, locale) => {
|
|
13242
|
+
return locale === "en" ? `Discard photo "${input.fotoId}"? The photo will be marked as descartada and won't be used in content.` : `\xBFDescartar la foto "${input.fotoId}"? La foto se marcar\xE1 como descartada y no se usar\xE1 en contenido.`;
|
|
13243
|
+
},
|
|
13244
|
+
martinSummaryTemplate: (input, output, locale) => {
|
|
13245
|
+
if (!output.ok) {
|
|
13246
|
+
if (output.code === "FOTO_NOT_FOUND") {
|
|
13247
|
+
return locale === "en" ? `Photo ${input.fotoId} not found.` : `No encontr\xE9 la foto ${input.fotoId}.`;
|
|
13248
|
+
}
|
|
13249
|
+
return locale === "en" ? `Cannot discard photo from state "${output.estadoActual ?? "unknown"}".` : `No se puede descartar la foto desde el estado "${output.estadoActual ?? "desconocido"}".`;
|
|
13250
|
+
}
|
|
13251
|
+
return locale === "en" ? `Photo ${output.fotoId} discarded (was ${output.estadoAnterior}).` : `Foto ${output.fotoId} descartada (estaba en ${output.estadoAnterior}).`;
|
|
13252
|
+
},
|
|
13253
|
+
auditAction: "marketing.foto.descartar",
|
|
13254
|
+
extractTargetPath: (input) => `tenants/${input.tenantId}/marketing_fotos/${input.fotoId}`,
|
|
13255
|
+
extractChanges: (_input, output) => ({
|
|
13256
|
+
before: output.ok ? { estado: output.estadoAnterior } : null,
|
|
13257
|
+
after: output.ok ? { estado: output.nuevoEstado } : null
|
|
13258
|
+
}),
|
|
13259
|
+
quotasConsumed: [],
|
|
13260
|
+
permissionScope: "module",
|
|
13261
|
+
permissionKey: "marketing",
|
|
13262
|
+
permissionAction: "editar",
|
|
13263
|
+
sideEffects: ["writes_firestore"]
|
|
13264
|
+
};
|
|
13265
|
+
var photoDescarterContract = MartinContractSchema.parse(
|
|
13266
|
+
rawContract16
|
|
13267
|
+
);
|
|
13268
|
+
async function photoEditadaMarker(input) {
|
|
13269
|
+
const { db, tenantId, fotoId, actor } = input;
|
|
13270
|
+
const ref = db.collection("tenants").doc(tenantId).collection("marketing_fotos").doc(fotoId);
|
|
13271
|
+
const snap = await ref.get();
|
|
13272
|
+
if (!snap.exists) {
|
|
13273
|
+
return { ok: false, code: "FOTO_NOT_FOUND" };
|
|
13274
|
+
}
|
|
13275
|
+
const data = snap.data();
|
|
13276
|
+
const estadoActual = typeof data.estado === "string" ? data.estado : "";
|
|
13277
|
+
const archivoOriginal = typeof data.archivoOriginal === "string" ? data.archivoOriginal : null;
|
|
13278
|
+
if (!validarTransicionFoto(estadoActual, ESTADO_FOTO.EDITADA)) {
|
|
13279
|
+
return {
|
|
13280
|
+
ok: false,
|
|
13281
|
+
code: "INVALID_STATE_TRANSITION",
|
|
13282
|
+
estadoActual
|
|
13283
|
+
};
|
|
13284
|
+
}
|
|
13285
|
+
if (!archivoOriginal) {
|
|
13286
|
+
return { ok: false, code: "FOTO_SIN_ARCHIVO_ORIGINAL", estadoActual };
|
|
13287
|
+
}
|
|
13288
|
+
await ref.update({
|
|
13289
|
+
estado: ESTADO_FOTO.EDITADA,
|
|
13290
|
+
archivoEditado: archivoOriginal,
|
|
13291
|
+
fechaEdicion: import_firebase_admin12.firestore.FieldValue.serverTimestamp(),
|
|
13292
|
+
marcadaEditadaPorId: actor.uid,
|
|
13293
|
+
marcadaEditadaPorNombre: actor.nombre,
|
|
13294
|
+
marcadaEditadaPorClient: actor.clientType,
|
|
13295
|
+
marcadaEditadaPorClientMetadata: actor.clientMetadata ?? null
|
|
13296
|
+
});
|
|
13297
|
+
return {
|
|
13298
|
+
ok: true,
|
|
13299
|
+
fotoId,
|
|
13300
|
+
estadoAnterior: estadoActual,
|
|
13301
|
+
nuevoEstado: ESTADO_FOTO.EDITADA,
|
|
13302
|
+
archivoEditado: archivoOriginal
|
|
13303
|
+
};
|
|
13304
|
+
}
|
|
13305
|
+
var ParamsSchema17 = import_zod52.z.object({
|
|
13306
|
+
tenantId: import_zod52.z.string().min(1).describe('Tenant identifier. Example: "your-tenant-id".'),
|
|
13307
|
+
fotoId: import_zod52.z.string().min(1).describe('Photo identifier. Example: "your-tenant_your-brand_1234567890_abcdef".'),
|
|
13308
|
+
actor: import_zod52.z.object({
|
|
13309
|
+
uid: import_zod52.z.string().min(1).describe('User id who marks the photo. Example: "your-user-uid".'),
|
|
13310
|
+
nombre: import_zod52.z.string().min(1).describe('Human-readable user name. Example: "Daniel Gonz\xE1lez".'),
|
|
13311
|
+
clientType: import_zod52.z.string().min(1).describe('Client surface origin. Example: one of "mcp_client" | "martin" | "web" | "admin".'),
|
|
13312
|
+
clientMetadata: import_zod52.z.record(import_zod52.z.string(), import_zod52.z.unknown()).nullable().optional().describe("Optional metadata about the client.")
|
|
13313
|
+
}).describe("Actor (user) marking the photo \u2014 extracted by the server.tool from ctx.user.")
|
|
13314
|
+
});
|
|
13315
|
+
var SuccessSchema8 = import_zod52.z.object({
|
|
13316
|
+
ok: import_zod52.z.literal(true),
|
|
13317
|
+
fotoId: import_zod52.z.string(),
|
|
13318
|
+
estadoAnterior: import_zod52.z.string(),
|
|
13319
|
+
nuevoEstado: import_zod52.z.literal("editada"),
|
|
13320
|
+
archivoEditado: import_zod52.z.string()
|
|
13321
|
+
});
|
|
13322
|
+
var FailureSchema8 = import_zod52.z.object({
|
|
13323
|
+
ok: import_zod52.z.literal(false),
|
|
13324
|
+
code: import_zod52.z.enum(["FOTO_NOT_FOUND", "INVALID_STATE_TRANSITION", "FOTO_SIN_ARCHIVO_ORIGINAL"]),
|
|
13325
|
+
estadoActual: import_zod52.z.string().optional()
|
|
13326
|
+
});
|
|
13327
|
+
var OutputSchema17 = import_zod52.z.discriminatedUnion("ok", [SuccessSchema8, FailureSchema8]);
|
|
13328
|
+
var rawContract17 = {
|
|
13329
|
+
name: "use_photo_as_is",
|
|
13330
|
+
description: "Mark a photo as editada using the original file (no Gemini edit). Validates state transition (procesando \u2192 editada). Use when the original photo is already good enough for publication and no editing is required. archivoEditado is set to the original URL.",
|
|
13331
|
+
paramsSchema: ParamsSchema17,
|
|
13332
|
+
outputSchema: OutputSchema17,
|
|
13333
|
+
requiresConfirmation: false,
|
|
13334
|
+
destructive: false,
|
|
13335
|
+
affectsPublication: false,
|
|
13336
|
+
affectsExternal: false,
|
|
13337
|
+
martinSummaryTemplate: (input, output, locale) => {
|
|
13338
|
+
if (!output.ok) {
|
|
13339
|
+
if (output.code === "FOTO_NOT_FOUND") {
|
|
13340
|
+
return locale === "en" ? `Photo ${input.fotoId} not found.` : `No encontr\xE9 la foto ${input.fotoId}.`;
|
|
13341
|
+
}
|
|
13342
|
+
if (output.code === "FOTO_SIN_ARCHIVO_ORIGINAL") {
|
|
13343
|
+
return locale === "en" ? `Photo ${input.fotoId} has no original file to use.` : `La foto ${input.fotoId} no tiene archivo original que usar.`;
|
|
13344
|
+
}
|
|
13345
|
+
return locale === "en" ? `Cannot mark photo as edited from state "${output.estadoActual ?? "unknown"}".` : `No se puede marcar como editada desde el estado "${output.estadoActual ?? "desconocido"}".`;
|
|
13346
|
+
}
|
|
13347
|
+
return locale === "en" ? `Photo ${output.fotoId} marked as editada using the original (was ${output.estadoAnterior}).` : `Foto ${output.fotoId} marcada como editada usando la original (estaba en ${output.estadoAnterior}).`;
|
|
13348
|
+
},
|
|
13349
|
+
auditAction: "marketing.foto.usar_tal_cual",
|
|
13350
|
+
extractTargetPath: (input) => `tenants/${input.tenantId}/marketing_fotos/${input.fotoId}`,
|
|
13351
|
+
extractChanges: (_input, output) => ({
|
|
13352
|
+
before: output.ok ? { estado: output.estadoAnterior } : null,
|
|
13353
|
+
after: output.ok ? { estado: output.nuevoEstado, archivoEditado: output.archivoEditado } : null
|
|
13354
|
+
}),
|
|
13355
|
+
quotasConsumed: [],
|
|
13356
|
+
permissionScope: "module",
|
|
13357
|
+
permissionKey: "marketing",
|
|
13358
|
+
permissionAction: "editar",
|
|
13359
|
+
sideEffects: ["writes_firestore"]
|
|
13360
|
+
};
|
|
13361
|
+
var photoEditadaMarkerContract = MartinContractSchema.parse(
|
|
13362
|
+
rawContract17
|
|
13363
|
+
);
|
|
13364
|
+
async function brandContextSetter(input) {
|
|
13365
|
+
const { db, tenantId, brandId } = input;
|
|
13366
|
+
const brandRef = db.collection("tenants").doc(tenantId).collection("marketing_config").doc(brandId);
|
|
13367
|
+
const brandSnap = await brandRef.get();
|
|
13368
|
+
if (!brandSnap.exists) {
|
|
13369
|
+
return {
|
|
13370
|
+
ok: false,
|
|
13371
|
+
code: "BRAND_NOT_FOUND_IN_TENANT",
|
|
13372
|
+
tenantId,
|
|
13373
|
+
brandId
|
|
13374
|
+
};
|
|
13375
|
+
}
|
|
13376
|
+
const data = brandSnap.data();
|
|
13377
|
+
return {
|
|
13378
|
+
ok: true,
|
|
13379
|
+
tenantId,
|
|
13380
|
+
brandId,
|
|
13381
|
+
brandNombre: typeof data.nombre === "string" ? data.nombre : null,
|
|
13382
|
+
brandDominio: typeof data.dominio === "string" ? data.dominio : null
|
|
13383
|
+
};
|
|
13384
|
+
}
|
|
13385
|
+
var ParamsSchema18 = import_zod53.z.object({
|
|
13386
|
+
tenantId: import_zod53.z.string().min(1).describe(`Tenant identifier (always the user's own tenant). Example: "your-tenant-id".`),
|
|
13387
|
+
brandId: import_zod53.z.string().min(1).describe(`Target brand identifier within the user's tenant. Example: "your-brand-id".`)
|
|
13388
|
+
});
|
|
13389
|
+
var SuccessSchema9 = import_zod53.z.object({
|
|
13390
|
+
ok: import_zod53.z.literal(true),
|
|
13391
|
+
tenantId: import_zod53.z.string(),
|
|
13392
|
+
brandId: import_zod53.z.string(),
|
|
13393
|
+
brandNombre: import_zod53.z.string().nullable(),
|
|
13394
|
+
brandDominio: import_zod53.z.string().nullable()
|
|
13395
|
+
});
|
|
13396
|
+
var FailureSchema9 = import_zod53.z.object({
|
|
13397
|
+
ok: import_zod53.z.literal(false),
|
|
13398
|
+
code: import_zod53.z.literal("BRAND_NOT_FOUND_IN_TENANT"),
|
|
13399
|
+
tenantId: import_zod53.z.string(),
|
|
13400
|
+
brandId: import_zod53.z.string()
|
|
13401
|
+
});
|
|
13402
|
+
var OutputSchema18 = import_zod53.z.discriminatedUnion("ok", [SuccessSchema9, FailureSchema9]);
|
|
13403
|
+
var rawContract18 = {
|
|
13404
|
+
name: "select_brand",
|
|
13405
|
+
description: "Switch the active brand within the user's tenant. Validates that the target brand exists in the tenant. Helper does not mutate Firestore \u2014 caller (MCP server) updates session local state after success. Audit log writes who switched brand (forensics for agency mode). Use when the user has multiple brands and wants to work with a different one without passing brandId on every tool call.",
|
|
13406
|
+
paramsSchema: ParamsSchema18,
|
|
13407
|
+
outputSchema: OutputSchema18,
|
|
13408
|
+
requiresConfirmation: false,
|
|
13409
|
+
destructive: false,
|
|
13410
|
+
affectsPublication: false,
|
|
13411
|
+
affectsExternal: false,
|
|
13412
|
+
martinSummaryTemplate: (input, output, locale) => {
|
|
13413
|
+
if (!output.ok) {
|
|
13414
|
+
return locale === "en" ? `Brand "${input.brandId}" does not exist in your tenant.` : `La brand "${input.brandId}" no existe en tu tenant.`;
|
|
13415
|
+
}
|
|
13416
|
+
return locale === "en" ? `Active brand set to ${output.brandNombre ?? output.brandId}.` : `Brand activa establecida: ${output.brandNombre ?? output.brandId}.`;
|
|
13417
|
+
},
|
|
13418
|
+
auditAction: "self.brand_context.cambiar",
|
|
13419
|
+
quotasConsumed: [],
|
|
13420
|
+
// scope='self' — operación sobre la sesión del propio user. NO requiere
|
|
13421
|
+
// permissionKey/permissionAction (superRefine de MartinContractSchema).
|
|
13422
|
+
permissionScope: "self",
|
|
13423
|
+
sideEffects: ["reads_firestore"]
|
|
13424
|
+
};
|
|
13425
|
+
var brandContextSetterContract = MartinContractSchema.parse(
|
|
13426
|
+
rawContract18
|
|
13427
|
+
);
|
|
13113
13428
|
var PLATAFORMA_A_FORMATO = {
|
|
13114
13429
|
gbp: "gbp_4_3",
|
|
13115
13430
|
shopify_blog: "blog_3_2",
|
|
@@ -13151,12 +13466,12 @@ async function photoAssigner(input) {
|
|
|
13151
13466
|
mediaVariante: {
|
|
13152
13467
|
url: varianteUrl,
|
|
13153
13468
|
formato,
|
|
13154
|
-
linkedAt:
|
|
13469
|
+
linkedAt: import_firebase_admin13.firestore.FieldValue.serverTimestamp(),
|
|
13155
13470
|
permalink: null,
|
|
13156
13471
|
// lo llenara publishToInstagram/etc en el futuro
|
|
13157
13472
|
mediaExternalId: null
|
|
13158
13473
|
},
|
|
13159
|
-
editadoAt:
|
|
13474
|
+
editadoAt: import_firebase_admin13.firestore.FieldValue.serverTimestamp()
|
|
13160
13475
|
};
|
|
13161
13476
|
let slotResolved = null;
|
|
13162
13477
|
if (calendarioItemRef) {
|
|
@@ -13206,7 +13521,7 @@ async function photoAssigner(input) {
|
|
|
13206
13521
|
tx.update(contenidoRefDoc, contenidoUpdatePayload);
|
|
13207
13522
|
tx.update(calDocRef, {
|
|
13208
13523
|
semanas,
|
|
13209
|
-
updatedAt:
|
|
13524
|
+
updatedAt: import_firebase_admin13.firestore.FieldValue.serverTimestamp()
|
|
13210
13525
|
});
|
|
13211
13526
|
});
|
|
13212
13527
|
} else {
|
|
@@ -13221,35 +13536,35 @@ async function photoAssigner(input) {
|
|
|
13221
13536
|
formato
|
|
13222
13537
|
};
|
|
13223
13538
|
}
|
|
13224
|
-
var
|
|
13225
|
-
tenantId:
|
|
13226
|
-
brandId:
|
|
13227
|
-
contenidoRef:
|
|
13228
|
-
fotoId:
|
|
13229
|
-
calendarioItemRef:
|
|
13539
|
+
var ParamsSchema19 = import_zod54.z.object({
|
|
13540
|
+
tenantId: import_zod54.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
13541
|
+
brandId: import_zod54.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
13542
|
+
contenidoRef: import_zod54.z.string().min(1).describe("Document ID in marketing_contenido (the post receiving the photo)."),
|
|
13543
|
+
fotoId: import_zod54.z.string().min(1).describe("Photo ID in marketing_fotos. Photo must be in state='editada'."),
|
|
13544
|
+
calendarioItemRef: import_zod54.z.string().regex(/^semana:\d+:slot:\d+$/).optional().describe(
|
|
13230
13545
|
'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.'
|
|
13231
13546
|
)
|
|
13232
13547
|
});
|
|
13233
|
-
var
|
|
13234
|
-
|
|
13235
|
-
ok:
|
|
13236
|
-
fotoId:
|
|
13237
|
-
contenidoRef:
|
|
13238
|
-
plataforma:
|
|
13239
|
-
varianteUrl:
|
|
13240
|
-
formato:
|
|
13548
|
+
var OutputSchema19 = import_zod54.z.discriminatedUnion("ok", [
|
|
13549
|
+
import_zod54.z.object({
|
|
13550
|
+
ok: import_zod54.z.literal(true),
|
|
13551
|
+
fotoId: import_zod54.z.string(),
|
|
13552
|
+
contenidoRef: import_zod54.z.string(),
|
|
13553
|
+
plataforma: import_zod54.z.string(),
|
|
13554
|
+
varianteUrl: import_zod54.z.string().nullable(),
|
|
13555
|
+
formato: import_zod54.z.string()
|
|
13241
13556
|
}),
|
|
13242
|
-
|
|
13243
|
-
ok:
|
|
13244
|
-
error:
|
|
13245
|
-
code:
|
|
13557
|
+
import_zod54.z.object({
|
|
13558
|
+
ok: import_zod54.z.literal(false),
|
|
13559
|
+
error: import_zod54.z.string(),
|
|
13560
|
+
code: import_zod54.z.enum(["CONTENT_NOT_FOUND", "CONTENT_TENANT_MISMATCH", "PHOTO_NOT_FOUND", "PHOTO_NOT_READY"]).optional()
|
|
13246
13561
|
})
|
|
13247
13562
|
]);
|
|
13248
|
-
var
|
|
13563
|
+
var rawContract19 = {
|
|
13249
13564
|
name: "assign_photo_to_content",
|
|
13250
13565
|
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".',
|
|
13251
|
-
paramsSchema:
|
|
13252
|
-
outputSchema:
|
|
13566
|
+
paramsSchema: ParamsSchema19,
|
|
13567
|
+
outputSchema: OutputSchema19,
|
|
13253
13568
|
// Cambia la foto vinculada al contenido — reversible (volver a llamar con
|
|
13254
13569
|
// otra fotoId), no publica nada externo. La foto editada queda intacta;
|
|
13255
13570
|
// solo se actualiza la referencia.
|
|
@@ -13291,7 +13606,7 @@ var rawContract16 = {
|
|
|
13291
13606
|
sideEffects: ["writes_firestore", "updates_calendar_slot"]
|
|
13292
13607
|
};
|
|
13293
13608
|
var photoAssignerContract = MartinContractSchema.parse(
|
|
13294
|
-
|
|
13609
|
+
rawContract19
|
|
13295
13610
|
);
|
|
13296
13611
|
function findPageByHeuristic(pages, pattern) {
|
|
13297
13612
|
return pages.find((p) => pattern.test(p.title || "")) || pages.find((p) => pattern.test(p.handle || "")) || null;
|
|
@@ -13480,27 +13795,27 @@ var BRAND_BRIEF_SCHEMA_HINT = {
|
|
|
13480
13795
|
escenas: [{ id: string, nombre: string, promptHint: string }]
|
|
13481
13796
|
}`
|
|
13482
13797
|
};
|
|
13483
|
-
var
|
|
13484
|
-
tenantId:
|
|
13485
|
-
brandId:
|
|
13798
|
+
var ParamsSchema20 = import_zod55.z.object({
|
|
13799
|
+
tenantId: import_zod55.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
13800
|
+
brandId: import_zod55.z.string().min(1).describe("Brand identifier within the tenant.")
|
|
13486
13801
|
});
|
|
13487
|
-
var
|
|
13488
|
-
|
|
13489
|
-
ok:
|
|
13802
|
+
var OutputSchema20 = import_zod55.z.discriminatedUnion("ok", [
|
|
13803
|
+
import_zod55.z.object({
|
|
13804
|
+
ok: import_zod55.z.literal(true),
|
|
13490
13805
|
/** Payload con instrucción + datos del negocio para que Claude genere el brief. */
|
|
13491
|
-
payload:
|
|
13806
|
+
payload: import_zod55.z.record(import_zod55.z.string(), import_zod55.z.unknown())
|
|
13492
13807
|
}),
|
|
13493
|
-
|
|
13494
|
-
ok:
|
|
13495
|
-
error:
|
|
13496
|
-
code:
|
|
13808
|
+
import_zod55.z.object({
|
|
13809
|
+
ok: import_zod55.z.literal(false),
|
|
13810
|
+
error: import_zod55.z.string(),
|
|
13811
|
+
code: import_zod55.z.enum(["BRAND_NOT_FOUND"]).optional()
|
|
13497
13812
|
})
|
|
13498
13813
|
]);
|
|
13499
|
-
var
|
|
13814
|
+
var rawContract20 = {
|
|
13500
13815
|
name: "generate_brand_brief",
|
|
13501
13816
|
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.",
|
|
13502
|
-
paramsSchema:
|
|
13503
|
-
outputSchema:
|
|
13817
|
+
paramsSchema: ParamsSchema20,
|
|
13818
|
+
outputSchema: OutputSchema20,
|
|
13504
13819
|
// Lectura pura, sin side effects de escritura ni publicación.
|
|
13505
13820
|
requiresConfirmation: false,
|
|
13506
13821
|
destructive: false,
|
|
@@ -13525,7 +13840,7 @@ var rawContract17 = {
|
|
|
13525
13840
|
sideEffects: ["reads_firestore"]
|
|
13526
13841
|
};
|
|
13527
13842
|
var brandBriefBuilderContract = MartinContractSchema.parse(
|
|
13528
|
-
|
|
13843
|
+
rawContract20
|
|
13529
13844
|
);
|
|
13530
13845
|
async function resolveLastImportId(db, tenantId, brandId) {
|
|
13531
13846
|
const configSnap = await db.collection("tenants").doc(tenantId).collection("marketing_config").doc(brandId).get();
|
|
@@ -13713,28 +14028,28 @@ var BLOG_STRATEGY_REGLAS = [
|
|
|
13713
14028
|
"blogId, handle, title, ultimoPostFecha, ultimoPostKeyword, totalArticulos \u2014 copiar del shopifyBlogs (datos reales del import)",
|
|
13714
14029
|
"defaultBlogId y defaultBlogHandle \u2014 usar el primer blog de la lista"
|
|
13715
14030
|
];
|
|
13716
|
-
var
|
|
13717
|
-
tenantId:
|
|
13718
|
-
brandId:
|
|
14031
|
+
var ParamsSchema21 = import_zod56.z.object({
|
|
14032
|
+
tenantId: import_zod56.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
14033
|
+
brandId: import_zod56.z.string().min(1).describe("Brand identifier within the tenant.")
|
|
13719
14034
|
});
|
|
13720
|
-
var
|
|
13721
|
-
|
|
13722
|
-
ok:
|
|
13723
|
-
payload:
|
|
14035
|
+
var OutputSchema21 = import_zod56.z.discriminatedUnion("ok", [
|
|
14036
|
+
import_zod56.z.object({
|
|
14037
|
+
ok: import_zod56.z.literal(true),
|
|
14038
|
+
payload: import_zod56.z.record(import_zod56.z.string(), import_zod56.z.unknown()).describe(
|
|
13724
14039
|
"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."
|
|
13725
14040
|
)
|
|
13726
14041
|
}),
|
|
13727
|
-
|
|
13728
|
-
ok:
|
|
13729
|
-
error:
|
|
13730
|
-
code:
|
|
14042
|
+
import_zod56.z.object({
|
|
14043
|
+
ok: import_zod56.z.literal(false),
|
|
14044
|
+
error: import_zod56.z.string(),
|
|
14045
|
+
code: import_zod56.z.enum(["BRAND_NOT_FOUND", "SEO_SNAPSHOT_MISSING"]).optional()
|
|
13731
14046
|
})
|
|
13732
14047
|
]);
|
|
13733
|
-
var
|
|
14048
|
+
var rawContract21 = {
|
|
13734
14049
|
name: "generate_marketing_plan",
|
|
13735
14050
|
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).",
|
|
13736
|
-
paramsSchema:
|
|
13737
|
-
outputSchema:
|
|
14051
|
+
paramsSchema: ParamsSchema21,
|
|
14052
|
+
outputSchema: OutputSchema21,
|
|
13738
14053
|
// Lectura pura, no muta nada.
|
|
13739
14054
|
requiresConfirmation: false,
|
|
13740
14055
|
destructive: false,
|
|
@@ -13759,7 +14074,7 @@ var rawContract18 = {
|
|
|
13759
14074
|
sideEffects: ["reads_firestore"]
|
|
13760
14075
|
};
|
|
13761
14076
|
var marketingPlanBuilderContract = MartinContractSchema.parse(
|
|
13762
|
-
|
|
14077
|
+
rawContract21
|
|
13763
14078
|
);
|
|
13764
14079
|
function buildGenId() {
|
|
13765
14080
|
return `mkt_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -13776,6 +14091,7 @@ async function contenidoWriter(input) {
|
|
|
13776
14091
|
fotoId,
|
|
13777
14092
|
datos,
|
|
13778
14093
|
calendarioItemRef,
|
|
14094
|
+
actor,
|
|
13779
14095
|
linkPipeline
|
|
13780
14096
|
} = input;
|
|
13781
14097
|
const configSnap = await db.collection("tenants").doc(tenantId).collection("marketing_config").doc(brandId).get();
|
|
@@ -13819,7 +14135,7 @@ async function contenidoWriter(input) {
|
|
|
13819
14135
|
await db.doc(`tenants/${tenantId}/marketing_contenido/${existente.id}`).update({
|
|
13820
14136
|
estado: "descartado",
|
|
13821
14137
|
rechazadoMotivo: "Reemplazado por contenido nuevo para el mismo slot",
|
|
13822
|
-
rechazadoAt:
|
|
14138
|
+
rechazadoAt: import_firebase_admin14.firestore.FieldValue.serverTimestamp()
|
|
13823
14139
|
});
|
|
13824
14140
|
descartados++;
|
|
13825
14141
|
}
|
|
@@ -13878,8 +14194,13 @@ async function contenidoWriter(input) {
|
|
|
13878
14194
|
mediaUrl: null,
|
|
13879
14195
|
calendarioItemRef: calendarioItemRef ?? null,
|
|
13880
14196
|
datos,
|
|
13881
|
-
creadoAt:
|
|
13882
|
-
|
|
14197
|
+
creadoAt: import_firebase_admin14.firestore.FieldValue.serverTimestamp(),
|
|
14198
|
+
// Decisión canónica §9.2 PLAN_C2: actor real desde ctx.user (NO
|
|
14199
|
+
// 'mcp-cowork' hardcoded). Reportado en smoke 1.0.86 final.
|
|
14200
|
+
creadoPorId: actor.uid,
|
|
14201
|
+
creadoPorNombre: actor.nombre,
|
|
14202
|
+
creadoPorClient: actor.clientType,
|
|
14203
|
+
creadoPorClientMetadata: actor.clientMetadata ?? null,
|
|
13883
14204
|
origen: "ai_assisted"
|
|
13884
14205
|
});
|
|
13885
14206
|
const contenidoRef = db.doc(`tenants/${tenantId}/marketing_contenido/${id}`);
|
|
@@ -13936,7 +14257,7 @@ async function contenidoWriter(input) {
|
|
|
13936
14257
|
tx.create(contenidoRef, contenido);
|
|
13937
14258
|
tx.update(calDocRef, {
|
|
13938
14259
|
semanas,
|
|
13939
|
-
updatedAt:
|
|
14260
|
+
updatedAt: import_firebase_admin14.firestore.FieldValue.serverTimestamp()
|
|
13940
14261
|
});
|
|
13941
14262
|
});
|
|
13942
14263
|
} else {
|
|
@@ -14000,54 +14321,60 @@ async function contenidoWriter(input) {
|
|
|
14000
14321
|
pipelineLinked
|
|
14001
14322
|
};
|
|
14002
14323
|
}
|
|
14003
|
-
var
|
|
14004
|
-
tenantId:
|
|
14005
|
-
brandId:
|
|
14006
|
-
plataforma:
|
|
14007
|
-
tipo:
|
|
14324
|
+
var ParamsSchema22 = import_zod57.z.object({
|
|
14325
|
+
tenantId: import_zod57.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
14326
|
+
brandId: import_zod57.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
14327
|
+
plataforma: import_zod57.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Target platform for the content."),
|
|
14328
|
+
tipo: import_zod57.z.string().optional().describe(
|
|
14008
14329
|
"Content type ('post', 'blog', 'carousel', 'reel', 'story', 'review_response'). Match to the platform."
|
|
14009
14330
|
),
|
|
14010
|
-
keyword:
|
|
14011
|
-
languageCode:
|
|
14331
|
+
keyword: import_zod57.z.string().optional().describe("Primary keyword for the content. OMIT if not applicable."),
|
|
14332
|
+
languageCode: import_zod57.z.string().optional().describe(
|
|
14012
14333
|
"Content language code (e.g. 'es', 'en'). For shopify_blog auto-injects to datos.languageCode if not present."
|
|
14013
14334
|
),
|
|
14014
|
-
fotoId:
|
|
14015
|
-
datos:
|
|
14335
|
+
fotoId: import_zod57.z.string().optional().describe("Linked photo ID. OMIT if no photo associated yet (use assign_photo_to_content later)."),
|
|
14336
|
+
datos: import_zod57.z.record(import_zod57.z.string(), import_zod57.z.unknown()).describe(
|
|
14016
14337
|
"Platform-specific content payload. Validated against Blog/GBP/IG/Review schemas. Use buildDatosBlog/GBP/IG/Review helpers to construct safely."
|
|
14017
14338
|
),
|
|
14018
|
-
calendarioItemRef:
|
|
14339
|
+
calendarioItemRef: import_zod57.z.string().optional().describe(
|
|
14019
14340
|
'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.'
|
|
14020
|
-
)
|
|
14341
|
+
),
|
|
14342
|
+
actor: import_zod57.z.object({
|
|
14343
|
+
uid: import_zod57.z.string().min(1).describe('User id who is creating the content. Example: "your-user-uid".'),
|
|
14344
|
+
nombre: import_zod57.z.string().min(1).describe('Human-readable user name. Example: "Daniel Gonz\xE1lez".'),
|
|
14345
|
+
clientType: import_zod57.z.string().min(1).describe('Client surface origin. Example: one of "mcp_client" | "martin" | "web" | "admin".'),
|
|
14346
|
+
clientMetadata: import_zod57.z.record(import_zod57.z.string(), import_zod57.z.unknown()).nullable().optional().describe("Optional metadata about the client (session id, device, etc.).")
|
|
14347
|
+
}).describe("Actor (user) creating the content \u2014 extracted by the server.tool from ctx.user (\xA79.2).")
|
|
14021
14348
|
});
|
|
14022
|
-
var PipelineLinkResultSchema =
|
|
14023
|
-
linked:
|
|
14024
|
-
paso:
|
|
14025
|
-
motivo:
|
|
14026
|
-
code:
|
|
14027
|
-
_instrucciones:
|
|
14349
|
+
var PipelineLinkResultSchema = import_zod57.z.object({
|
|
14350
|
+
linked: import_zod57.z.boolean(),
|
|
14351
|
+
paso: import_zod57.z.string().nullable(),
|
|
14352
|
+
motivo: import_zod57.z.string().optional(),
|
|
14353
|
+
code: import_zod57.z.string().optional(),
|
|
14354
|
+
_instrucciones: import_zod57.z.string().optional()
|
|
14028
14355
|
});
|
|
14029
|
-
var
|
|
14030
|
-
|
|
14031
|
-
ok:
|
|
14032
|
-
contenidoId:
|
|
14033
|
-
estado:
|
|
14034
|
-
plataforma:
|
|
14035
|
-
descartados:
|
|
14356
|
+
var OutputSchema22 = import_zod57.z.discriminatedUnion("ok", [
|
|
14357
|
+
import_zod57.z.object({
|
|
14358
|
+
ok: import_zod57.z.literal(true),
|
|
14359
|
+
contenidoId: import_zod57.z.string(),
|
|
14360
|
+
estado: import_zod57.z.string(),
|
|
14361
|
+
plataforma: import_zod57.z.enum(["gbp", "shopify_blog", "instagram", "review"]),
|
|
14362
|
+
descartados: import_zod57.z.number().int().nonnegative(),
|
|
14036
14363
|
pipelineLinked: PipelineLinkResultSchema
|
|
14037
14364
|
}),
|
|
14038
|
-
|
|
14039
|
-
ok:
|
|
14040
|
-
error:
|
|
14041
|
-
code:
|
|
14042
|
-
activosEstaSemana:
|
|
14043
|
-
limite:
|
|
14365
|
+
import_zod57.z.object({
|
|
14366
|
+
ok: import_zod57.z.literal(false),
|
|
14367
|
+
error: import_zod57.z.string(),
|
|
14368
|
+
code: import_zod57.z.enum(["CONTENT_FREQUENCY_LIMIT", "CONTENT_DATA_VALIDATION_FAILED"]).optional(),
|
|
14369
|
+
activosEstaSemana: import_zod57.z.number().int().optional(),
|
|
14370
|
+
limite: import_zod57.z.number().int().optional()
|
|
14044
14371
|
})
|
|
14045
14372
|
]);
|
|
14046
|
-
var
|
|
14373
|
+
var rawContract22 = {
|
|
14047
14374
|
name: "save_generated_content",
|
|
14048
14375
|
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.",
|
|
14049
|
-
paramsSchema:
|
|
14050
|
-
outputSchema:
|
|
14376
|
+
paramsSchema: ParamsSchema22,
|
|
14377
|
+
outputSchema: OutputSchema22,
|
|
14051
14378
|
// Crea borrador + descarta borrador previo del mismo slot. Reversible
|
|
14052
14379
|
// (volver a llamar con datos corregidos crea un nuevo borrador y descarta
|
|
14053
14380
|
// el actual). NO publica nada externo — la CF de publish se encarga
|
|
@@ -14092,7 +14419,7 @@ var rawContract19 = {
|
|
|
14092
14419
|
sideEffects: ["writes_firestore", "updates_calendar_slot"]
|
|
14093
14420
|
};
|
|
14094
14421
|
var contenidoWriterContract = MartinContractSchema.parse(
|
|
14095
|
-
|
|
14422
|
+
rawContract22
|
|
14096
14423
|
);
|
|
14097
14424
|
function fmtDate(d) {
|
|
14098
14425
|
const y = d.getFullYear();
|
|
@@ -14127,7 +14454,7 @@ function generarEstructuraCalendario(mes) {
|
|
|
14127
14454
|
return semanas;
|
|
14128
14455
|
}
|
|
14129
14456
|
async function weeklyContentBuilder(input) {
|
|
14130
|
-
const { db, tenantId, brandId, semana, modo } = input;
|
|
14457
|
+
const { db, tenantId, brandId, semana, modo, actor } = input;
|
|
14131
14458
|
const targetModo = modo ?? "planificar";
|
|
14132
14459
|
const now2 = /* @__PURE__ */ new Date();
|
|
14133
14460
|
const targetMes = now2.toISOString().slice(0, 7);
|
|
@@ -14148,8 +14475,12 @@ async function weeklyContentBuilder(input) {
|
|
|
14148
14475
|
brandId,
|
|
14149
14476
|
mes: targetMes,
|
|
14150
14477
|
semanas,
|
|
14151
|
-
creadoAt:
|
|
14152
|
-
|
|
14478
|
+
creadoAt: import_firebase_admin15.firestore.FieldValue.serverTimestamp(),
|
|
14479
|
+
// §9.2 PLAN_C2 — actor real desde ctx.user (NO 'mcp-cowork' hardcoded).
|
|
14480
|
+
creadoPorId: actor.uid,
|
|
14481
|
+
creadoPorNombre: actor.nombre,
|
|
14482
|
+
creadoPorClient: actor.clientType,
|
|
14483
|
+
creadoPorClientMetadata: actor.clientMetadata ?? null,
|
|
14153
14484
|
updatedAt: null
|
|
14154
14485
|
};
|
|
14155
14486
|
await db.doc(`tenants/${tenantId}/marketing_calendario/${calId}`).set(calDoc);
|
|
@@ -14367,34 +14698,40 @@ OBLIGATORIO: calendarioItemRef con formato EXACTO "semana:N:slot:M" (ej: "semana
|
|
|
14367
14698
|
OBLIGATORIO: fotoId \u2014 SIEMPRE pasa el ID de la foto que elegiste con get_photos_for_slot.
|
|
14368
14699
|
Si el slot tiene notas[], LEERLAS y usarlas como contexto. Si estado es revisar, regenerar adaptando a las notas.
|
|
14369
14700
|
BLOG SLOTS: Usa _blogJIT para el contexto de cada slot shopify_blog \u2014 blogTarget, tono, author, articulosExistentes para interlinking.`;
|
|
14370
|
-
var
|
|
14371
|
-
tenantId:
|
|
14372
|
-
brandId:
|
|
14373
|
-
semana:
|
|
14701
|
+
var ParamsSchema23 = import_zod58.z.object({
|
|
14702
|
+
tenantId: import_zod58.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
14703
|
+
brandId: import_zod58.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
14704
|
+
semana: import_zod58.z.number().int().min(1).max(5).optional().describe(
|
|
14374
14705
|
"Week number within the current month (1-5). OMIT to default to the current week inferred from today."
|
|
14375
14706
|
),
|
|
14376
|
-
modo:
|
|
14707
|
+
modo: import_zod58.z.enum(["planificar", "generar"]).optional().describe(
|
|
14377
14708
|
"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'."
|
|
14378
|
-
)
|
|
14709
|
+
),
|
|
14710
|
+
actor: import_zod58.z.object({
|
|
14711
|
+
uid: import_zod58.z.string().min(1).describe('User id who is generating the weekly content. Example: "your-user-uid".'),
|
|
14712
|
+
nombre: import_zod58.z.string().min(1).describe('Human-readable user name. Example: "Daniel Gonz\xE1lez".'),
|
|
14713
|
+
clientType: import_zod58.z.string().min(1).describe('Client surface origin. Example: one of "mcp_client" | "martin" | "web" | "admin".'),
|
|
14714
|
+
clientMetadata: import_zod58.z.record(import_zod58.z.string(), import_zod58.z.unknown()).nullable().optional().describe("Optional metadata about the client.")
|
|
14715
|
+
}).describe("Actor (user) generating the weekly content \u2014 extracted by the server.tool from ctx.user (\xA79.2). Used when auto-creating marketing_calendario doc.")
|
|
14379
14716
|
});
|
|
14380
|
-
var
|
|
14381
|
-
|
|
14382
|
-
ok:
|
|
14383
|
-
payload:
|
|
14717
|
+
var OutputSchema23 = import_zod58.z.discriminatedUnion("ok", [
|
|
14718
|
+
import_zod58.z.object({
|
|
14719
|
+
ok: import_zod58.z.literal(true),
|
|
14720
|
+
payload: import_zod58.z.record(import_zod58.z.string(), import_zod58.z.unknown()).describe(
|
|
14384
14721
|
"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)."
|
|
14385
14722
|
)
|
|
14386
14723
|
}),
|
|
14387
|
-
|
|
14388
|
-
ok:
|
|
14389
|
-
error:
|
|
14390
|
-
code:
|
|
14724
|
+
import_zod58.z.object({
|
|
14725
|
+
ok: import_zod58.z.literal(false),
|
|
14726
|
+
error: import_zod58.z.string(),
|
|
14727
|
+
code: import_zod58.z.enum(["BRAND_NOT_FOUND", "WEEK_HAS_NO_SLOTS"]).optional()
|
|
14391
14728
|
})
|
|
14392
14729
|
]);
|
|
14393
|
-
var
|
|
14730
|
+
var rawContract23 = {
|
|
14394
14731
|
name: "generate_weekly_content",
|
|
14395
14732
|
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.",
|
|
14396
|
-
paramsSchema:
|
|
14397
|
-
outputSchema:
|
|
14733
|
+
paramsSchema: ParamsSchema23,
|
|
14734
|
+
outputSchema: OutputSchema23,
|
|
14398
14735
|
// Auto-create de calendario es bootstrap reversible (volver a llamar usa el
|
|
14399
14736
|
// calendario existente). NO publica nada externo. modo='generar' tampoco
|
|
14400
14737
|
// publica — solo prepara contexto.
|
|
@@ -14439,7 +14776,7 @@ var rawContract20 = {
|
|
|
14439
14776
|
sideEffects: ["reads_firestore", "writes_firestore"]
|
|
14440
14777
|
};
|
|
14441
14778
|
var weeklyContentBuilderContract = MartinContractSchema.parse(
|
|
14442
|
-
|
|
14779
|
+
rawContract23
|
|
14443
14780
|
);
|
|
14444
14781
|
var DEFAULT_TEXT_THRESHOLD = 0.35;
|
|
14445
14782
|
var DEFAULT_IMAGE_THRESHOLD = 0.08;
|
|
@@ -14628,69 +14965,69 @@ async function contentFinder(input) {
|
|
|
14628
14965
|
}
|
|
14629
14966
|
return result;
|
|
14630
14967
|
}
|
|
14631
|
-
var IncludeSchema =
|
|
14632
|
-
products:
|
|
14633
|
-
collections:
|
|
14634
|
-
articles:
|
|
14635
|
-
pages:
|
|
14968
|
+
var IncludeSchema = import_zod59.z.object({
|
|
14969
|
+
products: import_zod59.z.boolean(),
|
|
14970
|
+
collections: import_zod59.z.boolean(),
|
|
14971
|
+
articles: import_zod59.z.boolean(),
|
|
14972
|
+
pages: import_zod59.z.boolean()
|
|
14636
14973
|
}).describe(
|
|
14637
14974
|
"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."
|
|
14638
14975
|
);
|
|
14639
|
-
var LimitSchema =
|
|
14640
|
-
products:
|
|
14641
|
-
collections:
|
|
14642
|
-
articles:
|
|
14643
|
-
pages:
|
|
14976
|
+
var LimitSchema = import_zod59.z.object({
|
|
14977
|
+
products: import_zod59.z.number().int().min(0).max(20),
|
|
14978
|
+
collections: import_zod59.z.number().int().min(0).max(10),
|
|
14979
|
+
articles: import_zod59.z.number().int().min(0).max(20),
|
|
14980
|
+
pages: import_zod59.z.number().int().min(0).max(10)
|
|
14644
14981
|
}).describe(
|
|
14645
14982
|
"Per-category result count caps. Defaults: products 5, collections 3, articles 5, pages 2."
|
|
14646
14983
|
);
|
|
14647
|
-
var
|
|
14648
|
-
tenantId:
|
|
14649
|
-
brandId:
|
|
14650
|
-
contexto:
|
|
14984
|
+
var ParamsSchema24 = import_zod59.z.object({
|
|
14985
|
+
tenantId: import_zod59.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
14986
|
+
brandId: import_zod59.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
14987
|
+
contexto: import_zod59.z.string().min(1).describe(
|
|
14651
14988
|
"Search context: a paragraph, keyword, or intent string. Embedded for vector search."
|
|
14652
14989
|
),
|
|
14653
|
-
fecha:
|
|
14990
|
+
fecha: import_zod59.z.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe(
|
|
14654
14991
|
"Content date in YYYY-MM-DD. Used to detect active season and prioritize matching collections."
|
|
14655
14992
|
),
|
|
14656
14993
|
include: IncludeSchema,
|
|
14657
14994
|
limit: LimitSchema,
|
|
14658
|
-
diversidad:
|
|
14995
|
+
diversidad: import_zod59.z.boolean().describe(
|
|
14659
14996
|
"Whether to apply Jaccard title diversification + handle dedupe to results. Default true."
|
|
14660
14997
|
),
|
|
14661
|
-
mode:
|
|
14998
|
+
mode: import_zod59.z.enum(["text", "hybrid"]).optional().describe(
|
|
14662
14999
|
"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."
|
|
14663
15000
|
)
|
|
14664
15001
|
});
|
|
14665
|
-
var VectorResultSchema =
|
|
14666
|
-
id:
|
|
14667
|
-
similarity:
|
|
15002
|
+
var VectorResultSchema = import_zod59.z.object({
|
|
15003
|
+
id: import_zod59.z.string(),
|
|
15004
|
+
similarity: import_zod59.z.number().optional()
|
|
14668
15005
|
}).passthrough();
|
|
14669
|
-
var TemporadaSchema2 =
|
|
14670
|
-
coleccion:
|
|
14671
|
-
titulo:
|
|
14672
|
-
razon:
|
|
14673
|
-
fechaInicio:
|
|
14674
|
-
fechaFin:
|
|
15006
|
+
var TemporadaSchema2 = import_zod59.z.object({
|
|
15007
|
+
coleccion: import_zod59.z.string().nullable(),
|
|
15008
|
+
titulo: import_zod59.z.string().nullable(),
|
|
15009
|
+
razon: import_zod59.z.string().nullable(),
|
|
15010
|
+
fechaInicio: import_zod59.z.string().nullable(),
|
|
15011
|
+
fechaFin: import_zod59.z.string().nullable()
|
|
14675
15012
|
});
|
|
14676
|
-
var SuggestedActionSchema22 =
|
|
14677
|
-
var DetectedConflictSchema22 =
|
|
14678
|
-
var
|
|
14679
|
-
productos:
|
|
14680
|
-
colecciones:
|
|
14681
|
-
articles:
|
|
14682
|
-
pages:
|
|
14683
|
-
_instrucciones:
|
|
14684
|
-
_negativePrompt:
|
|
15013
|
+
var SuggestedActionSchema22 = import_zod59.z.record(import_zod59.z.string(), import_zod59.z.unknown());
|
|
15014
|
+
var DetectedConflictSchema22 = import_zod59.z.record(import_zod59.z.string(), import_zod59.z.unknown());
|
|
15015
|
+
var OutputSchema24 = import_zod59.z.object({
|
|
15016
|
+
productos: import_zod59.z.array(VectorResultSchema),
|
|
15017
|
+
colecciones: import_zod59.z.array(VectorResultSchema),
|
|
15018
|
+
articles: import_zod59.z.array(VectorResultSchema),
|
|
15019
|
+
pages: import_zod59.z.array(VectorResultSchema),
|
|
15020
|
+
_instrucciones: import_zod59.z.string(),
|
|
15021
|
+
_negativePrompt: import_zod59.z.string(),
|
|
14685
15022
|
_temporadaActiva: TemporadaSchema2.nullable(),
|
|
14686
15023
|
_detectedConflict: DetectedConflictSchema22.optional(),
|
|
14687
|
-
_suggestedActions:
|
|
15024
|
+
_suggestedActions: import_zod59.z.array(SuggestedActionSchema22)
|
|
14688
15025
|
});
|
|
14689
|
-
var
|
|
15026
|
+
var rawContract24 = {
|
|
14690
15027
|
name: "find_content_for_topic",
|
|
14691
15028
|
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).",
|
|
14692
|
-
paramsSchema:
|
|
14693
|
-
outputSchema:
|
|
15029
|
+
paramsSchema: ParamsSchema24,
|
|
15030
|
+
outputSchema: OutputSchema24,
|
|
14694
15031
|
// Lectura pura (vector search). Failures internas en search degradan a
|
|
14695
15032
|
// arrays vacíos — el helper NO falla.
|
|
14696
15033
|
requiresConfirmation: false,
|
|
@@ -14713,7 +15050,7 @@ var rawContract21 = {
|
|
|
14713
15050
|
sideEffects: ["reads_firestore"]
|
|
14714
15051
|
};
|
|
14715
15052
|
var contentFinderContract = MartinContractSchema.parse(
|
|
14716
|
-
|
|
15053
|
+
rawContract24
|
|
14717
15054
|
);
|
|
14718
15055
|
var DEFAULT_PHOTO_THRESHOLD = 0.7;
|
|
14719
15056
|
var DEFAULT_SHOPIFY_THRESHOLD = 0.65;
|
|
@@ -14860,51 +15197,51 @@ async function slotAssetFinder(input) {
|
|
|
14860
15197
|
_fuente: fuente
|
|
14861
15198
|
};
|
|
14862
15199
|
}
|
|
14863
|
-
var
|
|
14864
|
-
tenantId:
|
|
14865
|
-
brandId:
|
|
14866
|
-
keyword:
|
|
15200
|
+
var ParamsSchema25 = import_zod60.z.object({
|
|
15201
|
+
tenantId: import_zod60.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
15202
|
+
brandId: import_zod60.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
15203
|
+
keyword: import_zod60.z.string().min(1).describe(
|
|
14867
15204
|
"Slot keyword to search photos for. Used as embedding query for multimodal vector search."
|
|
14868
15205
|
),
|
|
14869
|
-
plataforma:
|
|
15206
|
+
plataforma: import_zod60.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe(
|
|
14870
15207
|
"Target platform \u2014 determines the variant format to resolve (gbp_4_3, blog_3_2, ig_4_5, ig_1_1)."
|
|
14871
15208
|
),
|
|
14872
|
-
fecha:
|
|
15209
|
+
fecha: import_zod60.z.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe(
|
|
14873
15210
|
"Slot date in YYYY-MM-DD. Used to detect active season and apply seasonal catalog overrides."
|
|
14874
15211
|
),
|
|
14875
|
-
limit:
|
|
15212
|
+
limit: import_zod60.z.number().int().min(1).max(10).optional().describe("Max number of photos to return. Default 5. Max 10.")
|
|
14876
15213
|
});
|
|
14877
|
-
var TemporadaSchema22 =
|
|
14878
|
-
coleccion:
|
|
14879
|
-
titulo:
|
|
14880
|
-
razon:
|
|
14881
|
-
fechaInicio:
|
|
14882
|
-
fechaFin:
|
|
15214
|
+
var TemporadaSchema22 = import_zod60.z.object({
|
|
15215
|
+
coleccion: import_zod60.z.string().nullable(),
|
|
15216
|
+
titulo: import_zod60.z.string().nullable(),
|
|
15217
|
+
razon: import_zod60.z.string().nullable(),
|
|
15218
|
+
fechaInicio: import_zod60.z.string().nullable(),
|
|
15219
|
+
fechaFin: import_zod60.z.string().nullable()
|
|
14883
15220
|
});
|
|
14884
|
-
var
|
|
15221
|
+
var OutputSchema25 = import_zod60.z.discriminatedUnion("ok", [
|
|
14885
15222
|
// Note: success case does not include `ok: true` literal in helper return —
|
|
14886
15223
|
// helper returns the success shape directly. Adapter to discriminated union
|
|
14887
15224
|
// happens at wrapper level if needed; here we accept both shapes.
|
|
14888
|
-
|
|
14889
|
-
ok:
|
|
14890
|
-
fotos:
|
|
14891
|
-
_instrucciones:
|
|
14892
|
-
_negativePrompt:
|
|
15225
|
+
import_zod60.z.object({
|
|
15226
|
+
ok: import_zod60.z.literal(true),
|
|
15227
|
+
fotos: import_zod60.z.array(import_zod60.z.record(import_zod60.z.string(), import_zod60.z.unknown())),
|
|
15228
|
+
_instrucciones: import_zod60.z.string(),
|
|
15229
|
+
_negativePrompt: import_zod60.z.string(),
|
|
14893
15230
|
_temporadaActiva: TemporadaSchema22.nullable(),
|
|
14894
|
-
_bloqueoProducto:
|
|
14895
|
-
_fuente:
|
|
15231
|
+
_bloqueoProducto: import_zod60.z.boolean(),
|
|
15232
|
+
_fuente: import_zod60.z.enum(["tenant", "shopify_product"])
|
|
14896
15233
|
}),
|
|
14897
|
-
|
|
14898
|
-
ok:
|
|
14899
|
-
error:
|
|
14900
|
-
code:
|
|
15234
|
+
import_zod60.z.object({
|
|
15235
|
+
ok: import_zod60.z.literal(false),
|
|
15236
|
+
error: import_zod60.z.string(),
|
|
15237
|
+
code: import_zod60.z.enum(["BRAND_NOT_FOUND"]).optional()
|
|
14901
15238
|
})
|
|
14902
15239
|
]);
|
|
14903
|
-
var
|
|
15240
|
+
var rawContract25 = {
|
|
14904
15241
|
name: "get_photos_for_slot",
|
|
14905
15242
|
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.",
|
|
14906
|
-
paramsSchema:
|
|
14907
|
-
outputSchema:
|
|
15243
|
+
paramsSchema: ParamsSchema25,
|
|
15244
|
+
outputSchema: OutputSchema25,
|
|
14908
15245
|
requiresConfirmation: false,
|
|
14909
15246
|
destructive: false,
|
|
14910
15247
|
affectsPublication: false,
|
|
@@ -14931,7 +15268,7 @@ var rawContract22 = {
|
|
|
14931
15268
|
sideEffects: ["reads_firestore"]
|
|
14932
15269
|
};
|
|
14933
15270
|
var slotAssetFinderContract = MartinContractSchema.parse(
|
|
14934
|
-
|
|
15271
|
+
rawContract25
|
|
14935
15272
|
);
|
|
14936
15273
|
var DEFAULT_SIMILARITY_THRESHOLD = 0.6;
|
|
14937
15274
|
function cosineSimilarity(a, b) {
|
|
@@ -15008,44 +15345,44 @@ async function canvaTemplateSelector(input) {
|
|
|
15008
15345
|
_instrucciones: "Plantilla Canva seleccionada. Llama a marketingDesignWithCanva({contenidoId, plantillaId, fotoVariantePath, textos}) para renderizar la pieza final."
|
|
15009
15346
|
};
|
|
15010
15347
|
}
|
|
15011
|
-
var
|
|
15012
|
-
tenantId:
|
|
15013
|
-
brandId:
|
|
15014
|
-
plataforma:
|
|
15348
|
+
var ParamsSchema26 = import_zod61.z.object({
|
|
15349
|
+
tenantId: import_zod61.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
15350
|
+
brandId: import_zod61.z.string().min(1).describe("Brand identifier within the tenant."),
|
|
15351
|
+
plataforma: import_zod61.z.string().min(1).describe(
|
|
15015
15352
|
"Target platform (e.g. 'gbp', 'shopify_blog', 'instagram'). Filters templates that declare this plataforma."
|
|
15016
15353
|
),
|
|
15017
|
-
tipoContenido:
|
|
15354
|
+
tipoContenido: import_zod61.z.string().min(1).describe(
|
|
15018
15355
|
"Content type (e.g. 'post', 'carousel', 'story', 'blog'). Filters templates that declare this tipoContenido."
|
|
15019
15356
|
),
|
|
15020
|
-
keyword:
|
|
15357
|
+
keyword: import_zod61.z.string().min(1).describe("Slot keyword. Used as embedding query for cosine similarity match.")
|
|
15021
15358
|
});
|
|
15022
|
-
var
|
|
15023
|
-
|
|
15024
|
-
plantillaId:
|
|
15025
|
-
titulo:
|
|
15026
|
-
thumbnailUrl:
|
|
15027
|
-
similarity:
|
|
15028
|
-
_instrucciones:
|
|
15359
|
+
var OutputSchema26 = import_zod61.z.union([
|
|
15360
|
+
import_zod61.z.object({
|
|
15361
|
+
plantillaId: import_zod61.z.string(),
|
|
15362
|
+
titulo: import_zod61.z.string().nullable(),
|
|
15363
|
+
thumbnailUrl: import_zod61.z.string().nullable(),
|
|
15364
|
+
similarity: import_zod61.z.number(),
|
|
15365
|
+
_instrucciones: import_zod61.z.string()
|
|
15029
15366
|
}),
|
|
15030
|
-
|
|
15031
|
-
plantillaId:
|
|
15032
|
-
motivo:
|
|
15367
|
+
import_zod61.z.object({
|
|
15368
|
+
plantillaId: import_zod61.z.null(),
|
|
15369
|
+
motivo: import_zod61.z.enum([
|
|
15033
15370
|
"brand_no_encontrada",
|
|
15034
15371
|
"no_canva",
|
|
15035
15372
|
"no_match_plataforma",
|
|
15036
15373
|
"modo_cliente_no_soportado",
|
|
15037
15374
|
"similarity_baja"
|
|
15038
15375
|
]),
|
|
15039
|
-
similarity:
|
|
15040
|
-
_instrucciones:
|
|
15041
|
-
_todo:
|
|
15376
|
+
similarity: import_zod61.z.number().optional(),
|
|
15377
|
+
_instrucciones: import_zod61.z.string().optional(),
|
|
15378
|
+
_todo: import_zod61.z.string().optional()
|
|
15042
15379
|
})
|
|
15043
15380
|
]);
|
|
15044
|
-
var
|
|
15381
|
+
var rawContract26 = {
|
|
15045
15382
|
name: "select_canva_template",
|
|
15046
15383
|
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.",
|
|
15047
|
-
paramsSchema:
|
|
15048
|
-
outputSchema:
|
|
15384
|
+
paramsSchema: ParamsSchema26,
|
|
15385
|
+
outputSchema: OutputSchema26,
|
|
15049
15386
|
// Lectura pura. NO falla — todo "no encontré" es resultado válido del helper.
|
|
15050
15387
|
requiresConfirmation: false,
|
|
15051
15388
|
destructive: false,
|
|
@@ -15072,7 +15409,7 @@ var rawContract23 = {
|
|
|
15072
15409
|
sideEffects: ["reads_firestore"]
|
|
15073
15410
|
};
|
|
15074
15411
|
var canvaTemplateSelectorContract = MartinContractSchema.parse(
|
|
15075
|
-
|
|
15412
|
+
rawContract26
|
|
15076
15413
|
);
|
|
15077
15414
|
function buildDirectorPlanInstrucciones(catalogoVisual) {
|
|
15078
15415
|
const etiquetas = catalogoVisual.etiquetas || {};
|
|
@@ -15296,38 +15633,38 @@ async function photoDirectorExecute(input) {
|
|
|
15296
15633
|
_instrucciones: buildDirectorExecuteSuccessInstrucciones(result.balanceAfter)
|
|
15297
15634
|
};
|
|
15298
15635
|
}
|
|
15299
|
-
var PlanParamsSchema =
|
|
15300
|
-
tenantId:
|
|
15301
|
-
fotoId:
|
|
15636
|
+
var PlanParamsSchema = import_zod62.z.object({
|
|
15637
|
+
tenantId: import_zod62.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
15638
|
+
fotoId: import_zod62.z.string().min(1).describe("Photo ID in marketing_fotos. The photo must have an archivoOriginal uploaded.")
|
|
15302
15639
|
});
|
|
15303
|
-
var PlanContextSchema =
|
|
15304
|
-
fotoId:
|
|
15305
|
-
archivoOriginal:
|
|
15306
|
-
estrategia:
|
|
15307
|
-
brandBrief:
|
|
15308
|
-
segmento:
|
|
15309
|
-
personalidad:
|
|
15640
|
+
var PlanContextSchema = import_zod62.z.object({
|
|
15641
|
+
fotoId: import_zod62.z.string(),
|
|
15642
|
+
archivoOriginal: import_zod62.z.string(),
|
|
15643
|
+
estrategia: import_zod62.z.string(),
|
|
15644
|
+
brandBrief: import_zod62.z.object({
|
|
15645
|
+
segmento: import_zod62.z.string().nullable(),
|
|
15646
|
+
personalidad: import_zod62.z.unknown().nullable()
|
|
15310
15647
|
}),
|
|
15311
|
-
visualRules:
|
|
15312
|
-
catalogoVisual:
|
|
15313
|
-
estiloVisual:
|
|
15314
|
-
notaTenant:
|
|
15315
|
-
productoLinkeadoManual:
|
|
15316
|
-
_instrucciones:
|
|
15648
|
+
visualRules: import_zod62.z.record(import_zod62.z.string(), import_zod62.z.unknown()),
|
|
15649
|
+
catalogoVisual: import_zod62.z.record(import_zod62.z.string(), import_zod62.z.unknown()),
|
|
15650
|
+
estiloVisual: import_zod62.z.unknown().nullable(),
|
|
15651
|
+
notaTenant: import_zod62.z.unknown().nullable(),
|
|
15652
|
+
productoLinkeadoManual: import_zod62.z.unknown().nullable(),
|
|
15653
|
+
_instrucciones: import_zod62.z.string()
|
|
15317
15654
|
});
|
|
15318
|
-
var PlanOutputSchema =
|
|
15319
|
-
|
|
15320
|
-
ok:
|
|
15321
|
-
imageBase64:
|
|
15655
|
+
var PlanOutputSchema = import_zod62.z.discriminatedUnion("ok", [
|
|
15656
|
+
import_zod62.z.object({
|
|
15657
|
+
ok: import_zod62.z.literal(true),
|
|
15658
|
+
imageBase64: import_zod62.z.string().describe("Compressed photo as base64 for transport to the LLM."),
|
|
15322
15659
|
context: PlanContextSchema
|
|
15323
15660
|
}),
|
|
15324
|
-
|
|
15325
|
-
ok:
|
|
15326
|
-
code:
|
|
15327
|
-
error:
|
|
15328
|
-
_instrucciones:
|
|
15329
|
-
fix:
|
|
15330
|
-
fotoId:
|
|
15661
|
+
import_zod62.z.object({
|
|
15662
|
+
ok: import_zod62.z.literal(false),
|
|
15663
|
+
code: import_zod62.z.string(),
|
|
15664
|
+
error: import_zod62.z.string(),
|
|
15665
|
+
_instrucciones: import_zod62.z.string().optional(),
|
|
15666
|
+
fix: import_zod62.z.string().optional(),
|
|
15667
|
+
fotoId: import_zod62.z.string().optional()
|
|
15331
15668
|
})
|
|
15332
15669
|
]);
|
|
15333
15670
|
var planRawContract = {
|
|
@@ -15357,43 +15694,43 @@ var planRawContract = {
|
|
|
15357
15694
|
var photoDirectorPlanContract = MartinContractSchema.parse(
|
|
15358
15695
|
planRawContract
|
|
15359
15696
|
);
|
|
15360
|
-
var ExecuteParamsSchema =
|
|
15697
|
+
var ExecuteParamsSchema = import_zod62.z.object({
|
|
15361
15698
|
// tenantId is injected by the wrapper from ctx (A7 module-scope pattern,
|
|
15362
15699
|
// even though the helper function itself derives tenantId via the foto's
|
|
15363
15700
|
// brandId). Needed here for extractTargetPath canonical path.
|
|
15364
|
-
tenantId:
|
|
15365
|
-
fotoId:
|
|
15366
|
-
prompt:
|
|
15701
|
+
tenantId: import_zod62.z.string().min(1).describe("Tenant identifier (the business account)."),
|
|
15702
|
+
fotoId: import_zod62.z.string().min(1).describe("Photo ID to edit (must have been planned via plan_photo_edit)."),
|
|
15703
|
+
prompt: import_zod62.z.string().nullable().describe(
|
|
15367
15704
|
"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)."
|
|
15368
15705
|
),
|
|
15369
|
-
acciones:
|
|
15706
|
+
acciones: import_zod62.z.array(import_zod62.z.enum(["edit_background", "none"])).describe(
|
|
15370
15707
|
"Edit operations to perform. Use ['edit_background'] for AI background edit, ['none'] when strategy is 'tal_cual'."
|
|
15371
15708
|
),
|
|
15372
|
-
descripcion:
|
|
15373
|
-
tipo:
|
|
15374
|
-
tagsPrimarios:
|
|
15375
|
-
tagsSecundarios:
|
|
15376
|
-
tagsContexto:
|
|
15709
|
+
descripcion: import_zod62.z.string().describe("Semantic description in Spanish (saved as descripcionVision on the photo)."),
|
|
15710
|
+
tipo: import_zod62.z.string().nullable().describe("Photo type from the brand catalogoVisual. null only if catalog has no matching type."),
|
|
15711
|
+
tagsPrimarios: import_zod62.z.array(import_zod62.z.string()).describe("Primary tags from the catalogoVisual."),
|
|
15712
|
+
tagsSecundarios: import_zod62.z.array(import_zod62.z.string()).describe("Secondary tags from the catalogoVisual."),
|
|
15713
|
+
tagsContexto: import_zod62.z.array(import_zod62.z.string()).describe("Contextual tags (occasion, mood, style).")
|
|
15377
15714
|
});
|
|
15378
|
-
var ExecuteOutputSchema =
|
|
15379
|
-
|
|
15380
|
-
ok:
|
|
15381
|
-
fotoId:
|
|
15382
|
-
archivoEditado:
|
|
15383
|
-
thumbnailUrl:
|
|
15384
|
-
iteracion:
|
|
15385
|
-
editCosto:
|
|
15386
|
-
creditsConsumed:
|
|
15387
|
-
balanceAfter:
|
|
15388
|
-
imageBase64:
|
|
15389
|
-
_instrucciones:
|
|
15715
|
+
var ExecuteOutputSchema = import_zod62.z.discriminatedUnion("ok", [
|
|
15716
|
+
import_zod62.z.object({
|
|
15717
|
+
ok: import_zod62.z.literal(true),
|
|
15718
|
+
fotoId: import_zod62.z.string(),
|
|
15719
|
+
archivoEditado: import_zod62.z.string().optional(),
|
|
15720
|
+
thumbnailUrl: import_zod62.z.string().optional(),
|
|
15721
|
+
iteracion: import_zod62.z.number().optional(),
|
|
15722
|
+
editCosto: import_zod62.z.number().optional(),
|
|
15723
|
+
creditsConsumed: import_zod62.z.number().optional(),
|
|
15724
|
+
balanceAfter: import_zod62.z.number().optional(),
|
|
15725
|
+
imageBase64: import_zod62.z.string().nullable(),
|
|
15726
|
+
_instrucciones: import_zod62.z.string()
|
|
15390
15727
|
}),
|
|
15391
|
-
|
|
15392
|
-
ok:
|
|
15393
|
-
error:
|
|
15394
|
-
code:
|
|
15395
|
-
details:
|
|
15396
|
-
_instrucciones:
|
|
15728
|
+
import_zod62.z.object({
|
|
15729
|
+
ok: import_zod62.z.literal(false),
|
|
15730
|
+
error: import_zod62.z.string(),
|
|
15731
|
+
code: import_zod62.z.string(),
|
|
15732
|
+
details: import_zod62.z.record(import_zod62.z.string(), import_zod62.z.unknown()).optional(),
|
|
15733
|
+
_instrucciones: import_zod62.z.string()
|
|
15397
15734
|
})
|
|
15398
15735
|
]);
|
|
15399
15736
|
var executeRawContract = {
|
|
@@ -16050,6 +16387,15 @@ function callPhotoBriefingWriter(input) {
|
|
|
16050
16387
|
function callTenantContextSetter(input) {
|
|
16051
16388
|
return callCF("marketingTenantContextSetterCallable", input);
|
|
16052
16389
|
}
|
|
16390
|
+
function callPhotoDescarter(input) {
|
|
16391
|
+
return callCF("marketingPhotoDescarterCallable", input);
|
|
16392
|
+
}
|
|
16393
|
+
function callPhotoEditadaMarker(input) {
|
|
16394
|
+
return callCF("marketingPhotoEditadaMarkerCallable", input);
|
|
16395
|
+
}
|
|
16396
|
+
function callBrandContextSetter(input) {
|
|
16397
|
+
return callCF("marketingBrandContextSetterCallable", input);
|
|
16398
|
+
}
|
|
16053
16399
|
function callBrandBriefWriter(input) {
|
|
16054
16400
|
return callCF("marketingBrandBriefWriterCallable", input);
|
|
16055
16401
|
}
|
|
@@ -16199,8 +16545,8 @@ function registerContextTools(server, session) {
|
|
|
16199
16545
|
"set_context",
|
|
16200
16546
|
"Set the active tenant + brand for the current super_admin MCP session. Validates that the target brand exists. Available only to super_admin users.",
|
|
16201
16547
|
{
|
|
16202
|
-
tenantId:
|
|
16203
|
-
brandId:
|
|
16548
|
+
tenantId: import_zod63.z.string().describe("Target tenant identifier"),
|
|
16549
|
+
brandId: import_zod63.z.string().describe("Target brand identifier within the tenant")
|
|
16204
16550
|
},
|
|
16205
16551
|
async ({ tenantId, brandId }) => {
|
|
16206
16552
|
const ctx = await buildContext(session, brandId);
|
|
@@ -16237,12 +16583,62 @@ function registerContextTools(server, session) {
|
|
|
16237
16583
|
}
|
|
16238
16584
|
);
|
|
16239
16585
|
}
|
|
16586
|
+
const isMultiBrand = session.brands && session.brands.length > 1;
|
|
16587
|
+
const isSuperAdmin = session.canSwitchTenant;
|
|
16588
|
+
if (isMultiBrand || isSuperAdmin) {
|
|
16589
|
+
server.tool(
|
|
16590
|
+
"select_brand",
|
|
16591
|
+
"Switch the active brand within your tenant. Use when you have multi-brand access (agency mode) and want to work with a different brand without passing brandId on every tool call. Does NOT change tenant.",
|
|
16592
|
+
{
|
|
16593
|
+
brandId: import_zod63.z.string().describe("Target brand identifier (must be in your accessible brands list)")
|
|
16594
|
+
},
|
|
16595
|
+
async ({ brandId }) => {
|
|
16596
|
+
const tenantId = session.requireTenant();
|
|
16597
|
+
const ctx = await buildContext(session, brandId);
|
|
16598
|
+
const result = await dispatchWithContract({
|
|
16599
|
+
contract: brandContextSetterContract,
|
|
16600
|
+
helper: brandContextSetter,
|
|
16601
|
+
callable: callBrandContextSetter,
|
|
16602
|
+
input: { tenantId, brandId },
|
|
16603
|
+
ctx
|
|
16604
|
+
});
|
|
16605
|
+
let payload;
|
|
16606
|
+
if (result.state === "success" && result.structuredOutput) {
|
|
16607
|
+
const out = result.structuredOutput;
|
|
16608
|
+
if (out.ok) {
|
|
16609
|
+
try {
|
|
16610
|
+
session.setBrand(out.brandId);
|
|
16611
|
+
payload = {
|
|
16612
|
+
ok: true,
|
|
16613
|
+
tenant: out.tenantId,
|
|
16614
|
+
brand: out.brandNombre ?? out.brandId,
|
|
16615
|
+
dominio: out.brandDominio
|
|
16616
|
+
};
|
|
16617
|
+
} catch (err) {
|
|
16618
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
16619
|
+
payload = { ok: false, code: "BRAND_NOT_ACCESSIBLE_BY_USER", mensaje: msg };
|
|
16620
|
+
}
|
|
16621
|
+
} else {
|
|
16622
|
+
payload = out;
|
|
16623
|
+
}
|
|
16624
|
+
} else {
|
|
16625
|
+
payload = {
|
|
16626
|
+
ok: false,
|
|
16627
|
+
state: result.state,
|
|
16628
|
+
mensaje: result.text,
|
|
16629
|
+
...result.validationErrors && result.validationErrors.length > 0 ? { validationErrors: result.validationErrors } : {}
|
|
16630
|
+
};
|
|
16631
|
+
}
|
|
16632
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
16633
|
+
}
|
|
16634
|
+
);
|
|
16635
|
+
}
|
|
16240
16636
|
if (!session.serviceAccountPath) {
|
|
16241
16637
|
server.tool(
|
|
16242
16638
|
"connect_account",
|
|
16243
16639
|
"Conecta tu cuenta de Ponch para acceder a tus datos. Primero visita https://atpops.vercel.app/auth/mcp-connect para obtener el codigo.",
|
|
16244
16640
|
{
|
|
16245
|
-
code:
|
|
16641
|
+
code: import_zod63.z.string().describe("Codigo de conexion de 8 caracteres (ej: ABCD-EFGH) obtenido de la pagina web")
|
|
16246
16642
|
},
|
|
16247
16643
|
async ({ code }) => {
|
|
16248
16644
|
try {
|
|
@@ -16316,13 +16712,13 @@ function registerContextTools(server, session) {
|
|
|
16316
16712
|
}
|
|
16317
16713
|
|
|
16318
16714
|
// src/tools/core.ts
|
|
16319
|
-
var
|
|
16715
|
+
var import_zod64 = require("zod");
|
|
16320
16716
|
function registerCoreTools(server, session) {
|
|
16321
16717
|
server.tool(
|
|
16322
16718
|
"get_business_summary",
|
|
16323
16719
|
"Resumen general del negocio: metricas clave, contenido pendiente, estado de conexiones, alertas.",
|
|
16324
16720
|
{
|
|
16325
|
-
brandId:
|
|
16721
|
+
brandId: import_zod64.z.string().optional().describe("ID de la brand. Si no se pasa, usa la brand del contexto.")
|
|
16326
16722
|
},
|
|
16327
16723
|
async ({ brandId: inputBrandId }) => {
|
|
16328
16724
|
const tenantId = session.requireTenant();
|
|
@@ -16398,7 +16794,7 @@ function registerCoreTools(server, session) {
|
|
|
16398
16794
|
"get_pending_actions",
|
|
16399
16795
|
"Lista todo lo que necesita atencion: contenido por aprobar, fotos sin procesar.",
|
|
16400
16796
|
{
|
|
16401
|
-
brandId:
|
|
16797
|
+
brandId: import_zod64.z.string().optional().describe("ID de la brand")
|
|
16402
16798
|
},
|
|
16403
16799
|
async ({ brandId: inputBrandId }) => {
|
|
16404
16800
|
const tenantId = session.requireTenant();
|
|
@@ -16436,77 +16832,13 @@ function registerCoreTools(server, session) {
|
|
|
16436
16832
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
16437
16833
|
}
|
|
16438
16834
|
);
|
|
16439
|
-
server.tool(
|
|
16440
|
-
"execute_action",
|
|
16441
|
-
"Ejecuta una accion sobre contenido o foto: aprobar, rechazar, descartar foto, usar foto tal cual.",
|
|
16442
|
-
{
|
|
16443
|
-
accion: import_zod61.z.enum(["aprobar", "rechazar", "descartar_foto", "usar_foto_tal_cual"]).describe("Accion a ejecutar"),
|
|
16444
|
-
targetId: import_zod61.z.string().describe("ID del contenido o foto"),
|
|
16445
|
-
motivo: import_zod61.z.string().optional().describe("Motivo del rechazo (requerido para rechazar)")
|
|
16446
|
-
},
|
|
16447
|
-
async ({ accion, targetId, motivo }) => {
|
|
16448
|
-
session.requireTenant();
|
|
16449
|
-
if (accion === "aprobar") {
|
|
16450
|
-
const doc = await readDoc("marketing_contenido", targetId);
|
|
16451
|
-
if (!doc) return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Contenido no encontrado" }) }] };
|
|
16452
|
-
if (!esTransicionValida(doc.estado, ESTADO_CONTENIDO.APROBADO)) {
|
|
16453
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `No se puede aprobar desde estado "${doc.estado}"` }) }] };
|
|
16454
|
-
}
|
|
16455
|
-
await updateDoc("marketing_contenido", targetId, {
|
|
16456
|
-
estado: ESTADO_CONTENIDO.APROBADO,
|
|
16457
|
-
aprobadoAt: serverTimestamp(),
|
|
16458
|
-
aprobadoPorId: "mcp-cowork",
|
|
16459
|
-
aprobadoPorNombre: "Cowork AI"
|
|
16460
|
-
});
|
|
16461
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true, nuevoEstado: ESTADO_CONTENIDO.APROBADO }) }] };
|
|
16462
|
-
}
|
|
16463
|
-
if (accion === "rechazar") {
|
|
16464
|
-
if (!motivo) return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Motivo es requerido para rechazar" }) }] };
|
|
16465
|
-
const doc = await readDoc("marketing_contenido", targetId);
|
|
16466
|
-
if (!doc) return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Contenido no encontrado" }) }] };
|
|
16467
|
-
if (!esTransicionValida(doc.estado, ESTADO_CONTENIDO.RECHAZADO)) {
|
|
16468
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `No se puede rechazar desde estado "${doc.estado}"` }) }] };
|
|
16469
|
-
}
|
|
16470
|
-
await updateDoc("marketing_contenido", targetId, {
|
|
16471
|
-
estado: ESTADO_CONTENIDO.RECHAZADO,
|
|
16472
|
-
rechazadoAt: serverTimestamp(),
|
|
16473
|
-
rechazadoMotivo: motivo
|
|
16474
|
-
});
|
|
16475
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true, nuevoEstado: ESTADO_CONTENIDO.RECHAZADO }) }] };
|
|
16476
|
-
}
|
|
16477
|
-
if (accion === "descartar_foto") {
|
|
16478
|
-
const doc = await readDoc("marketing_fotos", targetId);
|
|
16479
|
-
if (!doc) return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Foto no encontrada" }) }] };
|
|
16480
|
-
if (!validarTransicionFoto(doc.estado, ESTADO_FOTO.DESCARTADA)) {
|
|
16481
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `No se puede descartar desde estado "${doc.estado}"` }) }] };
|
|
16482
|
-
}
|
|
16483
|
-
await updateDoc("marketing_fotos", targetId, { estado: ESTADO_FOTO.DESCARTADA });
|
|
16484
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true, nuevoEstado: ESTADO_FOTO.DESCARTADA }) }] };
|
|
16485
|
-
}
|
|
16486
|
-
if (accion === "usar_foto_tal_cual") {
|
|
16487
|
-
const doc = await readDoc("marketing_fotos", targetId);
|
|
16488
|
-
if (!doc) return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Foto no encontrada" }) }] };
|
|
16489
|
-
if (!validarTransicionFoto(doc.estado, ESTADO_FOTO.EDITADA)) {
|
|
16490
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `No se puede marcar como editada desde estado "${doc.estado}"` }) }] };
|
|
16491
|
-
}
|
|
16492
|
-
await updateDoc("marketing_fotos", targetId, {
|
|
16493
|
-
estado: ESTADO_FOTO.EDITADA,
|
|
16494
|
-
archivoEditado: doc.archivoOriginal,
|
|
16495
|
-
// usa la original como editada
|
|
16496
|
-
fechaEdicion: serverTimestamp()
|
|
16497
|
-
});
|
|
16498
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: true, nuevoEstado: ESTADO_FOTO.EDITADA }) }] };
|
|
16499
|
-
}
|
|
16500
|
-
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Accion "${accion}" no reconocida` }) }] };
|
|
16501
|
-
}
|
|
16502
|
-
);
|
|
16503
16835
|
}
|
|
16504
16836
|
|
|
16505
16837
|
// src/tools/marketing.ts
|
|
16506
|
-
var
|
|
16838
|
+
var import_zod67 = require("zod");
|
|
16507
16839
|
|
|
16508
16840
|
// src/tools/marketing/photos.ts
|
|
16509
|
-
var
|
|
16841
|
+
var import_zod65 = require("zod");
|
|
16510
16842
|
|
|
16511
16843
|
// src/services/marketingEmbeddings.ts
|
|
16512
16844
|
var import_google_auth_library = require("google-auth-library");
|
|
@@ -16802,11 +17134,11 @@ REGLAS:
|
|
|
16802
17134
|
|
|
16803
17135
|
USAR: antes de generar contenido de cualquier slot del calendario.`,
|
|
16804
17136
|
{
|
|
16805
|
-
brandId:
|
|
16806
|
-
keyword:
|
|
16807
|
-
plataforma:
|
|
16808
|
-
fecha:
|
|
16809
|
-
limit:
|
|
17137
|
+
brandId: import_zod65.z.string().optional().describe("ID de la brand"),
|
|
17138
|
+
keyword: import_zod65.z.string().describe("Keyword del slot"),
|
|
17139
|
+
plataforma: import_zod65.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Plataforma destino del slot"),
|
|
17140
|
+
fecha: import_zod65.z.string().describe("Fecha del slot en ISO YYYY-MM-DD"),
|
|
17141
|
+
limit: import_zod65.z.number().int().min(1).max(10).default(5)
|
|
16810
17142
|
},
|
|
16811
17143
|
async ({ brandId: inputBrandId, keyword, plataforma, fecha, limit }) => {
|
|
16812
17144
|
const tenantId = session.requireTenant();
|
|
@@ -16854,7 +17186,7 @@ DESPUES de ver la foto, decide:
|
|
|
16854
17186
|
|
|
16855
17187
|
Luego llama execute_photo_edit con tu analisis y prompt.`,
|
|
16856
17188
|
{
|
|
16857
|
-
fotoId:
|
|
17189
|
+
fotoId: import_zod65.z.string().describe("ID de la foto")
|
|
16858
17190
|
},
|
|
16859
17191
|
async ({ fotoId }) => {
|
|
16860
17192
|
const tenantId = session.requireTenant();
|
|
@@ -16902,14 +17234,14 @@ Retorna la foto editada para que la revises. Si no te gusta:
|
|
|
16902
17234
|
|
|
16903
17235
|
Si estrategia era 'tal_cual', pasa acciones=['none'] \u2014 no se edita pero si se genera thumbnail y embedding con tus tags.`,
|
|
16904
17236
|
{
|
|
16905
|
-
fotoId:
|
|
16906
|
-
prompt:
|
|
16907
|
-
acciones:
|
|
16908
|
-
descripcion:
|
|
16909
|
-
tipo:
|
|
16910
|
-
tagsPrimarios:
|
|
16911
|
-
tagsSecundarios:
|
|
16912
|
-
tagsContexto:
|
|
17237
|
+
fotoId: import_zod65.z.string(),
|
|
17238
|
+
prompt: import_zod65.z.string().nullable().describe("Prompt en ingles para Gemini Image Edit. null si tal_cual"),
|
|
17239
|
+
acciones: import_zod65.z.array(import_zod65.z.enum(["edit_background", "none"])),
|
|
17240
|
+
descripcion: import_zod65.z.string().describe("Descripcion semantica en espanol"),
|
|
17241
|
+
tipo: import_zod65.z.string().nullable().describe("Tipo del catalogoVisual del tenant"),
|
|
17242
|
+
tagsPrimarios: import_zod65.z.array(import_zod65.z.string()),
|
|
17243
|
+
tagsSecundarios: import_zod65.z.array(import_zod65.z.string()),
|
|
17244
|
+
tagsContexto: import_zod65.z.array(import_zod65.z.string())
|
|
16913
17245
|
},
|
|
16914
17246
|
async ({ fotoId, prompt, acciones, descripcion, tipo, tagsPrimarios, tagsSecundarios, tagsContexto }) => {
|
|
16915
17247
|
const tenantId = session.requireTenant();
|
|
@@ -16977,7 +17309,7 @@ Si estrategia era 'tal_cual', pasa acciones=['none'] \u2014 no se edita pero si
|
|
|
16977
17309
|
"get_photo_edit_status",
|
|
16978
17310
|
"Return full photo edit status: photo state + iteration counter + active lock info + brand credits balance + actionable decision (puedeEditar) with reason if not. Use BEFORE retrying execute_photo_edit when you got a PHOTO_LOCKED error, or to show the tenant remaining iterations and credits.",
|
|
16979
17311
|
{
|
|
16980
|
-
fotoId:
|
|
17312
|
+
fotoId: import_zod65.z.string().describe("Photo identifier")
|
|
16981
17313
|
},
|
|
16982
17314
|
async ({ fotoId }) => {
|
|
16983
17315
|
const tenantId = session.requireTenant();
|
|
@@ -17022,11 +17354,11 @@ Si estrategia era 'tal_cual', pasa acciones=['none'] \u2014 no se edita pero si
|
|
|
17022
17354
|
"find_products_for_content",
|
|
17023
17355
|
`[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.`,
|
|
17024
17356
|
{
|
|
17025
|
-
brandId:
|
|
17026
|
-
contexto:
|
|
17027
|
-
fecha:
|
|
17028
|
-
limit:
|
|
17029
|
-
diversidad:
|
|
17357
|
+
brandId: import_zod65.z.string().optional().describe("ID de la brand"),
|
|
17358
|
+
contexto: import_zod65.z.string().describe("Parrafo, keyword o intencion del contenido"),
|
|
17359
|
+
fecha: import_zod65.z.string().describe("Fecha del contenido en ISO YYYY-MM-DD"),
|
|
17360
|
+
limit: import_zod65.z.number().int().min(1).max(10).default(5),
|
|
17361
|
+
diversidad: import_zod65.z.boolean().default(true)
|
|
17030
17362
|
},
|
|
17031
17363
|
async ({ brandId: inputBrandId, contexto, fecha, limit, diversidad }) => {
|
|
17032
17364
|
console.warn(
|
|
@@ -17103,10 +17435,10 @@ RETORNA { plantillaId, titulo, thumbnailUrl } o { plantillaId: null, motivo: 'no
|
|
|
17103
17435
|
|
|
17104
17436
|
USAR: solo si tenant tiene Canva conectado (tenants/{tenantId}/marketing_config/{brandId}.canva.connected=true).`,
|
|
17105
17437
|
{
|
|
17106
|
-
brandId:
|
|
17107
|
-
plataforma:
|
|
17108
|
-
tipoContenido:
|
|
17109
|
-
keyword:
|
|
17438
|
+
brandId: import_zod65.z.string().optional().describe("ID de la brand"),
|
|
17439
|
+
plataforma: import_zod65.z.string().describe("gbp | instagram | shopify_blog"),
|
|
17440
|
+
tipoContenido: import_zod65.z.string().describe("post | carousel | story | blog"),
|
|
17441
|
+
keyword: import_zod65.z.string().describe("Keyword del slot")
|
|
17110
17442
|
},
|
|
17111
17443
|
async ({ brandId: inputBrandId, plataforma, tipoContenido, keyword }) => {
|
|
17112
17444
|
const tenantId = session.requireTenant();
|
|
@@ -17151,15 +17483,15 @@ USE WHEN: get_photos_for_slot returns few photos and the slot is not yet covered
|
|
|
17151
17483
|
|
|
17152
17484
|
IMPORTANT \u2014 'razon' field: the tenant sees this in the app. Write in friendly, clear language, NOT technical. BAD example: "get_photos_for_slot returned 0 photos for shopify_blog". GOOD example: "No hay fotos de alcatraz para el blog del 8 de abril". Use the tenant's preferred language.`,
|
|
17153
17485
|
{
|
|
17154
|
-
brandId:
|
|
17155
|
-
semana:
|
|
17156
|
-
necesidades:
|
|
17157
|
-
|
|
17158
|
-
tema:
|
|
17159
|
-
keyword:
|
|
17160
|
-
cantidadSugerida:
|
|
17161
|
-
razon:
|
|
17162
|
-
slotsAfectados:
|
|
17486
|
+
brandId: import_zod65.z.string().optional().describe("Brand identifier (defaults to session brand)"),
|
|
17487
|
+
semana: import_zod65.z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "semana debe ser ISO YYYY-MM-DD del lunes").describe("Week identifier (ISO date of Monday)"),
|
|
17488
|
+
necesidades: import_zod65.z.array(
|
|
17489
|
+
import_zod65.z.object({
|
|
17490
|
+
tema: import_zod65.z.string(),
|
|
17491
|
+
keyword: import_zod65.z.string(),
|
|
17492
|
+
cantidadSugerida: import_zod65.z.number().int().positive(),
|
|
17493
|
+
razon: import_zod65.z.string(),
|
|
17494
|
+
slotsAfectados: import_zod65.z.array(import_zod65.z.string()).optional().describe('Affected calendar slot refs (format "semana:N:slot:M")')
|
|
17163
17495
|
})
|
|
17164
17496
|
).min(1)
|
|
17165
17497
|
},
|
|
@@ -17198,10 +17530,87 @@ IMPORTANT \u2014 'razon' field: the tenant sees this in the app. Write in friend
|
|
|
17198
17530
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
17199
17531
|
}
|
|
17200
17532
|
);
|
|
17533
|
+
server.tool(
|
|
17534
|
+
"discard_photo",
|
|
17535
|
+
"Discard a marketing photo (mark as descartada). Validates state transition. Use when the photo is no longer needed. Requires confirmation: first call returns state=pending_confirmation; pass confirm=true on the second call to execute.",
|
|
17536
|
+
{
|
|
17537
|
+
fotoId: import_zod65.z.string().describe("Photo identifier to discard"),
|
|
17538
|
+
brandId: import_zod65.z.string().optional().describe("Brand identifier (defaults to session brand)"),
|
|
17539
|
+
confirm: import_zod65.z.boolean().optional().describe("Set to true on the re-invocation after the user confirms. Default false.")
|
|
17540
|
+
},
|
|
17541
|
+
async ({ fotoId, brandId: inputBrandId, confirm }) => {
|
|
17542
|
+
const tenantId = session.requireTenant();
|
|
17543
|
+
const brandId = inputBrandId ?? session.requireBrand();
|
|
17544
|
+
const ctx = await buildContext(session, brandId, { confirmationGranted: confirm === true });
|
|
17545
|
+
const actor = {
|
|
17546
|
+
uid: ctx.user.uid,
|
|
17547
|
+
nombre: ctx.user.nombre ?? ctx.user.uid,
|
|
17548
|
+
clientType: ctx.user.clientType ?? "mcp_client",
|
|
17549
|
+
clientMetadata: ctx.user.clientMetadata ?? null
|
|
17550
|
+
};
|
|
17551
|
+
const result = await dispatchWithContract({
|
|
17552
|
+
contract: photoDescarterContract,
|
|
17553
|
+
helper: photoDescarter,
|
|
17554
|
+
callable: callPhotoDescarter,
|
|
17555
|
+
input: { tenantId, fotoId, actor },
|
|
17556
|
+
ctx
|
|
17557
|
+
});
|
|
17558
|
+
let payload;
|
|
17559
|
+
if (result.state === "success" && result.structuredOutput) {
|
|
17560
|
+
payload = result.structuredOutput;
|
|
17561
|
+
} else {
|
|
17562
|
+
payload = {
|
|
17563
|
+
ok: false,
|
|
17564
|
+
state: result.state,
|
|
17565
|
+
mensaje: result.text,
|
|
17566
|
+
...result.validationErrors && result.validationErrors.length > 0 ? { validationErrors: result.validationErrors } : {}
|
|
17567
|
+
};
|
|
17568
|
+
}
|
|
17569
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
17570
|
+
}
|
|
17571
|
+
);
|
|
17572
|
+
server.tool(
|
|
17573
|
+
"use_photo_as_is",
|
|
17574
|
+
"Mark a photo as editada using the original file (no Gemini edit). Validates state transition (procesando \u2192 editada). Use when the original photo is already good enough for publication.",
|
|
17575
|
+
{
|
|
17576
|
+
fotoId: import_zod65.z.string().describe("Photo identifier to mark as edited"),
|
|
17577
|
+
brandId: import_zod65.z.string().optional().describe("Brand identifier (defaults to session brand)")
|
|
17578
|
+
},
|
|
17579
|
+
async ({ fotoId, brandId: inputBrandId }) => {
|
|
17580
|
+
const tenantId = session.requireTenant();
|
|
17581
|
+
const brandId = inputBrandId ?? session.requireBrand();
|
|
17582
|
+
const ctx = await buildContext(session, brandId);
|
|
17583
|
+
const actor = {
|
|
17584
|
+
uid: ctx.user.uid,
|
|
17585
|
+
nombre: ctx.user.nombre ?? ctx.user.uid,
|
|
17586
|
+
clientType: ctx.user.clientType ?? "mcp_client",
|
|
17587
|
+
clientMetadata: ctx.user.clientMetadata ?? null
|
|
17588
|
+
};
|
|
17589
|
+
const result = await dispatchWithContract({
|
|
17590
|
+
contract: photoEditadaMarkerContract,
|
|
17591
|
+
helper: photoEditadaMarker,
|
|
17592
|
+
callable: callPhotoEditadaMarker,
|
|
17593
|
+
input: { tenantId, fotoId, actor },
|
|
17594
|
+
ctx
|
|
17595
|
+
});
|
|
17596
|
+
let payload;
|
|
17597
|
+
if (result.state === "success" && result.structuredOutput) {
|
|
17598
|
+
payload = result.structuredOutput;
|
|
17599
|
+
} else {
|
|
17600
|
+
payload = {
|
|
17601
|
+
ok: false,
|
|
17602
|
+
state: result.state,
|
|
17603
|
+
mensaje: result.text,
|
|
17604
|
+
...result.validationErrors && result.validationErrors.length > 0 ? { validationErrors: result.validationErrors } : {}
|
|
17605
|
+
};
|
|
17606
|
+
}
|
|
17607
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
17608
|
+
}
|
|
17609
|
+
);
|
|
17201
17610
|
}
|
|
17202
17611
|
|
|
17203
17612
|
// src/tools/marketing/content.ts
|
|
17204
|
-
var
|
|
17613
|
+
var import_zod66 = require("zod");
|
|
17205
17614
|
var _logOverride = null;
|
|
17206
17615
|
async function logToMcpLogs(entry) {
|
|
17207
17616
|
if (_logOverride) return _logOverride(entry);
|
|
@@ -17214,22 +17623,22 @@ async function logToMcpLogs(entry) {
|
|
|
17214
17623
|
} catch {
|
|
17215
17624
|
}
|
|
17216
17625
|
}
|
|
17217
|
-
var IncludeSchema2 =
|
|
17218
|
-
products:
|
|
17219
|
-
collections:
|
|
17220
|
-
articles:
|
|
17221
|
-
pages:
|
|
17626
|
+
var IncludeSchema2 = import_zod66.z.object({
|
|
17627
|
+
products: import_zod66.z.boolean().default(true),
|
|
17628
|
+
collections: import_zod66.z.boolean().default(true),
|
|
17629
|
+
articles: import_zod66.z.boolean().default(true),
|
|
17630
|
+
pages: import_zod66.z.boolean().default(false)
|
|
17222
17631
|
}).default({
|
|
17223
17632
|
products: true,
|
|
17224
17633
|
collections: true,
|
|
17225
17634
|
articles: true,
|
|
17226
17635
|
pages: false
|
|
17227
17636
|
});
|
|
17228
|
-
var LimitSchema2 =
|
|
17229
|
-
products:
|
|
17230
|
-
collections:
|
|
17231
|
-
articles:
|
|
17232
|
-
pages:
|
|
17637
|
+
var LimitSchema2 = import_zod66.z.object({
|
|
17638
|
+
products: import_zod66.z.number().int().min(0).max(20).default(5),
|
|
17639
|
+
collections: import_zod66.z.number().int().min(0).max(10).default(3),
|
|
17640
|
+
articles: import_zod66.z.number().int().min(0).max(20).default(5),
|
|
17641
|
+
pages: import_zod66.z.number().int().min(0).max(10).default(2)
|
|
17233
17642
|
}).default({ products: 5, collections: 3, articles: 5, pages: 2 });
|
|
17234
17643
|
function registerContentTools(server, session) {
|
|
17235
17644
|
server.tool(
|
|
@@ -17257,13 +17666,13 @@ MODOS (parametro mode):
|
|
|
17257
17666
|
|
|
17258
17667
|
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.`,
|
|
17259
17668
|
{
|
|
17260
|
-
brandId:
|
|
17261
|
-
contexto:
|
|
17262
|
-
fecha:
|
|
17669
|
+
brandId: import_zod66.z.string().optional().describe("ID de la brand"),
|
|
17670
|
+
contexto: import_zod66.z.string().min(1).describe("Parrafo, keyword o intencion"),
|
|
17671
|
+
fecha: import_zod66.z.string().describe("Fecha del contenido en ISO YYYY-MM-DD"),
|
|
17263
17672
|
include: IncludeSchema2.optional(),
|
|
17264
17673
|
limit: LimitSchema2.optional(),
|
|
17265
|
-
diversidad:
|
|
17266
|
-
mode:
|
|
17674
|
+
diversidad: import_zod66.z.boolean().default(true),
|
|
17675
|
+
mode: import_zod66.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)")
|
|
17267
17676
|
},
|
|
17268
17677
|
async ({ brandId: inputBrandId, contexto, fecha, include, limit, diversidad, mode }) => {
|
|
17269
17678
|
const tenantId = session.requireTenant();
|
|
@@ -17474,8 +17883,8 @@ function registerMarketingTools(server, session) {
|
|
|
17474
17883
|
"get_calendar",
|
|
17475
17884
|
"Lee el calendario editorial del mes. Muestra semanas con items planificados por plataforma.",
|
|
17476
17885
|
{
|
|
17477
|
-
brandId:
|
|
17478
|
-
mes:
|
|
17886
|
+
brandId: import_zod67.z.string().optional().describe("ID de la brand"),
|
|
17887
|
+
mes: import_zod67.z.string().optional().describe("Mes en formato YYYY-MM (default: mes actual)")
|
|
17479
17888
|
},
|
|
17480
17889
|
async ({ brandId: inputBrandId, mes }) => {
|
|
17481
17890
|
const tenantId = session.requireTenant();
|
|
@@ -17513,7 +17922,7 @@ function registerMarketingTools(server, session) {
|
|
|
17513
17922
|
"get_seo_snapshot",
|
|
17514
17923
|
"Read the latest Semrush SEO snapshot for a brand: rank, top keywords, opportunities, competitors.",
|
|
17515
17924
|
{
|
|
17516
|
-
brandId:
|
|
17925
|
+
brandId: import_zod67.z.string().optional().describe("Brand identifier within the tenant")
|
|
17517
17926
|
},
|
|
17518
17927
|
async ({ brandId: inputBrandId }) => {
|
|
17519
17928
|
const tenantId = session.requireTenant();
|
|
@@ -17549,8 +17958,8 @@ function registerMarketingTools(server, session) {
|
|
|
17549
17958
|
"get_photo_gallery",
|
|
17550
17959
|
"List marketing photos for a brand with per-state counts. Optionally filter by state.",
|
|
17551
17960
|
{
|
|
17552
|
-
brandId:
|
|
17553
|
-
estado:
|
|
17961
|
+
brandId: import_zod67.z.string().optional().describe("Brand identifier within the tenant"),
|
|
17962
|
+
estado: import_zod67.z.string().optional().describe("Optional photo state filter (nueva, procesando, editada, usada, descartada, error)")
|
|
17554
17963
|
},
|
|
17555
17964
|
async ({ brandId: inputBrandId, estado }) => {
|
|
17556
17965
|
const tenantId = session.requireTenant();
|
|
@@ -17586,7 +17995,7 @@ function registerMarketingTools(server, session) {
|
|
|
17586
17995
|
"generate_marketing_plan",
|
|
17587
17996
|
"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.",
|
|
17588
17997
|
{
|
|
17589
|
-
brandId:
|
|
17998
|
+
brandId: import_zod67.z.string().optional().describe("ID de la brand")
|
|
17590
17999
|
},
|
|
17591
18000
|
async ({ brandId: inputBrandId }) => {
|
|
17592
18001
|
const tenantId = session.requireTenant();
|
|
@@ -17600,18 +18009,24 @@ function registerMarketingTools(server, session) {
|
|
|
17600
18009
|
"save_marketing_plan",
|
|
17601
18010
|
"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).",
|
|
17602
18011
|
{
|
|
17603
|
-
brandId:
|
|
17604
|
-
plan:
|
|
18012
|
+
brandId: import_zod67.z.string().optional().describe("Brand ID. If omitted, uses the active brand from session context."),
|
|
18013
|
+
plan: import_zod67.z.record(import_zod67.z.string(), import_zod67.z.unknown()).describe("Full marketing plan object. Common fields: keywordsPrioritarios, gapsCompetencia, temporadas, tonoMarca, quickWins, coleccionesPriorizadas, blogStrategy.")
|
|
17605
18014
|
},
|
|
17606
18015
|
async ({ brandId: inputBrandId, plan }) => {
|
|
17607
18016
|
const tenantId = session.requireTenant();
|
|
17608
18017
|
const brandId = inputBrandId ?? session.requireBrand();
|
|
17609
18018
|
const ctx = await buildContext(session, brandId);
|
|
18019
|
+
const actor = {
|
|
18020
|
+
uid: ctx.user.uid,
|
|
18021
|
+
nombre: ctx.user.nombre ?? ctx.user.uid,
|
|
18022
|
+
clientType: ctx.user.clientType ?? "mcp_client",
|
|
18023
|
+
clientMetadata: ctx.user.clientMetadata ?? null
|
|
18024
|
+
};
|
|
17610
18025
|
const result = await dispatchWithContract({
|
|
17611
18026
|
contract: planWriterSaveContract,
|
|
17612
18027
|
helper: ({ db, ...rest }) => planWriter.save({ db, ...rest }),
|
|
17613
18028
|
callable: callPlanWriterSave,
|
|
17614
|
-
input: { tenantId, brandId, plan },
|
|
18029
|
+
input: { tenantId, brandId, plan, actor },
|
|
17615
18030
|
ctx
|
|
17616
18031
|
});
|
|
17617
18032
|
const payload = result.state === "success" ? result.structuredOutput : {
|
|
@@ -17629,9 +18044,9 @@ function registerMarketingTools(server, session) {
|
|
|
17629
18044
|
"update_marketing_plan_field",
|
|
17630
18045
|
"Actualiza UN campo del plan de marketing sin sobreescribir el resto. Merge parcial. Ideal para agregar coleccionesPriorizadas, actualizar quickWins, etc.",
|
|
17631
18046
|
{
|
|
17632
|
-
brandId:
|
|
17633
|
-
field:
|
|
17634
|
-
value:
|
|
18047
|
+
brandId: import_zod67.z.string().optional().describe("ID de la brand"),
|
|
18048
|
+
field: import_zod67.z.string().describe('Nombre del campo a actualizar (ej: "coleccionesPriorizadas", "quickWins", "temporadas")'),
|
|
18049
|
+
value: import_zod67.z.unknown().describe("Valor del campo")
|
|
17635
18050
|
},
|
|
17636
18051
|
async ({ brandId: inputBrandId, field, value }) => {
|
|
17637
18052
|
const tenantId = session.requireTenant();
|
|
@@ -17644,7 +18059,7 @@ function registerMarketingTools(server, session) {
|
|
|
17644
18059
|
"generate_brand_brief",
|
|
17645
18060
|
"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.",
|
|
17646
18061
|
{
|
|
17647
|
-
brandId:
|
|
18062
|
+
brandId: import_zod67.z.string().optional().describe("Brand ID. If omitted, uses the active brand from session context.")
|
|
17648
18063
|
},
|
|
17649
18064
|
async ({ brandId: inputBrandId }) => {
|
|
17650
18065
|
const tenantId = session.requireTenant();
|
|
@@ -17677,8 +18092,8 @@ function registerMarketingTools(server, session) {
|
|
|
17677
18092
|
"save_brand_brief",
|
|
17678
18093
|
"Save the Brand Brief at tenants/{tenantId}/marketing_config/{brandId}.brandBrief. Partial merge \u2014 only the brandBrief field is overwritten.",
|
|
17679
18094
|
{
|
|
17680
|
-
brandId:
|
|
17681
|
-
brandBrief:
|
|
18095
|
+
brandId: import_zod67.z.string().optional().describe("ID de la brand"),
|
|
18096
|
+
brandBrief: import_zod67.z.record(import_zod67.z.string(), import_zod67.z.unknown()).describe("Full Brand Brief object.")
|
|
17682
18097
|
},
|
|
17683
18098
|
async ({ brandId: inputBrandId, brandBrief }) => {
|
|
17684
18099
|
const tenantId = session.requireTenant();
|
|
@@ -17704,19 +18119,25 @@ function registerMarketingTools(server, session) {
|
|
|
17704
18119
|
"generate_weekly_content",
|
|
17705
18120
|
"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.",
|
|
17706
18121
|
{
|
|
17707
|
-
brandId:
|
|
17708
|
-
semana:
|
|
17709
|
-
modo:
|
|
18122
|
+
brandId: import_zod67.z.string().optional().describe("ID de la brand"),
|
|
18123
|
+
semana: import_zod67.z.number().optional().describe("Numero de semana (1-5). Default: semana actual del mes."),
|
|
18124
|
+
modo: import_zod67.z.enum(["planificar", "generar"]).optional().describe("'planificar' = proponer distribucion (plataforma+keyword por dia). 'generar' = generar contenido real para slots ya planificados. Default: 'planificar'.")
|
|
17710
18125
|
},
|
|
17711
18126
|
async ({ brandId: inputBrandId, semana, modo }) => {
|
|
17712
18127
|
const tenantId = session.requireTenant();
|
|
17713
18128
|
const brandId = inputBrandId ?? session.requireBrand();
|
|
17714
18129
|
const ctx = await buildContext(session, brandId);
|
|
18130
|
+
const actor = {
|
|
18131
|
+
uid: ctx.user.uid,
|
|
18132
|
+
nombre: ctx.user.nombre ?? ctx.user.uid,
|
|
18133
|
+
clientType: ctx.user.clientType ?? "mcp_client",
|
|
18134
|
+
clientMetadata: ctx.user.clientMetadata ?? null
|
|
18135
|
+
};
|
|
17715
18136
|
const result = await dispatchWithContract({
|
|
17716
18137
|
contract: weeklyContentBuilderContract,
|
|
17717
18138
|
helper: weeklyContentBuilder,
|
|
17718
18139
|
callable: callWeeklyContentBuilder,
|
|
17719
|
-
input: { tenantId, brandId, semana, modo },
|
|
18140
|
+
input: { tenantId, brandId, semana, modo, actor },
|
|
17720
18141
|
ctx
|
|
17721
18142
|
});
|
|
17722
18143
|
const payload = result.state === "success" ? result.structuredOutput?.payload ?? result.structuredOutput : {
|
|
@@ -17734,25 +18155,31 @@ function registerMarketingTools(server, session) {
|
|
|
17734
18155
|
|
|
17735
18156
|
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.`,
|
|
17736
18157
|
{
|
|
17737
|
-
brandId:
|
|
17738
|
-
plataforma:
|
|
17739
|
-
tipo:
|
|
17740
|
-
keyword:
|
|
17741
|
-
languageCode:
|
|
17742
|
-
fotoId:
|
|
17743
|
-
datos:
|
|
17744
|
-
calendarioItemRef:
|
|
18158
|
+
brandId: import_zod67.z.string().optional().describe("ID de la brand"),
|
|
18159
|
+
plataforma: import_zod67.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Plataforma destino"),
|
|
18160
|
+
tipo: import_zod67.z.string().optional().describe("Tipo de contenido (post, blog, carousel, etc.)"),
|
|
18161
|
+
keyword: import_zod67.z.string().optional().describe("Keyword target"),
|
|
18162
|
+
languageCode: import_zod67.z.string().optional().describe("Idioma (es/en)"),
|
|
18163
|
+
fotoId: import_zod67.z.string().optional().describe("ID de la foto a asociar"),
|
|
18164
|
+
datos: import_zod67.z.record(import_zod67.z.string(), import_zod67.z.unknown()).describe("Datos especificos de la plataforma (output de buildDatos*)"),
|
|
18165
|
+
calendarioItemRef: import_zod67.z.string().optional().describe("Referencia al item del calendario")
|
|
17745
18166
|
},
|
|
17746
18167
|
async ({ brandId: inputBrandId, plataforma, tipo, keyword, languageCode, fotoId, datos, calendarioItemRef }) => {
|
|
17747
18168
|
const tenantId = session.requireTenant();
|
|
17748
18169
|
const brandId = inputBrandId ?? session.requireBrand();
|
|
17749
18170
|
try {
|
|
17750
18171
|
const ctx = await buildContext(session, brandId);
|
|
18172
|
+
const actor = {
|
|
18173
|
+
uid: ctx.user.uid,
|
|
18174
|
+
nombre: ctx.user.nombre ?? ctx.user.uid,
|
|
18175
|
+
clientType: ctx.user.clientType ?? "mcp_client",
|
|
18176
|
+
clientMetadata: ctx.user.clientMetadata ?? null
|
|
18177
|
+
};
|
|
17751
18178
|
const result = await dispatchWithContract({
|
|
17752
18179
|
contract: contenidoWriterContract,
|
|
17753
18180
|
helper: (input) => contenidoWriter({ ...input, linkPipeline: linkContenidoAPasoPipeline }),
|
|
17754
18181
|
callable: callContenidoWriter,
|
|
17755
|
-
input: { tenantId, brandId, plataforma, tipo, keyword, languageCode, fotoId, datos, calendarioItemRef },
|
|
18182
|
+
input: { tenantId, brandId, plataforma, tipo, keyword, languageCode, fotoId, datos, calendarioItemRef, actor },
|
|
17756
18183
|
ctx
|
|
17757
18184
|
});
|
|
17758
18185
|
const payload = result.state === "success" ? result.structuredOutput : {
|
|
@@ -17777,13 +18204,13 @@ Usa para: corregir body, metaTitle, tags, fotoId, o cualquier campo sin tener qu
|
|
|
17777
18204
|
NO puede cambiar: tenantId, brandId, id (inmutables).
|
|
17778
18205
|
Si pasas campos dentro de "datos", se hace merge con los datos existentes (no los reemplaza entero).`,
|
|
17779
18206
|
{
|
|
17780
|
-
contenidoId:
|
|
17781
|
-
datos:
|
|
17782
|
-
fotoId:
|
|
17783
|
-
keyword:
|
|
17784
|
-
languageCode:
|
|
17785
|
-
estado:
|
|
17786
|
-
calendarioItemRef:
|
|
18207
|
+
contenidoId: import_zod67.z.string().describe("ID del doc en marketing_contenido"),
|
|
18208
|
+
datos: import_zod67.z.record(import_zod67.z.string(), import_zod67.z.unknown()).optional().describe('Campos de datos a actualizar (merge parcial sobre datos existentes). Ej: { body: "...", metaTitle: "..." }'),
|
|
18209
|
+
fotoId: import_zod67.z.string().nullable().optional().describe("Actualizar foto asociada"),
|
|
18210
|
+
keyword: import_zod67.z.string().nullable().optional().describe("Actualizar keyword"),
|
|
18211
|
+
languageCode: import_zod67.z.string().optional().describe("Actualizar idioma"),
|
|
18212
|
+
estado: import_zod67.z.enum(["borrador", "generado", "pendiente_aprobacion", "aprobado", "rechazado"]).optional().describe("Cambiar estado manualmente"),
|
|
18213
|
+
calendarioItemRef: import_zod67.z.string().nullable().optional().describe("Vincular a un slot del calendario")
|
|
17787
18214
|
},
|
|
17788
18215
|
async ({ contenidoId, datos: newDatos, fotoId, keyword, languageCode, estado, calendarioItemRef }) => {
|
|
17789
18216
|
const tenantId = session.requireTenant();
|
|
@@ -17817,19 +18244,19 @@ Si pasas campos dentro de "datos", se hace merge con los datos existentes (no lo
|
|
|
17817
18244
|
"add_calendar_slot",
|
|
17818
18245
|
"Add a NEW slot to the editorial calendar. To modify an existing slot use update_calendar_slot instead.",
|
|
17819
18246
|
{
|
|
17820
|
-
brandId:
|
|
17821
|
-
mes:
|
|
17822
|
-
semana:
|
|
17823
|
-
slot:
|
|
17824
|
-
dia:
|
|
17825
|
-
plataforma:
|
|
17826
|
-
tipo:
|
|
17827
|
-
keyword:
|
|
17828
|
-
tema:
|
|
17829
|
-
productoId:
|
|
17830
|
-
estado:
|
|
17831
|
-
locationId:
|
|
17832
|
-
locationNombre:
|
|
18247
|
+
brandId: import_zod67.z.string().describe('Brand ID (e.g. "ponch", "teleglobos").'),
|
|
18248
|
+
mes: import_zod67.z.string().describe('Calendar month in YYYY-MM format (e.g. "2026-05").'),
|
|
18249
|
+
semana: import_zod67.z.number().describe("Week number within the month (1-5)."),
|
|
18250
|
+
slot: import_zod67.z.object({
|
|
18251
|
+
dia: import_zod67.z.string().describe("Slot date in YYYY-MM-DD format. Must fall within the week's fechaInicio/fechaFin range."),
|
|
18252
|
+
plataforma: import_zod67.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Target publishing platform."),
|
|
18253
|
+
tipo: import_zod67.z.enum(["post", "blog", "carousel", "reel", "story", "review_response"]).describe("Content type."),
|
|
18254
|
+
keyword: import_zod67.z.string().describe("Primary keyword for the content."),
|
|
18255
|
+
tema: import_zod67.z.string().optional().describe("Content topic/theme. OMIT this field if it does not apply. Do NOT send empty string or null."),
|
|
18256
|
+
productoId: import_zod67.z.string().optional().describe("Linked product ID. OMIT this field if no product is linked."),
|
|
18257
|
+
estado: import_zod67.z.enum(["planificado", "pre_aprobado", "generado", "revisar", "aprobado", "publicado", "rechazado"]).optional().describe('Initial status. OMIT to use default "planificado".'),
|
|
18258
|
+
locationId: import_zod67.z.string().optional().describe("GBP location ID \u2014 only when plataforma=gbp AND tenant is multi-location. OMIT otherwise."),
|
|
18259
|
+
locationNombre: import_zod67.z.string().optional().describe("Human-readable GBP location name. OMIT if locationId is not provided.")
|
|
17833
18260
|
}).describe("New slot data.")
|
|
17834
18261
|
},
|
|
17835
18262
|
async ({ brandId, mes, semana, slot }) => {
|
|
@@ -17855,27 +18282,27 @@ Si pasas campos dentro de "datos", se hace merge con los datos existentes (no lo
|
|
|
17855
18282
|
"update_calendar_slot",
|
|
17856
18283
|
"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.",
|
|
17857
18284
|
{
|
|
17858
|
-
brandId:
|
|
17859
|
-
mes:
|
|
17860
|
-
semana:
|
|
17861
|
-
slotIndex:
|
|
17862
|
-
cambios:
|
|
17863
|
-
dia:
|
|
17864
|
-
plataforma:
|
|
17865
|
-
tipo:
|
|
17866
|
-
keyword:
|
|
17867
|
-
tema:
|
|
17868
|
-
productoId:
|
|
17869
|
-
estado:
|
|
17870
|
-
contenidoRef:
|
|
17871
|
-
fotoIdAsignada:
|
|
17872
|
-
notas:
|
|
17873
|
-
locationId:
|
|
17874
|
-
locationNombre:
|
|
18285
|
+
brandId: import_zod67.z.string().optional().describe("Brand ID. If omitted, uses the active brand from session context."),
|
|
18286
|
+
mes: import_zod67.z.string().describe("Calendar month in YYYY-MM format."),
|
|
18287
|
+
semana: import_zod67.z.number().describe("Week number within the month (1-5)."),
|
|
18288
|
+
slotIndex: import_zod67.z.number().describe("Slot index (0-based). Must point to an existing slot \u2014 to add new slots use add_calendar_slot."),
|
|
18289
|
+
cambios: import_zod67.z.object({
|
|
18290
|
+
dia: import_zod67.z.string().nullable().optional().describe("Slot date in YYYY-MM-DD. OMIT if not changing date. Use null to explicitly clear."),
|
|
18291
|
+
plataforma: import_zod67.z.enum(["gbp", "shopify_blog", "instagram", "review"]).nullable().optional().describe("OMIT if not changing platform."),
|
|
18292
|
+
tipo: import_zod67.z.enum(["post", "blog", "carousel", "reel", "story", "review_response"]).nullable().optional().describe("OMIT if not changing content type."),
|
|
18293
|
+
keyword: import_zod67.z.string().nullable().optional().describe("OMIT if not changing keyword."),
|
|
18294
|
+
tema: import_zod67.z.string().nullable().optional().describe("OMIT if not changing topic."),
|
|
18295
|
+
productoId: import_zod67.z.string().nullable().optional().describe("OMIT if not changing linked product. Use null to unlink."),
|
|
18296
|
+
estado: import_zod67.z.enum(["planificado", "pre_aprobado", "generado", "revisar", "aprobado", "publicado", "rechazado"]).optional().describe("OMIT if not changing status manually."),
|
|
18297
|
+
contenidoRef: import_zod67.z.string().nullable().optional().describe("OMIT \u2014 managed by the system, not by callers."),
|
|
18298
|
+
fotoIdAsignada: import_zod67.z.string().nullable().optional().describe("Use assign_photo_to_content for photos. OMIT here."),
|
|
18299
|
+
notas: import_zod67.z.array(NotaCalendarioSchema).optional().describe("Append-only notes for the slot."),
|
|
18300
|
+
locationId: import_zod67.z.string().nullable().optional().describe("GBP location ID \u2014 only when plataforma=gbp."),
|
|
18301
|
+
locationNombre: import_zod67.z.string().nullable().optional().describe("Human-readable GBP location name (for UI).")
|
|
17875
18302
|
}).strict().describe("Fields to update on the slot. Only declared fields accepted; unknown fields are rejected. OMIT any field you are not changing."),
|
|
17876
|
-
accionContenidoExistente:
|
|
17877
|
-
|
|
17878
|
-
|
|
18303
|
+
accionContenidoExistente: import_zod67.z.union([
|
|
18304
|
+
import_zod67.z.enum(["descartar", "nuevo_slot", "mantener"]),
|
|
18305
|
+
import_zod67.z.string().regex(/^mover:semana:\d+:slot:\d+$/, "Format: mover:semana:N:slot:M")
|
|
17879
18306
|
]).optional().describe(
|
|
17880
18307
|
'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).'
|
|
17881
18308
|
)
|
|
@@ -17920,9 +18347,9 @@ ESCRIBE EN DOS LUGARES:
|
|
|
17920
18347
|
|
|
17921
18348
|
NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agenda, no en el post.`,
|
|
17922
18349
|
{
|
|
17923
|
-
contenidoRef:
|
|
17924
|
-
fotoId:
|
|
17925
|
-
calendarioItemRef:
|
|
18350
|
+
contenidoRef: import_zod67.z.string().describe("ID del doc en marketing_contenido"),
|
|
18351
|
+
fotoId: import_zod67.z.string().describe("ID de la foto en marketing_fotos (estado editada)"),
|
|
18352
|
+
calendarioItemRef: import_zod67.z.string().optional().describe('Ref del slot (formato "semana:N:slot:M") para actualizar fotoIdAsignada')
|
|
17926
18353
|
},
|
|
17927
18354
|
async ({ contenidoRef, fotoId, calendarioItemRef }) => {
|
|
17928
18355
|
const tenantId = session.requireTenant();
|
|
@@ -17948,9 +18375,9 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
17948
18375
|
"approve_content",
|
|
17949
18376
|
"Approve marketing content for publication. Validates state transition (pendiente_aprobacion \u2192 aprobado). Requires confirmation: first call returns state=pending_confirmation; pass confirm=true on the second call to execute.",
|
|
17950
18377
|
{
|
|
17951
|
-
contenidoId:
|
|
17952
|
-
brandId:
|
|
17953
|
-
confirm:
|
|
18378
|
+
contenidoId: import_zod67.z.string().describe("Marketing content document id"),
|
|
18379
|
+
brandId: import_zod67.z.string().optional().describe("Brand identifier (defaults to session brand)"),
|
|
18380
|
+
confirm: import_zod67.z.boolean().optional().describe("Set to true on the re-invocation after the user confirms. Default false.")
|
|
17954
18381
|
},
|
|
17955
18382
|
async ({ contenidoId, brandId: inputBrandId, confirm }) => {
|
|
17956
18383
|
const tenantId = session.requireTenant();
|
|
@@ -17988,10 +18415,10 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
17988
18415
|
"reject_content",
|
|
17989
18416
|
"Reject marketing content with a reason. Validates state transition (pendiente_aprobacion \u2192 rechazado). Reversible via rechazado \u2192 editado \u2192 pendiente_aprobacion \u2192 aprobado. Requires confirmation: first call returns state=pending_confirmation; pass confirm=true on the second call to execute.",
|
|
17990
18417
|
{
|
|
17991
|
-
contenidoId:
|
|
17992
|
-
motivo:
|
|
17993
|
-
brandId:
|
|
17994
|
-
confirm:
|
|
18418
|
+
contenidoId: import_zod67.z.string().describe("Marketing content document id"),
|
|
18419
|
+
motivo: import_zod67.z.string().describe("Reason for rejection (required, surfaced to tenant)"),
|
|
18420
|
+
brandId: import_zod67.z.string().optional().describe("Brand identifier (defaults to session brand)"),
|
|
18421
|
+
confirm: import_zod67.z.boolean().optional().describe("Set to true on the re-invocation after the user confirms. Default false.")
|
|
17995
18422
|
},
|
|
17996
18423
|
async ({ contenidoId, motivo, brandId: inputBrandId, confirm }) => {
|
|
17997
18424
|
const tenantId = session.requireTenant();
|
|
@@ -18029,7 +18456,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
18029
18456
|
"get_collections",
|
|
18030
18457
|
"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.",
|
|
18031
18458
|
{
|
|
18032
|
-
brandId:
|
|
18459
|
+
brandId: import_zod67.z.string().optional().describe("Brand identifier within the tenant")
|
|
18033
18460
|
},
|
|
18034
18461
|
async ({ brandId: inputBrandId }) => {
|
|
18035
18462
|
const tenantId = session.requireTenant();
|
|
@@ -18060,7 +18487,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
18060
18487
|
"save_collection_suggestions",
|
|
18061
18488
|
"Guarda sugerencias SEO para colecciones de Shopify. El tenant las aprueba en la UI.",
|
|
18062
18489
|
{
|
|
18063
|
-
brandId:
|
|
18490
|
+
brandId: import_zod67.z.string().optional().describe("ID de la brand"),
|
|
18064
18491
|
suggestions: CollectionSuggestionsInputArraySchema.describe("Array de sugerencias SEO. Cada una con max 60 chars en metaTitle, max 158 en metaDescription, max 125 en imageAlt")
|
|
18065
18492
|
},
|
|
18066
18493
|
async ({ brandId: inputBrandId, suggestions }) => {
|
|
@@ -18086,7 +18513,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
18086
18513
|
}
|
|
18087
18514
|
|
|
18088
18515
|
// src/tools/martin.ts
|
|
18089
|
-
var
|
|
18516
|
+
var import_zod68 = require("zod");
|
|
18090
18517
|
function renderResult(result) {
|
|
18091
18518
|
const payload = {
|
|
18092
18519
|
state: result.state,
|
|
@@ -18102,16 +18529,16 @@ function registerMartinTools(server, session) {
|
|
|
18102
18529
|
"recordar_memoria",
|
|
18103
18530
|
"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.",
|
|
18104
18531
|
{
|
|
18105
|
-
tipo:
|
|
18532
|
+
tipo: import_zod68.z.enum(["preferencia", "regla", "patron", "aversion"]).describe(
|
|
18106
18533
|
"Memory type: 'preferencia' (personal preference), 'regla' (operational rule), 'patron' (observed behavioral pattern), 'aversion' (explicit do-not-do rule)."
|
|
18107
18534
|
),
|
|
18108
|
-
categoria:
|
|
18109
|
-
contenido:
|
|
18110
|
-
origen:
|
|
18111
|
-
tipo:
|
|
18112
|
-
conversacionId:
|
|
18113
|
-
cardId:
|
|
18114
|
-
rationale:
|
|
18535
|
+
categoria: import_zod68.z.enum(["compras", "produccion", "dispatch", "ventas", "marketing", "operacion", "personal", "delegacion"]).describe("Business area this memory applies to."),
|
|
18536
|
+
contenido: import_zod68.z.string().min(3).max(500).describe("Memory content in the user's own words (3-500 chars)."),
|
|
18537
|
+
origen: import_zod68.z.object({
|
|
18538
|
+
tipo: import_zod68.z.enum(["conversacion", "feedback_card", "inferido_automatico"]),
|
|
18539
|
+
conversacionId: import_zod68.z.string().nullable(),
|
|
18540
|
+
cardId: import_zod68.z.string().nullable(),
|
|
18541
|
+
rationale: import_zod68.z.array(import_zod68.z.string()).optional()
|
|
18115
18542
|
}).describe("Where this memory was inferred from (conversation, card feedback, or auto-inferred).")
|
|
18116
18543
|
},
|
|
18117
18544
|
async ({ tipo, categoria, contenido, origen }) => {
|
|
@@ -18130,9 +18557,9 @@ function registerMartinTools(server, session) {
|
|
|
18130
18557
|
"olvidar_memoria",
|
|
18131
18558
|
"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.",
|
|
18132
18559
|
{
|
|
18133
|
-
memoriaId:
|
|
18134
|
-
motivo:
|
|
18135
|
-
confirm:
|
|
18560
|
+
memoriaId: import_zod68.z.string().min(1).describe("Memory document ID to archive."),
|
|
18561
|
+
motivo: import_zod68.z.string().optional().describe("Optional reason for archiving (logged for audit)."),
|
|
18562
|
+
confirm: import_zod68.z.boolean().optional().describe("Set to true on the re-invocation after the user confirms. Default false.")
|
|
18136
18563
|
},
|
|
18137
18564
|
async ({ memoriaId, motivo, confirm }) => {
|
|
18138
18565
|
const ctx = await buildContext(session, null, { confirmationGranted: confirm === true });
|
|
@@ -18150,25 +18577,25 @@ function registerMartinTools(server, session) {
|
|
|
18150
18577
|
"programar_rutina",
|
|
18151
18578
|
"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.",
|
|
18152
18579
|
{
|
|
18153
|
-
uidDestinatario:
|
|
18154
|
-
tipo:
|
|
18155
|
-
frecuencia:
|
|
18156
|
-
config:
|
|
18157
|
-
diaSemana:
|
|
18158
|
-
diaMes:
|
|
18159
|
-
hora:
|
|
18160
|
-
fechaPuntual:
|
|
18580
|
+
uidDestinatario: import_zod68.z.string().optional().describe("User ID who should receive the routine output. OMIT to default to the calling user (most common case)."),
|
|
18581
|
+
tipo: import_zod68.z.enum(["reporte", "recordatorio", "accion_delegada", "publicacion", "compra_sugerida"]).describe("Routine type from canonical TipoRutinaEnum."),
|
|
18582
|
+
frecuencia: import_zod68.z.enum(["diaria", "semanal", "quincenal", "mensual", "trimestral", "puntual"]).describe("Execution cadence."),
|
|
18583
|
+
config: import_zod68.z.object({
|
|
18584
|
+
diaSemana: import_zod68.z.number().int().min(0).max(6).nullable(),
|
|
18585
|
+
diaMes: import_zod68.z.number().int().min(1).max(31).nullable(),
|
|
18586
|
+
hora: import_zod68.z.string().regex(/^\d{2}:\d{2}$/),
|
|
18587
|
+
fechaPuntual: import_zod68.z.string().datetime({ offset: true }).nullable()
|
|
18161
18588
|
}).describe("Schedule configuration. Match fields to the frecuencia (semanal needs diaSemana; mensual needs diaMes; puntual needs fechaPuntual)."),
|
|
18162
|
-
accion:
|
|
18163
|
-
tool:
|
|
18164
|
-
params:
|
|
18589
|
+
accion: import_zod68.z.object({
|
|
18590
|
+
tool: import_zod68.z.string().min(1),
|
|
18591
|
+
params: import_zod68.z.record(import_zod68.z.string(), import_zod68.z.unknown())
|
|
18165
18592
|
}).describe("Action to execute on each fire (MCP tool name + params)."),
|
|
18166
|
-
origen:
|
|
18167
|
-
tipo:
|
|
18168
|
-
conversacionId:
|
|
18169
|
-
cardId:
|
|
18593
|
+
origen: import_zod68.z.object({
|
|
18594
|
+
tipo: import_zod68.z.enum(["conversacion", "feedback_card", "configurada_explicito"]),
|
|
18595
|
+
conversacionId: import_zod68.z.string().nullable(),
|
|
18596
|
+
cardId: import_zod68.z.string().nullable()
|
|
18170
18597
|
}).describe("Where this routine was scheduled from."),
|
|
18171
|
-
confirm:
|
|
18598
|
+
confirm: import_zod68.z.boolean().optional().describe("Set to true on the re-invocation after the user confirms. Default false.")
|
|
18172
18599
|
},
|
|
18173
18600
|
async ({ uidDestinatario, tipo, frecuencia, config, accion, origen, confirm }) => {
|
|
18174
18601
|
const ctx = await buildContext(session, null, { confirmationGranted: confirm === true });
|
|
@@ -18197,8 +18624,8 @@ function registerMartinTools(server, session) {
|
|
|
18197
18624
|
"pausar_rutina",
|
|
18198
18625
|
"Pause an active routine temporarily. Can be resumed later by re-enabling it.",
|
|
18199
18626
|
{
|
|
18200
|
-
rutinaId:
|
|
18201
|
-
motivo:
|
|
18627
|
+
rutinaId: import_zod68.z.string().min(1).describe("Routine document ID to pause."),
|
|
18628
|
+
motivo: import_zod68.z.string().optional().describe("Optional reason for pausing (logged for audit).")
|
|
18202
18629
|
},
|
|
18203
18630
|
async ({ rutinaId, motivo }) => {
|
|
18204
18631
|
const ctx = await buildContext(session, null);
|
|
@@ -18224,9 +18651,9 @@ function registerMartinTools(server, session) {
|
|
|
18224
18651
|
"archivar_rutina",
|
|
18225
18652
|
"Archive a routine that no longer applies. The routine is preserved for audit purposes but will NOT be executed. Requires confirmation.",
|
|
18226
18653
|
{
|
|
18227
|
-
rutinaId:
|
|
18228
|
-
motivo:
|
|
18229
|
-
confirm:
|
|
18654
|
+
rutinaId: import_zod68.z.string().min(1).describe("Routine document ID to archive (will not execute again)."),
|
|
18655
|
+
motivo: import_zod68.z.string().optional().describe("Optional reason for archiving (logged for audit)."),
|
|
18656
|
+
confirm: import_zod68.z.boolean().optional().describe("Set to true on the re-invocation after the user confirms. Default false.")
|
|
18230
18657
|
},
|
|
18231
18658
|
async ({ rutinaId, motivo, confirm }) => {
|
|
18232
18659
|
const ctx = await buildContext(session, null, { confirmationGranted: confirm === true });
|