Sistema de Gestión de Información de Seguridad para Productos Acuáticos con Spring Boot

Contexto del Proyecto

La seguridad de los productos acuáticos abarca toda la cadena de producción: cultivo, procesamiento, distribución y consumo. Los métodos tradicionales de gestión dependen de registros manuales y documentación física, lo que genera baja eficiencia, dificultades en el seguimiento y problemas de aislamiento de información. En los últimos años, los incidentes relacionados con la seguridad de productos acuáticos han aumentado significativamente (como excesos de metales pesados y residuos de medicamentos), lo que hace urgente implementar herramientas digitales para fortalecer la supervisión.

Relevancia Técnica

Spring Boot, como framework Java ligero, permite construir sistemas de alta disponibilidad de manera rápida. Sus ventajas principales incluyen:

  • Servidor Tomcat integrado que simplifica el despliegue, ideal para instituciones gubernamentales o empresas que necesitan implementar sistemas rápidamente
  • Integración con MyBatis/JPA para almacenamiento y consulta eficiente de datos de inspección de productos acuáticos
  • APIs RESTful que permiten conexión con sistemas externos como logística de cadena de frío y laboratorios de análisis

Impacto Social

  • Rastreabilidad completa: Mediante códigos QR o tecnología RFID se registran datos de cada etapa (cultivo, transporte, venta), permitiendo auditoría desde el origen
  • Alertas tempranas: Módulos de análisis de datos identifican resultados anómalos en inspecciones (como desbordes de Escherichi coli)
  • Servicios públicos: Interfaces de consulta para consumidores que mejoran la transparencia del mercado

Requisitos del Sector

El sistema cumple con los requisitos de informatización para supervisión de seguridad alimentaria establecidos en la legislación vigente, contribuyendo a:

  • Lado empresarial: Estandarización de procesos de producción y reducción de costos de cumplimiento normativo
  • Lado regulatorio: Monitoreo en tiempo real del estado de seguridad por región y mejora de la precisión en muestreos de inspección

Nota: El diseño real del sistema debe considerar estándares como HACCP o ISO 22000 para asegurar que las funcionalidades cubran los puntos de control críticos.

Arquitectura Tecnológica

Backend

Spring Boot constituye el núcleo del sistema, proporcionando capacidades de desarrollo rápido mediante la integración de componentes del ecosistema Spring (Spring MVC, Spring Data JPA). Spring Security maneja autenticación y control de acceso, soportando protocolos OAuth2 y JWT. Para la capa de persistencia se utilizan MyBatis (adecuado para SQL complejo) o Hibernate (mapping automatizado). Redis funciona como caché para acceso frecuente a datos o gestión de sesiones. Swagger/Knife4j genera documentación de APIs facilitando la colaboración entre equipos frontend y back end.

Frontend

Vue.js es apropiado para aplicaciones ligeras, mientras que React se ajusta mejor a escenarios con interacciones complejas. Las librerías de componentes Element UI o Ant Design ofrecen tablas y formularios prefabricados. ECharts proporciona visualización de datos estadísticos de calidad de productos acuáticos. Axios gestiona la comunicación HTTP entre cliente y servidor.

Almacenamiento de Datos

MySQL o PostgreSQL almacenan datos estructurados como registros de inspección e información de usuarios. MongoDB (opcional) es útil para documentos no estructurados como archivos adjuntos de reportes de inspección.

Infraestructura y DevOps

Docker facilita el despliegue mediante contenedores, simplificando configuración y mantenimiento. Jenkins o GitLab CI automatizan pruebas y despliegues. Prometheus y Grafana proporcionan monitoreo del sistema y alertas, rastreando rendimiento del servidor e indicadores de negocio.

Módulos Adicionales

RabbitMQ o Kafka gestionan procesamiento asíncrono de tareas de inspección y notificaciones. MinIO ofrece almacenamiento de objetos para archivos como reportes e imágenes. Elasticsearch habilita búsqueda de texto completo para consulta rápida de datos de seguridad.

Implementación del Sistema

A continuación se presenta la estructura fundamental del sistema de gestión de seguridad para productos acuáticos:

Configuración de Persistencia

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/seguridad_acuatica
    username: administrador
    password: secure_password_2024
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
  data:
    redis:
      host: localhost
      port: 6379

Entidades del Dominio

@Entity
@Table(name = "producto_acuatico")
public class ProductoAcuatico {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long identificador;
    
    @Column(nullable = false, length = 150)
    private String nombreProducto;
    
    @Column(nullable = false, length = 100)
    private String regionOrigen;
    
    @Column(nullable = false)
    private LocalDate fechaCosecha;
    
    @Column(nullable = false, length = 20)
    private String nivelSeguridad;
    
    @Column(precision = 10, scale = 2)
    private BigDecimal pesoPromedio;
    
    // constructores, getters y setters
}

@Entity
@Table(name = "registro_inspeccion")
public class RegistroInspeccion {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "producto_id")
    private ProductoAcuatico producto;
    
    private LocalDateTime momentoInspeccion;
    private String nombreInspector;
    private String resultado;
    private String observaciones;
    private BigDecimal temperaturaAlmacenamiento;
    
    // constructores, getters y setters
}

