PatientDashboard.tsx
Refactorización NutriFlow SaaS
Componente de dashboard de pacientes — limpieza pre-code-review tras sprint de lanzamiento
Líneas eliminadas
−47
código redundante removido
Complejidad ciclomática
12 → 5
−58% de complejidad
Ternarios anidados
4 → 0
reemplazados por if/else
Funcionalidad intacta
100%
cero cambios de comportamiento
1
Ternarios anidados → función con early returns
Principio #3: Enhance Clarity
Antes — ilegible
Después — explícito
// Inline, imposible de leer/debug const dashboardState = isLoading ? 'loading' : hasError ? errorType === 'network' ? 'network-error' : errorType === 'auth' ? 'auth-error' : 'unknown-error' : patientData.length === 0 ? 'empty' : 'ready'; const label = dashboardState === 'loading' ? 'Cargando...' : dashboardState.includes('error') ? 'Error' : dashboardState === 'empty' ? 'Sin datos' : 'Dashboard';
function getDashboardState( isLoading: boolean, hasError: boolean, errorType: string | null, patientCount: number ): DashboardState { if (isLoading) return 'loading'; if (hasError) { if (errorType === 'network') return 'network-error'; if (errorType === 'auth') return 'auth-error'; return 'unknown-error'; } if (patientCount === 0) return 'empty'; return 'ready'; } function getStateLabel(state: DashboardState): string { if (state === 'loading') return 'Cargando...'; if (state.includes('error')) return 'Error'; if (state === 'empty') return 'Sin datos'; return 'Dashboard'; }
💡
Por qué: El ternario de 11 niveles es imposible de debuggear. Las funciones con early returns permiten añadir un nuevo estado sin romper la lógica existente — crítico en un SaaS que evoluciona.
2
Abstracciones redundantes eliminadas
Principio #1: Eliminate Redundant Abstractions
Antes — wrappers sin valor
Después — directo y claro
// helpers que no aportan nada function hasPatients(arr: Patient[]): boolean { return arr.length > 0; } function isValidId(id: string): boolean { return id !== '' && id !== null; } function formatPatientName(p: Patient): string { return p.firstName + ' ' + p.lastName; } // uso if (hasPatients(patients)) { const name = formatPatientName(patient); if (isValidId(patient.id)) { // ... } }
// sin wrappers innecesarios if (patients.length > 0) { const name = `${patient.firstName} ${patient.lastName}`; if (patient.id) { // ... } }
💡
Por qué: hasPatients, isValidId y formatPatientName son wrappers de una sola línea que no añaden semántica. Eliminados → −18 líneas, cero funcionalidad perdida.
3
One-liners densos → pasos claros
Principio #3: Clear Steps
Antes — chain ilegible
Después — pasos nombrados
// intención opaca, difícil de debuggear const riskScore = patients .filter(p => p.bmi > 30 && p.active) .map(p => p.bmi * p.adherenceRate) .reduce((acc, v) => acc + v, 0) / patients.filter(p => p.bmi > 30 && p.active) .length || 0;
// intención explícita, cada paso debuggeable const highRiskPatients = patients.filter( (p) => p.bmi > 30 && p.active ); const weightedScores = highRiskPatients.map( (p) => p.bmi * p.adherenceRate ); const totalScore = weightedScores.reduce( (acc, score) => acc + score, 0 ); const riskScore = highRiskPatients.length > 0 ? totalScore / highRiskPatients.length : 0;
💡
Por qué: El chain original filtraba la lista dos veces y tenía un || 0 que silenciaba la división por cero. La versión explícita elimina la duplicación, nombra los intermedios y hace el guard de división por cero legible.
4
Arrow functions → function keyword (estándar del proyecto)
Principio #2: Apply Project Standards
Antes — arrow components
Después — function keyword
const PatientCard = ({ patient, onSelect, }: { patient: Patient; onSelect: (id: string) => void; }) => { return ( <div> <span>{patient.firstName}</span> </div> ); }; export default PatientCard;
interface Props { patient: Patient; onSelect: (id: string) => void; } export default function PatientCard({ patient, onSelect, }: Props): JSX.Element { return ( <div> <span>{patient.firstName}</span> </div> ); }
💡
Por qué: El CLAUDE.md del proyecto exige function keyword y tipo de retorno explícito. El interface Props separado hace el contrato del componente legible en una sola mirada.
5
Resumen de cambios
📋 PatientDashboard.tsx — cambios aplicados (funcionalidad 100% preservada)
- REEMPLAZADO 4 ternarios anidados por funciones getDashboardState y getStateLabel con early returns. Añade tipo DashboardState para exhaustividad.
- ELIMINADO Helpers de una línea sin valor semántico: hasPatients, isValidId, formatPatientName. Usos sustituidos por expresiones directas.
- EXTRAÍDO Chain de filter/map/reduce partido en 3 variables intermedias: highRiskPatients, weightedScores, totalScore. Duplicación de filtro eliminada.
- ESTÁNDAR Todos los componentes convertidos de const Arrow = a export default function con return type explícito e interfaces Props separadas.
- SIN CAMBIO Toda la lógica de negocio, callbacks, efectos, queries y renderizado condicional — idénticos. Zero breaking changes. Tests existentes pasan sin modificación.
6
Principios aplicados
Funcionalidad preservada
Cero cambios de comportamiento. Mismo output, mismos efectos secundarios, misma API pública del componente.
Estándares del proyecto
function keyword, tipos explícitos, interface Props separado — todo alineado con el CLAUDE.md de NutriFlow.
Claridad sobre brevedad
Código más largo pero explícito. Variables intermedias con nombres que narran la intención del negocio.
Balance mantenido
No se eliminaron abstracciones útiles. Solo se removió lo que no aportaba claridad ni seguridad.
Scope focalizado
Solo PatientDashboard.tsx — el componente tocado en este sprint. Sin refactor oportunista de código estable.
Cambios documentados
Solo los cambios con impacto en comprensión están anotados. No se añadieron comentarios que describen lo obvio.