refactor: rename 't configurar' command to 't alarma' with 't recordatorio' as alias

- Rename configurar.ts -> alarma.ts, function handleConfigurar -> handleAlarma
- Add 'alarma' and 'recordatorio' aliases in shared.ts; keep 'configurar'/'config' for backward compat
- Update all help text, usage messages, onboarding, docs, and tests
- t alarma, t recordatorio, t configurar, t config all route to the same handler
main
borja 1 month ago
parent 600bb46b09
commit 1c7a7ffdbe

@ -64,7 +64,7 @@ Siguiente paso de desarrollo: ejecutar el plan mínimo de CI/CD, healthcheck y b
- [ ] Logs accesibles; worker de cola activo.
- Funcional
- [ ] Bot añadido a los grupos; grupos activos sincronizados.
- [ ] Comandos base verificados en un grupo real (crear/ver/x/tomar/soltar/configurar).
- [ ] Comandos base verificados en un grupo real (crear/ver/x/tomar/soltar/alarma).
- Comunicación/privacidad
- [ ] Aviso a la junta: cómo usar, qué se guarda, cómo desactivar recordatorios.

@ -41,12 +41,12 @@ Comandos y alias
- Ejemplo: t soltar 26
- Idempotente: si no estabas asignado, lo indica sin error. La tarea puede quedar “sin responsable” si no quedan asignados.
- Configurar recordatorios
- Aliases: configurar, config
- Aliases: alarma, recordatorio, configurar, config
- Opciones: daily | weekly | off
- Ejemplos:
- t configurar daily
- t configurar weekly
- t configurar off
- t alarma daily
- t alarma weekly
- t alarma off
- Notas: resumen diario/weekly por DM; weekly los lunes a la hora configurada (por defecto 08:30 si aplica); se evita duplicar en el mismo día y no se envía si no hay tareas.
- Ayuda
- Aliases: ayuda, help, ?
@ -80,7 +80,7 @@ Ejemplos prácticos
- t tomar 42
- t soltar 42
- Configurar recordatorios:
- t configurar weekly
- t alarma weekly
Limitaciones y notas
- El bot no publica en grupos por diseño.

@ -116,10 +116,10 @@ Ejemplos
---
## t configurar (recordatorios)
## t alarma / t recordatorio (recordatorios)
Alias: `config`, `configurar`
Sintaxis: `t configurar diario|l-v|semanal|off [HH:MM]`
Alias: `alarma`, `recordatorio`, `configurar`, `config`
Sintaxis: `t alarma diario|l-v|semanal|off [HH:MM]`
Valores admitidos y alias
- `diario`/`diaria` → recordatorio diario (se guarda como `daily`).
@ -135,10 +135,10 @@ Nota de localización
- Internamente se almacenan claves en inglés (`daily`, `weekdays`, `weekly`, `off`), pero el copy al usuario es en español. Pendiente de revisión futura para evitar fugas como “weekly” en mensajes.
Ejemplos
- `t configurar diaria 09:00`
- `t configurar l-v 08:30`
- `t configurar semanal` (→ lunes 08:30)
- `t configurar off`
- `t alarma diaria 09:00`
- `t alarma l-v 08:30`
- `t alarma semanal` (→ lunes 08:30)
- `t alarma off`
---

