Timer de sesiones de productividad con dashboard, sincronización con Google Calendar/Sheets y overlay para OBS.
90Minutos/
server/ Express + WebSocket (timer, API, integraciones)
index.js Servidor principal, rutas API, lógica del timer
google.js OAuth2, Calendar, Sheets
storage.js Lectura/escritura atómica de JSON
data/ Config, sesiones, tokens (gitignored)
dashboard/ Panel de control web
index.html UI principal
dashboard.js Lógica del dashboard
dashboard.css Estilos (dark/light mode)
overlay/ Browser source para OBS
scripts/ Scripts auxiliares (auth, packaging, shell)
icon/ Icono de la app
- Node.js >= 18
- (Opcional) Cuenta Google para Calendar/Sheets
git clone <repo> && cd 90Minutos
npm install
cp .env.example .env
# Editar .env con tus credenciales
npm run devEl servidor arranca en http://127.0.0.1:5173. El dashboard está en /dashboard/index.html.
| Variable | Descripción | Requerida |
|---|---|---|
PORT |
Puerto HTTP (default: 5173) | No |
HOST |
Host de escucha (default: 127.0.0.1) | No |
BASE_URL |
URL base para OAuth callback | No |
GOOGLE_CLIENT_ID |
Client ID de Google OAuth | Para Calendar/Sheets |
GOOGLE_CLIENT_SECRET |
Client Secret de Google OAuth | Para Calendar/Sheets |
SHEET_ID |
ID del Google Sheet | Para sync Sheets |
GOOGLE_CALENDAR_ID |
ID del calendario (default: primary) | No |
npm run dev # Iniciar servidor
npm run stop # Detener servidor
npm run google:auth # Abrir flujo OAuth de GooglePanel de control accesible en http://127.0.0.1:5173/dashboard/index.html.
- Control remoto del timer (start/pause/resume/reset/finish) con WebSocket en tiempo real
- Templates de sesión — inicio rápido con categoría, duración y tipo preconfigurados
- KPI Cards — horas totales, sesiones esta semana, duración media, día más productivo
- Heatmap de actividad — calendario estilo GitHub contributions (~6 meses)
- Gráficos — distribución por categoría (donut), últimos 7 días (barras), tendencia mensual (line chart), radar de categorías
- Gamificación — sistema de XP/niveles (10h = 1 nivel), racha diaria y mejor racha
- Achievements — 13 badges desbloqueables
- Vista calendario mensual — navegación mes a mes con horas por día
- Comparativa semanal — esta semana vs anterior con delta porcentual
- Historial de sesiones — tabla con filtros, búsqueda, paginación (20/página), selección múltiple y borrado en lote
- Gestión de categorías — crear, renombrar, color picker, eliminar
- Export CSV — descarga de sesiones filtradas
- Sync Google Sheets — importar sesiones desde el Sheet configurado
- Backup/Restore — exportar/importar config + sesiones como JSON
- Duplicar sesión — clonar una sesión existente
- Notas por sesión — texto libre asociado a cada sesión
- Modo Pomodoro — ciclos trabajo/descanso configurables
- Dark/Light mode — toggle con persistencia en localStorage
- Atajos de teclado — Space (play/pause), R (reset), Ctrl+S (guardar), ? (ayuda)
Todos los endpoints escuchan en 127.0.0.1:5173.
| Método | Ruta | Descripción |
|---|---|---|
GET |
/api/state |
Estado actual del timer |
GET |
/api/config |
Configuración |
POST |
/api/config |
Actualizar configuración |
GET |
/api/sessions |
Listar sesiones |
POST |
/api/sessions |
Crear/duplicar sesión |
PUT |
/api/sessions/:id |
Editar sesión |
DELETE |
/api/sessions/:id |
Borrar sesión |
DELETE |
/api/sessions |
Borrar todas (requiere header X-Confirm-Delete: true) |
POST |
/api/sessions/bulk-delete |
Borrado múltiple |
POST |
/api/sessions/importFromSheets |
Importar desde Google Sheets |
GET |
/api/stats |
Estadísticas por categoría |
GET |
/api/health |
Health check del servidor |
GET |
/api/backup |
Exportar backup completo |
POST |
/api/restore |
Restaurar desde backup |
GET |
/api/google/auth |
Iniciar flujo OAuth |
GET |
/api/google/callback |
Callback OAuth |
GET |
/api/sheet/id |
Obtener Sheet ID configurado |
Puerto 8765 en 127.0.0.1. Mensajes JSON:
Server → Client:
{ type: "state", payload: { state, durationSec, remainingSec, category, ... } }{ type: "session:complete", payload: { ... } }{ type: "config:update", payload: { ... } }
Client → Server:
{ type: "command", action: "start|pause|resume|reset|finish|add|setCategory|setDurationSec|setLanguage|setSessionType|setSessionUrl", payload: ... }
- El servidor solo escucha en
127.0.0.1(no accesible desde la red) - CORS restringido a orígenes exactos (
http://127.0.0.1:5173,http://localhost:5173) - Borrado masivo requiere header de confirmación
X-Confirm-Delete - POST /api/config filtra solo keys permitidas (whitelist)
- Notificaciones de escritorio usan
execFile(sin shell) para evitar inyección de comandos - Dashboard sanitiza HTML en todas las interpolaciones dinámicas
server/data/y.envestán en.gitignore
Browser source en http://127.0.0.1:5173/overlay/index.html. Se conecta vía WebSocket y muestra el timer en tiempo real con dial de progreso y alarma sonora al finalizar.
Ver LICENSE.