Compare commits

...

4 Commits

Author SHA1 Message Date
brobert 226e1bc01f test: usar display_code en /t y validar no encontrada tras completar
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
7 days ago
brobert fb7cc90b77 fix: usar display_code activo en comandos y DMs; eliminar fallback al PK
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
7 days ago
brobert 26a19add33 actualiza copy de punto de acceso a login 7 days ago
brobert b6e335b275 feat: añade soporte de prefers-color-scheme en login
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
7 days ago

@ -40,16 +40,34 @@ export const GET: RequestHandler = async (event) => {
<title>Acceder</title>
<meta name="robots" content="noindex,nofollow" />
<meta name="referrer" content="no-referrer" />
<meta name="color-scheme" content="light dark" />
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial, 'Apple Color Emoji', 'Segoe UI Emoji'; padding: 2rem; }
.card { max-width: 480px; margin: 0 auto; border: 1px solid #ddd; border-radius: 8px; padding: 1.5rem; }
:root { color-scheme: light dark; }
body {
font-family: sans-serif;
padding: 2rem;
background: #ffffff;
color: #111111;
}
.card {
max-width: 480px;
margin: 0 auto;
border: 1px solid #dddddd;
border-radius: 8px;
padding: 1.5rem;
background: #ffffff;
}
button[disabled] { opacity: .6; cursor: not-allowed; }
@media (prefers-color-scheme: dark) {
body { background: #0b0b0b; color: #eeeeee; }
.card { border-color: #333333; background: #161616; }
}
</style>
</head>
<body>
<div class="card">
<h1>Acceso seguro</h1>
<p>Para continuar, pulsa Continuar. Si no funciona, asegúrate de abrir este enlace en tu navegador.</p>
<h1>Acceso a las tareas</h1>
<p>Pulsa Continuar. Si no funciona, copia y pega el enlace en tu navegador.</p>
<form method="POST" action="/login">
<input type="hidden" name="token" value="${escapeHtml(token)}" />
<input type="hidden" id="nonceInput" name="nonce" value="${escapeHtml(nonce)}" />

@ -156,11 +156,8 @@ export class CommandService {
}
private static resolveTaskIdFromInput(n: number): number | null {
// Resolver primero por display_code en tareas activas; si no, por PK
const byCode = TaskService.getActiveTaskByDisplayCode(n);
if (byCode) return byCode.id;
const byId = TaskService.getTaskById(n);
return byId ? byId.id : null;
return byCode ? byCode.id : null;
}
private static async processTareaCommand(
@ -903,7 +900,7 @@ export class CommandService {
if (res.now_unassigned) {
const lines = [
`${ICONS.unassigned} ${codeId(resolvedId, res.task?.display_code)} (${resolvedId})`,
`${ICONS.unassigned} ${codeId(resolvedId, res.task?.display_code)}`,
`${res.task?.description || '(sin descripción)'}`,
res.task?.due_date ? `${ICONS.date} ${formatDDMM(res.task?.due_date)}` : '',
italic('queda sin responsable.')
@ -1279,8 +1276,8 @@ export class CommandService {
`${description || '(sin descripción)'}`,
formatDDMM(dueDate) ? `${ICONS.date} ${formatDDMM(dueDate)}` : null,
groupName ? `Grupo: ${groupName}` : null,
`- Completar: \`/t x ${taskId}\``,
`- Soltar: \`/t soltar ${taskId}\``
`- Completar: \`/t x ${createdTask?.display_code}\``,
`- Soltar: \`/t soltar ${createdTask?.display_code}\``
].filter(Boolean).join('\n'),
mentions: [creatorJid]
});

@ -607,7 +607,7 @@ export class TaskService {
const row = this.dbInstance.prepare(`
SELECT id, description, due_date, display_code
FROM tasks
WHERE display_code = ? AND COALESCE(completed, 0) = 0
WHERE display_code = ? AND COALESCE(completed, 0) = 0 AND completed_at IS NULL
LIMIT 1
`).get(displayCode) as any;
if (!row) return null;

@ -36,6 +36,15 @@ describe('CommandService - /t tomar y /t soltar', () => {
return taskId;
}
function getDisplayCode(id: number): number {
const row = memdb.prepare('SELECT display_code FROM tasks WHERE id = ?').get(id) as any;
return Number(row?.display_code || 0);
}
function code4(n: number): string {
return '`' + String(n).padStart(4, '0') + '`';
}
const ctx = (sender: string, message: string) => ({
sender,
groupId: '', // DM o vacío; sin relevancia para tomar/soltar
@ -57,13 +66,14 @@ describe('CommandService - /t tomar y /t soltar', () => {
it('tomar: happy y luego already', async () => {
const taskId = createTask('Desc tomar', '999', '2025-09-12');
const r1 = await CommandService.handle(ctx('111', `/t tomar ${taskId}`));
const dc = getDisplayCode(taskId);
const r1 = await CommandService.handle(ctx('111', `/t tomar ${dc}`));
expect(r1[0].message).toContain('Has tomado');
expect(r1[0].message).toContain(String(taskId));
expect(r1[0].message).toContain(code4(dc));
expect(r1[0].message).toContain('Desc tomar');
expect(r1[0].message).toContain('📅'); // formato dd/MM
const r2 = await CommandService.handle(ctx('111', `/t tomar ${taskId}`));
const r2 = await CommandService.handle(ctx('111', `/t tomar ${dc}`));
expect(r2[0].message).toContain('ya la tenías');
});
@ -72,8 +82,9 @@ describe('CommandService - /t tomar y /t soltar', () => {
const comp = TaskService.completeTask(taskId, '111');
expect(comp.status).toBe('updated');
const res = await CommandService.handle(ctx('222', `/t tomar ${taskId}`));
expect(res[0].message).toContain('ya estaba completada');
const dc = getDisplayCode(taskId);
const res = await CommandService.handle(ctx('222', `/t tomar ${dc}`));
expect(res[0].message).toContain('no encontrada');
});
it('soltar: uso inválido (sin id)', async () => {
@ -88,13 +99,15 @@ describe('CommandService - /t tomar y /t soltar', () => {
it('soltar: personal única asignación → denegado', async () => {
const taskId = createTask('Desc soltar', '999', '2025-09-12', ['111']);
const res = await CommandService.handle(ctx('111', `/t soltar ${taskId}`));
const dc = getDisplayCode(taskId);
const res = await CommandService.handle(ctx('111', `/t soltar ${dc}`));
expect(res[0].message).toContain('No puedes soltar una tarea personal. Márcala como completada para eliminarla');
});
it('soltar: not_assigned muestra mensaje informativo', async () => {
const taskId = createTask('Nunca asignada a 111', '999', null, ['222']);
const res = await CommandService.handle(ctx('111', `/t soltar ${taskId}`));
const dc = getDisplayCode(taskId);
const res = await CommandService.handle(ctx('111', `/t soltar ${dc}`));
expect(res[0].message).toContain('no la tenías asignada');
});
@ -103,7 +116,8 @@ describe('CommandService - /t tomar y /t soltar', () => {
const comp = TaskService.completeTask(taskId, '111');
expect(comp.status).toBe('updated');
const res = await CommandService.handle(ctx('111', `/t soltar ${taskId}`));
expect(res[0].message).toContain('ya estaba completada');
const dc = getDisplayCode(taskId);
const res = await CommandService.handle(ctx('111', `/t soltar ${dc}`));
expect(res[0].message).toContain('no encontrada');
});
});

@ -115,26 +115,28 @@ test('completar tarea: camino feliz, ya completada y no encontrada', async () =>
created_by: '1111111111',
});
const dc = Number((memDb.prepare(`SELECT display_code FROM tasks WHERE id = ?`).get(taskId) as any)?.display_code || 0);
// 1) Camino feliz
let responses = await CommandService.handle({
sender: '1234567890',
groupId: 'test-group@g.us',
mentions: [],
message: `/t x ${taskId}`
message: `/t x ${dc}`
});
expect(responses.length).toBe(1);
expect(responses[0].recipient).toBe('1234567890');
expect(responses[0].message).toMatch(/^✅ `\d{4}` _completada_/);
// 2) Ya completada
// 2) Ya completada (ahora no debe resolverse por display_code → no encontrada)
responses = await CommandService.handle({
sender: '1234567890',
groupId: 'test-group@g.us',
mentions: [],
message: `/t x ${taskId}`
message: `/t x ${dc}`
});
expect(responses.length).toBe(1);
expect(responses[0].message).toContain('_Ya estaba completada_');
expect(responses[0].message).toContain('no encontrada');
// 3) No encontrada
responses = await CommandService.handle({

Loading…
Cancel
Save