ponch-mcp-server 1.0.74 → 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 +417 -255
- 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
|
};
|
|
@@ -6401,6 +6465,19 @@ function wrapWithContract(contract, helper, options = {}) {
|
|
|
6401
6465
|
}
|
|
6402
6466
|
const parseResult = contract.paramsSchema.safeParse(input);
|
|
6403
6467
|
if (!parseResult.success) {
|
|
6468
|
+
const validationErrors = parseResult.error.issues.map((i) => {
|
|
6469
|
+
const issue = i;
|
|
6470
|
+
return {
|
|
6471
|
+
path: i.path,
|
|
6472
|
+
code: i.code,
|
|
6473
|
+
message: i.message,
|
|
6474
|
+
received: issue.received,
|
|
6475
|
+
expected: issue.expected
|
|
6476
|
+
};
|
|
6477
|
+
});
|
|
6478
|
+
const camposFallidos = validationErrors.map((v) => v.path.join(".")).filter(Boolean);
|
|
6479
|
+
const baseMsg = getWrapperMessage("input_invalido", locale);
|
|
6480
|
+
const text = camposFallidos.length ? locale === "en" ? `${baseMsg} Issues with: ${camposFallidos.join(", ")}.` : `${baseMsg} Hubo problemas con: ${camposFallidos.join(", ")}.` : baseMsg;
|
|
6404
6481
|
await writeAuditLog({
|
|
6405
6482
|
tenantId: ctx.tenantId,
|
|
6406
6483
|
brandId: ctx.brandId ?? null,
|
|
@@ -6409,13 +6486,14 @@ function wrapWithContract(contract, helper, options = {}) {
|
|
|
6409
6486
|
motivo: "Input inv\xE1lido \u2014 Zod parse failed",
|
|
6410
6487
|
conversacionId: ctx.conversacionId ?? null,
|
|
6411
6488
|
status: "error",
|
|
6412
|
-
errorMessage: `Input shape inv\xE1lido: ${
|
|
6489
|
+
errorMessage: `Input shape inv\xE1lido: ${validationErrors.map((v) => `${v.path.join(".")}: ${v.code} (${v.message})`).join("; ")}`,
|
|
6413
6490
|
durationMs: Date.now() - startMs
|
|
6414
6491
|
});
|
|
6415
6492
|
return {
|
|
6416
|
-
text
|
|
6493
|
+
text,
|
|
6417
6494
|
structuredOutput: null,
|
|
6418
|
-
state: "error"
|
|
6495
|
+
state: "error",
|
|
6496
|
+
validationErrors
|
|
6419
6497
|
};
|
|
6420
6498
|
}
|
|
6421
6499
|
const parsedInput = parseResult.data;
|
|
@@ -6462,7 +6540,6 @@ function wrapWithContract(contract, helper, options = {}) {
|
|
|
6462
6540
|
let output;
|
|
6463
6541
|
try {
|
|
6464
6542
|
output = await helper(parsedInput);
|
|
6465
|
-
contract.outputSchema.parse(output);
|
|
6466
6543
|
} catch (err) {
|
|
6467
6544
|
await writeAuditLog({
|
|
6468
6545
|
tenantId: ctx.tenantId,
|
|
@@ -6481,6 +6558,36 @@ function wrapWithContract(contract, helper, options = {}) {
|
|
|
6481
6558
|
state: "error"
|
|
6482
6559
|
};
|
|
6483
6560
|
}
|
|
6561
|
+
const outputParse = contract.outputSchema.safeParse(output);
|
|
6562
|
+
if (!outputParse.success) {
|
|
6563
|
+
const validationErrors = outputParse.error.issues.map((i) => {
|
|
6564
|
+
const issue = i;
|
|
6565
|
+
return {
|
|
6566
|
+
path: i.path,
|
|
6567
|
+
code: i.code,
|
|
6568
|
+
message: i.message,
|
|
6569
|
+
received: issue.received,
|
|
6570
|
+
expected: issue.expected
|
|
6571
|
+
};
|
|
6572
|
+
});
|
|
6573
|
+
await writeAuditLog({
|
|
6574
|
+
tenantId: ctx.tenantId,
|
|
6575
|
+
brandId: ctx.brandId ?? null,
|
|
6576
|
+
actor: { type: "martin", uid: ctx.user.uid, nombre: ctx.user.nombre },
|
|
6577
|
+
action: contract.auditAction,
|
|
6578
|
+
motivo: `Output del helper inv\xE1lido \u2014 bug de "${contract.name}"`,
|
|
6579
|
+
conversacionId: ctx.conversacionId ?? null,
|
|
6580
|
+
status: "error",
|
|
6581
|
+
errorMessage: `Output shape inv\xE1lido: ${validationErrors.map((v) => `${v.path.join(".")}: ${v.code} (${v.message})`).join("; ")}`,
|
|
6582
|
+
durationMs: Date.now() - startMs
|
|
6583
|
+
});
|
|
6584
|
+
return {
|
|
6585
|
+
text: martinSafeError(new Error("output_invalid"), locale),
|
|
6586
|
+
structuredOutput: null,
|
|
6587
|
+
state: "error",
|
|
6588
|
+
validationErrors
|
|
6589
|
+
};
|
|
6590
|
+
}
|
|
6484
6591
|
const maybeDisabled = output;
|
|
6485
6592
|
if (maybeDisabled.disabled === true) {
|
|
6486
6593
|
const code = maybeDisabled.code;
|
|
@@ -6535,63 +6642,63 @@ function wrapWithContract(contract, helper, options = {}) {
|
|
|
6535
6642
|
};
|
|
6536
6643
|
};
|
|
6537
6644
|
}
|
|
6538
|
-
var RecordarMemoriaParamsSchema =
|
|
6645
|
+
var RecordarMemoriaParamsSchema = import_zod34.z.object({
|
|
6539
6646
|
tipo: TipoMemoriaEnum,
|
|
6540
6647
|
categoria: CategoriaMemoriaEnum,
|
|
6541
|
-
contenido:
|
|
6648
|
+
contenido: import_zod34.z.string().min(3).max(500)
|
|
6542
6649
|
});
|
|
6543
|
-
var RecordarMemoriaOutputSchema =
|
|
6544
|
-
memoriaId:
|
|
6545
|
-
status:
|
|
6650
|
+
var RecordarMemoriaOutputSchema = import_zod34.z.object({
|
|
6651
|
+
memoriaId: import_zod34.z.string(),
|
|
6652
|
+
status: import_zod34.z.literal("creada")
|
|
6546
6653
|
});
|
|
6547
|
-
var OlvidarMemoriaParamsSchema =
|
|
6548
|
-
memoriaId:
|
|
6549
|
-
motivo:
|
|
6654
|
+
var OlvidarMemoriaParamsSchema = import_zod34.z.object({
|
|
6655
|
+
memoriaId: import_zod34.z.string(),
|
|
6656
|
+
motivo: import_zod34.z.string().optional()
|
|
6550
6657
|
});
|
|
6551
|
-
var OlvidarMemoriaOutputSchema =
|
|
6552
|
-
status:
|
|
6658
|
+
var OlvidarMemoriaOutputSchema = import_zod34.z.object({
|
|
6659
|
+
status: import_zod34.z.literal("archivada")
|
|
6553
6660
|
});
|
|
6554
|
-
var ConfigInputSchema =
|
|
6555
|
-
diaSemana:
|
|
6556
|
-
diaMes:
|
|
6557
|
-
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}$/),
|
|
6558
6665
|
// Timezone NO viene en input — hereda de tenants/{tid}.zonaHoraria (audit fix 2).
|
|
6559
|
-
fechaPuntual:
|
|
6666
|
+
fechaPuntual: import_zod35.z.string().datetime({ offset: true }).nullable()
|
|
6560
6667
|
}).strict();
|
|
6561
|
-
var AccionInputSchema =
|
|
6562
|
-
tool:
|
|
6563
|
-
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())
|
|
6564
6671
|
}).strict();
|
|
6565
|
-
var ProgramarRutinaParamsSchema =
|
|
6672
|
+
var ProgramarRutinaParamsSchema = import_zod35.z.object({
|
|
6566
6673
|
tipo: TipoRutinaEnum,
|
|
6567
6674
|
frecuencia: FrecuenciaRutinaEnum,
|
|
6568
6675
|
config: ConfigInputSchema,
|
|
6569
6676
|
accion: AccionInputSchema,
|
|
6570
|
-
uidDestinatario:
|
|
6677
|
+
uidDestinatario: import_zod35.z.string()
|
|
6571
6678
|
});
|
|
6572
|
-
var ProgramarRutinaOutputSchema =
|
|
6573
|
-
rutinaId:
|
|
6574
|
-
proximaEjecucionAt:
|
|
6679
|
+
var ProgramarRutinaOutputSchema = import_zod35.z.object({
|
|
6680
|
+
rutinaId: import_zod35.z.string(),
|
|
6681
|
+
proximaEjecucionAt: import_zod35.z.string().datetime()
|
|
6575
6682
|
});
|
|
6576
|
-
var PausarRutinaParamsSchema =
|
|
6577
|
-
rutinaId:
|
|
6578
|
-
motivo:
|
|
6683
|
+
var PausarRutinaParamsSchema = import_zod35.z.object({
|
|
6684
|
+
rutinaId: import_zod35.z.string(),
|
|
6685
|
+
motivo: import_zod35.z.string().optional()
|
|
6579
6686
|
});
|
|
6580
|
-
var PausarRutinaOutputSchema =
|
|
6581
|
-
status:
|
|
6687
|
+
var PausarRutinaOutputSchema = import_zod35.z.object({
|
|
6688
|
+
status: import_zod35.z.literal("pausada")
|
|
6582
6689
|
});
|
|
6583
|
-
var ArchivarRutinaParamsSchema =
|
|
6584
|
-
rutinaId:
|
|
6585
|
-
motivo:
|
|
6690
|
+
var ArchivarRutinaParamsSchema = import_zod35.z.object({
|
|
6691
|
+
rutinaId: import_zod35.z.string(),
|
|
6692
|
+
motivo: import_zod35.z.string().optional()
|
|
6586
6693
|
});
|
|
6587
|
-
var ArchivarRutinaOutputSchema =
|
|
6588
|
-
status:
|
|
6694
|
+
var ArchivarRutinaOutputSchema = import_zod35.z.object({
|
|
6695
|
+
status: import_zod35.z.literal("archivada")
|
|
6589
6696
|
});
|
|
6590
|
-
var ListarRutinasParamsSchema =
|
|
6591
|
-
uid:
|
|
6697
|
+
var ListarRutinasParamsSchema = import_zod35.z.object({
|
|
6698
|
+
uid: import_zod35.z.string().optional()
|
|
6592
6699
|
});
|
|
6593
|
-
var ListarRutinasOutputSchema =
|
|
6594
|
-
rutinas:
|
|
6700
|
+
var ListarRutinasOutputSchema = import_zod35.z.object({
|
|
6701
|
+
rutinas: import_zod35.z.array(MartinRutinaSchema)
|
|
6595
6702
|
});
|
|
6596
6703
|
|
|
6597
6704
|
// src/tools/martinContext.ts
|
|
@@ -6668,6 +6775,9 @@ async function callCF(name, input) {
|
|
|
6668
6775
|
function callGetCalendar(input) {
|
|
6669
6776
|
return callCF("marketingGetCalendarCallable", input);
|
|
6670
6777
|
}
|
|
6778
|
+
function callAddCalendarSlot(input) {
|
|
6779
|
+
return callCF("marketingAddCalendarSlotCallable", input);
|
|
6780
|
+
}
|
|
6671
6781
|
function callBrandBriefWriter(input) {
|
|
6672
6782
|
return callCF("marketingBrandBriefWriterCallable", input);
|
|
6673
6783
|
}
|
|
@@ -6718,7 +6828,7 @@ function callPhotoDirectorExecute(input) {
|
|
|
6718
6828
|
}
|
|
6719
6829
|
|
|
6720
6830
|
// src/tools/marketing/photos.ts
|
|
6721
|
-
var
|
|
6831
|
+
var import_zod37 = require("zod");
|
|
6722
6832
|
|
|
6723
6833
|
// src/services/marketingEmbeddings.ts
|
|
6724
6834
|
var import_google_auth_library = require("google-auth-library");
|
|
@@ -6986,7 +7096,7 @@ async function findNearestInCollectionWithOverride(params) {
|
|
|
6986
7096
|
}
|
|
6987
7097
|
|
|
6988
7098
|
// src/tools/marketing/content.ts
|
|
6989
|
-
var
|
|
7099
|
+
var import_zod36 = require("zod");
|
|
6990
7100
|
var _logOverride = null;
|
|
6991
7101
|
async function logToMcpLogs(entry) {
|
|
6992
7102
|
if (_logOverride) return _logOverride(entry);
|
|
@@ -6999,22 +7109,22 @@ async function logToMcpLogs(entry) {
|
|
|
6999
7109
|
} catch {
|
|
7000
7110
|
}
|
|
7001
7111
|
}
|
|
7002
|
-
var IncludeSchema =
|
|
7003
|
-
products:
|
|
7004
|
-
collections:
|
|
7005
|
-
articles:
|
|
7006
|
-
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)
|
|
7007
7117
|
}).default({
|
|
7008
7118
|
products: true,
|
|
7009
7119
|
collections: true,
|
|
7010
7120
|
articles: true,
|
|
7011
7121
|
pages: false
|
|
7012
7122
|
});
|
|
7013
|
-
var LimitSchema =
|
|
7014
|
-
products:
|
|
7015
|
-
collections:
|
|
7016
|
-
articles:
|
|
7017
|
-
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)
|
|
7018
7128
|
}).default({ products: 5, collections: 3, articles: 5, pages: 2 });
|
|
7019
7129
|
async function findContentForTopicHandler(input, session) {
|
|
7020
7130
|
const tenantId = session.requireTenant();
|
|
@@ -7100,13 +7210,13 @@ MODOS (parametro mode):
|
|
|
7100
7210
|
|
|
7101
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.`,
|
|
7102
7212
|
{
|
|
7103
|
-
brandId:
|
|
7104
|
-
contexto:
|
|
7105
|
-
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"),
|
|
7106
7216
|
include: IncludeSchema.optional(),
|
|
7107
7217
|
limit: LimitSchema.optional(),
|
|
7108
|
-
diversidad:
|
|
7109
|
-
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)")
|
|
7110
7220
|
},
|
|
7111
7221
|
async ({ brandId: inputBrandId, contexto, fecha, include, limit, diversidad, mode: mode2 }) => {
|
|
7112
7222
|
const brandId = inputBrandId ?? session.requireBrand();
|
|
@@ -7210,11 +7320,11 @@ REGLAS:
|
|
|
7210
7320
|
|
|
7211
7321
|
USAR: antes de generar contenido de cualquier slot del calendario.`,
|
|
7212
7322
|
{
|
|
7213
|
-
brandId:
|
|
7214
|
-
keyword:
|
|
7215
|
-
plataforma:
|
|
7216
|
-
fecha:
|
|
7217
|
-
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)
|
|
7218
7328
|
},
|
|
7219
7329
|
async ({ brandId: inputBrandId, keyword, plataforma, fecha, limit }) => {
|
|
7220
7330
|
const tenantId = session.requireTenant();
|
|
@@ -7255,7 +7365,7 @@ DESPUES de ver la foto, decide:
|
|
|
7255
7365
|
|
|
7256
7366
|
Luego llama execute_photo_edit con tu analisis y prompt.`,
|
|
7257
7367
|
{
|
|
7258
|
-
fotoId:
|
|
7368
|
+
fotoId: import_zod37.z.string().describe("ID de la foto")
|
|
7259
7369
|
},
|
|
7260
7370
|
async ({ fotoId }) => {
|
|
7261
7371
|
const tenantId = session.requireTenant();
|
|
@@ -7288,14 +7398,14 @@ Retorna la foto editada para que la revises. Si no te gusta:
|
|
|
7288
7398
|
|
|
7289
7399
|
Si estrategia era 'tal_cual', pasa acciones=['none'] \u2014 no se edita pero si se genera thumbnail y embedding con tus tags.`,
|
|
7290
7400
|
{
|
|
7291
|
-
fotoId:
|
|
7292
|
-
prompt:
|
|
7293
|
-
acciones:
|
|
7294
|
-
descripcion:
|
|
7295
|
-
tipo:
|
|
7296
|
-
tagsPrimarios:
|
|
7297
|
-
tagsSecundarios:
|
|
7298
|
-
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())
|
|
7299
7409
|
},
|
|
7300
7410
|
async ({ fotoId, prompt, acciones, descripcion, tipo, tagsPrimarios, tagsSecundarios, tagsContexto }) => {
|
|
7301
7411
|
const tenantId = session.requireTenant();
|
|
@@ -7372,7 +7482,7 @@ Retorna:
|
|
|
7372
7482
|
- creditos: { balance, planId, periodEnd, costoPhotoEdit }
|
|
7373
7483
|
- _instrucciones: que hacer segun el estado`,
|
|
7374
7484
|
{
|
|
7375
|
-
fotoId:
|
|
7485
|
+
fotoId: import_zod37.z.string().describe("ID de la foto")
|
|
7376
7486
|
},
|
|
7377
7487
|
async ({ fotoId }) => {
|
|
7378
7488
|
const tenantId = session.requireTenant();
|
|
@@ -7472,11 +7582,11 @@ Retorna:
|
|
|
7472
7582
|
"find_products_for_content",
|
|
7473
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.`,
|
|
7474
7584
|
{
|
|
7475
|
-
brandId:
|
|
7476
|
-
contexto:
|
|
7477
|
-
fecha:
|
|
7478
|
-
limit:
|
|
7479
|
-
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)
|
|
7480
7590
|
},
|
|
7481
7591
|
async ({ brandId: inputBrandId, contexto, fecha, limit, diversidad }) => {
|
|
7482
7592
|
console.warn(
|
|
@@ -7529,10 +7639,10 @@ RETORNA { plantillaId, titulo, thumbnailUrl } o { plantillaId: null, motivo: 'no
|
|
|
7529
7639
|
|
|
7530
7640
|
USAR: solo si tenant tiene Canva conectado (tenants/{tenantId}/marketing_config/{brandId}.canva.connected=true).`,
|
|
7531
7641
|
{
|
|
7532
|
-
brandId:
|
|
7533
|
-
plataforma:
|
|
7534
|
-
tipoContenido:
|
|
7535
|
-
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")
|
|
7536
7646
|
},
|
|
7537
7647
|
async ({ brandId: inputBrandId, plataforma, tipoContenido, keyword }) => {
|
|
7538
7648
|
const tenantId = session.requireTenant();
|
|
@@ -7562,15 +7672,15 @@ USAR: cuando get_photos_for_slot retorna pocas fotos y el slot aun no esta cubie
|
|
|
7562
7672
|
|
|
7563
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.`,
|
|
7564
7674
|
{
|
|
7565
|
-
brandId:
|
|
7566
|
-
semana:
|
|
7567
|
-
necesidades:
|
|
7568
|
-
|
|
7569
|
-
tema:
|
|
7570
|
-
keyword:
|
|
7571
|
-
cantidadSugerida:
|
|
7572
|
-
razon:
|
|
7573
|
-
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")')
|
|
7574
7684
|
})
|
|
7575
7685
|
).min(1)
|
|
7576
7686
|
},
|
|
@@ -7716,8 +7826,8 @@ function registerMarketingTools(server, session) {
|
|
|
7716
7826
|
"get_calendar",
|
|
7717
7827
|
"Lee el calendario editorial del mes. Muestra semanas con items planificados por plataforma.",
|
|
7718
7828
|
{
|
|
7719
|
-
brandId:
|
|
7720
|
-
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)")
|
|
7721
7831
|
},
|
|
7722
7832
|
async ({ brandId: inputBrandId, mes }) => {
|
|
7723
7833
|
const tenantId = session.requireTenant();
|
|
@@ -7750,7 +7860,7 @@ function registerMarketingTools(server, session) {
|
|
|
7750
7860
|
"get_seo_snapshot",
|
|
7751
7861
|
"Lee el snapshot SEO de la brand: rank, keywords, oportunidades, competidores.",
|
|
7752
7862
|
{
|
|
7753
|
-
brandId:
|
|
7863
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand")
|
|
7754
7864
|
},
|
|
7755
7865
|
async ({ brandId: inputBrandId }) => {
|
|
7756
7866
|
const tenantId = session.requireTenant();
|
|
@@ -7769,8 +7879,8 @@ function registerMarketingTools(server, session) {
|
|
|
7769
7879
|
"get_photo_gallery",
|
|
7770
7880
|
"Fotos disponibles filtradas por estado. Muestra fotos con metadata y conteo.",
|
|
7771
7881
|
{
|
|
7772
|
-
brandId:
|
|
7773
|
-
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)")
|
|
7774
7884
|
},
|
|
7775
7885
|
async ({ brandId: inputBrandId, estado }) => {
|
|
7776
7886
|
session.requireTenant();
|
|
@@ -7810,7 +7920,7 @@ function registerMarketingTools(server, session) {
|
|
|
7810
7920
|
"generate_marketing_plan",
|
|
7811
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.",
|
|
7812
7922
|
{
|
|
7813
|
-
brandId:
|
|
7923
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand")
|
|
7814
7924
|
},
|
|
7815
7925
|
async ({ brandId: inputBrandId }) => {
|
|
7816
7926
|
const tenantId = session.requireTenant();
|
|
@@ -7824,8 +7934,8 @@ function registerMarketingTools(server, session) {
|
|
|
7824
7934
|
"save_marketing_plan",
|
|
7825
7935
|
"Guarda el plan de marketing generado por Claude en la configuracion de la brand. Escribe en tenants/{tenantId}/marketing_config/{brandId}.plan.",
|
|
7826
7936
|
{
|
|
7827
|
-
brandId:
|
|
7828
|
-
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.)")
|
|
7829
7939
|
},
|
|
7830
7940
|
async ({ brandId: inputBrandId, plan }) => {
|
|
7831
7941
|
const tenantId = session.requireTenant();
|
|
@@ -7838,7 +7948,14 @@ function registerMarketingTools(server, session) {
|
|
|
7838
7948
|
input: { tenantId, brandId, plan },
|
|
7839
7949
|
ctx
|
|
7840
7950
|
});
|
|
7841
|
-
const payload = result.state === "success" ? result.structuredOutput : {
|
|
7951
|
+
const payload = result.state === "success" ? result.structuredOutput : {
|
|
7952
|
+
ok: false,
|
|
7953
|
+
state: result.state,
|
|
7954
|
+
mensaje: result.text,
|
|
7955
|
+
// HITO 6 A6.7: incluir detalles Zod estructurados para que
|
|
7956
|
+
// Claude (LLM) pueda auto-recuperarse en el siguiente intento.
|
|
7957
|
+
...result.validationErrors && result.validationErrors.length > 0 ? { validationErrors: result.validationErrors } : {}
|
|
7958
|
+
};
|
|
7842
7959
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
7843
7960
|
}
|
|
7844
7961
|
);
|
|
@@ -7846,9 +7963,9 @@ function registerMarketingTools(server, session) {
|
|
|
7846
7963
|
"update_marketing_plan_field",
|
|
7847
7964
|
"Actualiza UN campo del plan de marketing sin sobreescribir el resto. Merge parcial. Ideal para agregar coleccionesPriorizadas, actualizar quickWins, etc.",
|
|
7848
7965
|
{
|
|
7849
|
-
brandId:
|
|
7850
|
-
field:
|
|
7851
|
-
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")
|
|
7852
7969
|
},
|
|
7853
7970
|
async ({ brandId: inputBrandId, field, value }) => {
|
|
7854
7971
|
const tenantId = session.requireTenant();
|
|
@@ -7861,7 +7978,7 @@ function registerMarketingTools(server, session) {
|
|
|
7861
7978
|
"generate_brand_brief",
|
|
7862
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.",
|
|
7863
7980
|
{
|
|
7864
|
-
brandId:
|
|
7981
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand")
|
|
7865
7982
|
},
|
|
7866
7983
|
async ({ brandId: inputBrandId }) => {
|
|
7867
7984
|
const tenantId = session.requireTenant();
|
|
@@ -7889,8 +8006,8 @@ function registerMarketingTools(server, session) {
|
|
|
7889
8006
|
"save_brand_brief",
|
|
7890
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).",
|
|
7891
8008
|
{
|
|
7892
|
-
brandId:
|
|
7893
|
-
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")
|
|
7894
8011
|
},
|
|
7895
8012
|
async ({ brandId: inputBrandId, brandBrief }) => {
|
|
7896
8013
|
const tenantId = session.requireTenant();
|
|
@@ -7903,9 +8020,9 @@ function registerMarketingTools(server, session) {
|
|
|
7903
8020
|
"generate_weekly_content",
|
|
7904
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.",
|
|
7905
8022
|
{
|
|
7906
|
-
brandId:
|
|
7907
|
-
semana:
|
|
7908
|
-
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'.")
|
|
7909
8026
|
},
|
|
7910
8027
|
async ({ brandId: inputBrandId, semana, modo }) => {
|
|
7911
8028
|
const tenantId = session.requireTenant();
|
|
@@ -7921,14 +8038,14 @@ function registerMarketingTools(server, session) {
|
|
|
7921
8038
|
|
|
7922
8039
|
IMPORTANTE: Si seleccionaste una foto con get_photos_for_slot, SIEMPRE pasa fotoId aqu\xED. Sin fotoId el post se publica sin imagen.`,
|
|
7923
8040
|
{
|
|
7924
|
-
brandId:
|
|
7925
|
-
plataforma:
|
|
7926
|
-
tipo:
|
|
7927
|
-
keyword:
|
|
7928
|
-
languageCode:
|
|
7929
|
-
fotoId:
|
|
7930
|
-
datos:
|
|
7931
|
-
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")
|
|
7932
8049
|
},
|
|
7933
8050
|
async ({ brandId: inputBrandId, plataforma, tipo, keyword, languageCode, fotoId, datos, calendarioItemRef }) => {
|
|
7934
8051
|
const tenantId = session.requireTenant();
|
|
@@ -7973,13 +8090,13 @@ Usa para: corregir body, metaTitle, tags, fotoId, o cualquier campo sin tener qu
|
|
|
7973
8090
|
NO puede cambiar: tenantId, brandId, id (inmutables).
|
|
7974
8091
|
Si pasas campos dentro de "datos", se hace merge con los datos existentes (no los reemplaza entero).`,
|
|
7975
8092
|
{
|
|
7976
|
-
contenidoId:
|
|
7977
|
-
datos:
|
|
7978
|
-
fotoId:
|
|
7979
|
-
keyword:
|
|
7980
|
-
languageCode:
|
|
7981
|
-
estado:
|
|
7982
|
-
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")
|
|
7983
8100
|
},
|
|
7984
8101
|
async ({ contenidoId, datos: newDatos, fotoId, keyword, languageCode, estado, calendarioItemRef }) => {
|
|
7985
8102
|
const tenantId = session.requireTenant();
|
|
@@ -8006,31 +8123,69 @@ Si pasas campos dentro de "datos", se hace merge con los datos existentes (no lo
|
|
|
8006
8123
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
8007
8124
|
}
|
|
8008
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
|
+
);
|
|
8009
8164
|
server.tool(
|
|
8010
8165
|
"update_calendar_slot",
|
|
8011
|
-
"
|
|
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.",
|
|
8012
8167
|
{
|
|
8013
|
-
brandId:
|
|
8014
|
-
mes:
|
|
8015
|
-
semana:
|
|
8016
|
-
slotIndex:
|
|
8017
|
-
cambios:
|
|
8018
|
-
dia:
|
|
8019
|
-
plataforma:
|
|
8020
|
-
tipo:
|
|
8021
|
-
keyword:
|
|
8022
|
-
tema:
|
|
8023
|
-
productoId:
|
|
8024
|
-
estado:
|
|
8025
|
-
contenidoRef:
|
|
8026
|
-
fotoIdAsignada:
|
|
8027
|
-
notas:
|
|
8028
|
-
locationId:
|
|
8029
|
-
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)")
|
|
8030
8185
|
}).strict().describe("Campos del slot. Solo acepta campos validos del schema. Campos no reconocidos seran rechazados."),
|
|
8031
|
-
accionContenidoExistente:
|
|
8032
|
-
|
|
8033
|
-
|
|
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")
|
|
8034
8189
|
]).optional().describe(
|
|
8035
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)."
|
|
8036
8191
|
)
|
|
@@ -8046,7 +8201,14 @@ Si pasas campos dentro de "datos", se hace merge con los datos existentes (no lo
|
|
|
8046
8201
|
input: { tenantId, brandId, mes, semana, slotIndex, cambios, accionContenidoExistente },
|
|
8047
8202
|
ctx
|
|
8048
8203
|
});
|
|
8049
|
-
const payload = result.state === "success" ? result.structuredOutput : {
|
|
8204
|
+
const payload = result.state === "success" ? result.structuredOutput : {
|
|
8205
|
+
ok: false,
|
|
8206
|
+
state: result.state,
|
|
8207
|
+
mensaje: result.text,
|
|
8208
|
+
// HITO 6 A6.7: incluir detalles Zod estructurados para que
|
|
8209
|
+
// Claude (LLM) pueda auto-recuperarse en el siguiente intento.
|
|
8210
|
+
...result.validationErrors && result.validationErrors.length > 0 ? { validationErrors: result.validationErrors } : {}
|
|
8211
|
+
};
|
|
8050
8212
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
8051
8213
|
}
|
|
8052
8214
|
);
|
|
@@ -8060,9 +8222,9 @@ ESCRIBE EN DOS LUGARES:
|
|
|
8060
8222
|
|
|
8061
8223
|
NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agenda, no en el post.`,
|
|
8062
8224
|
{
|
|
8063
|
-
contenidoRef:
|
|
8064
|
-
fotoId:
|
|
8065
|
-
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')
|
|
8066
8228
|
},
|
|
8067
8229
|
async ({ contenidoRef, fotoId, calendarioItemRef }) => {
|
|
8068
8230
|
const tenantId = session.requireTenant();
|
|
@@ -8075,7 +8237,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
8075
8237
|
"approve_content",
|
|
8076
8238
|
"Aprueba contenido para publicacion. Valida transicion de estado.",
|
|
8077
8239
|
{
|
|
8078
|
-
contenidoId:
|
|
8240
|
+
contenidoId: import_zod38.z.string().describe("ID del contenido a aprobar")
|
|
8079
8241
|
},
|
|
8080
8242
|
async ({ contenidoId }) => {
|
|
8081
8243
|
session.requireTenant();
|
|
@@ -8099,8 +8261,8 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
8099
8261
|
"reject_content",
|
|
8100
8262
|
"Rechaza contenido con motivo. Valida transicion de estado.",
|
|
8101
8263
|
{
|
|
8102
|
-
contenidoId:
|
|
8103
|
-
motivo:
|
|
8264
|
+
contenidoId: import_zod38.z.string().describe("ID del contenido a rechazar"),
|
|
8265
|
+
motivo: import_zod38.z.string().describe("Motivo del rechazo")
|
|
8104
8266
|
},
|
|
8105
8267
|
async ({ contenidoId, motivo }) => {
|
|
8106
8268
|
session.requireTenant();
|
|
@@ -8123,7 +8285,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
8123
8285
|
"get_collections",
|
|
8124
8286
|
"Lee todas las colecciones canonicas (Shopify/WordPress/webonly) con sus 6 campos SEO actuales. Incluye best practices 2026.",
|
|
8125
8287
|
{
|
|
8126
|
-
brandId:
|
|
8288
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand")
|
|
8127
8289
|
},
|
|
8128
8290
|
async ({ brandId: inputBrandId }) => {
|
|
8129
8291
|
const tenantId = session.requireTenant();
|
|
@@ -8210,7 +8372,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
8210
8372
|
"save_collection_suggestions",
|
|
8211
8373
|
"Guarda sugerencias SEO para colecciones de Shopify. El tenant las aprueba en la UI.",
|
|
8212
8374
|
{
|
|
8213
|
-
brandId:
|
|
8375
|
+
brandId: import_zod38.z.string().optional().describe("ID de la brand"),
|
|
8214
8376
|
suggestions: CollectionSuggestionsInputArraySchema.describe("Array de sugerencias SEO. Cada una con max 60 chars en metaTitle, max 158 en metaDescription, max 125 en imageAlt")
|
|
8215
8377
|
},
|
|
8216
8378
|
async ({ brandId: inputBrandId, suggestions }) => {
|