@hostwebhook/platform-contracts 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/servicios/almacenes-vectoriales.d.ts +105 -0
- package/dist/servicios/almacenes-vectoriales.js +26 -0
- package/dist/servicios/aprobaciones-pendientes.d.ts +207 -0
- package/dist/servicios/aprobaciones-pendientes.js +51 -0
- package/dist/servicios/avisos-en-vivo.d.ts +106 -0
- package/dist/servicios/avisos-en-vivo.js +60 -0
- package/dist/servicios/canal-de-stream.d.ts +78 -0
- package/dist/servicios/canal-de-stream.js +5 -0
- package/dist/servicios/conversaciones-del-chat.d.ts +139 -0
- package/dist/servicios/conversaciones-del-chat.js +51 -0
- package/dist/servicios/corridas-programadas.d.ts +29 -0
- package/dist/servicios/corridas-programadas.js +5 -0
- package/dist/servicios/credencial/permiso-sobre-credencial.d.ts +61 -0
- package/dist/servicios/credencial/permiso-sobre-credencial.js +42 -0
- package/dist/servicios/credenciales-del-gateway.d.ts +722 -0
- package/dist/servicios/credenciales-del-gateway.js +114 -0
- package/dist/servicios/cuentas-de-atlassian.d.ts +22 -0
- package/dist/servicios/cuentas-de-atlassian.js +5 -0
- package/dist/servicios/descarga-de-drive.d.ts +74 -0
- package/dist/servicios/descarga-de-drive.js +41 -0
- package/dist/servicios/ejecucion-de-ia.d.ts +81 -0
- package/dist/servicios/ejecucion-de-ia.js +39 -0
- package/dist/servicios/enlaces-de-flujo.d.ts +59 -0
- package/dist/servicios/enlaces-de-flujo.js +27 -0
- package/dist/servicios/ficheros-del-gateway.d.ts +210 -0
- package/dist/servicios/ficheros-del-gateway.js +8 -0
- package/dist/servicios/formas-compartidas.d.ts +57 -0
- package/dist/servicios/formas-compartidas.js +22 -0
- package/dist/servicios/historial-de-corridas.d.ts +75 -0
- package/dist/servicios/historial-de-corridas.js +7 -0
- package/dist/servicios/index.d.ts +57 -0
- package/dist/servicios/index.js +44 -0
- package/dist/servicios/limites-del-plan.d.ts +82 -0
- package/dist/servicios/limites-del-plan.js +41 -0
- package/dist/servicios/motor-de-ejecucion.d.ts +197 -0
- package/dist/servicios/motor-de-ejecucion.js +11 -0
- package/dist/servicios/plantillas-de-origen.d.ts +25 -0
- package/dist/servicios/plantillas-de-origen.js +5 -0
- package/dist/servicios/posts-sociales.d.ts +69 -0
- package/dist/servicios/posts-sociales.js +23 -0
- package/dist/servicios/registro-de-entregas.d.ts +93 -0
- package/dist/servicios/registro-de-entregas.js +46 -0
- package/dist/servicios/registro-de-eventos.d.ts +135 -0
- package/dist/servicios/registro-de-eventos.js +62 -0
- package/dist/servicios/restauracion-de-nodos.d.ts +53 -0
- package/dist/servicios/restauracion-de-nodos.js +4 -0
- package/dist/servicios/salud-del-webhook.d.ts +26 -0
- package/dist/servicios/salud-del-webhook.js +6 -0
- package/dist/servicios/secretos-de-firma.d.ts +26 -0
- package/dist/servicios/secretos-de-firma.js +5 -0
- package/dist/servicios/telemetria.d.ts +114 -0
- package/dist/servicios/telemetria.js +60 -0
- package/dist/servicios/trazas-de-llm.d.ts +79 -0
- package/dist/servicios/trazas-de-llm.js +23 -0
- package/dist/servicios/tuneles.d.ts +106 -0
- package/dist/servicios/tuneles.js +87 -0
- package/dist/servicios/workspace/acceso-al-recurso.d.ts +39 -0
- package/dist/servicios/workspace/acceso-al-recurso.js +5 -0
- package/dist/servicios/zona-horaria-del-usuario.d.ts +40 -0
- package/dist/servicios/zona-horaria-del-usuario.js +7 -0
- package/package.json +9 -3
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lo que el nodo de redes le pide al calendario de publicaciones.
|
|
3
|
+
*
|
|
4
|
+
* Costura de la Fase 2, del lote de las pequeñas. `SocialPostsService`
|
|
5
|
+
* arrastra la colección de posts, el planificador por zona horaria y el
|
|
6
|
+
* publicador con sus siete proveedores. El nodo publica y deja constancia.
|
|
7
|
+
*
|
|
8
|
+
* ## Cómo viaja
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ `publicarDesdeNodo` devolvía un documento de Mongoose. Su llamante lee
|
|
11
|
+
* TRES campos —`id`, `status` y `results`— y nada más; se comprobó llamante
|
|
12
|
+
* por llamante. El contrato devuelve esos tres, planos.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ `publishApproved` devuelve `null` cuando el post sigue esperando su hora,
|
|
15
|
+
* y eso NO es un error: el llamante lo distingue en el log («sigue esperando
|
|
16
|
+
* su hora»). Un contrato que lanzara en vez de devolver `null` convertiría un
|
|
17
|
+
* caso normal en un fallo.
|
|
18
|
+
*/
|
|
19
|
+
/** Una fila por (red × cuenta): de aquí sale el `3/7` del parcial. */
|
|
20
|
+
export interface ResultadoDePublicacion {
|
|
21
|
+
platform: string;
|
|
22
|
+
credentialId: string;
|
|
23
|
+
success: boolean;
|
|
24
|
+
url?: string;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
/** Lo que el nodo lee de un post recién creado. Tres campos. */
|
|
28
|
+
export interface PostCreado {
|
|
29
|
+
id: string;
|
|
30
|
+
status: string;
|
|
31
|
+
results: ResultadoDePublicacion[];
|
|
32
|
+
}
|
|
33
|
+
export interface PostsSociales {
|
|
34
|
+
publicarDesdeNodo(params: {
|
|
35
|
+
orgId: string;
|
|
36
|
+
workspaceId: string;
|
|
37
|
+
ownerUserId: string;
|
|
38
|
+
sourceNodeId?: string;
|
|
39
|
+
entityName?: string;
|
|
40
|
+
platforms: string[];
|
|
41
|
+
credentialIds: Record<string, string[]>;
|
|
42
|
+
text: string;
|
|
43
|
+
media?: Array<Record<string, unknown>>;
|
|
44
|
+
perNetwork?: Record<string, {
|
|
45
|
+
text?: string;
|
|
46
|
+
media?: unknown[];
|
|
47
|
+
}>;
|
|
48
|
+
}): Promise<PostCreado>;
|
|
49
|
+
/** Deja constancia de lo que ya publicó el flujo por su cuenta. */
|
|
50
|
+
recordFlowPost(params: {
|
|
51
|
+
orgId: string;
|
|
52
|
+
workspaceId: string;
|
|
53
|
+
sourceNodeId?: string;
|
|
54
|
+
text?: string;
|
|
55
|
+
media?: Array<Record<string, unknown>>;
|
|
56
|
+
results: Array<{
|
|
57
|
+
platform: string;
|
|
58
|
+
credentialId: string;
|
|
59
|
+
success: boolean;
|
|
60
|
+
url?: string;
|
|
61
|
+
error?: string;
|
|
62
|
+
}>;
|
|
63
|
+
}): Promise<void>;
|
|
64
|
+
/** ⚠️ `null` = aprobado pero aún no le toca. No es un error. */
|
|
65
|
+
publishApproved(postId: string, porQuien?: string): Promise<string | null>;
|
|
66
|
+
rejectApproval(postId: string, porQuien?: string): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
/** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
|
|
69
|
+
export declare const POSTS_SOCIALES = "POSTS_SOCIALES";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Lo que el nodo de redes le pide al calendario de publicaciones.
|
|
4
|
+
*
|
|
5
|
+
* Costura de la Fase 2, del lote de las pequeñas. `SocialPostsService`
|
|
6
|
+
* arrastra la colección de posts, el planificador por zona horaria y el
|
|
7
|
+
* publicador con sus siete proveedores. El nodo publica y deja constancia.
|
|
8
|
+
*
|
|
9
|
+
* ## Cómo viaja
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ `publicarDesdeNodo` devolvía un documento de Mongoose. Su llamante lee
|
|
12
|
+
* TRES campos —`id`, `status` y `results`— y nada más; se comprobó llamante
|
|
13
|
+
* por llamante. El contrato devuelve esos tres, planos.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ `publishApproved` devuelve `null` cuando el post sigue esperando su hora,
|
|
16
|
+
* y eso NO es un error: el llamante lo distingue en el log («sigue esperando
|
|
17
|
+
* su hora»). Un contrato que lanzara en vez de devolver `null` convertiría un
|
|
18
|
+
* caso normal en un fallo.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.POSTS_SOCIALES = void 0;
|
|
22
|
+
/** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
|
|
23
|
+
exports.POSTS_SOCIALES = 'POSTS_SOCIALES';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lo que el motor apunta y consulta sobre las entregas.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué esta costura es distinta de las trece anteriores
|
|
5
|
+
*
|
|
6
|
+
* Las otras invertían un SERVICIO. Ésta invierte el acceso directo a una
|
|
7
|
+
* COLECCIÓN: `nodes/` y el motor inyectaban `Model<DeliveryDocument>` y
|
|
8
|
+
* consultaban `deliveries` a mano.
|
|
9
|
+
*
|
|
10
|
+
* Por eso se dejó fuera del lote del #468: cerrarla obliga a decidir de quién
|
|
11
|
+
* es la colección. La respuesta, mirando lo que se hace con ella, es que es
|
|
12
|
+
* del gateway — `deliveries` es lo que el panel enseña como historial de
|
|
13
|
+
* entregas, tiene su propia política de retención en `plans/` y su controlador
|
|
14
|
+
* de reintentos. El motor sólo escribe filas y pregunta si ya escribió una.
|
|
15
|
+
*
|
|
16
|
+
* ## Las tres operaciones, medidas
|
|
17
|
+
*
|
|
18
|
+
* 3 `exists` la comprobación de IDEMPOTENCIA de email, sheets y
|
|
19
|
+
* notificaciones
|
|
20
|
+
* 3 `create` la fila de cada entrega, con la misma forma las tres
|
|
21
|
+
* 2 `deleteMany` las dos cascadas: por webhook y por evento
|
|
22
|
+
*
|
|
23
|
+
* ## ⚠️ Lo que no se puede perder
|
|
24
|
+
*
|
|
25
|
+
* **`soloSiTuvoExito`.** El chequeo de notificaciones añade `success: true` al
|
|
26
|
+
* filtro cuando `triggerOn` NO es `'always'`. Perder esa condición tiene dos
|
|
27
|
+
* caras y las dos son malas: sin ella, una notificación que falló cuenta como
|
|
28
|
+
* entregada y no se reintenta nunca; con ella siempre puesta, una que sí se
|
|
29
|
+
* mandó se manda otra vez en cada reintento del evento.
|
|
30
|
+
*
|
|
31
|
+
* **`refIdIn` en el borrado.** `webhookId` se guarda como cadena en unas filas
|
|
32
|
+
* y como `ObjectId` en otras. Filtrar por una sola forma deja la mitad de las
|
|
33
|
+
* entregas huérfanas — que es justo lo que la cascada existe para evitar. El
|
|
34
|
+
* contrato recibe los ids en crudo y la conversión se queda en el gateway,
|
|
35
|
+
* donde vive el problema.
|
|
36
|
+
*
|
|
37
|
+
* ## Cómo viaja
|
|
38
|
+
*
|
|
39
|
+
* Bien: entran y salen datos planos, y `anotar` es «dispara y olvida» —el SDK
|
|
40
|
+
* ya la llama con `.catch()` colgado, sin esperarla—.
|
|
41
|
+
*/
|
|
42
|
+
/** Una fila del historial de entregas. */
|
|
43
|
+
export interface EntregaAnotada {
|
|
44
|
+
eventId: string;
|
|
45
|
+
webhookId?: string;
|
|
46
|
+
emailActionId?: string;
|
|
47
|
+
sheetsActionId?: string;
|
|
48
|
+
notificationActionId?: string;
|
|
49
|
+
targetUrl?: string;
|
|
50
|
+
attempt?: number;
|
|
51
|
+
statusCode?: number;
|
|
52
|
+
responseBody?: string;
|
|
53
|
+
latencyMs?: number;
|
|
54
|
+
success?: boolean;
|
|
55
|
+
error?: string | null;
|
|
56
|
+
}
|
|
57
|
+
export interface RegistroDeEntregas {
|
|
58
|
+
/**
|
|
59
|
+
* ¿Ya se entregó esto para este evento?
|
|
60
|
+
*
|
|
61
|
+
* ⚠️ `soloSiTuvoExito` NO es opcional en el sentido de «da igual»: cambia
|
|
62
|
+
* qué cuenta como entregado. Ver la cabecera.
|
|
63
|
+
*/
|
|
64
|
+
yaSeEntrego(filtro: {
|
|
65
|
+
eventId: string;
|
|
66
|
+
emailActionId?: string;
|
|
67
|
+
sheetsActionId?: string;
|
|
68
|
+
notificationActionId?: string;
|
|
69
|
+
soloSiTuvoExito?: boolean;
|
|
70
|
+
}): Promise<boolean>;
|
|
71
|
+
/** ⚠️ No se espera. El SDK la llama con un `.catch()` colgado. */
|
|
72
|
+
anotar(entrega: EntregaAnotada): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* La cascada al borrar webhooks.
|
|
75
|
+
*
|
|
76
|
+
* ⚠️ Recibe los ids en crudo a propósito: la conversión a las dos formas en
|
|
77
|
+
* que se guardan —cadena y `ObjectId`— se queda en el gateway. Ver la
|
|
78
|
+
* cabecera.
|
|
79
|
+
*/
|
|
80
|
+
borrarDeWebhooks(webhookIds: ReadonlyArray<string>): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* La otra cascada: al borrar un trigger, sus entregas.
|
|
83
|
+
*
|
|
84
|
+
* ⚠️ Va por `eventId` y NO por `webhookId`, y ésa es toda la razón de que
|
|
85
|
+
* sean dos métodos. Las entregas se indexan por el evento; el llamante tiene
|
|
86
|
+
* que recoger los ids de evento ANTES de borrar los eventos, porque después
|
|
87
|
+
* no queda nada por lo que resolverlos y se quedan huérfanas en silencio.
|
|
88
|
+
* Ver `cascade-trigger-events.ts`, que existe justo por eso.
|
|
89
|
+
*/
|
|
90
|
+
borrarDeEventos(eventIds: ReadonlyArray<string>): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
/** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
|
|
93
|
+
export declare const REGISTRO_DE_ENTREGAS = "REGISTRO_DE_ENTREGAS";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Lo que el motor apunta y consulta sobre las entregas.
|
|
4
|
+
*
|
|
5
|
+
* ## Por qué esta costura es distinta de las trece anteriores
|
|
6
|
+
*
|
|
7
|
+
* Las otras invertían un SERVICIO. Ésta invierte el acceso directo a una
|
|
8
|
+
* COLECCIÓN: `nodes/` y el motor inyectaban `Model<DeliveryDocument>` y
|
|
9
|
+
* consultaban `deliveries` a mano.
|
|
10
|
+
*
|
|
11
|
+
* Por eso se dejó fuera del lote del #468: cerrarla obliga a decidir de quién
|
|
12
|
+
* es la colección. La respuesta, mirando lo que se hace con ella, es que es
|
|
13
|
+
* del gateway — `deliveries` es lo que el panel enseña como historial de
|
|
14
|
+
* entregas, tiene su propia política de retención en `plans/` y su controlador
|
|
15
|
+
* de reintentos. El motor sólo escribe filas y pregunta si ya escribió una.
|
|
16
|
+
*
|
|
17
|
+
* ## Las tres operaciones, medidas
|
|
18
|
+
*
|
|
19
|
+
* 3 `exists` la comprobación de IDEMPOTENCIA de email, sheets y
|
|
20
|
+
* notificaciones
|
|
21
|
+
* 3 `create` la fila de cada entrega, con la misma forma las tres
|
|
22
|
+
* 2 `deleteMany` las dos cascadas: por webhook y por evento
|
|
23
|
+
*
|
|
24
|
+
* ## ⚠️ Lo que no se puede perder
|
|
25
|
+
*
|
|
26
|
+
* **`soloSiTuvoExito`.** El chequeo de notificaciones añade `success: true` al
|
|
27
|
+
* filtro cuando `triggerOn` NO es `'always'`. Perder esa condición tiene dos
|
|
28
|
+
* caras y las dos son malas: sin ella, una notificación que falló cuenta como
|
|
29
|
+
* entregada y no se reintenta nunca; con ella siempre puesta, una que sí se
|
|
30
|
+
* mandó se manda otra vez en cada reintento del evento.
|
|
31
|
+
*
|
|
32
|
+
* **`refIdIn` en el borrado.** `webhookId` se guarda como cadena en unas filas
|
|
33
|
+
* y como `ObjectId` en otras. Filtrar por una sola forma deja la mitad de las
|
|
34
|
+
* entregas huérfanas — que es justo lo que la cascada existe para evitar. El
|
|
35
|
+
* contrato recibe los ids en crudo y la conversión se queda en el gateway,
|
|
36
|
+
* donde vive el problema.
|
|
37
|
+
*
|
|
38
|
+
* ## Cómo viaja
|
|
39
|
+
*
|
|
40
|
+
* Bien: entran y salen datos planos, y `anotar` es «dispara y olvida» —el SDK
|
|
41
|
+
* ya la llama con `.catch()` colgado, sin esperarla—.
|
|
42
|
+
*/
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.REGISTRO_DE_ENTREGAS = void 0;
|
|
45
|
+
/** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
|
|
46
|
+
exports.REGISTRO_DE_ENTREGAS = 'REGISTRO_DE_ENTREGAS';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lo que un nodo apunta y pregunta sobre eventos.
|
|
3
|
+
*
|
|
4
|
+
* ## ⚠️ Esta costura es MUCHO más pequeña de lo que el conteo decía
|
|
5
|
+
*
|
|
6
|
+
* `nodes/` y `common/` tocan el modelo de eventos en seis ficheros y con una
|
|
7
|
+
* superficie grande —`create` ×10, `findById` ×5, `findByIdAndUpdate` ×5,
|
|
8
|
+
* `hydrate`, `aggregate`, incluso `db.db` en crudo—. Con ese número, esto
|
|
9
|
+
* parecía la costura más cara de la fase.
|
|
10
|
+
*
|
|
11
|
+
* Lo es sólo si uno olvida leer el plan. `event-pipeline` y `pipeline-run`
|
|
12
|
+
* **se quedan en el gateway**: este servicio ejecuta nodos, no orquesta
|
|
13
|
+
* corridas. Y `agent-context` también es del gateway. O sea que casi toda esa
|
|
14
|
+
* superficie es del motor consultando SU PROPIA colección de trabajo, y no
|
|
15
|
+
* hay nada que invertir ahí.
|
|
16
|
+
*
|
|
17
|
+
* Lo que de verdad viaja son tres ficheros y tres operaciones:
|
|
18
|
+
*
|
|
19
|
+
* `triggers.service` crea el evento de un empujón externo
|
|
20
|
+
* `scheduled-workflows.processor` crea el de un cron
|
|
21
|
+
* `webhooks.service` cuenta para las estadísticas y borra en
|
|
22
|
+
* cascada
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ Y `common/node-handlers.ts` lo inyectaba sin usarlo NUNCA. Fuera.
|
|
25
|
+
*
|
|
26
|
+
* ## Cómo viaja
|
|
27
|
+
*
|
|
28
|
+
* Bien: entra un objeto plano y sale lo que el llamante lee. De los tres
|
|
29
|
+
* `create`, dos DESCARTAN el resultado y sólo el del cron le lee dos campos.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* En qué estado está un evento.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ Vivía en `events/event.entity.ts`, junto al esquema de Mongoose. Es un
|
|
35
|
+
* enum puro y los nodos lo necesitan, así que se muda aquí y allí queda el
|
|
36
|
+
* reenvío — mismo reparto que `Audited` o `PasoEjecutado`.
|
|
37
|
+
*/
|
|
38
|
+
export declare enum EstadoDeEvento {
|
|
39
|
+
PENDING = "pending",
|
|
40
|
+
DELIVERED = "delivered",
|
|
41
|
+
FAILED = "failed",
|
|
42
|
+
RETRYING = "retrying",
|
|
43
|
+
FILTERED = "filtered",
|
|
44
|
+
SCHEMA_INVALID = "schema_invalid",
|
|
45
|
+
AWAITING_APPROVAL = "awaiting_approval",
|
|
46
|
+
DELAYED = "delayed",
|
|
47
|
+
DEDUPLICATED = "deduplicated",
|
|
48
|
+
RATE_LIMITED = "rate_limited",
|
|
49
|
+
IP_BLOCKED = "ip_blocked",
|
|
50
|
+
SIGNATURE_INVALID = "signature_invalid",
|
|
51
|
+
/** Payload contained a `_file` ref that the malware scanner flagged
|
|
52
|
+
* as infected or scan_failed. Distinct from FAILED so dashboards
|
|
53
|
+
* and stats can highlight security blocks separately from
|
|
54
|
+
* delivery errors. */
|
|
55
|
+
MALICIOUS_FILE = "malicious_file"
|
|
56
|
+
}
|
|
57
|
+
/** Un evento que entra al sistema. */
|
|
58
|
+
export interface EventoEntrante {
|
|
59
|
+
/** El nodo fuente que lo produjo: webhook, trigger o programado. */
|
|
60
|
+
webhookId: string;
|
|
61
|
+
payload: unknown;
|
|
62
|
+
rawBody: string;
|
|
63
|
+
headers: Record<string, string>;
|
|
64
|
+
sourceIp: string;
|
|
65
|
+
status: string;
|
|
66
|
+
/** ⚠️ Opcional: el trigger no lo pone y el esquema tiene su valor por
|
|
67
|
+
* defecto. Exigirlo aquí obligaría a inventárselo en el llamante. */
|
|
68
|
+
eventType?: string;
|
|
69
|
+
attemptCount?: number;
|
|
70
|
+
/**
|
|
71
|
+
* La corrida a la que pertenece.
|
|
72
|
+
*
|
|
73
|
+
* ⚠️ Ausente significa «este evento ABRE una corrida», y entonces la abre
|
|
74
|
+
* con su propio id. No es lo mismo que ponerlo a `null`.
|
|
75
|
+
*/
|
|
76
|
+
correlationId?: string;
|
|
77
|
+
/**
|
|
78
|
+
* La llave con que se reconoce un empujón repetido del proveedor.
|
|
79
|
+
*
|
|
80
|
+
* ⚠️ Gmail, Drive y compañía reenvían la misma notificación más de una vez.
|
|
81
|
+
* Sin esto, un cambio se despacha dos veces.
|
|
82
|
+
*/
|
|
83
|
+
idempotencyKey?: string;
|
|
84
|
+
}
|
|
85
|
+
/** Lo que el llamante lee de un evento recién creado. Dos campos. */
|
|
86
|
+
export interface EventoCreado {
|
|
87
|
+
id: string;
|
|
88
|
+
correlationId?: string;
|
|
89
|
+
}
|
|
90
|
+
export interface RegistroDeEventos {
|
|
91
|
+
/**
|
|
92
|
+
* ⚠️ Devuelve datos planos, no el documento. Se comprobó llamante por
|
|
93
|
+
* llamante: dos de los tres tiran el resultado y el tercero lee `id` y
|
|
94
|
+
* `correlationId` para enhebrar la corrida.
|
|
95
|
+
*/
|
|
96
|
+
crear(evento: EventoEntrante): Promise<EventoCreado>;
|
|
97
|
+
/**
|
|
98
|
+
* Cuántos eventos tiene un webhook, para el `successRate` del panel.
|
|
99
|
+
*
|
|
100
|
+
* ⚠️ `soloEntregados` cambia la pregunta, no la afina: sin él es el
|
|
101
|
+
* denominador y con él el numerador. Y las pruebas NO cuentan —`isTest`
|
|
102
|
+
* queda fuera siempre—, porque un webhook que se probó diez veces tendría
|
|
103
|
+
* un porcentaje que no describe su tráfico real.
|
|
104
|
+
*/
|
|
105
|
+
contarDeWebhook(webhookId: string, opts?: {
|
|
106
|
+
soloEntregados?: boolean;
|
|
107
|
+
}): Promise<number>;
|
|
108
|
+
/**
|
|
109
|
+
* La cascada al borrar webhooks.
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ Los ids van en crudo: `webhookId` se guarda como cadena en unas filas
|
|
112
|
+
* y como `ObjectId` en otras, y esa conversión se queda en el gateway,
|
|
113
|
+
* donde vive el problema. Mismo reparto que el registro de entregas.
|
|
114
|
+
*/
|
|
115
|
+
borrarDeWebhooks(webhookIds: ReadonlyArray<string>): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* ¿Ya se despachó este empujón?
|
|
118
|
+
*
|
|
119
|
+
* ⚠️ Falla ABIERTO y hay que conservarlo: si la consulta revienta devuelve
|
|
120
|
+
* `false` —«no lo he visto»— y el evento se despacha igual. El peor caso es
|
|
121
|
+
* un duplicado aguas abajo; el otro camino es tragarse un cambio real en
|
|
122
|
+
* silencio, que es mucho peor y no deja rastro.
|
|
123
|
+
*/
|
|
124
|
+
yaSeDespacho(webhookId: string, idempotencyKey: string): Promise<boolean>;
|
|
125
|
+
/**
|
|
126
|
+
* Los ids de los eventos de un nodo fuente.
|
|
127
|
+
*
|
|
128
|
+
* ⚠️ Se recogen ANTES de borrarlos: las entregas se indexan por `eventId`,
|
|
129
|
+
* y una vez borrados los eventos no queda nada por lo que resolverlas.
|
|
130
|
+
* Ver `cascade-trigger-events.ts`.
|
|
131
|
+
*/
|
|
132
|
+
idsDeWebhook(webhookId: string): Promise<string[]>;
|
|
133
|
+
}
|
|
134
|
+
/** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
|
|
135
|
+
export declare const REGISTRO_DE_EVENTOS = "REGISTRO_DE_EVENTOS";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Lo que un nodo apunta y pregunta sobre eventos.
|
|
4
|
+
*
|
|
5
|
+
* ## ⚠️ Esta costura es MUCHO más pequeña de lo que el conteo decía
|
|
6
|
+
*
|
|
7
|
+
* `nodes/` y `common/` tocan el modelo de eventos en seis ficheros y con una
|
|
8
|
+
* superficie grande —`create` ×10, `findById` ×5, `findByIdAndUpdate` ×5,
|
|
9
|
+
* `hydrate`, `aggregate`, incluso `db.db` en crudo—. Con ese número, esto
|
|
10
|
+
* parecía la costura más cara de la fase.
|
|
11
|
+
*
|
|
12
|
+
* Lo es sólo si uno olvida leer el plan. `event-pipeline` y `pipeline-run`
|
|
13
|
+
* **se quedan en el gateway**: este servicio ejecuta nodos, no orquesta
|
|
14
|
+
* corridas. Y `agent-context` también es del gateway. O sea que casi toda esa
|
|
15
|
+
* superficie es del motor consultando SU PROPIA colección de trabajo, y no
|
|
16
|
+
* hay nada que invertir ahí.
|
|
17
|
+
*
|
|
18
|
+
* Lo que de verdad viaja son tres ficheros y tres operaciones:
|
|
19
|
+
*
|
|
20
|
+
* `triggers.service` crea el evento de un empujón externo
|
|
21
|
+
* `scheduled-workflows.processor` crea el de un cron
|
|
22
|
+
* `webhooks.service` cuenta para las estadísticas y borra en
|
|
23
|
+
* cascada
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ Y `common/node-handlers.ts` lo inyectaba sin usarlo NUNCA. Fuera.
|
|
26
|
+
*
|
|
27
|
+
* ## Cómo viaja
|
|
28
|
+
*
|
|
29
|
+
* Bien: entra un objeto plano y sale lo que el llamante lee. De los tres
|
|
30
|
+
* `create`, dos DESCARTAN el resultado y sólo el del cron le lee dos campos.
|
|
31
|
+
*/
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.REGISTRO_DE_EVENTOS = exports.EstadoDeEvento = void 0;
|
|
34
|
+
/**
|
|
35
|
+
* En qué estado está un evento.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ Vivía en `events/event.entity.ts`, junto al esquema de Mongoose. Es un
|
|
38
|
+
* enum puro y los nodos lo necesitan, así que se muda aquí y allí queda el
|
|
39
|
+
* reenvío — mismo reparto que `Audited` o `PasoEjecutado`.
|
|
40
|
+
*/
|
|
41
|
+
var EstadoDeEvento;
|
|
42
|
+
(function (EstadoDeEvento) {
|
|
43
|
+
EstadoDeEvento["PENDING"] = "pending";
|
|
44
|
+
EstadoDeEvento["DELIVERED"] = "delivered";
|
|
45
|
+
EstadoDeEvento["FAILED"] = "failed";
|
|
46
|
+
EstadoDeEvento["RETRYING"] = "retrying";
|
|
47
|
+
EstadoDeEvento["FILTERED"] = "filtered";
|
|
48
|
+
EstadoDeEvento["SCHEMA_INVALID"] = "schema_invalid";
|
|
49
|
+
EstadoDeEvento["AWAITING_APPROVAL"] = "awaiting_approval";
|
|
50
|
+
EstadoDeEvento["DELAYED"] = "delayed";
|
|
51
|
+
EstadoDeEvento["DEDUPLICATED"] = "deduplicated";
|
|
52
|
+
EstadoDeEvento["RATE_LIMITED"] = "rate_limited";
|
|
53
|
+
EstadoDeEvento["IP_BLOCKED"] = "ip_blocked";
|
|
54
|
+
EstadoDeEvento["SIGNATURE_INVALID"] = "signature_invalid";
|
|
55
|
+
/** Payload contained a `_file` ref that the malware scanner flagged
|
|
56
|
+
* as infected or scan_failed. Distinct from FAILED so dashboards
|
|
57
|
+
* and stats can highlight security blocks separately from
|
|
58
|
+
* delivery errors. */
|
|
59
|
+
EstadoDeEvento["MALICIOUS_FILE"] = "malicious_file";
|
|
60
|
+
})(EstadoDeEvento || (exports.EstadoDeEvento = EstadoDeEvento = {}));
|
|
61
|
+
/** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
|
|
62
|
+
exports.REGISTRO_DE_EVENTOS = 'REGISTRO_DE_EVENTOS';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Restaurar las entidades de nodo de un snapshot del lienzo.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué esto es un contrato y no una lectura cruda
|
|
5
|
+
*
|
|
6
|
+
* `agent-context` dejó de necesitar las entidades leyendo las colecciones a
|
|
7
|
+
* pelo (api#555). Aquí no vale, y la diferencia es de una palabra: **aquí se
|
|
8
|
+
* escribe**.
|
|
9
|
+
*
|
|
10
|
+
* `model.create` aplica los `default`, el casteo y las validaciones del
|
|
11
|
+
* esquema. Un `insertOne` crudo no aplica ninguno, así que un snapshot viejo
|
|
12
|
+
* restaurado contra un esquema nuevo saldría sin los campos que el esquema
|
|
13
|
+
* habría rellenado — y eso no falla al escribir, falla mucho más tarde, en
|
|
14
|
+
* quien lee. La escritura tiene que ocurrir donde vive el esquema.
|
|
15
|
+
*
|
|
16
|
+
* ## Y por eso la restauración es de los dos lados
|
|
17
|
+
*
|
|
18
|
+
* Un lienzo mezcla nodos de origen —del gateway— con nodos de acción, que se
|
|
19
|
+
* van a hw-nodes. De las cuarenta y nueve claves que un snapshot puede traer, **cinco
|
|
20
|
+
* son del gateway** —los cinco nodos de ORIGEN, que es exactamente la frontera
|
|
21
|
+
* del corte— y **cuarenta y cuatro se van**.
|
|
22
|
+
*
|
|
23
|
+
* Así que los dos lados reciben el snapshot ENTERO y cada uno escribe su
|
|
24
|
+
* parte: el bucle vive en `@hostwebhook/node-sdk` y a cada lado se le pasan sus
|
|
25
|
+
* modelos. Una clave que un lado no posee se salta sin contarla.
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ La caché del lienzo se tira UNA vez, cuando han terminado los dos. Por eso
|
|
28
|
+
* el contrato devuelve los workspaces tocados en vez de invalidar por su
|
|
29
|
+
* cuenta: invalidar antes de que el otro lado haya escrito devuelve el grafo
|
|
30
|
+
* viejo durante 30 s, que es justo el momento en el que nadie se cree que la
|
|
31
|
+
* restauración haya funcionado.
|
|
32
|
+
*/
|
|
33
|
+
export interface RestauracionDeNodos {
|
|
34
|
+
/**
|
|
35
|
+
* Las claves del snapshot que este lado sabe escribir.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ Se pregunta, no se escribe a mano en el gateway: un segundo mapa que se
|
|
38
|
+
* queda viejo es exactamente cómo se perdió `mailchimpAction` del registro de
|
|
39
|
+
* conexiones. El que sabe qué nodos tiene es quien los tiene.
|
|
40
|
+
*/
|
|
41
|
+
clavesQueRestaura(): string[];
|
|
42
|
+
restaurar(peticion: {
|
|
43
|
+
/** El `entities` del snapshot, entero — el otro lado ignora lo que no es suyo. */
|
|
44
|
+
entidades: Record<string, unknown[]>;
|
|
45
|
+
orgId: string;
|
|
46
|
+
/** Quien restaura. La identidad del nodo se rederiva de aquí, no del snapshot. */
|
|
47
|
+
userId: string;
|
|
48
|
+
}): Promise<{
|
|
49
|
+
contadores: Record<string, number>;
|
|
50
|
+
workspacesTocados: string[];
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
export declare const RESTAURACION_DE_NODOS = "RESTAURACION_DE_NODOS";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lo que el motor necesita del comprobador de salud del webhook. UN método.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué
|
|
5
|
+
*
|
|
6
|
+
* `event-pipeline` llama a `HealthCheckerService.startRecovery(webhookId)`
|
|
7
|
+
* cuando un webhook pasa a `down` y tiene la recuperación automática puesta.
|
|
8
|
+
* Es la ÚNICA llamada: medido, `startRecovery` y nada más.
|
|
9
|
+
*
|
|
10
|
+
* El motor viaja a hw-nodes; `webhooks` es uno de los cinco nodos de origen que
|
|
11
|
+
* se quedan en el gateway. Sin esto, el motor arrastra un servicio del gateway
|
|
12
|
+
* entero por un método de cuatro líneas.
|
|
13
|
+
*
|
|
14
|
+
* ## Lo que cruza
|
|
15
|
+
*
|
|
16
|
+
* Un id y nada más. No hay respuesta que esperar: quien llama lo lanza con
|
|
17
|
+
* `void`, porque arrancar la recuperación no puede retrasar el despacho del
|
|
18
|
+
* evento que la disparó.
|
|
19
|
+
*/
|
|
20
|
+
export interface SaludDelWebhook {
|
|
21
|
+
/** Marca el webhook como «recuperándose». No devuelve nada. */
|
|
22
|
+
arrancarRecuperacion(webhookId: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** ⚠️ Cadena y no `Symbol`: un `Symbol` no sobrevive a una frontera de módulo
|
|
25
|
+
* duplicada. */
|
|
26
|
+
export declare const SALUD_DEL_WEBHOOK = "SALUD_DEL_WEBHOOK";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Si un secreto de firma es de esta organización. Una pregunta.
|
|
3
|
+
*
|
|
4
|
+
* Costura de la Fase 2, del lote de las pequeñas. `webhooks.service` hacía
|
|
5
|
+
* `connection.model(SigningSecret.name).collection.findOne(...)` — o sea,
|
|
6
|
+
* consultaba una colección de OTRO dominio a través de la conexión, sólo para
|
|
7
|
+
* comprobar que el id que llega en el cuerpo es suyo.
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ Importaba la clase de la entidad para usar su `.name` como cadena. Eso
|
|
10
|
+
* arrastra el fichero de la entidad entero —con sus decoradores de Mongoose—
|
|
11
|
+
* para leerle una palabra.
|
|
12
|
+
*
|
|
13
|
+
* ## Lo que hay que conservar
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ La comprobación existe porque `signingSecretId` llega del CUERPO de la
|
|
16
|
+
* petición y sólo se validaba con `@IsMongoId()`: sin esto, un id de otra
|
|
17
|
+
* organización pasaba el DTO. Devolver `true` cuando no se sabe abriría justo
|
|
18
|
+
* esa puerta, así que quien lo implemente por red tiene que fallar hacia
|
|
19
|
+
* `false` — al revés que la mayoría de fallbacks de esta fase.
|
|
20
|
+
*/
|
|
21
|
+
export interface SecretosDeFirma {
|
|
22
|
+
/** ⚠️ Ante la duda, `false`. Ver la cabecera. */
|
|
23
|
+
esDeLaOrganizacion(secretoId: string, orgId: string): Promise<boolean>;
|
|
24
|
+
}
|
|
25
|
+
/** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
|
|
26
|
+
export declare const SECRETOS_DE_FIRMA = "SECRETOS_DE_FIRMA";
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lo que un nodo apunta en el registro. Una sola operación.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué
|
|
5
|
+
*
|
|
6
|
+
* Costura de la Fase 2. Después del #459 quedaban 16 ficheros de `nodes/`
|
|
7
|
+
* importando `TelemetryService`, más tres del motor en `common/` —que viaja
|
|
8
|
+
* con ellos—. Los 19 lo usan para **lo mismo**: `emit`.
|
|
9
|
+
*
|
|
10
|
+
* `TelemetryService` arrastra la colección de logs, su conexión de Mongo
|
|
11
|
+
* propia y el gateway de sockets que empuja la fila al panel en vivo. Nada de
|
|
12
|
+
* eso puede vivir en `hw-nodes`: el registro es del gateway, y lo que el nodo
|
|
13
|
+
* necesita es poder DECIR lo que ha pasado.
|
|
14
|
+
*
|
|
15
|
+
* ## Tres métodos, y medidos
|
|
16
|
+
*
|
|
17
|
+
* En `nodes/` el inventario dio **uno solo** —`emit`— en 15 de los 16
|
|
18
|
+
* ficheros. El que sobraba (`ai-nodes.controller`) no lo llamaba: se lo
|
|
19
|
+
* pasaba al probador, y para eso ya existe la bandera `conTelemetria` desde
|
|
20
|
+
* el #458.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ Pero el inventario iba apuntado sólo a `nodes/`, y el motor vive en
|
|
23
|
+
* `common/`, que viaja con ellos. Al compilar salieron dos métodos más:
|
|
24
|
+
* `hasRecentSuccess` y `emitAndWait`. Los dos son de la recuperación de
|
|
25
|
+
* caídas, no del registro: sin ellos un nodo caro se re-ejecuta. Lo cazó tsc,
|
|
26
|
+
* no yo — la lección de siempre, con el barrido mal apuntado.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ Y ahí apareció una regresión MÍA. El #459 le quitó a
|
|
29
|
+
* `code-nodes.controller` el parámetro `telemetry` y su `super()`, pero dejó
|
|
30
|
+
* en pie un `this.telemetry` en la ruta `:id/test` escrita a mano. Desde
|
|
31
|
+
* entonces valía `undefined`, así que un paso de Run Pipeline sobre un Code
|
|
32
|
+
* node **no anotaba nada** — justo el comportamiento que el #459 decía haber
|
|
33
|
+
* conservado. No lo vio tsc porque la clase base del factory está tipada como
|
|
34
|
+
* `=> any`: dentro de una subclase, `this.loquesea` compila.
|
|
35
|
+
*
|
|
36
|
+
* ## Los campos son los que se usan
|
|
37
|
+
*
|
|
38
|
+
* El esquema `TelemetryLog` tiene ~20 campos. Se contaron los que aparecen de
|
|
39
|
+
* verdad en las 67 llamadas de `nodes/` y las 53 de `common/`: son quince.
|
|
40
|
+
* Fuera quedan `timestamp` —lo pone el gateway si falta—, `correlationId` y
|
|
41
|
+
* los dos de voz, que nadie de este lado escribe.
|
|
42
|
+
*
|
|
43
|
+
* ## Cómo viaja
|
|
44
|
+
*
|
|
45
|
+
* Bien: sólo datos planos. `TelemetryLog` es una clase con decoradores pero
|
|
46
|
+
* sin métodos, así que no hay documento vivo que aplanar; aun así el contrato
|
|
47
|
+
* lo declara aparte para no arrastrar `@nestjs/mongoose` a `hw-nodes`.
|
|
48
|
+
*
|
|
49
|
+
* ⚠️ `emit` devuelve `void` y no espera: por dentro hace `create(...).catch()`.
|
|
50
|
+
* Eso es deliberado y hay que conservarlo cuando esto sea una llamada de red —
|
|
51
|
+
* un nodo no puede quedarse esperando a que se escriba una fila de log, y
|
|
52
|
+
* menos aún fallar porque no se pudo escribir.
|
|
53
|
+
*/
|
|
54
|
+
/** Nivel de la fila. Los mismos cuatro del esquema. */
|
|
55
|
+
export type NivelDeRegistro = 'info' | 'warn' | 'error' | 'debug';
|
|
56
|
+
/**
|
|
57
|
+
* Una fila del registro.
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ Los quince campos MEDIDOS, ni uno más. Si hace falta otro, se añade
|
|
60
|
+
* aquí: tsc rechaza una clave desconocida en un literal, así que no se puede
|
|
61
|
+
* colar por accidente.
|
|
62
|
+
*/
|
|
63
|
+
export interface EntradaDeTelemetria {
|
|
64
|
+
organizationId: string;
|
|
65
|
+
level: NivelDeRegistro;
|
|
66
|
+
/** Cadena abierta, no un enum: un tipo de nodo nuevo funciona solo. */
|
|
67
|
+
source: string;
|
|
68
|
+
message: string;
|
|
69
|
+
/** `pipeline_test` distingue una corrida de una prueba suelta. */
|
|
70
|
+
trigger?: 'event' | 'pipeline_test';
|
|
71
|
+
eventId?: string;
|
|
72
|
+
webhookId?: string;
|
|
73
|
+
webhookName?: string;
|
|
74
|
+
nodeId?: string;
|
|
75
|
+
nodeName?: string;
|
|
76
|
+
scheduledWorkflowId?: string;
|
|
77
|
+
durationMs?: number;
|
|
78
|
+
statusCode?: number;
|
|
79
|
+
success?: boolean;
|
|
80
|
+
metadata?: Record<string, unknown>;
|
|
81
|
+
}
|
|
82
|
+
/** Apuntar en el registro, y preguntarle a la recuperación de caídas. */
|
|
83
|
+
export interface Telemetria {
|
|
84
|
+
/**
|
|
85
|
+
* ⚠️ `void`, y no espera. Ver la nota de arriba: una implementación por red
|
|
86
|
+
* tiene que seguir sin bloquear y sin propagar su propio fallo.
|
|
87
|
+
*/
|
|
88
|
+
emit(entrada: EntradaDeTelemetria): void;
|
|
89
|
+
/**
|
|
90
|
+
* Lo mismo, pero espera a que la fila esté escrita.
|
|
91
|
+
*
|
|
92
|
+
* ⚠️ No es un capricho de consistencia: lo usan los handlers que declaran
|
|
93
|
+
* `awaitTelemetry: true`, que son los caros (llamadas a un LLM). Si un
|
|
94
|
+
* SIGTERM cae entre el handler y la escritura, al recuperar
|
|
95
|
+
* `hasRecentSuccess` dice que no y la operación **se repite** — y al
|
|
96
|
+
* usuario se la cobran dos veces. Una implementación por red tiene que
|
|
97
|
+
* seguir esperando de verdad aquí.
|
|
98
|
+
*/
|
|
99
|
+
emitAndWait(entrada: EntradaDeTelemetria): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* ¿Ya corrió con éxito este nodo para este evento hace poco?
|
|
102
|
+
*
|
|
103
|
+
* ⚠️ Falla ABIERTO: si la consulta revienta devuelve `false`, o sea «no
|
|
104
|
+
* corrió», y el nodo se re-ejecuta. Es lo correcto —repetir es más seguro
|
|
105
|
+
* que saltarse un paso— pero hay que conservarlo: una implementación por
|
|
106
|
+
* red que devolviera `true` al fallar dejaría pasos sin ejecutar en
|
|
107
|
+
* silencio, que es el fallo que no se ve.
|
|
108
|
+
*/
|
|
109
|
+
hasRecentSuccess(eventId: string, nodeId: string, withinMs?: number): Promise<boolean>;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2.
|
|
113
|
+
*/
|
|
114
|
+
export declare const TELEMETRIA = "TELEMETRIA";
|