npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@spira-labs/scorm-lms-sdk

v1.1.6

Published

SDK completo para cargar, gestionar y reproducir contenido SCORM 1.2 y 2004 con Express + Firebase

Downloads

114

Readme

🎓 SCORM LMS SDK

SDK completo para cargar, gestionar y reproducir contenido SCORM 1.2 y 2004 en cualquier aplicación web. Incluye servidor Express listo para producción y cliente JavaScript framework-agnostic.

✨ Características

  • 🚀 Instalación simple - Un solo comando npm
  • 🖥️ Servidor incluido - Express server con API REST completa
  • ☁️ Firebase integrado - Firestore para metadata y progreso de estudiantes
  • 📁 Filesystem preservado - Archivos SCORM en servidor (referencias relativas funcionan)
  • 🎨 Framework agnostic - React, Vue, Angular, Vanilla JS
  • 📊 SCORM compliant - Soporta SCORM 1.2 y 2004
  • 💾 Progreso persistente - Guardado automático en Firebase
  • 🔧 Templates incluidos - Configuraciones listas para producción
  • 📦 PM2 ready - Config incluida para despliegue con PM2

📦 Instalación npm:

npm install @spira-labs/scorm-lms-sdk

Yarn:

yarn add @spira-labs/scorm-lms-sdk

🚀 Quick Start

1. Instalar dependencias

npm install firebase

2. Copiar archivos de configuración

El SDK incluye templates listos para usar. Cópialos a tu proyecto:

# Desde tu proyecto, copiar templates
cp node_modules/@spira-labs/scorm-lms-sdk/templates/server.js ./server.js

# Si tu app esta con Vite tambien copia vite.config.js
cp node_modules/@spira-labs/scorm-lms-sdk/templates/vite.config.js ./vite.config.js

O crea manualmente:

server.js (en la raíz de tu proyecto):

import express from 'express';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();
app.use(express.json({ limit: '100mb' }));

const publicDir = path.join(__dirname, 'public');
const uploadsDir = path.join(publicDir, 'uploads');

if (!fs.existsSync(uploadsDir)) {
  fs.mkdirSync(uploadsDir, { recursive: true });
}

app.use(express.static(publicDir));

// Rutas API (ver templates/server.js completo)
// ...

app.listen(3001, () => {
  console.log('File storage server running on port 3001');
});

vite.config.js:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    host: '127.0.0.1', // IMPORTANTE: debe ser 127.0.0.1
    proxy: {
      "/api": {
        target: "http://localhost:3001",
        changeOrigin: true,
      },
      "/uploads": {
        target: "http://localhost:3001",
        changeOrigin: true,
      },
    },
  },
});

3. Iniciar servidor

# Terminal 1: Servidor Express
node server.js

# Terminal 2: React CRA
yarn start

# Terminal 2 alterno: Vite dev server
npm run dev

4. Configurar Firebase

// src/config/firebase.js
export const firebaseConfig = {
  apiKey: "YOUR-API-KEY",
  projectId: "your-project",
  authDomain: "your-project.firebaseapp.com",
  storageBucket: "your-project.appspot.com",
  messagingSenderId: "123456789",
  appId: "1:123456789:web:abc123"
};

// Configuración del servidor
export const serverUrl = ''; // Vacío para desarrollo (usa proxy de Vite)
export const basePath = '';

⚠️ Si tu app ya tiene Firebase inicializado

Si tu aplicación ya usa Firebase y recibes el error:

FirebaseError: Firebase App named '[DEFAULT]' already exists with different options or config

El SDK tiene 3 formas de uso para evitar conflictos:

Opción 1: Pasar la app Firebase existente (RECOMENDADO)

import { getApp } from 'firebase/app';
import { ScormManager } from '@spira-labs/scorm-lms-sdk';

// Obtener la app Firebase que tu aplicación ya inicializó
const firebaseApp = getApp();

