paperclip-plugin-autopilot-ops 0.1.30 → 0.1.31
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 +57 -1
- package/dist/ui/index.js +57 -1
- package/dist/worker.js +347 -1
- package/migrations/007_escrituras_seguras.sql +16 -0
- package/package.json +1 -1
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.
|
|
3
|
+
var PLUGIN_VERSION = "0.1.31";
|
|
4
4
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
5
5
|
var PAGE_ROUTE = "autopilot";
|
|
6
6
|
var TOOL_NAMES = {
|
|
@@ -9,6 +9,10 @@ var TOOL_NAMES = {
|
|
|
9
9
|
abrirCaso: "ops_abrir_caso",
|
|
10
10
|
transicionar: "ops_transicionar",
|
|
11
11
|
registrarAccion: "ops_registrar_accion",
|
|
12
|
+
reservarEscritura: "ops_reservar_escritura",
|
|
13
|
+
confirmarEscritura: "ops_confirmar_escritura",
|
|
14
|
+
cerrarEscritura: "ops_cerrar_escritura",
|
|
15
|
+
verificarEscritura: "ops_verificar_escritura",
|
|
12
16
|
consultarIndicador: "ops_consultar_indicador",
|
|
13
17
|
publicarTablero: "ops_publicar_tablero",
|
|
14
18
|
listarTableros: "ops_listar_tableros",
|
|
@@ -161,6 +165,58 @@ var manifest = {
|
|
|
161
165
|
required: ["casoId", "tipo", "conector", "objetivo", "resultado"]
|
|
162
166
|
}
|
|
163
167
|
},
|
|
168
|
+
{
|
|
169
|
+
name: TOOL_NAMES.reservarEscritura,
|
|
170
|
+
displayName: "Reservar una escritura en el HIS",
|
|
171
|
+
description: "Paso 1 de toda escritura en el HIS: antes de tocarlo, dec\xED qu\xE9 vas a escribir (clave de negocio \xFAnica, p. ej. turno:2026-10-20T10:00|prof:15|sol:8812), sobre qu\xE9 registro (p. ej. solicitud:8812) y qu\xE9 le\xEDste de ese registro reci\xE9n (leido). El plugin la rechaza si el freno est\xE1 puesto, fuera de horario, si se pas\xF3 el tope del d\xEDa, si esa escritura ya se hizo o qued\xF3 dudosa, o si otro trabajo escribe el mismo registro. Devuelve el id de la reserva (vence en 15 minutos).",
|
|
172
|
+
parametersSchema: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {
|
|
175
|
+
casoId: { type: "string" },
|
|
176
|
+
clave: { type: "string", description: "Identifica la escritura de negocio: la misma clave no se escribe dos veces." },
|
|
177
|
+
registro: { type: "string", description: "El registro del HIS que se toca (tipo:id)." },
|
|
178
|
+
conector: { type: "string", enum: ["api", "pantalla", "archivo"] },
|
|
179
|
+
objetivo: { type: "string", description: "Qu\xE9 pantalla, endpoint o archivo." },
|
|
180
|
+
leido: { type: "object", description: "Lo que le\xEDste del registro ahora (campos y valores); se compara justo antes de guardar." }
|
|
181
|
+
},
|
|
182
|
+
required: ["casoId", "clave", "registro", "conector", "objetivo", "leido"]
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: TOOL_NAMES.confirmarEscritura,
|
|
187
|
+
displayName: "Confirmar antes de guardar",
|
|
188
|
+
description: "Paso 2 (solo si la funci\xF3n lo exige, sobre sistemas que no validan por s\xED mismos): justo antes de guardar, volv\xE9 a leer el registro y mand\xE1 lo le\xEDdo con los mismos campos. Si cambi\xF3 desde la reserva (lo toc\xF3 una persona), la escritura se descarta: no guardes y volv\xE9 a proponer.",
|
|
189
|
+
parametersSchema: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: { accionId: { type: "string" }, leido: { type: "object" } },
|
|
192
|
+
required: ["accionId", "leido"]
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: TOOL_NAMES.cerrarEscritura,
|
|
197
|
+
displayName: "Cerrar una escritura",
|
|
198
|
+
description: "Paso 3: c\xF3mo sali\xF3 la escritura. ok (qued\xF3 escrita; si la funci\xF3n exige confirmar, solo despu\xE9s de confirmarla), error (no se escribi\xF3 nada) o incierta (no sab\xE9s si qued\xF3 escrita: no reintentes; verificala con ops_verificar_escritura). Con 3 fallas seguidas la funci\xF3n se frena sola.",
|
|
199
|
+
parametersSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
accionId: { type: "string" },
|
|
203
|
+
resultado: { type: "string", enum: ["ok", "error", "incierta"] },
|
|
204
|
+
evidencia: { type: "object" },
|
|
205
|
+
detalle: { type: "string" }
|
|
206
|
+
},
|
|
207
|
+
required: ["accionId", "resultado"]
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: TOOL_NAMES.verificarEscritura,
|
|
212
|
+
displayName: "Verificar una escritura dudosa",
|
|
213
|
+
description: "Para una escritura que qued\xF3 incierta: rele\xE9 el HIS y dec\xED si qued\xF3 escrita (quedo true: se da por hecha y segu\xEDs), si no qued\xF3 (quedo false: se puede reintentar) o si no lo pod\xE9s saber (quedo null: despu\xE9s de 2 intentos o una hora pasa a una persona). Mand\xE1 en leido lo que rele\xEDste.",
|
|
214
|
+
parametersSchema: {
|
|
215
|
+
type: "object",
|
|
216
|
+
properties: { accionId: { type: "string" }, quedo: { type: ["boolean", "null"] }, leido: { type: "object" }, detalle: { type: "string" } },
|
|
217
|
+
required: ["accionId", "quedo", "leido"]
|
|
218
|
+
}
|
|
219
|
+
},
|
|
164
220
|
{
|
|
165
221
|
name: TOOL_NAMES.consultarIndicador,
|
|
166
222
|
displayName: "Consultar indicador",
|
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.
|
|
11
|
+
var PLUGIN_VERSION = "0.1.31";
|
|
12
12
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
13
13
|
var PAGE_ROUTE = "autopilot";
|
|
14
14
|
var TOOL_NAMES = {
|
|
@@ -17,6 +17,10 @@ var TOOL_NAMES = {
|
|
|
17
17
|
abrirCaso: "ops_abrir_caso",
|
|
18
18
|
transicionar: "ops_transicionar",
|
|
19
19
|
registrarAccion: "ops_registrar_accion",
|
|
20
|
+
reservarEscritura: "ops_reservar_escritura",
|
|
21
|
+
confirmarEscritura: "ops_confirmar_escritura",
|
|
22
|
+
cerrarEscritura: "ops_cerrar_escritura",
|
|
23
|
+
verificarEscritura: "ops_verificar_escritura",
|
|
20
24
|
consultarIndicador: "ops_consultar_indicador",
|
|
21
25
|
publicarTablero: "ops_publicar_tablero",
|
|
22
26
|
listarTableros: "ops_listar_tableros",
|
|
@@ -169,6 +173,58 @@ var manifest = {
|
|
|
169
173
|
required: ["casoId", "tipo", "conector", "objetivo", "resultado"]
|
|
170
174
|
}
|
|
171
175
|
},
|
|
176
|
+
{
|
|
177
|
+
name: TOOL_NAMES.reservarEscritura,
|
|
178
|
+
displayName: "Reservar una escritura en el HIS",
|
|
179
|
+
description: "Paso 1 de toda escritura en el HIS: antes de tocarlo, dec\xED qu\xE9 vas a escribir (clave de negocio \xFAnica, p. ej. turno:2026-10-20T10:00|prof:15|sol:8812), sobre qu\xE9 registro (p. ej. solicitud:8812) y qu\xE9 le\xEDste de ese registro reci\xE9n (leido). El plugin la rechaza si el freno est\xE1 puesto, fuera de horario, si se pas\xF3 el tope del d\xEDa, si esa escritura ya se hizo o qued\xF3 dudosa, o si otro trabajo escribe el mismo registro. Devuelve el id de la reserva (vence en 15 minutos).",
|
|
180
|
+
parametersSchema: {
|
|
181
|
+
type: "object",
|
|
182
|
+
properties: {
|
|
183
|
+
casoId: { type: "string" },
|
|
184
|
+
clave: { type: "string", description: "Identifica la escritura de negocio: la misma clave no se escribe dos veces." },
|
|
185
|
+
registro: { type: "string", description: "El registro del HIS que se toca (tipo:id)." },
|
|
186
|
+
conector: { type: "string", enum: ["api", "pantalla", "archivo"] },
|
|
187
|
+
objetivo: { type: "string", description: "Qu\xE9 pantalla, endpoint o archivo." },
|
|
188
|
+
leido: { type: "object", description: "Lo que le\xEDste del registro ahora (campos y valores); se compara justo antes de guardar." }
|
|
189
|
+
},
|
|
190
|
+
required: ["casoId", "clave", "registro", "conector", "objetivo", "leido"]
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: TOOL_NAMES.confirmarEscritura,
|
|
195
|
+
displayName: "Confirmar antes de guardar",
|
|
196
|
+
description: "Paso 2 (solo si la funci\xF3n lo exige, sobre sistemas que no validan por s\xED mismos): justo antes de guardar, volv\xE9 a leer el registro y mand\xE1 lo le\xEDdo con los mismos campos. Si cambi\xF3 desde la reserva (lo toc\xF3 una persona), la escritura se descarta: no guardes y volv\xE9 a proponer.",
|
|
197
|
+
parametersSchema: {
|
|
198
|
+
type: "object",
|
|
199
|
+
properties: { accionId: { type: "string" }, leido: { type: "object" } },
|
|
200
|
+
required: ["accionId", "leido"]
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: TOOL_NAMES.cerrarEscritura,
|
|
205
|
+
displayName: "Cerrar una escritura",
|
|
206
|
+
description: "Paso 3: c\xF3mo sali\xF3 la escritura. ok (qued\xF3 escrita; si la funci\xF3n exige confirmar, solo despu\xE9s de confirmarla), error (no se escribi\xF3 nada) o incierta (no sab\xE9s si qued\xF3 escrita: no reintentes; verificala con ops_verificar_escritura). Con 3 fallas seguidas la funci\xF3n se frena sola.",
|
|
207
|
+
parametersSchema: {
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: {
|
|
210
|
+
accionId: { type: "string" },
|
|
211
|
+
resultado: { type: "string", enum: ["ok", "error", "incierta"] },
|
|
212
|
+
evidencia: { type: "object" },
|
|
213
|
+
detalle: { type: "string" }
|
|
214
|
+
},
|
|
215
|
+
required: ["accionId", "resultado"]
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: TOOL_NAMES.verificarEscritura,
|
|
220
|
+
displayName: "Verificar una escritura dudosa",
|
|
221
|
+
description: "Para una escritura que qued\xF3 incierta: rele\xE9 el HIS y dec\xED si qued\xF3 escrita (quedo true: se da por hecha y segu\xEDs), si no qued\xF3 (quedo false: se puede reintentar) o si no lo pod\xE9s saber (quedo null: despu\xE9s de 2 intentos o una hora pasa a una persona). Mand\xE1 en leido lo que rele\xEDste.",
|
|
222
|
+
parametersSchema: {
|
|
223
|
+
type: "object",
|
|
224
|
+
properties: { accionId: { type: "string" }, quedo: { type: ["boolean", "null"] }, leido: { type: "object" }, detalle: { type: "string" } },
|
|
225
|
+
required: ["accionId", "quedo", "leido"]
|
|
226
|
+
}
|
|
227
|
+
},
|
|
172
228
|
{
|
|
173
229
|
name: TOOL_NAMES.consultarIndicador,
|
|
174
230
|
displayName: "Consultar indicador",
|
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.
|
|
39935
|
+
var PLUGIN_VERSION = "0.1.31";
|
|
39936
39936
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
39937
39937
|
var PAGE_ROUTE = "autopilot";
|
|
39938
39938
|
var TOOL_NAMES = {
|
|
@@ -39941,6 +39941,10 @@ var TOOL_NAMES = {
|
|
|
39941
39941
|
abrirCaso: "ops_abrir_caso",
|
|
39942
39942
|
transicionar: "ops_transicionar",
|
|
39943
39943
|
registrarAccion: "ops_registrar_accion",
|
|
39944
|
+
reservarEscritura: "ops_reservar_escritura",
|
|
39945
|
+
confirmarEscritura: "ops_confirmar_escritura",
|
|
39946
|
+
cerrarEscritura: "ops_cerrar_escritura",
|
|
39947
|
+
verificarEscritura: "ops_verificar_escritura",
|
|
39944
39948
|
consultarIndicador: "ops_consultar_indicador",
|
|
39945
39949
|
publicarTablero: "ops_publicar_tablero",
|
|
39946
39950
|
listarTableros: "ops_listar_tableros",
|
|
@@ -40093,6 +40097,58 @@ var manifest = {
|
|
|
40093
40097
|
required: ["casoId", "tipo", "conector", "objetivo", "resultado"]
|
|
40094
40098
|
}
|
|
40095
40099
|
},
|
|
40100
|
+
{
|
|
40101
|
+
name: TOOL_NAMES.reservarEscritura,
|
|
40102
|
+
displayName: "Reservar una escritura en el HIS",
|
|
40103
|
+
description: "Paso 1 de toda escritura en el HIS: antes de tocarlo, dec\xED qu\xE9 vas a escribir (clave de negocio \xFAnica, p. ej. turno:2026-10-20T10:00|prof:15|sol:8812), sobre qu\xE9 registro (p. ej. solicitud:8812) y qu\xE9 le\xEDste de ese registro reci\xE9n (leido). El plugin la rechaza si el freno est\xE1 puesto, fuera de horario, si se pas\xF3 el tope del d\xEDa, si esa escritura ya se hizo o qued\xF3 dudosa, o si otro trabajo escribe el mismo registro. Devuelve el id de la reserva (vence en 15 minutos).",
|
|
40104
|
+
parametersSchema: {
|
|
40105
|
+
type: "object",
|
|
40106
|
+
properties: {
|
|
40107
|
+
casoId: { type: "string" },
|
|
40108
|
+
clave: { type: "string", description: "Identifica la escritura de negocio: la misma clave no se escribe dos veces." },
|
|
40109
|
+
registro: { type: "string", description: "El registro del HIS que se toca (tipo:id)." },
|
|
40110
|
+
conector: { type: "string", enum: ["api", "pantalla", "archivo"] },
|
|
40111
|
+
objetivo: { type: "string", description: "Qu\xE9 pantalla, endpoint o archivo." },
|
|
40112
|
+
leido: { type: "object", description: "Lo que le\xEDste del registro ahora (campos y valores); se compara justo antes de guardar." }
|
|
40113
|
+
},
|
|
40114
|
+
required: ["casoId", "clave", "registro", "conector", "objetivo", "leido"]
|
|
40115
|
+
}
|
|
40116
|
+
},
|
|
40117
|
+
{
|
|
40118
|
+
name: TOOL_NAMES.confirmarEscritura,
|
|
40119
|
+
displayName: "Confirmar antes de guardar",
|
|
40120
|
+
description: "Paso 2 (solo si la funci\xF3n lo exige, sobre sistemas que no validan por s\xED mismos): justo antes de guardar, volv\xE9 a leer el registro y mand\xE1 lo le\xEDdo con los mismos campos. Si cambi\xF3 desde la reserva (lo toc\xF3 una persona), la escritura se descarta: no guardes y volv\xE9 a proponer.",
|
|
40121
|
+
parametersSchema: {
|
|
40122
|
+
type: "object",
|
|
40123
|
+
properties: { accionId: { type: "string" }, leido: { type: "object" } },
|
|
40124
|
+
required: ["accionId", "leido"]
|
|
40125
|
+
}
|
|
40126
|
+
},
|
|
40127
|
+
{
|
|
40128
|
+
name: TOOL_NAMES.cerrarEscritura,
|
|
40129
|
+
displayName: "Cerrar una escritura",
|
|
40130
|
+
description: "Paso 3: c\xF3mo sali\xF3 la escritura. ok (qued\xF3 escrita; si la funci\xF3n exige confirmar, solo despu\xE9s de confirmarla), error (no se escribi\xF3 nada) o incierta (no sab\xE9s si qued\xF3 escrita: no reintentes; verificala con ops_verificar_escritura). Con 3 fallas seguidas la funci\xF3n se frena sola.",
|
|
40131
|
+
parametersSchema: {
|
|
40132
|
+
type: "object",
|
|
40133
|
+
properties: {
|
|
40134
|
+
accionId: { type: "string" },
|
|
40135
|
+
resultado: { type: "string", enum: ["ok", "error", "incierta"] },
|
|
40136
|
+
evidencia: { type: "object" },
|
|
40137
|
+
detalle: { type: "string" }
|
|
40138
|
+
},
|
|
40139
|
+
required: ["accionId", "resultado"]
|
|
40140
|
+
}
|
|
40141
|
+
},
|
|
40142
|
+
{
|
|
40143
|
+
name: TOOL_NAMES.verificarEscritura,
|
|
40144
|
+
displayName: "Verificar una escritura dudosa",
|
|
40145
|
+
description: "Para una escritura que qued\xF3 incierta: rele\xE9 el HIS y dec\xED si qued\xF3 escrita (quedo true: se da por hecha y segu\xEDs), si no qued\xF3 (quedo false: se puede reintentar) o si no lo pod\xE9s saber (quedo null: despu\xE9s de 2 intentos o una hora pasa a una persona). Mand\xE1 en leido lo que rele\xEDste.",
|
|
40146
|
+
parametersSchema: {
|
|
40147
|
+
type: "object",
|
|
40148
|
+
properties: { accionId: { type: "string" }, quedo: { type: ["boolean", "null"] }, leido: { type: "object" }, detalle: { type: "string" } },
|
|
40149
|
+
required: ["accionId", "quedo", "leido"]
|
|
40150
|
+
}
|
|
40151
|
+
},
|
|
40096
40152
|
{
|
|
40097
40153
|
name: TOOL_NAMES.consultarIndicador,
|
|
40098
40154
|
displayName: "Consultar indicador",
|
|
@@ -40391,6 +40447,60 @@ var manifest_default = manifest;
|
|
|
40391
40447
|
// src/ops.ts
|
|
40392
40448
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
40393
40449
|
|
|
40450
|
+
// src/escrituras.ts
|
|
40451
|
+
var MINUTOS_RESERVA = 15;
|
|
40452
|
+
var FALLAS_PARA_FRENAR = 3;
|
|
40453
|
+
var INTENTOS_VERIFICACION = 2;
|
|
40454
|
+
var MINUTOS_DUDOSA_PARA_PERSONA = 60;
|
|
40455
|
+
function dudosaParaPersona(intentosSinSaber, minutosDudosa) {
|
|
40456
|
+
return intentosSinSaber >= INTENTOS_VERIFICACION || minutosDudosa >= MINUTOS_DUDOSA_PARA_PERSONA;
|
|
40457
|
+
}
|
|
40458
|
+
var RESULTADOS_CIERRE = ["ok", "error", "incierta"];
|
|
40459
|
+
var HORA = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
40460
|
+
function leerVentana(v) {
|
|
40461
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return null;
|
|
40462
|
+
const x = v;
|
|
40463
|
+
const desde = String(x.desde ?? "").trim();
|
|
40464
|
+
const hasta = String(x.hasta ?? "").trim();
|
|
40465
|
+
if (!HORA.test(desde) || !HORA.test(hasta) || desde >= hasta) return null;
|
|
40466
|
+
const dias = Array.isArray(x.dias) ? [...new Set(x.dias.map(Number).filter((d) => Number.isInteger(d) && d >= 1 && d <= 7))].sort() : null;
|
|
40467
|
+
return { desde, hasta, dias: dias && dias.length ? dias : null };
|
|
40468
|
+
}
|
|
40469
|
+
function leerTopeDiario(v) {
|
|
40470
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
40471
|
+
return Number.isInteger(n) && n >= 1 && n <= 1e4 ? n : null;
|
|
40472
|
+
}
|
|
40473
|
+
function evaluarReserva(h) {
|
|
40474
|
+
if (h.freno) return { ok: false, codigo: "freno", motivo: `el freno de la instituci\xF3n est\xE1 puesto (${h.freno}): ning\xFAn puesto escribe en el HIS` };
|
|
40475
|
+
if (h.ventana) {
|
|
40476
|
+
if (h.ventana.dias && !h.ventana.dias.includes(h.diaSemana)) return { ok: false, codigo: "fuera_de_horario", motivo: "hoy no es un d\xEDa en que la funci\xF3n pueda escribir en el HIS" };
|
|
40477
|
+
if (h.hora < h.ventana.desde || h.hora >= h.ventana.hasta) return { ok: false, codigo: "fuera_de_horario", motivo: `la funci\xF3n escribe en el HIS de ${h.ventana.desde} a ${h.ventana.hasta}` };
|
|
40478
|
+
}
|
|
40479
|
+
if (h.tope !== null && h.escriturasHoy >= h.tope) return { ok: false, codigo: "tope_diario", motivo: `se lleg\xF3 al tope de ${h.tope} escrituras por d\xEDa de la funci\xF3n` };
|
|
40480
|
+
const m = h.mismaClave;
|
|
40481
|
+
if (m) {
|
|
40482
|
+
if (m.resultado === "ok") return { ok: false, codigo: "ya_escrita", motivo: "esa escritura ya se hizo: no se repite", accionId: m.id };
|
|
40483
|
+
if (m.resultado === "pendiente" && !m.vencida) return { ok: false, codigo: "en_curso", motivo: "esa escritura ya est\xE1 en curso", accionId: m.id };
|
|
40484
|
+
if (m.resultado === "incierta" || m.resultado === "pendiente" && m.vencida) {
|
|
40485
|
+
return { ok: false, codigo: "incierta", motivo: "una escritura anterior con esa clave qued\xF3 dudosa: verificala releyendo el HIS (ops_verificar_escritura) antes de reintentar", accionId: m.id };
|
|
40486
|
+
}
|
|
40487
|
+
}
|
|
40488
|
+
if (h.ocupado) return { ok: false, codigo: "registro_ocupado", motivo: "otro trabajo est\xE1 escribiendo ese mismo registro; esperar a que termine", accionId: h.ocupado.id };
|
|
40489
|
+
return { ok: true };
|
|
40490
|
+
}
|
|
40491
|
+
var normal = (v) => v === null || v === void 0 ? "" : typeof v === "object" ? JSON.stringify(v) : String(v).trim().toLowerCase();
|
|
40492
|
+
function compararLecturas(antes, ahora) {
|
|
40493
|
+
return Object.keys(antes).filter((k) => normal(antes[k]) !== normal(ahora[k])).sort();
|
|
40494
|
+
}
|
|
40495
|
+
function debeFrenar(resultados, fallas = FALLAS_PARA_FRENAR) {
|
|
40496
|
+
if (resultados.length < fallas) return false;
|
|
40497
|
+
return resultados.slice(0, fallas).every((r) => r === "error" || r === "incierta");
|
|
40498
|
+
}
|
|
40499
|
+
function diaDeLaSemana(fecha5) {
|
|
40500
|
+
const d = (/* @__PURE__ */ new Date(`${fecha5}T12:00:00Z`)).getUTCDay();
|
|
40501
|
+
return d === 0 ? 7 : d;
|
|
40502
|
+
}
|
|
40503
|
+
|
|
40394
40504
|
// src/demanda.ts
|
|
40395
40505
|
var txt = (v) => typeof v === "string" && v.trim() ? v.trim() : typeof v === "number" ? String(v) : null;
|
|
40396
40506
|
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : typeof v === "string" && /^-?\d+(\.\d+)?$/.test(v.trim()) ? Number(v) : null;
|
|
@@ -44272,6 +44382,9 @@ async function registrarAccion(ctx, input2) {
|
|
|
44272
44382
|
const modo = await obtenerModo(ctx, input2.companyId, caso.workflow);
|
|
44273
44383
|
if (!modoHabilita(modo, "ejecuta")) throw new OpsError("politica", `la pol\xEDtica vigente de ${caso.workflow} es "${modo}": un agente no puede registrar escrituras sobre el HIS`);
|
|
44274
44384
|
}
|
|
44385
|
+
if (input2.tipo === "escritura" && input2.actor.tipo === "agente") {
|
|
44386
|
+
throw new OpsError("usar_reserva", "un puesto escribe en el HIS en tres pasos: ops_reservar_escritura, ops_confirmar_escritura (releyendo justo antes de guardar) y ops_cerrar_escritura");
|
|
44387
|
+
}
|
|
44275
44388
|
const id3 = randomUUID2();
|
|
44276
44389
|
const evidencia = { ...input2.evidencia ?? {} };
|
|
44277
44390
|
delete evidencia.fuera_de_flujo;
|
|
@@ -44300,6 +44413,217 @@ async function registrarAccion(ctx, input2) {
|
|
|
44300
44413
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
44301
44414
|
};
|
|
44302
44415
|
}
|
|
44416
|
+
var FRENO_INSTITUCION = "*";
|
|
44417
|
+
async function leerFreno(ctx, companyId) {
|
|
44418
|
+
const rows = await ctx.db.query(
|
|
44419
|
+
`SELECT modo, motivo FROM ${t2(ctx, "politica")} WHERE company_id = $1 AND workflow = $2 LIMIT 1`,
|
|
44420
|
+
[companyId, FRENO_INSTITUCION]
|
|
44421
|
+
);
|
|
44422
|
+
return rows[0] && String(rows[0].modo) === "corte" ? str2(rows[0].motivo) ?? "freno puesto" : null;
|
|
44423
|
+
}
|
|
44424
|
+
async function guardarModo(ctx, companyId, workflow, modo, motivo, actor, anterior) {
|
|
44425
|
+
const existe = await ctx.db.query(`SELECT id FROM ${t2(ctx, "politica")} WHERE company_id = $1 AND workflow = $2 LIMIT 1`, [companyId, workflow]);
|
|
44426
|
+
if (existe.length > 0) {
|
|
44427
|
+
await ctx.db.execute(
|
|
44428
|
+
`UPDATE ${t2(ctx, "politica")} SET modo = $3, motivo = $4, actor_tipo = $5, actor_id = $6, updated_at = now() WHERE company_id = $1 AND workflow = $2`,
|
|
44429
|
+
[companyId, workflow, modo, motivo, actor.tipo, actor.id]
|
|
44430
|
+
);
|
|
44431
|
+
} else {
|
|
44432
|
+
await ctx.db.execute(
|
|
44433
|
+
`INSERT INTO ${t2(ctx, "politica")} (id, company_id, workflow, modo, motivo, actor_tipo, actor_id, opciones) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb)`,
|
|
44434
|
+
[randomUUID2(), companyId, workflow, modo, motivo, actor.tipo, actor.id, "{}"]
|
|
44435
|
+
);
|
|
44436
|
+
}
|
|
44437
|
+
await ctx.db.execute(
|
|
44438
|
+
`INSERT INTO ${t2(ctx, "politica_historial")} (id, company_id, workflow, modo_anterior, modo_nuevo, motivo, actor_tipo, actor_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
44439
|
+
[randomUUID2(), companyId, workflow, anterior, modo, motivo, actor.tipo, actor.id]
|
|
44440
|
+
);
|
|
44441
|
+
}
|
|
44442
|
+
async function frenarFuncion(ctx, companyId, workflow) {
|
|
44443
|
+
const anterior = await obtenerModo(ctx, companyId, workflow);
|
|
44444
|
+
if (anterior === "corte") return;
|
|
44445
|
+
const motivo = `Freno autom\xE1tico: ${FALLAS_PARA_FRENAR} escrituras seguidas en el HIS fallaron o quedaron sin confirmar. Revisar y volver a habilitar.`;
|
|
44446
|
+
await guardarModo(ctx, companyId, workflow, "corte", motivo, { tipo: "sistema", id: null }, anterior);
|
|
44447
|
+
await ctx.activity.log({ companyId, message: `Autopilot Care: ${workflow} frenada autom\xE1ticamente (${FALLAS_PARA_FRENAR} escrituras fallidas seguidas)`, metadata: { workflow, freno: "automatico" } });
|
|
44448
|
+
}
|
|
44449
|
+
async function marcarDudosa(ctx, companyId, accion, detalle) {
|
|
44450
|
+
await ctx.db.execute(
|
|
44451
|
+
`UPDATE ${t2(ctx, "accion")} SET resultado = 'incierta', detalle = $3, cerrada_at = now() WHERE company_id = $1 AND id = $2`,
|
|
44452
|
+
[companyId, String(accion.id), detalle]
|
|
44453
|
+
);
|
|
44454
|
+
await ctx.db.execute(
|
|
44455
|
+
`UPDATE ${t2(ctx, "caso")} SET datos = datos || $3::jsonb, updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
44456
|
+
[companyId, String(accion.caso_id), JSON.stringify({ escritura_incierta: { accion_id: String(accion.id), objetivo: str2(accion.objetivo), detalle, desde: (/* @__PURE__ */ new Date()).toISOString(), intentos_verificacion: 0, para_persona: false } })]
|
|
44457
|
+
);
|
|
44458
|
+
await ctx.activity.log({ companyId, message: `Autopilot Care: escritura dudosa en el HIS (${str2(accion.objetivo) ?? "sin objetivo"}): ${detalle}`, metadata: { accionId: String(accion.id), casoId: String(accion.caso_id) } });
|
|
44459
|
+
}
|
|
44460
|
+
async function verificarEscritura(ctx, input2) {
|
|
44461
|
+
const fila = await leerAccionEscritura(ctx, input2.companyId, input2.accionId);
|
|
44462
|
+
if (String(fila.resultado) !== "incierta") throw new OpsError("no_dudosa", `la escritura ${input2.accionId} no est\xE1 dudosa (${String(fila.resultado)})`);
|
|
44463
|
+
const leido = input2.leido && typeof input2.leido === "object" && !Array.isArray(input2.leido) ? input2.leido : {};
|
|
44464
|
+
if (Object.keys(leido).length === 0) throw new OpsError("validacion", "leido: lo que se reley\xF3 del HIS para verificar (al menos un campo)");
|
|
44465
|
+
const evidencia = jsonb2(fila.evidencia);
|
|
44466
|
+
const caso = await leerCaso(ctx, input2.companyId, String(fila.caso_id));
|
|
44467
|
+
const dudosa = caso?.datos.escritura_incierta && typeof caso.datos.escritura_incierta === "object" ? caso.datos.escritura_incierta : {};
|
|
44468
|
+
if (input2.quedo === true || input2.quedo === false) {
|
|
44469
|
+
const resultado = input2.quedo ? "ok" : "error";
|
|
44470
|
+
const nueva = { ...evidencia, verificada_despues: leido };
|
|
44471
|
+
await ctx.db.execute(
|
|
44472
|
+
`UPDATE ${t2(ctx, "accion")} SET resultado = $3, evidencia = $4::jsonb, detalle = $5, cerrada_at = now() WHERE company_id = $1 AND id = $2`,
|
|
44473
|
+
[input2.companyId, input2.accionId, resultado, JSON.stringify(nueva), str2(input2.detalle) ?? (input2.quedo ? "verificada releyendo el HIS: qued\xF3 escrita" : "verificada releyendo el HIS: no se escribi\xF3")]
|
|
44474
|
+
);
|
|
44475
|
+
await ctx.db.execute(
|
|
44476
|
+
`UPDATE ${t2(ctx, "caso")} SET datos = datos - 'escritura_incierta', updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
44477
|
+
[input2.companyId, String(fila.caso_id)]
|
|
44478
|
+
);
|
|
44479
|
+
return { accion: aAccion(fila, { resultado, evidencia: nueva }), paraPersona: false };
|
|
44480
|
+
}
|
|
44481
|
+
const intentos = Number(dudosa.intentos_verificacion ?? 0) + 1;
|
|
44482
|
+
const desde = Date.parse(String(dudosa.desde ?? "")) || Date.now();
|
|
44483
|
+
const paraPersona = dudosaParaPersona(intentos, (Date.now() - desde) / 6e4);
|
|
44484
|
+
await ctx.db.execute(
|
|
44485
|
+
`UPDATE ${t2(ctx, "caso")} SET datos = datos || $3::jsonb, updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
44486
|
+
[input2.companyId, String(fila.caso_id), JSON.stringify({ escritura_incierta: { ...dudosa, intentos_verificacion: intentos, ultimo_intento: (/* @__PURE__ */ new Date()).toISOString(), ultima_lectura: leido, para_persona: paraPersona } })]
|
|
44487
|
+
);
|
|
44488
|
+
if (paraPersona) await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: escritura dudosa que el puesto no pudo verificar, para una persona (${str2(fila.objetivo) ?? "sin objetivo"})`, metadata: { accionId: input2.accionId, casoId: String(fila.caso_id) } });
|
|
44489
|
+
return { accion: aAccion(fila), paraPersona };
|
|
44490
|
+
}
|
|
44491
|
+
async function leerAccionEscritura(ctx, companyId, accionId) {
|
|
44492
|
+
const rows = await ctx.db.query(
|
|
44493
|
+
`SELECT id, caso_id, tipo, conector, objetivo, resultado, evidencia, vence_en, confirmada_at, clave, registro, actor_tipo, actor_id, created_at FROM ${t2(ctx, "accion")} WHERE company_id = $1 AND id = $2 LIMIT 1`,
|
|
44494
|
+
[companyId, accionId]
|
|
44495
|
+
);
|
|
44496
|
+
const fila = rows[0];
|
|
44497
|
+
if (!fila || String(fila.tipo) !== "escritura" || !str2(fila.clave)) throw new OpsError("reserva_inexistente", `no existe la reserva de escritura ${accionId} en esta instituci\xF3n`);
|
|
44498
|
+
return fila;
|
|
44499
|
+
}
|
|
44500
|
+
function aAccion(fila, extra = {}) {
|
|
44501
|
+
return {
|
|
44502
|
+
id: String(fila.id),
|
|
44503
|
+
casoId: String(fila.caso_id),
|
|
44504
|
+
tipo: String(fila.tipo),
|
|
44505
|
+
conector: String(fila.conector),
|
|
44506
|
+
objetivo: String(fila.objetivo),
|
|
44507
|
+
resultado: String(fila.resultado),
|
|
44508
|
+
evidencia: jsonb2(fila.evidencia),
|
|
44509
|
+
detalle: fila.detalle ?? null,
|
|
44510
|
+
actorTipo: String(fila.actor_tipo),
|
|
44511
|
+
actorId: fila.actor_id ?? null,
|
|
44512
|
+
createdAt: iso2(fila.created_at),
|
|
44513
|
+
...extra
|
|
44514
|
+
};
|
|
44515
|
+
}
|
|
44516
|
+
async function reservarEscritura(ctx, input2) {
|
|
44517
|
+
const caso = await leerCaso(ctx, input2.companyId, input2.casoId);
|
|
44518
|
+
if (!caso) throw new OpsError("caso_inexistente", `no existe el caso ${input2.casoId} en esta instituci\xF3n`);
|
|
44519
|
+
if (input2.actor.tipo !== "agente") throw new OpsError("actor_no_permitido", "las reservas de escritura son de los puestos; lo que carga una persona se registra como carga manual");
|
|
44520
|
+
if (WORKFLOWS_SOLO_LECTURA.has(caso.workflow)) throw new OpsError("politica", `en ${caso.workflow} el puesto solo lee: no escribe en el HIS; eso lo hace el equipo`);
|
|
44521
|
+
const modo = await obtenerModo(ctx, input2.companyId, caso.workflow);
|
|
44522
|
+
if (!modoHabilita(modo, "ejecuta")) throw new OpsError("politica", `la pol\xEDtica vigente de ${caso.workflow} es "${modo}": un agente no puede escribir en el HIS`);
|
|
44523
|
+
const clave = str2(input2.clave);
|
|
44524
|
+
const registro = str2(input2.registro);
|
|
44525
|
+
const objetivo = str2(input2.objetivo);
|
|
44526
|
+
if (!clave || !registro) throw new OpsError("validacion", "clave (qu\xE9 escritura de negocio es) y registro (qu\xE9 registro del HIS toca) son obligatorios");
|
|
44527
|
+
if (!objetivo) throw new OpsError("objetivo_requerido", "objetivo es obligatorio (qu\xE9 pantalla, endpoint o archivo se toca)");
|
|
44528
|
+
if (!CONECTORES.includes(input2.conector)) throw new OpsError("conector_invalido", `conector debe ser: ${CONECTORES.join(", ")}`);
|
|
44529
|
+
const leido = input2.leido && typeof input2.leido === "object" && !Array.isArray(input2.leido) ? input2.leido : {};
|
|
44530
|
+
if (Object.keys(leido).length === 0) throw new OpsError("validacion", "leido: lo que se ley\xF3 del registro antes de escribir (al menos un campo); se compara justo antes de guardar");
|
|
44531
|
+
const opciones = await obtenerOpciones(ctx, input2.companyId, caso.workflow);
|
|
44532
|
+
const { hoy, hora, inicioDelDia } = ahoraEnHospital();
|
|
44533
|
+
const cuenta = await ctx.db.query(
|
|
44534
|
+
`SELECT count(*) AS cantidad FROM ${t2(ctx, "accion")} a JOIN ${t2(ctx, "caso")} c ON c.company_id = a.company_id AND c.id = a.caso_id WHERE a.company_id = $1 AND c.workflow = $2 AND a.tipo = 'escritura' AND a.actor_tipo = 'agente' AND a.clave IS NOT NULL AND a.resultado IN ('ok', 'pendiente', 'incierta') AND a.created_at >= $3`,
|
|
44535
|
+
[input2.companyId, caso.workflow, inicioDelDia.toISOString()]
|
|
44536
|
+
);
|
|
44537
|
+
const previas = await ctx.db.query(
|
|
44538
|
+
`SELECT id, caso_id, objetivo, resultado, vence_en FROM ${t2(ctx, "accion")} WHERE company_id = $1 AND clave = $2 AND tipo = 'escritura' ORDER BY created_at DESC LIMIT 1`,
|
|
44539
|
+
[input2.companyId, clave]
|
|
44540
|
+
);
|
|
44541
|
+
const ocupadas = await ctx.db.query(
|
|
44542
|
+
`SELECT id, caso_id FROM ${t2(ctx, "accion")} WHERE company_id = $1 AND registro = $2 AND tipo = 'escritura' AND resultado = 'pendiente' AND vence_en > now() AND caso_id <> $3 LIMIT 1`,
|
|
44543
|
+
[input2.companyId, registro, caso.id]
|
|
44544
|
+
);
|
|
44545
|
+
const previa = previas[0];
|
|
44546
|
+
const vencida = Boolean(previa && String(previa.resultado) === "pendiente" && previa.vence_en && Date.parse(iso2(previa.vence_en)) < Date.now());
|
|
44547
|
+
if (previa && vencida) await marcarDudosa(ctx, input2.companyId, previa, "la reserva venci\xF3 sin cerrarse: no se sabe si se escribi\xF3");
|
|
44548
|
+
const r = evaluarReserva({
|
|
44549
|
+
freno: await leerFreno(ctx, input2.companyId),
|
|
44550
|
+
ventana: leerVentana(opciones.ventana_escritura),
|
|
44551
|
+
hora,
|
|
44552
|
+
diaSemana: diaDeLaSemana(hoy),
|
|
44553
|
+
tope: leerTopeDiario(opciones.tope_escrituras_dia),
|
|
44554
|
+
escriturasHoy: Number(cuenta[0]?.cantidad ?? 0),
|
|
44555
|
+
mismaClave: previa ? { id: String(previa.id), resultado: vencida ? "incierta" : String(previa.resultado), vencida } : null,
|
|
44556
|
+
ocupado: ocupadas[0] ? { id: String(ocupadas[0].id), casoId: String(ocupadas[0].caso_id) } : null
|
|
44557
|
+
});
|
|
44558
|
+
if (!r.ok) throw new OpsError(r.codigo, r.motivo, r.accionId ? { accionId: r.accionId } : void 0);
|
|
44559
|
+
const id3 = randomUUID2();
|
|
44560
|
+
const evidencia = { leido };
|
|
44561
|
+
if (caso.workflow === "recupero_turno" && caso.estado !== "asignando") evidencia.fuera_de_flujo = true;
|
|
44562
|
+
const venceEn = new Date(Date.now() + MINUTOS_RESERVA * 6e4).toISOString();
|
|
44563
|
+
await ctx.db.execute(
|
|
44564
|
+
`INSERT INTO ${t2(ctx, "accion")} (id, company_id, caso_id, tipo, conector, objetivo, resultado, evidencia, detalle, actor_tipo, actor_id, run_id, clave, registro, vence_en) VALUES ($1, $2, $3, 'escritura', $4, $5, 'pendiente', $6::jsonb, $7, $8, $9, $10, $11, $12, $13)`,
|
|
44565
|
+
[id3, input2.companyId, caso.id, input2.conector, objetivo, JSON.stringify(evidencia), null, input2.actor.tipo, input2.actor.id, input2.actor.runId ?? null, clave, registro, venceEn]
|
|
44566
|
+
);
|
|
44567
|
+
await ctx.db.execute(`UPDATE ${t2(ctx, "caso")} SET updated_at = now() WHERE company_id = $1 AND id = $2`, [input2.companyId, caso.id]);
|
|
44568
|
+
return { id: id3, casoId: caso.id, tipo: "escritura", conector: input2.conector, objetivo, resultado: "pendiente", evidencia, detalle: null, actorTipo: input2.actor.tipo, actorId: input2.actor.id, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
44569
|
+
}
|
|
44570
|
+
async function confirmarEscritura(ctx, input2) {
|
|
44571
|
+
const fila = await leerAccionEscritura(ctx, input2.companyId, input2.accionId);
|
|
44572
|
+
if (String(fila.resultado) !== "pendiente") throw new OpsError("reserva_cerrada", `la reserva ${input2.accionId} ya est\xE1 cerrada (${String(fila.resultado)})`);
|
|
44573
|
+
if (fila.vence_en && Date.parse(iso2(fila.vence_en)) < Date.now()) {
|
|
44574
|
+
await marcarDudosa(ctx, input2.companyId, fila, "la reserva venci\xF3 antes de confirmarse");
|
|
44575
|
+
throw new OpsError("reserva_vencida", "la reserva venci\xF3: una persona tiene que mirar el HIS antes de reintentar");
|
|
44576
|
+
}
|
|
44577
|
+
const evidencia = jsonb2(fila.evidencia);
|
|
44578
|
+
const leidoAntes = evidencia.leido && typeof evidencia.leido === "object" ? evidencia.leido : {};
|
|
44579
|
+
const leido = input2.leido && typeof input2.leido === "object" && !Array.isArray(input2.leido) ? input2.leido : {};
|
|
44580
|
+
const cambios = compararLecturas(leidoAntes, leido);
|
|
44581
|
+
const nueva = { ...evidencia, leido_al_confirmar: leido };
|
|
44582
|
+
if (cambios.length > 0) {
|
|
44583
|
+
await ctx.db.execute(
|
|
44584
|
+
`UPDATE ${t2(ctx, "accion")} SET resultado = 'descartada', evidencia = $3::jsonb, detalle = $4, cerrada_at = now() WHERE company_id = $1 AND id = $2`,
|
|
44585
|
+
[input2.companyId, input2.accionId, JSON.stringify(nueva), `el registro cambi\xF3 desde que se ley\xF3 (${cambios.join(", ")}): no se escribi\xF3`]
|
|
44586
|
+
);
|
|
44587
|
+
throw new OpsError("registro_cambiado", `el registro cambi\xF3 desde que se ley\xF3 (${cambios.join(", ")}): no se escribe; volver a proponer con la lectura nueva`, { diferencias: cambios });
|
|
44588
|
+
}
|
|
44589
|
+
await ctx.db.execute(
|
|
44590
|
+
`UPDATE ${t2(ctx, "accion")} SET confirmada_at = now(), evidencia = $3::jsonb WHERE company_id = $1 AND id = $2`,
|
|
44591
|
+
[input2.companyId, input2.accionId, JSON.stringify(nueva)]
|
|
44592
|
+
);
|
|
44593
|
+
return aAccion(fila, { evidencia: nueva });
|
|
44594
|
+
}
|
|
44595
|
+
async function cerrarEscritura(ctx, input2) {
|
|
44596
|
+
if (!RESULTADOS_CIERRE.includes(input2.resultado)) throw new OpsError("resultado_invalido", `resultado debe ser: ${RESULTADOS_CIERRE.join(", ")}`);
|
|
44597
|
+
const fila = await leerAccionEscritura(ctx, input2.companyId, input2.accionId);
|
|
44598
|
+
if (String(fila.resultado) !== "pendiente") throw new OpsError("reserva_cerrada", `la reserva ${input2.accionId} ya est\xE1 cerrada (${String(fila.resultado)})`);
|
|
44599
|
+
const casoDeLaEscritura = await leerCaso(ctx, input2.companyId, String(fila.caso_id));
|
|
44600
|
+
const exigeRelectura = Boolean(casoDeLaEscritura && getWorkflow(casoDeLaEscritura.workflow)?.releerAntesDeEscribir);
|
|
44601
|
+
if (input2.resultado === "ok" && exigeRelectura && !fila.confirmada_at) throw new OpsError("sin_confirmar", "en esta funci\xF3n no se puede dar por hecha una escritura que no se confirm\xF3 releyendo el registro justo antes de guardar");
|
|
44602
|
+
const evidencia = { ...jsonb2(fila.evidencia), ...input2.evidencia ?? {} };
|
|
44603
|
+
const detalle = str2(input2.detalle);
|
|
44604
|
+
if (input2.resultado === "incierta") {
|
|
44605
|
+
await ctx.db.execute(`UPDATE ${t2(ctx, "accion")} SET evidencia = $3::jsonb WHERE company_id = $1 AND id = $2`, [input2.companyId, input2.accionId, JSON.stringify(evidencia)]);
|
|
44606
|
+
await marcarDudosa(ctx, input2.companyId, fila, detalle ?? "el puesto no pudo comprobar si la escritura qued\xF3 hecha");
|
|
44607
|
+
} else {
|
|
44608
|
+
await ctx.db.execute(
|
|
44609
|
+
`UPDATE ${t2(ctx, "accion")} SET resultado = $3, evidencia = $4::jsonb, detalle = $5, cerrada_at = now() WHERE company_id = $1 AND id = $2`,
|
|
44610
|
+
[input2.companyId, input2.accionId, input2.resultado, JSON.stringify(evidencia), detalle]
|
|
44611
|
+
);
|
|
44612
|
+
}
|
|
44613
|
+
const caso = await leerCaso(ctx, input2.companyId, String(fila.caso_id));
|
|
44614
|
+
let frenada = false;
|
|
44615
|
+
if (caso && input2.resultado !== "ok") {
|
|
44616
|
+
const ultimas = await ctx.db.query(
|
|
44617
|
+
`SELECT a.resultado FROM ${t2(ctx, "accion")} a JOIN ${t2(ctx, "caso")} c ON c.company_id = a.company_id AND c.id = a.caso_id WHERE a.company_id = $1 AND c.workflow = $2 AND a.tipo = 'escritura' AND a.actor_tipo = 'agente' AND a.cerrada_at IS NOT NULL AND a.resultado IN ('ok', 'error', 'incierta') ORDER BY a.cerrada_at DESC LIMIT ${FALLAS_PARA_FRENAR}`,
|
|
44618
|
+
[input2.companyId, caso.workflow]
|
|
44619
|
+
);
|
|
44620
|
+
if (debeFrenar(ultimas.map((u) => String(u.resultado)))) {
|
|
44621
|
+
await frenarFuncion(ctx, input2.companyId, caso.workflow);
|
|
44622
|
+
frenada = true;
|
|
44623
|
+
}
|
|
44624
|
+
}
|
|
44625
|
+
return { accion: aAccion(fila, { resultado: input2.resultado, evidencia, detalle }), frenada };
|
|
44626
|
+
}
|
|
44303
44627
|
function numero(v) {
|
|
44304
44628
|
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
44305
44629
|
if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) return Number(v);
|
|
@@ -45441,6 +45765,28 @@ Transiciones registradas: ${detalle.transiciones.length}; acciones registradas:
|
|
|
45441
45765
|
});
|
|
45442
45766
|
return { content: `Acci\xF3n registrada ${accion.id} (${accion.tipo} \xB7 ${accion.conector} \xB7 ${accion.resultado})`, data: { accion } };
|
|
45443
45767
|
}));
|
|
45768
|
+
ctx.tools.register(TOOL_NAMES.reservarEscritura, toolSchema(TOOL_NAMES.reservarEscritura), async (params, runCtx) => conErrores(async () => {
|
|
45769
|
+
const p = obj(params);
|
|
45770
|
+
const accion = await reservarEscritura(ctx, { companyId: runCtx.companyId, casoId: str3(p.casoId) ?? "", clave: str3(p.clave) ?? "", registro: str3(p.registro) ?? "", conector: str3(p.conector) ?? "", objetivo: str3(p.objetivo) ?? "", leido: obj(p.leido), actor: actorAgente(runCtx) });
|
|
45771
|
+
return { content: `Escritura reservada ${accion.id}: rele\xE9 el registro justo antes de guardar y confirmala con ops_confirmar_escritura.`, data: { accion } };
|
|
45772
|
+
}));
|
|
45773
|
+
ctx.tools.register(TOOL_NAMES.confirmarEscritura, toolSchema(TOOL_NAMES.confirmarEscritura), async (params, runCtx) => conErrores(async () => {
|
|
45774
|
+
const p = obj(params);
|
|
45775
|
+
const accion = await confirmarEscritura(ctx, { companyId: runCtx.companyId, accionId: str3(p.accionId) ?? "", leido: obj(p.leido), actor: actorAgente(runCtx) });
|
|
45776
|
+
return { content: `Confirmada ${accion.id}: el registro no cambi\xF3. Guard\xE1 en el HIS y cerr\xE1 con ops_cerrar_escritura.`, data: { accion } };
|
|
45777
|
+
}));
|
|
45778
|
+
ctx.tools.register(TOOL_NAMES.cerrarEscritura, toolSchema(TOOL_NAMES.cerrarEscritura), async (params, runCtx) => conErrores(async () => {
|
|
45779
|
+
const p = obj(params);
|
|
45780
|
+
const r = await cerrarEscritura(ctx, { companyId: runCtx.companyId, accionId: str3(p.accionId) ?? "", resultado: str3(p.resultado) ?? "", evidencia: obj(p.evidencia), detalle: str3(p.detalle), actor: actorAgente(runCtx) });
|
|
45781
|
+
return { content: `Escritura ${r.accion.id} cerrada: ${r.accion.resultado}${r.frenada ? " \xB7 la funci\xF3n qued\xF3 frenada por fallas seguidas" : ""}`, data: r };
|
|
45782
|
+
}));
|
|
45783
|
+
ctx.tools.register(TOOL_NAMES.verificarEscritura, toolSchema(TOOL_NAMES.verificarEscritura), async (params, runCtx) => conErrores(async () => {
|
|
45784
|
+
const p = obj(params);
|
|
45785
|
+
const quedo = p.quedo === true ? true : p.quedo === false ? false : null;
|
|
45786
|
+
const r = await verificarEscritura(ctx, { companyId: runCtx.companyId, accionId: str3(p.accionId) ?? "", quedo, leido: obj(p.leido), detalle: str3(p.detalle), actor: actorAgente(runCtx) });
|
|
45787
|
+
const texto7 = quedo === true ? "qued\xF3 escrita: se da por hecha" : quedo === false ? "no se escribi\xF3: se puede reintentar" : r.paraPersona ? "no se pudo saber: pasa a una persona" : "no se pudo saber: volv\xE9 a verificar m\xE1s tarde";
|
|
45788
|
+
return { content: `Escritura ${r.accion.id}: ${texto7}`, data: r };
|
|
45789
|
+
}));
|
|
45444
45790
|
ctx.tools.register(TOOL_NAMES.listarReglas, toolSchema(TOOL_NAMES.listarReglas), async (params, runCtx) => conErrores(async () => {
|
|
45445
45791
|
const p = obj(params);
|
|
45446
45792
|
const reglas = await listarReglas(ctx, runCtx.companyId, { workflow: str3(p.workflow) ?? "demanda_pendiente", estado: str3(p.estado) });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Escrituras seguras sobre el HIS (src/escrituras.ts): una escritura de un puesto se reserva con una clave de negocio
|
|
2
|
+
-- y el registro que toca, se confirma releyendo justo antes de guardar y se cierra con su resultado.
|
|
3
|
+
-- clave: la misma escritura no se repite. registro: dos trabajos no escriben a la vez el mismo registro.
|
|
4
|
+
-- vence_en: una reserva sin cerrar a tiempo se considera dudosa.
|
|
5
|
+
|
|
6
|
+
ALTER TABLE plugin_autopilot_ops_1a68482ede.accion ADD COLUMN clave text;
|
|
7
|
+
ALTER TABLE plugin_autopilot_ops_1a68482ede.accion ADD COLUMN registro text;
|
|
8
|
+
ALTER TABLE plugin_autopilot_ops_1a68482ede.accion ADD COLUMN vence_en timestamptz;
|
|
9
|
+
ALTER TABLE plugin_autopilot_ops_1a68482ede.accion ADD COLUMN confirmada_at timestamptz;
|
|
10
|
+
ALTER TABLE plugin_autopilot_ops_1a68482ede.accion ADD COLUMN cerrada_at timestamptz;
|
|
11
|
+
|
|
12
|
+
CREATE INDEX accion_clave_idx
|
|
13
|
+
ON plugin_autopilot_ops_1a68482ede.accion (company_id, clave);
|
|
14
|
+
|
|
15
|
+
CREATE INDEX accion_registro_idx
|
|
16
|
+
ON plugin_autopilot_ops_1a68482ede.accion (company_id, registro);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperclip-plugin-autopilot-ops",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
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": [
|