Fase 5: Implementada la configuración dinámica. Implementado el scraping web.

This commit is contained in:
2025-10-28 12:56:42 -03:00
parent 9be62937bd
commit 75d06820aa
11 changed files with 395 additions and 21 deletions

View File

@@ -1,31 +1,82 @@
// frontend/src/components/FormularioConfiguracion.tsx
import { Box, TextField, Button, Paper, Typography } from '@mui/material';
import { useEffect, useState } from 'react';
import { Box, TextField, Button, Paper, Typography, CircularProgress } from '@mui/material';
import type { Configuracion } from '../types';
import * as api from '../services/apiService';
const FormularioConfiguracion = () => {
const [config, setConfig] = useState<Configuracion | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [success, setSuccess] = useState(false);
useEffect(() => {
api.obtenerConfiguracion()
.then(data => {
setConfig(data);
setLoading(false);
})
.catch(err => console.error("Error al cargar configuración", err));
}, []);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (!config) return;
const { name, value } = event.target;
setConfig({
...config,
[name]: name === 'rutaCsv' ? value : Number(value) // Convertir a número si no es la ruta
});
};
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!config) return;
setSaving(true);
setSuccess(false);
try {
await api.guardarConfiguracion(config);
setSuccess(true);
setTimeout(() => setSuccess(false), 2000); // El mensaje de éxito desaparece después de 2s
} catch (err) {
console.error("Error al guardar configuración", err);
} finally {
setSaving(false);
}
};
if (loading) {
return <CircularProgress />;
}
if (!config) {
return <Typography color="error">No se pudo cargar la configuración.</Typography>
}
return (
<Paper elevation={3} sx={{ padding: 2, marginBottom: 3 }}>
<Typography variant="h6" gutterBottom>
Configuración
</Typography>
<Box component="form" noValidate autoComplete="off">
<Typography variant="h6" gutterBottom>Configuración</Typography>
<Box component="form" onSubmit={handleSubmit}>
<TextField
fullWidth
label="Ruta del archivo CSV"
defaultValue="/var/data/headlines.csv"
variant="outlined"
sx={{ marginBottom: 2 }}
fullWidth name="rutaCsv" label="Ruta del archivo CSV"
value={config.rutaCsv} onChange={handleChange}
variant="outlined" sx={{ marginBottom: 2 }} disabled={saving}
/>
<TextField
fullWidth
label="Intervalo de Actualización (minutos)"
defaultValue={5}
type="number"
variant="outlined"
sx={{ marginBottom: 2 }}
fullWidth name="intervaloMinutos" label="Intervalo de Actualización (minutos)"
value={config.intervaloMinutos} onChange={handleChange}
type="number" variant="outlined" sx={{ marginBottom: 2 }} disabled={saving}
/>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button variant="contained">Guardar Cambios</Button>
<TextField
fullWidth name="cantidadTitularesAScrapear" label="Titulares a Capturar por Ciclo"
value={config.cantidadTitularesAScrapear} onChange={handleChange}
type="number" variant="outlined" sx={{ marginBottom: 2 }} disabled={saving}
/>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
{success && <Typography color="success.main" sx={{ mr: 2 }}>¡Guardado!</Typography>}
<Button type="submit" variant="contained" disabled={saving}>
{saving ? <CircularProgress size={24} /> : 'Guardar Cambios'}
</Button>
</Box>
</Box>
</Paper>

View File

@@ -1,7 +1,7 @@
// frontend/src/services/apiService.ts
import axios from 'axios';
import type { Titular } from '../types';
import type { Configuracion, Titular } from '../types';
const API_URL = 'http://localhost:5174/api';
@@ -30,4 +30,13 @@ interface ReordenarPayload {
export const actualizarOrdenTitulares = async (payload: ReordenarPayload[]): Promise<void> => {
await apiClient.put('/titulares/reordenar', payload);
};
export const obtenerConfiguracion = async (): Promise<Configuracion> => {
const response = await apiClient.get('/configuracion');
return response.data;
};
export const guardarConfiguracion = async (config: Configuracion): Promise<void> => {
await apiClient.post('/configuracion', config);
};

View File

@@ -10,4 +10,11 @@ export interface Titular {
fechaCreacion: string;
tipo: 'Scraped' | 'Edited' | 'Manual' | null;
fuente: string | null;
}
export interface Configuracion {
rutaCsv: string;
intervaloMinutos: number;
cantidadTitularesAScrapear: number;
limiteTotalEnDb: number;
}