Cliente: NutriFlow Analytics · Node.js 20 + TypeScript + PostgreSQL 15 + Redis 7
null en lugar de [] cuando no hay registros en el rango
macros.map() llama a null → TypeError
page.pdf()
| Evidencia | Tipo | H1 Datos | H2 Estado | H3 Integr. | H4 Lógica | H5 Recurs. | H6 Entorno |
|---|---|---|---|---|---|---|---|
| macro.repository.ts:156 retorna null | DIRECTA | C | ? | N | N | ? | N |
| report.service.ts:216 .map() en null | DIRECTA | C | ? | N | N | N | N |
| Redis hit rate 94%→71% tras v2.4.1 | CORREL. | P | C | ? | N | P | N |
| TTL cambiado 3600→300s en cache/config.ts:34 | DIRECTA | N | C | N | N | ? | N |
| Fallo 35% no determinista en mismo input | CORREL. | P | C | P | N | P | N |
| Stack trace falla antes de llegar a Puppeteer | CONTRADICT. | C | ? | N | ? | ? | ? |
| Clínicas >500 pacientes más afectadas | CORREL. | C | C | ? | N | P | N |
| Score (C=3, P=1, N=0, ?=0.5) | 21 | 18 | 5 | 0 | 7 | 0 |
findByDateRange que retorna null (no []) cuando no hay macros en el rangomacros.map(m => m.macros) en línea 216 lanza TypeError porque macros es nullEl 65% de las llamadas tiene cache hit (los datos ya están) y funciona. El 35% ocurre en el momento exacto de expiración/miss donde findByDateRange es invocado — y si el paciente no tiene datos en el rango solicitado, retorna null.
Descartada como causa raíz. El bug ocurre upstream en la capa de servicio, antes de invocar PDF generation. Puppeteer v22 puede tener otros issues pero no causa este bug.
El repositorio debe retornar array vacío, no null. Una línea de fix.
// src/repositories/macro.repository.ts:156
async findByDateRange(patientId, range) {
const rows = await db.query(SQL, [patientId, range]);
return rows ?? []; // garantiza array, nunca null
}
Aunque el fix 1 resuelve la causa raíz, añadir defensa en profundidad.
// src/services/report.service.ts:215-217
const macros = await db.macros.findByDateRange(
patientId, dateRange
) ?? [];
const data = {
macros: macros.map(m => m.macros), // seguro
summary: calculateSummary(macros)
};
Revertir TTL a 3600s o usar mutex distribuido para evitar thundering herd.
// src/cache/config.ts:34
const REPORT_CACHE_TTL = 3600; // revertido
// src/services/report.service.ts:211
const lock = await redis.set(
`lock:report:${patientId}`, 1,
'NX', 'EX', 10
);
if (!lock) return waitForCache(patientId);
Añadir tests y métricas de conexiones de pool para el H5 inconcluso.
// tests/report.service.test.ts
it('devuelve data vacía si no hay macros', async () => {
mockRepo.findByDateRange.mockResolvedValue(null);
const result = await buildReportData('p1', range);
expect(result.macros).toEqual([]);
expect(result.summary).toBeDefined();
});