ponch-mcp-server 1.0.75 → 1.0.76
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 +354 -249
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2726,7 +2726,7 @@ function registerCoreTools(server, session) {
|
|
|
2726
2726
|
}
|
|
2727
2727
|
|
|
2728
2728
|
// src/tools/marketing.ts
|
|
2729
|
-
var
|
|
2729
|
+
var import_zod38 = require("zod");
|
|
2730
2730
|
|
|
2731
2731
|
// ../packages/marketing-business-logic/dist/index.js
|
|
2732
2732
|
var import_firebase_admin2 = require("firebase-admin");
|
|
@@ -2738,18 +2738,20 @@ var import_firebase_admin4 = require("firebase-admin");
|
|
|
2738
2738
|
var import_firebase_admin5 = require("firebase-admin");
|
|
2739
2739
|
var import_firebase_admin6 = require("firebase-admin");
|
|
2740
2740
|
var import_zod30 = require("zod");
|
|
2741
|
-
var import_zod31 = require("zod");
|
|
2742
2741
|
var import_firebase_admin7 = require("firebase-admin");
|
|
2742
|
+
var import_zod31 = require("zod");
|
|
2743
2743
|
var import_zod32 = require("zod");
|
|
2744
2744
|
var import_firebase_admin8 = require("firebase-admin");
|
|
2745
|
+
var import_zod33 = require("zod");
|
|
2745
2746
|
var import_firebase_admin9 = require("firebase-admin");
|
|
2747
|
+
var import_firebase_admin10 = require("firebase-admin");
|
|
2746
2748
|
var import_firestore3 = require("firebase-admin/firestore");
|
|
2747
2749
|
var import_firestore4 = require("firebase-admin/firestore");
|
|
2748
2750
|
var import_firestore5 = require("firebase-admin/firestore");
|
|
2749
2751
|
var import_firestore6 = require("firebase-admin/firestore");
|
|
2750
|
-
var import_zod33 = require("zod");
|
|
2751
|
-
var import_firestore7 = require("firebase-admin/firestore");
|
|
2752
2752
|
var import_zod34 = require("zod");
|
|
2753
|
+
var import_firestore7 = require("firebase-admin/firestore");
|
|
2754
|
+
var import_zod35 = require("zod");
|
|
2753
2755
|
var import_firestore8 = require("firebase-admin/firestore");
|
|
2754
2756
|
var RULE_NEGATIVES = {
|
|
2755
2757
|
allowFaces: "no people, no faces, no hands",
|
|
@@ -3506,6 +3508,95 @@ async function contenidoUpdater(input) {
|
|
|
3506
3508
|
camposActualizados: Object.keys(update).filter((k) => k !== "updatedAt")
|
|
3507
3509
|
};
|
|
3508
3510
|
}
|
|
3511
|
+
async function addCalendarSlot(input) {
|
|
3512
|
+
const { db, tenantId, brandId, mes, semana, slot } = input;
|
|
3513
|
+
const calQuery = await db.collection("tenants").doc(tenantId).collection("marketing_calendario").where("brandId", "==", brandId).where("mes", "==", mes).limit(1).get();
|
|
3514
|
+
if (calQuery.empty) {
|
|
3515
|
+
return { ok: false, error: `Calendario ${mes} no encontrado para brand ${brandId}` };
|
|
3516
|
+
}
|
|
3517
|
+
const calDocRef = calQuery.docs[0].ref;
|
|
3518
|
+
let resultSlotIndex = -1;
|
|
3519
|
+
await db.runTransaction(async (tx) => {
|
|
3520
|
+
const freshSnap = await tx.get(calDocRef);
|
|
3521
|
+
if (!freshSnap.exists) {
|
|
3522
|
+
throw new Error(`addCalendarSlot: calendario ${calDocRef.id} desapareci\xF3`);
|
|
3523
|
+
}
|
|
3524
|
+
const freshCal = freshSnap.data();
|
|
3525
|
+
const freshSemanas = freshCal.semanas ?? [];
|
|
3526
|
+
const freshSemana = freshSemanas[semana - 1];
|
|
3527
|
+
if (!freshSemana) {
|
|
3528
|
+
throw new Error(`Semana ${semana} no existe en el calendario ${mes}`);
|
|
3529
|
+
}
|
|
3530
|
+
const freshItems = freshSemana.items ?? [];
|
|
3531
|
+
freshItems.push({
|
|
3532
|
+
...slot,
|
|
3533
|
+
estado: slot.estado ?? ESTADO_CALENDARIO_ITEM.PLANIFICADO
|
|
3534
|
+
});
|
|
3535
|
+
resultSlotIndex = freshItems.length - 1;
|
|
3536
|
+
freshSemana.items = freshItems;
|
|
3537
|
+
freshSemanas[semana - 1] = freshSemana;
|
|
3538
|
+
tx.update(calDocRef, {
|
|
3539
|
+
semanas: freshSemanas,
|
|
3540
|
+
updatedAt: import_firebase_admin6.firestore.FieldValue.serverTimestamp()
|
|
3541
|
+
});
|
|
3542
|
+
});
|
|
3543
|
+
return { ok: true, slotIndex: resultSlotIndex };
|
|
3544
|
+
}
|
|
3545
|
+
var SlotSchema = import_zod30.z.object({
|
|
3546
|
+
dia: import_zod30.z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Formato YYYY-MM-DD"),
|
|
3547
|
+
plataforma: import_zod30.z.enum(["gbp", "shopify_blog", "instagram", "review"]),
|
|
3548
|
+
tipo: import_zod30.z.enum(["post", "blog", "carousel", "reel", "story", "review_response"]),
|
|
3549
|
+
keyword: import_zod30.z.string().min(1),
|
|
3550
|
+
tema: import_zod30.z.string().optional(),
|
|
3551
|
+
productoId: import_zod30.z.string().optional(),
|
|
3552
|
+
estado: import_zod30.z.enum(["planificado", "pre_aprobado", "generado", "revisar", "aprobado", "publicado", "rechazado"]).optional(),
|
|
3553
|
+
locationId: import_zod30.z.string().optional(),
|
|
3554
|
+
locationNombre: import_zod30.z.string().optional()
|
|
3555
|
+
});
|
|
3556
|
+
var ParamsSchema2 = import_zod30.z.object({
|
|
3557
|
+
tenantId: import_zod30.z.string().min(1),
|
|
3558
|
+
brandId: import_zod30.z.string().min(1),
|
|
3559
|
+
mes: import_zod30.z.string().regex(/^\d{4}-\d{2}$/, "Formato YYYY-MM"),
|
|
3560
|
+
semana: import_zod30.z.number().int().min(1).max(5),
|
|
3561
|
+
slot: SlotSchema
|
|
3562
|
+
});
|
|
3563
|
+
var OutputSchema2 = import_zod30.z.discriminatedUnion("ok", [
|
|
3564
|
+
import_zod30.z.object({ ok: import_zod30.z.literal(true), slotIndex: import_zod30.z.number().int() }),
|
|
3565
|
+
import_zod30.z.object({ ok: import_zod30.z.literal(false), error: import_zod30.z.string() })
|
|
3566
|
+
]);
|
|
3567
|
+
var rawContract2 = {
|
|
3568
|
+
name: "add_calendar_slot",
|
|
3569
|
+
description: "Agrega un slot nuevo al calendario editorial. NO modifica slots existentes \u2014 para eso usa update_calendar_slot.",
|
|
3570
|
+
paramsSchema: ParamsSchema2,
|
|
3571
|
+
outputSchema: OutputSchema2,
|
|
3572
|
+
requiresConfirmation: false,
|
|
3573
|
+
destructive: false,
|
|
3574
|
+
affectsPublication: false,
|
|
3575
|
+
affectsExternal: false,
|
|
3576
|
+
martinSummaryTemplate: (input, output, locale) => {
|
|
3577
|
+
if (!output.ok) {
|
|
3578
|
+
return locale === "en" ? `I couldn't add the slot: ${output.error}` : `No pude agregar el slot: ${output.error}`;
|
|
3579
|
+
}
|
|
3580
|
+
return locale === "en" ? `Added a slot in week ${input.semana} of ${input.mes}.` : `Agregu\xE9 un slot en la semana ${input.semana} de ${input.mes}.`;
|
|
3581
|
+
},
|
|
3582
|
+
auditAction: "marketing.calendario.slot.agregar",
|
|
3583
|
+
extractTargetPath: (input, output) => {
|
|
3584
|
+
if (!output.ok) return "";
|
|
3585
|
+
return `tenants/${input.tenantId}/marketing_calendario/(brand=${input.brandId},mes=${input.mes}).semana=${input.semana}.slot=${output.slotIndex}`;
|
|
3586
|
+
},
|
|
3587
|
+
extractChanges: (input, output) => ({
|
|
3588
|
+
before: null,
|
|
3589
|
+
after: output.ok ? input.slot : null
|
|
3590
|
+
}),
|
|
3591
|
+
quotasConsumed: [],
|
|
3592
|
+
permissionScope: "module",
|
|
3593
|
+
permissionKey: "marketing",
|
|
3594
|
+
permissionAction: "editar",
|
|
3595
|
+
sideEffects: ["writes_firestore", "updates_calendar_slot"]
|
|
3596
|
+
};
|
|
3597
|
+
var addCalendarSlotContract = MartinContractSchema.parse(
|
|
3598
|
+
rawContract2
|
|
3599
|
+
);
|
|
3509
3600
|
var CAMPOS_SEMANTICOS = ["keyword", "tema", "plataforma", "tipo"];
|
|
3510
3601
|
function mapContenidoEstadoToSlotEstado(contenidoEstado) {
|
|
3511
3602
|
switch (contenidoEstado) {
|
|
@@ -3542,13 +3633,16 @@ async function calendarSlotUpdater(input) {
|
|
|
3542
3633
|
return { ok: false, error: `Semana ${semana} no existe` };
|
|
3543
3634
|
}
|
|
3544
3635
|
const items = semanaData.items ?? [];
|
|
3545
|
-
|
|
3636
|
+
if (slotIndex >= items.length) {
|
|
3637
|
+
return {
|
|
3638
|
+
ok: false,
|
|
3639
|
+
error: `slotIndex ${slotIndex} no existe (semana ${semana} tiene ${items.length} slots). Para agregar un slot nuevo usa add_calendar_slot.`,
|
|
3640
|
+
code: "SLOT_NOT_FOUND"
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3546
3643
|
const tocaSemantica = CAMPOS_SEMANTICOS.some(
|
|
3547
3644
|
(campo) => campo in cambios && cambios[campo] !== void 0
|
|
3548
3645
|
);
|
|
3549
|
-
if (isNewSlot) {
|
|
3550
|
-
return handleAddSlot({ db, calDocRef, semana, cambios });
|
|
3551
|
-
}
|
|
3552
3646
|
const slotAnterior = items[slotIndex];
|
|
3553
3647
|
const oldContenidoRef = slotAnterior.contenidoRef ?? null;
|
|
3554
3648
|
if (oldContenidoRef && tocaSemantica && !accionContenidoExistente) {
|
|
@@ -3694,7 +3788,7 @@ async function calendarSlotUpdater(input) {
|
|
|
3694
3788
|
freshSemanas[semana - 1] = freshSemana;
|
|
3695
3789
|
tx.update(calDocRef, {
|
|
3696
3790
|
semanas: freshSemanas,
|
|
3697
|
-
updatedAt:
|
|
3791
|
+
updatedAt: import_firebase_admin7.firestore.FieldValue.serverTimestamp()
|
|
3698
3792
|
});
|
|
3699
3793
|
});
|
|
3700
3794
|
if (moveTarget && oldContenidoRef) {
|
|
@@ -3715,7 +3809,7 @@ async function calendarSlotUpdater(input) {
|
|
|
3715
3809
|
await c.ref.update({
|
|
3716
3810
|
estado: "descartado",
|
|
3717
3811
|
rechazadoMotivo: "Slot del calendario fue modificado",
|
|
3718
|
-
rechazadoAt:
|
|
3812
|
+
rechazadoAt: import_firebase_admin7.firestore.FieldValue.serverTimestamp()
|
|
3719
3813
|
});
|
|
3720
3814
|
descartados++;
|
|
3721
3815
|
} catch (err) {
|
|
@@ -3733,35 +3827,6 @@ async function calendarSlotUpdater(input) {
|
|
|
3733
3827
|
...movedTo ? { movedTo } : {}
|
|
3734
3828
|
};
|
|
3735
3829
|
}
|
|
3736
|
-
async function handleAddSlot(args) {
|
|
3737
|
-
const { db, calDocRef, semana, cambios } = args;
|
|
3738
|
-
let resultSlotIndex = -1;
|
|
3739
|
-
await db.runTransaction(async (tx) => {
|
|
3740
|
-
const freshSnap = await tx.get(calDocRef);
|
|
3741
|
-
if (!freshSnap.exists) {
|
|
3742
|
-
throw new Error(`handleAddSlot: calendario ${calDocRef.id} desaparecio`);
|
|
3743
|
-
}
|
|
3744
|
-
const freshCal = freshSnap.data();
|
|
3745
|
-
const freshSemanas = freshCal.semanas ?? [];
|
|
3746
|
-
const freshSemana = freshSemanas[semana - 1];
|
|
3747
|
-
if (!freshSemana) {
|
|
3748
|
-
throw new Error(`handleAddSlot: semana ${semana} desaparecio`);
|
|
3749
|
-
}
|
|
3750
|
-
const freshItems = freshSemana.items ?? [];
|
|
3751
|
-
freshItems.push({
|
|
3752
|
-
...cambios,
|
|
3753
|
-
estado: cambios.estado ?? ESTADO_CALENDARIO_ITEM.PLANIFICADO
|
|
3754
|
-
});
|
|
3755
|
-
resultSlotIndex = freshItems.length - 1;
|
|
3756
|
-
freshSemana.items = freshItems;
|
|
3757
|
-
freshSemanas[semana - 1] = freshSemana;
|
|
3758
|
-
tx.update(calDocRef, {
|
|
3759
|
-
semanas: freshSemanas,
|
|
3760
|
-
updatedAt: import_firebase_admin6.firestore.FieldValue.serverTimestamp()
|
|
3761
|
-
});
|
|
3762
|
-
});
|
|
3763
|
-
return { ok: true, action: "added", slotIndex: resultSlotIndex, descartados: 0 };
|
|
3764
|
-
}
|
|
3765
3830
|
async function handleNuevoSlot(args) {
|
|
3766
3831
|
const { db, calDocRef, semana, cambios, currentSlot } = args;
|
|
3767
3832
|
const dia = cambios.dia ?? currentSlot.dia ?? null;
|
|
@@ -3788,7 +3853,7 @@ async function handleNuevoSlot(args) {
|
|
|
3788
3853
|
freshSemanas[semana - 1] = freshSemana;
|
|
3789
3854
|
tx.update(calDocRef, {
|
|
3790
3855
|
semanas: freshSemanas,
|
|
3791
|
-
updatedAt:
|
|
3856
|
+
updatedAt: import_firebase_admin7.firestore.FieldValue.serverTimestamp()
|
|
3792
3857
|
});
|
|
3793
3858
|
});
|
|
3794
3859
|
return {
|
|
@@ -3798,35 +3863,35 @@ async function handleNuevoSlot(args) {
|
|
|
3798
3863
|
descartados: 0
|
|
3799
3864
|
};
|
|
3800
3865
|
}
|
|
3801
|
-
var
|
|
3802
|
-
tenantId:
|
|
3803
|
-
brandId:
|
|
3804
|
-
mes:
|
|
3805
|
-
semana:
|
|
3806
|
-
slotIndex:
|
|
3807
|
-
cambios:
|
|
3808
|
-
accionContenidoExistente:
|
|
3866
|
+
var ParamsSchema3 = import_zod31.z.object({
|
|
3867
|
+
tenantId: import_zod31.z.string().min(1),
|
|
3868
|
+
brandId: import_zod31.z.string().min(1),
|
|
3869
|
+
mes: import_zod31.z.string().regex(/^\d{4}-\d{2}$/, "Formato YYYY-MM"),
|
|
3870
|
+
semana: import_zod31.z.number().int().min(1).max(5),
|
|
3871
|
+
slotIndex: import_zod31.z.number().int().min(0),
|
|
3872
|
+
cambios: import_zod31.z.record(import_zod31.z.string(), import_zod31.z.unknown()),
|
|
3873
|
+
accionContenidoExistente: import_zod31.z.string().optional()
|
|
3809
3874
|
});
|
|
3810
|
-
var
|
|
3811
|
-
|
|
3812
|
-
ok:
|
|
3813
|
-
action:
|
|
3814
|
-
slotIndex:
|
|
3815
|
-
descartados:
|
|
3816
|
-
movedTo:
|
|
3875
|
+
var OutputSchema3 = import_zod31.z.discriminatedUnion("ok", [
|
|
3876
|
+
import_zod31.z.object({
|
|
3877
|
+
ok: import_zod31.z.literal(true),
|
|
3878
|
+
action: import_zod31.z.enum(["updated", "nuevo_slot", "moved"]),
|
|
3879
|
+
slotIndex: import_zod31.z.number().int(),
|
|
3880
|
+
descartados: import_zod31.z.number().int(),
|
|
3881
|
+
movedTo: import_zod31.z.string().optional()
|
|
3817
3882
|
}),
|
|
3818
|
-
|
|
3819
|
-
ok:
|
|
3820
|
-
error:
|
|
3821
|
-
code:
|
|
3822
|
-
opciones:
|
|
3883
|
+
import_zod31.z.object({
|
|
3884
|
+
ok: import_zod31.z.literal(false),
|
|
3885
|
+
error: import_zod31.z.string(),
|
|
3886
|
+
code: import_zod31.z.enum(["SLOT_NOT_FOUND", "ACCION_CONTENIDO_EXISTENTE_REQUIRED", "MOVE_TARGET_OCCUPIED", "MOVE_TARGET_INVALID"]).optional(),
|
|
3887
|
+
opciones: import_zod31.z.array(import_zod31.z.string()).optional()
|
|
3823
3888
|
})
|
|
3824
3889
|
]);
|
|
3825
|
-
var
|
|
3890
|
+
var rawContract3 = {
|
|
3826
3891
|
name: "update_calendar_slot",
|
|
3827
|
-
description: "
|
|
3828
|
-
paramsSchema:
|
|
3829
|
-
outputSchema:
|
|
3892
|
+
description: "MODIFICA un slot existente del calendario editorial. NO agrega slots nuevos \u2014 para eso usa add_calendar_slot. Si el slot ten\xEDa contenidoRef previo y los cambios tocan campos sem\xE1nticos (keyword/tema/plataforma/tipo), exige accionContenidoExistente con 4 opciones: descartar | mover:semana:N:slot:M | nuevo_slot | mantener.",
|
|
3893
|
+
paramsSchema: ParamsSchema3,
|
|
3894
|
+
outputSchema: OutputSchema3,
|
|
3830
3895
|
requiresConfirmation: false,
|
|
3831
3896
|
destructive: false,
|
|
3832
3897
|
// Mutación, pero reversible (puede deshacerse cambiando el slot).
|
|
@@ -3838,7 +3903,6 @@ var rawContract2 = {
|
|
|
3838
3903
|
return `No pude actualizar el slot: ${output.error}`;
|
|
3839
3904
|
}
|
|
3840
3905
|
const verb = {
|
|
3841
|
-
added: locale === "en" ? "added" : "agregu\xE9",
|
|
3842
3906
|
updated: locale === "en" ? "updated" : "actualic\xE9",
|
|
3843
3907
|
nuevo_slot: locale === "en" ? "created a new slot" : "cre\xE9 un slot nuevo",
|
|
3844
3908
|
moved: locale === "en" ? "moved" : "mov\xED"
|
|
@@ -3868,7 +3932,7 @@ var rawContract2 = {
|
|
|
3868
3932
|
sideEffects: ["writes_firestore", "updates_calendar_slot"]
|
|
3869
3933
|
};
|
|
3870
3934
|
var calendarSlotUpdaterContract = MartinContractSchema.parse(
|
|
3871
|
-
|
|
3935
|
+
rawContract3
|
|
3872
3936
|
);
|
|
3873
3937
|
async function getCalendar(input) {
|
|
3874
3938
|
const { db, tenantId, brandId, mes } = input;
|
|
@@ -3890,23 +3954,23 @@ async function getCalendar(input) {
|
|
|
3890
3954
|
calendario: { id: doc.id, ...doc.data() }
|
|
3891
3955
|
};
|
|
3892
3956
|
}
|
|
3893
|
-
var
|
|
3894
|
-
tenantId:
|
|
3895
|
-
brandId:
|
|
3896
|
-
mes:
|
|
3957
|
+
var ParamsSchema4 = import_zod32.z.object({
|
|
3958
|
+
tenantId: import_zod32.z.string().min(1),
|
|
3959
|
+
brandId: import_zod32.z.string().min(1),
|
|
3960
|
+
mes: import_zod32.z.string().regex(/^\d{4}-\d{2}$/, "Formato YYYY-MM")
|
|
3897
3961
|
});
|
|
3898
|
-
var
|
|
3899
|
-
ok:
|
|
3900
|
-
mes:
|
|
3901
|
-
brandId:
|
|
3902
|
-
calendario:
|
|
3903
|
-
mensaje:
|
|
3962
|
+
var OutputSchema4 = import_zod32.z.object({
|
|
3963
|
+
ok: import_zod32.z.literal(true),
|
|
3964
|
+
mes: import_zod32.z.string(),
|
|
3965
|
+
brandId: import_zod32.z.string(),
|
|
3966
|
+
calendario: import_zod32.z.record(import_zod32.z.string(), import_zod32.z.unknown()).nullable(),
|
|
3967
|
+
mensaje: import_zod32.z.string().optional()
|
|
3904
3968
|
});
|
|
3905
|
-
var
|
|
3969
|
+
var rawContract4 = {
|
|
3906
3970
|
name: "get_calendar",
|
|
3907
3971
|
description: "Lee el calendario editorial del mes para una brand. Retorna semanas con items planificados por plataforma.",
|
|
3908
|
-
paramsSchema:
|
|
3909
|
-
outputSchema:
|
|
3972
|
+
paramsSchema: ParamsSchema4,
|
|
3973
|
+
outputSchema: OutputSchema4,
|
|
3910
3974
|
requiresConfirmation: false,
|
|
3911
3975
|
destructive: false,
|
|
3912
3976
|
affectsPublication: false,
|
|
@@ -3928,7 +3992,7 @@ var rawContract3 = {
|
|
|
3928
3992
|
sideEffects: ["reads_firestore"]
|
|
3929
3993
|
};
|
|
3930
3994
|
var getCalendarContract = MartinContractSchema.parse(
|
|
3931
|
-
|
|
3995
|
+
rawContract4
|
|
3932
3996
|
);
|
|
3933
3997
|
var PLATAFORMA_A_FORMATO = {
|
|
3934
3998
|
gbp: "gbp_4_3",
|
|
@@ -3966,12 +4030,12 @@ async function photoAssigner(input) {
|
|
|
3966
4030
|
mediaVariante: {
|
|
3967
4031
|
url: varianteUrl,
|
|
3968
4032
|
formato,
|
|
3969
|
-
linkedAt:
|
|
4033
|
+
linkedAt: import_firebase_admin8.firestore.FieldValue.serverTimestamp(),
|
|
3970
4034
|
permalink: null,
|
|
3971
4035
|
// lo llenara publishToInstagram/etc en el futuro
|
|
3972
4036
|
mediaExternalId: null
|
|
3973
4037
|
},
|
|
3974
|
-
editadoAt:
|
|
4038
|
+
editadoAt: import_firebase_admin8.firestore.FieldValue.serverTimestamp()
|
|
3975
4039
|
};
|
|
3976
4040
|
let slotResolved = null;
|
|
3977
4041
|
if (calendarioItemRef) {
|
|
@@ -4021,7 +4085,7 @@ async function photoAssigner(input) {
|
|
|
4021
4085
|
tx.update(contenidoRefDoc, contenidoUpdatePayload);
|
|
4022
4086
|
tx.update(calDocRef, {
|
|
4023
4087
|
semanas,
|
|
4024
|
-
updatedAt:
|
|
4088
|
+
updatedAt: import_firebase_admin8.firestore.FieldValue.serverTimestamp()
|
|
4025
4089
|
});
|
|
4026
4090
|
});
|
|
4027
4091
|
} else {
|
|
@@ -4223,26 +4287,26 @@ var BRAND_BRIEF_SCHEMA_HINT = {
|
|
|
4223
4287
|
escenas: [{ id: string, nombre: string, promptHint: string }]
|
|
4224
4288
|
}`
|
|
4225
4289
|
};
|
|
4226
|
-
var
|
|
4227
|
-
tenantId:
|
|
4228
|
-
brandId:
|
|
4290
|
+
var ParamsSchema5 = import_zod33.z.object({
|
|
4291
|
+
tenantId: import_zod33.z.string().min(1),
|
|
4292
|
+
brandId: import_zod33.z.string().min(1)
|
|
4229
4293
|
});
|
|
4230
|
-
var
|
|
4231
|
-
|
|
4232
|
-
ok:
|
|
4294
|
+
var OutputSchema5 = import_zod33.z.discriminatedUnion("ok", [
|
|
4295
|
+
import_zod33.z.object({
|
|
4296
|
+
ok: import_zod33.z.literal(true),
|
|
4233
4297
|
/** Payload con instrucción + datos del negocio para que Claude genere el brief. */
|
|
4234
|
-
payload:
|
|
4298
|
+
payload: import_zod33.z.record(import_zod33.z.string(), import_zod33.z.unknown())
|
|
4235
4299
|
}),
|
|
4236
|
-
|
|
4237
|
-
ok:
|
|
4238
|
-
error:
|
|
4300
|
+
import_zod33.z.object({
|
|
4301
|
+
ok: import_zod33.z.literal(false),
|
|
4302
|
+
error: import_zod33.z.string()
|
|
4239
4303
|
})
|
|
4240
4304
|
]);
|
|
4241
|
-
var
|
|
4305
|
+
var rawContract5 = {
|
|
4242
4306
|
name: "generate_brand_brief",
|
|
4243
4307
|
description: "Prepara los datos del negocio (Shopify, SEO, GBP, site_content scrape, brand config, ubicaciones del tenant) para que el sistema genere un Brand Brief pre-llenado. NO escribe el brief \u2014 s\xF3lo arma el payload.",
|
|
4244
|
-
paramsSchema:
|
|
4245
|
-
outputSchema:
|
|
4308
|
+
paramsSchema: ParamsSchema5,
|
|
4309
|
+
outputSchema: OutputSchema5,
|
|
4246
4310
|
// Lectura pura, sin side effects de escritura ni publicación.
|
|
4247
4311
|
requiresConfirmation: false,
|
|
4248
4312
|
destructive: false,
|
|
@@ -4265,7 +4329,7 @@ var rawContract4 = {
|
|
|
4265
4329
|
sideEffects: ["reads_firestore"]
|
|
4266
4330
|
};
|
|
4267
4331
|
var brandBriefBuilderContract = MartinContractSchema.parse(
|
|
4268
|
-
|
|
4332
|
+
rawContract5
|
|
4269
4333
|
);
|
|
4270
4334
|
async function resolveLastImportId(db, tenantId, brandId) {
|
|
4271
4335
|
const configSnap = await db.collection("tenants").doc(tenantId).collection("marketing_config").doc(brandId).get();
|
|
@@ -4509,7 +4573,7 @@ async function contenidoWriter(input) {
|
|
|
4509
4573
|
await db.doc(`tenants/${tenantId}/marketing_contenido/${existente.id}`).update({
|
|
4510
4574
|
estado: "descartado",
|
|
4511
4575
|
rechazadoMotivo: "Reemplazado por contenido nuevo para el mismo slot",
|
|
4512
|
-
rechazadoAt:
|
|
4576
|
+
rechazadoAt: import_firebase_admin9.firestore.FieldValue.serverTimestamp()
|
|
4513
4577
|
});
|
|
4514
4578
|
descartados++;
|
|
4515
4579
|
}
|
|
@@ -4568,7 +4632,7 @@ async function contenidoWriter(input) {
|
|
|
4568
4632
|
mediaUrl: null,
|
|
4569
4633
|
calendarioItemRef: calendarioItemRef ?? null,
|
|
4570
4634
|
datos,
|
|
4571
|
-
creadoAt:
|
|
4635
|
+
creadoAt: import_firebase_admin9.firestore.FieldValue.serverTimestamp(),
|
|
4572
4636
|
creadoPorId: "mcp-cowork",
|
|
4573
4637
|
origen: "ai_assisted"
|
|
4574
4638
|
});
|
|
@@ -4626,7 +4690,7 @@ async function contenidoWriter(input) {
|
|
|
4626
4690
|
tx.create(contenidoRef, contenido);
|
|
4627
4691
|
tx.update(calDocRef, {
|
|
4628
4692
|
semanas,
|
|
4629
|
-
updatedAt:
|
|
4693
|
+
updatedAt: import_firebase_admin9.firestore.FieldValue.serverTimestamp()
|
|
4630
4694
|
});
|
|
4631
4695
|
});
|
|
4632
4696
|
} else {
|
|
@@ -4744,7 +4808,7 @@ async function weeklyContentBuilder(input) {
|
|
|
4744
4808
|
brandId,
|
|
4745
4809
|
mes: targetMes,
|
|
4746
4810
|
semanas,
|
|
4747
|
-
creadoAt:
|
|
4811
|
+
creadoAt: import_firebase_admin10.firestore.FieldValue.serverTimestamp(),
|
|
4748
4812
|
creadoPorId: "mcp-cowork",
|
|
4749
4813
|
updatedAt: null
|
|
4750
4814
|
};
|
|
@@ -6578,63 +6642,63 @@ function wrapWithContract(contract, helper, options = {}) {
|
|
|
6578
6642
|
};
|
|
6579
6643
|
};
|
|
6580
6644
|
}
|
|
6581
|
-
var RecordarMemoriaParamsSchema =
|
|
6645
|
+
var RecordarMemoriaParamsSchema = import_zod34.z.object({
|
|
6582
6646
|
tipo: TipoMemoriaEnum,
|
|
6583
6647
|
categoria: CategoriaMemoriaEnum,
|
|
6584
|
-
contenido:
|
|
6648
|
+
contenido: import_zod34.z.string().min(3).max(500)
|
|
6585
6649
|
});
|
|
6586
|
-
var RecordarMemoriaOutputSchema =
|
|
6587
|
-
memoriaId:
|
|
6588
|
-
status:
|
|
6650
|
+
var RecordarMemoriaOutputSchema = import_zod34.z.object({
|
|
6651
|
+
memoriaId: import_zod34.z.string(),
|
|
6652
|
+
status: import_zod34.z.literal("creada")
|
|
6589
6653
|
});
|
|
6590
|
-
var OlvidarMemoriaParamsSchema =
|
|
6591
|
-
memoriaId:
|
|
6592
|
-
motivo:
|
|
6654
|
+
var OlvidarMemoriaParamsSchema = import_zod34.z.object({
|
|
6655
|
+
memoriaId: import_zod34.z.string(),
|
|
6656
|
+
motivo: import_zod34.z.string().optional()
|
|
6593
6657
|
});
|
|
6594
|
-
var OlvidarMemoriaOutputSchema =
|
|
6595
|
-
status:
|
|
6658
|
+
var OlvidarMemoriaOutputSchema = import_zod34.z.object({
|
|
6659
|
+
status: import_zod34.z.literal("archivada")
|
|
6596
6660
|
});
|
|
6597
|
-
var ConfigInputSchema =
|
|
6598
|
-
diaSemana:
|
|
6599
|
-
diaMes:
|
|
6600
|
-
hora:
|
|
6661
|
+
var ConfigInputSchema = import_zod35.z.object({
|
|
6662
|
+
diaSemana: import_zod35.z.number().int().min(0).max(6).nullable(),
|
|
6663
|
+
diaMes: import_zod35.z.number().int().min(1).max(31).nullable(),
|
|
6664
|
+
hora: import_zod35.z.string().regex(/^\d{2}:\d{2}$/),
|
|
6601
6665
|
// Timezone NO viene en input — hereda de tenants/{tid}.zonaHoraria (audit fix 2).
|
|
6602
|
-
fechaPuntual:
|
|
6666
|
+
fechaPuntual: import_zod35.z.string().datetime({ offset: true }).nullable()
|
|
6603
6667
|
}).strict();
|
|
6604
|
-
var AccionInputSchema =
|
|
6605
|
-
tool:
|
|
6606
|
-
params:
|
|
6668
|
+
var AccionInputSchema = import_zod35.z.object({
|
|
6669
|
+
tool: import_zod35.z.string().min(1),
|
|
6670
|
+
params: import_zod35.z.record(import_zod35.z.string(), import_zod35.z.unknown())
|
|
6607
6671
|
}).strict();
|
|
6608
|
-
var ProgramarRutinaParamsSchema =
|
|
6672
|
+
var ProgramarRutinaParamsSchema = import_zod35.z.object({
|
|
6609
6673
|
tipo: TipoRutinaEnum,
|
|
6610
6674
|
frecuencia: FrecuenciaRutinaEnum,
|
|
6611
6675
|
config: ConfigInputSchema,
|
|
6612
6676
|
accion: AccionInputSchema,
|
|
6613
|
-
uidDestinatario:
|
|
6677
|
+
uidDestinatario: import_zod35.z.string()
|
|
6614
6678
|
});
|
|
6615
|
-
var ProgramarRutinaOutputSchema =
|
|
6616
|
-
rutinaId:
|
|
6617
|
-
proximaEjecucionAt:
|
|
6679
|
+
var ProgramarRutinaOutputSchema = import_zod35.z.object({
|
|
6680
|
+
rutinaId: import_zod35.z.string(),
|
|
6681
|
+
proximaEjecucionAt: import_zod35.z.string().datetime()
|
|
6618
6682
|
});
|
|
6619
|
-
var PausarRutinaParamsSchema =
|
|
6620
|
-
rutinaId:
|
|
6621
|
-
motivo:
|
|
6683
|
+
var PausarRutinaParamsSchema = import_zod35.z.object({
|
|
6684
|
+
rutinaId: import_zod35.z.string(),
|
|
6685
|
+
motivo: import_zod35.z.string().optional()
|
|
6622
6686
|
});
|
|
6623
|
-
var PausarRutinaOutputSchema =
|
|
6624
|
-
status:
|
|
6687
|
+
var PausarRutinaOutputSchema = import_zod35.z.object({
|
|
6688
|
+
status: import_zod35.z.literal("pausada")
|
|
6625
6689
|
});
|
|
6626
|
-
var ArchivarRutinaParamsSchema =
|
|
6627
|
-
rutinaId:
|
|
6628
|
-
motivo:
|
|
6690
|
+
var ArchivarRutinaParamsSchema = import_zod35.z.object({
|
|
6691
|
+
rutinaId: import_zod35.z.string(),
|
|
6692
|
+
motivo: import_zod35.z.string().optional()
|
|
6629
6693
|
});
|
|
6630
|
-
var ArchivarRutinaOutputSchema =
|
|
6631
|
-
status:
|
|
6694
|
+
var ArchivarRutinaOutputSchema = import_zod35.z.object({
|
|
6695
|
+
status: import_zod35.z.literal("archivada")
|
|
6632
6696
|
});
|
|
6633
|
-
var ListarRutinasParamsSchema =
|
|
6634
|
-
uid:
|
|
6697
|
+
var ListarRutinasParamsSchema = import_zod35.z.object({
|
|
6698
|
+
uid: import_zod35.z.string().optional()
|
|
6635
6699
|
});
|
|
6636
|
-
var ListarRutinasOutputSchema =
|
|
6637
|
-
rutinas:
|
|
6700
|
+
var ListarRutinasOutputSchema = import_zod35.z.object({
|
|
6701
|
+
rutinas: import_zod35.z.array(MartinRutinaSchema)
|
|
6638
6702
|
});
|
|
6639
6703
|
|
|
6640
6704
|
// src/tools/martinContext.ts
|
|
@@ -6711,6 +6775,9 @@ async function callCF(name, input) {
|
|
|
6711
6775
|
function callGetCalendar(input) {
|
|
6712
6776
|
return callCF("marketingGetCalendarCallable", input);
|
|
6713
6777
|
}
|
|
6778
|
+
function callAddCalendarSlot(input) {
|
|
6779
|
+
return callCF("marketingAddCalendarSlotCallable", input);
|
|
6780
|
+
}
|
|
6714
6781
|
function callBrandBriefWriter(input) {
|
|
6715
6782
|
return callCF("marketingBrandBriefWriterCallable", input);
|
|
6716
6783
|
}
|
|
@@ -6761,7 +6828,7 @@ function callPhotoDirectorExecute(input) {
|
|
|
6761
6828
|
}
|
|
6762
6829
|
|
|
6763
6830
|
// src/tools/marketing/photos.ts
|
|
6764
|
-
var
|
|
6831
|
+
var import_zod37 = require("zod");
|
|
6765
6832
|
|
|
6766
6833
|
// src/services/marketingEmbeddings.ts
|
|
6767
6834
|
var import_google_auth_library = require("google-auth-library");
|
|
@@ -7029,7 +7096,7 @@ async function findNearestInCollectionWithOverride(params) {
|
|
|
7029
7096
|
}
|
|
7030
7097
|
|
|
7031
7098
|
// src/tools/marketing/content.ts
|
|
7032
|
-
var
|
|
7099
|
+
var import_zod36 = require("zod");
|
|
7033
7100
|
var _logOverride = null;
|
|
7034
7101
|
async function logToMcpLogs(entry) {
|
|
7035
7102
|
if (_logOverride) return _logOverride(entry);
|
|
@@ -7042,22 +7109,22 @@ async function logToMcpLogs(entry) {
|
|
|
7042
7109
|
} catch {
|
|
7043
7110
|
}
|
|
7044
7111
|
}
|
|
7045
|
-
var IncludeSchema =
|
|
7046
|
-
products:
|
|
7047
|
-
collections:
|
|
7048
|
-
articles:
|
|
7049
|
-
pages:
|
|
7112
|
+
var IncludeSchema = import_zod36.z.object({
|
|
7113
|
+
products: import_zod36.z.boolean().default(true),
|
|
7114
|
+
collections: import_zod36.z.boolean().default(true),
|
|
7115
|
+
articles: import_zod36.z.boolean().default(true),
|
|
7116
|
+
pages: import_zod36.z.boolean().default(false)
|
|
7050
7117
|
}).default({
|
|
7051
7118
|
products: true,
|
|
7052
7119
|
collections: true,
|
|
7053
7120
|
articles: true,
|
|
7054
7121
|
pages: false
|
|
7055
7122
|
});
|
|
7056
|
-
var LimitSchema =
|
|
7057
|
-
products:
|
|
7058
|
-
collections:
|
|
7059
|
-
articles:
|
|
7060
|
-
pages:
|
|
7123
|
+
var LimitSchema = import_zod36.z.object({
|
|
7124
|
+
products: import_zod36.z.number().int().min(0).max(20).default(5),
|
|
7125
|
+
collections: import_zod36.z.number().int().min(0).max(10).default(3),
|
|
7126
|
+
articles: import_zod36.z.number().int().min(0).max(20).default(5),
|
|
7127
|
+
pages: import_zod36.z.number().int().min(0).max(10).default(2)
|
|
7061
7128
|
}).default({ products: 5, collections: 3, articles: 5, pages: 2 });
|
|
7062
7129
|
async function findContentForTopicHandler(input, session) {
|
|
7063
7130
|
const tenantId = session.requireTenant();
|
|
@@ -7143,13 +7210,13 @@ MODOS (parametro mode):
|
|
|
7143
7210
|
|
|
7144
7211
|
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.`,
|
|
7145
7212
|
{
|
|
7146
|
-
brandId:
|
|
7147
|
-
contexto:
|
|
7148
|
-
fecha:
|
|
7213
|
+
brandId: import_zod36.z.string().optional().describe("ID de la brand"),
|
|
7214
|
+
contexto: import_zod36.z.string().min(1).describe("Parrafo, keyword o intencion"),
|
|
7215
|
+
fecha: import_zod36.z.string().describe("Fecha del contenido en ISO YYYY-MM-DD"),
|
|
7149
7216
|
include: IncludeSchema.optional(),
|
|
7150
7217
|
limit: LimitSchema.optional(),
|
|
7151
|
-
diversidad:
|
|
7152
|
-
mode:
|
|
7218
|
+
diversidad: import_zod36.z.boolean().default(true),
|
|
7219
|
+
mode: import_zod36.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)")
|
|
7153
7220
|
},
|
|
7154
7221
|
async ({ brandId: inputBrandId, contexto, fecha, include, limit, diversidad, mode: mode2 }) => {
|
|
7155
7222
|
const brandId = inputBrandId ?? session.requireBrand();
|
|
@@ -7253,11 +7320,11 @@ REGLAS:
|
|
|
7253
7320
|
|
|
7254
7321
|
USAR: antes de generar contenido de cualquier slot del calendario.`,
|
|
7255
7322
|
{
|
|
7256
|
-
brandId:
|
|
7257
|
-
keyword:
|
|
7258
|
-
plataforma:
|
|
7259
|
-
fecha:
|
|
7260
|
-
limit:
|
|
7323
|
+
brandId: import_zod37.z.string().optional().describe("ID de la brand"),
|
|
7324
|
+
keyword: import_zod37.z.string().describe("Keyword del slot"),
|
|
7325
|
+
plataforma: import_zod37.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Plataforma destino del slot"),
|
|
7326
|
+
fecha: import_zod37.z.string().describe("Fecha del slot en ISO YYYY-MM-DD"),
|
|
7327
|
+
limit: import_zod37.z.number().int().min(1).max(10).default(5)
|
|
7261
7328
|
},
|
|
7262
7329
|
async ({ brandId: inputBrandId, keyword, plataforma, fecha, limit }) => {
|
|
7263
7330
|
const tenantId = session.requireTenant();
|
|
@@ -7298,7 +7365,7 @@ DESPUES de ver la foto, decide:
|
|
|
7298
7365
|
|
|
7299
7366
|
Luego llama execute_photo_edit con tu analisis y prompt.`,
|
|
7300
7367
|
{
|
|
7301
|
-
fotoId:
|
|
7368
|
+
fotoId: import_zod37.z.string().describe("ID de la foto")
|
|
7302
7369
|
},
|
|
7303
7370
|
async ({ fotoId }) => {
|
|
7304
7371
|
const tenantId = session.requireTenant();
|
|
@@ -7331,14 +7398,14 @@ Retorna la foto editada para que la revises. Si no te gusta:
|
|
|
7331
7398
|
|
|
7332
7399
|
Si estrategia era 'tal_cual', pasa acciones=['none'] \u2014 no se edita pero si se genera thumbnail y embedding con tus tags.`,
|
|
7333
7400
|
{
|
|
7334
|
-
fotoId:
|
|
7335
|
-
prompt:
|
|
7336
|
-
acciones:
|
|
7337
|
-
descripcion:
|
|
7338
|
-
tipo:
|
|
7339
|
-
tagsPrimarios:
|
|
7340
|
-
tagsSecundarios:
|
|
7341
|
-
tagsContexto:
|
|
7401
|
+
fotoId: import_zod37.z.string(),
|
|
7402
|
+
prompt: import_zod37.z.string().nullable().describe("Prompt en ingles para Gemini Image Edit. null si tal_cual"),
|
|
7403
|
+
acciones: import_zod37.z.array(import_zod37.z.enum(["edit_background", "none"])),
|
|
7404
|
+
descripcion: import_zod37.z.string().describe("Descripcion semantica en espanol"),
|
|
7405
|
+
tipo: import_zod37.z.string().nullable().describe("Tipo del catalogoVisual del tenant"),
|
|
7406
|
+
tagsPrimarios: import_zod37.z.array(import_zod37.z.string()),
|
|
7407
|
+
tagsSecundarios: import_zod37.z.array(import_zod37.z.string()),
|
|
7408
|
+
tagsContexto: import_zod37.z.array(import_zod37.z.string())
|
|
7342
7409
|
},
|
|
7343
7410
|
async ({ fotoId, prompt, acciones, descripcion, tipo, tagsPrimarios, tagsSecundarios, tagsContexto }) => {
|
|
7344
7411
|
const tenantId = session.requireTenant();
|
|
@@ -7415,7 +7482,7 @@ Retorna:
|
|
|
7415
7482
|
- creditos: { balance, planId, periodEnd, costoPhotoEdit }
|
|
7416
7483
|
- _instrucciones: que hacer segun el estado`,
|
|
7417
7484
|
{
|
|
7418
|
-
fotoId:
|
|
7485
|
+
fotoId: import_zod37.z.string().describe("ID de la foto")
|
|
7419
7486
|
},
|
|
7420
7487
|
async ({ fotoId }) => {
|
|
7421
7488
|
const tenantId = session.requireTenant();
|
|
@@ -7515,11 +7582,11 @@ Retorna:
|
|
|
7515
7582
|
"find_products_for_content",
|
|
7516
7583
|
`[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.`,
|
|
7517
7584
|
{
|
|
7518
|
-
brandId:
|
|
7519
|
-
contexto:
|
|
7520
|
-
fecha:
|
|
7521
|
-
limit:
|
|
7522
|
-
diversidad:
|
|
7585
|
+
brandId: import_zod37.z.string().optional().describe("ID de la brand"),
|
|
7586
|
+
contexto: import_zod37.z.string().describe("Parrafo, keyword o intencion del contenido"),
|
|
7587
|
+
fecha: import_zod37.z.string().describe("Fecha del contenido en ISO YYYY-MM-DD"),
|
|
7588
|
+
limit: import_zod37.z.number().int().min(1).max(10).default(5),
|
|
7589
|
+
diversidad: import_zod37.z.boolean().default(true)
|
|
7523
7590
|
},
|
|
7524
7591
|
async ({ brandId: inputBrandId, contexto, fecha, limit, diversidad }) => {
|
|
7525
7592
|
console.warn(
|
|
@@ -7572,10 +7639,10 @@ RETORNA { plantillaId, titulo, thumbnailUrl } o { plantillaId: null, motivo: 'no
|
|
|
7572
7639
|
|
|
7573
7640
|
USAR: solo si tenant tiene Canva conectado (tenants/{tenantId}/marketing_config/{brandId}.canva.connected=true).`,
|
|
7574
7641
|
{
|
|
7575
|
-
brandId:
|
|
7576
|
-
plataforma:
|
|
7577
|
-
tipoContenido:
|
|
7578
|
-
keyword:
|
|
7642
|
+
brandId: import_zod37.z.string().optional().describe("ID de la brand"),
|
|
7643
|
+
plataforma: import_zod37.z.string().describe("gbp | instagram | shopify_blog"),
|
|
7644
|
+
tipoContenido: import_zod37.z.string().describe("post | carousel | story | blog"),
|
|
7645
|
+
keyword: import_zod37.z.string().describe("Keyword del slot")
|
|
7579
7646
|
},
|
|
7580
7647
|
async ({ brandId: inputBrandId, plataforma, tipoContenido, keyword }) => {
|
|
7581
7648
|
const tenantId = session.requireTenant();
|
|
@@ -7605,15 +7672,15 @@ USAR: cuando get_photos_for_slot retorna pocas fotos y el slot aun no esta cubie
|
|
|
7605
7672
|
|
|
7606
7673
|
IMPORTANTE \u2014 campo 'razon': Este texto lo ve el tenant en la app. Escribe en lenguaje amigable y claro, NO tecnico. Ejemplo MALO: "get_photos_for_slot retorno 0 fotos para shopify_blog". Ejemplo BUENO: "No hay fotos de alcatraz para el blog del 8 de abril". Siempre en espanol si el tenant habla espanol.`,
|
|
7607
7674
|
{
|
|
7608
|
-
brandId:
|
|
7609
|
-
semana:
|
|
7610
|
-
necesidades:
|
|
7611
|
-
|
|
7612
|
-
tema:
|
|
7613
|
-
keyword:
|
|
7614
|
-
cantidadSugerida:
|
|
7615
|
-
razon:
|
|
7616
|
-
slotsAfectados:
|
|
7675
|
+
brandId: import_zod37.z.string().optional().describe("ID de la brand"),
|
|
7676
|
+
semana: import_zod37.z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "semana debe ser ISO YYYY-MM-DD").describe("Semana en ISO del lunes"),
|
|
7677
|
+
necesidades: import_zod37.z.array(
|
|
7678
|
+
import_zod37.z.object({
|
|
7679
|
+
tema: import_zod37.z.string(),
|
|
7680
|
+
keyword: import_zod37.z.string(),
|
|
7681
|
+
cantidadSugerida: import_zod37.z.number().int().positive(),
|
|
7682
|
+
razon: import_zod37.z.string(),
|
|
7683
|
+
slotsAfectados: import_zod37.z.array(import_zod37.z.string()).optional().describe('Refs de slots afectados (formato "semana:N:slot:M")')
|
|
7617
7684
|
})
|
|
7618
7685
|
).min(1)
|
|
7619
7686
|
},
|
|
@@ -7759,8 +7826,8 @@ function registerMarketingTools(server, session) {
|
|
|
7759
7826
|
"get_calendar",
|
|
7760
7827
|
"Lee el calendario editorial del mes. Muestra semanas con items planificados por plataforma.",
|
|
7761
7828
|
{
|
|
7762
|
-
brandId:
|
|
7763
|
-
mes:
|
|
7829
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand"),
|
|
7830
|
+
mes: import_zod38.z.string().optional().describe("Mes en formato YYYY-MM (default: mes actual)")
|
|
7764
7831
|
},
|
|
7765
7832
|
async ({ brandId: inputBrandId, mes }) => {
|
|
7766
7833
|
const tenantId = session.requireTenant();
|
|
@@ -7793,7 +7860,7 @@ function registerMarketingTools(server, session) {
|
|
|
7793
7860
|
"get_seo_snapshot",
|
|
7794
7861
|
"Lee el snapshot SEO de la brand: rank, keywords, oportunidades, competidores.",
|
|
7795
7862
|
{
|
|
7796
|
-
brandId:
|
|
7863
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand")
|
|
7797
7864
|
},
|
|
7798
7865
|
async ({ brandId: inputBrandId }) => {
|
|
7799
7866
|
const tenantId = session.requireTenant();
|
|
@@ -7812,8 +7879,8 @@ function registerMarketingTools(server, session) {
|
|
|
7812
7879
|
"get_photo_gallery",
|
|
7813
7880
|
"Fotos disponibles filtradas por estado. Muestra fotos con metadata y conteo.",
|
|
7814
7881
|
{
|
|
7815
|
-
brandId:
|
|
7816
|
-
estado:
|
|
7882
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand"),
|
|
7883
|
+
estado: import_zod38.z.string().optional().describe("Filtrar por estado (nueva, procesando, editada, usada, descartada, error)")
|
|
7817
7884
|
},
|
|
7818
7885
|
async ({ brandId: inputBrandId, estado }) => {
|
|
7819
7886
|
session.requireTenant();
|
|
@@ -7853,7 +7920,7 @@ function registerMarketingTools(server, session) {
|
|
|
7853
7920
|
"generate_marketing_plan",
|
|
7854
7921
|
"Prepara los datos necesarios para generar un plan de marketing estrategico. Retorna snapshot SEO + productos para que Claude genere el plan con el system prompt.",
|
|
7855
7922
|
{
|
|
7856
|
-
brandId:
|
|
7923
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand")
|
|
7857
7924
|
},
|
|
7858
7925
|
async ({ brandId: inputBrandId }) => {
|
|
7859
7926
|
const tenantId = session.requireTenant();
|
|
@@ -7867,8 +7934,8 @@ function registerMarketingTools(server, session) {
|
|
|
7867
7934
|
"save_marketing_plan",
|
|
7868
7935
|
"Guarda el plan de marketing generado por Claude en la configuracion de la brand. Escribe en tenants/{tenantId}/marketing_config/{brandId}.plan.",
|
|
7869
7936
|
{
|
|
7870
|
-
brandId:
|
|
7871
|
-
plan:
|
|
7937
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand"),
|
|
7938
|
+
plan: import_zod38.z.record(import_zod38.z.string(), import_zod38.z.unknown()).describe("Plan de marketing generado (keywordsPrioritarios, gapsCompetencia, temporadas, tonoMarca, quickWins, etc.)")
|
|
7872
7939
|
},
|
|
7873
7940
|
async ({ brandId: inputBrandId, plan }) => {
|
|
7874
7941
|
const tenantId = session.requireTenant();
|
|
@@ -7896,9 +7963,9 @@ function registerMarketingTools(server, session) {
|
|
|
7896
7963
|
"update_marketing_plan_field",
|
|
7897
7964
|
"Actualiza UN campo del plan de marketing sin sobreescribir el resto. Merge parcial. Ideal para agregar coleccionesPriorizadas, actualizar quickWins, etc.",
|
|
7898
7965
|
{
|
|
7899
|
-
brandId:
|
|
7900
|
-
field:
|
|
7901
|
-
value:
|
|
7966
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand"),
|
|
7967
|
+
field: import_zod38.z.string().describe('Nombre del campo a actualizar (ej: "coleccionesPriorizadas", "quickWins", "temporadas")'),
|
|
7968
|
+
value: import_zod38.z.unknown().describe("Valor del campo")
|
|
7902
7969
|
},
|
|
7903
7970
|
async ({ brandId: inputBrandId, field, value }) => {
|
|
7904
7971
|
const tenantId = session.requireTenant();
|
|
@@ -7911,7 +7978,7 @@ function registerMarketingTools(server, session) {
|
|
|
7911
7978
|
"generate_brand_brief",
|
|
7912
7979
|
"Prepara todos los datos del negocio para que Claude genere un Brand Brief pre-llenado. Retorna Shopify + SEO + GBP + tenant data. Claude genera el brief, luego usa save_brand_brief para guardar.",
|
|
7913
7980
|
{
|
|
7914
|
-
brandId:
|
|
7981
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand")
|
|
7915
7982
|
},
|
|
7916
7983
|
async ({ brandId: inputBrandId }) => {
|
|
7917
7984
|
const tenantId = session.requireTenant();
|
|
@@ -7939,8 +8006,8 @@ function registerMarketingTools(server, session) {
|
|
|
7939
8006
|
"save_brand_brief",
|
|
7940
8007
|
"Guarda el Brand Brief generado por Claude en tenants/{tenantId}/marketing_config/{brandId}.brandBrief. Escribe solo ese campo (merge parcial, no rebuild del doc).",
|
|
7941
8008
|
{
|
|
7942
|
-
brandId:
|
|
7943
|
-
brandBrief:
|
|
8009
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand"),
|
|
8010
|
+
brandBrief: import_zod38.z.record(import_zod38.z.string(), import_zod38.z.unknown()).describe("Brand Brief completo generado por Claude")
|
|
7944
8011
|
},
|
|
7945
8012
|
async ({ brandId: inputBrandId, brandBrief }) => {
|
|
7946
8013
|
const tenantId = session.requireTenant();
|
|
@@ -7953,9 +8020,9 @@ function registerMarketingTools(server, session) {
|
|
|
7953
8020
|
"generate_weekly_content",
|
|
7954
8021
|
"Prepara datos del calendario + fotos + plan para generar el contenido de la semana. Claude genera con el system prompt, luego usa save_generated_content para guardar.",
|
|
7955
8022
|
{
|
|
7956
|
-
brandId:
|
|
7957
|
-
semana:
|
|
7958
|
-
modo:
|
|
8023
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand"),
|
|
8024
|
+
semana: import_zod38.z.number().optional().describe("Numero de semana (1-5). Default: semana actual del mes."),
|
|
8025
|
+
modo: import_zod38.z.enum(["planificar", "generar"]).optional().describe("'planificar' = proponer distribucion (plataforma+keyword por dia). 'generar' = generar contenido real para slots ya planificados. Default: 'planificar'.")
|
|
7959
8026
|
},
|
|
7960
8027
|
async ({ brandId: inputBrandId, semana, modo }) => {
|
|
7961
8028
|
const tenantId = session.requireTenant();
|
|
@@ -7971,14 +8038,14 @@ function registerMarketingTools(server, session) {
|
|
|
7971
8038
|
|
|
7972
8039
|
IMPORTANTE: Si seleccionaste una foto con get_photos_for_slot, SIEMPRE pasa fotoId aqu\xED. Sin fotoId el post se publica sin imagen.`,
|
|
7973
8040
|
{
|
|
7974
|
-
brandId:
|
|
7975
|
-
plataforma:
|
|
7976
|
-
tipo:
|
|
7977
|
-
keyword:
|
|
7978
|
-
languageCode:
|
|
7979
|
-
fotoId:
|
|
7980
|
-
datos:
|
|
7981
|
-
calendarioItemRef:
|
|
8041
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand"),
|
|
8042
|
+
plataforma: import_zod38.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Plataforma destino"),
|
|
8043
|
+
tipo: import_zod38.z.string().optional().describe("Tipo de contenido (post, blog, carousel, etc.)"),
|
|
8044
|
+
keyword: import_zod38.z.string().optional().describe("Keyword target"),
|
|
8045
|
+
languageCode: import_zod38.z.string().optional().describe("Idioma (es/en)"),
|
|
8046
|
+
fotoId: import_zod38.z.string().optional().describe("ID de la foto a asociar"),
|
|
8047
|
+
datos: import_zod38.z.record(import_zod38.z.string(), import_zod38.z.unknown()).describe("Datos especificos de la plataforma (output de buildDatos*)"),
|
|
8048
|
+
calendarioItemRef: import_zod38.z.string().optional().describe("Referencia al item del calendario")
|
|
7982
8049
|
},
|
|
7983
8050
|
async ({ brandId: inputBrandId, plataforma, tipo, keyword, languageCode, fotoId, datos, calendarioItemRef }) => {
|
|
7984
8051
|
const tenantId = session.requireTenant();
|
|
@@ -8023,13 +8090,13 @@ Usa para: corregir body, metaTitle, tags, fotoId, o cualquier campo sin tener qu
|
|
|
8023
8090
|
NO puede cambiar: tenantId, brandId, id (inmutables).
|
|
8024
8091
|
Si pasas campos dentro de "datos", se hace merge con los datos existentes (no los reemplaza entero).`,
|
|
8025
8092
|
{
|
|
8026
|
-
contenidoId:
|
|
8027
|
-
datos:
|
|
8028
|
-
fotoId:
|
|
8029
|
-
keyword:
|
|
8030
|
-
languageCode:
|
|
8031
|
-
estado:
|
|
8032
|
-
calendarioItemRef:
|
|
8093
|
+
contenidoId: import_zod38.z.string().describe("ID del doc en marketing_contenido"),
|
|
8094
|
+
datos: import_zod38.z.record(import_zod38.z.string(), import_zod38.z.unknown()).optional().describe('Campos de datos a actualizar (merge parcial sobre datos existentes). Ej: { body: "...", metaTitle: "..." }'),
|
|
8095
|
+
fotoId: import_zod38.z.string().nullable().optional().describe("Actualizar foto asociada"),
|
|
8096
|
+
keyword: import_zod38.z.string().nullable().optional().describe("Actualizar keyword"),
|
|
8097
|
+
languageCode: import_zod38.z.string().optional().describe("Actualizar idioma"),
|
|
8098
|
+
estado: import_zod38.z.enum(["borrador", "generado", "pendiente_aprobacion", "aprobado", "rechazado"]).optional().describe("Cambiar estado manualmente"),
|
|
8099
|
+
calendarioItemRef: import_zod38.z.string().nullable().optional().describe("Vincular a un slot del calendario")
|
|
8033
8100
|
},
|
|
8034
8101
|
async ({ contenidoId, datos: newDatos, fotoId, keyword, languageCode, estado, calendarioItemRef }) => {
|
|
8035
8102
|
const tenantId = session.requireTenant();
|
|
@@ -8056,31 +8123,69 @@ Si pasas campos dentro de "datos", se hace merge con los datos existentes (no lo
|
|
|
8056
8123
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
8057
8124
|
}
|
|
8058
8125
|
);
|
|
8126
|
+
server.tool(
|
|
8127
|
+
"add_calendar_slot",
|
|
8128
|
+
"Agrega un slot NUEVO al calendario editorial. Para modificar un slot existente usa update_calendar_slot.",
|
|
8129
|
+
{
|
|
8130
|
+
brandId: import_zod38.z.string().describe("ID de la brand"),
|
|
8131
|
+
mes: import_zod38.z.string().describe("Mes del calendario en formato YYYY-MM"),
|
|
8132
|
+
semana: import_zod38.z.number().describe("Numero de semana (1-5)"),
|
|
8133
|
+
slot: import_zod38.z.object({
|
|
8134
|
+
dia: import_zod38.z.string().describe("Fecha del slot en formato YYYY-MM-DD. Debe caer dentro del rango fechaInicio/fechaFin de la semana indicada."),
|
|
8135
|
+
plataforma: import_zod38.z.enum(["gbp", "shopify_blog", "instagram", "review"]).describe("Plataforma destino"),
|
|
8136
|
+
tipo: import_zod38.z.enum(["post", "blog", "carousel", "reel", "story", "review_response"]).describe("Tipo de contenido"),
|
|
8137
|
+
keyword: import_zod38.z.string().describe("Keyword principal del contenido"),
|
|
8138
|
+
tema: import_zod38.z.string().optional().describe("Tema del contenido. OMITE el campo si no aplica, no envies cadena vacia ni null."),
|
|
8139
|
+
productoId: import_zod38.z.string().optional().describe("ID de producto vinculado. OMITE el campo si no aplica."),
|
|
8140
|
+
estado: import_zod38.z.enum(["planificado", "pre_aprobado", "generado", "revisar", "aprobado", "publicado", "rechazado"]).optional().describe("Estado inicial. OMITE si quieres default 'planificado'."),
|
|
8141
|
+
locationId: import_zod38.z.string().optional().describe("GBP location ID \u2014 solo si plataforma=gbp y multi-sucursal. OMITE si no aplica."),
|
|
8142
|
+
locationNombre: import_zod38.z.string().optional().describe("Nombre de la sucursal GBP. OMITE si no aplica.")
|
|
8143
|
+
}).describe("Datos del slot nuevo")
|
|
8144
|
+
},
|
|
8145
|
+
async ({ brandId, mes, semana, slot }) => {
|
|
8146
|
+
const tenantId = session.requireTenant();
|
|
8147
|
+
const ctx = buildMartinContext(session, brandId);
|
|
8148
|
+
const result = await dispatchWithContract({
|
|
8149
|
+
contract: addCalendarSlotContract,
|
|
8150
|
+
helper: addCalendarSlot,
|
|
8151
|
+
callable: callAddCalendarSlot,
|
|
8152
|
+
input: { tenantId, brandId, mes, semana, slot },
|
|
8153
|
+
ctx
|
|
8154
|
+
});
|
|
8155
|
+
const payload = result.state === "success" ? result.structuredOutput : {
|
|
8156
|
+
ok: false,
|
|
8157
|
+
state: result.state,
|
|
8158
|
+
mensaje: result.text,
|
|
8159
|
+
...result.validationErrors && result.validationErrors.length > 0 ? { validationErrors: result.validationErrors } : {}
|
|
8160
|
+
};
|
|
8161
|
+
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
8162
|
+
}
|
|
8163
|
+
);
|
|
8059
8164
|
server.tool(
|
|
8060
8165
|
"update_calendar_slot",
|
|
8061
|
-
"
|
|
8166
|
+
"MODIFICA un slot EXISTENTE del calendario editorial. Para crear un slot nuevo usa add_calendar_slot. Si slotIndex no existe, retorna error SLOT_NOT_FOUND.",
|
|
8062
8167
|
{
|
|
8063
|
-
brandId:
|
|
8064
|
-
mes:
|
|
8065
|
-
semana:
|
|
8066
|
-
slotIndex:
|
|
8067
|
-
cambios:
|
|
8068
|
-
dia:
|
|
8069
|
-
plataforma:
|
|
8070
|
-
tipo:
|
|
8071
|
-
keyword:
|
|
8072
|
-
tema:
|
|
8073
|
-
productoId:
|
|
8074
|
-
estado:
|
|
8075
|
-
contenidoRef:
|
|
8076
|
-
fotoIdAsignada:
|
|
8077
|
-
notas:
|
|
8078
|
-
locationId:
|
|
8079
|
-
locationNombre:
|
|
8168
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand"),
|
|
8169
|
+
mes: import_zod38.z.string().describe("Mes del calendario (YYYY-MM)"),
|
|
8170
|
+
semana: import_zod38.z.number().describe("Numero de semana (1-5)"),
|
|
8171
|
+
slotIndex: import_zod38.z.number().describe("Indice del slot (0-based). Si >= items.length, agrega nuevo slot al final."),
|
|
8172
|
+
cambios: import_zod38.z.object({
|
|
8173
|
+
dia: import_zod38.z.string().nullable().optional(),
|
|
8174
|
+
plataforma: import_zod38.z.enum(["gbp", "shopify_blog", "instagram", "review"]).nullable().optional(),
|
|
8175
|
+
tipo: import_zod38.z.enum(["post", "blog", "carousel", "reel", "story", "review_response"]).nullable().optional(),
|
|
8176
|
+
keyword: import_zod38.z.string().nullable().optional(),
|
|
8177
|
+
tema: import_zod38.z.string().nullable().optional(),
|
|
8178
|
+
productoId: import_zod38.z.string().nullable().optional(),
|
|
8179
|
+
estado: import_zod38.z.enum(["planificado", "pre_aprobado", "generado", "revisar", "aprobado", "publicado", "rechazado"]).optional(),
|
|
8180
|
+
contenidoRef: import_zod38.z.string().nullable().optional(),
|
|
8181
|
+
fotoIdAsignada: import_zod38.z.string().nullable().optional(),
|
|
8182
|
+
notas: import_zod38.z.array(NotaCalendarioSchema).optional(),
|
|
8183
|
+
locationId: import_zod38.z.string().nullable().optional().describe("GBP location ID \u2014 a qu\xE9 sucursal publicar el post"),
|
|
8184
|
+
locationNombre: import_zod38.z.string().nullable().optional().describe("Nombre de la sucursal GBP (para UI)")
|
|
8080
8185
|
}).strict().describe("Campos del slot. Solo acepta campos validos del schema. Campos no reconocidos seran rechazados."),
|
|
8081
|
-
accionContenidoExistente:
|
|
8082
|
-
|
|
8083
|
-
|
|
8186
|
+
accionContenidoExistente: import_zod38.z.union([
|
|
8187
|
+
import_zod38.z.enum(["descartar", "nuevo_slot", "mantener"]),
|
|
8188
|
+
import_zod38.z.string().regex(/^mover:semana:\d+:slot:\d+$/, "Formato: mover:semana:N:slot:M")
|
|
8084
8189
|
]).optional().describe(
|
|
8085
8190
|
"Decision del tenant cuando el slot ya tiene un contenidoRef existente Y los cambios tocan keyword/tema/plataforma/tipo. Requerido en ese escenario (si no se pasa, el helper retorna ACCION_CONTENIDO_EXISTENTE_REQUIRED con las 4 opciones para que preguntes al tenant). Valores: 'descartar' (marca el contenido existente como descartado y aplica cambios al slot) | 'mover:semana:N:slot:M' (mueve el contenido existente al slot destino vacio y aplica cambios al slot origen) | 'nuevo_slot' (NO toca este slot ni su contenido; crea un slot nuevo el mismo dia con los cambios \u2014 util para multiples publicaciones/dia) | 'mantener' (conserva el contenido existente aqui y aplica los cambios igualmente \u2014 caso typo fix)."
|
|
8086
8191
|
)
|
|
@@ -8117,9 +8222,9 @@ ESCRIBE EN DOS LUGARES:
|
|
|
8117
8222
|
|
|
8118
8223
|
NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agenda, no en el post.`,
|
|
8119
8224
|
{
|
|
8120
|
-
contenidoRef:
|
|
8121
|
-
fotoId:
|
|
8122
|
-
calendarioItemRef:
|
|
8225
|
+
contenidoRef: import_zod38.z.string().describe("ID del doc en marketing_contenido"),
|
|
8226
|
+
fotoId: import_zod38.z.string().describe("ID de la foto en marketing_fotos (estado editada)"),
|
|
8227
|
+
calendarioItemRef: import_zod38.z.string().optional().describe('Ref del slot (formato "semana:N:slot:M") para actualizar fotoIdAsignada')
|
|
8123
8228
|
},
|
|
8124
8229
|
async ({ contenidoRef, fotoId, calendarioItemRef }) => {
|
|
8125
8230
|
const tenantId = session.requireTenant();
|
|
@@ -8132,7 +8237,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
8132
8237
|
"approve_content",
|
|
8133
8238
|
"Aprueba contenido para publicacion. Valida transicion de estado.",
|
|
8134
8239
|
{
|
|
8135
|
-
contenidoId:
|
|
8240
|
+
contenidoId: import_zod38.z.string().describe("ID del contenido a aprobar")
|
|
8136
8241
|
},
|
|
8137
8242
|
async ({ contenidoId }) => {
|
|
8138
8243
|
session.requireTenant();
|
|
@@ -8156,8 +8261,8 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
8156
8261
|
"reject_content",
|
|
8157
8262
|
"Rechaza contenido con motivo. Valida transicion de estado.",
|
|
8158
8263
|
{
|
|
8159
|
-
contenidoId:
|
|
8160
|
-
motivo:
|
|
8264
|
+
contenidoId: import_zod38.z.string().describe("ID del contenido a rechazar"),
|
|
8265
|
+
motivo: import_zod38.z.string().describe("Motivo del rechazo")
|
|
8161
8266
|
},
|
|
8162
8267
|
async ({ contenidoId, motivo }) => {
|
|
8163
8268
|
session.requireTenant();
|
|
@@ -8180,7 +8285,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
8180
8285
|
"get_collections",
|
|
8181
8286
|
"Lee todas las colecciones canonicas (Shopify/WordPress/webonly) con sus 6 campos SEO actuales. Incluye best practices 2026.",
|
|
8182
8287
|
{
|
|
8183
|
-
brandId:
|
|
8288
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand")
|
|
8184
8289
|
},
|
|
8185
8290
|
async ({ brandId: inputBrandId }) => {
|
|
8186
8291
|
const tenantId = session.requireTenant();
|
|
@@ -8267,7 +8372,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
8267
8372
|
"save_collection_suggestions",
|
|
8268
8373
|
"Guarda sugerencias SEO para colecciones de Shopify. El tenant las aprueba en la UI.",
|
|
8269
8374
|
{
|
|
8270
|
-
brandId:
|
|
8375
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand"),
|
|
8271
8376
|
suggestions: CollectionSuggestionsInputArraySchema.describe("Array de sugerencias SEO. Cada una con max 60 chars en metaTitle, max 158 en metaDescription, max 125 en imageAlt")
|
|
8272
8377
|
},
|
|
8273
8378
|
async ({ brandId: inputBrandId, suggestions }) => {
|