// Pasar la app al ScormManager
const manager = new ScormManager(
  { app: firebaseApp },  // ← Pasas la app directamente
  'http://localhost:3001'
);

Opción 2: Usar firebaseConfig (el SDK detecta apps existentes)

import { ScormManager } from '@spira-labs/scorm-lms-sdk';

// El SDK verifica si ya existe una app antes de inicializar
const manager = new ScormManager(
  { firebaseConfig: firebaseConfig },  // ← Wrapper con firebaseConfig
  'http://localhost:3001'
);

Opción 3: Backward compatibility (config directo)

// Forma anterior - el SDK ahora detecta automáticamente apps existentes
const manager = new ScormManager(
  firebaseConfig,  // ← Config directo (sigue funcionando)
  'http://localhost:3001'
);

5. Usar en tu aplicación

import { ScormManager, ScormPlayer } from '@spira-labs/scorm-lms-sdk';
import { firebaseConfig } from './config/firebase';

// Crear manager (elige una de las 3 opciones de arriba)
const manager = new ScormManager(
  firebaseConfig,
  'http://localhost:3001'
);

// Subir curso SCORM
const handleUpload = async (zipFile) => {
  const course = await manager.uploadCourse(zipFile, {
    userId: 'user123',
    title: 'Mi Curso'
  });
  console.log('Curso subido:', course.id);
};

// Listar cursos
const courses = await manager.listCourses();

// Reproducir curso
const player = new ScormPlayer(
  courseId,
  document.getElementById('player-container'),
  manager
);

await player.launch('user123');

// Escuchar eventos
player.onProgress((data) => {
  console.log('Progreso:', data.completionStatus, data.scoreRaw);
});

player.onComplete(() => {
  alert('¡Curso completado!');
});

🏗️ Arquitectura

Tu Aplicación
├── Frontend (React/Vue/Angular/etc)
│   └── @spira-labs/scorm-lms-sdk/client
│       ├── ScormManager → Gestión de cursos
│       ├── ScormPlayer → Reproductor SCORM
│       └── ScormAPI → Comunicación con SCORM
├── Backend (Express Server)
│   └── @spira-labs/scorm-lms-sdk/server
│       ├── /api/upload → Subir cursos
│       ├── /api/courses → Listar cursos
│       └── /uploads → Servir archivos SCORM
└── Firebase Firestore
    ├── /courses → Metadata de cursos
    └── /progress → Progreso de estudiantes

¿Dónde va cada cosa?

| Componente | Ubicación | Propósito | |------------|-----------|-----------| | Archivos SCORM (HTML, JS, CSS, assets) | Express Server (public/uploads/) | Preservar referencias relativas del contenido | | Metadata de cursos (título, versión, ruta) | Firebase Firestore /courses | Información del curso | | Progreso estudiantes (CMI data, scores) | Firebase Firestore /progress | Estado de avance | | UI/UX | Tu proyecto | Diseño personalizado |


📚 API Reference

ScormManager

Gestiona la carga, listado y eliminación de cursos SCORM.

const manager = new ScormManager(firebaseConfig, serverUrl);

// Subir curso
const course = await manager.uploadCourse(
  file,              // File object (zip)
  {
    userId: 'user123',
    title: 'Nombre del curso',
    description: 'Descripción opcional'
  }
);

// Listar todos los cursos
const allCourses = await manager.listCourses();

// Listar cursos de un usuario
const userCourses = await manager.listCourses('user123');

// Obtener curso específico
const course = await manager.getCourse(courseId);

// Eliminar curso
await manager.deleteCourse(courseId);

// Guardar progreso manualmente (normalmente automático)
await manager.saveCourseProgress(courseId, userId, {
  "cmi.core.lesson_status": "completed",
  "cmi.core.score.raw": "85"
});

// Obtener progreso
const progress = await manager.getCourseProgress(courseId, userId);

ScormPlayer

Reproduce contenido SCORM en un iframe y maneja la comunicación API.

const player = new ScormPlayer(courseId, containerElement, manager);