@ -20,7 +20,7 @@
" Uso: `t x 26` o múltiples: `t x 14 19 24` o `t x 14,19,24` (máx. 10)",
" Uso: `t tomar 26` o múltiples: `t tomar 12 19 50` o `t tomar 12,19,50` (máx. 10)",
" Uso: `t soltar 26`",
" Uso: `t configurar diario|l-v|semanal|off [HH:MM]`"
" Uso: `t alarma diario|l-v|semanal|off [HH:MM]`"
],
"errors": [
"⚠️ Tarea {id} no encontrada.",

@ -42,7 +42,7 @@ Objetivo: hacer la ayuda consistente, útil ante comandos desconocidos, visible
- Comandos: `t soltar`, `t unassign`, `t dejar`, `t liberar`, `t renunciar`
- Un solo ID
- Configurar recordatorios
- Comandos: `t configurar diario|l-v|semanal|off [HH:MM]`
- Comandos: `t alarma diario|l-v|semanal|off [HH:MM]`
- Mapea alias a `daily`, `weekdays`, `weekly`, `off`; hora opcional con normalización
- Ayuda
- Comandos: `t ayuda`, `t help`, `t ?`, y variante “ayuda avanzada”
@ -96,7 +96,7 @@ Objetivo: hacer la ayuda consistente, útil ante comandos desconocidos, visible
- Contenido sugerido (resumen):
- Ayuda rápida:
- Secciones: “COMANDOS BÁSICOS”, “LISTADOS”, “ACCESO WEB”
- Bullets con: crear (`t n ...`), ver (`t ver mis|grupo|todos|sin`), completar/tomar/soltar, configurar recordatorios, y `t web`
- Bullets con: crear (`t n ...`), ver (`t ver mis|grupo|todos|sin`), completar/tomar/soltar, alarmas/recordatorios, y `t web`
- Nota: _El bot responde por DM, incluso si escribes desde un grupo._
- Ayuda extendida:
- Además: formatos de fecha (`YYYY-MM-DD`, `YY-MM-DD`→`20YY-MM-DD`, `hoy|mañana`), límites (máx. 10 IDs), reglas de asignación por contexto, gating de grupos, detalles de “ver todos”.

@ -82,7 +82,7 @@ Impacto en tests
Objetivos
- Enviar un paquete de 2 DMs (Mensaje 1 + Mensaje 2) por usuario cuando se crea una tarea en un grupo.
- Mensaje 1: CTA “t tomar {CÓDIGO}” + “t info”.
- Mensaje 2: minichuleta (“t mias”, “t todas”, “t configurar …”, “t web”), 510 s después del Mensaje 1.
- Mensaje 2: minichuleta (“t mias”, “t todas”, “t alarma …”, “t web”), 510 s después del Mensaje 1.
- Repetir el mismo paquete una única vez más si pasan ≥ 14 días sin interacción del usuario (si hubo interacción, no se envía el segundo paquete).
- Cap por evento; sin mensajes en grupos.
@ -121,7 +121,7 @@ Copys de onboarding (exactos)
- Mensaje 2 (minichuleta; se envía tras 510 s, en ambos disparos):
- “Guía rápida (este es un mensaje único):
· Tus tareas: t mias · Todas: t todas
· Recordatorios: t configurar diario | lv | semanal | off
· Recordatorios: t alarma diario | lv | semanal | off
· Web: t web”
Impacto en tests
@ -246,7 +246,7 @@ Ideas a evaluar después
- “Bienvenida al primer DM inbound” (mensaje corto de bienvenida una única vez cuando el usuario inicia chat).
- Ventanas horarias: programar next_attempt_at si se detecta horario nocturno (requiere mínima lógica extra).
- Tabla explícita de onboarding (si se quiere persistir fuera de response_queue), p. ej. user_onboarding con timestamps y contadores.
- Resumen semanal optin (ya soportado con “t configurar …”): medir retención y satisfacción.
- Resumen semanal optin (ya soportado con “t alarma …”): medir retención y satisfacción.
---
@ -291,7 +291,7 @@ Ideas a evaluar después
2) Onboarding — Mensaje 2 (reminder, único)
- “Guía rápida (este es un mensaje único):
· Tus tareas: t mias · Todas: t todas
· Recordatorios: t configurar diario | lv | semanal | off
· Recordatorios: t alarma diario | lv | semanal | off
· Web: t web”
3) Transición cuando se intenta listar desde grupo (responder por DM)

