623 lines
13 KiB
Markdown
623 lines
13 KiB
Markdown
# Dashboard de Seguimiento - Guía de Implementación
|
|
|
|
> Sistema KISS de seguimiento académico: Markdown + Basic Auth + PHP + Vanilla JS
|
|
|
|
**Servidor:** qu3v3d0.tech | **Filosofía:** Unix + KISS
|
|
|
|
---
|
|
|
|
## Qué hace este sistema
|
|
|
|
```
|
|
Profesor sube tabla-de-seguimiento.md
|
|
↓
|
|
Alumnos acceden con user/pass SFTP
|
|
↓
|
|
Cada alumno ve SOLO su progreso
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 1: Crear htpasswd para Basic Auth
|
|
|
|
```bash
|
|
# Generar archivo de passwords (password = username por defecto)
|
|
sudo touch /etc/nginx/.htpasswd_sftp
|
|
|
|
for user in alumno{01..20}; do
|
|
echo "$user:$(openssl passwd -apr1 $user)" | \
|
|
sudo tee -a /etc/nginx/.htpasswd_sftp > /dev/null
|
|
done
|
|
|
|
# Permisos restrictivos
|
|
sudo chmod 640 /etc/nginx/.htpasswd_sftp
|
|
sudo chown root:www-data /etc/nginx/.htpasswd_sftp
|
|
|
|
# Verificar
|
|
sudo cat /etc/nginx/.htpasswd_sftp
|
|
```
|
|
|
|
**✅ Verificación:**
|
|
```
|
|
alumno01:$apr1$xyz$...
|
|
alumno02:$apr1$abc$...
|
|
...
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 2: Crear estructura de directorios
|
|
|
|
```bash
|
|
# Crear directorios
|
|
sudo mkdir -p /home/admin/html/dashboard/data
|
|
|
|
# Permisos
|
|
sudo chown -R admin:www-data /home/admin/html/dashboard
|
|
sudo chmod 755 /home/admin/html/dashboard
|
|
sudo chmod 775 /home/admin/html/dashboard/data
|
|
```
|
|
|
|
**Estructura final:**
|
|
```
|
|
/home/admin/html/dashboard/
|
|
├── index.html # Cliente (vanilla JS + marked.js)
|
|
├── api.php # filtro por usuario
|
|
├── style.css # estilo minimalista
|
|
└── data/
|
|
└── tabla-de-seguimiento.md
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 3: Crear api.php
|
|
|
|
```bash
|
|
sudo tee /home/admin/html/dashboard/api.php > /dev/null << 'EOF'
|
|
<?php
|
|
header('Content-Type: text/plain; charset=utf-8');
|
|
|
|
// Obtener usuario autenticado
|
|
$user = $_SERVER['REMOTE_USER'] ?? '';
|
|
if (empty($user)) {
|
|
http_response_code(401);
|
|
exit("# Error\n\nNo autenticado.");
|
|
}
|
|
|
|
// Leer markdown
|
|
$file = __DIR__ . '/data/tabla-de-seguimiento.md';
|
|
if (!file_exists($file)) {
|
|
http_response_code(404);
|
|
exit("# Dashboard sin Datos\n\nEl profesor aún no ha subido la tabla.");
|
|
}
|
|
|
|
$markdown = file_get_contents($file);
|
|
$lines = explode("\n", $markdown);
|
|
|
|
// Filtrar solo sección del usuario
|
|
$output = [];
|
|
$inUserSection = false;
|
|
$headerShown = false;
|
|
|
|
foreach ($lines as $line) {
|
|
// Título principal
|
|
if (!$headerShown && preg_match('/^# /', $line)) {
|
|
$output[] = $line;
|
|
$headerShown = true;
|
|
continue;
|
|
}
|
|
|
|
// Detectar sección ## username
|
|
if (preg_match('/^## (.+)$/', $line, $matches)) {
|
|
$sectionUser = trim($matches[1]);
|
|
$inUserSection = ($sectionUser === $user);
|
|
if ($inUserSection) {
|
|
$output[] = $line;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Si estamos en sección del usuario, capturar
|
|
if ($inUserSection) {
|
|
$output[] = $line;
|
|
}
|
|
}
|
|
|
|
// Si no hay datos para el usuario
|
|
if (count($output) <= 1) {
|
|
$output[] = "\n## $user\n";
|
|
$output[] = "**No hay datos registrados.**\n";
|
|
}
|
|
|
|
echo implode("\n", $output);
|
|
EOF
|
|
|
|
sudo chown admin:www-data /home/admin/html/dashboard/api.php
|
|
sudo chmod 644 /home/admin/html/dashboard/api.php
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 4: Crear index.html
|
|
|
|
```bash
|
|
sudo tee /home/admin/html/dashboard/index.html > /dev/null << 'EOF'
|
|
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Dashboard de Seguimiento</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>🎯 Dashboard de Seguimiento</h1>
|
|
<p class="subtitle">qu3v3d0.tech</p>
|
|
</header>
|
|
|
|
<main id="dashboard">
|
|
<div class="loader">
|
|
<p>Cargando tu progreso...</p>
|
|
<div class="spinner"></div>
|
|
</div>
|
|
</main>
|
|
|
|
<footer>
|
|
<p>Filosofía KISS + Unix</p>
|
|
</footer>
|
|
|
|
<script>
|
|
const parseMarkdown = (md) => marked.parse(md);
|
|
const renderHTML = (html) => {
|
|
document.getElementById('dashboard').innerHTML = html;
|
|
};
|
|
const showError = (msg) => {
|
|
const html = `<div class="error"><h2>⚠️ Error</h2><p>${msg}</p></div>`;
|
|
renderHTML(html);
|
|
};
|
|
|
|
fetch('api.php')
|
|
.then(r => r.ok ? r.text() : Promise.reject(r))
|
|
.then(parseMarkdown)
|
|
.then(renderHTML)
|
|
.catch(e => showError(e.message));
|
|
</script>
|
|
</body>
|
|
</html>
|
|
EOF
|
|
|
|
sudo chown admin:www-data /home/admin/html/dashboard/index.html
|
|
sudo chmod 644 /home/admin/html/dashboard/index.html
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 5: Crear style.css
|
|
|
|
```bash
|
|
sudo tee /home/admin/html/dashboard/style.css > /dev/null << 'EOF'
|
|
:root {
|
|
--bg: #f5f5f5;
|
|
--fg: #333;
|
|
--accent: #0066cc;
|
|
--success: #28a745;
|
|
--error: #dc3545;
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family: system-ui, sans-serif;
|
|
line-height: 1.6;
|
|
color: var(--fg);
|
|
background: var(--bg);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
header {
|
|
background: var(--accent);
|
|
color: white;
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
}
|
|
|
|
header h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
|
|
.subtitle { font-size: 0.9rem; opacity: 0.9; }
|
|
|
|
main {
|
|
flex: 1;
|
|
max-width: 900px;
|
|
width: 100%;
|
|
margin: 2rem auto;
|
|
background: white;
|
|
padding: 2rem;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.loader { text-align: center; padding: 2rem; }
|
|
.spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
margin: 1rem auto;
|
|
border: 4px solid #ddd;
|
|
border-top-color: var(--accent);
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
h1 {
|
|
color: var(--accent);
|
|
border-bottom: 2px solid var(--accent);
|
|
padding-bottom: 0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
h2 { margin-top: 1.5rem; margin-bottom: 1rem; color: #555; }
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
th, td {
|
|
padding: 0.75rem;
|
|
text-align: left;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
th {
|
|
background: var(--bg);
|
|
font-weight: 600;
|
|
color: var(--accent);
|
|
}
|
|
|
|
tbody tr:hover { background: #f9f9f9; }
|
|
|
|
ul { margin-left: 1.5rem; margin-bottom: 1rem; }
|
|
li { margin-bottom: 0.5rem; }
|
|
|
|
input[type="checkbox"] { margin-right: 0.5rem; }
|
|
|
|
.error {
|
|
background: #fff3cd;
|
|
border: 2px solid #ffc107;
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.error h2 { color: var(--error); margin-bottom: 1rem; }
|
|
|
|
footer {
|
|
background: #2c3e50;
|
|
color: white;
|
|
text-align: center;
|
|
padding: 1rem;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
main { margin: 1rem; padding: 1rem; }
|
|
header h1 { font-size: 1.4rem; }
|
|
}
|
|
EOF
|
|
|
|
sudo chown admin:www-data /home/admin/html/dashboard/style.css
|
|
sudo chmod 644 /home/admin/html/dashboard/style.css
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 6: Configurar nginx
|
|
|
|
```bash
|
|
sudo tee /etc/nginx/sites-available/dashboard > /dev/null << 'EOF'
|
|
server {
|
|
listen 80;
|
|
server_name dashboard.qu3v3d0.tech;
|
|
|
|
root /home/admin/html/dashboard;
|
|
index index.html;
|
|
|
|
# Basic Auth
|
|
auth_basic "Dashboard Alumnos";
|
|
auth_basic_user_file /etc/nginx/.htpasswd_sftp;
|
|
|
|
# PHP-FPM
|
|
location ~ \.php$ {
|
|
include snippets/fastcgi-php.conf;
|
|
fastcgi_pass unix:/var/run/php/php-fpm.sock;
|
|
fastcgi_param REMOTE_USER $remote_user;
|
|
}
|
|
|
|
# Caché de estáticos
|
|
location ~* \.(css|js)$ {
|
|
expires 1h;
|
|
add_header Cache-Control "public";
|
|
}
|
|
|
|
# Proteger data/
|
|
location /data/ { deny all; }
|
|
|
|
# Logs
|
|
access_log /var/log/nginx/dashboard-access.log;
|
|
error_log /var/log/nginx/dashboard-error.log;
|
|
|
|
autoindex off;
|
|
}
|
|
EOF
|
|
|
|
# Activar
|
|
sudo ln -s /etc/nginx/sites-available/dashboard /etc/nginx/sites-enabled/
|
|
|
|
# Validar y recargar
|
|
sudo nginx -t
|
|
sudo systemctl reload nginx
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 7: Crear tabla-de-seguimiento.md
|
|
|
|
```bash
|
|
sudo tee /home/admin/html/dashboard/data/tabla-de-seguimiento.md > /dev/null << 'EOF'
|
|
# Seguimiento - Grupo 2026
|
|
|
|
## alumno01
|
|
| Tarea | Estado | Fecha |
|
|
|:------|:------:|------:|
|
|
| Config nginx básica | ❌ | - |
|
|
| JSON con CORS | ❌ | - |
|
|
| Error pages | ❌ | - |
|
|
| Caché estático | ❌ | - |
|
|
|
|
## alumno02
|
|
| Tarea | Estado | Fecha |
|
|
|:------|:------:|------:|
|
|
| Config nginx básica | ❌ | - |
|
|
| JSON con CORS | ❌ | - |
|
|
| Error pages | ❌ | - |
|
|
| Caché estático | ❌ | - |
|
|
|
|
## alumno03
|
|
| Tarea | Estado | Fecha |
|
|
|:------|:------:|------:|
|
|
| Config nginx básica | ❌ | - |
|
|
| JSON con CORS | ❌ | - |
|
|
| Error pages | ❌ | - |
|
|
| Caché estático | ❌ | - |
|
|
EOF
|
|
|
|
sudo chown admin:www-data /home/admin/html/dashboard/data/tabla-de-seguimiento.md
|
|
sudo chmod 664 /home/admin/html/dashboard/data/tabla-de-seguimiento.md
|
|
```
|
|
|
|
**Formato alternativo con checkboxes:**
|
|
```markdown
|
|
## alumno01
|
|
- [ ] Config nginx básica
|
|
- [ ] JSON con CORS
|
|
- [ ] Error pages
|
|
- [ ] Caché estático
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 8: Verificación
|
|
|
|
```bash
|
|
# 1. Permisos
|
|
ls -la /home/admin/html/dashboard/
|
|
ls -la /home/admin/html/dashboard/data/
|
|
|
|
# 2. Nginx
|
|
sudo nginx -t
|
|
sudo systemctl status nginx
|
|
|
|
# 3. PHP-FPM
|
|
sudo systemctl status php*-fpm
|
|
|
|
# 4. Test htpasswd
|
|
htpasswd -v /etc/nginx/.htpasswd_sftp alumno01
|
|
|
|
# 5. Test curl
|
|
curl -u alumno01:alumno01 http://localhost/dashboard/api.php
|
|
```
|
|
|
|
**✅ Debe retornar:**
|
|
```markdown
|
|
# Seguimiento - Grupo 2026
|
|
|
|
## alumno01
|
|
| Tarea | Estado | Fecha |
|
|
...
|
|
```
|
|
|
|
---
|
|
|
|
## Paso 9: Test en navegador
|
|
|
|
1. Abrir: `http://dashboard.qu3v3d0.tech`
|
|
2. Login: `alumno01` / `alumno01`
|
|
3. Debe mostrar solo la sección de alumno01
|
|
4. Probar con alumno02, alumno03...
|
|
|
|
---
|
|
|
|
## Uso para el Profesor
|
|
|
|
### Actualizar tabla via SFTP
|
|
|
|
```
|
|
Host: qu3v3d0.tech
|
|
User: admin
|
|
Password: ****
|
|
Path: /html/dashboard/data/
|
|
File: tabla-de-seguimiento.md
|
|
```
|
|
|
|
### Actualizar desde servidor
|
|
|
|
```bash
|
|
ssh admin@qu3v3d0.tech
|
|
nano ~/html/dashboard/data/tabla-de-seguimiento.md
|
|
# Editar y guardar
|
|
```
|
|
|
|
### Formato de la tabla
|
|
|
|
**Emojis de estado:**
|
|
- ✅ Completado
|
|
- ❌ Pendiente
|
|
- ⏳ En progreso
|
|
|
|
**Ejemplo:**
|
|
```markdown
|
|
## alumno01
|
|
| Tarea | Estado | Fecha |
|
|
|:------|:------:|------:|
|
|
| Config nginx básica | ✅ | 2026-01-20 |
|
|
| JSON con CORS | ⏳ | - |
|
|
| Error pages | ❌ | - |
|
|
|
|
**Observaciones:** Buen progreso, revisar CORS.
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Error 401 Unauthorized
|
|
|
|
```bash
|
|
# Verificar htpasswd
|
|
ls -la /etc/nginx/.htpasswd_sftp
|
|
grep alumno01 /etc/nginx/.htpasswd_sftp
|
|
htpasswd -v /etc/nginx/.htpasswd_sftp alumno01
|
|
```
|
|
|
|
### Error 403 Forbidden
|
|
|
|
```bash
|
|
# Verificar permisos
|
|
ls -la /home/admin/html/dashboard/
|
|
ps aux | grep nginx # Usuario debe ser www-data
|
|
```
|
|
|
|
### PHP no procesa
|
|
|
|
```bash
|
|
# Verificar PHP-FPM
|
|
sudo systemctl status php*-fpm
|
|
ls -la /var/run/php/php*-fpm.sock
|
|
sudo tail -f /var/log/nginx/dashboard-error.log
|
|
```
|
|
|
|
### Página en blanco
|
|
|
|
```bash
|
|
# Test API directamente
|
|
curl -u alumno01:alumno01 http://dashboard.qu3v3d0.tech/api.php
|
|
|
|
# Ver consola navegador (F12)
|
|
# Verificar que marked.js carga correctamente
|
|
```
|
|
|
|
### Usuario ve datos de otros
|
|
|
|
```bash
|
|
# En api.php, agregar debug temporal:
|
|
echo "DEBUG: User = $user\n";
|
|
|
|
# Verificar que ## username coincide exactamente
|
|
# (sin espacios extras, case-sensitive)
|
|
```
|
|
|
|
---
|
|
|
|
## Extensión: Notificación XMPP
|
|
|
|
Notificar cuando profesor actualiza la tabla:
|
|
|
|
```bash
|
|
# Script watcher
|
|
sudo tee /usr/local/bin/dashboard-notify.sh > /dev/null << 'EOF'
|
|
#!/bin/bash
|
|
WATCH_FILE="/home/admin/html/dashboard/data/tabla-de-seguimiento.md"
|
|
inotifywait -m -e close_write "$WATCH_FILE" | while read event; do
|
|
/usr/local/bin/xmpp-notify.py "📊 Tabla actualizada por el profesor"
|
|
done
|
|
EOF
|
|
|
|
sudo chmod +x /usr/local/bin/dashboard-notify.sh
|
|
|
|
# Servicio systemd
|
|
sudo tee /etc/systemd/system/dashboard-notify.service > /dev/null << 'EOF'
|
|
[Unit]
|
|
Description=Dashboard Update Notifier
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/local/bin/dashboard-notify.sh
|
|
Restart=always
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now dashboard-notify.service
|
|
```
|
|
|
|
---
|
|
|
|
## Checklist de Implementación
|
|
|
|
- [ ] htpasswd creado (`/etc/nginx/.htpasswd_sftp`)
|
|
- [ ] Directorios creados (`/home/admin/html/dashboard/`)
|
|
- [ ] api.php desplegado (644)
|
|
- [ ] index.html desplegado (644)
|
|
- [ ] style.css desplegado (644)
|
|
- [ ] nginx config creado (`/etc/nginx/sites-available/dashboard`)
|
|
- [ ] nginx config activado (symlink a sites-enabled)
|
|
- [ ] nginx reload sin errores (`nginx -t`)
|
|
- [ ] tabla-de-seguimiento.md inicial (664)
|
|
- [ ] PHP-FPM corriendo
|
|
- [ ] Test curl exitoso
|
|
- [ ] Test navegador alumno01 OK
|
|
- [ ] Test navegador alumno02 OK
|
|
- [ ] Cada alumno ve solo sus datos
|
|
- [ ] Markdown renderiza correctamente
|
|
- [ ] Responsive en móvil OK
|
|
|
|
---
|
|
|
|
## Resumen
|
|
|
|
**Componentes:**
|
|
- nginx: Auth + routing
|
|
- PHP: Filtrado por usuario
|
|
- Markdown: Storage
|
|
- marked.js: Parsing
|
|
- Vanilla JS: Cliente
|
|
|
|
**Filosofía KISS:**
|
|
- 3 archivos principales
|
|
- Sin DB
|
|
- Sin frameworks
|
|
- Sin compilación
|
|
|
|
**Resultado:** Sistema robusto, mantenible, educativo.
|
|
|
|
---
|
|
|
|
*Implementado: 2026-02-13 | qu3v3d0.tech | fenix + Claude*
|