/** * Utilidades compartidas para handlers de comandos (Etapa 1). * Aún no se usan desde CommandService; servirán en etapas siguientes. */ import { TaskService } from '../../tasks/service'; import { GroupSyncService } from '../group-sync'; export const ACTION_ALIASES: Record = { 'n': 'nueva', 'nueva': 'nueva', 'crear': 'nueva', '+': 'nueva', 'ver': 'ver', 'mostrar': 'ver', 'listar': 'ver', 'ls': 'ver', 'mias': 'ver', 'mías': 'ver', 'todas': 'ver', 'todos': 'ver', 'x': 'completar', 'hecho': 'completar', 'completar': 'completar', 'done': 'completar', 'tomar': 'tomar', 'claim': 'tomar', 'asumir': 'tomar', 'asumo': 'tomar', 'soltar': 'soltar', 'unassign': 'soltar', 'dejar': 'soltar', 'liberar': 'soltar', 'renunciar': 'soltar', 'ayuda': 'ayuda', 'help': 'ayuda', 'info': 'ayuda', '?': 'ayuda', 'config': 'configurar', 'configurar': 'configurar', 'web': 'web' }; export const SCOPE_ALIASES: Record = { 'todo': 'todos', 'todos': 'todos', 'todas': 'todos', 'mis': 'mis', 'mias': 'mis', 'mías': 'mis', 'yo': 'mis' }; export const CTA_HELP = 'ℹ️ Tus tareas: `/t mias` · Todas: `/t todas` · Info: `/t info` · Web: `/t web`'; /** * Formatea un Date a YYYY-MM-DD respetando TZ (por defecto Europe/Madrid). */ export function ymdInTZ(d: Date, tz?: string): string { const TZ = (tz && tz.trim()) || (process.env.TZ && process.env.TZ.trim()) || 'Europe/Madrid'; const parts = new Intl.DateTimeFormat('en-GB', { timeZone: TZ, year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(d); const get = (t: string) => parts.find(p => p.type === t)?.value || ''; return `${get('year')}-${get('month')}-${get('day')}`; } export function todayYMD(tz?: string): string { return ymdInTZ(new Date(), tz); } /** * Parsea múltiples IDs desde tokens, deduplica, y aplica límite. */ export function parseMultipleIds(tokens: string[], max: number = 10): { ids: number[]; truncated: boolean } { const raw = (tokens || []).join(' ').trim(); const all = raw ? raw .split(/[,\s]+/) .map(t => t.trim()) .filter(Boolean) .map(t => parseInt(t, 10)) .filter(n => Number.isFinite(n) && n > 0) : []; const dedup: number[] = []; const seen = new Set(); for (const n of all) { if (!seen.has(n)) { seen.add(n); dedup.push(n); } } const truncated = dedup.length > max; const ids = dedup.slice(0, max); return { ids, truncated }; } /** * Resuelve un ID de entrada (display_code) a task.id si está activa. */ export function resolveTaskIdFromInput(n: number): number | null { const byCode = TaskService.getActiveTaskByDisplayCode(n); return byCode ? byCode.id : null; } /** * Aplica la política de membresía para acciones sobre una tarea. * Devuelve true si el usuario está permitido según flags/env. */ export function enforceMembership(sender: string, task: { group_id?: string | null }, enforceFlag?: boolean): boolean { const enforce = typeof enforceFlag === 'boolean' ? enforceFlag : String(process.env.GROUP_MEMBERS_ENFORCE || '').toLowerCase() === 'true'; const gid = task?.group_id || null; if (!gid) return true; // tareas personales no requieren membresía if (!enforce) return true; if (!GroupSyncService.isSnapshotFresh(gid)) return true; return GroupSyncService.isUserActiveInGroup(sender, gid); }