|
|
|
|
@ -108,8 +108,13 @@ export class CommandService {
|
|
|
|
|
'done': 'completar',
|
|
|
|
|
'tomar': 'tomar',
|
|
|
|
|
'claim': 'tomar',
|
|
|
|
|
'asumir': 'tomar',
|
|
|
|
|
'asumo': 'tomar',
|
|
|
|
|
'soltar': 'soltar',
|
|
|
|
|
'unassign': 'soltar',
|
|
|
|
|
'dejar': 'soltar',
|
|
|
|
|
'liberar': 'soltar',
|
|
|
|
|
'renunciar': 'soltar',
|
|
|
|
|
'ayuda': 'ayuda',
|
|
|
|
|
'help': 'ayuda',
|
|
|
|
|
'?': 'ayuda',
|
|
|
|
|
@ -148,6 +153,125 @@ export class CommandService {
|
|
|
|
|
const scope = (tokens[2] || '').toLowerCase() || (isGroupId(context.groupId) ? 'grupo' : 'mis');
|
|
|
|
|
const LIMIT = 10;
|
|
|
|
|
|
|
|
|
|
// Ver sin dueño del grupo actual
|
|
|
|
|
if (scope === 'sin') {
|
|
|
|
|
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.listGroupUnassigned(context.groupId, LIMIT);
|
|
|
|
|
const groupName = GroupSyncService.activeGroupsCache.get(context.groupId) || context.groupId;
|
|
|
|
|
|
|
|
|
|
if (items.length === 0) {
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: `No hay tareas sin dueño en ${groupName}.`
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rendered = items.map((t) => {
|
|
|
|
|
const datePart = t.due_date ? ` — 📅 ${formatDDMM(t.due_date)}` : '';
|
|
|
|
|
return `- ${t.id}) “*${t.description || '(sin descripción)'}*”${datePart} — 👥 sin dueño`;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const total = TaskService.countGroupUnassigned(context.groupId);
|
|
|
|
|
if (total > items.length) {
|
|
|
|
|
rendered.push(`… y ${total - items.length} más`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: [`${groupName} — Sin dueño`, ...rendered].join('\n')
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ver todos: "tus tareas" + "sin dueño (grupo actual)" si estás en un grupo
|
|
|
|
|
if (scope === 'todos') {
|
|
|
|
|
const sections: string[] = [];
|
|
|
|
|
|
|
|
|
|
// Tus tareas (mis)
|
|
|
|
|
const myItems = TaskService.listUserPending(context.sender, LIMIT);
|
|
|
|
|
if (myItems.length > 0) {
|
|
|
|
|
// Agrupar por grupo como en "ver mis"
|
|
|
|
|
const byGroup = new Map<string, typeof myItems>();
|
|
|
|
|
for (const t of myItems) {
|
|
|
|
|
const key = t.group_id || '(sin grupo)';
|
|
|
|
|
const arr = byGroup.get(key) || [];
|
|
|
|
|
arr.push(t);
|
|
|
|
|
byGroup.set(key, arr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sections.push('Tus tareas');
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const totalMy = TaskService.countUserPending(context.sender);
|
|
|
|
|
if (totalMy > myItems.length) {
|
|
|
|
|
sections.push(`… y ${totalMy - myItems.length} más`);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
sections.push('No tienes tareas pendientes.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Si se invoca en un grupo activo, añadir "sin dueño" de ese grupo
|
|
|
|
|
if (isGroupId(context.groupId)) {
|
|
|
|
|
if (!GroupSyncService.isGroupActive(context.groupId)) {
|
|
|
|
|
sections.push('⚠️ Este grupo no está activo.');
|
|
|
|
|
} else {
|
|
|
|
|
const groupName = GroupSyncService.activeGroupsCache.get(context.groupId) || context.groupId;
|
|
|
|
|
const unassigned = TaskService.listGroupUnassigned(context.groupId, LIMIT);
|
|
|
|
|
if (unassigned.length > 0) {
|
|
|
|
|
sections.push(`${groupName} — Sin dueño`);
|
|
|
|
|
const renderedUnassigned = unassigned.map((t) => {
|
|
|
|
|
const datePart = t.due_date ? ` — 📅 ${formatDDMM(t.due_date)}` : '';
|
|
|
|
|
return `- ${t.id}) “*${t.description || '(sin descripción)'}*”${datePart} — 👥 sin dueño`;
|
|
|
|
|
});
|
|
|
|
|
sections.push(...renderedUnassigned);
|
|
|
|
|
|
|
|
|
|
const totalUnassigned = TaskService.countGroupUnassigned(context.groupId);
|
|
|
|
|
if (totalUnassigned > unassigned.length) {
|
|
|
|
|
sections.push(`… y ${totalUnassigned - unassigned.length} más`);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
sections.push(`${groupName} — Sin dueño\n(no hay tareas sin dueño)`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// En DM: nota instructiva
|
|
|
|
|
sections.push('ℹ️ Para ver tareas sin dueño de un grupo, usa “/t ver sin” desde ese grupo.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: sections.join('\n')
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ver grupo
|
|
|
|
|
if (scope === 'grupo') {
|
|
|
|
|
if (!isGroupId(context.groupId)) {
|
|
|
|
|
|