From bd4f0cc364f75963145fdbf6f4e348d76073ec59 Mon Sep 17 00:00:00 2001 From: brobert Date: Sat, 25 Oct 2025 11:46:12 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20a=C3=B1adir=20m=C3=A9tricas=20de=20onbo?= =?UTF-8?q?arding=20y=20alias;=20recalcular=20tras=20comandos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: aider (openrouter/openai/gpt-5) --- src/services/command.ts | 16 +++++++++- src/services/response-queue.ts | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/services/command.ts b/src/services/command.ts index 9d7981d..9e3896e 100644 --- a/src/services/command.ts +++ b/src/services/command.ts @@ -202,6 +202,18 @@ export class CommandService { 'configurar': 'configurar' }; const action = ACTION_ALIASES[rawAction] || rawAction; + // Métrica: uso de alias (info/mias/todas) + try { + if (rawAction === 'info') { + Metrics.inc('commands_alias_used_total', 1, { action: 'info' }); + } else if (rawAction === 'mias' || rawAction === 'mías') { + Metrics.inc('commands_alias_used_total', 1, { action: 'mias' }); + } else if (rawAction === 'todas' || rawAction === 'todos') { + Metrics.inc('commands_alias_used_total', 1, { action: 'todas' }); + } + } catch {} + // Refrescar métricas agregadas de onboarding tras cualquier comando (para conversión) + try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {} // Usar formatDDMM desde utils/formatting @@ -300,6 +312,7 @@ export class CommandService { // En grupos: no listamos; responder por DM con transición if (isGroupId(context.groupId)) { + try { Metrics.inc('ver_dm_transition_total'); } catch {} return [{ recipient: context.sender, message: 'No respondo en grupos. Tus tareas: /t mias · Todas: /t todas · Info: /t info · Web: /t web' @@ -1295,7 +1308,8 @@ export class CommandService { const cooldownRaw = Number(process.env.ONBOARDING_DM_COOLDOWN_DAYS); const cooldownDays = Number.isFinite(cooldownRaw) && cooldownRaw >= 0 ? Math.floor(cooldownRaw) : 14; - const delay2 = 5000 + Math.floor(Math.random() * 5001); // 5–10s + const delayEnv = Number(process.env.ONBOARDING_BUNDLE_DELAY_MS); + const delay2 = Number.isFinite(delayEnv) && delayEnv >= 0 ? Math.floor(delayEnv) : 5000 + Math.floor(Math.random() * 5001); // 5–10s por defecto const groupLabel = GroupSyncService.activeGroupsCache.get(gid) || gid; const codeStr = String(displayCode); diff --git a/src/services/response-queue.ts b/src/services/response-queue.ts index 1305a6a..b9a3e21 100644 --- a/src/services/response-queue.ts +++ b/src/services/response-queue.ts @@ -415,6 +415,15 @@ export const ResponseQueue = { updated_at = strftime('%Y-%m-%d %H:%M:%f', 'now') WHERE id = ? `).run(statusCode ?? null, id); + // Recalcular métricas agregadas de onboarding si aplica + try { + const row = this.dbInstance.prepare(`SELECT metadata FROM response_queue WHERE id = ?`).get(id) as any; + let meta: any = null; + try { meta = row?.metadata ? JSON.parse(String(row.metadata)) : null; } catch {} + if (meta && meta.kind === 'onboarding') { + this.setOnboardingAggregatesMetrics(); + } + } catch {} }, markFailed(id: number, errorMsg: string, statusCode?: number, attempts?: number) { @@ -444,6 +453,50 @@ export const ResponseQueue = { `).run(nextAttempts, nextAttemptAt, msg, statusCode ?? null, id); }, + setOnboardingAggregatesMetrics(): void { + try { + // Total de mensajes de onboarding enviados + const sentRow = this.dbInstance.prepare(` + SELECT COUNT(*) AS c + FROM response_queue + WHERE status = 'sent' AND metadata LIKE '%"kind":"onboarding"%' + `).get() as any; + const sentAbs = Number(sentRow?.c || 0); + + // Destinatarios únicos con al menos 1 onboarding enviado + const rcptRow = this.dbInstance.prepare(` + SELECT COUNT(DISTINCT recipient) AS c + FROM response_queue + WHERE status = 'sent' AND metadata LIKE '%"kind":"onboarding"%' + `).get() as any; + const recipientsAbs = Number(rcptRow?.c || 0); + + // Usuarios convertidos: last_command_at > primer onboarding enviado + const convRow = this.dbInstance.prepare(` + SELECT COUNT(*) AS c + FROM users u + JOIN ( + SELECT recipient, MIN(created_at) AS first_at + FROM response_queue + WHERE status = 'sent' AND metadata LIKE '%"kind":"onboarding"%' + GROUP BY recipient + ) f ON f.recipient = u.id + WHERE u.last_command_at IS NOT NULL + AND u.last_command_at > f.first_at + `).get() as any; + const convertedAbs = Number(convRow?.c || 0); + + const rate = recipientsAbs > 0 ? Math.max(0, Math.min(1, convertedAbs / recipientsAbs)) : 0; + + try { Metrics.set('onboarding_dm_sent_abs', sentAbs); } catch {} + try { Metrics.set('onboarding_recipients_abs', recipientsAbs); } catch {} + try { Metrics.set('onboarding_converted_users_abs', convertedAbs); } catch {} + try { Metrics.set('onboarding_conversion_rate', rate); } catch {} + } catch { + // no-op + } + }, + async workerLoop(workerId: number) { while (this._running) { try {