🔍
PR #47 — feat: sistema de recomendaciones de dieta basado en IA
NutriFlow · SaaS de nutrición B2B · revisado por Ana Blanco · 16 jun 2026
🔄 Cambios requeridos +342 / -18
AB
Ana Blanco
Senior Developer · revisora asignada
🔄 Cambios requeridos
📁 4 archivos cambiados
~35 min de revisión
📏 342 líneas añadidas
🔗 Issue #32 vinculado
✅ CI pasando
📋Proceso de revisión
1
Contexto y alcance
2 min
  • PR description leída — feature de IA bien descrita
  • Issue #32 vinculado — requisito de negocio claro
  • !
    342 líneas — en límite superior; aceptable dado el scope
  • CI pasando — tests unitarios existentes en verde
  • Sin design doc previo para el microservicio IA — riesgo arquitectural
2
Alto nivel — Arquitectura y diseño
8 min
  • Separación de responsabilidades: router / service / component
  • Endpoint público sin autenticación — riesgo de seguridad crítico
  • URL del microservicio hardcodeada — no sigue patrón existente de env vars
  • !
    Ausencia de capa de caché — el microservicio IA tiene coste por llamada
  • Componente UI bien aislado, reutilizable
3
Línea a línea
20 min
  • SQL injection en query directa con string interpolation
  • Tipos any en 2 funciones — rompe type safety
  • !
    allergies como string en vez de array — API incompleta
  • !
    fetch sin timeout ni error handling
  • Componente RecommendationCard tipado correctamente
  • Nombres de variables descriptivos en UI
4
Tests
5 min
  • !
    Test happy path existe pero assertions demasiado vagas
  • Sin test de autenticación (usuario no logado)
  • Sin test de paciente inexistente
  • Sin test de fallo del microservicio IA
  • Test con nombre descriptivo (generates recommendation)
💬Comentarios inline
🔴 blocking src/api/recommendations.ts línea 12
SQL injection crítica. La query construye la sentencia SQL con interpolación de strings directamente desde el input del usuario. Cualquier valor en patientId o data.result puede ejecutar SQL arbitrario en la base de datos. Esto es una vulnerabilidad OWASP Top 10. ¿Qué pasaría si patientId contiene '; DROP TABLE recommendations;--?
@@ recommendations.ts:12
-
await db.query(`INSERT INTO recommendations VALUES ('${input.patientId}', '${data.result}')`);
+
await db.query(
+
'INSERT INTO recommendations (patient_id, result) VALUES ($1, $2)',
+
[input.patientId, data.result]
+
);
🔴 blocking src/api/recommendations.ts línea 3
El procedimiento está usando publicProcedure, lo que significa que cualquier usuario sin autenticar puede generar recomendaciones y escribir en la BD. Debe ser protectedProcedure para que tRPC valide el token de sesión automáticamente. Adicionalmente, habría que verificar que el patientId pertenece al médico autenticado.
@@ recommendations.ts:3
-
generate: publicProcedure
+
generate: protectedProcedure // ctx.session.user disponible
🔴 blocking src/services/dietAI.ts líneas 1, 3
El tipo any en el input y el retorno elimina todo el beneficio de TypeScript. Cuando usamos any, el compilador deja de detectar errores, y perdemos autocompletado. El microservicio IA ya tiene un contrato definido — ¿podemos extraer los tipos de la especificación OpenAPI del servicio o definirlos aquí?
@@ dietAI.ts:1
-
export async function generate(input: any) {
+
interface DietInput { patientId: string; allergies: string[]; preferences?: string[]; }
+
interface DietRecommendation { meals: Meal[]; calories: number; notes: string; }
+
export async function generate(input: DietInput): Promise<DietRecommendation> {
🔴 blocking src/services/dietAI.ts línea 5
La URL del microservicio está hardcodeada como http://ai-service/recommend. En producción apunta a otra URL, y en staging a otra. El resto del proyecto usa process.env para URLs externas (ver src/services/stripe.ts). Además, no hay manejo de errores si el servicio falla o tarda más de lo esperado — esto podría dejar requests colgadas. Sugiero añadir un timeout y un bloque try/catch.
@@ dietAI.ts:5
-
const res = await fetch('http://ai-service/recommend', {
-
method: 'POST',
+
const AI_URL = process.env.DIET_AI_SERVICE_URL;
+
if (!AI_URL) throw new Error('DIET_AI_SERVICE_URL not configured');
+
const controller = new AbortController();
+
const timeout = setTimeout(() => controller.abort(), 10_000);
+
const res = await fetch(`${AI_URL}/recommend`, {
+
method: 'POST', signal: controller.signal,
🟡 important src/api/recommendations.ts línea 8
El campo allergies está tipado como string pero conceptualmente es una lista (un paciente puede tener varias alergias). Si lo enviamos como string ahora, migrar después rompería la API y los datos existentes. ¿Podemos cambiar a z.array(z.string()) desde el inicio? El UI ya manda múltiples values según el issue #32.
@@ recommendations.ts:8
-
allergies: z.string(),
+
allergies: z.array(z.string()).default([]),
🟡 important tests/recommendations.test.ts líneas 1–10
El test actual solo verifica que la función devuelve algo definido, pero no comprueba nada del contrato. Hay tres casos que necesitan cobertura antes del merge: (1) usuario no autenticado debe recibir UNAUTHORIZED, (2) patientId inexistente debe devolver NOT_FOUND, (3) si el microservicio IA falla, el endpoint debe devolver un error manejable y no crashear. ¿Puedo ayudarte con los mocks? Podemos hacer pair en esto.
🎉 praise src/components/RecommendationCard.tsx todo el archivo
Excelente trabajo con el componente. La interface RecommendationProps está bien definida, la separación entre MealList y CalorieSummary es exactamente el nivel de abstracción correcto, y el componente no tiene side effects. Esto es exactamente como deberían ser nuestros componentes de presentación. Muy buen progreso desde las primeras PRs.
💡 suggestion src/services/dietAI.ts nivel arquitectura
No es bloqueante, pero el microservicio IA cobra por llamada (~0,02€). Para el mismo paciente con el mismo perfil, la recomendación no cambia en un día. ¿Podríamos añadir un TTL de caché en Redis usando la key recommendation:{patientId}:{date}? Estimado del ahorro: ~60% de llamadas reducidas. Lo dejo como suggestion para una PR de seguimiento si prefieres.
📊Resumen de la revisión

✅ Fortalezas

  • Componente UI bien estructurado y tipado
  • Separación de capas (router / service / UI)
  • CI en verde, test base existe
  • Nombres descriptivos en el componente
  • PR bien descrita con contexto de negocio

🔴 Cambios requeridos

  • SQL injection — prioritario, riesgo alto
  • Endpoint sin autenticación
  • Tipos any en service layer
  • URL hardcodeada + sin error handling
  • Tests insuficientes (auth, errores, edge cases)

🔄 Solicitud de cambios — no aprobar hasta resolver 4 blocking issues

Carlos, el trabajo general muestra una comprensión sólida de la separación de capas y el componente UI es un gran ejemplo de cómo estructurar presentación. Los issues bloqueantes son todos patrones conocidos del equipo — la SQL injection y la autenticación son especialmente críticos antes de que esto llegue a producción con datos reales de pacientes. Puedo hacer pair contigo en los tests y el manejo de errores si te es útil. Una vez resueltos los 4 blocking, apruebo con los suggestions opcionales para otra PR.