diff --git a/src/services/reminders.ts b/src/services/reminders.ts index 175f213..b191f03 100644 --- a/src/services/reminders.ts +++ b/src/services/reminders.ts @@ -111,6 +111,7 @@ export class RemindersService { try { const allItems = TaskService.listUserPending(pref.user_id, 10); const items = enforce ? allItems.filter(t => !t.group_id || AllowedGroups.isAllowed(t.group_id)) : allItems; + const total = TaskService.countUserPending(pref.user_id); if (!items || items.length === 0) { // No enviar si no hay tareas; no marcamos last_reminded_on para permitir enviar si aparecen más tarde hoy continue; @@ -159,7 +160,10 @@ export class RemindersService { sections.push(...rendered); } - // No contamos "total" global para evitar inconsistencias de grupos bloqueados; dejamos el resumen por ítems visibles. + // Si hay más tareas de las listadas (tope), añadir resumen + if (total > items.length) { + sections.push(italic(`… y ${total - items.length} más`)); + } // (Etapa 3) Sección opcional de "sin responsable" filtrada por membresía activa + snapshot fresca. const includeUnassigned = String(process.env.REMINDERS_INCLUDE_UNASSIGNED_FROM_MEMBERSHIP || '').toLowerCase() === 'true'; diff --git a/tests/unit/tasks/service.gating.test.ts b/tests/unit/tasks/service.gating.test.ts index 6b36194..39a1d39 100644 --- a/tests/unit/tasks/service.gating.test.ts +++ b/tests/unit/tasks/service.gating.test.ts @@ -5,21 +5,54 @@ import { TaskService } from '../../../src/tasks/service'; import { AllowedGroups } from '../../../src/services/allowed-groups'; function seedGroup(db: Database, groupId: string) { - // Intento genérico de seed para la tabla groups con columnas comunes + // Sembrado robusto: cubrir columnas NOT NULL sin valor por defecto const cols = db.query(`PRAGMA table_info(groups)`).all() as any[]; - const colNames = cols.map(c => String(c.name)); const values: Record = {}; - for (const c of colNames) { - if (c === 'id') values[c] = groupId; - else if (c === 'name' || c === 'title' || c === 'subject') values[c] = 'Test Group'; - else if (c === 'is_active' || c === 'active') values[c] = 1; - else if (c.endsWith('_at')) values[c] = new Date().toISOString().replace('T', ' ').replace('Z', ''); - else if (c === 'created_by') values[c] = 'tester'; - // Para otras columnas dejaremos NULL si lo permite + const nowIso = new Date().toISOString().replace('T', ' ').replace('Z', ''); + + for (const c of cols) { + const name = String(c.name); + const type = String(c.type || '').toUpperCase(); + const notnull = Number(c.notnull || 0) === 1; + const hasDefault = c.dflt_value != null; + + if (name === 'id') { + values[name] = groupId; + continue; + } + + // Preconfigurar algunos alias comunes + if (name === 'name' || name === 'title' || name === 'subject') { + values[name] = 'Test Group'; + continue; + } + if (name === 'created_by') { + values[name] = 'tester'; + continue; + } + if (name.endsWith('_at')) { + values[name] = nowIso; + continue; + } + if (name === 'is_active' || name === 'active') { + values[name] = 1; + continue; + } + + // Para columnas NOT NULL sin valor por defecto, asignar valores genéricos + if (notnull && !hasDefault) { + if (type.includes('INT')) values[name] = 1; + else if (type.includes('REAL')) values[name] = 0; + else values[name] = 'N/A'; + } } + + // Asegurar que id esté siempre + if (!('id' in values)) values['id'] = groupId; + const colsList = Object.keys(values); const placeholders = colsList.map(() => '?').join(', '); - const sql = `INSERT OR IGNORE INTO groups (${colsList.join(', ')}) VALUES (${placeholders})`; + const sql = `INSERT OR REPLACE INTO groups (${colsList.join(', ')}) VALUES (${placeholders})`; db.prepare(sql).run(...colsList.map(k => values[k])); }