AgroSmart SaaS — JavaScript + Class Components → TypeScript + Hooks + React Router v6
// src/utils/api.js — sin interfaces const fetchSensors = async (farmId) => { const res = await fetch( `/api/farms/${farmId}/sensors` ); if (!res.ok) throw new Error('Error al obtener sensores'); return res.json(); // ⚠ devuelve `any` }; const updateSensor = async (id, data) => { const res = await fetch(`/api/sensors/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); return res.json(); }; module.exports = { fetchSensors, updateSensor };
// src/utils/api.ts — tipado completo interface Sensor { id: string; farmId: string; type: 'humidity' | 'temperature' | 'ph'; value: number; unit: string; lastSeen: string; active: boolean; } type SensorPatch = Partial<Pick<Sensor, 'value' | 'active'>>; export const fetchSensors = async ( farmId: string ): Promise<Sensor[]> => { const res = await fetch( `/api/farms/${farmId}/sensors` ); if (!res.ok) throw new Error('Error al obtener sensores'); return res.json() as Promise<Sensor[]>; }; export const updateSensor = async ( id: string, data: SensorPatch ): Promise<Sensor> => { const res = await fetch(`/api/sensors/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); return res.json() as Promise<Sensor>; };
// SensorDashboard.jsx class SensorDashboard extends React.Component { state = { sensors: [], loading: true, error: null, }; componentDidMount() { this.loadSensors(); } componentDidUpdate(prevProps) { if (prevProps.farmId !== this.props.farmId) this.loadSensors(); } loadSensors() { this.setState({ loading: true }); fetchSensors(this.props.farmId) .then(sensors => this.setState({ sensors, loading: false }) ) .catch(err => this.setState({ error: err.message }) ); } render() { const { loading, sensors } = this.state; if (loading) return <Spinner />; return <SensorList sensors={sensors} />; } }
// hooks/useSensors.ts — hook extraído function useSensors(farmId: string) { const [sensors, setSensors] = useState<Sensor[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { setLoading(true); fetchSensors(farmId) .then(setSensors) .catch(e => setError(e.message)) .finally(() => setLoading(false)); }, [farmId]); return { sensors, loading, error }; } // SensorDashboard.tsx — componente limpio function SensorDashboard({ farmId, }: { farmId: string }) { const { sensors, loading, error } = useSensors(farmId); if (loading) return <Spinner />; if (error) return <ErrorBanner msg={error} />; return <SensorList sensors={sensors} />; }
| Archivo | Tipo migración | Riesgo | Type errors corregidos | Estado |
|---|---|---|---|---|
| src/utils/api.js | JS → TS + interfaces | Bajo | 12 | ✓ Listo |
| src/utils/date.js | JS → TS | Bajo | 3 | ✓ Listo |
| src/components/SensorDashboard.jsx | Class → hooks + TS | Medio | 18 | ✓ Listo |
| src/components/FarmMap.jsx | Class → hooks (Leaflet ref) | Alto | — | ⏳ WIP |
| src/components/AlertList.jsx | Class → hooks + TS | Bajo | — | Pendiente |
| src/pages/Dashboard.jsx | JS → TS + React Router v6 | Medio | — | Pendiente |
| src/pages/Settings.jsx | JS → TS + useNavigate | Bajo | — | Pendiente |
| src/App.jsx | Switch → Routes (v6) | Alto | — | Pendiente |