|
|
|
|
@ -118,6 +118,17 @@ export class CommandService {
|
|
|
|
|
};
|
|
|
|
|
const action = ACTION_ALIASES[rawAction] || rawAction;
|
|
|
|
|
|
|
|
|
|
// Helper para fechas dd/MM
|
|
|
|
|
const formatDDMM = (ymd?: string | null): string | null => {
|
|
|
|
|
if (!ymd) return null;
|
|
|
|
|
const parts = String(ymd).split('-');
|
|
|
|
|
if (parts.length >= 3) {
|
|
|
|
|
const [Y, M, D] = parts;
|
|
|
|
|
if (D && M) return `${D}/${M}`;
|
|
|
|
|
}
|
|
|
|
|
return String(ymd);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!action || action === 'ayuda') {
|
|
|
|
|
const help = [
|
|
|
|
|
'Guía rápida:',
|
|
|
|
|
@ -132,6 +143,133 @@ export class CommandService {
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Listar pendientes
|
|
|
|
|
if (action === 'ver') {
|
|
|
|
|
const scope = (tokens[2] || '').toLowerCase() || (isGroupId(context.groupId) ? 'grupo' : 'mis');
|
|
|
|
|
const LIMIT = 10;
|
|
|
|
|
|
|
|
|
|
// Ver grupo
|
|
|
|
|
if (scope === 'grupo') {
|
|
|
|
|
if (!isGroupId(context.groupId)) {
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: 'Este comando se usa en grupos. Prueba: /t ver mis'
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
if (!GroupSyncService.isGroupActive(context.groupId)) {
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: '⚠️ Este grupo no está activo.'
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
const items = TaskService.listGroupPending(context.groupId, LIMIT);
|
|
|
|
|
const groupName = GroupSyncService.activeGroupsCache.get(context.groupId) || context.groupId;
|
|
|
|
|
|
|
|
|
|
if (items.length === 0) {
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: `No hay pendientes en ${groupName}.`
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rendered = await Promise.all(items.map(async (t) => {
|
|
|
|
|
const names = await Promise.all(
|
|
|
|
|
(t.assignees || []).map(async (uid) => (await ContactsService.getDisplayName(uid)) || uid)
|
|
|
|
|
);
|
|
|
|
|
const owner =
|
|
|
|
|
(t.assignees?.length || 0) === 0
|
|
|
|
|
? '👥 sin dueño'
|
|
|
|
|
: `${t.assignees!.length > 1 ? '👥' : '👤'} ${names.join(', ')}`;
|
|
|
|
|
const datePart = t.due_date ? ` — 📅 ${formatDDMM(t.due_date)}` : '';
|
|
|
|
|
return `- ${t.id}) “*${t.description || '(sin descripción)'}*”${datePart} — ${owner}`;
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: [groupName, ...rendered].join('\n')
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ver mis
|
|
|
|
|
const items = TaskService.listUserPending(context.sender, LIMIT);
|
|
|
|
|
if (items.length === 0) {
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: 'No tienes tareas pendientes.'
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Agrupar por grupo
|
|
|
|
|
const byGroup = new Map<string, typeof items>();
|
|
|
|
|
for (const t of items) {
|
|
|
|
|
const key = t.group_id || '(sin grupo)';
|
|
|
|
|
const arr = byGroup.get(key) || [];
|
|
|
|
|
arr.push(t);
|
|
|
|
|
byGroup.set(key, arr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sections: string[] = [];
|
|
|
|
|
for (const [groupId, arr] of byGroup.entries()) {
|
|
|
|
|
const groupName =
|
|
|
|
|
(groupId && GroupSyncService.activeGroupsCache.get(groupId)) ||
|
|
|
|
|
(groupId && groupId !== '(sin grupo)' ? groupId : 'Sin grupo');
|
|
|
|
|
|
|
|
|
|
sections.push(groupName);
|
|
|
|
|
const rendered = await Promise.all(arr.map(async (t) => {
|
|
|
|
|
const names = await Promise.all(
|
|
|
|
|
(t.assignees || []).map(async (uid) => (await ContactsService.getDisplayName(uid)) || uid)
|
|
|
|
|
);
|
|
|
|
|
const owner =
|
|
|
|
|
(t.assignees?.length || 0) === 0
|
|
|
|
|
? '👥 sin dueño'
|
|
|
|
|
: `${t.assignees!.length > 1 ? '👥' : '👤'} ${names.join(', ')}`;
|
|
|
|
|
const datePart = t.due_date ? ` — 📅 ${formatDDMM(t.due_date)}` : '';
|
|
|
|
|
return `- ${t.id}) “*${t.description || '(sin descripción)'}*”${datePart} — ${owner}`;
|
|
|
|
|
}));
|
|
|
|
|
sections.push(...rendered);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: sections.join('\n')
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Completar tarea
|
|
|
|
|
if (action === 'completar') {
|
|
|
|
|
const idToken = tokens[2];
|
|
|
|
|
const id = idToken ? parseInt(idToken, 10) : NaN;
|
|
|
|
|
if (!id || Number.isNaN(id)) {
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: 'Uso: /t x <id>'
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const res = TaskService.completeTask(id, context.sender);
|
|
|
|
|
if (res.status === 'not_found') {
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: `⚠️ Tarea ${id} no encontrada.`
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const who = (await ContactsService.getDisplayName(context.sender)) || context.sender;
|
|
|
|
|
if (res.status === 'already') {
|
|
|
|
|
const due = res.task?.due_date ? ` — 📅 ${formatDDMM(res.task?.due_date)}` : '';
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: `ℹ️ ${id} ya estaba completada — “*${res.task?.description || '(sin descripción)'}*”${due}`
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const due = res.task?.due_date ? ` — 📅 ${formatDDMM(res.task?.due_date)}` : '';
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: `✔️ ${id} completada — “*${res.task?.description || '(sin descripción)'}*”${due}\nGracias, ${who}.`
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (action !== 'nueva') {
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
@ -228,7 +366,8 @@ export class CommandService {
|
|
|
|
|
// 1) Ack al creador con formato compacto
|
|
|
|
|
const ackHeader = `✅ ${taskId} “*${description || '(sin descripción)'}*”`;
|
|
|
|
|
const ackLines: string[] = [ackHeader];
|
|
|
|
|
if (dueDate) ackLines.push(`📅 ${dueDate}`);
|
|
|
|
|
const dueFmt = formatDDMM(dueDate);
|
|
|
|
|
if (dueFmt) ackLines.push(`📅 ${dueFmt}`);
|
|
|
|
|
if (assignmentUserIds.length === 0) {
|
|
|
|
|
ackLines.push(`👥 sin dueño${groupName ? ` (${groupName})` : ''}`);
|
|
|
|
|
} else {
|
|
|
|
|
|