@saulwade/swl-ses 1.3.8 → 1.4.1

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.
Files changed (148) hide show
  1. package/CLAUDE.md +15 -6
  2. package/README.md +15 -14
  3. package/agentes/nemesis-auditor-swl.md +161 -0
  4. package/bin/swl-mcp-server.js +187 -187
  5. package/bin/swl-webhook-server.js +198 -0
  6. package/comandos/swl/.evolved.json +22 -22
  7. package/comandos/swl/adoptar-proyecto.md +21 -1
  8. package/comandos/swl/claudemd.md +14 -1
  9. package/comandos/swl/contribuir.md +233 -233
  10. package/comandos/swl/exportar-vault.md +108 -0
  11. package/comandos/swl/nemesis.md +122 -0
  12. package/comandos/swl/nuevo-proyecto.md +24 -2
  13. package/comandos/swl/salud.md +34 -0
  14. package/comandos/swl/verificar.md +45 -0
  15. package/gateway/adapters/base.js +109 -0
  16. package/gateway/adapters/discord.js +167 -0
  17. package/gateway/adapters/email.js +221 -0
  18. package/gateway/adapters/slack.js +192 -0
  19. package/gateway/adapters/telegram.js +183 -0
  20. package/gateway/adapters/webhook.js +113 -0
  21. package/gateway/adapters/whatsapp.js +214 -0
  22. package/gateway/agent-executor.js +322 -0
  23. package/gateway/command-relay.js +271 -0
  24. package/gateway/cron/jobs.js +263 -0
  25. package/gateway/cron/scheduler.js +322 -0
  26. package/gateway/cron/store.js +335 -0
  27. package/gateway/index.js +320 -0
  28. package/gateway/lib/event-channel.js +191 -0
  29. package/gateway/session.js +131 -0
  30. package/gateway/webhook-server.js +324 -0
  31. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  32. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  33. package/habilidades/build-errors-nextjs/SKILL.md +55 -1
  34. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  35. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  36. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  37. package/habilidades/eval-framework/SKILL.md +212 -212
  38. package/habilidades/extractor-de-aprendizajes/SKILL.md +20 -10
  39. package/habilidades/feynman-auditor-swl/SKILL.md +123 -0
  40. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -0
  41. package/habilidades/harness-claude-code/SKILL.md +299 -299
  42. package/habilidades/infra-github-actions/SKILL.md +166 -166
  43. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  44. package/habilidades/manejo-errores/.evolved.json +8 -8
  45. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  46. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  47. package/habilidades/nextjs-testing/SKILL.md +89 -5
  48. package/habilidades/node-experto/SKILL.md +37 -1
  49. package/habilidades/patrones-python/SKILL.md +229 -229
  50. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  51. package/habilidades/planear-fase/SKILL.md +319 -319
  52. package/habilidades/react-experto/SKILL.md +45 -4
  53. package/habilidades/release-semver/.evolved.json +8 -8
  54. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -0
  55. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -0
  56. package/habilidades/tdd-workflow/SKILL.md +36 -4
  57. package/habilidades/testing-python/SKILL.md +340 -340
  58. package/habilidades/web-fetcher-routing/SKILL.md +75 -0
  59. package/hooks/claudemd-bloat-detector.js +161 -161
  60. package/hooks/inyeccion-contexto.js +8 -3
  61. package/hooks/lib/agent-routing.js +107 -107
  62. package/hooks/lib/auto-consolidator.js +335 -335
  63. package/hooks/lib/error-classifier.js +308 -308
  64. package/hooks/lib/merkle-audit.js +96 -96
  65. package/hooks/lib/provenance-tracker.js +191 -191
  66. package/hooks/lib/rate-limit-ip.js +177 -0
  67. package/hooks/lib/rate-limit-tracker.js +253 -253
  68. package/hooks/lib/resource-quota.js +122 -122
  69. package/hooks/lib/retry-jitter.js +165 -165
  70. package/hooks/lib/security-net.js +201 -0
  71. package/hooks/lib/skill-auditor.js +588 -588
  72. package/hooks/lib/sync-status.js +228 -228
  73. package/hooks/lib/taint-tracker.js +107 -107
  74. package/hooks/lib/text-similarity.js +241 -241
  75. package/hooks/lib/toon-compressor.js +245 -245
  76. package/hooks/lib/webhook-dedup.js +184 -0
  77. package/hooks/lib/webhook-verify.js +123 -0
  78. package/hooks/proteccion-rutas.js +120 -15
  79. package/hooks/registro-turnos.js +209 -209
  80. package/hooks/sugerir-regenerar-inventario.js +170 -170
  81. package/hooks/validar-formato-post-subagente.js +140 -140
  82. package/hooks/validar-memoria-hook.js +218 -218
  83. package/instintos/prompt-appendices.yaml +57 -57
  84. package/manifiestos/agent-output-schemas.json +57 -57
  85. package/manifiestos/modulos.json +31 -0
  86. package/manifiestos/skills-lock.json +1114 -1093
  87. package/package.json +6 -4
  88. package/plantillas/auditor-veto-template.md +105 -105
  89. package/plantillas/github-workflows/README.md +47 -47
  90. package/plantillas/github-workflows/release-please.yml +44 -44
  91. package/plantillas/github-workflows/swl-ci.yml +107 -107
  92. package/plantillas/github-workflows/swl-security.yml +51 -51
  93. package/plugin.json +2 -2
  94. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  95. package/reglas/arreglar-al-detectar.md +147 -147
  96. package/reglas/fragmentos-compartidos.md +152 -152
  97. package/reglas/harness-claude-code.md +213 -213
  98. package/reglas/usar-context7.md +226 -226
  99. package/reglas/usar-sistema-swl.md +251 -0
  100. package/schemas/diary-entry.schema.json +80 -80
  101. package/scripts/audit-tools/audit-history.js +330 -0
  102. package/scripts/audit-tools/bundle-tracker.js +290 -0
  103. package/scripts/audit-tools/canary-monitor.js +352 -0
  104. package/scripts/audit-tools/code-profiler.js +605 -0
  105. package/scripts/audit-tools/dep-doctor.js +320 -0
  106. package/scripts/audit-tools/env-validator.js +206 -0
  107. package/scripts/audit-tools/lib/fs-walk.js +48 -0
  108. package/scripts/audit-tools/lib/output.js +23 -0
  109. package/scripts/audit-tools/migration-checker.js +392 -0
  110. package/scripts/audit-tools/pentest-scanner.js +1436 -0
  111. package/scripts/benchmark-memoria.js +167 -167
  112. package/scripts/comandos/skills.js +251 -2
  113. package/scripts/configurar-branch-protection.js +418 -418
  114. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  115. package/scripts/field-report.js +199 -199
  116. package/scripts/generar-checklists-consolidados.js +273 -273
  117. package/scripts/generar-inventario.js +420 -420
  118. package/scripts/generar-matriz-lenguajes.js +271 -271
  119. package/scripts/lib/artefactos-python.js +43 -43
  120. package/scripts/lib/benchmark-metrics.js +160 -160
  121. package/scripts/lib/budget-enforcer.js +252 -252
  122. package/scripts/lib/configurar-ci.js +380 -380
  123. package/scripts/lib/contadores-inventario.js +217 -217
  124. package/scripts/lib/detectar-stack-detallado.js +307 -307
  125. package/scripts/lib/diary-entry.js +234 -234
  126. package/scripts/lib/eval-metrics-store.js +218 -218
  127. package/scripts/lib/eval-quality.js +171 -171
  128. package/scripts/lib/eval-schemas.js +144 -144
  129. package/scripts/lib/eval-self-correct.js +106 -106
  130. package/scripts/lib/eval-validator.js +185 -185
  131. package/scripts/lib/jaccard-similarity.js +98 -98
  132. package/scripts/lib/longmemeval-runner.js +125 -125
  133. package/scripts/lib/npm-version.js +261 -261
  134. package/scripts/lib/paquetes-conocidos.js +50 -50
  135. package/scripts/lib/prompt-builder.js +264 -264
  136. package/scripts/lib/rrf-fusion.js +175 -175
  137. package/scripts/lib/scoring-instintos.js +277 -277
  138. package/scripts/lib/semantic-search.js +252 -252
  139. package/scripts/limpiar-artefactos-python.js +131 -131
  140. package/scripts/mcp-server/README.md +128 -128
  141. package/scripts/mcp-server/handlers.js +206 -206
  142. package/scripts/migrar-csv-a-array.js +168 -168
  143. package/scripts/migrar-fase-dominio.js +201 -201
  144. package/scripts/publicar.js +511 -511
  145. package/scripts/run-eval.js +141 -141
  146. package/scripts/validar-manifest.js +195 -195
  147. package/scripts/validar-userland-vacio.js +110 -110
  148. package/scripts/verificar-release.js +110 -0
