ponch-mcp-server 1.0.93 → 1.0.95
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 +195 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12942,7 +12942,9 @@ var brandBriefWriterContract = MartinContractSchema.parse(
|
|
|
12942
12942
|
);
|
|
12943
12943
|
async function save(input) {
|
|
12944
12944
|
const { db, tenantId, brandId, plan, actor } = input;
|
|
12945
|
-
const brandRef = db.
|
|
12945
|
+
const brandRef = db.doc(
|
|
12946
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
12947
|
+
);
|
|
12946
12948
|
const brandSnap = await brandRef.get();
|
|
12947
12949
|
if (!brandSnap.exists) {
|
|
12948
12950
|
return { ok: false, error: `Brand "${brandId}" no encontrada`, code: "BRAND_NOT_FOUND" };
|
|
@@ -12973,7 +12975,9 @@ async function save(input) {
|
|
|
12973
12975
|
}
|
|
12974
12976
|
async function updateField(input) {
|
|
12975
12977
|
const { db, tenantId, brandId, field, value } = input;
|
|
12976
|
-
const brandRef = db.
|
|
12978
|
+
const brandRef = db.doc(
|
|
12979
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
12980
|
+
);
|
|
12977
12981
|
const brandSnap = await brandRef.get();
|
|
12978
12982
|
if (!brandSnap.exists) {
|
|
12979
12983
|
return { ok: false, error: `Brand "${brandId}" no encontrada`, code: "BRAND_NOT_FOUND" };
|
|
@@ -13574,6 +13578,12 @@ var rawContract6 = {
|
|
|
13574
13578
|
destructive: false,
|
|
13575
13579
|
affectsPublication: true,
|
|
13576
13580
|
affectsExternal: true,
|
|
13581
|
+
martinConfirmationTemplate: (input, locale) => {
|
|
13582
|
+
return getMessage("marketing.seoSuggestion.confirmApply", locale, {
|
|
13583
|
+
suggestionId: input.suggestionId,
|
|
13584
|
+
fieldsCount: String(input.fieldsToApply?.length ?? 0)
|
|
13585
|
+
});
|
|
13586
|
+
},
|
|
13577
13587
|
martinSummaryTemplate: (_input, output, locale) => {
|
|
13578
13588
|
if (!output.ok) {
|
|
13579
13589
|
if (output.code) {
|
|
@@ -13851,11 +13861,12 @@ var rawContract7 = {
|
|
|
13851
13861
|
after: output.ok ? {
|
|
13852
13862
|
camposActualizados: output.camposActualizados,
|
|
13853
13863
|
datosKeys: input.datos ? Object.keys(input.datos) : [],
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
|
|
13864
|
+
// Firestore rechaza undefined — normalizar a null cuando el caller omite.
|
|
13865
|
+
fotoId: input.fotoId ?? null,
|
|
13866
|
+
keyword: input.keyword ?? null,
|
|
13867
|
+
languageCode: input.languageCode ?? null,
|
|
13868
|
+
estado: input.estado ?? null,
|
|
13869
|
+
calendarioItemRef: input.calendarioItemRef ?? null
|
|
13859
13870
|
} : null
|
|
13860
13871
|
}),
|
|
13861
13872
|
quotasConsumed: [],
|
|
@@ -13869,7 +13880,7 @@ var contenidoUpdaterContract = MartinContractSchema.parse(
|
|
|
13869
13880
|
);
|
|
13870
13881
|
async function addCalendarSlot(input) {
|
|
13871
13882
|
const { db, tenantId, brandId, mes, semana, slot } = input;
|
|
13872
|
-
const calQuery = await db.collection("
|
|
13883
|
+
const calQuery = await db.collection(collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_calendario")).where("mes", "==", mes).limit(1).get();
|
|
13873
13884
|
if (calQuery.empty) {
|
|
13874
13885
|
return {
|
|
13875
13886
|
ok: false,
|
|
@@ -13993,7 +14004,7 @@ async function calendarSlotUpdater(input) {
|
|
|
13993
14004
|
if (slotIndex < 0) {
|
|
13994
14005
|
return { ok: false, error: "slotIndex no puede ser negativo" };
|
|
13995
14006
|
}
|
|
13996
|
-
const calQuery = await db.collection("
|
|
14007
|
+
const calQuery = await db.collection(collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_calendario")).where("mes", "==", mes).limit(1).get();
|
|
13997
14008
|
if (calQuery.empty) {
|
|
13998
14009
|
return { ok: false, error: "Calendario no encontrado" };
|
|
13999
14010
|
}
|
|
@@ -14028,7 +14039,7 @@ async function calendarSlotUpdater(input) {
|
|
|
14028
14039
|
}
|
|
14029
14040
|
let contenidosADescartar = [];
|
|
14030
14041
|
if (oldContenidoRef && (accionContenidoExistente === "descartar" || !tocaSemantica)) {
|
|
14031
|
-
const contenidoQuery = await db.collection("
|
|
14042
|
+
const contenidoQuery = await db.collection(collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_contenido")).where("calendarioItemRef", "==", oldContenidoRef).limit(10).get();
|
|
14032
14043
|
contenidosADescartar = contenidoQuery.docs.filter((c) => {
|
|
14033
14044
|
const data = c.data();
|
|
14034
14045
|
return data.estado !== "descartado" && data.estado !== ESTADO_CONTENIDO.PUBLICADO;
|
|
@@ -14088,7 +14099,13 @@ async function calendarSlotUpdater(input) {
|
|
|
14088
14099
|
let movedSlotEstado = null;
|
|
14089
14100
|
if (moveTarget && oldContenidoRef) {
|
|
14090
14101
|
try {
|
|
14091
|
-
const contenidoSnap = await db.doc(
|
|
14102
|
+
const contenidoSnap = await db.doc(
|
|
14103
|
+
docPath(
|
|
14104
|
+
{ kind: "brand-nested", tenantId, brandId },
|
|
14105
|
+
"marketing_contenido",
|
|
14106
|
+
oldContenidoRef
|
|
14107
|
+
)
|
|
14108
|
+
).get();
|
|
14092
14109
|
if (contenidoSnap.exists) {
|
|
14093
14110
|
const estadoContenido = contenidoSnap.data().estado;
|
|
14094
14111
|
movedSlotEstado = mapContenidoEstadoToSlotEstado(estadoContenido);
|
|
@@ -14161,7 +14178,13 @@ async function calendarSlotUpdater(input) {
|
|
|
14161
14178
|
});
|
|
14162
14179
|
if (moveTarget && oldContenidoRef) {
|
|
14163
14180
|
try {
|
|
14164
|
-
await db.doc(
|
|
14181
|
+
await db.doc(
|
|
14182
|
+
docPath(
|
|
14183
|
+
{ kind: "brand-nested", tenantId, brandId },
|
|
14184
|
+
"marketing_contenido",
|
|
14185
|
+
oldContenidoRef
|
|
14186
|
+
)
|
|
14187
|
+
).update({
|
|
14165
14188
|
calendarioItemRef: moveTarget.targetRef
|
|
14166
14189
|
});
|
|
14167
14190
|
} catch (err) {
|
|
@@ -14325,7 +14348,7 @@ var calendarSlotUpdaterContract = MartinContractSchema.parse(
|
|
|
14325
14348
|
);
|
|
14326
14349
|
async function getCalendar(input) {
|
|
14327
14350
|
const { db, tenantId, brandId, mes } = input;
|
|
14328
|
-
const snap = await db.collection("
|
|
14351
|
+
const snap = await db.collection(collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_calendario")).where("mes", "==", mes).limit(1).get();
|
|
14329
14352
|
if (snap.empty) {
|
|
14330
14353
|
return {
|
|
14331
14354
|
ok: true,
|
|
@@ -14386,6 +14409,21 @@ var rawContract10 = {
|
|
|
14386
14409
|
var getCalendarContract = MartinContractSchema.parse(
|
|
14387
14410
|
rawContract10
|
|
14388
14411
|
);
|
|
14412
|
+
function normalizeFechaSnapshot(raw) {
|
|
14413
|
+
if (raw === null || raw === void 0) return null;
|
|
14414
|
+
if (typeof raw === "string") return raw;
|
|
14415
|
+
if (raw instanceof Date) return raw.toISOString();
|
|
14416
|
+
const maybeTimestamp = raw;
|
|
14417
|
+
if (typeof maybeTimestamp.toDate === "function") {
|
|
14418
|
+
const d = maybeTimestamp.toDate();
|
|
14419
|
+
if (d instanceof Date) return d.toISOString();
|
|
14420
|
+
}
|
|
14421
|
+
const maybeSerialized = raw;
|
|
14422
|
+
if (typeof maybeSerialized._seconds === "number") {
|
|
14423
|
+
return new Date(maybeSerialized._seconds * 1e3).toISOString();
|
|
14424
|
+
}
|
|
14425
|
+
return null;
|
|
14426
|
+
}
|
|
14389
14427
|
var DEFAULT_ALERTS_ES = {
|
|
14390
14428
|
noSeo: () => "No hay snapshot SEO. Genera uno para esta marca.",
|
|
14391
14429
|
noPlan: () => "No hay plan de marketing. Genera uno desde el snapshot SEO.",
|
|
@@ -14394,14 +14432,20 @@ var DEFAULT_ALERTS_ES = {
|
|
|
14394
14432
|
};
|
|
14395
14433
|
async function brandMarketingSummary(input, alerts = DEFAULT_ALERTS_ES) {
|
|
14396
14434
|
const { db, tenantId, brandId } = input;
|
|
14397
|
-
const brandRef = db.
|
|
14435
|
+
const brandRef = db.doc(
|
|
14436
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
14437
|
+
);
|
|
14398
14438
|
const brandSnap = await brandRef.get();
|
|
14399
14439
|
if (!brandSnap.exists) {
|
|
14400
14440
|
return { ok: false, code: "BRAND_NOT_FOUND" };
|
|
14401
14441
|
}
|
|
14402
14442
|
const brand = brandSnap.data() ?? {};
|
|
14403
|
-
const contenidoCol = db.collection(
|
|
14404
|
-
|
|
14443
|
+
const contenidoCol = db.collection(
|
|
14444
|
+
collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_contenido")
|
|
14445
|
+
);
|
|
14446
|
+
const fotosCol = db.collection(
|
|
14447
|
+
collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_fotos")
|
|
14448
|
+
);
|
|
14405
14449
|
const [pendientesSnap, publicadosSnap, fotosNuevasSnap] = await Promise.all([
|
|
14406
14450
|
contenidoCol.where("estado", "==", ESTADO_CONTENIDO.PENDIENTE_APROBACION).get(),
|
|
14407
14451
|
contenidoCol.where("estado", "==", ESTADO_CONTENIDO.PUBLICADO).limit(50).get(),
|
|
@@ -14426,7 +14470,9 @@ async function brandMarketingSummary(input, alerts = DEFAULT_ALERTS_ES) {
|
|
|
14426
14470
|
rank: seoSnapshot.rank ?? null,
|
|
14427
14471
|
totalKeywords: seoSnapshot.totalKeywords ?? null,
|
|
14428
14472
|
traficoOrganico: seoSnapshot.traficoOrganico ?? null,
|
|
14429
|
-
|
|
14473
|
+
// fechaSnapshot en Firestore es Timestamp. outputSchema declara
|
|
14474
|
+
// string ISO. Normalizamos vía firebase-admin Timestamp check.
|
|
14475
|
+
ultimoSnapshot: normalizeFechaSnapshot(seoSnapshot.fechaSnapshot)
|
|
14430
14476
|
} : null,
|
|
14431
14477
|
plan: { existe: planExiste },
|
|
14432
14478
|
contenido: {
|
|
@@ -14527,14 +14573,20 @@ function extractPreview(plataforma, datos) {
|
|
|
14527
14573
|
}
|
|
14528
14574
|
async function brandMarketingPendingActions(input) {
|
|
14529
14575
|
const { db, tenantId, brandId } = input;
|
|
14530
|
-
const brandRef = db.
|
|
14576
|
+
const brandRef = db.doc(
|
|
14577
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
14578
|
+
);
|
|
14531
14579
|
const brandSnap = await brandRef.get();
|
|
14532
14580
|
if (!brandSnap.exists) {
|
|
14533
14581
|
return { ok: false, code: "BRAND_NOT_FOUND" };
|
|
14534
14582
|
}
|
|
14535
14583
|
const brand = brandSnap.data() ?? {};
|
|
14536
|
-
const contenidoCol = db.collection(
|
|
14537
|
-
|
|
14584
|
+
const contenidoCol = db.collection(
|
|
14585
|
+
collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_contenido")
|
|
14586
|
+
);
|
|
14587
|
+
const fotosCol = db.collection(
|
|
14588
|
+
collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_fotos")
|
|
14589
|
+
);
|
|
14538
14590
|
const [pendientesSnap, fotosNuevasSnap] = await Promise.all([
|
|
14539
14591
|
contenidoCol.where("estado", "==", ESTADO_CONTENIDO.PENDIENTE_APROBACION).get(),
|
|
14540
14592
|
fotosCol.where("estado", "==", ESTADO_FOTO.NUEVA).get()
|
|
@@ -14624,7 +14676,7 @@ var DEFAULT_ALERTS_ES2 = {
|
|
|
14624
14676
|
};
|
|
14625
14677
|
async function tenantMarketingSummary(input, alerts = DEFAULT_ALERTS_ES2) {
|
|
14626
14678
|
const { db, tenantId } = input;
|
|
14627
|
-
const brandsSnap = await db.collection("
|
|
14679
|
+
const brandsSnap = await db.collection(collectionPath({ kind: "tenant-nested", tenantId }, "brands")).get();
|
|
14628
14680
|
const totalBrandsInTenant = brandsSnap.size;
|
|
14629
14681
|
if (totalBrandsInTenant === 0) {
|
|
14630
14682
|
return { ok: false, code: "TENANT_HAS_NO_BRANDS" };
|
|
@@ -14758,7 +14810,7 @@ var tenantMarketingSummaryContract = MartinContractSchema.parse(
|
|
|
14758
14810
|
var TENANT_PENDING_CAP = 20;
|
|
14759
14811
|
async function tenantMarketingPendingActions(input) {
|
|
14760
14812
|
const { db, tenantId } = input;
|
|
14761
|
-
const brandsSnap = await db.collection("
|
|
14813
|
+
const brandsSnap = await db.collection(collectionPath({ kind: "tenant-nested", tenantId }, "brands")).get();
|
|
14762
14814
|
const totalBrandsInTenant = brandsSnap.size;
|
|
14763
14815
|
if (totalBrandsInTenant === 0) {
|
|
14764
14816
|
return { ok: false, code: "TENANT_HAS_NO_BRANDS" };
|
|
@@ -14774,7 +14826,9 @@ async function tenantMarketingPendingActions(input) {
|
|
|
14774
14826
|
const brandIds = brandsSnap.docs.map((d) => d.id);
|
|
14775
14827
|
const nombrePorBrand = /* @__PURE__ */ new Map();
|
|
14776
14828
|
const configReads = brandIds.map(
|
|
14777
|
-
(brandId) => db.
|
|
14829
|
+
(brandId) => db.doc(
|
|
14830
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
14831
|
+
).get().then((snap) => {
|
|
14778
14832
|
const nombre = snap.exists ? snap.data()?.nombre ?? null : null;
|
|
14779
14833
|
nombrePorBrand.set(brandId, nombre);
|
|
14780
14834
|
})
|
|
@@ -14886,12 +14940,16 @@ var tenantMarketingPendingActionsContract = MartinContractSchema.parse(
|
|
|
14886
14940
|
);
|
|
14887
14941
|
async function seoSnapshotReader(input) {
|
|
14888
14942
|
const { db, tenantId, brandId } = input;
|
|
14889
|
-
const brandRef = db.
|
|
14943
|
+
const brandRef = db.doc(
|
|
14944
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
14945
|
+
);
|
|
14890
14946
|
const brandSnap = await brandRef.get();
|
|
14891
14947
|
if (!brandSnap.exists) {
|
|
14892
14948
|
return { ok: false, code: "BRAND_NOT_FOUND" };
|
|
14893
14949
|
}
|
|
14894
|
-
const snap = await db.collection(
|
|
14950
|
+
const snap = await db.collection(
|
|
14951
|
+
collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_snapshots_semrush")
|
|
14952
|
+
).orderBy("mes", "desc").limit(1).get();
|
|
14895
14953
|
if (snap.empty) {
|
|
14896
14954
|
return { ok: false, code: "SEO_SNAPSHOT_MISSING" };
|
|
14897
14955
|
}
|
|
@@ -14998,12 +15056,14 @@ var SCHEMA_FOR_SAVE_EN = {
|
|
|
14998
15056
|
};
|
|
14999
15057
|
async function collectionsReader(input) {
|
|
15000
15058
|
const { db, tenantId, brandId } = input;
|
|
15001
|
-
const brandRef = db.
|
|
15059
|
+
const brandRef = db.doc(
|
|
15060
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
15061
|
+
);
|
|
15002
15062
|
const brandSnap = await brandRef.get();
|
|
15003
15063
|
if (!brandSnap.exists) {
|
|
15004
15064
|
return { ok: false, code: "BRAND_NOT_FOUND" };
|
|
15005
15065
|
}
|
|
15006
|
-
const collSnap = await db.
|
|
15066
|
+
const collSnap = await db.doc(docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_snapshots", "main")).collection("collections").get();
|
|
15007
15067
|
if (collSnap.empty) {
|
|
15008
15068
|
return { ok: false, code: "NO_COLLECTIONS_NESTED" };
|
|
15009
15069
|
}
|
|
@@ -15011,7 +15071,9 @@ async function collectionsReader(input) {
|
|
|
15011
15071
|
const brand = brandSnap.data() ?? {};
|
|
15012
15072
|
const plan = brand.plan ?? {};
|
|
15013
15073
|
const keywordsPrioritarios = Array.isArray(plan.keywordsPrioritarios) ? plan.keywordsPrioritarios : [];
|
|
15014
|
-
const seoSuggestionsCol = db.collection(
|
|
15074
|
+
const seoSuggestionsCol = db.collection(
|
|
15075
|
+
collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_suggestions_seo")
|
|
15076
|
+
);
|
|
15015
15077
|
const seoSnap = await seoSuggestionsCol.where("intent", "==", "seo_meta").where("target.category", "==", "collection").where("estado", "in", ["pendiente", "aplicada"]).get();
|
|
15016
15078
|
const existingSuggestions = {};
|
|
15017
15079
|
for (const d of seoSnap.docs) {
|
|
@@ -15132,7 +15194,9 @@ var collectionsReaderContract = MartinContractSchema.parse(
|
|
|
15132
15194
|
var QUERY_LIMIT = 50;
|
|
15133
15195
|
async function photoGalleryReader(input) {
|
|
15134
15196
|
const { db, tenantId, brandId, estado } = input;
|
|
15135
|
-
let query = db.collection(
|
|
15197
|
+
let query = db.collection(
|
|
15198
|
+
collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_fotos")
|
|
15199
|
+
);
|
|
15136
15200
|
if (estado) {
|
|
15137
15201
|
query = query.where("estado", "==", estado);
|
|
15138
15202
|
}
|
|
@@ -15340,7 +15404,9 @@ async function photoEditStatusReader(input) {
|
|
|
15340
15404
|
const { db, tenantId, brandId, fotoId } = input;
|
|
15341
15405
|
const maxPerDay = MARKETING_CONSTRAINTS.photoEdit.maxIterationsPerPhotoPerDay;
|
|
15342
15406
|
const costoPhotoEdit = CREDIT_COSTS.photo_edit;
|
|
15343
|
-
const fotoRef = db.
|
|
15407
|
+
const fotoRef = db.doc(
|
|
15408
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_fotos", fotoId)
|
|
15409
|
+
);
|
|
15344
15410
|
const fotoSnap = await fotoRef.get();
|
|
15345
15411
|
if (!fotoSnap.exists) {
|
|
15346
15412
|
return { ok: false, code: "FOTO_NOT_FOUND" };
|
|
@@ -15354,7 +15420,9 @@ async function photoEditStatusReader(input) {
|
|
|
15354
15420
|
const estado = typeof foto.estado === "string" ? foto.estado : "desconocido";
|
|
15355
15421
|
const ultimoError = typeof foto.ultimoError === "string" ? foto.ultimoError : null;
|
|
15356
15422
|
const lockId = `photo_edit_${fotoId}`;
|
|
15357
|
-
const lockSnap = await db.
|
|
15423
|
+
const lockSnap = await db.doc(
|
|
15424
|
+
docPath({ kind: "tenant-nested", tenantId }, "locks", lockId)
|
|
15425
|
+
).get();
|
|
15358
15426
|
let lock = { held: false };
|
|
15359
15427
|
if (lockSnap.exists) {
|
|
15360
15428
|
const lockData = lockSnap.data();
|
|
@@ -15376,7 +15444,9 @@ async function photoEditStatusReader(input) {
|
|
|
15376
15444
|
periodEnd: null,
|
|
15377
15445
|
costoPhotoEdit
|
|
15378
15446
|
};
|
|
15379
|
-
const creditsSnap = await db.
|
|
15447
|
+
const creditsSnap = await db.doc(
|
|
15448
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "credits", "main")
|
|
15449
|
+
).get();
|
|
15380
15450
|
if (creditsSnap.exists) {
|
|
15381
15451
|
const creditsData = creditsSnap.data();
|
|
15382
15452
|
credits = {
|
|
@@ -15502,13 +15572,18 @@ var photoEditStatusReaderContract = MartinContractSchema.parse(
|
|
|
15502
15572
|
rawContract18
|
|
15503
15573
|
);
|
|
15504
15574
|
async function contenidoApprover(input) {
|
|
15505
|
-
const { db, tenantId, contenidoId, actor } = input;
|
|
15506
|
-
const ref = db.
|
|
15575
|
+
const { db, tenantId, brandId, contenidoId, actor } = input;
|
|
15576
|
+
const ref = db.doc(
|
|
15577
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_contenido", contenidoId)
|
|
15578
|
+
);
|
|
15507
15579
|
const snap = await ref.get();
|
|
15508
15580
|
if (!snap.exists) {
|
|
15509
15581
|
return { ok: false, code: "CONTENT_NOT_FOUND" };
|
|
15510
15582
|
}
|
|
15511
15583
|
const data = snap.data();
|
|
15584
|
+
if (typeof data.brandId === "string" && data.brandId !== brandId) {
|
|
15585
|
+
return { ok: false, code: "CONTENT_BRAND_MISMATCH" };
|
|
15586
|
+
}
|
|
15512
15587
|
const estadoActual = typeof data.estado === "string" ? data.estado : "";
|
|
15513
15588
|
if (!esTransicionValida(estadoActual, ESTADO_CONTENIDO.APROBADO)) {
|
|
15514
15589
|
return {
|
|
@@ -15534,6 +15609,7 @@ async function contenidoApprover(input) {
|
|
|
15534
15609
|
}
|
|
15535
15610
|
var ParamsSchema19 = import_zod54.z.object({
|
|
15536
15611
|
tenantId: import_zod54.z.string().min(1).describe('Tenant identifier (the business account). Example: "your-tenant-id".'),
|
|
15612
|
+
brandId: import_zod54.z.string().min(1).describe('Brand identifier within the tenant. Example: "your-brand-id".'),
|
|
15537
15613
|
contenidoId: import_zod54.z.string().min(1).describe('Marketing content document id. Example: "BgAeOYTcPaQ0gglhK4PU".'),
|
|
15538
15614
|
actor: import_zod54.z.object({
|
|
15539
15615
|
uid: import_zod54.z.string().min(1).describe('User id who is approving. Example: "your-user-uid".'),
|
|
@@ -15550,7 +15626,7 @@ var SuccessSchema8 = import_zod54.z.object({
|
|
|
15550
15626
|
});
|
|
15551
15627
|
var FailureSchema8 = import_zod54.z.object({
|
|
15552
15628
|
ok: import_zod54.z.literal(false),
|
|
15553
|
-
code: import_zod54.z.enum(["CONTENT_NOT_FOUND", "INVALID_STATE_TRANSITION"]),
|
|
15629
|
+
code: import_zod54.z.enum(["CONTENT_NOT_FOUND", "INVALID_STATE_TRANSITION", "CONTENT_BRAND_MISMATCH"]),
|
|
15554
15630
|
estadoActual: import_zod54.z.string().optional()
|
|
15555
15631
|
});
|
|
15556
15632
|
var OutputSchema19 = import_zod54.z.discriminatedUnion("ok", [SuccessSchema8, FailureSchema8]);
|
|
@@ -15585,7 +15661,7 @@ var rawContract19 = {
|
|
|
15585
15661
|
});
|
|
15586
15662
|
},
|
|
15587
15663
|
auditAction: "marketing.contenido.aprobar",
|
|
15588
|
-
extractTargetPath: (input) => `tenants/${input.tenantId}/marketing_contenido/${input.contenidoId}`,
|
|
15664
|
+
extractTargetPath: (input) => `tenants/${input.tenantId}/brands/${input.brandId}/marketing_contenido/${input.contenidoId}`,
|
|
15589
15665
|
extractChanges: (_input, output) => ({
|
|
15590
15666
|
before: output.ok ? { estado: output.estadoAnterior } : null,
|
|
15591
15667
|
after: output.ok ? { estado: output.nuevoEstado } : null
|
|
@@ -15600,13 +15676,18 @@ var contenidoApproverContract = MartinContractSchema.parse(
|
|
|
15600
15676
|
rawContract19
|
|
15601
15677
|
);
|
|
15602
15678
|
async function contenidoRejecter(input) {
|
|
15603
|
-
const { db, tenantId, contenidoId, motivo, actor } = input;
|
|
15604
|
-
const ref = db.
|
|
15679
|
+
const { db, tenantId, brandId, contenidoId, motivo, actor } = input;
|
|
15680
|
+
const ref = db.doc(
|
|
15681
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_contenido", contenidoId)
|
|
15682
|
+
);
|
|
15605
15683
|
const snap = await ref.get();
|
|
15606
15684
|
if (!snap.exists) {
|
|
15607
15685
|
return { ok: false, code: "CONTENT_NOT_FOUND" };
|
|
15608
15686
|
}
|
|
15609
15687
|
const data = snap.data();
|
|
15688
|
+
if (typeof data.brandId === "string" && data.brandId !== brandId) {
|
|
15689
|
+
return { ok: false, code: "CONTENT_BRAND_MISMATCH" };
|
|
15690
|
+
}
|
|
15610
15691
|
const estadoActual = typeof data.estado === "string" ? data.estado : "";
|
|
15611
15692
|
if (!esTransicionValida(estadoActual, ESTADO_CONTENIDO.RECHAZADO)) {
|
|
15612
15693
|
return {
|
|
@@ -15634,6 +15715,7 @@ async function contenidoRejecter(input) {
|
|
|
15634
15715
|
}
|
|
15635
15716
|
var ParamsSchema20 = import_zod55.z.object({
|
|
15636
15717
|
tenantId: import_zod55.z.string().min(1).describe('Tenant identifier (the business account). Example: "your-tenant-id".'),
|
|
15718
|
+
brandId: import_zod55.z.string().min(1).describe('Brand identifier within the tenant. Example: "your-brand-id".'),
|
|
15637
15719
|
contenidoId: import_zod55.z.string().min(1).describe('Marketing content document id. Example: "BgAeOYTcPaQ0gglhK4PU".'),
|
|
15638
15720
|
motivo: import_zod55.z.string().min(1).describe('Reason for rejection (required, surfaced to tenant in UI). Example: "tono no encaja con la marca".'),
|
|
15639
15721
|
actor: import_zod55.z.object({
|
|
@@ -15652,7 +15734,7 @@ var SuccessSchema9 = import_zod55.z.object({
|
|
|
15652
15734
|
});
|
|
15653
15735
|
var FailureSchema9 = import_zod55.z.object({
|
|
15654
15736
|
ok: import_zod55.z.literal(false),
|
|
15655
|
-
code: import_zod55.z.enum(["CONTENT_NOT_FOUND", "INVALID_STATE_TRANSITION"]),
|
|
15737
|
+
code: import_zod55.z.enum(["CONTENT_NOT_FOUND", "INVALID_STATE_TRANSITION", "CONTENT_BRAND_MISMATCH"]),
|
|
15656
15738
|
estadoActual: import_zod55.z.string().optional()
|
|
15657
15739
|
});
|
|
15658
15740
|
var OutputSchema20 = import_zod55.z.discriminatedUnion("ok", [SuccessSchema9, FailureSchema9]);
|
|
@@ -15689,7 +15771,7 @@ var rawContract20 = {
|
|
|
15689
15771
|
});
|
|
15690
15772
|
},
|
|
15691
15773
|
auditAction: "marketing.contenido.rechazar",
|
|
15692
|
-
extractTargetPath: (input) => `tenants/${input.tenantId}/marketing_contenido/${input.contenidoId}`,
|
|
15774
|
+
extractTargetPath: (input) => `tenants/${input.tenantId}/brands/${input.brandId}/marketing_contenido/${input.contenidoId}`,
|
|
15693
15775
|
extractChanges: (_input, output) => ({
|
|
15694
15776
|
before: output.ok ? { estado: output.estadoAnterior } : null,
|
|
15695
15777
|
after: output.ok ? { estado: output.nuevoEstado, rechazadoMotivo: output.motivo } : null
|
|
@@ -15706,7 +15788,9 @@ var contenidoRejecterContract = MartinContractSchema.parse(
|
|
|
15706
15788
|
async function photoBriefingWriter(input) {
|
|
15707
15789
|
const { db, tenantId, brandId, semana, necesidades, actor } = input;
|
|
15708
15790
|
const docId = `${tenantId}_${brandId}_${semana}`;
|
|
15709
|
-
const ref = db.
|
|
15791
|
+
const ref = db.doc(
|
|
15792
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_fotobriefings", docId)
|
|
15793
|
+
);
|
|
15710
15794
|
const snap = await ref.get();
|
|
15711
15795
|
const existing = snap.exists ? snap.data() : null;
|
|
15712
15796
|
const prevNecesidades = Array.isArray(existing?.necesidades) ? existing.necesidades : [];
|
|
@@ -15769,7 +15853,7 @@ async function photoBriefingWriter(input) {
|
|
|
15769
15853
|
return {
|
|
15770
15854
|
ok: true,
|
|
15771
15855
|
briefingId: docId,
|
|
15772
|
-
briefingPath:
|
|
15856
|
+
briefingPath: docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_fotobriefings", docId),
|
|
15773
15857
|
totalNecesidades,
|
|
15774
15858
|
totalCubiertas,
|
|
15775
15859
|
necesidadesAgregadas: necesidades.length,
|
|
@@ -15822,7 +15906,7 @@ var rawContract21 = {
|
|
|
15822
15906
|
});
|
|
15823
15907
|
},
|
|
15824
15908
|
auditAction: "marketing.foto_briefing.agregar",
|
|
15825
|
-
extractTargetPath: (input) => `tenants/${input.tenantId}/marketing_fotobriefings/${input.tenantId}_${input.brandId}_${input.semana}`,
|
|
15909
|
+
extractTargetPath: (input) => `tenants/${input.tenantId}/brands/${input.brandId}/marketing_fotobriefings/${input.tenantId}_${input.brandId}_${input.semana}`,
|
|
15826
15910
|
extractChanges: (input, output) => ({
|
|
15827
15911
|
before: null,
|
|
15828
15912
|
// Helper no captura snapshot pre-merge.
|
|
@@ -15844,7 +15928,9 @@ var photoBriefingWriterContract = MartinContractSchema.parse(
|
|
|
15844
15928
|
);
|
|
15845
15929
|
async function tenantContextSetter(input) {
|
|
15846
15930
|
const { db, tenantId, brandId } = input;
|
|
15847
|
-
const brandConfigRef = db.
|
|
15931
|
+
const brandConfigRef = db.doc(
|
|
15932
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
15933
|
+
);
|
|
15848
15934
|
const brandSnap = await brandConfigRef.get();
|
|
15849
15935
|
if (brandSnap.exists) {
|
|
15850
15936
|
const data = brandSnap.data();
|
|
@@ -15856,7 +15942,7 @@ async function tenantContextSetter(input) {
|
|
|
15856
15942
|
brandDominio: typeof data.dominio === "string" ? data.dominio : null
|
|
15857
15943
|
};
|
|
15858
15944
|
}
|
|
15859
|
-
const allBrandsSnap = await db.collection("
|
|
15945
|
+
const allBrandsSnap = await db.collection(collectionPath({ kind: "tenant-nested", tenantId }, "brands")).get();
|
|
15860
15946
|
if (allBrandsSnap.empty) {
|
|
15861
15947
|
return {
|
|
15862
15948
|
ok: false,
|
|
@@ -15947,13 +16033,18 @@ var tenantContextSetterContract = MartinContractSchema.parse(
|
|
|
15947
16033
|
rawContract22
|
|
15948
16034
|
);
|
|
15949
16035
|
async function photoDescarter(input) {
|
|
15950
|
-
const { db, tenantId, fotoId, actor } = input;
|
|
15951
|
-
const ref = db.
|
|
16036
|
+
const { db, tenantId, brandId, fotoId, actor } = input;
|
|
16037
|
+
const ref = db.doc(
|
|
16038
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_fotos", fotoId)
|
|
16039
|
+
);
|
|
15952
16040
|
const snap = await ref.get();
|
|
15953
16041
|
if (!snap.exists) {
|
|
15954
16042
|
return { ok: false, code: "FOTO_NOT_FOUND" };
|
|
15955
16043
|
}
|
|
15956
16044
|
const data = snap.data();
|
|
16045
|
+
if (typeof data.brandId === "string" && data.brandId !== brandId) {
|
|
16046
|
+
return { ok: false, code: "FOTO_BRAND_MISMATCH" };
|
|
16047
|
+
}
|
|
15957
16048
|
const estadoActual = typeof data.estado === "string" ? data.estado : "";
|
|
15958
16049
|
if (!validarTransicionFoto(estadoActual, ESTADO_FOTO.DESCARTADA)) {
|
|
15959
16050
|
return {
|
|
@@ -15979,6 +16070,7 @@ async function photoDescarter(input) {
|
|
|
15979
16070
|
}
|
|
15980
16071
|
var ParamsSchema23 = import_zod58.z.object({
|
|
15981
16072
|
tenantId: import_zod58.z.string().min(1).describe('Tenant identifier (the business account). Example: "your-tenant-id".'),
|
|
16073
|
+
brandId: import_zod58.z.string().min(1).describe('Brand identifier within the tenant. Example: "your-brand-id".'),
|
|
15982
16074
|
fotoId: import_zod58.z.string().min(1).describe('Photo identifier. Example: "your-tenant_your-brand_1234567890_abcdef".'),
|
|
15983
16075
|
actor: import_zod58.z.object({
|
|
15984
16076
|
uid: import_zod58.z.string().min(1).describe('User id who is discarding. Example: "your-user-uid".'),
|
|
@@ -15995,7 +16087,7 @@ var SuccessSchema11 = import_zod58.z.object({
|
|
|
15995
16087
|
});
|
|
15996
16088
|
var FailureSchema11 = import_zod58.z.object({
|
|
15997
16089
|
ok: import_zod58.z.literal(false),
|
|
15998
|
-
code: import_zod58.z.enum(["FOTO_NOT_FOUND", "INVALID_STATE_TRANSITION"]),
|
|
16090
|
+
code: import_zod58.z.enum(["FOTO_NOT_FOUND", "INVALID_STATE_TRANSITION", "FOTO_BRAND_MISMATCH"]),
|
|
15999
16091
|
estadoActual: import_zod58.z.string().optional()
|
|
16000
16092
|
});
|
|
16001
16093
|
var OutputSchema23 = import_zod58.z.discriminatedUnion("ok", [SuccessSchema11, FailureSchema11]);
|
|
@@ -16030,7 +16122,7 @@ var rawContract23 = {
|
|
|
16030
16122
|
});
|
|
16031
16123
|
},
|
|
16032
16124
|
auditAction: "marketing.foto.descartar",
|
|
16033
|
-
extractTargetPath: (input) => `tenants/${input.tenantId}/marketing_fotos/${input.fotoId}`,
|
|
16125
|
+
extractTargetPath: (input) => `tenants/${input.tenantId}/brands/${input.brandId}/marketing_fotos/${input.fotoId}`,
|
|
16034
16126
|
extractChanges: (_input, output) => ({
|
|
16035
16127
|
before: output.ok ? { estado: output.estadoAnterior } : null,
|
|
16036
16128
|
after: output.ok ? { estado: output.nuevoEstado } : null
|
|
@@ -16045,13 +16137,18 @@ var photoDescarterContract = MartinContractSchema.parse(
|
|
|
16045
16137
|
rawContract23
|
|
16046
16138
|
);
|
|
16047
16139
|
async function photoEditadaMarker(input) {
|
|
16048
|
-
const { db, tenantId, fotoId, actor } = input;
|
|
16049
|
-
const ref = db.
|
|
16140
|
+
const { db, tenantId, brandId, fotoId, actor } = input;
|
|
16141
|
+
const ref = db.doc(
|
|
16142
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_fotos", fotoId)
|
|
16143
|
+
);
|
|
16050
16144
|
const snap = await ref.get();
|
|
16051
16145
|
if (!snap.exists) {
|
|
16052
16146
|
return { ok: false, code: "FOTO_NOT_FOUND" };
|
|
16053
16147
|
}
|
|
16054
16148
|
const data = snap.data();
|
|
16149
|
+
if (typeof data.brandId === "string" && data.brandId !== brandId) {
|
|
16150
|
+
return { ok: false, code: "FOTO_BRAND_MISMATCH" };
|
|
16151
|
+
}
|
|
16055
16152
|
const estadoActual = typeof data.estado === "string" ? data.estado : "";
|
|
16056
16153
|
const archivoOriginal = typeof data.archivoOriginal === "string" ? data.archivoOriginal : null;
|
|
16057
16154
|
if (!validarTransicionFoto(estadoActual, ESTADO_FOTO.EDITADA)) {
|
|
@@ -16083,6 +16180,7 @@ async function photoEditadaMarker(input) {
|
|
|
16083
16180
|
}
|
|
16084
16181
|
var ParamsSchema24 = import_zod59.z.object({
|
|
16085
16182
|
tenantId: import_zod59.z.string().min(1).describe('Tenant identifier. Example: "your-tenant-id".'),
|
|
16183
|
+
brandId: import_zod59.z.string().min(1).describe('Brand identifier within the tenant. Example: "your-brand-id".'),
|
|
16086
16184
|
fotoId: import_zod59.z.string().min(1).describe('Photo identifier. Example: "your-tenant_your-brand_1234567890_abcdef".'),
|
|
16087
16185
|
actor: import_zod59.z.object({
|
|
16088
16186
|
uid: import_zod59.z.string().min(1).describe('User id who marks the photo. Example: "your-user-uid".'),
|
|
@@ -16100,7 +16198,7 @@ var SuccessSchema12 = import_zod59.z.object({
|
|
|
16100
16198
|
});
|
|
16101
16199
|
var FailureSchema12 = import_zod59.z.object({
|
|
16102
16200
|
ok: import_zod59.z.literal(false),
|
|
16103
|
-
code: import_zod59.z.enum(["FOTO_NOT_FOUND", "INVALID_STATE_TRANSITION", "FOTO_SIN_ARCHIVO_ORIGINAL"]),
|
|
16201
|
+
code: import_zod59.z.enum(["FOTO_NOT_FOUND", "INVALID_STATE_TRANSITION", "FOTO_SIN_ARCHIVO_ORIGINAL", "FOTO_BRAND_MISMATCH"]),
|
|
16104
16202
|
estadoActual: import_zod59.z.string().optional()
|
|
16105
16203
|
});
|
|
16106
16204
|
var OutputSchema24 = import_zod59.z.discriminatedUnion("ok", [SuccessSchema12, FailureSchema12]);
|
|
@@ -16135,7 +16233,7 @@ var rawContract24 = {
|
|
|
16135
16233
|
});
|
|
16136
16234
|
},
|
|
16137
16235
|
auditAction: "marketing.foto.usar_tal_cual",
|
|
16138
|
-
extractTargetPath: (input) => `tenants/${input.tenantId}/marketing_fotos/${input.fotoId}`,
|
|
16236
|
+
extractTargetPath: (input) => `tenants/${input.tenantId}/brands/${input.brandId}/marketing_fotos/${input.fotoId}`,
|
|
16139
16237
|
extractChanges: (_input, output) => ({
|
|
16140
16238
|
before: output.ok ? { estado: output.estadoAnterior } : null,
|
|
16141
16239
|
after: output.ok ? { estado: output.nuevoEstado, archivoEditado: output.archivoEditado } : null
|
|
@@ -16151,7 +16249,9 @@ var photoEditadaMarkerContract = MartinContractSchema.parse(
|
|
|
16151
16249
|
);
|
|
16152
16250
|
async function brandContextSetter(input) {
|
|
16153
16251
|
const { db, tenantId, brandId } = input;
|
|
16154
|
-
const brandRef = db.
|
|
16252
|
+
const brandRef = db.doc(
|
|
16253
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
16254
|
+
);
|
|
16155
16255
|
const brandSnap = await brandRef.get();
|
|
16156
16256
|
if (!brandSnap.exists) {
|
|
16157
16257
|
return {
|
|
@@ -16634,7 +16734,9 @@ var brandBriefBuilderContract = MartinContractSchema.parse(
|
|
|
16634
16734
|
rawContract27
|
|
16635
16735
|
);
|
|
16636
16736
|
async function resolveLastImportId(db, tenantId, brandId) {
|
|
16637
|
-
const configSnap = await db.
|
|
16737
|
+
const configSnap = await db.doc(
|
|
16738
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
16739
|
+
).get();
|
|
16638
16740
|
const brand = configSnap.data();
|
|
16639
16741
|
const canalId = brand?.canalId;
|
|
16640
16742
|
if (!canalId) return null;
|
|
@@ -16645,7 +16747,9 @@ async function resolveLastImportId(db, tenantId, brandId) {
|
|
|
16645
16747
|
}
|
|
16646
16748
|
async function marketingPlanBuilder(input) {
|
|
16647
16749
|
const { db, tenantId, brandId } = input;
|
|
16648
|
-
const configSnap = await db.
|
|
16750
|
+
const configSnap = await db.doc(
|
|
16751
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
16752
|
+
).get();
|
|
16649
16753
|
if (!configSnap.exists) {
|
|
16650
16754
|
return { ok: false, error: `Brand "${brandId}" no encontrada`, code: "BRAND_NOT_FOUND" };
|
|
16651
16755
|
}
|
|
@@ -16659,7 +16763,9 @@ async function marketingPlanBuilder(input) {
|
|
|
16659
16763
|
}
|
|
16660
16764
|
const productosQ = await db.collection("productos").where("tenantId", "==", tenantId).limit(100).get();
|
|
16661
16765
|
const productos = productosQ.docs.map((d) => d.data());
|
|
16662
|
-
const histQ = await db.collection(
|
|
16766
|
+
const histQ = await db.collection(
|
|
16767
|
+
collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_contenido")
|
|
16768
|
+
).where("estado", "==", ESTADO_CONTENIDO.PUBLICADO).limit(20).get();
|
|
16663
16769
|
const historial = histQ.docs.map((d) => d.data());
|
|
16664
16770
|
const lastImportId = await resolveLastImportId(db, tenantId, brandId);
|
|
16665
16771
|
let colecciones = [];
|
|
@@ -16704,7 +16810,9 @@ async function marketingPlanBuilder(input) {
|
|
|
16704
16810
|
shopifyBlogs = Object.values(blogMap);
|
|
16705
16811
|
}
|
|
16706
16812
|
const existingBlogStrategy = brand.blogStrategy;
|
|
16707
|
-
const seoSuggestionsCol = db.collection(
|
|
16813
|
+
const seoSuggestionsCol = db.collection(
|
|
16814
|
+
collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_suggestions_seo")
|
|
16815
|
+
);
|
|
16708
16816
|
const appliedSnap = await seoSuggestionsCol.where("intent", "==", "seo_meta").where("target.category", "==", "collection").where("estado", "==", "aplicada").get();
|
|
16709
16817
|
const appliedChanges = [];
|
|
16710
16818
|
for (const d of appliedSnap.docs) {
|
|
@@ -17197,10 +17305,11 @@ var rawContract29 = {
|
|
|
17197
17305
|
after: output.ok ? {
|
|
17198
17306
|
contenidoId: output.contenidoId,
|
|
17199
17307
|
plataforma: output.plataforma,
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17308
|
+
// Firestore rechaza undefined — normalizamos a null cuando el caller omite.
|
|
17309
|
+
tipo: input.tipo ?? null,
|
|
17310
|
+
keyword: input.keyword ?? null,
|
|
17311
|
+
fotoId: input.fotoId ?? null,
|
|
17312
|
+
calendarioItemRef: input.calendarioItemRef ?? null,
|
|
17204
17313
|
descartados: output.descartados,
|
|
17205
17314
|
pipelineLinked: output.pipelineLinked
|
|
17206
17315
|
} : null
|
|
@@ -17874,7 +17983,9 @@ async function slotAssetFinder(input) {
|
|
|
17874
17983
|
const limit = input.limit ?? 5;
|
|
17875
17984
|
const photoThreshold = deps.thresholds?.photos ?? DEFAULT_PHOTO_THRESHOLD;
|
|
17876
17985
|
const shopifyThreshold = deps.thresholds?.shopify ?? DEFAULT_SHOPIFY_THRESHOLD;
|
|
17877
|
-
const configSnap = await db.
|
|
17986
|
+
const configSnap = await db.doc(
|
|
17987
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
17988
|
+
).get();
|
|
17878
17989
|
if (!configSnap.exists) {
|
|
17879
17990
|
return { ok: false, error: `Brand "${brandId}" no encontrada`, code: "BRAND_NOT_FOUND" };
|
|
17880
17991
|
}
|
|
@@ -18079,7 +18190,9 @@ function cosineSimilarity(a, b) {
|
|
|
18079
18190
|
async function canvaTemplateSelector(input) {
|
|
18080
18191
|
const { db, tenantId, brandId, plataforma, tipoContenido, keyword, deps } = input;
|
|
18081
18192
|
const threshold = deps.threshold ?? DEFAULT_SIMILARITY_THRESHOLD;
|
|
18082
|
-
const configSnap = await db.
|
|
18193
|
+
const configSnap = await db.doc(
|
|
18194
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
18195
|
+
).get();
|
|
18083
18196
|
if (!configSnap.exists) {
|
|
18084
18197
|
return { plantillaId: null, motivo: "brand_no_encontrada" };
|
|
18085
18198
|
}
|
|
@@ -18314,12 +18427,14 @@ function instruccionesParaError(code, details, lang) {
|
|
|
18314
18427
|
}
|
|
18315
18428
|
async function photoDirectorPlan(input) {
|
|
18316
18429
|
const { db, tenantId, brandId, fotoId, deps } = input;
|
|
18317
|
-
const fotoQ = await db.collection("
|
|
18430
|
+
const fotoQ = await db.collection(collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_fotos")).where("id", "==", fotoId).limit(1).get();
|
|
18318
18431
|
if (fotoQ.empty) {
|
|
18319
18432
|
return { ok: false, code: "FOTO_NOT_FOUND", error: `Foto ${fotoId} no encontrada` };
|
|
18320
18433
|
}
|
|
18321
18434
|
const foto = fotoQ.docs[0].data();
|
|
18322
|
-
const configSnap = await db.
|
|
18435
|
+
const configSnap = await db.doc(
|
|
18436
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
18437
|
+
).get();
|
|
18323
18438
|
const brand = configSnap.exists ? configSnap.data() : null;
|
|
18324
18439
|
const brandBrief = (brand?.brandBrief ?? null) || {};
|
|
18325
18440
|
const visualRules = brandBrief.visualRules ?? {};
|
|
@@ -18895,12 +19010,16 @@ Campos comunes de save_generated_content:
|
|
|
18895
19010
|
tipo: string (del slot)
|
|
18896
19011
|
`;
|
|
18897
19012
|
async function buildTenantContext(db, tenantId, brandId) {
|
|
18898
|
-
const configSnap = await db.
|
|
19013
|
+
const configSnap = await db.doc(
|
|
19014
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
19015
|
+
).get();
|
|
18899
19016
|
if (!configSnap.exists) return "";
|
|
18900
19017
|
const brand = configSnap.data();
|
|
18901
19018
|
const [fotosQ, contenidoQ, productosQ] = await Promise.all([
|
|
18902
|
-
db.collection("
|
|
18903
|
-
db.collection(
|
|
19019
|
+
db.collection(collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_fotos")).where("estado", "==", ESTADO_FOTO.EDITADA).limit(10).get(),
|
|
19020
|
+
db.collection(
|
|
19021
|
+
collectionPath({ kind: "brand-nested", tenantId, brandId }, "marketing_contenido")
|
|
19022
|
+
).where("estado", "==", ESTADO_CONTENIDO.PUBLICADO).limit(10).get(),
|
|
18904
19023
|
db.collection("productos").where("tenantId", "==", tenantId).limit(50).get()
|
|
18905
19024
|
]);
|
|
18906
19025
|
const fotosEditadas = fotosQ.docs.map((d) => d.data());
|
|
@@ -19109,7 +19228,9 @@ async function buildSystemPrompt(input) {
|
|
|
19109
19228
|
const p2 = PARTE_2_REGLAS.replace("{{SHAPES_BLOCK}}", shapesBlock);
|
|
19110
19229
|
return p1 + p2;
|
|
19111
19230
|
}
|
|
19112
|
-
const configSnap = await db.
|
|
19231
|
+
const configSnap = await db.doc(
|
|
19232
|
+
docPath({ kind: "brand-nested", tenantId, brandId }, "marketing_config", "main")
|
|
19233
|
+
).get();
|
|
19113
19234
|
const brand = configSnap.exists ? configSnap.data() : null;
|
|
19114
19235
|
let prompt = PARTE_1_IDENTIDAD.replace(
|
|
19115
19236
|
"{{brand_nombre}}",
|
|
@@ -20244,7 +20365,7 @@ IMPORTANT \u2014 'razon' field: the tenant sees this in the app. Write in friend
|
|
|
20244
20365
|
contract: photoDescarterContract,
|
|
20245
20366
|
helper: photoDescarter,
|
|
20246
20367
|
callable: callPhotoDescarter,
|
|
20247
|
-
input: { tenantId, fotoId, actor },
|
|
20368
|
+
input: { tenantId, brandId, fotoId, actor },
|
|
20248
20369
|
ctx
|
|
20249
20370
|
});
|
|
20250
20371
|
let payload;
|
|
@@ -20282,7 +20403,7 @@ IMPORTANT \u2014 'razon' field: the tenant sees this in the app. Write in friend
|
|
|
20282
20403
|
contract: photoEditadaMarkerContract,
|
|
20283
20404
|
helper: photoEditadaMarker,
|
|
20284
20405
|
callable: callPhotoEditadaMarker,
|
|
20285
|
-
input: { tenantId, fotoId, actor },
|
|
20406
|
+
input: { tenantId, brandId, fotoId, actor },
|
|
20286
20407
|
ctx
|
|
20287
20408
|
});
|
|
20288
20409
|
let payload;
|
|
@@ -21094,7 +21215,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
21094
21215
|
contract: contenidoApproverContract,
|
|
21095
21216
|
helper: contenidoApprover,
|
|
21096
21217
|
callable: callContenidoApprover,
|
|
21097
|
-
input: { tenantId, contenidoId, actor },
|
|
21218
|
+
input: { tenantId, brandId, contenidoId, actor },
|
|
21098
21219
|
ctx
|
|
21099
21220
|
});
|
|
21100
21221
|
let payload;
|
|
@@ -21135,7 +21256,7 @@ NUNCA uses update_calendar_slot para asignar fotos \u2014 eso escribe en la agen
|
|
|
21135
21256
|
contract: contenidoRejecterContract,
|
|
21136
21257
|
helper: contenidoRejecter,
|
|
21137
21258
|
callable: callContenidoRejecter,
|
|
21138
|
-
input: { tenantId, contenidoId, motivo, actor },
|
|
21259
|
+
input: { tenantId, brandId, contenidoId, motivo, actor },
|
|
21139
21260
|
ctx
|
|
21140
21261
|
});
|
|
21141
21262
|
let payload;
|