Capa de Acceso a Datos

public interface RepositorioProducto extends JpaRepository<ProductoAcuatico, Long> {
    List<ProductoAcuatico> buscarPorNivelSeguridad(String nivel);
    
    @Query("SELECT p FROM ProductoAcuatico p WHERE p.fechaCosecha BETWEEN :inicio AND :fin")
    List<ProductoAcuatico> buscarPorRangoFechas(
        @Param("inicio") LocalDate inicio, 
        @Param("fin") LocalDate fin
    );
    
    Optional<ProductoAcuatico> findByCodigoBarras(String codigo);
}

public interface RepositorioInspeccion extends JpaRepository<RegistroInspeccion, Long> {
    List<RegistroInspeccion> buscarPorProductoId(Long productoId);
    
    Page<RegistroInspeccion> findByResultado(String resultado, Pageable paginacion);
}

Lógica de Negocio

@Service
@RequiredArgsConstructor
public class ServicioProducto {
    private final RepositorioProducto repositorioProducto;
    private final RepositorioInspeccion repositorioInspeccion;

    public ProductoAcuatico registrarProducto(ProductoAcuatico producto) {
        validarDatosProducto(producto);
        return repositorioProducto.save(producto);
    }
    
    public Page<ProductoAcuatico> obtenerListadoProductos(Pageable paginacion) {
        return repositorioProducto.findAll(paginacion);
    }
    
    public List<RegistroInspeccion> historialInspecciones(Long productoId) {
        ProductoAcuatico producto = repositorioProducto.findById(productoId)
            .orElseThrow(() -> new RecursoNoEncontradoException("Producto no encontrado"));
        return repositorioInspeccion.buscarPorProductoId(productoId);
    }
    
    private void validarDatosProducto(ProductoAcuatico producto) {
        if (producto.getFechaCosecha().isAfter(LocalDate.now())) {
            throw new ValidacionException("La fecha de cosecha no puede ser futura");
        }
    }
}

Controladores REST

@RestController
@RequestMapping("/api/v1/productos")
@RequiredArgsConstructor
public class ControladorProducto {
    private final ServicioProducto servicioProducto;

    @PostMapping
    public Respuesta<ProductoAcuatico> crearProducto(@RequestBody ProductoAcuatico producto) {
        ProductoAcuatico nuevo = servicioProducto.registrarProducto(producto);
        return Respuesta.ok(nuevo);
    }
    
    @GetMapping("/seguridad/{nivel}")
    public Respuesta<List<ProductoAcuatico>> filtrarPorNivel(@PathVariable String nivel) {
        return Respuesta.ok(servicioProducto.buscarPorNivelSeguridad(nivel));
    }
    
    @GetMapping("/{id}/historial")
    public Respuesta<List<RegistroInspeccion>> obtenerHistorial(@PathVariable Long id) {
        return Respuesta.ok(servicioProducto.historialInspecciones(id));
    }
}