@ -28,7 +28,7 @@ Este documento describe un plan incremental y sin cambios de UX para refactoriza
- completar.ts
- tomar.ts
- soltar.ts
- configurar.ts
- alarma.ts
- web.ts
- src/services/onboarding.ts ← JIT y bundle de 2 DMs (disparado desde “nueva”)
- (opcional) src/services/web-access.ts ← gestión de tokens web (invalidate, generate, hash, URL)
@ -65,7 +65,7 @@ Etapa 2 — Parser de “nueva”
- Tests unitarios: hoy/mañana (con y sin acento), YYYY-MM-DD y YY-MM-DD→20YY, @yo, limpieza de puntuación, excluir @menciones de la descripción.
Etapa 3 — Handlers pequeños
- configurar.ts: upsert en `user_preferences`, validación `HH:MM`, etiquetas de respuesta.
- alarma.ts: upsert en `user_preferences`, validación `HH:MM`, etiquetas de respuesta.
- web.ts: invalidar tokens vigentes, generar token, hash (sha256), insertar y construir URL. Métrica `web_tokens_issued_total`.
- Conectar ambos en el router.
@ -110,7 +110,7 @@ Etapa 8 — Hardening y observabilidad
## Detalles técnicos por handler
- configurar:
- configurar (alarma):
- Validar mapa de alias: diario/daily, lv/weekdays, semanal/weekly, off/apagar/ninguno.
- Hora opcional `HH:MM` (clamp 0023; minutos 0059).
- Upsert con `last_reminded_on` a NULL al cambiar frecuencia.
@ -129,7 +129,7 @@ Etapa 8 — Hardening y observabilidad
## Utilidades compartidas (shared.ts)
- ACTION_ALIASES y SCOPE_ALIASES (incluyendo: n/+/crear/nueva, ver/ls/listar/mostrar, x/hecho/completar/done, tomar/claim, soltar/unassign, ayuda/help/info/?, configurar/config, web).
- ACTION_ALIASES y SCOPE_ALIASES (incluyendo: n/+/crear/nueva, ver/ls/listar/mostrar, x/hecho/completar/done, tomar/claim, soltar/unassign, ayuda/help/info/?, alarma/recordatorio/configurar/config, web).
- ymdInTZ(d, TZ) y todayYMD.
- formatters: reutilizar `formatDDMM`, `codeId`, `padTaskId`, `bold`, `italic`, `code`, `section`.
- parseMultipleIds(tokens: string[], max=10): retorna números válidos > 0, dedup, truncado.

