ponch-mcp-server 1.0.88 → 1.0.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -171,29 +171,36 @@ var Session = class {
171
171
  this.context.brandId = brandId;
172
172
  }
173
173
  /**
174
- * Cambia la brand activa SIN cambiar tenant. Disponible para cualquier
175
- * user con multi-brand (agency mode). DEUDA 3 sesión 211.1 H/I verifica
176
- * que brandId esté en la lista de brands accesibles del user (cargada
177
- * en connect_account).
174
+ * Cambia la brand activa SIN cambiar tenant. Disponible para:
175
+ * - User Modo B con multi-brand (brands.length > 1)agency mode.
176
+ * - Super_admin Modo A (canSwitchTenant=true) service account
177
+ * puede operar cualquier brand dentro del tenant activo seteado
178
+ * previamente vía set_context.
179
+ *
180
+ * DEUDA 3 sesión 211.1 H/I + fix J post-smoke 1.0.88.
178
181
  *
179
182
  * Lanza si:
180
- * - El user no tiene multi-brand (brands.length <= 1).
181
- * - El brandId solicitado NO está en la lista del user (defensa
182
- * extra contra cross-brand attack el helper también valida que
183
- * exista en Firestore antes de invocar esto).
183
+ * - El user no es super_admin Y no tiene multi-brand.
184
+ * - El brandId NO está en lista del user (defensa cross-brand Modo B).
185
+ * Para super_admin Modo A se delega validación al helper (Firestore
186
+ * existence check) no hay "lista accesible" porque tiene acceso
187
+ * total al tenant.
184
188
  */
