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. - [ ] Logs accesibles; worker de cola activo.
- Funcional - Funcional
- [ ] Bot añadido a los grupos; grupos activos sincronizados. - [ ] 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 - Comunicación/privacidad
- [ ] Aviso a la junta: cómo usar, qué se guarda, cómo desactivar recordatorios. - [ ] 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 - Ejemplo: t soltar 26
- Idempotente: si no estabas asignado, lo indica sin error. La tarea puede quedar “sin responsable” si no quedan asignados. - Idempotente: si no estabas asignado, lo indica sin error. La tarea puede quedar “sin responsable” si no quedan asignados.
- Configurar recordatorios - Configurar recordatorios
- Aliases: configurar, config - Aliases: alarma, recordatorio, configurar, config
- Opciones: daily | weekly | off - Opciones: daily | weekly | off
- Ejemplos: - Ejemplos:
- t configurar daily - t alarma daily
- t configurar weekly - t alarma weekly
- t configurar off - 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. - 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 - Ayuda
- Aliases: ayuda, help, ? - Aliases: ayuda, help, ?
@ -80,7 +80,7 @@ Ejemplos prácticos
- t tomar 42 - t tomar 42
- t soltar 42 - t soltar 42
- Configurar recordatorios: - Configurar recordatorios:
- t configurar weekly - t alarma weekly
Limitaciones y notas Limitaciones y notas
- El bot no publica en grupos por diseño. - 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` Alias: `alarma`, `recordatorio`, `configurar`, `config`
Sintaxis: `t configurar diario|l-v|semanal|off [HH:MM]` Sintaxis: `t alarma diario|l-v|semanal|off [HH:MM]`
Valores admitidos y alias Valores admitidos y alias
- `diario`/`diaria` → recordatorio diario (se guarda como `daily`). - `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. - 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 Ejemplos
- `t configurar diaria 09:00` - `t alarma diaria 09:00`
- `t configurar l-v 08:30` - `t alarma l-v 08:30`
- `t configurar semanal` (→ lunes 08:30) - `t alarma semanal` (→ lunes 08:30)
- `t configurar off` - `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 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 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 soltar 26`",
" Uso: `t configurar diario|l-v|semanal|off [HH:MM]`" " Uso: `t alarma diario|l-v|semanal|off [HH:MM]`"
], ],
"errors": [ "errors": [
"⚠️ Tarea {id} no encontrada.", "⚠️ 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` - Comandos: `t soltar`, `t unassign`, `t dejar`, `t liberar`, `t renunciar`
- Un solo ID - Un solo ID
- Configurar recordatorios - 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 - Mapea alias a `daily`, `weekdays`, `weekly`, `off`; hora opcional con normalización
- Ayuda - Ayuda
- Comandos: `t ayuda`, `t help`, `t ?`, y variante “ayuda avanzada” - 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): - Contenido sugerido (resumen):
- Ayuda rápida: - Ayuda rápida:
- Secciones: “COMANDOS BÁSICOS”, “LISTADOS”, “ACCESO WEB” - 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._ - Nota: _El bot responde por DM, incluso si escribes desde un grupo._
- Ayuda extendida: - 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”. - 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 Objetivos
- Enviar un paquete de 2 DMs (Mensaje 1 + Mensaje 2) por usuario cuando se crea una tarea en un grupo. - 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 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). - 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. - 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): - Mensaje 2 (minichuleta; se envía tras 510 s, en ambos disparos):
- “Guía rápida (este es un mensaje único): - “Guía rápida (este es un mensaje único):
· Tus tareas: t mias · Todas: t todas · Tus tareas: t mias · Todas: t todas
· Recordatorios: t configurar diario | lv | semanal | off · Recordatorios: t alarma diario | lv | semanal | off
· Web: t web” · Web: t web”
Impacto en tests 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). - “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). - 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. - 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) 2) Onboarding — Mensaje 2 (reminder, único)
- “Guía rápida (este es un mensaje único): - “Guía rápida (este es un mensaje único):
· Tus tareas: t mias · Todas: t todas · Tus tareas: t mias · Todas: t todas
· Recordatorios: t configurar diario | lv | semanal | off · Recordatorios: t alarma diario | lv | semanal | off
· Web: t web” · Web: t web”
3) Transición cuando se intenta listar desde grupo (responder por DM) 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 - completar.ts
- tomar.ts - tomar.ts
- soltar.ts - soltar.ts
- configurar.ts - alarma.ts
- web.ts - web.ts
- src/services/onboarding.ts ← JIT y bundle de 2 DMs (disparado desde “nueva”) - 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) - (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. - 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 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`. - web.ts: invalidar tokens vigentes, generar token, hash (sha256), insertar y construir URL. Métrica `web_tokens_issued_total`.
- Conectar ambos en el router. - Conectar ambos en el router.
@ -110,7 +110,7 @@ Etapa 8 — Hardening y observabilidad
## Detalles técnicos por handler ## Detalles técnicos por handler
- configurar: - configurar (alarma):
- Validar mapa de alias: diario/daily, lv/weekdays, semanal/weekly, off/apagar/ninguno. - Validar mapa de alias: diario/daily, lv/weekdays, semanal/weekly, off/apagar/ninguno.
- Hora opcional `HH:MM` (clamp 0023; minutos 0059). - Hora opcional `HH:MM` (clamp 0023; minutos 0059).
- Upsert con `last_reminded_on` a NULL al cambiar frecuencia. - Upsert con `last_reminded_on` a NULL al cambiar frecuencia.
@ -129,7 +129,7 @@ Etapa 8 — Hardening y observabilidad
## Utilidades compartidas (shared.ts) ## 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. - ymdInTZ(d, TZ) y todayYMD.
- formatters: reutilizar `formatDDMM`, `codeId`, `padTaskId`, `bold`, `italic`, `code`, `section`. - formatters: reutilizar `formatDDMM`, `codeId`, `padTaskId`, `bold`, `italic`, `code`, `section`.
- parseMultipleIds(tokens: string[], max=10): retorna números válidos > 0, dedup, truncado. - 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 - Sufijo “... y N más” si aplica
- Ayuda rápida: - Ayuda rápida:
- Secciones: “COMANDOS BÁSICOS”, “LISTADOS”, “ACCESO WEB” - 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 Localización
- Todo copy en español. Evitar fugas de claves internas en inglés (ej. “weekly”). - 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 n Descripción 2025-11-05 @Ana`
- `t ver` (en grupo) · `t ver mis` (DM) · `t ver todos` - `t ver` (en grupo) · `t ver mis` (DM) · `t ver todos`
- `t x 26` · `t tomar 12` - `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` - `t web`
_El bot responde por DM, incluso si escribes desde un grupo._ _El bot responde por DM, incluso si escribes desde un grupo._
``` ```

@ -13,7 +13,7 @@ type Msg = {
mentions?: string[]; 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 tokens = (context.message || '').trim().split(/\s+/);
const optRaw = (tokens[2] || '').toLowerCase(); const optRaw = (tokens[2] || '').toLowerCase();
@ -41,7 +41,7 @@ export function handleConfigurar(context: Ctx, deps: { db: Database }): Msg[] {
if (!m) { if (!m) {
return [{ return [{
recipient: context.sender, 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))); 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) { if (!freq) {
return [{ return [{
recipient: context.sender, 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) * 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. * Nota: No importar CommandService aquí para evitar ciclos de import.
*/ */
import type { Database } from 'bun:sqlite'; import type { Database } from 'bun:sqlite';
import { ACTION_ALIASES } from './shared'; import { ACTION_ALIASES } from './shared';
import { handleConfigurar } from './handlers/configurar'; import { handleAlarma } from './handlers/alarma';
import { handleWeb } from './handlers/web'; import { handleWeb } from './handlers/web';
import { handleVer } from './handlers/ver'; import { handleVer } from './handlers/ver';
import { handleCompletar } from './handlers/completar'; import { handleCompletar } from './handlers/completar';
@ -17,7 +17,7 @@ type VerCtx = Parameters<typeof handleVer>[0];
type CompletarCtx = Parameters<typeof handleCompletar>[0]; type CompletarCtx = Parameters<typeof handleCompletar>[0];
type TomarCtx = Parameters<typeof handleTomar>[0]; type TomarCtx = Parameters<typeof handleTomar>[0];
type SoltarCtx = Parameters<typeof handleSoltar>[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]; type WebCtx = Parameters<typeof handleWeb>[0];
import { ResponseQueue } from '../response-queue'; import { ResponseQueue } from '../response-queue';
import { isGroupId } from '../../utils/whatsapp'; import { isGroupId } from '../../utils/whatsapp';
@ -31,7 +31,7 @@ function getQuickHelp(): string {
'- Crear: `t n Descripción 2028-11-26 @Ana`', '- Crear: `t n Descripción 2028-11-26 @Ana`',
'- Completar: `t x 123`', '- Completar: `t x 123`',
'- Tomar: `t tomar 12`', '- 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`' '- Web: `t web`'
].join('\n'); ].join('\n');
} }
@ -46,7 +46,7 @@ function getFullHelp(): string {
' · Tomar: `tomar`, `claim`', ' · Tomar: `tomar`, `claim`',
' · Soltar: `soltar`, `unassign`', ' · Soltar: `soltar`, `unassign`',
'Preferencias:', 'Preferencias:',
' · `t configurar diario|l-v|semanal|off [HH:MM]`', ' · `t alarma diario|l-v|semanal|off [HH:MM]`',
'Fechas:', 'Fechas:',
' · `YYYY-MM-DD` o `YY-MM-DD` → `20YY-MM-DD` (ej.: 27-09-04)', ' · `YYYY-MM-DD` o `YY-MM-DD` → `20YY-MM-DD` (ej.: 27-09-04)',
' · Palabras: `hoy`, `mañana`', ' · 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 === 'tomar') { trackOnboarding(); return await handleTomar(context as unknown as TomarCtx); }
if (action === 'soltar') { trackOnboarding(); return await handleSoltar(context as unknown as SoltarCtx); } if (action === 'soltar') { trackOnboarding(); return await handleSoltar(context as unknown as SoltarCtx); }
// --- configurar / web --- // --- alarma / web ---
if (action === 'configurar') { trackOnboarding(); return handleConfigurar(context as unknown as ConfigurarCtx, { db: database }); } 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 }); } if (action === 'web') { trackOnboarding(); return await handleWeb(context as unknown as WebCtx, { db: database }); }
// --- desconocido --- // --- desconocido ---

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

@ -213,7 +213,7 @@ function buildOnboardingMessage2(): string {
Puedes interactuar con el bot escribiéndome por privado: Puedes interactuar con el bot escribiéndome por privado:
- Ver todas las tareas: ${code('t todas')} - Ver todas las tareas: ${code('t todas')}
- Ver solo tus tareas: ${code('t mias')} - 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')}`; - Web: ${code('t web')}`;
} }

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

Loading…
Cancel
Save