@ -46,7 +46,7 @@ Patrones comunes
- Sufijo “... y N más” si aplica
- Ayuda rápida:
- Secciones: “COMANDOS BÁSICOS”, “LISTADOS”, “ACCESO WEB”
- Bullets con ejemplos: `` `t n ...` ``, `` `t ver mis|grupo|todos|sin` ``, `` `t x 26` ``, `` `t tomar 12` ``, `` `t configurar ...` ``, `` `t web` ``
- Bullets con ejemplos: `` `t n ...` ``, `` `t ver mis|grupo|todos|sin` ``, `` `t x 26` ``, `` `t tomar 12` ``, `` `t alarma ...` ``, `` `t web` ``
Localización
- Todo copy en español. Evitar fugas de claves internas en inglés (ej. “weekly”).
@ -77,7 +77,7 @@ Ayuda rápida
- `t n Descripción 2025-11-05 @Ana`
- `t ver` (en grupo) · `t ver mis` (DM) · `t ver todos`
- `t x 26` · `t tomar 12`
- `t configurar diario|l-v|semanal|off [HH:MM]`
- `t alarma diario|l-v|semanal|off [HH:MM]`
- `t web`
_El bot responde por DM, incluso si escribes desde un grupo._
```

@ -13,7 +13,7 @@ type Msg = {
mentions?: string[];
};
export function handleConfigurar(context: Ctx, deps: { db: Database }): Msg[] {
export function handleAlarma(context: Ctx, deps: { db: Database }): Msg[] {
const tokens = (context.message || '').trim().split(/\s+/);
const optRaw = (tokens[2] || '').toLowerCase();
@ -41,7 +41,7 @@ export function handleConfigurar(context: Ctx, deps: { db: Database }): Msg[] {
if (!m) {
return [{
recipient: context.sender,
message: ' Uso: `t configurar diario|l-v|semanal|off [HH:MM]`'
message: ' Uso: `t alarma diario|l-v|semanal|off [HH:MM]`'
}];
}
const hh = Math.max(0, Math.min(23, parseInt(m[1], 10)));
@ -51,7 +51,7 @@ export function handleConfigurar(context: Ctx, deps: { db: Database }): Msg[] {
if (!freq) {
return [{
recipient: context.sender,
message: ' Uso: `t configurar diario|l-v|semanal|off [HH:MM]`'
message: ' Uso: `t alarma diario|l-v|semanal|off [HH:MM]`'
}];
}

@ -1,11 +1,11 @@
/**
* Router de comandos (Etapa 3)
* Maneja 'configurar' y 'web', y delega el resto al código actual (null fallback).
* Maneja 'alarma' y 'web', y delega el resto al código actual (null fallback).
* Nota: No importar CommandService aquí para evitar ciclos de import.
*/
import type { Database } from 'bun:sqlite';
import { ACTION_ALIASES } from './shared';
import { handleConfigurar } from './handlers/configurar';
import { handleAlarma } from './handlers/alarma';
import { handleWeb } from './handlers/web';
import { handleVer } from './handlers/ver';
import { handleCompletar } from './handlers/completar';
@ -17,7 +17,7 @@ type VerCtx = Parameters<typeof handleVer>[0];
type CompletarCtx = Parameters<typeof handleCompletar>[0];
type TomarCtx = Parameters<typeof handleTomar>[0];
type SoltarCtx = Parameters<typeof handleSoltar>[0];
type ConfigurarCtx = Parameters<typeof handleConfigurar>[0];
type ConfigurarCtx = Parameters<typeof handleAlarma>[0];
type WebCtx = Parameters<typeof handleWeb>[0];
import { ResponseQueue } from '../response-queue';
import { isGroupId } from '../../utils/whatsapp';
@ -31,7 +31,7 @@ function getQuickHelp(): string {
'- Crear: `t n Descripción 2028-11-26 @Ana`',
'- Completar: `t x 123`',
'- Tomar: `t tomar 12`',
'- Configurar recordatorios: `t configurar diario|l-v|semanal|off [HH:MM]`',
'- Alarmas/recordatorios: `t alarma diario|l-v|semanal|off [HH:MM]`',
'- Web: `t web`'
].join('\n');
}
@ -46,7 +46,7 @@ function getFullHelp(): string {
' · Tomar: `tomar`, `claim`',
' · Soltar: `soltar`, `unassign`',
'Preferencias:',
' · `t configurar diario|l-v|semanal|off [HH:MM]`',
' · `t alarma diario|l-v|semanal|off [HH:MM]`',
'Fechas:',
' · `YYYY-MM-DD` o `YY-MM-DD` → `20YY-MM-DD` (ej.: 27-09-04)',
' · Palabras: `hoy`, `mañana`',
@ -151,8 +151,8 @@ export async function route(context: RouteContext, deps?: { db: Database }): Pro
if (action === 'tomar') { trackOnboarding(); return await handleTomar(context as unknown as TomarCtx); }
if (action === 'soltar') { trackOnboarding(); return await handleSoltar(context as unknown as SoltarCtx); }
// --- configurar / web ---
if (action === 'configurar') { trackOnboarding(); return handleConfigurar(context as unknown as ConfigurarCtx, { db: database }); }
// --- alarma / web ---
if (action === 'alarma') { trackOnboarding(); return handleAlarma(context as unknown as ConfigurarCtx, { db: database }); }
if (action === 'web') { trackOnboarding(); return await handleWeb(context as unknown as WebCtx, { db: database }); }
// --- desconocido ---

@ -38,8 +38,10 @@ export const ACTION_ALIASES: Record<string, string> = {
'help': 'ayuda',
'info': 'ayuda',
'?': 'ayuda',
'config': 'configurar',
'configurar': 'configurar',
'config': 'alarma',
'configurar': 'alarma',
'alarma': 'alarma',
'recordatorio': 'alarma',
'web': 'web'
};

@ -213,7 +213,7 @@ function buildOnboardingMessage2(): string {
Puedes interactuar con el bot escribiéndome por privado:
- Ver todas las tareas: ${code('t todas')}
- Ver solo tus tareas: ${code('t mias')}
- ¿Quieres recordatorios?: ${code('t configurar diario|l-v|semanal|off')}
- ¿Quieres recordatorios?: ${code('t alarma diario|l-v|semanal|off')}
- Web: ${code('t web')}`;
}

