El presente artículo describe la arquitectura y el desarrollo de un portal web pensado para que los alumnos de educación primaria amplíen sus conocimientos fuera del aula. El sistema combina un front-end ligero basado en HTML5 y Bootstrap con un back-end robusto implementado con Spring, Spring MVC y MyBatis (SSM). A continuación se detallan los componentes más relevantes.
Arquitectura general
- Cliente: HTML5, CSS3, JavaScript ES6, jQuery 3.x, componentes LayUI.
- Servidor: Java 11, Spring 5, Spring MVC, MyBatis 3, Maven 3.8, Tomcat 9.
- Datos: MySQL 8.0 (modelo relacional), con scripts de migración para SQL Server.
- Entorno de desarrollo: IntelliJ IDEA, Navicat, Git.
Diagrama de paquetes (back-end)
com.educare.primaria
├── controller
│ ├── RecursoControlador.java
│ ├── FavoritoControlador.java
│ └── UsuarioControlador.java
├── service
│ ├── RecursoServicio.java
│ └── impl
│ └── RecursoServicioImpl.java
├── mapper
│ └── RecursoMapper.xml
└── domain
├── Recurso.java
└── Favorito.java
Modelo de datos simplificado
CREATE TABLE recurso (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
titulo VARCHAR(120) NOT NULL,
descripcion TEXT,
url_contenido VARCHAR(255),
nivel ENUM('1','2','3','4','5','6') NOT NULL,
materia ENUM('MAT','LEN','CIE','ART','ING') NOT NULL,
fecha_alta DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE favorito (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
usuario_id BIGINT NOT NULL,
recurso_id BIGINT NOT NULL,
fecha_guardado DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (usuario_id) REFERENCES usuario(id),
FOREIGN KEY (recurso_id) REFERENCES recurso(id)
);
Código de ejemplo: controlador de faovritos
package com.educare.primaria.controller;
import com.educare.primaria.domain.Favorito;
import com.educare.primaria.service.FavoritoServicio;
import com.educare.primaria.util.Respuesta;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/favorito")
public class FavoritoControlador {
@Autowired
private FavoritoServicio favoritoServicio;
@GetMapping("/pagina")
public Respuesta pagina(@RequestParam Map<String, Object> params,
HttpServletRequest peticion) {
Long usuarioId = (Long) peticion.getSession().getAttribute("uid");
return Respuesta.ok()
.conDatos(favoritoServicio.listarPaginado(params, usuarioId));
}
@PostMapping
public Respuesta guardar(@RequestBody Favorito favorito,
HttpServletRequest peticion) {
favorito.setUsuarioId((Long) peticion.getSession().getAttribute("uid"));
favoritoServicio.insertar(favorito);
return Respuesta.ok("Recurso guardado en favoritos");
}
@DeleteMapping("/{ids}")
public Respuesta borrar(@PathVariable List<Long> ids,
HttpServletRequest peticion) {
Long usuarioId = (Long) peticion.getSession().getAttribute("uid");
favoritoServicio.eliminar(ids, usuarioId);
return Respuesta.ok("Favoritos eliminados");
}
@GetMapping("/recordatorio/{campo}/{tipo}")
public Respuesta recordatorio(@PathVariable String campo,
@PathVariable String tipo,
@RequestParam Map<String, Object> filtros,
HttpServletRequest peticion) {
filtros.put("columna", campo);
filtros.put("tipo", tipo);
Long usuarioId = (Long) peticion.getSession().getAttribute("uid");
int total = favoritoServicio.contar(filtros, usuarioId);
return Respuesta.ok().conDato("total", total);
}
}
Fragmento de srevicio
@Service
public class FavoritoServicioImpl implements FavoritoServicio {
@Autowired
private FavoritoMapper mapper;
@Override
public Page<Favorito> listarPaginado(Map<String, Object> params, Long usuarioId) {
QueryWrapper<Favorito> qw = new QueryWrapper<>();
qw.eq("usuario_id", usuarioId);
Page<Favorito> page = new Page<>(params);
return mapper.selectPage(page, qw);
}
@Override
public void insertar(Favorito favorito) {
favorito.setId(SnowFlake.nextId());
mapper.insert(favorito);
}
@Override
public void eliminar(List<Long> ids, Long usuarioId) {
mapper.deleteBatchIds(ids, usuarioId);
}
@Override
public int contar(Map<String, Object> filtros, Long usuarioId) {
QueryWrapper<Favorito> qw = new QueryWrapper<>();
qw.eq("usuario_id", usuarioId);
if (filtros.containsKey("inicio")) {
qw.ge("fecha_guardado", filtros.get("inicio"));
}
if (filtros.containsKey("fin")) {
qw.le("fecha_guardado", filtros.get("fin"));
}
return mapper.selectCount(qw);
}
}
Pruebas de sistema
El plan de pruebas incluye:
- Pruebas unitarias: validan la lógica de negocio de cada servicio con JUnit 5 y Mockito.
- Pruebas de integración: verifican la correcta interacción entre controladores y base de datos mediante Testcontainers y MySQL en Docker.
- Pruebas de estrés: utilizan JMeter para simular 500 usuarios concurrentes accediendo a recursos de matemáticas de segundo grado.
- Pruebas de regresión: se ejecutan automado cada pull request con GitHub Actions para garantizar que los cambios no rompen funcionalidades previas.
Despliegue rápido con Docker Compose
version: "3.9"
services:
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: educare
ports:
- "3306:3306"
app:
build: .
ports:
- "8080:8080"
depends_on:
- db
Con este conjunto de tecnologías y prácticas, el portal se mantiene ligero para el navegador del alumno y robusto en el servidor, permitiendo una experiencia fluida tanto en tablets como en equipos de escritorio.