
Capacidades
- Tandas en directo desde TrackLap
- Back office
- Imágenes en CDN
- Email transaccional
Frontend
Backend
Sport Moto Team
En desarrolloSport Moto Team es la Yamaha Official Riding School: más de quince años organizando tandas de moto en circuito por toda España. Su web vivía en una plataforma antigua y la rehice como un proyecto Next.js 14 con App Router y componentes de servidor: home con la próxima tanda y cuenta atrás, calendario mensual y anual, alquiler, seguro anual, libro de visitas, media y contacto. Las tandas no se escriben a mano: se leen de la API pública de TrackLap, donde el club ya las gestiona. Detrás hay un panel desde el que el equipo edita textos, banners, patrocinadores, fotos y opiniones, sobre PostgreSQL y con las imágenes en Cloudinary.
El reto
El club ya gestiona sus tandas en TrackLap, así que la web no podía convertirse en un segundo sitio donde mantener las mismas fechas, plazas y precios: tenía que leerlas de allí y estar siempre al día sin que nadie tocara nada. Todo lo demás —textos, fotos, patrocinadores, opiniones— lo cambia el propio club, muchas veces desde el móvil a pie de circuito, así que el panel tenía que ser cómodo en pantalla táctil y no depender de un desarrollador para cada cambio. Y la misma base de código tenía que desplegarse igual en Vercel que en un VPS propio.
Capturas

Escritorio 
Móvil
Arquitectura
Diagrama de arquitectura de Sport Moto Team
Implementación
Tandas en directo desde TrackLap
La home, el calendario y la cuenta atrás de la próxima tanda se alimentan de la API pública de TrackLap con componentes de servidor y revalidación cada cinco minutos; el botón de reservar lleva a la inscripción en TrackLap. Un test de contrato compara la respuesta real de producción con el tipado local para que un cambio en el backend salte antes de llegar a la web.
El contenido manda desde la base de datos
Cada texto de la web es un bloque en content_blocks: el literal del código es solo el fallback si la fila no existe. El club edita por secciones con vista previa o directamente pinchando el texto en la propia web con el modo edición, en las ocho páginas.
Panel multiusuario con sesión firmada
Usuarios con rol owner y editor, contraseñas con scrypt y sesión en cookie HttpOnly firmada con HMAC-SHA256. El middleware corre en Edge y solo valida firma y caducidad; la comprobación autoritativa la hace cada ruta de API en Node, y cambiar una contraseña o desactivar a alguien caduca sus sesiones al instante subiendo su token_version.
Freno a la fuerza bruta y arranque en frío
Cinco fallos por IP y usuario bloquean el login quince minutos. Si la tabla de usuarios está vacía, la primera entrada con la contraseña de entorno crea al owner; a partir de ahí manda la base de datos y esa variable deja de usarse.
Libro de visitas moderado con quince años de histórico
Las opiniones nacen ocultas y se publican desde el panel, con posición fija opcional, y las 660 opiniones de la web antigua (2011–2026) se importan de forma idempotente por su número original.
Imágenes a medida desde Cloudinary
El panel sube fotos, banners y logos de patrocinadores a Cloudinary; las URLs se reescriben con f_auto, q_auto y el ancho justo para cada sitio, y la portada pasó de unos 9 MB a menos de 1 MB de imágenes.
Formularios que nunca se pierden
Seguro anual y libro de visitas guardan primero en la base de datos y después avisan al club por correo con Nodemailer: si el correo falla, la solicitud sigue ahí y se ve desde el panel.
Un código, dos despliegues
El cliente de base de datos elige driver según DATABASE_URL: el HTTP serverless de Neon en Vercel, o pg con pool contra un Postgres normal en el VPS con EasyPanel, empaquetado con Dockerfile. Los tests sustituyen la base de datos por un Postgres de mentira en memoria: ninguno toca una real.
Panel pensado para el móvil
Se usa a pie de circuito: nada esencial detrás de un hover, 44 px de área táctil, confirmaciones en dos pasos en lugar de window.confirm y una barra de guardar fija con el número de cambios pendientes.