📋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.