feat: añade alias para tomar/soltar y ver sin/todos con consultas por grupo

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
pull/1/head
borja 2 months ago
parent 3f9280eb1a
commit 137e0d2d07

@ -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)) {

@ -222,4 +222,54 @@ export class TaskService {
},
};
}
// Listar pendientes sin dueño del grupo (limite por defecto 10)
static listGroupUnassigned(groupId: string, limit: number = 10): Array<{
id: number;
description: string;
due_date: string | null;
group_id: string | null;
assignees: string[];
}> {
const rows = this.dbInstance
.prepare(`
SELECT id, description, due_date, group_id
FROM tasks
WHERE group_id = ?
AND (completed = 0 OR completed_at IS NULL)
AND NOT EXISTS (
SELECT 1 FROM task_assignments ta WHERE ta.task_id = tasks.id
)
ORDER BY
CASE WHEN due_date IS NULL THEN 1 ELSE 0 END,
due_date ASC,
id ASC
LIMIT ?
`)
.all(groupId, limit) as any[];
return rows.map((r) => ({
id: Number(r.id),
description: String(r.description || ''),
due_date: r.due_date ? String(r.due_date) : null,
group_id: r.group_id ? String(r.group_id) : null,
assignees: [],
}));
}
// Contar pendientes sin dueño del grupo (sin límite)
static countGroupUnassigned(groupId: string): number {
const row = this.dbInstance
.prepare(`
SELECT COUNT(*) as cnt
FROM tasks t
WHERE t.group_id = ?
AND (t.completed = 0 OR t.completed_at IS NULL)
AND NOT EXISTS (
SELECT 1 FROM task_assignments a WHERE a.task_id = t.id
)
`)
.get(groupId) as any;
return Number(row?.cnt || 0);
}
}

Loading…
Cancel
Save