- Código muerto – reexport innecesaria:
IrrigationLineCardse vuelve a exportar desde este índice pero ningún archivo importa desdecomponents/index. Todos los consumidores hacenimport ... from "@/components/ui/IrrigationLineCard", por lo que la reexport queda huérfana.- Sugerencia: eliminar la reexport o reemplazar los imports de la app para centralizarlos en
components/index.ts, evitando tener dos rutas distintas para el mismo componente.
- Sugerencia: eliminar la reexport o reemplazar los imports de la app para centralizarlos en
- Código muerto – export default sin uso: El archivo exporta
export default app;, pero no existe ningúnimport app from "@/lib/firebase". Esto mantiene código sin ejecutar y puede generar confusión si se asume que la instancia default se usa en algún sitio.- Sugerencia: remover el
export defaulto cambiar los consumidores aimport app from "@/lib/firebase"si realmente se requiere exponer la instancia.
- Sugerencia: remover el
- Código muerto –
adminAppsin consumidores externos: Sólo se importadbdesde este módulo (app/api/ingest/route.ts).adminAppqueda exportado pero nunca se utiliza.- Sugerencia: eliminar la exportación o exponer utilidades adicionales (por ejemplo auth admin) que justifiquen el símbolo.
- Código muerto – tipos exportados sin consumo: La interfaz
IrrigationLinese exporta pero únicamente se usa dentro del propio archivo.- Sugerencia: quitar el
exporto mover el tipo a un módulo compartido si se espera usarlo en componentes comoIrrigationLineCard.
- Sugerencia: quitar el
- Violación DRY – patrón de suscripción repetido: Este hook replica el mismo esquema
useState+onSnapshot+setLoading/setErrorque también está enuseNotifications,useUsers,useSensorsyuseSystemConfig.- Sugerencia: extraer un helper (
useFirestoreSubscription) que reciba la referencia y un mapper; así sólo se define la transformación específica y se reutiliza el manejo de estados y errores.
- Sugerencia: extraer un helper (
- Código muerto – tipos sin uso externo:
NotificationTypeyNotificationDatase exportan pero no se consumen fuera del hook; sóloNotificationse importa enapp/(dashboard)/layout.tsx.- Sugerencia: dejar los tipos como internos o moverlos a un módulo dedicado a tipados compartidos.
- Violación DRY: La lógica de
onSnapshot,loading,errorycleanupes casi idéntica a la de los demás hooks de Firestore.- Sugerencia: reutilizar el helper propuesto arriba para mantener un único lugar con el manejo genérico de suscripciones.
- Código muerto – import y variable sin uso: Se importa
wherey se calculaconst startTimestamp = Timestamp.fromDate(startDate);pero ninguna de las dos referencias se usa después (ESLint lo reporta en la ejecución debun run lint:web).- Sugerencia: reintroducir el filtro
where("timestamp", ">=", startTimestamp)en la query para aprovechar la fecha calculada y evitar traer datos innecesarios; esto anula los warnings y mejora la performance.
- Sugerencia: reintroducir el filtro
- Oportunidad de rendimiento: Actualmente se descargan todas las lecturas de la colección, se filtran en el cliente y se registran múltiples
console.logpor documento. Con históricos grandes esto provoca tráfico y ruido en consola.- Sugerencia: usar la comparación por fecha dentro de la query (como se mencionó) y eliminar los
console.logmasivos o condicionarlos a entornos de desarrollo medianteif (process.env.NODE_ENV !== "production").
- Sugerencia: usar la comparación por fecha dentro de la query (como se mencionó) y eliminar los
- Código muerto – interfaz
Sensorsin consumidores externos: Sólo el propio hook la usa.- Sugerencia: convertirla en tipo interno o moverla a un archivo común si se planea tipar los componentes de sensores.
- Violación DRY: Lógica de carga e incertidumbre similar a los otros hooks de Firestore.
- Sugerencia: reutilizar el helper de suscripciones para que el código de
useSensorsse limite al mapeo del documento.
- Sugerencia: reutilizar el helper de suscripciones para que el código de
- Código muerto – interfaz
SystemConfigsin uso externo: Sólo se usa dentro del hook; no hay importaciones desde otros módulos.- Sugerencia: eliminar el
exporto mover la interfaz a un archivo de tipos compartidos sipage.tsxu otros formularios la necesitan.
- Sugerencia: eliminar el
- Violación DRY: Comparte exactamente el mismo patrón de
doc+onSnapshot+updateConfigque otros hooks de Firestore.- Sugerencia: al extraer un helper, la lógica compartida (manejo de loading/errores) se reduce y sólo queda la parte específica del documento.
- Código muerto – interfaz
AppUsersin uso fuera del hook.- Sugerencia: convertirla en tipo local o compartirla desde un módulo de modelos.
- Violación DRY: La suscripción a
collection('users')repite el mismo boilerplate que el resto de hooks.- Sugerencia: reutilizar el helper de Firestore para evitar divergencias en el manejo de errores.
- Violación DRY – componente duplicado: Ambos archivos definen
const GoogleIcon = () => (...)con el mismo SVG (línea 14 en cada archivo).- Sugerencia: mover el ícono a
components/icons/Google.tsxo acomponents/ui/social-button.tsxpara reutilizarlo (incluyendo atributos accesibles).
- Sugerencia: mover el ícono a
- Violación DRY – layout y banners idénticos: Las secciones de hero, card y banner de error (
bg-red-50 border border-red-200 ..., líneas 227 y 232 respectivamente) son iguales en login y register, e incluso el dashboard usa el mismo banner en su estado de error.- Sugerencia: crear componentes como
<AuthShell>,<AuthErrorBanner>y<AuthHero>que reciban el contenido variable y eviten repetir 100+ líneas en cada vista.
- Sugerencia: crear componentes como
- Código muerto – banner reutilizado sin abstracción: El error container (
bg-red-50 border border-red-200 ..., línea 132) replica la misma estructura que login/register.- Sugerencia: reutilizar el mismo componente de alerta global mencionado arriba.
- Violación DRY – tarjetas de métricas repetidas: Cada tarjeta en la cuadrícula de estadísticas repite exactamente la misma estructura
bg-white p-4 ...cambiando sólo el texto y el valor.- Sugerencia: crear un
StatCardque recibatitle,valueycolorpara definir la cuadrícula desde un arreglo de configuraciones.
- Sugerencia: crear un
- Oportunidad de rendimiento – cálculos no memoizados: En cada render se ejecutan
lines.filter(...)dos veces,!lines.isActiveotra vez y unreducepara promedio. Con muchas líneas esto provoca cuatro recorridos del array en cada render.- Sugerencia: encapsular los conteos y promedios en un
useMemodependiente delines, devolviendo un objeto contotal,active,inactiveyaverageHumidity.
- Sugerencia: encapsular los conteos y promedios en un
- Oportunidad de rendimiento – callbacks inestables: Dentro del map de
linesse pasaonToggle={(checked) => handleToggleLine(line.id, checked)}. Esa función inline crea una nueva referencia en cada render, impidiendo cualquierReact.memoenIrrigationLineCard.- Sugerencia: envolver
handleToggleLineenuseCallbacky exponer un helperconst toggleLine = useCallback((lineId) => (checked) => {...}, [db])para entregar callbacks estables.
- Sugerencia: envolver
- Oportunidad de rendimiento – componente no memoizado: Aunque recibe props primitivos, el
onTogglecambia en cada render del padre, forzando renders innecesarios.- Sugerencia: envolver el componente en
React.memoy tiparonToggleconuseCallbackdesde el padre para evitar renders cuandolineno cambie.
- Sugerencia: envolver el componente en
- Oportunidad de rendimiento – componente monolítico (582 líneas): Este layout maneja autenticación, navegación, popover de notificaciones, toast en primer plano y banner FCM. Cualquier cambio en
sidebarOpen,notifications,tokenoshowNotificationToastvuelve a renderizar toda la página.- Sugerencia: dividir el archivo en subcomponentes (
DashboardSidebar,DashboardNavbar,NotificationsPopover,ForegroundToast) y envolver los que reciben props complejas enReact.memo. Además, movernavigationItemsa unuseMemodependiente derole/pathnamereduce trabajo.
- Sugerencia: dividir el archivo en subcomponentes (
- Oportunidad de rendimiento – filtrado costoso sin memo:
const filteredUsers = users.filter(...)se ejecuta en cada render, aunque sólo depende deusersysearchQuery.- Sugerencia: usar
useMemo(() => users.filter(...), [users, searchQuery])para evitar recalcular toda la lista cuando cambian estados no relacionados (por ejemplo, diálogos abiertos/cerrados).
- Sugerencia: usar
- Violación DRY – navegación de sensores repetida: Las funciones
handlePrevSensoryhandleNextSensorrepiten la misma lógica de índice (buscar el actual, validar límites, actualizar).- Sugerencia: crear un helper
const jumpSensor = (step: 1 | -1) => { ... }o unuseSensorNavigatorpara encapsular el cálculo del índice siguiente y reducir la duplicación.
- Sugerencia: crear un helper
- Oportunidad de rendimiento – renderizado del Select sin memo: El menú
<SelectItem>se recalcula completo cada vez que cambianselectedRangeoreadings, pese a depender sólo desensors.- Sugerencia: envolver el mapeo de
sensorsenuseMemoo extraerlo a un componente hijo memoizado para evitar renders innecesarios cuando llegan nuevas lecturas.
- Sugerencia: envolver el mapeo de
apps/web/app/(dashboard)/layout.tsx & apps/web/app/(dashboard)/page.tsx & apps/web/app/(auth)/*/page.tsx
- Violación DRY – banners de estado repetidos: Además del error banner, hay múltiples bloques para "Modo sin conexión", "Modo solo lectura" y mensajes vacíos con la misma estructura (
icon + título + descripción + colores).- Sugerencia: extraer un componente
StatusBannerparametrizable convariant(error,warning,info) y reutilizarlo en las distintas vistas.
- Sugerencia: extraer un componente
- Oportunidad de rendimiento – estados
loading/errorindependientes: Cada hook manejaloadingeerrorpor separado aunque comparten la misma semántica. Esto dificulta cachear resultados o compartirlos entre vistas.- Sugerencia: además del helper de suscripción, considerar un
useFirestoreResource<T>que acepte claves de cache y devuelva resultados memoizados (por ejemplo con Zustand o React Query) para evitar múltiples listeners sobre la misma colección en diferentes componentes.
- Sugerencia: además del helper de suscripción, considerar un