// Lanzar curso
await player.launch(userId, existingProgress?);

// Eventos
player.onProgress((data) => {
  // data.completionStatus, data.successStatus, data.scoreRaw, etc.
});

player.onComplete(() => {
  // Curso completado
});

player.onExit(() => {
  // Usuario salió del curso
});

// Limpiar recursos
player.destroy();

ScormAPI

API SCORM que implementa los estándares 1.2 y 2004 (uso interno del player).

import { ScormAPI } from '@spira-labs/scorm-lms-sdk';

const api = new ScormAPI('1.2'); // o '2004'

api.LMSInitialize('');
api.LMSSetValue('cmi.core.lesson_status', 'completed');
const status = api.LMSGetValue('cmi.core.lesson_status');
api.LMSCommit('');
api.LMSFinish('');

🔧 Configuración

Servidor de Desarrollo

Opción 1: CLI (más rápido)

npx scorm-server --port 3001 --cors-origin "*"

Opción 2: Script en package.json

{
  "scripts": {
    "scorm": "scorm-server --port 3001",
    "dev": "vite",
    "dev:all": "concurrently \"npm run scorm\" \"npm run dev\""
  }
}

Servidor de Producción

El SDK incluye templates listos para producción:

# En tu proyecto, copiar templates
cp node_modules/@spira-labs/scorm-lms-sdk/templates/server.production.js ./server.js
cp node_modules/@spira-labs/scorm-lms-sdk/templates/ecosystem.config.cjs ./

server.js - Servidor optimizado para producción:

const express = require('express');
const path = require('path');
const scormRoutes = require('@spira-labs/scorm-lms-sdk/server');

const app = express();
const PORT = process.env.PORT || 3001;

// Middleware
app.use(express.json({ limit: '200mb' }));
app.use(express.static(path.join(__dirname, 'dist')));
app.use('/uploads', express.static(path.join(__dirname, 'public/uploads')));

// API Routes
app.use('/api', scormRoutes);

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// SPA fallback
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

app.listen(PORT, () => {
  console.log(`✅ Server running on port ${PORT}`);
});

ecosystem.config.cjs - Configuración PM2:

module.exports = {
  apps: [{
    name: 'scorm-lms',
    script: './server.js',
    instances: 1,
    exec_mode: 'fork',
    env: {
      NODE_ENV: 'production',
      PORT: 3001
    },
    error_file: './logs/error.log',
    out_file: './logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    watch: false,
    max_memory_restart: '500M'
  }]
};

Firebase Firestore

Solo necesitas Firestore (NO Storage). Reglas de seguridad:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Cursos: lectura pública, escritura autenticada
    match /courses/{courseId} {
      allow read: if true;
      allow write: if request.auth != null;
    }
    
    // Progreso: solo el usuario dueño
    match /progress/{userId}/courses/{courseId} {
      allow read, write: if request.auth != null && 
                          request.auth.uid == userId;
    }
  }
}

🚀 Despliegue en Producción

Preparar tu aplicación (Local)

# 1. Navegar a tu proyecto
cd tu-proyecto

# 2. Copiar templates del SDK
cp node_modules/@spira-labs/scorm-lms-sdk/templates/server.production.js ./server.js
cp node_modules/@spira-labs/scorm-lms-sdk/templates/ecosystem.config.cjs ./

# 3. Build del frontend
npm run build

# 4. Crear paquete para desplegar
zip -r deploy.zip \
  dist/ \
  server.js \
  ecosystem.config.cjs \
  package.json \
  package-lock.json

Desplegar en VPS

# 1. Subir archivo (ejemplo con scp)
scp deploy.zip usuario@tu-servidor:~/

# 2. En el servidor
ssh usuario@tu-servidor
mkdir -p ~/apps/scorm-lms
cd ~/apps/scorm-lms
unzip ~/deploy.zip

# 3. Instalar dependencias
npm install --omit=dev

