paperclip-plugin-autopilot-ops 0.1.41 → 0.1.43

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/manifest.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/manifest.ts
2
2
  var PLUGIN_ID = "alephoo.autopilot-ops";
3
- var PLUGIN_VERSION = "0.1.41";
3
+ var PLUGIN_VERSION = "0.1.43";
4
4
  var DB_NAMESPACE_SLUG = "autopilot_ops";
5
5
  var PAGE_ROUTE = "autopilot";
6
6
  var TOOL_NAMES = {
@@ -16,6 +16,7 @@ var TOOL_NAMES = {
16
16
  consultarIndicador: "ops_consultar_indicador",
17
17
  publicarTablero: "ops_publicar_tablero",
18
18
  listarTableros: "ops_listar_tableros",
19
+ proponerRetiroTablero: "ops_proponer_retiro_tablero",
19
20
  listarReglas: "ops_listar_reglas",
20
21
  informarPruebaRegla: "ops_informar_prueba_regla",
21
22
  evaluarRentabilidad: "ops_evaluar_rentabilidad"
@@ -271,6 +272,12 @@ var manifest = {
271
272
  displayName: "Listar tableros",
272
273
  description: "Lista los tableros publicados para esta instituci\xF3n (clave, t\xEDtulo, per\xEDodo, fuentes, cu\xE1ndo se actualiz\xF3 y cu\xE1ntos widgets tiene). Usar antes de publicar para reutilizar la clave de un tablero existente en vez de crear uno nuevo. Solo lectura.",
273
274
  parametersSchema: { type: "object", properties: {} }
275
+ },
276
+ {
277
+ name: TOOL_NAMES.proponerRetiroTablero,
278
+ displayName: "Proponer retirar un tablero",
279
+ description: "Cuando la direcci\xF3n pide dar de baja (eliminar, borrar, sacar) un tablero: propon\xE9 retirarlo con su clave y el id de la tarea. No lo retira: el portal le muestra la propuesta a la direcci\xF3n en ese pedido y lo retira una persona. No publiques nada en esa tarea.",
280
+ parametersSchema: { type: "object", properties: { clave: { type: "string" }, issueId: { type: "string" } }, required: ["clave", "issueId"] }
274
281
  }
275
282
  ],
276
283
  apiRoutes: [
@@ -402,6 +409,22 @@ var manifest = {
402
409
  capability: "api.routes.register",
403
410
  companyResolution: { from: "query", key: "companyId" }
404
411
  },
412
+ {
413
+ routeKey: "tableros-publicaciones",
414
+ method: "GET",
415
+ path: "/tableros-publicaciones",
416
+ auth: "board",
417
+ capability: "api.routes.register",
418
+ companyResolution: { from: "query", key: "companyId" }
419
+ },
420
+ {
421
+ routeKey: "descartar-retiro-tablero",
422
+ method: "POST",
423
+ path: "/tableros/:clave/no-retirar",
424
+ auth: "board",
425
+ capability: "api.routes.register",
426
+ companyResolution: { from: "query", key: "companyId" }
427
+ },
405
428
  {
406
429
  routeKey: "tableros-retirados",
407
430
  method: "GET",
package/dist/ui/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
 
9
9
  // src/manifest.ts
10
10
  var PLUGIN_ID = "alephoo.autopilot-ops";
11
- var PLUGIN_VERSION = "0.1.41";
11
+ var PLUGIN_VERSION = "0.1.43";
12
12
  var DB_NAMESPACE_SLUG = "autopilot_ops";
13
13
  var PAGE_ROUTE = "autopilot";
14
14
  var TOOL_NAMES = {
@@ -24,6 +24,7 @@ var TOOL_NAMES = {
24
24
  consultarIndicador: "ops_consultar_indicador",
25
25
  publicarTablero: "ops_publicar_tablero",
26
26
  listarTableros: "ops_listar_tableros",
27
+ proponerRetiroTablero: "ops_proponer_retiro_tablero",
27
28
  listarReglas: "ops_listar_reglas",
28
29
  informarPruebaRegla: "ops_informar_prueba_regla",
29
30
  evaluarRentabilidad: "ops_evaluar_rentabilidad"
@@ -279,6 +280,12 @@ var manifest = {
279
280
  displayName: "Listar tableros",
280
281
  description: "Lista los tableros publicados para esta instituci\xF3n (clave, t\xEDtulo, per\xEDodo, fuentes, cu\xE1ndo se actualiz\xF3 y cu\xE1ntos widgets tiene). Usar antes de publicar para reutilizar la clave de un tablero existente en vez de crear uno nuevo. Solo lectura.",
281
282
  parametersSchema: { type: "object", properties: {} }
283
+ },
284
+ {
285
+ name: TOOL_NAMES.proponerRetiroTablero,
286
+ displayName: "Proponer retirar un tablero",
287
+ description: "Cuando la direcci\xF3n pide dar de baja (eliminar, borrar, sacar) un tablero: propon\xE9 retirarlo con su clave y el id de la tarea. No lo retira: el portal le muestra la propuesta a la direcci\xF3n en ese pedido y lo retira una persona. No publiques nada en esa tarea.",
288
+ parametersSchema: { type: "object", properties: { clave: { type: "string" }, issueId: { type: "string" } }, required: ["clave", "issueId"] }
282
289
  }
283
290
  ],
284
291
  apiRoutes: [
@@ -410,6 +417,22 @@ var manifest = {
410
417
  capability: "api.routes.register",
411
418
  companyResolution: { from: "query", key: "companyId" }
412
419
  },
420
+ {
421
+ routeKey: "tableros-publicaciones",
422
+ method: "GET",
423
+ path: "/tableros-publicaciones",
424
+ auth: "board",
425
+ capability: "api.routes.register",
426
+ companyResolution: { from: "query", key: "companyId" }
427
+ },
428
+ {
429
+ routeKey: "descartar-retiro-tablero",
430
+ method: "POST",
431
+ path: "/tableros/:clave/no-retirar",
432
+ auth: "board",
433
+ capability: "api.routes.register",
434
+ companyResolution: { from: "query", key: "companyId" }
435
+ },
413
436
  {
414
437
  routeKey: "tableros-retirados",
415
438
  method: "GET",
package/dist/worker.js CHANGED
@@ -39932,7 +39932,7 @@ function startWorkerRpcHost(options) {
39932
39932
 
39933
39933
  // src/manifest.ts
39934
39934
  var PLUGIN_ID = "alephoo.autopilot-ops";
39935
- var PLUGIN_VERSION = "0.1.41";
39935
+ var PLUGIN_VERSION = "0.1.43";
39936
39936
  var DB_NAMESPACE_SLUG = "autopilot_ops";
39937
39937
  var PAGE_ROUTE = "autopilot";
39938
39938
  var TOOL_NAMES = {
@@ -39948,6 +39948,7 @@ var TOOL_NAMES = {
39948
39948
  consultarIndicador: "ops_consultar_indicador",
39949
39949
  publicarTablero: "ops_publicar_tablero",
39950
39950
  listarTableros: "ops_listar_tableros",
39951
+ proponerRetiroTablero: "ops_proponer_retiro_tablero",
39951
39952
  listarReglas: "ops_listar_reglas",
39952
39953
  informarPruebaRegla: "ops_informar_prueba_regla",
39953
39954
  evaluarRentabilidad: "ops_evaluar_rentabilidad"
@@ -40203,6 +40204,12 @@ var manifest = {
40203
40204
  displayName: "Listar tableros",
40204
40205
  description: "Lista los tableros publicados para esta instituci\xF3n (clave, t\xEDtulo, per\xEDodo, fuentes, cu\xE1ndo se actualiz\xF3 y cu\xE1ntos widgets tiene). Usar antes de publicar para reutilizar la clave de un tablero existente en vez de crear uno nuevo. Solo lectura.",
40205
40206
  parametersSchema: { type: "object", properties: {} }
40207
+ },
40208
+ {
40209
+ name: TOOL_NAMES.proponerRetiroTablero,
40210
+ displayName: "Proponer retirar un tablero",
40211
+ description: "Cuando la direcci\xF3n pide dar de baja (eliminar, borrar, sacar) un tablero: propon\xE9 retirarlo con su clave y el id de la tarea. No lo retira: el portal le muestra la propuesta a la direcci\xF3n en ese pedido y lo retira una persona. No publiques nada en esa tarea.",
40212
+ parametersSchema: { type: "object", properties: { clave: { type: "string" }, issueId: { type: "string" } }, required: ["clave", "issueId"] }
40206
40213
  }
40207
40214
  ],
40208
40215
  apiRoutes: [
@@ -40334,6 +40341,22 @@ var manifest = {
40334
40341
  capability: "api.routes.register",
40335
40342
  companyResolution: { from: "query", key: "companyId" }
40336
40343
  },
40344
+ {
40345
+ routeKey: "tableros-publicaciones",
40346
+ method: "GET",
40347
+ path: "/tableros-publicaciones",
40348
+ auth: "board",
40349
+ capability: "api.routes.register",
40350
+ companyResolution: { from: "query", key: "companyId" }
40351
+ },
40352
+ {
40353
+ routeKey: "descartar-retiro-tablero",
40354
+ method: "POST",
40355
+ path: "/tableros/:clave/no-retirar",
40356
+ auth: "board",
40357
+ capability: "api.routes.register",
40358
+ companyResolution: { from: "query", key: "companyId" }
40359
+ },
40337
40360
  {
40338
40361
  routeKey: "tableros-retirados",
40339
40362
  method: "GET",
@@ -46971,6 +46994,10 @@ function controlarTabla(ref, columnas, filas, problemas) {
46971
46994
  }
46972
46995
  }
46973
46996
  }
46997
+ function jergaEn(texto10) {
46998
+ const m = /\b[A-Z]{2,5}-\d+\b|\b[a-z]+_[a-z0-9_]+\b|\bv\d+\.\d+(\.\d+)?\b/.exec(texto10) ?? /\bAlephoo\b|\bcapa sem[aá]ntica\b|\brecetas? sem[aá]nticas?\b/i.exec(texto10);
46999
+ return m ? m[0] : null;
47000
+ }
46974
47001
  var MAX_BARRAS = 12;
46975
47002
  function validarWidgets(entrada) {
46976
47003
  const problemas = [];
@@ -47054,6 +47081,16 @@ function validarWidgets(entrada) {
47054
47081
  }
47055
47082
  }
47056
47083
  });
47084
+ for (const [k, w] of salida.entries()) {
47085
+ const textos = [w.tipo === "texto" ? w.texto : null, w.tipo === "texto" ? w.titulo : null, "titulo" in w ? w.titulo : null, w.tipo === "indicador" ? w.contexto : null, w.tipo === "indicador" ? w.etiqueta : null].filter((x) => typeof x === "string");
47086
+ for (const x of textos) {
47087
+ const jerga = jergaEn(x);
47088
+ if (jerga) problemas.push(`widgets[${k}]: \xAB${jerga}\xBB es un t\xE9rmino interno; la direcci\xF3n no lo entiende (us\xE1 \xABel HIS\xBB y nombres de negocio; el detalle t\xE9cnico va en fuentes o en el comentario de la tarea)`);
47089
+ }
47090
+ }
47091
+ const tonos = (t6) => salida.filter((w) => w.tipo === "indicador" && w.tono === t6).length;
47092
+ if (tonos("ambar") > 1) problemas.push("como m\xE1ximo un indicador en \xE1mbar por tablero: el que pide una acci\xF3n de la direcci\xF3n (los dem\xE1s, neutro)");
47093
+ if (tonos("corte") > 1) problemas.push("como m\xE1ximo un indicador en rojo (corte) por tablero: solo lo urgente");
47057
47094
  if (problemas.length) throw new OpsError("tablero_invalido", `el tablero no se puede publicar: ${problemas.slice(0, 12).join("; ")}${problemas.length > 12 ? ` (y ${problemas.length - 12} m\xE1s)` : ""}`, { problemas });
47058
47095
  return salida;
47059
47096
  }
@@ -47075,10 +47112,11 @@ function mapTablero(r) {
47075
47112
  versiones: Number(r.versiones ?? 1),
47076
47113
  createdAt: iso4(r.created_at),
47077
47114
  updatedAt: iso4(r.updated_at),
47078
- retirado: r.retirado_at ? { en: iso4(r.retirado_at), por: r.retirado_por ?? null, motivo: r.retirado_motivo ?? null } : null
47115
+ retirado: r.retirado_at ? { en: iso4(r.retirado_at), por: r.retirado_por ?? null, motivo: r.retirado_motivo ?? null } : null,
47116
+ retiroPropuesto: r.retiro_propuesto_issue ? { issueId: String(r.retiro_propuesto_issue), en: iso4(r.retiro_propuesto_at) } : null
47079
47117
  };
47080
47118
  }
47081
- var COLS2 = "id, company_id, clave, titulo, descripcion, pedido, periodo, fuentes, widgets, agente_id, issue_id, publicado_por_tipo, publicado_por_id, versiones, created_at, updated_at, retirado_at, retirado_por, retirado_motivo";
47119
+ var COLS2 = "id, company_id, clave, titulo, descripcion, pedido, periodo, fuentes, widgets, agente_id, issue_id, publicado_por_tipo, publicado_por_id, versiones, created_at, updated_at, retirado_at, retirado_por, retirado_motivo, retiro_propuesto_issue, retiro_propuesto_at";
47082
47120
  var CLAVE_RE2 = /^[a-z0-9][a-z0-9-]{1,60}$/;
47083
47121
  async function listarTableros(ctx, companyId) {
47084
47122
  const rows = await ctx.db.query(`SELECT ${COLS2} FROM ${t5(ctx, "tablero")} WHERE company_id = $1 AND retirado_at IS NULL ORDER BY updated_at DESC`, [companyId]);
@@ -47093,16 +47131,24 @@ async function publicarTablero(ctx, input2) {
47093
47131
  if (!CLAVE_RE2.test(clave)) throw new OpsError("clave_invalida", "clave debe ser un identificador estable en min\xFAsculas (letras, n\xFAmeros y guiones, 2 a 61 caracteres), p. ej. turnos-ausentismo");
47094
47132
  const titulo = typeof input2.titulo === "string" ? input2.titulo.trim().slice(0, 140) : "";
47095
47133
  if (!titulo) throw new OpsError("titulo_requerido", "titulo es obligatorio");
47134
+ for (const [campo, v] of [["titulo", titulo], ["descripcion", input2.descripcion], ["periodo", input2.periodo]]) {
47135
+ const jerga = typeof v === "string" ? jergaEn(v) : null;
47136
+ if (jerga) throw new OpsError("tablero_invalido", `${campo}: \xAB${jerga}\xBB es un t\xE9rmino interno; la direcci\xF3n no lo entiende (us\xE1 \xABel HIS\xBB y nombres de negocio)`);
47137
+ }
47096
47138
  const widgets = validarWidgets(input2.widgets);
47097
47139
  const fuentes = arr(input2.fuentes).filter((f) => typeof f === "string" && f.trim()).map((f) => String(f).trim().slice(0, 120)).slice(0, 12);
47098
47140
  const limpio = (v, max) => typeof v === "string" && v.trim() ? v.trim().slice(0, max) : null;
47099
47141
  const previo = await leerTablero(ctx, input2.companyId, clave);
47142
+ if (previo?.retirado && input2.actor.tipo !== "humano") throw new OpsError("tablero_retirado", "la direcci\xF3n retir\xF3 este tablero: no se vuelve a publicar con la misma clave; si lo quieren de nuevo, lo vuelve a mostrar una persona desde el portal");
47143
+ input2 = { ...input2, pedido: typeof input2.pedido === "string" ? input2.pedido.replace(/\s*\((pedido (de|por)|pedido el)[^)]*\)\s*[.,;]?\s*$/i, "").trim() : input2.pedido };
47100
47144
  const now = (/* @__PURE__ */ new Date()).toISOString();
47145
+ const registrar = async (version2) => ctx.db.execute(`INSERT INTO ${t5(ctx, "tablero_publicacion")} (id, company_id, clave, version, issue_id) VALUES ($1, $2, $3, $4, $5)`, [randomUUID5(), input2.companyId, clave, version2, input2.issueId ?? null]);
47101
47146
  if (previo) {
47102
47147
  await ctx.db.execute(
47103
47148
  `UPDATE ${t5(ctx, "tablero")} SET titulo = $3, descripcion = $4, pedido = COALESCE($5, pedido), periodo = $6, fuentes = $7::jsonb, widgets = $8::jsonb, agente_id = COALESCE($9, agente_id), issue_id = COALESCE($10, issue_id), publicado_por_tipo = $11, publicado_por_id = $12, versiones = versiones + 1, updated_at = now(), retirado_at = NULL, retirado_por = NULL, retirado_motivo = NULL WHERE company_id = $1 AND clave = $2`,
47104
47149
  [input2.companyId, clave, titulo, limpio(input2.descripcion, 600), limpio(input2.pedido, 1e3), limpio(input2.periodo, 80), JSON.stringify(fuentes), JSON.stringify(widgets), input2.agenteId ?? null, input2.issueId ?? null, input2.actor.tipo, input2.actor.id]
47105
47150
  );
47151
+ await registrar(previo.versiones + 1);
47106
47152
  await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: tablero \xAB${titulo}\xBB actualizado (${clave}, versi\xF3n ${previo.versiones + 1})`, metadata: { clave, widgets: widgets.length, agentId: input2.agenteId ?? null, issueId: input2.issueId ?? null } });
47107
47153
  return {
47108
47154
  creado: false,
@@ -47114,10 +47160,11 @@ async function publicarTablero(ctx, input2) {
47114
47160
  `INSERT INTO ${t5(ctx, "tablero")} (id, company_id, clave, titulo, descripcion, pedido, periodo, fuentes, widgets, agente_id, issue_id, publicado_por_tipo, publicado_por_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9::jsonb, $10, $11, $12, $13)`,
47115
47161
  [id3, input2.companyId, clave, titulo, limpio(input2.descripcion, 600), limpio(input2.pedido, 1e3), limpio(input2.periodo, 80), JSON.stringify(fuentes), JSON.stringify(widgets), input2.agenteId ?? null, input2.issueId ?? null, input2.actor.tipo, input2.actor.id]
47116
47162
  );
47163
+ await registrar(1);
47117
47164
  await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: tablero \xAB${titulo}\xBB publicado (${clave})`, metadata: { clave, widgets: widgets.length, agentId: input2.agenteId ?? null, issueId: input2.issueId ?? null } });
47118
47165
  return {
47119
47166
  creado: true,
47120
- tablero: { id: id3, companyId: input2.companyId, clave, titulo, descripcion: limpio(input2.descripcion, 600), pedido: limpio(input2.pedido, 1e3), periodo: limpio(input2.periodo, 80), fuentes, widgets, agenteId: input2.agenteId ?? null, issueId: input2.issueId ?? null, publicadoPorTipo: input2.actor.tipo, publicadoPorId: input2.actor.id, versiones: 1, createdAt: now, updatedAt: now, retirado: null }
47167
+ tablero: { id: id3, companyId: input2.companyId, clave, titulo, descripcion: limpio(input2.descripcion, 600), pedido: limpio(input2.pedido, 1e3), periodo: limpio(input2.periodo, 80), fuentes, widgets, agenteId: input2.agenteId ?? null, issueId: input2.issueId ?? null, publicadoPorTipo: input2.actor.tipo, publicadoPorId: input2.actor.id, versiones: 1, createdAt: now, updatedAt: now, retirado: null, retiroPropuesto: null }
47121
47168
  };
47122
47169
  }
47123
47170
  async function listarTablerosRetirados(ctx, companyId) {
@@ -47130,7 +47177,7 @@ async function restaurarTablero(ctx, input2) {
47130
47177
  const previo = CLAVE_RE2.test(clave) ? await leerTablero(ctx, input2.companyId, clave) : null;
47131
47178
  if (!previo) throw new OpsError("tablero_inexistente", "no hay un tablero con esa clave");
47132
47179
  if (!previo.retirado) return previo;
47133
- await ctx.db.execute(`UPDATE ${t5(ctx, "tablero")} SET retirado_at = NULL, retirado_por = NULL, retirado_motivo = NULL WHERE company_id = $1 AND clave = $2`, [input2.companyId, clave]);
47180
+ await ctx.db.execute(`UPDATE ${t5(ctx, "tablero")} SET retirado_at = NULL, retirado_por = NULL, retirado_motivo = NULL, retiro_propuesto_issue = NULL, retiro_propuesto_at = NULL WHERE company_id = $1 AND clave = $2`, [input2.companyId, clave]);
47134
47181
  await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: tablero \xAB${previo.titulo}\xBB vuelve a mostrarse (${clave})`, metadata: { clave, por: input2.actor.id } });
47135
47182
  return { ...previo, retirado: null };
47136
47183
  }
@@ -47141,9 +47188,32 @@ async function retirarTablero(ctx, input2) {
47141
47188
  if (!previo) throw new OpsError("tablero_inexistente", "no hay un tablero con esa clave");
47142
47189
  if (previo.retirado) return previo;
47143
47190
  const motivo = typeof input2.motivo === "string" && input2.motivo.trim() ? input2.motivo.trim().slice(0, 300) : null;
47144
- await ctx.db.execute(`UPDATE ${t5(ctx, "tablero")} SET retirado_at = now(), retirado_por = $3, retirado_motivo = $4 WHERE company_id = $1 AND clave = $2`, [input2.companyId, clave, input2.actor.id, motivo]);
47191
+ await ctx.db.execute(`UPDATE ${t5(ctx, "tablero")} SET retirado_at = now(), retirado_por = $3, retirado_motivo = $4, retiro_propuesto_issue = NULL, retiro_propuesto_at = NULL WHERE company_id = $1 AND clave = $2`, [input2.companyId, clave, input2.actor.id, motivo]);
47145
47192
  await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: tablero \xAB${previo.titulo}\xBB retirado (${clave})`, metadata: { clave, por: input2.actor.id, motivo } });
47146
- return { ...previo, retirado: { en: (/* @__PURE__ */ new Date()).toISOString(), por: input2.actor.id, motivo } };
47193
+ return { ...previo, retirado: { en: (/* @__PURE__ */ new Date()).toISOString(), por: input2.actor.id, motivo }, retiroPropuesto: null };
47194
+ }
47195
+ async function proponerRetiroTablero(ctx, input2) {
47196
+ const clave = typeof input2.clave === "string" ? input2.clave.trim().toLowerCase() : "";
47197
+ const previo = CLAVE_RE2.test(clave) ? await leerTablero(ctx, input2.companyId, clave) : null;
47198
+ if (!previo) throw new OpsError("tablero_inexistente", "no hay un tablero con esa clave (revis\xE1 ops_listar_tableros)");
47199
+ if (previo.retirado) throw new OpsError("tablero_retirado", "ese tablero ya est\xE1 retirado");
47200
+ if (!input2.issueId) throw new OpsError("issue_requerido", "issueId: la tarea en la que la direcci\xF3n pidi\xF3 la baja");
47201
+ await ctx.db.execute(`UPDATE ${t5(ctx, "tablero")} SET retiro_propuesto_issue = $3, retiro_propuesto_at = now() WHERE company_id = $1 AND clave = $2`, [input2.companyId, clave, input2.issueId]);
47202
+ await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: se propone retirar el tablero \xAB${previo.titulo}\xBB (${clave})`, metadata: { clave, issueId: input2.issueId } });
47203
+ return { ...previo, retiroPropuesto: { issueId: input2.issueId, en: (/* @__PURE__ */ new Date()).toISOString() } };
47204
+ }
47205
+ async function descartarRetiroTablero(ctx, input2) {
47206
+ if (input2.actor.tipo !== "humano") throw new OpsError("actor_no_permitido", "lo decide una persona de la instituci\xF3n");
47207
+ await ctx.db.execute(`UPDATE ${t5(ctx, "tablero")} SET retiro_propuesto_issue = NULL, retiro_propuesto_at = NULL WHERE company_id = $1 AND clave = $2`, [input2.companyId, String(input2.clave).toLowerCase()]);
47208
+ }
47209
+ async function listarPublicaciones(ctx, companyId) {
47210
+ const rows = await ctx.db.query(`SELECT clave, version, issue_id, publicado_at FROM ${t5(ctx, "tablero_publicacion")} WHERE company_id = $1 ORDER BY publicado_at DESC LIMIT 500`, [companyId]);
47211
+ const lista3 = rows.map((r) => ({ clave: String(r.clave), version: Number(r.version), issueId: r.issue_id ?? null, en: iso4(r.publicado_at) }));
47212
+ const tableros = await ctx.db.query(`SELECT clave, versiones, issue_id, updated_at FROM ${t5(ctx, "tablero")} WHERE company_id = $1 AND issue_id IS NOT NULL`, [companyId]);
47213
+ for (const tb of tableros) {
47214
+ if (!lista3.some((x) => x.issueId === tb.issue_id)) lista3.push({ clave: String(tb.clave), version: Number(tb.versiones ?? 1), issueId: String(tb.issue_id), en: iso4(tb.updated_at) });
47215
+ }
47216
+ return lista3;
47147
47217
  }
47148
47218
 
47149
47219
  // src/worker.ts
@@ -47352,6 +47422,11 @@ Transiciones registradas: ${detalle.transiciones.length}; acciones registradas:
47352
47422
  data: { tablero: { ...r.tablero, widgets: void 0 }, creado: r.creado }
47353
47423
  };
47354
47424
  }));
47425
+ ctx.tools.register(TOOL_NAMES.proponerRetiroTablero, toolSchema(TOOL_NAMES.proponerRetiroTablero), async (params, runCtx) => conErrores(async () => {
47426
+ const p = obj(params);
47427
+ const r = await proponerRetiroTablero(ctx, { companyId: runCtx.companyId, clave: str3(p.clave) ?? "", issueId: uuidOrNull(p.issueId), actor: actorAgente(runCtx) });
47428
+ return { content: `Propuesta registrada: la direcci\xF3n ve en el portal \xABretirar ${r.titulo}\xBB en ese pedido y decide. No publiques nada en esa tarea; dejala en revisi\xF3n con el p\xE1rrafo \xABPara la direcci\xF3n\xBB.`, data: { clave: r.clave } };
47429
+ }));
47355
47430
  ctx.tools.register(TOOL_NAMES.listarTableros, toolSchema(TOOL_NAMES.listarTableros), async (_params, runCtx) => conErrores(async () => {
47356
47431
  const tableros = await listarTableros(ctx, runCtx.companyId);
47357
47432
  const resumen = tableros.map((x) => ({ clave: x.clave, titulo: x.titulo, periodo: x.periodo, fuentes: x.fuentes, widgets: x.widgets.length, versiones: x.versiones, actualizadoEn: x.updatedAt, issueId: x.issueId }));
@@ -47479,6 +47554,13 @@ async function manejarApi(ctx, input2) {
47479
47554
  const tb = await leerTablero(ctx, companyId, (input2.params.clave ?? "").toLowerCase());
47480
47555
  return tb && !tb.retirado ? respuesta(200, tb) : respuesta(404, { error: "tablero_inexistente" });
47481
47556
  }
47557
+ case "tableros-publicaciones":
47558
+ return respuesta(200, { publicaciones: await listarPublicaciones(ctx, companyId) });
47559
+ case "descartar-retiro-tablero": {
47560
+ if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "lo decide una persona de la instituci\xF3n" });
47561
+ await descartarRetiroTablero(ctx, { companyId, clave: input2.params.clave ?? "", actor: { tipo: "humano", id: decididoPor ?? actor.id } });
47562
+ return respuesta(200, { ok: true });
47563
+ }
47482
47564
  case "tableros-retirados":
47483
47565
  return respuesta(200, { tableros: await listarTablerosRetirados(ctx, companyId) });
47484
47566
  case "restaurar-tablero": {
@@ -0,0 +1,15 @@
1
+ -- Cada publicación de un tablero, con la tarea (pedido) que la originó: así el portal sabe con certeza qué tablero salió
2
+ -- de qué pedido, sin deducirlo del texto.
3
+ CREATE TABLE plugin_autopilot_ops_1a68482ede.tablero_publicacion (
4
+ id uuid PRIMARY KEY,
5
+ company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
6
+ clave text NOT NULL,
7
+ version integer NOT NULL,
8
+ issue_id uuid,
9
+ publicado_at timestamptz NOT NULL DEFAULT now()
10
+ );
11
+ CREATE INDEX tablero_publicacion_empresa ON plugin_autopilot_ops_1a68482ede.tablero_publicacion (company_id, publicado_at DESC);
12
+
13
+ -- El agente propone retirar un tablero (cuando la dirección se lo pide); retirarlo lo decide una persona.
14
+ ALTER TABLE plugin_autopilot_ops_1a68482ede.tablero ADD COLUMN retiro_propuesto_issue uuid;
15
+ ALTER TABLE plugin_autopilot_ops_1a68482ede.tablero ADD COLUMN retiro_propuesto_at timestamptz;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-plugin-autopilot-ops",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "description": "Autopilot Care · operación autónoma gobernada para instituciones de salud: workflows determinísticos, validaciones, registro de negocio, políticas (observa/propone/ejecuta/corte) y tablero del director, como plugin de Paperclip.",
6
6
  "files": [