diff --git a/src/server.ts b/src/server.ts index 4760d0e..24d4517 100644 --- a/src/server.ts +++ b/src/server.ts @@ -335,6 +335,18 @@ export class WebhookServer { const messageTextTrimmed = messageText.trim(); const isAdminCmd = messageTextTrimmed.startsWith('/admin'); + // A4: Primer DM "activar" — alta/confirmación idempotente (solo en DM) + if (!isGroupId(remoteJid) && messageTextTrimmed === 'activar') { + const base = (process.env.WEB_BASE_URL || '').trim(); + const msg = base + ? "Listo, ya puedes reclamar/ser responsable y acceder a la web. Para acceder a la web, envía '/t web' y abre el enlace." + : "Listo, ya puedes reclamar/ser responsable."; + try { + await ResponseQueue.add([{ recipient: normalizedSenderId, message: msg }]); + } catch {} + return; + } + // Etapa 2: Descubrimiento seguro de grupos (modo 'discover') if (isGroupId(remoteJid)) { try { (AllowedGroups as any).dbInstance = WebhookServer.dbInstance; } catch {} diff --git a/src/services/command.ts b/src/services/command.ts index 83ef7c5..31fe1a9 100644 --- a/src/services/command.ts +++ b/src/services/command.ts @@ -1042,10 +1042,15 @@ export class CommandService { }; // 1) Menciones aportadas por el backend (JIDs crudos) + const unresolvedAssigneeDisplays: string[] = []; const mentionsNormalizedFromContext = Array.from(new Set( (context.mentions || []).map((j) => { const norm = normalizeWhatsAppId(j); if (!norm) { + // agregar a no resolubles para JIT (mostrar sin @ ni dominio) + const raw = String(j || ''); + const disp = raw.split('@')[0].split(':')[0].replace(/^@+/, '').replace(/^\+/, ''); + if (disp) unresolvedAssigneeDisplays.push(disp); incOnboardingFailure('mentions', 'invalid'); return null; } @@ -1053,6 +1058,8 @@ export class CommandService { if (resolved) return resolved; const p = plausibility(norm); if (p.ok) return norm; + // conservar para copy JIT + unresolvedAssigneeDisplays.push(norm); incOnboardingFailure('mentions', p.reason!); return null; }).filter((id): id is string => !!id) @@ -1066,6 +1073,8 @@ export class CommandService { atTokenCandidates.map((v) => { const norm = normalizeWhatsAppId(v); if (!norm) { + // agregar a no resolubles para JIT (texto ya viene sin @/+) + if (v) unresolvedAssigneeDisplays.push(v); incOnboardingFailure('tokens', 'invalid'); return null; } @@ -1073,6 +1082,8 @@ export class CommandService { if (resolved) return resolved; const p = plausibility(norm); if (p.ok) return norm; + // conservar para copy JIT (preferimos el token limpio v) + unresolvedAssigneeDisplays.push(v); incOnboardingFailure('tokens', p.reason!); return null; }).filter((id): id is string => !!id) @@ -1195,7 +1206,38 @@ export class CommandService { }); } - + // A4: DM JIT al asignador si quedaron menciones/tokens irrecuperables + { + const unresolvedList = Array.from(new Set(unresolvedAssigneeDisplays.filter(Boolean))); + if (unresolvedList.length > 0) { + const isTest = String(process.env.NODE_ENV || '').toLowerCase() === 'test'; + const enabled = isTest + ? String(process.env.ONBOARDING_ENABLE_IN_TEST || '').toLowerCase() === 'true' + : (() => { + const v = process.env.ONBOARDING_PROMPTS_ENABLED; + return v == null ? true : ['true','1','yes'].includes(String(v).toLowerCase()); + })(); + const groupLabel = isGroupId(context.groupId) ? String(context.groupId) : 'dm'; + if (!enabled) { + try { Metrics.inc('onboarding_prompts_skipped_total', 1, { group_id: groupLabel, source: 'jit_assignee_failure', reason: 'disabled' }); } catch {} + } else { + const bot = String(process.env.CHATBOT_PHONE_NUMBER || '').trim(); + if (!bot || !/^\d+$/.test(bot)) { + try { Metrics.inc('onboarding_prompts_skipped_total', 1, { group_id: groupLabel, source: 'jit_assignee_failure', reason: 'missing_bot_number' }); } catch {} + } else { + const list = unresolvedList.join(', '); + let groupCtx = ''; + if (isGroupId(context.groupId)) { + const name = GroupSyncService.activeGroupsCache.get(context.groupId) || context.groupId; + groupCtx = ` (en el grupo ${name})`; + } + const msg = `No puedo asignar a ${list} aún${groupCtx}. Pídele que toque este enlace y diga 'activar': https://wa.me/${bot}`; + responses.push({ recipient: createdBy, message: msg }); + try { Metrics.inc('onboarding_prompts_sent_total', 1, { group_id: groupLabel, source: 'jit_assignee_failure' }); } catch {} + } + } + } + } return responses; }