From cddb45469259925602d6afebd40c27ffb76f6e13 Mon Sep 17 00:00:00 2001 From: brobert Date: Tue, 21 Oct 2025 00:04:35 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20encolar=20reacci=C3=B3n=20=E2=9C=85=20a?= =?UTF-8?q?l=20completar=20tarea=20dentro=20TTL=20y=20filtrado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: aider (openrouter/openai/gpt-5) --- src/tasks/service.ts | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/tasks/service.ts b/src/tasks/service.ts index 0a87a88..fb7a2a9 100644 --- a/src/tasks/service.ts +++ b/src/tasks/service.ts @@ -2,6 +2,8 @@ import type { Database } from 'bun:sqlite'; import { db, ensureUserExists } from '../db'; import { AllowedGroups } from '../services/allowed-groups'; import { isGroupId } from '../utils/whatsapp'; +import { ResponseQueue } from '../services/response-queue'; +import { Metrics } from '../services/metrics'; type CreateTaskInput = { description: string; @@ -278,6 +280,53 @@ export class TaskService { `) .run(ensured, taskId); + // Fase 2: reacción ✅ al completar dentro del TTL y con gating + try { + const rxEnabled = String(process.env.REACTIONS_ENABLED || 'false').toLowerCase(); + const enabled = ['true','1','yes','on'].includes(rxEnabled); + if (enabled) { + const origin = this.dbInstance.prepare(` + SELECT chat_id, message_id, created_at + FROM task_origins + WHERE task_id = ? + `).get(taskId) as any; + + if (origin && origin.chat_id && origin.message_id) { + const chatId = String(origin.chat_id); + const scope = String(process.env.REACTIONS_SCOPE || 'groups').toLowerCase(); + if (scope === 'all' || isGroupId(chatId)) { + // TTL desde REACTIONS_TTL_DAYS (usar tal cual; default 14 si inválido) + const ttlDaysEnv = Number(process.env.REACTIONS_TTL_DAYS); + const ttlDays = Number.isFinite(ttlDaysEnv) && ttlDaysEnv > 0 ? ttlDaysEnv : 14; + const maxAgeMs = ttlDays * 24 * 60 * 60 * 1000; + + const createdRaw = String(origin.created_at || ''); + const createdIso = createdRaw.includes('T') ? createdRaw : (createdRaw.replace(' ', 'T') + 'Z'); + const createdMs = Date.parse(createdIso); + const withinTtl = Number.isFinite(createdMs) ? (Date.now() - createdMs <= maxAgeMs) : false; + + if (withinTtl) { + // Gating 'enforce' para grupos + let allowed = true; + if (isGroupId(chatId)) { + try { (AllowedGroups as any).dbInstance = this.dbInstance; } catch {} + const mode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase(); + if (mode === 'enforce') { + try { allowed = AllowedGroups.isAllowed(chatId); } catch { allowed = true; } + } + } + if (allowed) { + // Encolar reacción ✅ con idempotencia; no bloquear si falla + ResponseQueue.enqueueReaction(chatId, String(origin.message_id), '✅') + .then(() => { try { Metrics.inc('reactions_enqueued_total', 1, { emoji: 'check' }); } catch {} }) + .catch(() => {}); + } + } + } + } + } + } catch {} + return { status: 'updated', task: {