185
189
  setBrand(brandId) {
186
190
  const brands = this._authContext.brands ?? [];
187
- if (brands.length <= 1) {
191
+ const isSuperAdmin = this._authContext.canSwitchTenant;
192
+ if (!isSuperAdmin && brands.length <= 1) {
188
193
  throw new Error(
189
- `select_brand requires multi-brand access. This user has access to ${brands.length} brand(s).`
194
+ `select_brand requires multi-brand access or super_admin. This user has access to ${brands.length} brand(s) and is not super_admin.`
190
195
  );
191
196
  }
192
- const allowed = brands.some((b) => b.id === brandId);
193
- if (!allowed) {
194
- throw new Error(
195
- `Brand "${brandId}" is not in the user's accessible brands list [${brands.map((b) => b.id).join(", ")}].`
196
- );
197
+ if (!isSuperAdmin) {
198
+ const allowed = brands.some((b) => b.id === brandId);
199
+ if (!allowed) {
200
+ throw new Error(
201
+ `Brand "${brandId}" is not in the user's accessible brands list [${brands.map((b) => b.id).join(", ")}].`
202
+ );
203
+ }
197
204
  }
198
205
  this.context.brandId = brandId;
199
206
  this._authContext.brandId = brandId;
@@ -1525,7 +1532,7 @@ var ORIGENES_CONTENIDO = [
1525
1532
  "imported"
1526
1533
  // sync Shopify/WordPress
1527
1534
  ];
1528
- var ESTADO_CONTENIDO2 = {
1535
+ var ESTADO_CONTENIDO = {
1529
1536
  BORRADOR: "borrador",
1530
1537
  GENERADO: "generado",
1531
1538
  PENDIENTE_APROBACION: "pendiente_aprobacion",
@@ -1723,7 +1730,7 @@ var ESTADOS_FOTO = [
1723
1730
  var ESTRATEGIAS_EDICION = ["gemini_edit", "tal_cual"];
1724
1731
  var FotoEstadoEnum = import_zod13.z.enum(ESTADOS_FOTO);
1725
1732
  var FotoEstrategiaEdicionEnum = import_zod13.z.enum(ESTRATEGIAS_EDICION);
1726
- var ESTADO_FOTO2 = {
1733
+ var ESTADO_FOTO = {
1727
1734
  NUEVA: "nueva",
1728
1735
  PROCESANDO: "procesando",
1729
1736
  EDITADA: "editada",
@@ -11869,7 +11876,7 @@ async function calendarSlotUpdater(input) {
11869
11876
  const contenidoQuery = await db.collection("tenants").doc(tenantId).collection("marketing_contenido").where("brandId", "==", brandId).where("calendarioItemRef", "==", oldContenidoRef).limit(10).get();
11870
11877
  contenidosADescartar = contenidoQuery.docs.filter((c) => {
11871
11878
  const data = c.data();
11872
- return data.estado !== "descartado" && data.estado !== ESTADO_CONTENIDO2.PUBLICADO;
11879
+ return data.estado !== "descartado" && data.estado !== ESTADO_CONTENIDO.PUBLICADO;
11873
11880
  });
11874
11881
  }
11875
11882
  if (accionContenidoExistente === "nuevo_slot") {
@@ -12784,7 +12791,7 @@ async function contenidoApprover(input) {
12784
12791
  }
12785
12792
  const data = snap.data();
12786
12793
  const estadoActual = typeof data.estado === "string" ? data.estado : "";
12787
- if (!esTransicionValida(estadoActual, ESTADO_CONTENIDO2.APROBADO)) {
12794
+ if (!esTransicionValida(estadoActual, ESTADO_CONTENIDO.APROBADO)) {
12788
12795
  return {
12789
12796
  ok: false,
12790
12797
  code: "INVALID_STATE_TRANSITION",
@@ -12792,7 +12799,7 @@ async function contenidoApprover(input) {
12792
12799
  };
12793
12800
  }
12794
12801
  await ref.update({
12795
- estado: ESTADO_CONTENIDO2.APROBADO,
12802
+ estado: ESTADO_CONTENIDO.APROBADO,
12796
12803
  aprobadoAt: import_firebase_admin8.firestore.FieldValue.serverTimestamp(),
12797
12804
  aprobadoPorId: actor.uid,
12798
12805
  aprobadoPorNombre: actor.nombre,
@@ -12803,7 +12810,7 @@ async function contenidoApprover(input) {
12803
12810
  ok: true,
12804
12811
  contenidoId,
12805
12812
  estadoAnterior: estadoActual,
12806
- nuevoEstado: ESTADO_CONTENIDO2.APROBADO
12813
+ nuevoEstado: ESTADO_CONTENIDO.APROBADO
12807
12814
  };
12808
12815
  }
12809
12816
  var ParamsSchema12 = import_zod47.z.object({
@@ -12873,7 +12880,7 @@ async function contenidoRejecter(input) {
12873
12880
  }
12874
12881
  const data = snap.data();
12875
12882
  const estadoActual = typeof data.estado === "string" ? data.estado : "";
12876
- if (!esTransicionValida(estadoActual, ESTADO_CONTENIDO2.RECHAZADO)) {
12883
+ if (!esTransicionValida(estadoActual, ESTADO_CONTENIDO.RECHAZADO)) {
12877
12884
  return {
12878
12885
  ok: false,
12879
12886
  code: "INVALID_STATE_TRANSITION",
@@ -12881,7 +12888,7 @@ async function contenidoRejecter(input) {
12881
12888
  };
12882
12889
  }
12883
12890
  await ref.update({
12884
- estado: ESTADO_CONTENIDO2.RECHAZADO,
12891
+ estado: ESTADO_CONTENIDO.RECHAZADO,
12885
12892
  rechazadoAt: import_firebase_admin9.firestore.FieldValue.serverTimestamp(),
12886
12893
  rechazadoMotivo: motivo,
12887
12894
  rechazadoPorId: actor.uid,
@@ -12893,7 +12900,7 @@ async function contenidoRejecter(input) {
12893
12900
  ok: true,
12894
12901
  contenidoId,
12895
12902
  estadoAnterior: estadoActual,
12896
- nuevoEstado: ESTADO_CONTENIDO2.RECHAZADO,
12903
+ nuevoEstado: ESTADO_CONTENIDO.RECHAZADO,
12897
12904
  motivo
12898
12905
  };
12899
12906
  }
@@ -13178,7 +13185,7 @@ async function photoDescarter(input) {
13178
13185
  }
13179
13186
  const data = snap.data();
13180
13187
  const estadoActual = typeof data.estado === "string" ? data.estado : "";
13181
- if (!validarTransicionFoto(estadoActual, ESTADO_FOTO2.DESCARTADA)) {
13188
+ if (!validarTransicionFoto(estadoActual, ESTADO_FOTO.DESCARTADA)) {
13182
13189
  return {
13183
13190
  ok: false,
13184
13191
  code: "INVALID_STATE_TRANSITION",
@@ -13186,7 +13193,7 @@ async function photoDescarter(input) {
13186
13193
  };
13187
13194
  }
13188
13195
  await ref.update({
13189
- estado: ESTADO_FOTO2.DESCARTADA,
13196
+ estado: ESTADO_FOTO.DESCARTADA,
13190
13197
  descartadaAt: import_firebase_admin11.firestore.FieldValue.serverTimestamp(),
13191
13198
  descartadaPorId: actor.uid,
13192
13199
  descartadaPorNombre: actor.nombre,
@@ -13197,7 +13204,7 @@ async function photoDescarter(input) {
13197
13204
  ok: true,
13198
13205
  fotoId,
13199
13206
  estadoAnterior: estadoActual,
13200
- nuevoEstado: ESTADO_FOTO2.DESCARTADA
13207
+ nuevoEstado: ESTADO_FOTO.DESCARTADA
13201
13208
  };
13202
13209
  }
13203
13210
  var ParamsSchema16 = import_zod51.z.object({
@@ -13268,7 +13275,7 @@ async function photoEditadaMarker(input) {
13268
13275
  const data = snap.data();
13269
13276
  const estadoActual = typeof data.estado === "string" ? data.estado : "";
13270
13277
  const archivoOriginal = typeof data.archivoOriginal === "string" ? data.archivoOriginal : null;
13271
- if (!validarTransicionFoto(estadoActual, ESTADO_FOTO2.EDITADA)) {
13278
+ if (!validarTransicionFoto(estadoActual, ESTADO_FOTO.EDITADA)) {
13272
13279
  return {
13273
13280
  ok: false,
13274
13281
  code: "INVALID_STATE_TRANSITION",
@@ -13279,7 +13286,7 @@ async function photoEditadaMarker(input) {
13279
13286
  return { ok: false, code: "FOTO_SIN_ARCHIVO_ORIGINAL", estadoActual };
13280
13287
  }
13281
13288
  await ref.update({
13282
- estado: ESTADO_FOTO2.EDITADA,
13289
+ estado: ESTADO_FOTO.EDITADA,
13283
13290
  archivoEditado: archivoOriginal,
13284
13291
  fechaEdicion: import_firebase_admin12.firestore.FieldValue.serverTimestamp(),
13285
13292
  marcadaEditadaPorId: actor.uid,
@@ -13291,7 +13298,7 @@ async function photoEditadaMarker(input) {
13291
13298
  ok: true,
13292
13299
  fotoId,
13293
13300
  estadoAnterior: estadoActual,
13294
- nuevoEstado: ESTADO_FOTO2.EDITADA,
13301
+ nuevoEstado: ESTADO_FOTO.EDITADA,
13295
13302
  archivoEditado: archivoOriginal
13296
13303
  };
13297
13304
  }
@@ -13444,7 +13451,7 @@ async function photoAssigner(input) {
13444
13451
  return { ok: false, error: `Foto ${fotoId} no encontrada`, code: "PHOTO_NOT_FOUND" };
13445
13452
  }
13446
13453
  const foto = fotoQuery.docs[0].data();
13447
- if (foto.estado !== ESTADO_FOTO2.EDITADA && foto.estado !== "usada") {
13454
+ if (foto.estado !== ESTADO_FOTO.EDITADA && foto.estado !== "usada") {
13448
13455
  return {
13449
13456
  ok: false,
13450
13457
  error: `Foto en estado "${foto.estado}", debe ser "editada"`,
@@ -13861,7 +13868,7 @@ async function marketingPlanBuilder(input) {
13861
13868
  }
13862
13869
  const productosQ = await db.collection("productos").where("tenantId", "==", tenantId).limit(100).get();
13863
13870
  const productos = productosQ.docs.map((d) => d.data());
13864
- const histQ = await db.collection("tenants").doc(tenantId).collection("marketing_contenido").where("brandId", "==", brandId).where("estado", "==", ESTADO_CONTENIDO2.PUBLICADO).limit(20).get();
13871
+ const histQ = await db.collection("tenants").doc(tenantId).collection("marketing_contenido").where("brandId", "==", brandId).where("estado", "==", ESTADO_CONTENIDO.PUBLICADO).limit(20).get();
13865
13872
  const historial = histQ.docs.map((d) => d.data());
13866
13873
  const lastImportId = await resolveLastImportId(db, tenantId, brandId);
13867
13874
  let colecciones = [];
@@ -14182,7 +14189,7 @@ async function contenidoWriter(input) {
14182
14189
  tipo: tipo ?? "post",
14183
14190
  keyword: keyword ?? null,
14184
14191
  languageCode,
14185
- estado: ESTADO_CONTENIDO2.PENDIENTE_APROBACION,
14192
+ estado: ESTADO_CONTENIDO.PENDIENTE_APROBACION,
14186
14193
  fotoId: fotoId ?? null,
14187
14194
  mediaUrl: null,
14188
14195
  calendarioItemRef: calendarioItemRef ?? null,
@@ -14308,7 +14315,7 @@ async function contenidoWriter(input) {
14308
14315
  return {
14309
14316
  ok: true,
14310
14317
  contenidoId: id,
14311
- estado: ESTADO_CONTENIDO2.PENDIENTE_APROBACION,
14318
+ estado: ESTADO_CONTENIDO.PENDIENTE_APROBACION,
14312
14319
  plataforma,
14313
14320
  descartados,
14314
14321
  pipelineLinked
@@ -14542,7 +14549,7 @@ async function weeklyContentBuilder(input) {
14542
14549
  }
14543
14550
  };
14544
14551
  }
14545
- const fotosQ = await db.collection("tenants").doc(tenantId).collection("marketing_fotos").where("brandId", "==", brandId).where("estado", "==", ESTADO_FOTO2.EDITADA).limit(20).get();
14552
+ const fotosQ = await db.collection("tenants").doc(tenantId).collection("marketing_fotos").where("brandId", "==", brandId).where("estado", "==", ESTADO_FOTO.EDITADA).limit(20).get();
14546
14553
  const fotosDisponibles = fotosQ.docs.map((d) => d.data());
14547
14554
  const histQ = await db.collection("tenants").doc(tenantId).collection("marketing_contenido").where("brandId", "==", brandId).limit(20).get();
14548
14555
  const historial = histQ.docs.map((d) => d.data());
@@ -14552,7 +14559,7 @@ async function weeklyContentBuilder(input) {
14552
14559
  const blogStrategy = brand.blogStrategy;
14553
14560
  const blogs = blogStrategy?.blogs ?? [];
14554
14561
  const idiomas = brand.idiomas;
14555
- const articulosQ = await db.collection("tenants").doc(tenantId).collection("marketing_contenido").where("brandId", "==", brandId).where("plataforma", "==", "shopify_blog").where("estado", "==", ESTADO_CONTENIDO2.PUBLICADO).limit(20).get();
14562
+ const articulosQ = await db.collection("tenants").doc(tenantId).collection("marketing_contenido").where("brandId", "==", brandId).where("plataforma", "==", "shopify_blog").where("estado", "==", ESTADO_CONTENIDO.PUBLICADO).limit(20).get();
14556
14563
  const articulosExistentes = articulosQ.docs.map(
14557
14564
  (d) => d.data()
14558
14565
  );
@@ -16108,8 +16115,8 @@ async function buildTenantContext(db, tenantId, brandId) {
16108
16115
  if (!configSnap.exists) return "";
16109
16116
  const brand = configSnap.data();
16110
16117
  const [fotosQ, contenidoQ, productosQ] = await Promise.all([
16111
- db.collection("tenants").doc(tenantId).collection("marketing_fotos").where("brandId", "==", brandId).where("estado", "==", ESTADO_FOTO2.EDITADA).limit(10).get(),
16112
- db.collection("tenants").doc(tenantId).collection("marketing_contenido").where("brandId", "==", brandId).where("estado", "==", ESTADO_CONTENIDO2.PUBLICADO).limit(10).get(),
16118
+ db.collection("tenants").doc(tenantId).collection("marketing_fotos").where("brandId", "==", brandId).where("estado", "==", ESTADO_FOTO.EDITADA).limit(10).get(),
16119
+ db.collection("tenants").doc(tenantId).collection("marketing_contenido").where("brandId", "==", brandId).where("estado", "==", ESTADO_CONTENIDO.PUBLICADO).limit(10).get(),
16113
16120
  db.collection("productos").where("tenantId", "==", tenantId).limit(50).get()
16114
16121
  ]);
16115
16122
  const fotosEditadas = fotosQ.docs.map((d) => d.data());
@@ -16576,7 +16583,9 @@ function registerContextTools(server, session) {
16576
16583
  }
16577
16584
  );
16578
16585
  }
16579
- if (session.brands && session.brands.length > 1) {
16586
+ const isMultiBrand = session.brands && session.brands.length > 1;
16587
+ const isSuperAdmin = session.canSwitchTenant;
16588
+ if (isMultiBrand || isSuperAdmin) {
16580
16589
  server.tool(
16581
16590
  "select_brand",
16582
16591
  "Switch the active brand within your tenant. Use when you have multi-brand access (agency mode) and want to work with a different brand without passing brandId on every tool call. Does NOT change tenant.",