paperclip-plugin-autopilot-ops 0.1.29 → 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 +379 -28
- package/migrations/006_minutos_revisados.sql +13 -0
- 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;
|
|
@@ -41053,18 +41163,18 @@ function leerRegla(v) {
|
|
|
41053
41163
|
return { id: rid, prof: prof === null ? null : redondear2(prof), inst: inst === null ? null : redondear2(inst) };
|
|
41054
41164
|
}
|
|
41055
41165
|
function honorarioDe(input2) {
|
|
41056
|
-
const { regla, monto, cantidad } = input2;
|
|
41166
|
+
const { regla, monto, cantidad: cantidad2 } = input2;
|
|
41057
41167
|
if (!regla) return 0;
|
|
41058
|
-
if (regla.prof !== null) return redondear2(regla.prof *
|
|
41059
|
-
return redondear2(monto *
|
|
41168
|
+
if (regla.prof !== null) return redondear2(regla.prof * cantidad2);
|
|
41169
|
+
return redondear2(monto * cantidad2 * ((regla.inst ?? 0) / 100));
|
|
41060
41170
|
}
|
|
41061
41171
|
function motivoCero(regla, honorario, delHis = false) {
|
|
41062
41172
|
if (honorario > 0) return null;
|
|
41063
41173
|
if (delHis) return "segun_his";
|
|
41064
41174
|
return regla ? "regla_sin_valor" : "sin_regla";
|
|
41065
41175
|
}
|
|
41066
|
-
function honorarioLeido(r, regla, monto,
|
|
41067
|
-
if (r.honorario === void 0 || r.honorario === null || r.honorario === "") return { honorario: honorarioDe({ regla, monto, cantidad }), delHis: false };
|
|
41176
|
+
function honorarioLeido(r, regla, monto, cantidad2) {
|
|
41177
|
+
if (r.honorario === void 0 || r.honorario === null || r.honorario === "") return { honorario: honorarioDe({ regla, monto, cantidad: cantidad2 }), delHis: false };
|
|
41068
41178
|
const h = num3(r.honorario);
|
|
41069
41179
|
return h === null || h < 0 ? null : { honorario: redondear2(h), delHis: true };
|
|
41070
41180
|
}
|
|
@@ -41078,12 +41188,12 @@ function leerPrestaciones(v) {
|
|
|
41078
41188
|
const bono = id2(r.bono_id);
|
|
41079
41189
|
const f = dia(r.fecha);
|
|
41080
41190
|
const monto = num3(r.monto);
|
|
41081
|
-
const
|
|
41191
|
+
const cantidad2 = num3(r.cantidad) ?? 1;
|
|
41082
41192
|
if (!item || !bono || !f || monto === null) return { error: `prestaciones[${i}]: faltan item_id, bono_id, fecha o monto` };
|
|
41083
41193
|
if (r.regla !== null && r.regla !== void 0 && !leerRegla(r.regla)) return { error: `prestaciones[${i}].regla: tiene que ser {id, prof, inst} o null` };
|
|
41084
|
-
const h = honorarioLeido(r, leerRegla(r.regla), monto,
|
|
41194
|
+
const h = honorarioLeido(r, leerRegla(r.regla), monto, cantidad2 > 0 ? cantidad2 : 1);
|
|
41085
41195
|
if (!h) return { error: `prestaciones[${i}].honorario: tiene que ser un n\xFAmero mayor o igual a 0` };
|
|
41086
|
-
out.push({ item_id: item, bono_id: bono, fecha: f, prestacion: texto2(r.prestacion)?.slice(0, 160) ?? "Prestaci\xF3n", obra_social: texto2(r.obra_social)?.slice(0, 120) ?? null, monto: redondear2(monto), cantidad:
|
|
41196
|
+
out.push({ item_id: item, bono_id: bono, fecha: f, prestacion: texto2(r.prestacion)?.slice(0, 160) ?? "Prestaci\xF3n", obra_social: texto2(r.obra_social)?.slice(0, 120) ?? null, monto: redondear2(monto), cantidad: cantidad2 > 0 ? cantidad2 : 1, a_liquidar: verdad2(r.a_liquidar), liquidada: verdad2(r.liquidada), regla: leerRegla(r.regla), honorario_his: h.delHis ? h.honorario : null });
|
|
41087
41197
|
}
|
|
41088
41198
|
return { prestaciones: out };
|
|
41089
41199
|
}
|
|
@@ -41122,13 +41232,13 @@ function leerDebitos(v) {
|
|
|
41122
41232
|
const ncId = nc ? id2(nc.id) : null;
|
|
41123
41233
|
if (!item || !bono || monto === null || !ncId) return { error: `debitos[${i}]: faltan item_id, bono_id, monto_debitado o nota_credito.id` };
|
|
41124
41234
|
const montoItem = num3(r.monto) ?? 0;
|
|
41125
|
-
const
|
|
41235
|
+
const cantidad2 = num3(r.cantidad) ?? 1;
|
|
41126
41236
|
const regla = leerRegla(r.regla);
|
|
41127
41237
|
const liquidada = verdad2(r.liquidada);
|
|
41128
|
-
const hl = honorarioLeido(r, regla, montoItem,
|
|
41238
|
+
const hl = honorarioLeido(r, regla, montoItem, cantidad2);
|
|
41129
41239
|
if (!hl) return { error: `debitos[${i}].honorario: tiene que ser un n\xFAmero mayor o igual a 0` };
|
|
41130
41240
|
const hon = liquidada ? hl.honorario : null;
|
|
41131
|
-
const imp = liquidada ? redondear2(montoItem *
|
|
41241
|
+
const imp = liquidada ? redondear2(montoItem * cantidad2) : null;
|
|
41132
41242
|
out.push({
|
|
41133
41243
|
item_id: item,
|
|
41134
41244
|
bono_id: bono,
|
|
@@ -41139,7 +41249,7 @@ function leerDebitos(v) {
|
|
|
41139
41249
|
monto_debitado: redondear2(Math.abs(monto)),
|
|
41140
41250
|
liquidada,
|
|
41141
41251
|
monto: redondear2(montoItem),
|
|
41142
|
-
cantidad:
|
|
41252
|
+
cantidad: cantidad2 > 0 ? cantidad2 : 1,
|
|
41143
41253
|
regla,
|
|
41144
41254
|
liquidado: hon !== null && imp !== null ? { honorario: hon, importe: imp } : null,
|
|
41145
41255
|
ya_trasladado: redondear2(Math.abs(num3(r.ya_trasladado) ?? 0))
|
|
@@ -41172,12 +41282,12 @@ function leerLineasLiquidacion(v) {
|
|
|
41172
41282
|
for (const it of r.items) {
|
|
41173
41283
|
const x = it && typeof it === "object" ? it : {};
|
|
41174
41284
|
const monto = num3(x.monto) ?? 0;
|
|
41175
|
-
const
|
|
41285
|
+
const cantidad2 = num3(x.cantidad) ?? 1;
|
|
41176
41286
|
const regla = leerRegla(x.regla);
|
|
41177
|
-
const hl = honorarioLeido(x, regla, monto,
|
|
41287
|
+
const hl = honorarioLeido(x, regla, monto, cantidad2);
|
|
41178
41288
|
if (!hl) return { error: `lineas[${i}].items: honorario tiene que ser un n\xFAmero mayor o igual a 0` };
|
|
41179
41289
|
const h = hl.honorario;
|
|
41180
|
-
importe += monto *
|
|
41290
|
+
importe += monto * cantidad2;
|
|
41181
41291
|
honorario += h;
|
|
41182
41292
|
if (h <= 0) enCero += 1;
|
|
41183
41293
|
}
|
|
@@ -41382,6 +41492,7 @@ var RESPUESTAS_FINANCIADOR = ["paga", "observa", "no_responde"];
|
|
|
41382
41492
|
// src/workflows.ts
|
|
41383
41493
|
var MODOS = ["corte", "observa", "propone", "ejecuta", "autonomo"];
|
|
41384
41494
|
var MODO_POR_DEFECTO = "propone";
|
|
41495
|
+
var cantidad = (v) => Array.isArray(v) ? v.length : 0;
|
|
41385
41496
|
function num5(v) {
|
|
41386
41497
|
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
41387
41498
|
if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) return Number(v);
|
|
@@ -41416,7 +41527,7 @@ var actualizacionArancel = {
|
|
|
41416
41527
|
],
|
|
41417
41528
|
finalesOk: ["verificado"],
|
|
41418
41529
|
finalesNoOk: ["rechazado", "cancelado"],
|
|
41419
|
-
minutosAbsorbidos:
|
|
41530
|
+
minutosAbsorbidos: 8,
|
|
41420
41531
|
transiciones: [
|
|
41421
41532
|
{
|
|
41422
41533
|
via: "proponer",
|
|
@@ -41669,7 +41780,7 @@ var recuperoTurno = {
|
|
|
41669
41780
|
],
|
|
41670
41781
|
finalesOk: ["verificado"],
|
|
41671
41782
|
finalesNoOk: ["sin_candidato", "rechazado", "cancelado"],
|
|
41672
|
-
minutosAbsorbidos:
|
|
41783
|
+
minutosAbsorbidos: 12,
|
|
41673
41784
|
transiciones: [
|
|
41674
41785
|
{
|
|
41675
41786
|
via: "proponer",
|
|
@@ -41910,7 +42021,7 @@ var cuentaCompleta = {
|
|
|
41910
42021
|
estados: ["detectado", "para_completar", "completado", "con_faltantes", "completa", "facturado_incompleto", "no_corresponde", "descartado"],
|
|
41911
42022
|
finalesOk: ["completa"],
|
|
41912
42023
|
finalesNoOk: ["facturado_incompleto", "no_corresponde", "descartado"],
|
|
41913
|
-
minutosAbsorbidos:
|
|
42024
|
+
minutosAbsorbidos: 4,
|
|
41914
42025
|
transiciones: [
|
|
41915
42026
|
{
|
|
41916
42027
|
via: "observar",
|
|
@@ -42010,7 +42121,7 @@ var facturacionPendiente = {
|
|
|
42010
42121
|
estados: ["detectado", "por_presentar", "en_prefactura", "presentado", "facturada", "no_corresponde", "descartado"],
|
|
42011
42122
|
finalesOk: ["facturada"],
|
|
42012
42123
|
finalesNoOk: ["no_corresponde", "descartado"],
|
|
42013
|
-
minutosAbsorbidos:
|
|
42124
|
+
minutosAbsorbidos: 3,
|
|
42014
42125
|
transiciones: [
|
|
42015
42126
|
{
|
|
42016
42127
|
via: "observar",
|
|
@@ -42119,7 +42230,7 @@ var credencialProfesional = {
|
|
|
42119
42230
|
estados: ["detectado", "vigente", "observada", "corregida", "no_corresponde", "descartado"],
|
|
42120
42231
|
finalesOk: ["vigente"],
|
|
42121
42232
|
finalesNoOk: ["no_corresponde", "descartado"],
|
|
42122
|
-
minutosAbsorbidos:
|
|
42233
|
+
minutosAbsorbidos: 5,
|
|
42123
42234
|
transiciones: [
|
|
42124
42235
|
{
|
|
42125
42236
|
via: "verificar",
|
|
@@ -42222,7 +42333,7 @@ var demandaPendiente = {
|
|
|
42222
42333
|
],
|
|
42223
42334
|
finalesOk: ["turno_agendado", "realizado"],
|
|
42224
42335
|
finalesNoOk: ["no_quiere", "cerrado_sin_turno", "no_corresponde", "descartado"],
|
|
42225
|
-
minutosAbsorbidos:
|
|
42336
|
+
minutosAbsorbidos: 6,
|
|
42226
42337
|
transiciones: [
|
|
42227
42338
|
{
|
|
42228
42339
|
via: "contactar",
|
|
@@ -42332,7 +42443,9 @@ var circularAranceles = {
|
|
|
42332
42443
|
estados: ["recibida", "extraida", "pendiente_aprobacion", "aprobada", "aplicada", "verificada", "con_diferencias", "rechazada", "cancelada"],
|
|
42333
42444
|
finalesOk: ["verificada"],
|
|
42334
42445
|
finalesNoOk: ["rechazada", "cancelada"],
|
|
42335
|
-
minutosAbsorbidos:
|
|
42446
|
+
minutosAbsorbidos: 30,
|
|
42447
|
+
// Base de lectura y armado, más el cruce y la comprobación de cada valor (tope: una jornada).
|
|
42448
|
+
minutosSegun: (caso) => Math.min(480, 30 + 3 * cantidad(caso.datos.items)),
|
|
42336
42449
|
transiciones: [
|
|
42337
42450
|
{
|
|
42338
42451
|
via: "extraer",
|
|
@@ -42488,7 +42601,9 @@ var cobranzaFinanciador = {
|
|
|
42488
42601
|
estados: ["detectado", "pendiente_aprobacion", "aprobado", "reclamado", "comprometido", "escalado", "cobrado", "cerrado", "rechazado", "descartado"],
|
|
42489
42602
|
finalesOk: ["cobrado"],
|
|
42490
42603
|
finalesNoOk: ["cerrado", "rechazado", "descartado"],
|
|
42491
|
-
minutosAbsorbidos:
|
|
42604
|
+
minutosAbsorbidos: 20,
|
|
42605
|
+
// Base del reclamo y su seguimiento, más el armado de cada factura reclamada.
|
|
42606
|
+
minutosSegun: (caso) => Math.min(180, 20 + 2 * cantidad(caso.datos.items)),
|
|
42492
42607
|
transiciones: [
|
|
42493
42608
|
{
|
|
42494
42609
|
via: "proponer",
|
|
@@ -42660,7 +42775,7 @@ var internacionAlDia = {
|
|
|
42660
42775
|
estados: ["detectado", "al_dia", "con_pendientes", "completado", "egresada", "descartado"],
|
|
42661
42776
|
finalesOk: ["egresada"],
|
|
42662
42777
|
finalesNoOk: ["descartado"],
|
|
42663
|
-
minutosAbsorbidos:
|
|
42778
|
+
minutosAbsorbidos: 6,
|
|
42664
42779
|
transiciones: [
|
|
42665
42780
|
{
|
|
42666
42781
|
via: "observar",
|
|
@@ -42719,7 +42834,7 @@ var honorarioEnCero = {
|
|
|
42719
42834
|
estados: ["detectado", "para_cargar", "completado", "resuelto", "no_corresponde", "descartado"],
|
|
42720
42835
|
finalesOk: ["resuelto"],
|
|
42721
42836
|
finalesNoOk: ["no_corresponde", "descartado"],
|
|
42722
|
-
minutosAbsorbidos:
|
|
42837
|
+
minutosAbsorbidos: 10,
|
|
42723
42838
|
transiciones: [
|
|
42724
42839
|
{
|
|
42725
42840
|
via: "observar",
|
|
@@ -42889,7 +43004,7 @@ var liquidacionProfesional = {
|
|
|
42889
43004
|
estados: ["detectado", "pendiente_aprobacion", "aprobado", "enviada", "rechazado", "descartado"],
|
|
42890
43005
|
finalesOk: ["enviada"],
|
|
42891
43006
|
finalesNoOk: ["rechazado", "descartado"],
|
|
42892
|
-
minutosAbsorbidos:
|
|
43007
|
+
minutosAbsorbidos: 15,
|
|
42893
43008
|
transiciones: [
|
|
42894
43009
|
{
|
|
42895
43010
|
via: "preparar",
|
|
@@ -43021,7 +43136,7 @@ function evaluarTransicion(input2) {
|
|
|
43021
43136
|
a,
|
|
43022
43137
|
via,
|
|
43023
43138
|
validaciones,
|
|
43024
|
-
minutosAbsorbidos: esFinalOk ? wf.minutosAbsorbidos : 0,
|
|
43139
|
+
minutosAbsorbidos: esFinalOk ? wf.minutosSegun ? wf.minutosSegun(caso) : wf.minutosAbsorbidos : 0,
|
|
43025
43140
|
esFinal: esFinalOk || wf.finalesNoOk.includes(a),
|
|
43026
43141
|
autoaprobado
|
|
43027
43142
|
};
|
|
@@ -44267,6 +44382,9 @@ async function registrarAccion(ctx, input2) {
|
|
|
44267
44382
|
const modo = await obtenerModo(ctx, input2.companyId, caso.workflow);
|
|
44268
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`);
|
|
44269
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
|
+
}
|
|
44270
44388
|
const id3 = randomUUID2();
|
|
44271
44389
|
const evidencia = { ...input2.evidencia ?? {} };
|
|
44272
44390
|
delete evidencia.fuera_de_flujo;
|
|
@@ -44295,6 +44413,217 @@ async function registrarAccion(ctx, input2) {
|
|
|
44295
44413
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
44296
44414
|
};
|
|
44297
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
|
+
}
|
|
44298
44627
|
function numero(v) {
|
|
44299
44628
|
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
44300
44629
|
if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) return Number(v);
|
|
@@ -45436,6 +45765,28 @@ Transiciones registradas: ${detalle.transiciones.length}; acciones registradas:
|
|
|
45436
45765
|
});
|
|
45437
45766
|
return { content: `Acci\xF3n registrada ${accion.id} (${accion.tipo} \xB7 ${accion.conector} \xB7 ${accion.resultado})`, data: { accion } };
|
|
45438
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
|
+
}));
|
|
45439
45790
|
ctx.tools.register(TOOL_NAMES.listarReglas, toolSchema(TOOL_NAMES.listarReglas), async (params, runCtx) => conErrores(async () => {
|
|
45440
45791
|
const p = obj(params);
|
|
45441
45792
|
const reglas = await listarReglas(ctx, runCtx.companyId, { workflow: str3(p.workflow) ?? "demanda_pendiente", estado: str3(p.estado) });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Minutos de trabajo humano revisados el 17/10/2026 (ver docs/ESTIMACION-MINUTOS.md): se corrigen los casos ya cerrados
|
|
2
|
+
-- que contaron minutos, para que el histórico y lo nuevo se midan igual. Los casos que no contaron (0) no cambian.
|
|
3
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = 8 WHERE workflow = 'actualizacion_arancel' AND minutos_absorbidos > 0;
|
|
4
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = 12 WHERE workflow = 'recupero_turno' AND minutos_absorbidos > 0;
|
|
5
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = 4 WHERE workflow = 'cuenta_completa' AND minutos_absorbidos > 0;
|
|
6
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = 3 WHERE workflow = 'facturacion_pendiente' AND minutos_absorbidos > 0;
|
|
7
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = 5 WHERE workflow = 'credencial_profesional' AND minutos_absorbidos > 0;
|
|
8
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = 6 WHERE workflow = 'demanda_pendiente' AND minutos_absorbidos > 0;
|
|
9
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = LEAST(480, 30 + 3 * jsonb_array_length(CASE WHEN jsonb_typeof(datos->'items') = 'array' THEN datos->'items' ELSE '[]'::jsonb END)) WHERE workflow = 'circular_aranceles' AND minutos_absorbidos > 0;
|
|
10
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = LEAST(180, 20 + 2 * jsonb_array_length(CASE WHEN jsonb_typeof(datos->'items') = 'array' THEN datos->'items' ELSE '[]'::jsonb END)) WHERE workflow = 'cobranza_financiador' AND minutos_absorbidos > 0;
|
|
11
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = 6 WHERE workflow = 'internacion_al_dia' AND minutos_absorbidos > 0;
|
|
12
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = 10 WHERE workflow = 'honorario_en_cero' AND minutos_absorbidos > 0;
|
|
13
|
+
UPDATE plugin_autopilot_ops_1a68482ede.caso SET minutos_absorbidos = 15 WHERE workflow = 'liquidacion_profesional' AND minutos_absorbidos > 0;
|
|
@@ -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": [
|