Configuración de Seguridad

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class ConfiguracionSeguridad extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity seguridad) throws Exception {
        seguridad.csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/v1/autenticacion/**").permitAll()
            .antMatchers("/api/v1/productos/**").hasAnyRole("ADMINISTRADOR", "INSPECTOR")
            .antMatchers("/api/v1/admin/**").hasRole("ADMINISTRADOR")
            .anyRequest().authenticated()
            .and()
            .addFilter(new FiltroAutenticacionJWT(managerAutenticacion()))
            .addFilter(new FiltroAutorizacionJWT(managerAutenticacion()))
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

Manejo de Errores

@ControllerAdvice
public class ManejadorExcepcionesGlobales {
    
    @ExceptionHandler(RecursoNoEncontradoException.class)
    public ResponseEntity<Map<String, String>> manejarRecursoNoEncontrado(RecursoNoEncontradoException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(Map.of("error", ex.getMessage()));
    }
    
    @ExceptionHandler(ValidacionException.class)
    public ResponseEntity<Map<String, String>> manejarValidacion(ValidacionException ex) {
        return ResponseEntity.badRequest()
            .body(Map.of("error", "Error de validación: " + ex.getMessage()));
    }
    
    @ExceptionHandler(AccesoDenegadoException.class)
    public ResponseEntity<Map<String, String>> manejarAccesoDenegado() {
        return ResponseEntity.status(HttpStatus.FORBIDDEN)
            .body(Map.of("error", "No tiene permisos para acceder a este recurso"));
    }
}

La arquitectura implementa las siguientes características fundamentales:

  • Capa de persistencia basada en JPA con validaciones de negocio
  • Control de acceso mediante roles utilizando Spring Security
  • Diseño de APIs遵循RESTful规范
  • Manejo centralizado de excepciones
  • Consultas paginadas y filtros por criterios múltiples
  • Mapeo de relaciones entre entidades (producto-inspecciones uno a muchos)

El sistema puede extenderse con módulos adicionales como carga de archivos para reportes, integración con colas de mensajes para notificaciones de alerta, generación de estadísticas analíticas y funcionalidad de códigos QR para trazabilidad.

Diseño de Base de Datos

Modelo Entidad-Relación

El sistema de información de seguridad acuática contiene las siguientes entidades principales:

  • ProductoAcuatico: Almacena información básica (nombre, lote, fecha producción, proveedor)
  • Inspeccion: Guarda datos de pruebas de seguridad (residuos plaguicidas, contenido metales pesados)
  • Proveedor: Gestiona información de proveedores (licencias, datos contacto)
  • Usuario: Define roles del sistema (administrador, inspector, usuario regular)

Esquema de Tablas

-- Tabla de productos acuáticos
CREATE TABLE producto_acuatico (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    nombre_producto VARCHAR(150) NOT NULL,
    codigo_lote VARCHAR(50) UNIQUE,
    fecha_produccion DATE,
    proveedor_id BIGINT,
    nivel_seguridad VARCHAR(20),
    INDEX idx_codigo_lote (codigo_lote),
    INDEX idx_fecha_produccion (fecha_produccion),
    FOREIGN KEY (proveedor_id) REFERENCES proveedor(id)
);

-- Tabla de inspecciones
CREATE TABLE registro_inspeccion (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    producto_id BIGINT NOT NULL,
    inspector_id BIGINT NOT NULL,
    fecha_prueba DATE,
    residuo_plaguicidas DECIMAL(10,3),
    metal_pesado VARCHAR(50),
    resultado ENUM('APROBADO', 'RECHAZADO', 'PENDIENTE'),
    observaciones TEXT,
    FOREIGN KEY (producto_id) REFERENCES producto_acuatico(id),
    FOREIGN KEY (inspector_id) REFERENCES usuario(id),
    INDEX idx_resultado (resultado)
);

-- Tabla de proveedores
CREATE TABLE proveedor (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    nombre VARCHAR(150) NOT NULL,
    numero_licencia VARCHAR(50) UNIQUE,
    telefono_contacto VARCHAR(20),
    direccion VARCHAR(255),
    estado ENUM('ACTIVO', 'INACTIVO', 'SUSPENDIDO')
);

-- Tabla de usuarios
CREATE TABLE usuario (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    nombre_usuario VARCHAR(50) UNIQUE NOT NULL,
    contrasena_hash VARCHAR(255) NOT NULL,
    rol ENUM('ADMINISTRADOR', 'INSPECTOR', 'USUARIO'),
    correo VARCHAR(100),
    fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Estrategia de Pruebas

Pruebas Unitarias

Utilizando JUnit y Mockito para verificar la lógica de la capa de servicio:

@SpringBootTest
public class PruebaServicioInspeccion {
    @Autowired
    private ServicioInspeccion servicioInspeccion;

    @Test
    public void probarGuardadoInspeccionValida() {
        DTOInspeccion dto = new DTOInspeccion();
        dto.setProductoId(1L);
        dto.setResultado("APROBADO");
        dto.setMetalPesado("0.05");
        
        assertDoesNotThrow(() -> servicioInspeccion.guardarInspeccion(dto));
    }

    @Test
    public void probarGuardadoInspeccionProductoInvalido() {
        DTOInspeccion dto = new DTOInspeccion();
        dto.setProductoId(9999L);
        dto.setResultado("RECHAZADO");
        
        assertThrows(RecursoNoEncontradoException.class, 
            () -> servicioInspeccion.guardarInspeccion(dto));
    }
}

Pruebas de Integración de API

Validación de endpoints REST utilizando Postman o TestRestTemplate:

  • POST /api/v1/inspecciones: Envío de reporte de inspección, verificación de código HTTP y cuerpo de respuesta
  • GET /api/v1/productos/{id}: Consulta de detalles de producto, validación de campos返回

Pruebas de Seguridad

@WithMockUser(roles = "USUARIO")
@Test
public void probarAccesoDenegadoRolUsuario() {
    mockMvc.perform(get("/api/v1/admin/reportes"))
           .andExpect(status().isForbidden());
}

Pruebas de Rendimiento

Utilizando JMeter para simular solicitudes concurrentes (como consultas masivas de productos) y monitorear tiempos de respuesta y carga de base de datos.

Pruebas de Interfaz (Opcional)

Selenium para automatizar flujos de interfaz como envío de formularios y visualización de datos.

Consideraciones Importantes

  • Crear índices en campos frecuentemente consultados (como código de lote) para optimizar rendimiento
  • Los datos de prueba deben incluir casos extremos (resultados de inspección en valores límite)
  • Las pruebas de integración deben verificar rollbacks de transacciones (consistencia de datos cuando falla el guardado de inspección)
  • Implementar logging adecuado para auditoría y diagnóstico de problemas en producción
  • Considerar estrategias de caché para consultas frecuentes de productos con seguridad aprobado

Etiquetas: spring-boot java seguridad-alimentaria desarrollo-web MySQL

Publicado el 6-26 19:10