@ -4,7 +4,7 @@ import { initializeDatabase } from '../../../src/db';
import { CommandService } from '../../../src/services/command';
import { setDb } from '../../../src/db/locator';
describe('CommandService - configurar recordatorios', () => {
describe('CommandService - alarma recordatorios', () => {
let memdb: Database;
const SENDER = '34600123456';
@ -39,8 +39,8 @@ describe('CommandService - configurar recordatorios', () => {
return { freq: String(row.freq), time: row.time ? String(row.time) : null };
}
it('configurar daily guarda preferencia y responde confirmación', async () => {
const res = await runCmd('t configurar daily');
it('alarma daily guarda preferencia y responde confirmación', async () => {
const res = await runCmd('t alarma daily');
expect(res).toHaveLength(1);
expect(res[0].recipient).toBe(SENDER);
expect(res[0].message).toContain('✅ Recordatorios: diario');
@ -51,8 +51,8 @@ describe('CommandService - configurar recordatorios', () => {
expect(pref!.time).toBe('08:30'); // default
});
it('configurar weekly guarda preferencia y responde confirmación', async () => {
const res = await runCmd('t configurar weekly');
it('alarma weekly guarda preferencia y responde confirmación', async () => {
const res = await runCmd('t alarma weekly');
expect(res).toHaveLength(1);
expect(res[0].message).toContain('semanal (lunes 08:30)');
@ -61,8 +61,8 @@ describe('CommandService - configurar recordatorios', () => {
expect(pref!.freq).toBe('weekly');
});
it('configurar off guarda preferencia y responde confirmación', async () => {
const res = await runCmd('t configurar off');
it('alarma off guarda preferencia y responde confirmación', async () => {
const res = await runCmd('t alarma off');
expect(res).toHaveLength(1);
expect(res[0].message).toContain('apagado');
@ -71,27 +71,27 @@ describe('CommandService - configurar recordatorios', () => {
expect(pref!.freq).toBe('off');
});
it('configurar con opción inválida devuelve uso correcto y no escribe en DB', async () => {
const res = await runCmd('t configurar foo');
it('alarma con opción inválida devuelve uso correcto y no escribe en DB', async () => {
const res = await runCmd('t alarma foo');
expect(res).toHaveLength(1);
expect(res[0].message).toContain('Uso: `t configurar diario|l-v|semanal|off [HH:MM]`');
expect(res[0].message).toContain('Uso: `t alarma diario|l-v|semanal|off [HH:MM]`');
const pref = getPref();
expect(pref).toBeNull();
});
it('upsert idempotente: cambiar de daily a off actualiza la fila existente', async () => {
await runCmd('t configurar daily');
await runCmd('t alarma daily');
let pref = getPref();
expect(pref!.freq).toBe('daily');
await runCmd('t configurar off');
await runCmd('t alarma off');
pref = getPref();
expect(pref!.freq).toBe('off');
});
it('configurar l-v con hora guarda weekdays y respeta hora', async () => {
const res = await runCmd('t configurar l-v 8:00');
it('alarma l-v con hora guarda weekdays y respeta hora', async () => {
const res = await runCmd('t alarma l-v 8:00');
expect(res).toHaveLength(1);
expect(res[0].recipient).toBe(SENDER);
expect(res[0].message).toContain('laborables');

@ -34,6 +34,6 @@ describe('CommandService - comando desconocido devuelve ayuda rápida', () => {
expect(msg).toContain('t info');
expect(msg).toContain('t mias');
expect(msg).toContain('t web');
expect(msg).toContain('t configurar');
expect(msg).toContain('t alarma');
});
});

Loading…
Cancel
Save