@@ -0,0 +1,324 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * webhook-server.js — Servidor HTTP local para webhooks entrantes firmados.
5
+ *
6
+ * Orquesta las tres librerías de las fases anteriores:
7
+ *
8
+ * webhook-verify → valida HMAC SHA-256 (GitHub) o Bearer (generic)
9
+ * rate-limit-ip → token bucket per-IP
10
+ * webhook-dedup → idempotencia por event-id en ledger JSONL
11
+ *
12
+ * Endpoints:
13
+ *
14
+ * POST /webhooks/github — requiere X-Hub-Signature-256 + secret
15
+ * POST /webhooks/generic — requiere Authorization: Bearer + secret
16
+ * GET /healthz — liveness (sin auth)
17
+ * * — 404
18
+ *
19
+ * Pipeline por request:
20
+ *
21
+ * 1. IP allowlist (si está configurada)
22
+ * 2. Rate limit per-IP (barato; aborta antes de HMAC costoso)
23
+ * 3. Lectura de body con cap de tamaño
24
+ * 4. HMAC verify (GitHub) o Bearer verify (generic)
25
+ * 5. Extracción de event-id (X-GitHub-Delivery o X-Request-ID o hash del body)
26
+ * 6. Dedup (rechazo idempotente: 200 OK pero no escribe)
27
+ * 7. Escritura a .planning/inbox/cmd-*.json (schema compatible con /swl:inbox)
28
+ *
29
+ * Diseño:
30
+ *
31
+ * - `http` nativo de Node, sin Express ni Fastify (zero-deps).
32
+ * - Función factory `crearServidor(deps)` que retorna http.Server sin
33
+ * arrancar. El bootstrap CLI (Fase 4) llama `.listen()`. Esto permite
34
+ * tests con puerto efímero `0` y dependencias mock.
35
+ * - Configuración 100 % vía objeto `deps` inyectado — sin lectura directa
36
+ * de process.env (eso lo hace el bootstrap CLI).
37
+ * - Bind por defecto `127.0.0.1` (ADR-0017 decisión #1). Cambiar requiere
38
+ * intencionalidad explícita en el bootstrap.
39
+ *
40
+ * @module gateway/webhook-server
41
+ */
42
+
43
+ const http = require('http');
44
+ const crypto = require('crypto');
45
+ const fs = require('fs');
46
+ const path = require('path');
47
+
48
+ const { verifyGithubSignature, verifyBearer } = require('../hooks/lib/webhook-verify');
49
+
50
+ const HEADER_GITHUB_SIG = 'x-hub-signature-256';
51
+ const HEADER_GITHUB_DELIVERY = 'x-hub-delivery';
52
+ const HEADER_GITHUB_DELIVERY_ALT = 'x-github-delivery';
53
+ const HEADER_GITHUB_EVENT = 'x-github-event';
54
+ const HEADER_AUTHORIZATION = 'authorization';
55
+ const HEADER_REQUEST_ID = 'x-request-id';
56
+
57
+ /**
58
+ * Crea un servidor HTTP listo para arrancar con `.listen()`.
59
+ *
60
+ * @param {object} deps Dependencias inyectadas.
61
+ * @param {string} deps.inboxDir Ruta absoluta al directorio inbox.
62
+ * @param {object} deps.dedup Instancia de WebhookDedup.
63
+ * @param {object} deps.rateLimiter Instancia de RateLimiterIP.
64
+ * @param {string|null} deps.githubSecret Secreto HMAC (null = endpoint deshabilitado).
65
+ * @param {string|null} deps.bearerSecret Token Bearer (null = endpoint deshabilitado).
66
+ * @param {number} deps.maxPayloadBytes Tope de tamaño del body.
67
+ * @param {Array<string>|null} deps.allowIps Lista de IPs permitidas (null = todas).
68
+ * @param {function} [deps.logger] Función de logging (default: console.error).
69
+ * @returns {http.Server}
70
+ */
71
+ function crearServidor(deps) {
72
+ validarDeps(deps);
73
+ const logger = deps.logger || ((nivel, msg, extra) => {
74
+ const linea = JSON.stringify({ nivel, msg, ...(extra || {}), ts: new Date().toISOString() });
75
+ if (nivel === 'error' || nivel === 'warn') console.error(linea);
76
+ else console.log(linea);
77
+ });
78
+
79
+ const server = http.createServer((req, res) => {
80
+ manejarRequest(req, res, deps, logger).catch(err => {
81
+ logger('error', 'manejarRequest threw', { err: err.message });
82
+ enviarRespuesta(res, 500, { error: 'internal-error' });
83
+ });
84
+ });
85
+
86
+ return server;
87
+ }
88
+
89
+ async function manejarRequest(req, res, deps, logger) {
90
+ const url = req.url || '';
91
+ const metodo = req.method || 'GET';
92
+ const ip = obtenerIp(req);
93
+
94
+ // 1. Healthz (sin auth, sin rate limit)
95
+ if (metodo === 'GET' && url === '/healthz') {
96
+ return enviarRespuesta(res, 200, { status: 'ok' });
97
+ }
98
+
99
+ // 2. Solo POST para endpoints de webhook
100
+ if (metodo !== 'POST') {
101
+ return enviarRespuesta(res, 404, { error: 'not-found' });
102
+ }
103
+
104
+ // 3. Path conocido
105
+ const proveedor = identificarProveedor(url);
106
+ if (!proveedor) {
107
+ return enviarRespuesta(res, 404, { error: 'not-found' });
108
+ }
109
+
110
+ // 4. IP allowlist
111
+ if (deps.allowIps && deps.allowIps.length > 0 && !ipPermitida(ip, deps.allowIps)) {
112
+ logger('warn', 'ip-blocked', { ip, url });
113
+ return enviarRespuesta(res, 403, { error: 'forbidden' });
114
+ }
115
+
116
+ // 5. Rate limit
117
+ if (!deps.rateLimiter.permite(ip)) {
118
+ logger('warn', 'rate-limit', { ip, url });
119
+ return enviarRespuesta(res, 429, { error: 'rate-limit-exceeded' });
120
+ }
121
+
122
+ // 6. Endpoint habilitado (secreto configurado)
123
+ const secretoRequerido = proveedor === 'github' ? deps.githubSecret : deps.bearerSecret;
124
+ if (!secretoRequerido) {
125
+ logger('warn', 'endpoint-disabled', { proveedor });
126
+ return enviarRespuesta(res, 404, { error: 'not-found' });
127
+ }
128
+
129
+ // 7. Leer body con cap
130
+ let body;
131
+ try {
132
+ body = await leerBody(req, deps.maxPayloadBytes);
133
+ } catch (err) {
134
+ if (err.code === 'PAYLOAD_TOO_LARGE') {
135
+ return enviarRespuesta(res, 413, { error: 'payload-too-large' });
136
+ }
137
+ logger('error', 'read-body-error', { err: err.message });
138
+ return enviarRespuesta(res, 400, { error: 'bad-request' });
139
+ }
140
+
141
+ // 8. Verify firma
142
+ const autorizado = proveedor === 'github'
143
+ ? verifyGithubSignature(body, req.headers[HEADER_GITHUB_SIG], deps.githubSecret)
144
+ : verifyBearer(req.headers[HEADER_AUTHORIZATION], deps.bearerSecret);
145
+
146
+ if (!autorizado) {
147
+ logger('warn', 'unauthorized', { ip, proveedor });
148
+ return enviarRespuesta(res, 401, { error: 'unauthorized' });
149
+ }
150
+
151
+ // 9. Extraer event-id
152
+ const eventId = extraerEventId(req, body, proveedor);
153
+
154
+ // 10. Dedup
155
+ if (deps.dedup.yaVisto(eventId)) {
156
+ logger('info', 'duplicate', { proveedor, eventId });
157
+ return enviarRespuesta(res, 200, { status: 'duplicate', event_id: eventId });
158
+ }
159
+
160
+ // 11. Escribir al inbox
161
+ let bodyParseado;
162
+ try {
163
+ bodyParseado = JSON.parse(body.toString('utf8'));
164
+ } catch (_) {
165
+ // Body no es JSON — guardarlo como string
166
+ bodyParseado = body.toString('utf8');
167
+ }
168
+
169
+ const eventoGithub = req.headers[HEADER_GITHUB_EVENT];
170
+ const cmdId = `cmd-${Date.now().toString(36)}-${crypto.randomBytes(3).toString('hex')}`;
171
+ const cmd = {
172
+ id: cmdId,
173
+ platform: 'webhook',
174
+ userId: proveedor,
175
+ userName: `webhook-${proveedor}`,
176
+ texto: resumirEvento(proveedor, eventoGithub, bodyParseado),
177
+ recibidoEn: new Date().toISOString(),
178
+ estado: 'pending',
179
+ chatId: null,
180
+ comando: null,
181
+ webhookProvider: proveedor,
182
+ webhookEventId: eventId,
183
+ webhookEventType: eventoGithub || null,
184
+ webhookBody: bodyParseado,
185
+ };
186
+
187
+ try {
188
+ fs.mkdirSync(deps.inboxDir, { recursive: true });
189
+ fs.writeFileSync(path.join(deps.inboxDir, `${cmdId}.json`), JSON.stringify(cmd, null, 2), 'utf8');
190
+ deps.dedup.registrar(eventId, proveedor);
191
+ } catch (err) {
192
+ logger('error', 'inbox-write-error', { err: err.message });
193
+ return enviarRespuesta(res, 500, { error: 'write-error' });
194
+ }
195
+
196
+ logger('info', 'accepted', { proveedor, eventId, cmdId });
197
+ return enviarRespuesta(res, 200, { status: 'accepted', event_id: eventId, cmd_id: cmdId });
198
+ }
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // Helpers
202
+ // ---------------------------------------------------------------------------
203
+
204
+ function validarDeps(deps) {
205
+ if (!deps || typeof deps !== 'object') throw new Error('webhook-server: deps requerido');
206
+ if (!deps.inboxDir || typeof deps.inboxDir !== 'string') {
207
+ throw new Error('webhook-server: deps.inboxDir requerido');
208
+ }
209
+ if (!deps.dedup || typeof deps.dedup.yaVisto !== 'function') {
210
+ throw new Error('webhook-server: deps.dedup inválido');
211
+ }
212
+ if (!deps.rateLimiter || typeof deps.rateLimiter.permite !== 'function') {
213
+ throw new Error('webhook-server: deps.rateLimiter inválido');
214
+ }
215
+ if (!Number.isFinite(deps.maxPayloadBytes) || deps.maxPayloadBytes <= 0) {
216
+ throw new Error('webhook-server: deps.maxPayloadBytes requerido y positivo');
217
+ }
218
+ }
219
+
220
+ function identificarProveedor(url) {
221
+ // url puede traer query string; comparar solo el path
222
+ const pathOnly = url.split('?')[0];
223
+ if (pathOnly === '/webhooks/github') return 'github';
224
+ if (pathOnly === '/webhooks/generic') return 'generic';
225
+ return null;
226
+ }
227
+
228
+ function obtenerIp(req) {
229
+ // En este servidor sin proxy, socket.remoteAddress es la IP real.
230
+ // Si se pone detrás de reverse proxy, hay que leer X-Forwarded-For — pero
231
+ // eso es responsabilidad del proxy y requiere config explícita (no implementado).
232
+ const ip = req.socket && req.socket.remoteAddress;
233
+ if (!ip) return '0.0.0.0';
234
+ // Normalizar IPv4-mapped IPv6 (::ffff:127.0.0.1 → 127.0.0.1)
235
+ return ip.startsWith('::ffff:') ? ip.slice(7) : ip;
236
+ }
237
+
238
+ function ipPermitida(ip, allowIps) {
239
+ // Comparación exacta. CIDR queda fuera de scope de Fase 3 — si se necesita,
240
+ // el bootstrap usa una librería de CIDR matching. Aquí: equality match.
241
+ return allowIps.includes(ip);
242
+ }
243
+
244
+ function leerBody(req, maxBytes) {
245
+ return new Promise((resolve, reject) => {
246
+ const chunks = [];
247
+ let total = 0;
248
+ let abortado = false;
249
+
250
+ req.on('data', chunk => {
251
+ if (abortado) return;
252
+ total += chunk.length;
253
+ if (total > maxBytes) {
254
+ abortado = true;
255
+ // Pausar la lectura — NO destruir el socket. Destruir cerraría la
256
+ // conexión antes de que el handler pueda enviar la respuesta 413,
257
+ // causando ECONNRESET en el cliente. Con pause, el socket queda
258
+ // vivo, res.end() envía la respuesta y luego Node cierra el socket.
259
+ req.pause();
260
+ const err = new Error('payload too large');
261
+ err.code = 'PAYLOAD_TOO_LARGE';
262
+ return reject(err);
263
+ }
264
+ chunks.push(chunk);
265
+ });
266
+ req.on('end', () => {
267
+ if (!abortado) resolve(Buffer.concat(chunks));
268
+ });
269
+ req.on('error', err => {
270
+ if (!abortado) reject(err);
271
+ });
272
+ });
273
+ }
274
+
275
+ function extraerEventId(req, body, proveedor) {
276
+ if (proveedor === 'github') {
277
+ const id = req.headers[HEADER_GITHUB_DELIVERY] || req.headers[HEADER_GITHUB_DELIVERY_ALT];
278
+ if (id) return String(id);
279
+ }
280
+ const reqId = req.headers[HEADER_REQUEST_ID];
281
+ if (reqId) return String(reqId);
282
+ // Fallback: hash del body. Mismo body = mismo id = idempotente.
283
+ return 'sha256-' + crypto.createHash('sha256').update(body).digest('hex');
284
+ }
285
+
286
+ function resumirEvento(proveedor, eventoGithub, body) {
287
+ if (proveedor === 'github' && eventoGithub) {
288
+ // Resumen útil para que /swl:inbox decida qué hacer
289
+ const ref = body && typeof body === 'object' ? body.ref : null;
290
+ const action = body && typeof body === 'object' ? body.action : null;
291
+ if (ref) return `github:${eventoGithub} ${ref}`;
292
+ if (action) return `github:${eventoGithub} ${action}`;
293
+ return `github:${eventoGithub}`;
294
+ }
295
+ if (proveedor === 'generic') {
296
+ if (body && typeof body === 'object' && body.command) {
297
+ return `generic:${String(body.command)}`;
298
+ }
299
+ const raw = typeof body === 'string' ? body : JSON.stringify(body);
300
+ return `generic:${raw.slice(0, 200)}`;
301
+ }
302
+ return `${proveedor}:event`;
303
+ }
304
+
305
+ function enviarRespuesta(res, status, body) {
306
+ const payload = JSON.stringify(body);
307
+ res.writeHead(status, {
308
+ 'Content-Type': 'application/json',
309
+ 'Content-Length': Buffer.byteLength(payload),
310
+ });
311
+ res.end(payload);
312
+ }
313
+
314
+ module.exports = {
315
+ crearServidor,
316
+ // exportar helpers para tests de unidad si crece la necesidad
317
+ _internals: {
318
+ identificarProveedor,
319
+ obtenerIp,
320
+ ipPermitida,
321
+ extraerEventId,
322
+ resumirEvento,
323
+ },
324
+ };