Bentoboxd es una aplicación web que convierte el feed de Letterboxd de cualquier usuario en un panel de visualización interactivo con una estética editorial minimalista de paleta restringida. Ingresa un usuario y obtén métricas, gráficos y vistas alternativas de su actividad cinematográfica reciente.
Nace como un experimento personal para explorar visualización de datos, integración de APIs y animaciones CSS, todo en una interfaz sobria de cinco tonos. Sin frameworks de UI, sin TypeScript, sin dependencias pesadas — solo React, Vite y CSS.
- Feed RSS de Letterboxd — Ingresa un nombre de usuario o URL y obtén sus últimas 50 películas vistas (las listas del feed se ignoran). Las vistas muestran las 20 más recientes y «Ver todas» abre las 50 con filtros por me gusta, nota y año; las estadísticas y los juegos siempre usan las 50. Letterboxd no publica más en el feed, así que las cifras no cubren el historial completo
- 3 modos de visualización:
- Bento — Cuadrícula dinámica sin huecos con tarjetas 1x1, 2x1, 1x2 y 2x2 (en móvil, 3 columnas sin 1x2)
- Línea de tiempo — Lista cronológica con pósters, calificaciones y fechas
- Terminal — Listado estilo terminal retro
- Datos clave del perfil — Una frase generada con su ritmo, género principal y notas, seguida de promedio, tiempo viendo, comparación con el público (TMDB) y películas por semana
- Paneles de análisis (con un índice fijo para saltar entre ellos):
- Resumen — Me gusta, vistas otra vez, día más activo, década más vista y distribución de calificaciones
- Evolución mensual — Películas y nota media por mes, según la fecha en que se vio cada una
- Opiniones impopulares — Las películas con mayor diferencia frente al público (TMDB), a favor y en contra, con «Cómo se calcula»
- Mejor calificados — Géneros y directores con el mejor promedio de calificaciones del perfil (mínimo 2 películas)
- Géneros — 4 tipos de gráfico (Dona, Barras, Matriz, Medidores)
- Directores — Quienes tienen al menos 2 películas, en vista de lista con foto u holograma
- Reparto — Cuadrícula con fotos
- Países de producción — Lista o sectores con banderas
- Plataformas de streaming — Disponibilidad en México (datos de JustWatch)
- Explorar los datos — Al pulsar un género, director, actor, país o plataforma se despliegan sus películas, enlazadas a Letterboxd
- Juegos (páginas propias, enlazables y compatibles con el botón atrás):
- Higher or Lower (
#higher-lower) — Adivina si la siguiente película tiene mayor/menor rating, popularidad o año (en popularidad solo se muestra cuál gana) - Quiz (
#quiz) — 5 preguntas sobre calificación, año de estreno, dirección o póster
- Higher or Lower (
- Skeleton loading mientras cargan el feed y los metadatos de TMDB
- Tema claro y oscuro — Botón de cambio en la cabecera; respeta
prefers-color-schemeen la primera visita y recuerda tu elección en localStorage (sin parpadeo al cargar) - Español e inglés — Selector ES/EN en la cabecera; usa el idioma del navegador en la primera visita, recuerda tu elección en localStorage y muestra los títulos en español cuando TMDB los tiene
- Paleta de cinco tonos — Todos los colores salen de tokens CSS (tintas y crema); los gráficos usan rampas derivadas de ellos
- Diseño responsive con layout adaptado a móvil
- Accesibilidad — Etiquetas ARIA, anuncios en vivo para los juegos y enlaces externos señalados para lectores de pantalla
- Enlaces compartibles — El perfil va en la URL (
?user=<usuario>, combinable con#quizo#higher-lower): se puede compartir con «Copiar enlace», recargar y navegar con atrás/adelante entre perfiles - Persistencia — Últimos 3 perfiles guardados en localStorage, disponibles también desde la búsqueda
| Tecnología | Versión |
|---|---|
| React | 19 |
| Vite | 7 |
| ESLint (flat config) | 9 |
| CSS | Vanilla + design tokens |
| Tipografías | Milker, DM Sans (autoalojadas) |
| Tests | Vitest 3 + Testing Library |
| APIs | Letterboxd RSS, TMDB v3 |
- Node.js (LTS recomendada)
- npm
git clone https://github.com/Andrijo/Bentoboxd.git
cd Bentoboxd
npm installCopia .env.example a .env y completa las variables:
TMDB_API_KEY=tu_api_key_de_tmdb
# Solo producción (ver Despliegue)
# VITE_RSS_PROXY_URL=/api/rssLa key de TMDB nunca llega al navegador: el cliente llama a /api/tmdb/... y el proxy (Vite en local, api/tmdb.js en producción) la añade del lado del servidor. Solo se permiten los endpoints que usa la app (search/movie y movie/<id>).
Obtén una API key gratuita en themoviedb.org.
npm run dev # Servidor de desarrollo → http://localhost:5173
npm run build # Build de producción → dist/
npm run preview # Previsualizar build
npm run lint # Ejecutar ESLint
npm run test # Ejecutar tests (Vitest, modo watch)
npm run test:run # Ejecutar tests una vez (CI)Los tests usan Vitest + Testing Library (jsdom) y viven junto al código que prueban (*.test.js/*.test.jsx).
GitHub Actions (.github/workflows/ci.yml) ejecuta lint, test:run y build con Node 22 en cada push a main y en cada pull request.
El RSS de Letterboxd no permite CORS y la key de TMDB no debe ir en el bundle, así que en producción la app usa dos funciones serverless listas para Vercel: api/rss.js y api/tmdb.js (enrutada por vercel.json).
- Importa el repositorio en Vercel (detecta Vite automáticamente).
- En Settings → Environment Variables define:
TMDB_API_KEY— tu key de TMDB. Si antes usabasVITE_TMDB_API_KEY, bórrala: ya no se usa y el prefijoVITE_la expondría.VITE_RSS_PROXY_URL— opcional: en producción se usa/api/rsspor defecto. Solo cámbialo si usas otro proxy.
- Deploy. La app llamará a
/api/rss?user=<usuario>y a/api/tmdb/....
Si ves un error 401 de TMDB, la variable
TMDB_API_KEYno está definida (o es inválida) en el entorno de las funciones de Vercel. Corrígela y haz Redeploy.
En local solo necesitas TMDB_API_KEY en .env: npm run dev usa los proxies de vite.config.js (/rss-proxy y /api/tmdb).
Como la key ya no se compila en el bundle, rótala en TMDB si la versión anterior estuvo publicada.
En otro hosting, apunta
VITE_RSS_PROXY_URLa tu propio proxy. Por defecto se le pasa?user=<usuario>; si la URL contiene{user}, se usa en modo path-style.
- Cabeceras (
vercel.json): CSP estricta (solo scripts propios; imágenes de Letterboxd, TMDB y flagcdn),frame-ancestors 'none',nosniff,Referrer-PolicyyPermissions-Policy. El script que aplica el tema vive enpublic/theme-init.jspara no necesitar'unsafe-inline'. - API (
api/*): las respuestas llevanContent-Security-Policy: default-src 'none'; sandboxy un límite de peticiones por IP (20/min en el RSS, 240/min en TMDB). El límite es en memoria por instancia: para algo estricto, añade una regla de rate limit en el firewall de Vercel. - CORS:
/api/rsssolo responde al mismo origen. Si sirves el frontend desde otro dominio, defineRSS_ALLOWED_ORIGINcon ese origen. - Si mueves algún proxy a otro dominio (
VITE_RSS_PROXY_URLoVITE_TMDB_PROXY_URLabsolutos), añádelo aconnect-srcen la CSP devercel.json.
api/
├── rss.js # Proxy serverless del RSS (Vercel)
├── tmdb.js # Proxy serverless de TMDB (Vercel)
└── _lib/
├── apiHeaders.js # Cabeceras de seguridad y respuesta 429
├── rateLimit.js # Límite de peticiones por IP
└── tmdbUpstream.js # Rutas permitidas de TMDB (prod y dev)
src/
├── App.jsx # Componente principal
├── HighLowGame.jsx # Página Higher or Lower (#higher-lower)
├── main.jsx # Punto de entrada
├── index.css # Estilos globales
├── components/ # Componentes React
│ ├── AppHeader.jsx # Cabecera y navegación
│ ├── AppFooter.jsx # Pie con atribución (Letterboxd, TMDB)
│ ├── SearchSection.jsx # Búsqueda de usuario
│ ├── RecentProfiles.jsx # Perfiles recientes
│ ├── EmptyLanding.jsx # Pantalla inicial sin perfil
│ ├── ProfileOverview.jsx # Titular, datos clave y «Copiar enlace»
│ ├── SectionNav.jsx # Índice fijo de secciones
│ ├── Panel/ # Panel y SegmentedControl compartidos
│ ├── MovieViews.jsx # Bento / Línea de tiempo / Terminal
│ ├── MovieFilters.jsx # Filtros de «Ver todas»
│ ├── MovieDrilldown.jsx # Películas de un género, persona, país o plataforma
│ ├── DataPanels.jsx # Contenedor de paneles de análisis
│ ├── HowCalculated.jsx # Explicación desplegable «Cómo se calcula»
│ ├── Skeleton.jsx # Placeholders de carga
│ ├── PosterImage.jsx # Póster con carga diferida
│ ├── PersonAvatar.jsx # Foto o iniciales de una persona
│ ├── StarRating.jsx # Calificación en estrellas
│ ├── KeepAlive.jsx # Conserva el estado de los juegos entre pestañas
│ ├── ErrorBoundary.jsx # Pantalla de error inesperado
│ ├── ErrorNotice.jsx # Aviso de error con reintento
│ ├── BentoGrid/ # Cuadrícula bento
│ ├── StatsSummary/ # Resumen e histograma de calificaciones
│ ├── MonthlyPanel/ # Evolución mensual
│ ├── GenrePanel/ # Gráficos de géneros
│ ├── DirectorsPanel/ # Paneles de directores
│ ├── ActorsPanel/ # Paneles de actores
│ ├── CountriesPanel/ # Paneles de países
│ ├── PlatformsPanel/ # Plataformas de streaming
│ ├── TopRatedPanel/ # Mejor calificados del perfil
│ ├── OpinionsPanel/ # Opiniones impopulares (vs TMDB)
│ └── Quiz/ # Página de quiz (#quiz)
├── hooks/ # Custom hooks
│ ├── useLetterboxdFeed.js # Fetch y parse de RSS
│ ├── useTMDBMetadata.js # Fetch de metadatos TMDB
│ ├── useHashRoute.js # Páginas por hash (#quiz, #higher-lower)
│ ├── useGridColumns.js # Columnas según el ancho disponible
│ ├── useTheme.js # Tema claro/oscuro con persistencia
│ └── useOverscrollColor.js # Color del rebote de overscroll
├── services/ # Servicios API
│ ├── letterboxd.js # Parseo de RSS
│ └── tmdb.js # Cliente TMDB
├── constants/ # Constantes y mapeos
├── i18n/ # Idioma activo y diccionarios (es, en)
├── test/setup.js # Setup de Vitest (jest-dom)
└── utils/ # Utilidades
El código se publica bajo la licencia MIT.
La tipografía Milker (public/fonts/Milker.woff2) no está incluida en esa licencia. Es © 2023 Brandsemut, conserva sus propios términos y su autor la ofrece gratis solo para uso personal: el uso comercial o promocional requiere una licencia aparte.