# 4. Crear directorios necesarios
mkdir -p public/uploads logs

# 5. Iniciar con PM2
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup

Configurar Nginx

server {
    listen 80;
    server_name tu-dominio.com;

    client_max_body_size 100M;

    # API del SDK
    location /api/ {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        
        # Timeouts para uploads grandes
        proxy_connect_timeout 600;
        proxy_send_timeout 600;
        proxy_read_timeout 600;
    }

    # Archivos SCORM subidos
    location ^~ /uploads/ {
        alias /home/usuario/apps/scorm-lms/public/uploads/;
        autoindex off;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Health check
    location /health {
        proxy_pass http://localhost:3001;
        access_log off;
    }

    # Frontend estático
    location / {
        root /home/usuario/apps/scorm-lms/dist;
        try_files $uri $uri/ /index.html;
        index index.html;
    }
}

Importante: Dar permisos a Nginx para acceder a los archivos:

sudo chmod o+rx /home/usuario
sudo chmod o+rx /home/usuario/apps
sudo chmod -R o+rx /home/usuario/apps/scorm-lms/dist
sudo chmod -R o+rx /home/usuario/apps/scorm-lms/public
sudo systemctl restart nginx

📁 Estructura de Datos

Firestore

// Collection: courses
{
  id: "course_1234567890",
  title: "Introducción a JavaScript",
  description: "Curso básico de JS",
  scormVersion: "SCORM 1.2",
  launchPath: "index.html",
  uploadedBy: "user123",
  serverUrl: "http://localhost:3001",
  coursePath: "/uploads/course_1234567890",
  createdAt: Timestamp
}

// Collection: progress/{userId}/courses
{
  courseId: "course_1234567890",
  userId: "user123",
  scormData: {
    "cmi.core.lesson_status": "incomplete",
    "cmi.core.score.raw": "75",
    "cmi.core.score.min": "0",
    "cmi.core.score.max": "100",
    "cmi.suspend_data": "bookmark=lesson3",
    "cmi.core.lesson_location": "page5"
  },
  completionStatus: "incomplete",
  successStatus: "unknown",
  scoreRaw: 75,
  lastAccess: Timestamp,
  updatedAt: Timestamp
}

Filesystem (Servidor)

tu-proyecto/
├── dist/                           # Frontend compilado
├── public/
│   └── uploads/                    # Cursos SCORM
│       └── course_1234567890/
│           ├── index.html          # Entry point del curso
│           ├── imsmanifest.xml     # Manifest SCORM
│           └── scormcontent/       # Contenido del curso
│               ├── assets/
│               ├── js/
│               └── css/
├── server.js                       # Servidor producción
├── ecosystem.config.cjs            # Config PM2
└── package.json

📖 Ejemplos Completos

React + Vite

import { useState, useEffect, useRef } from 'react';
import { ScormManager, ScormPlayer } from '@spira-labs/scorm-lms-sdk';
import { firebaseConfig } from './config/firebase';

function App() {
  const [manager] = useState(() => 
    new ScormManager(firebaseConfig, 'http://localhost:3001')
  );
  const [courses, setCourses] = useState([]);
  const [selectedCourse, setSelectedCourse] = useState(null);

  useEffect(() => {
    loadCourses();
  }, []);

  const loadCourses = async () => {
    const data = await manager.listCourses();
    setCourses(data);
  };

  const handleUpload = async (e) => {
    const file = e.target.files[0];
    if (!file) return;
    
    try {
      await manager.uploadCourse(file, { 
        userId: 'user123',
        title: file.name.replace('.zip', '')
      });
      await loadCourses();
      alert('¡Curso subido exitosamente!');
    } catch (error) {
      alert('Error al subir curso: ' + error.message);
    }
  };

  const launchCourse = (courseId) => {
    setSelectedCourse(courseId);
  };

  return (
    <div>
      <h1>Mi LMS SCORM</h1>
      
      {!selectedCourse ? (
        <>
          <input 
            type="file" 
            accept=".zip" 
            onChange={handleUpload} 
          />
          
          <ul>
            {courses.map(course => (
              <li key={course.id}>
                {course.title}
                <button onClick={() => launchCourse(course.id)}>
                  Lanzar
                </button>
              </li>
            ))}
          </ul>
        </>
      ) : (
        <ScormPlayerComponent 
          courseId={selectedCourse}
          manager={manager}
          onClose={() => setSelectedCourse(null)}
        />
      )}
    </div>
  );
}

function ScormPlayerComponent({ courseId, manager, onClose }) {
  const containerRef = useRef(null);
  const playerRef = useRef(null);

  useEffect(() => {
    const player = new ScormPlayer(courseId, containerRef.current, manager);
    playerRef.current = player;

    player.launch('user123');

    player.onProgress((data) => {
      console.log('Progreso:', data);
    });

    player.onComplete(() => {
      alert('¡Curso completado!');
    });

    return () => player.destroy();
  }, [courseId]);

  return (
    <div>
      <button onClick={onClose}>Cerrar</button>
      <div ref={containerRef} style={{ width: '100%', height: '600px' }} />
    </div>
  );
}

export default App;

Vue 3

<script setup>
import { ref, onMounted } from 'vue';
import { ScormManager } from '@spira-labs/scorm-lms-sdk';
import { firebaseConfig } from './config/firebase';

const manager = new ScormManager(firebaseConfig, 'http://localhost:3001');
const courses = ref([]);

onMounted(async () => {
  courses.value = await manager.listCourses();
});

const handleUpload = async (event) => {
  const file = event.target.files[0];
  await manager.uploadCourse(file, { userId: 'user123' });
  courses.value = await manager.listCourses();
};
</script>

<template>
  <div>
    <input type="file" @change="handleUpload" accept=".zip" />
    <ul>
      <li v-for="course in courses" :key="course.id">
        {{ course.title }}
      </li>
    </ul>
  </div>
</template>

Vanilla JavaScript

<!DOCTYPE html>
<html>
<head>
  <title>SCORM LMS</title>
</head>
<body>
  <input type="file" id="upload" accept=".zip" />
  <div id="courses"></div>
  <div id="player"></div>

  <script type="module">
    import { ScormManager, ScormPlayer } from '@spira-labs/scorm-lms-sdk';
    
    const manager = new ScormManager(firebaseConfig, 'http://localhost:3001');
    
    // Upload
    document.getElementById('upload').addEventListener('change', async (e) => {
      const file = e.target.files[0];
      await manager.uploadCourse(file, { userId: 'user123' });
      loadCourses();
    });
    
    // List courses
    async function loadCourses() {
      const courses = await manager.listCourses();
      const html = courses.map(c => `
        <div>
          <h3>${c.title}</h3>
          <button onclick="launchCourse('${c.id}')">Lanzar</button>
        </div>
      `).join('');
      document.getElementById('courses').innerHTML = html;
    }
    
    // Launch course
    window.launchCourse = async (courseId) => {
      const container = document.getElementById('player');
      const player = new ScormPlayer(courseId, container, manager);
      await player.launch('user123');
      
      player.onComplete(() => {
        alert('¡Completado!');
      });
    };
    
    loadCourses();
  </script>
</body>
</html>

🛠️ Comandos Útiles

Desarrollo

# Iniciar servidor SCORM
npx scorm-server

# Con opciones
npx scorm-server --port 4000 --cors-origin "http://localhost:5173"

# Ver ayuda
npx scorm-server --help

Producción (PM2)

# Iniciar
pm2 start ecosystem.config.cjs

# Estado
pm2 status

# Logs
pm2 logs scorm-lms
pm2 logs scorm-lms --lines 100

# Reiniciar
pm2 restart scorm-lms

# Detener
pm2 stop scorm-lms

# Auto-arranque
pm2 save
pm2 startup

# Monitoreo
pm2 monit

Nginx

# Probar configuración
sudo nginx -t

# Reiniciar
sudo systemctl restart nginx

# Ver logs
sudo tail -f /var/log/nginx/error.log

# Estado
sudo systemctl status nginx

🐛 Troubleshooting

Error 502 Bad Gateway

Causa: Nginx no puede conectarse al backend.

# Verificar que PM2 está corriendo
pm2 status

# Ver logs
pm2 logs scorm-lms

# Verificar puerto
sudo lsof -i :3001

# Reiniciar
pm2 restart scorm-lms

No se pueden subir cursos

Causa: Permisos o límite de tamaño.

# Verificar directorio
ls -la public/uploads/
chmod -R 755 public/uploads/

# Verificar límite en Nginx (debe tener client_max_body_size 100M)
sudo nano /etc/nginx/sites-available/scorm-lms

# Ver logs
pm2 logs scorm-lms

Archivos SCORM dan 403/404

Causa: Nginx no tiene permisos.

# Dar permisos
sudo chmod o+rx /home/$(whoami)
sudo chmod o+rx /home/$(whoami)/apps
sudo chmod -R o+rx /home/$(whoami)/apps/scorm-lms/public

# Reiniciar nginx
sudo systemctl restart nginx

Player no carga el curso

Causas comunes:

  1. CORS mal configurado
  2. Ruta del servidor incorrecta
  3. Firebase no configurado
// Verificar config
console.log('Server URL:', manager.serverUrl);
console.log('Firebase Config:', firebaseConfig);

// Ver errores en consola del navegador (F12)

📊 Estructura Final en Producción

VPS/Servidor
└── ~/apps/scorm-lms/
    ├── dist/                       # Frontend (React/Vue build)
    │   ├── index.html
    │   └── assets/
    │       ├── index-[hash].js
    │       └── index-[hash].css
    ├── node_modules/
    │   └── @spira-labs/
    │       └── scorm-lms-sdk/      # El SDK
    │           ├── client/         # Cliente JS
    │           └── server/         # Express server
    ├── public/
    │   └── uploads/                # Cursos SCORM subidos
    │       └── course_*/
    ├── logs/                       # Logs de PM2
    ├── server.js                   # Servidor producción
    ├── ecosystem.config.cjs        # Config PM2
    ├── package.json
    └── package-lock.json

✅ Checklist de Despliegue

  • [ ] SDK instalado (npm install @spira-labs/scorm-lms-sdk)
  • [ ] Firebase configurado (Firestore + reglas)
  • [ ] Frontend compilado (npm run build)
  • [ ] Templates copiados (server.js, ecosystem.config.cjs)
  • [ ] Dependencias instaladas en servidor (npm install --omit=dev)
  • [ ] Directorios creados (public/uploads, logs)
  • [ ] PM2 corriendo y configurado para auto-arranque
  • [ ] Nginx configurado como proxy reverso
  • [ ] Permisos correctos para Nginx (chmod o+rx)
  • [ ] Firewall configurado (puertos 80, 443)
  • [ ] Health check funciona (curl http://localhost:3001/health)
  • [ ] Aplicación accesible desde navegador

🤝 Soporte y Documentación

  • 📚 Guía Completa: Ver /docs/GUIA-COMPLETA.md
  • 🏗️ Arquitectura: Ver /docs/ARQUITECTURA.md
  • 🚀 Deploy Nginx: Ver /docs/DEPLOY-NGINX.md
  • 💻 Demo App: Ver /demo para ejemplo completo
  • 🐛 Issues: GitHub Issues

📝 Licencia

MIT © Spira Labs


🎉 ¡Comienza Ahora!

# Instalar
npm install @spira-labs/scorm-lms-sdk firebase

# Iniciar servidor
npx scorm-server

# Ver ejemplo completo
cd node_modules/@spira-labs/scorm-lms-sdk/demo
npm install
npm run dev

¡Listo para desplegar tu LMS SCORM! 🚀